1- import { ASTUtils } from '@typescript-eslint/utils' ;
2-
31import { createTestingLibraryRule } from '../create-testing-library-rule' ;
42import {
53 findClosestCallNode ,
@@ -21,52 +19,6 @@ export type Options = [];
2119
2220const NEGATED_ABSENCE_MATCHERS = [ 'toBeVisible' ] ;
2321
24- function isNestedFunction ( node : TSESTree . Node ) : boolean {
25- return (
26- isArrowFunctionExpression ( node ) ||
27- isFunctionExpression ( node ) ||
28- isFunctionDeclaration ( node )
29- ) ;
30- }
31-
32- function containsNode (
33- node : TSESTree . Node ,
34- predicate : ( n : TSESTree . Node ) => boolean
35- ) : boolean {
36- if ( predicate ( node ) ) {
37- return true ;
38- }
39-
40- if ( isNestedFunction ( node ) ) {
41- return false ;
42- }
43-
44- for ( const key of Object . keys ( node ) ) {
45- if ( key === 'parent' ) continue ;
46- const child = ( node as unknown as Record < string , unknown > ) [ key ] ;
47- if ( child && typeof child === 'object' ) {
48- if ( Array . isArray ( child ) ) {
49- for ( const item of child ) {
50- if (
51- item &&
52- typeof item === 'object' &&
53- 'type' in item &&
54- containsNode ( item as TSESTree . Node , predicate )
55- ) {
56- return true ;
57- }
58- }
59- } else if (
60- 'type' in child &&
61- containsNode ( child as TSESTree . Node , predicate )
62- ) {
63- return true ;
64- }
65- }
66- }
67- return false ;
68- }
69-
7022export default createTestingLibraryRule < Options , MessageIds > ( {
7123 name : RULE_NAME ,
7224 meta : {
@@ -93,6 +45,13 @@ export default createTestingLibraryRule<Options, MessageIds>({
9345 defaultOptions : [ ] ,
9446
9547 create ( context , _ , helpers ) {
48+ const earliestSettlingIndex = new WeakMap < TSESTree . Statement [ ] , number > ( ) ;
49+ const candidates : Array < {
50+ identifier : TSESTree . Identifier ;
51+ body : TSESTree . Statement [ ] ;
52+ stmtIndex : number ;
53+ } > = [ ] ;
54+
9655 function isAbsenceAssertion ( node : TSESTree . MemberExpression ) : boolean {
9756 if ( helpers . isAbsenceAssert ( node ) ) {
9857 return true ;
@@ -148,6 +107,24 @@ export default createTestingLibraryRule<Options, MessageIds>({
148107 return null ;
149108 }
150109
110+ function findImmediateFunctionBody (
111+ node : TSESTree . Node
112+ ) : TSESTree . Statement [ ] | null {
113+ let current : TSESTree . Node | undefined = node . parent ;
114+
115+ while ( current ) {
116+ if (
117+ isArrowFunctionExpression ( current ) ||
118+ isFunctionExpression ( current ) ||
119+ isFunctionDeclaration ( current )
120+ ) {
121+ return isBlockStatement ( current . body ) ? current . body . body : null ;
122+ }
123+ current = current . parent ;
124+ }
125+ return null ;
126+ }
127+
151128 function findAncestorStatement (
152129 node : TSESTree . Node ,
153130 statements : TSESTree . Statement [ ]
@@ -162,19 +139,28 @@ export default createTestingLibraryRule<Options, MessageIds>({
162139 return null ;
163140 }
164141
165- function hasSettlingExpression ( statement : TSESTree . Statement ) : boolean {
166- const hasAwait = containsNode ( statement , ( n ) =>
167- ASTUtils . isAwaitExpression ( n )
168- ) ;
169- const hasGetQuery = containsNode (
170- statement ,
171- ( n ) => ASTUtils . isIdentifier ( n ) && helpers . isGetQueryVariant ( n )
172- ) ;
173- return hasAwait || hasGetQuery ;
142+ function recordSettling ( node : TSESTree . Node ) : void {
143+ const body = findImmediateFunctionBody ( node ) ;
144+ if ( ! body ) return ;
145+ const stmt = findAncestorStatement ( node , body ) ;
146+ if ( ! stmt ) return ;
147+ const stmtIndex = body . indexOf ( stmt ) ;
148+ const prev = earliestSettlingIndex . get ( body ) ;
149+ if ( prev === undefined || stmtIndex < prev ) {
150+ earliestSettlingIndex . set ( body , stmtIndex ) ;
151+ }
174152 }
175153
176154 return {
155+ AwaitExpression ( node : TSESTree . AwaitExpression ) {
156+ recordSettling ( node ) ;
157+ } ,
177158 'CallExpression Identifier' ( node : TSESTree . Identifier ) {
159+ if ( helpers . isGetQueryVariant ( node ) ) {
160+ recordSettling ( node ) ;
161+ return ;
162+ }
163+
178164 if ( ! helpers . isQueryQueryVariant ( node ) ) {
179165 return ;
180166 }
@@ -200,26 +186,34 @@ export default createTestingLibraryRule<Options, MessageIds>({
200186 return ;
201187 }
202188
203- const functionBody = findEnclosingFunctionBody ( node ) ;
204- if ( ! functionBody ) {
189+ const body = findEnclosingFunctionBody ( node ) ;
190+ if ( ! body ) {
205191 return ;
206192 }
207193
208- const containingStatement = findAncestorStatement ( node , functionBody ) ;
209- if ( ! containingStatement ) {
194+ const stmt = findAncestorStatement ( node , body ) ;
195+ if ( ! stmt ) {
210196 return ;
211197 }
212198
213- const stmtIndex = functionBody . indexOf ( containingStatement ) ;
214- const precedingStatements = functionBody . slice ( 0 , stmtIndex ) ;
215- const hasSettled = precedingStatements . some ( hasSettlingExpression ) ;
216-
217- if ( ! hasSettled ) {
218- context . report ( {
219- node,
220- messageId : 'noUnsettledAbsenceQuery' ,
221- data : { queryMethod : node . name } ,
222- } ) ;
199+ candidates . push ( {
200+ identifier : node ,
201+ body,
202+ stmtIndex : body . indexOf ( stmt ) ,
203+ } ) ;
204+ } ,
205+ 'Program:exit' ( ) {
206+ for ( const candidate of candidates ) {
207+ const earliest = earliestSettlingIndex . get ( candidate . body ) ;
208+ const settled =
209+ earliest !== undefined && earliest < candidate . stmtIndex ;
210+ if ( ! settled ) {
211+ context . report ( {
212+ node : candidate . identifier ,
213+ messageId : 'noUnsettledAbsenceQuery' ,
214+ data : { queryMethod : candidate . identifier . name } ,
215+ } ) ;
216+ }
223217 }
224218 } ,
225219 } ;
0 commit comments