@@ -89,8 +89,8 @@ export default createTestingLibraryRule<Options, MessageIds>({
8989 // Track variables assigned from userEvent.setup() (directly or via destructuring)
9090 const userEventSetupVars = new Set < string > ( ) ;
9191
92- // Temporary: Map function names to property names that are assigned from userEvent.setup()
93- const tempSetupFunctionProps = new Map < string , Set < string > > ( ) ;
92+ // Track functions that return userEvent.setup() instances and their property names
93+ const setupFunctions = new Map < string , Set < string > > ( ) ;
9494
9595 function reportUnhandledNode ( {
9696 node,
@@ -121,6 +121,32 @@ export default createTestingLibraryRule<Options, MessageIds>({
121121 }
122122 }
123123
124+ function isUserEventSetupCall ( node : TSESTree . Node ) : boolean {
125+ return (
126+ node . type === AST_NODE_TYPES . CallExpression &&
127+ node . callee . type === AST_NODE_TYPES . MemberExpression &&
128+ node . callee . object . type === AST_NODE_TYPES . Identifier &&
129+ node . callee . object . name === USER_EVENT_NAME &&
130+ node . callee . property . type === AST_NODE_TYPES . Identifier &&
131+ node . callee . property . name === USER_EVENT_SETUP_FUNCTION_NAME
132+ ) ;
133+ }
134+
135+ function findFunctionName ( node : TSESTree . Node ) : string | null {
136+ let current : TSESTree . Node | undefined = node ;
137+ while ( current ) {
138+ if (
139+ current . type === AST_NODE_TYPES . FunctionDeclaration ||
140+ current . type === AST_NODE_TYPES . FunctionExpression ||
141+ current . type === AST_NODE_TYPES . ArrowFunctionExpression
142+ ) {
143+ return getFunctionName ( current ) ;
144+ }
145+ current = current . parent ;
146+ }
147+ return null ;
148+ }
149+
124150 const eventModules =
125151 typeof options . eventModule === 'string'
126152 ? [ options . eventModule ]
@@ -131,39 +157,37 @@ export default createTestingLibraryRule<Options, MessageIds>({
131157 return {
132158 // Track variables assigned from userEvent.setup() and destructuring from setup functions
133159 VariableDeclarator ( node : TSESTree . VariableDeclarator ) {
160+ if ( ! isUserEventEnabled ) return ;
161+
134162 // Direct assignment: const user = userEvent.setup();
135163 if (
136- isUserEventEnabled &&
137164 node . init &&
138- node . init . type === AST_NODE_TYPES . CallExpression &&
139- node . init . callee . type === AST_NODE_TYPES . MemberExpression &&
140- node . init . callee . object . type === AST_NODE_TYPES . Identifier &&
141- node . init . callee . object . name === USER_EVENT_NAME &&
142- node . init . callee . property . type === AST_NODE_TYPES . Identifier &&
143- node . init . callee . property . name === USER_EVENT_SETUP_FUNCTION_NAME &&
165+ isUserEventSetupCall ( node . init ) &&
144166 node . id . type === AST_NODE_TYPES . Identifier
145167 ) {
146168 userEventSetupVars . add ( node . id . name ) ;
147169 }
148170
149171 // Destructuring: const { user, myUser: alias } = setup(...)
150172 if (
151- isUserEventEnabled &&
152173 node . id . type === AST_NODE_TYPES . ObjectPattern &&
153174 node . init &&
154175 node . init . type === AST_NODE_TYPES . CallExpression &&
155- node . init . callee . type === AST_NODE_TYPES . Identifier &&
156- tempSetupFunctionProps . has ( node . init . callee . name )
176+ node . init . callee . type === AST_NODE_TYPES . Identifier
157177 ) {
158- const setupProps = tempSetupFunctionProps . get ( node . init . callee . name ) ! ;
159- for ( const prop of node . id . properties ) {
160- if (
161- prop . type === AST_NODE_TYPES . Property &&
162- prop . key . type === AST_NODE_TYPES . Identifier &&
163- setupProps . has ( prop . key . name ) &&
164- prop . value . type === AST_NODE_TYPES . Identifier
165- ) {
166- userEventSetupVars . add ( prop . value . name ) ;
178+ const functionName = node . init . callee . name ;
179+ const setupProps = setupFunctions . get ( functionName ) ;
180+
181+ if ( setupProps ) {
182+ for ( const prop of node . id . properties ) {
183+ if (
184+ prop . type === AST_NODE_TYPES . Property &&
185+ prop . key . type === AST_NODE_TYPES . Identifier &&
186+ setupProps . has ( prop . key . name ) &&
187+ prop . value . type === AST_NODE_TYPES . Identifier
188+ ) {
189+ userEventSetupVars . add ( prop . value . name ) ;
190+ }
167191 }
168192 }
169193 }
@@ -172,54 +196,37 @@ export default createTestingLibraryRule<Options, MessageIds>({
172196 // Track functions that return { ...: userEvent.setup(), ... }
173197 ReturnStatement ( node : TSESTree . ReturnStatement ) {
174198 if (
175- isUserEventEnabled &&
176- node . argument &&
177- node . argument . type = == AST_NODE_TYPES . ObjectExpression
199+ ! isUserEventEnabled ||
200+ ! node . argument ||
201+ node . argument . type ! == AST_NODE_TYPES . ObjectExpression
178202 ) {
179- const setupProps = new Set < string > ( ) ;
180- for ( const prop of node . argument . properties ) {
181- if (
182- prop . type === AST_NODE_TYPES . Property &&
183- prop . key . type === AST_NODE_TYPES . Identifier
203+ return ;
204+ }
205+
206+ const setupProps = new Set < string > ( ) ;
207+ for ( const prop of node . argument . properties ) {
208+ if (
209+ prop . type === AST_NODE_TYPES . Property &&
210+ prop . key . type === AST_NODE_TYPES . Identifier
211+ ) {
212+ // Direct: foo: userEvent.setup()
213+ if ( isUserEventSetupCall ( prop . value ) ) {
214+ setupProps . add ( prop . key . name ) ;
215+ }
216+ // Indirect: foo: u, where u is a userEvent.setup() var
217+ else if (
218+ prop . value . type === AST_NODE_TYPES . Identifier &&
219+ userEventSetupVars . has ( prop . value . name )
184220 ) {
185- // Direct: foo: userEvent.setup()
186- if (
187- prop . value . type === AST_NODE_TYPES . CallExpression &&
188- prop . value . callee . type === AST_NODE_TYPES . MemberExpression &&
189- prop . value . callee . object . type === AST_NODE_TYPES . Identifier &&
190- prop . value . callee . object . name === USER_EVENT_NAME &&
191- prop . value . callee . property . type === AST_NODE_TYPES . Identifier &&
192- prop . value . callee . property . name ===
193- USER_EVENT_SETUP_FUNCTION_NAME
194- ) {
195- setupProps . add ( prop . key . name ) ;
196- }
197- // Indirect: foo: u, where u is a userEvent.setup() var
198- else if (
199- prop . value . type === AST_NODE_TYPES . Identifier &&
200- userEventSetupVars . has ( prop . value . name )
201- ) {
202- setupProps . add ( prop . key . name ) ;
203- }
221+ setupProps . add ( prop . key . name ) ;
204222 }
205223 }
206- if ( setupProps . size > 0 ) {
207- // Find the function this return is in
208- let parent : TSESTree . Node | undefined = node . parent ;
209- while ( parent ) {
210- if (
211- parent . type === AST_NODE_TYPES . FunctionDeclaration ||
212- parent . type === AST_NODE_TYPES . FunctionExpression ||
213- parent . type === AST_NODE_TYPES . ArrowFunctionExpression
214- ) {
215- const name = getFunctionName ( parent ) ;
216- if ( name ) {
217- tempSetupFunctionProps . set ( name , setupProps ) ;
218- }
219- break ;
220- }
221- parent = parent . parent ;
222- }
224+ }
225+
226+ if ( setupProps . size > 0 ) {
227+ const functionName = findFunctionName ( node ) ;
228+ if ( functionName ) {
229+ setupFunctions . set ( functionName , setupProps ) ;
223230 }
224231 }
225232 } ,
0 commit comments