5
5
} from '@typescript-eslint/experimental-utils' ;
6
6
import {
7
7
getAssertNodeInfo ,
8
- getIdentifierNode ,
9
8
getImportModuleName ,
9
+ getPropertyIdentifierNode ,
10
+ getReferenceNode ,
10
11
ImportModuleNode ,
11
12
isImportDeclaration ,
12
13
isImportNamespaceSpecifier ,
@@ -23,7 +24,7 @@ import {
23
24
} from './utils' ;
24
25
25
26
export type TestingLibrarySettings = {
26
- 'testing-library/module' ?: string ;
27
+ 'testing-library/utils- module' ?: string ;
27
28
'testing-library/filename-pattern' ?: string ;
28
29
'testing-library/custom-renders' ?: string [ ] ;
29
30
} ;
@@ -47,32 +48,54 @@ export type EnhancedRuleCreate<
47
48
detectionHelpers : Readonly < DetectionHelpers >
48
49
) => TRuleListener ;
49
50
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
+ }
76
99
77
100
const DEFAULT_FILENAME_PATTERN = '^.*\\.(test|spec)\\.[jt]sx?$' ;
78
101
@@ -95,12 +118,33 @@ export function detectTestingLibraryUtils<
95
118
let importedCustomModuleNode : ImportModuleNode | null = null ;
96
119
97
120
// Init options based on shared ESLint settings
98
- const customModule = context . settings [ 'testing-library/module' ] ;
121
+ const customModule = context . settings [ 'testing-library/utils- module' ] ;
99
122
const filenamePattern =
100
123
context . settings [ 'testing-library/filename-pattern' ] ??
101
124
DEFAULT_FILENAME_PATTERN ;
102
125
const customRenders = context . settings [ 'testing-library/custom-renders' ] ;
103
126
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
+
104
148
/**
105
149
* Determines whether aggressive module reporting is enabled or not.
106
150
*
@@ -126,21 +170,22 @@ export function detectTestingLibraryUtils<
126
170
! Array . isArray ( customRenders ) || customRenders . length === 0 ;
127
171
128
172
// Helpers for Testing Library detection.
129
- const getTestingLibraryImportNode : DetectionHelpers [ 'getTestingLibraryImportNode' ] = ( ) => {
173
+ const getTestingLibraryImportNode : GetTestingLibraryImportNodeFn = ( ) => {
130
174
return importedTestingLibraryNode ;
131
175
} ;
132
176
133
- const getCustomModuleImportNode : DetectionHelpers [ 'getCustomModuleImportNode' ] = ( ) => {
177
+ const getCustomModuleImportNode : GetCustomModuleImportNodeFn = ( ) => {
134
178
return importedCustomModuleNode ;
135
179
} ;
136
180
137
- const getTestingLibraryImportName : DetectionHelpers [ 'getTestingLibraryImportName' ] = ( ) => {
181
+ const getTestingLibraryImportName : GetTestingLibraryImportNameFn = ( ) => {
138
182
return getImportModuleName ( importedTestingLibraryNode ) ;
139
183
} ;
140
184
141
- const getCustomModuleImportName : DetectionHelpers [ 'getCustomModuleImportName' ] = ( ) => {
185
+ const getCustomModuleImportName : GetCustomModuleImportNameFn = ( ) => {
142
186
return getImportModuleName ( importedCustomModuleNode ) ;
143
187
} ;
188
+
144
189
/**
145
190
* Determines whether Testing Library utils are imported or not for
146
191
* current file being analyzed.
@@ -150,84 +195,92 @@ export function detectTestingLibraryUtils<
150
195
* custom modules.
151
196
*
152
197
* 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,
154
199
* then this method will return `true` ONLY IF a testing-library package
155
200
* or custom module are imported.
156
201
*/
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
+ ) ;
163
208
} ;
164
209
165
210
/**
166
211
* Determines whether filename is valid or not for current file
167
212
* being analyzed based on "testing-library/filename-pattern" setting.
168
213
*/
169
- const isValidFilename : DetectionHelpers [ 'isValidFilename' ] = ( ) => {
214
+ const isValidFilename : IsValidFilenameFn = ( ) => {
170
215
const fileName = context . getFilename ( ) ;
171
216
return ! ! fileName . match ( filenamePattern ) ;
172
217
} ;
173
218
174
219
/**
175
220
* Determines whether a given node is `get*` query variant or not.
176
221
*/
177
- const isGetQueryVariant : DetectionHelpers [ 'isGetQueryVariant' ] = ( node ) => {
222
+ const isGetQueryVariant : IsGetQueryVariantFn = ( node ) => {
178
223
return / ^ g e t ( A l l ) ? B y .+ $ / . test ( node . name ) ;
179
224
} ;
180
225
181
226
/**
182
227
* Determines whether a given node is `query*` query variant or not.
183
228
*/
184
- const isQueryQueryVariant : DetectionHelpers [ 'isQueryQueryVariant' ] = (
185
- node
186
- ) => {
229
+ const isQueryQueryVariant : IsQueryQueryVariantFn = ( node ) => {
187
230
return / ^ q u e r y ( A l l ) ? B y .+ $ / . test ( node . name ) ;
188
231
} ;
189
232
190
233
/**
191
234
* Determines whether a given node is `find*` query variant or not.
192
235
*/
193
- const isFindQueryVariant : DetectionHelpers [ 'isFindQueryVariant' ] = (
194
- node
195
- ) => {
236
+ const isFindQueryVariant : IsFindQueryVariantFn = ( node ) => {
196
237
return / ^ f i n d ( A l l ) ? B y .+ $ / . test ( node . name ) ;
197
238
} ;
198
239
199
240
/**
200
241
* Determines whether a given node is sync query or not.
201
242
*/
202
- const isSyncQuery : DetectionHelpers [ 'isSyncQuery' ] = ( node ) => {
243
+ const isSyncQuery : IsSyncQueryFn = ( node ) => {
203
244
return isGetQueryVariant ( node ) || isQueryQueryVariant ( node ) ;
204
245
} ;
205
246
206
247
/**
207
248
* Determines whether a given node is async query or not.
208
249
*/
209
- const isAsyncQuery : DetectionHelpers [ 'isAsyncQuery' ] = ( node ) => {
250
+ const isAsyncQuery : IsAsyncQueryFn = ( node ) => {
210
251
return isFindQueryVariant ( node ) ;
211
252
} ;
212
253
213
- const isCustomQuery : DetectionHelpers [ 'isCustomQuery' ] = ( node ) => {
254
+ const isCustomQuery : IsCustomQueryFn = ( node ) => {
214
255
return (
215
256
( isSyncQuery ( node ) || isAsyncQuery ( node ) ) &&
216
257
! ALL_QUERIES_COMBINATIONS . includes ( node . name )
217
258
) ;
218
259
} ;
219
260
220
261
/**
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.
222
273
*/
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
+ ) ;
225
278
} ;
226
279
227
280
/**
228
281
* Determines whether a given node is fireEvent method or not
229
282
*/
230
- const isFireEventMethod : DetectionHelpers [ 'isFireEventMethod' ] = ( node ) => {
283
+ const isFireEventMethod : IsFireEventMethodFn = ( node ) => {
231
284
const fireEventUtil = findImportedUtilSpecifier ( FIRE_EVENT_NAME ) ;
232
285
let fireEventUtilName : string | undefined ;
233
286
@@ -293,29 +346,14 @@ export function detectTestingLibraryUtils<
293
346
* Testing Library. Otherwise, it means `custom-module` has been set up, so
294
347
* only those nodes coming from Testing Library will be considered as valid.
295
348
*/
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 ) => {
304
351
if ( isAggressiveRenderReportingEnabled ( ) ) {
305
- return identifier . name . toLowerCase ( ) . includes ( RENDER_NAME ) ;
352
+ return identifierNode . name . toLowerCase ( ) . includes ( RENDER_NAME ) ;
306
353
}
307
354
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
+ } ) ;
319
357
} ;
320
358
321
359
/**
@@ -325,7 +363,7 @@ export function detectTestingLibraryUtils<
325
363
* - expect(element).toBeInTheDocument()
326
364
* - expect(element).not.toBeNull()
327
365
*/
328
- const isPresenceAssert : DetectionHelpers [ 'isPresenceAssert' ] = ( node ) => {
366
+ const isPresenceAssert : IsPresenceAssertFn = ( node ) => {
329
367
const { matcher, isNegated } = getAssertNodeInfo ( node ) ;
330
368
331
369
if ( ! matcher ) {
@@ -344,7 +382,7 @@ export function detectTestingLibraryUtils<
344
382
* - expect(element).toBeNull()
345
383
* - expect(element).not.toBeInTheDocument()
346
384
*/
347
- const isAbsenceAssert : DetectionHelpers [ 'isAbsenceAssert' ] = ( node ) => {
385
+ const isAbsenceAssert : IsAbsenceAssertFn = ( node ) => {
348
386
const { matcher, isNegated } = getAssertNodeInfo ( node ) ;
349
387
350
388
if ( ! matcher ) {
@@ -360,7 +398,7 @@ export function detectTestingLibraryUtils<
360
398
* Gets a string and verifies if it was imported/required by Testing Library
361
399
* related module.
362
400
*/
363
- const findImportedUtilSpecifier : DetectionHelpers [ 'findImportedUtilSpecifier' ] = (
401
+ const findImportedUtilSpecifier : FindImportedUtilSpecifierFn = (
364
402
specifierName
365
403
) => {
366
404
const node = getCustomModuleImportNode ( ) ?? getTestingLibraryImportNode ( ) ;
@@ -398,32 +436,23 @@ export function detectTestingLibraryUtils<
398
436
/**
399
437
* Determines if file inspected meets all conditions to be reported by rules or not.
400
438
*/
401
- const canReportErrors : DetectionHelpers [ 'canReportErrors' ] = ( ) => {
439
+ const canReportErrors : CanReportErrorsFn = ( ) => {
402
440
return isTestingLibraryImported ( ) && isValidFilename ( ) ;
403
441
} ;
404
442
/**
405
443
* Takes a MemberExpression or an Identifier and verifies if its name comes from the import in TL
406
444
* @param node a MemberExpression (in "foo.property" it would be property) or an Identifier
407
445
*/
408
- const isNodeComingFromTestingLibrary : DetectionHelpers [ 'isNodeComingFromTestingLibrary' ] = (
446
+ const isNodeComingFromTestingLibrary : IsNodeComingFromTestingLibraryFn = (
409
447
node
410
448
) => {
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 ;
422
451
423
452
return ! ! findImportedUtilSpecifier ( identifierName ) ;
424
453
} ;
425
454
426
- const helpers = {
455
+ const helpers : DetectionHelpers = {
427
456
getTestingLibraryImportNode,
428
457
getCustomModuleImportNode,
429
458
getTestingLibraryImportName,
0 commit comments