55} from '@typescript-eslint/experimental-utils' ;
66import {
77 getAssertNodeInfo ,
8- getIdentifierNode ,
98 getImportModuleName ,
9+ getPropertyIdentifierNode ,
10+ getReferenceNode ,
1011 ImportModuleNode ,
1112 isImportDeclaration ,
1213 isImportNamespaceSpecifier ,
@@ -23,7 +24,7 @@ import {
2324} from './utils' ;
2425
2526export type TestingLibrarySettings = {
26- 'testing-library/module' ?: string ;
27+ 'testing-library/utils- module' ?: string ;
2728 'testing-library/filename-pattern' ?: string ;
2829 'testing-library/custom-renders' ?: string [ ] ;
2930} ;
@@ -47,32 +48,54 @@ export type EnhancedRuleCreate<
4748 detectionHelpers : Readonly < DetectionHelpers >
4849) => TRuleListener ;
4950
50- export type DetectionHelpers = {
51- getTestingLibraryImportNode : ( ) => ImportModuleNode | null ;
52- getCustomModuleImportNode : ( ) => ImportModuleNode | null ;
53- getTestingLibraryImportName : ( ) => string | undefined ;
54- getCustomModuleImportName : ( ) => string | undefined ;
55- isTestingLibraryImported : ( ) => boolean ;
56- isValidFilename : ( ) => boolean ;
57- isGetQueryVariant : ( node : TSESTree . Identifier ) => boolean ;
58- isQueryQueryVariant : ( node : TSESTree . Identifier ) => boolean ;
59- isFindQueryVariant : ( node : TSESTree . Identifier ) => boolean ;
60- isSyncQuery : ( node : TSESTree . Identifier ) => boolean ;
61- isAsyncQuery : ( node : TSESTree . Identifier ) => boolean ;
62- isCustomQuery : ( node : TSESTree . Identifier ) => boolean ;
63- isAsyncUtil : ( node : TSESTree . Identifier ) => boolean ;
64- isFireEventMethod : ( node : TSESTree . Identifier ) => boolean ;
65- isRenderUtil : ( node : TSESTree . Node ) => boolean ;
66- isPresenceAssert : ( node : TSESTree . MemberExpression ) => boolean ;
67- isAbsenceAssert : ( node : TSESTree . MemberExpression ) => boolean ;
68- canReportErrors : ( ) => boolean ;
69- findImportedUtilSpecifier : (
70- specifierName : string
71- ) => TSESTree . ImportClause | TSESTree . Identifier | undefined ;
72- isNodeComingFromTestingLibrary : (
73- node : TSESTree . MemberExpression | TSESTree . Identifier
74- ) => boolean ;
75- } ;
51+ // Helpers methods
52+ type GetTestingLibraryImportNodeFn = ( ) => ImportModuleNode | null ;
53+ type GetCustomModuleImportNodeFn = ( ) => ImportModuleNode | null ;
54+ type GetTestingLibraryImportNameFn = ( ) => string | undefined ;
55+ type GetCustomModuleImportNameFn = ( ) => string | undefined ;
56+ type IsTestingLibraryImportedFn = ( ) => boolean ;
57+ type IsValidFilenameFn = ( ) => boolean ;
58+ type IsGetQueryVariantFn = ( node : TSESTree . Identifier ) => boolean ;
59+ type IsQueryQueryVariantFn = ( node : TSESTree . Identifier ) => boolean ;
60+ type IsFindQueryVariantFn = ( node : TSESTree . Identifier ) => boolean ;
61+ type IsSyncQueryFn = ( node : TSESTree . Identifier ) => boolean ;
62+ type IsAsyncQueryFn = ( node : TSESTree . Identifier ) => boolean ;
63+ type IsCustomQueryFn = ( node : TSESTree . Identifier ) => boolean ;
64+ type IsAsyncUtilFn = ( node : TSESTree . Identifier ) => boolean ;
65+ type IsFireEventMethodFn = ( node : TSESTree . Identifier ) => boolean ;
66+ type IsRenderUtilFn = ( node : TSESTree . Identifier ) => boolean ;
67+ type IsPresenceAssertFn = ( node : TSESTree . MemberExpression ) => boolean ;
68+ type IsAbsenceAssertFn = ( node : TSESTree . MemberExpression ) => boolean ;
69+ type CanReportErrorsFn = ( ) => boolean ;
70+ type FindImportedUtilSpecifierFn = (
71+ specifierName : string
72+ ) => TSESTree . ImportClause | TSESTree . Identifier | undefined ;
73+ type IsNodeComingFromTestingLibraryFn = (
74+ node : TSESTree . MemberExpression | TSESTree . Identifier
75+ ) => boolean ;
76+
77+ export interface DetectionHelpers {
78+ getTestingLibraryImportNode : GetTestingLibraryImportNodeFn ;
79+ getCustomModuleImportNode : GetCustomModuleImportNodeFn ;
80+ getTestingLibraryImportName : GetTestingLibraryImportNameFn ;
81+ getCustomModuleImportName : GetCustomModuleImportNameFn ;
82+ isTestingLibraryImported : IsTestingLibraryImportedFn ;
83+ isValidFilename : IsValidFilenameFn ;
84+ isGetQueryVariant : IsGetQueryVariantFn ;
85+ isQueryQueryVariant : IsQueryQueryVariantFn ;
86+ isFindQueryVariant : IsFindQueryVariantFn ;
87+ isSyncQuery : IsSyncQueryFn ;
88+ isAsyncQuery : IsAsyncQueryFn ;
89+ isCustomQuery : IsCustomQueryFn ;
90+ isAsyncUtil : IsAsyncUtilFn ;
91+ isFireEventMethod : IsFireEventMethodFn ;
92+ isRenderUtil : IsRenderUtilFn ;
93+ isPresenceAssert : IsPresenceAssertFn ;
94+ isAbsenceAssert : IsAbsenceAssertFn ;
95+ canReportErrors : CanReportErrorsFn ;
96+ findImportedUtilSpecifier : FindImportedUtilSpecifierFn ;
97+ isNodeComingFromTestingLibrary : IsNodeComingFromTestingLibraryFn ;
98+ }
7699
77100const DEFAULT_FILENAME_PATTERN = '^.*\\.(test|spec)\\.[jt]sx?$' ;
78101
@@ -95,12 +118,33 @@ export function detectTestingLibraryUtils<
95118 let importedCustomModuleNode : ImportModuleNode | null = null ;
96119
97120 // Init options based on shared ESLint settings
98- const customModule = context . settings [ 'testing-library/module' ] ;
121+ const customModule = context . settings [ 'testing-library/utils- module' ] ;
99122 const filenamePattern =
100123 context . settings [ 'testing-library/filename-pattern' ] ??
101124 DEFAULT_FILENAME_PATTERN ;
102125 const customRenders = context . settings [ 'testing-library/custom-renders' ] ;
103126
127+ /**
128+ * Small method to extract common checks to determine whether a node is
129+ * related to Testing Library or not.
130+ */
131+ function isTestingLibraryUtil (
132+ node : TSESTree . Identifier ,
133+ isUtilCallback : ( identifierNode : TSESTree . Identifier ) => boolean
134+ ) : boolean {
135+ if ( ! isUtilCallback ( node ) ) {
136+ return false ;
137+ }
138+
139+ const referenceNode = getReferenceNode ( node ) ;
140+ const referenceNodeIdentifier = getPropertyIdentifierNode ( referenceNode ) ;
141+
142+ return (
143+ isAggressiveModuleReportingEnabled ( ) ||
144+ isNodeComingFromTestingLibrary ( referenceNodeIdentifier )
145+ ) ;
146+ }
147+
104148 /**
105149 * Determines whether aggressive module reporting is enabled or not.
106150 *
@@ -126,21 +170,22 @@ export function detectTestingLibraryUtils<
126170 ! Array . isArray ( customRenders ) || customRenders . length === 0 ;
127171
128172 // Helpers for Testing Library detection.
129- const getTestingLibraryImportNode : DetectionHelpers [ 'getTestingLibraryImportNode' ] = ( ) => {
173+ const getTestingLibraryImportNode : GetTestingLibraryImportNodeFn = ( ) => {
130174 return importedTestingLibraryNode ;
131175 } ;
132176
133- const getCustomModuleImportNode : DetectionHelpers [ 'getCustomModuleImportNode' ] = ( ) => {
177+ const getCustomModuleImportNode : GetCustomModuleImportNodeFn = ( ) => {
134178 return importedCustomModuleNode ;
135179 } ;
136180
137- const getTestingLibraryImportName : DetectionHelpers [ 'getTestingLibraryImportName' ] = ( ) => {
181+ const getTestingLibraryImportName : GetTestingLibraryImportNameFn = ( ) => {
138182 return getImportModuleName ( importedTestingLibraryNode ) ;
139183 } ;
140184
141- const getCustomModuleImportName : DetectionHelpers [ 'getCustomModuleImportName' ] = ( ) => {
185+ const getCustomModuleImportName : GetCustomModuleImportNameFn = ( ) => {
142186 return getImportModuleName ( importedCustomModuleNode ) ;
143187 } ;
188+
144189 /**
145190 * Determines whether Testing Library utils are imported or not for
146191 * current file being analyzed.
@@ -150,84 +195,92 @@ export function detectTestingLibraryUtils<
150195 * custom modules.
151196 *
152197 * However, there is a setting to customize the module where TL utils can
153- * be imported from: "testing-library/module". If this setting is enabled,
198+ * be imported from: "testing-library/utils- module". If this setting is enabled,
154199 * then this method will return `true` ONLY IF a testing-library package
155200 * or custom module are imported.
156201 */
157- const isTestingLibraryImported : DetectionHelpers [ 'isTestingLibraryImported' ] = ( ) => {
158- if ( isAggressiveModuleReportingEnabled ( ) ) {
159- return true ;
160- }
161-
162- return ! ! importedTestingLibraryNode || ! ! importedCustomModuleNode ;
202+ const isTestingLibraryImported : IsTestingLibraryImportedFn = ( ) => {
203+ return (
204+ isAggressiveModuleReportingEnabled ( ) ||
205+ ! ! importedTestingLibraryNode ||
206+ ! ! importedCustomModuleNode
207+ ) ;
163208 } ;
164209
165210 /**
166211 * Determines whether filename is valid or not for current file
167212 * being analyzed based on "testing-library/filename-pattern" setting.
168213 */
169- const isValidFilename : DetectionHelpers [ 'isValidFilename' ] = ( ) => {
214+ const isValidFilename : IsValidFilenameFn = ( ) => {
170215 const fileName = context . getFilename ( ) ;
171216 return ! ! fileName . match ( filenamePattern ) ;
172217 } ;
173218
174219 /**
175220 * Determines whether a given node is `get*` query variant or not.
176221 */
177- const isGetQueryVariant : DetectionHelpers [ 'isGetQueryVariant' ] = ( node ) => {
222+ const isGetQueryVariant : IsGetQueryVariantFn = ( node ) => {
178223 return / ^ g e t ( A l l ) ? B y .+ $ / . test ( node . name ) ;
179224 } ;
180225
181226 /**
182227 * Determines whether a given node is `query*` query variant or not.
183228 */
184- const isQueryQueryVariant : DetectionHelpers [ 'isQueryQueryVariant' ] = (
185- node
186- ) => {
229+ const isQueryQueryVariant : IsQueryQueryVariantFn = ( node ) => {
187230 return / ^ q u e r y ( A l l ) ? B y .+ $ / . test ( node . name ) ;
188231 } ;
189232
190233 /**
191234 * Determines whether a given node is `find*` query variant or not.
192235 */
193- const isFindQueryVariant : DetectionHelpers [ 'isFindQueryVariant' ] = (
194- node
195- ) => {
236+ const isFindQueryVariant : IsFindQueryVariantFn = ( node ) => {
196237 return / ^ f i n d ( A l l ) ? B y .+ $ / . test ( node . name ) ;
197238 } ;
198239
199240 /**
200241 * Determines whether a given node is sync query or not.
201242 */
202- const isSyncQuery : DetectionHelpers [ 'isSyncQuery' ] = ( node ) => {
243+ const isSyncQuery : IsSyncQueryFn = ( node ) => {
203244 return isGetQueryVariant ( node ) || isQueryQueryVariant ( node ) ;
204245 } ;
205246
206247 /**
207248 * Determines whether a given node is async query or not.
208249 */
209- const isAsyncQuery : DetectionHelpers [ 'isAsyncQuery' ] = ( node ) => {
250+ const isAsyncQuery : IsAsyncQueryFn = ( node ) => {
210251 return isFindQueryVariant ( node ) ;
211252 } ;
212253
213- const isCustomQuery : DetectionHelpers [ 'isCustomQuery' ] = ( node ) => {
254+ const isCustomQuery : IsCustomQueryFn = ( node ) => {
214255 return (
215256 ( isSyncQuery ( node ) || isAsyncQuery ( node ) ) &&
216257 ! ALL_QUERIES_COMBINATIONS . includes ( node . name )
217258 ) ;
218259 } ;
219260
220261 /**
221- * Determines whether a given node is async util or not.
262+ * Determines whether a given node is a valid async util or not.
263+ *
264+ * A node will be interpreted as a valid async util based on two conditions:
265+ * the name matches with some Testing Library async util, and the node is
266+ * coming from Testing Library module.
267+ *
268+ * The latter depends on Aggressive module reporting:
269+ * if enabled, then it doesn't matter from where the given node was imported
270+ * from as it will be considered part of Testing Library.
271+ * Otherwise, it means `custom-module` has been set up, so only those nodes
272+ * coming from Testing Library will be considered as valid.
222273 */
223- const isAsyncUtil : DetectionHelpers [ 'isAsyncUtil' ] = ( node ) => {
224- return ASYNC_UTILS . includes ( node . name ) ;
274+ const isAsyncUtil : IsAsyncUtilFn = ( node ) => {
275+ return isTestingLibraryUtil ( node , ( identifierNode ) =>
276+ ASYNC_UTILS . includes ( identifierNode . name )
277+ ) ;
225278 } ;
226279
227280 /**
228281 * Determines whether a given node is fireEvent method or not
229282 */
230- const isFireEventMethod : DetectionHelpers [ 'isFireEventMethod' ] = ( node ) => {
283+ const isFireEventMethod : IsFireEventMethodFn = ( node ) => {
231284 const fireEventUtil = findImportedUtilSpecifier ( FIRE_EVENT_NAME ) ;
232285 let fireEventUtilName : string | undefined ;
233286
@@ -293,29 +346,14 @@ export function detectTestingLibraryUtils<
293346 * Testing Library. Otherwise, it means `custom-module` has been set up, so
294347 * only those nodes coming from Testing Library will be considered as valid.
295348 */
296- const isRenderUtil : DetectionHelpers [ 'isRenderUtil' ] = ( node ) => {
297- const identifier = getIdentifierNode ( node ) ;
298-
299- if ( ! identifier ) {
300- return false ;
301- }
302-
303- const isNameMatching = ( function ( ) {
349+ const isRenderUtil : IsRenderUtilFn = ( node ) => {
350+ return isTestingLibraryUtil ( node , ( identifierNode ) => {
304351 if ( isAggressiveRenderReportingEnabled ( ) ) {
305- return identifier . name . toLowerCase ( ) . includes ( RENDER_NAME ) ;
352+ return identifierNode . name . toLowerCase ( ) . includes ( RENDER_NAME ) ;
306353 }
307354
308- return [ RENDER_NAME , ...customRenders ] . includes ( identifier . name ) ;
309- } ) ( ) ;
310-
311- if ( ! isNameMatching ) {
312- return false ;
313- }
314-
315- return (
316- isAggressiveModuleReportingEnabled ( ) ||
317- isNodeComingFromTestingLibrary ( identifier )
318- ) ;
355+ return [ RENDER_NAME , ...customRenders ] . includes ( identifierNode . name ) ;
356+ } ) ;
319357 } ;
320358
321359 /**
@@ -325,7 +363,7 @@ export function detectTestingLibraryUtils<
325363 * - expect(element).toBeInTheDocument()
326364 * - expect(element).not.toBeNull()
327365 */
328- const isPresenceAssert : DetectionHelpers [ 'isPresenceAssert' ] = ( node ) => {
366+ const isPresenceAssert : IsPresenceAssertFn = ( node ) => {
329367 const { matcher, isNegated } = getAssertNodeInfo ( node ) ;
330368
331369 if ( ! matcher ) {
@@ -344,7 +382,7 @@ export function detectTestingLibraryUtils<
344382 * - expect(element).toBeNull()
345383 * - expect(element).not.toBeInTheDocument()
346384 */
347- const isAbsenceAssert : DetectionHelpers [ 'isAbsenceAssert' ] = ( node ) => {
385+ const isAbsenceAssert : IsAbsenceAssertFn = ( node ) => {
348386 const { matcher, isNegated } = getAssertNodeInfo ( node ) ;
349387
350388 if ( ! matcher ) {
@@ -360,7 +398,7 @@ export function detectTestingLibraryUtils<
360398 * Gets a string and verifies if it was imported/required by Testing Library
361399 * related module.
362400 */
363- const findImportedUtilSpecifier : DetectionHelpers [ 'findImportedUtilSpecifier' ] = (
401+ const findImportedUtilSpecifier : FindImportedUtilSpecifierFn = (
364402 specifierName
365403 ) => {
366404 const node = getCustomModuleImportNode ( ) ?? getTestingLibraryImportNode ( ) ;
@@ -398,32 +436,23 @@ export function detectTestingLibraryUtils<
398436 /**
399437 * Determines if file inspected meets all conditions to be reported by rules or not.
400438 */
401- const canReportErrors : DetectionHelpers [ 'canReportErrors' ] = ( ) => {
439+ const canReportErrors : CanReportErrorsFn = ( ) => {
402440 return isTestingLibraryImported ( ) && isValidFilename ( ) ;
403441 } ;
404442 /**
405443 * Takes a MemberExpression or an Identifier and verifies if its name comes from the import in TL
406444 * @param node a MemberExpression (in "foo.property" it would be property) or an Identifier
407445 */
408- const isNodeComingFromTestingLibrary : DetectionHelpers [ 'isNodeComingFromTestingLibrary' ] = (
446+ const isNodeComingFromTestingLibrary : IsNodeComingFromTestingLibraryFn = (
409447 node
410448 ) => {
411- let identifierName : string | undefined ;
412-
413- if ( ASTUtils . isIdentifier ( node ) ) {
414- identifierName = node . name ;
415- } else if ( ASTUtils . isIdentifier ( node . object ) ) {
416- identifierName = node . object . name ;
417- }
418-
419- if ( ! identifierName ) {
420- return ;
421- }
449+ const identifierName : string | undefined = getPropertyIdentifierNode ( node )
450+ . name ;
422451
423452 return ! ! findImportedUtilSpecifier ( identifierName ) ;
424453 } ;
425454
426- const helpers = {
455+ const helpers : DetectionHelpers = {
427456 getTestingLibraryImportNode,
428457 getCustomModuleImportNode,
429458 getTestingLibraryImportName,
0 commit comments