8
8
getImportModuleName ,
9
9
getPropertyIdentifierNode ,
10
10
getReferenceNode ,
11
+ hasImportMatch ,
11
12
ImportModuleNode ,
12
13
isImportDeclaration ,
13
14
isImportNamespaceSpecifier ,
@@ -127,22 +128,41 @@ export function detectTestingLibraryUtils<
127
128
/**
128
129
* Small method to extract common checks to determine whether a node is
129
130
* related to Testing Library or not.
131
+ *
132
+ * To determine whether a node is a valid Testing Library util, there are
133
+ * two conditions to match:
134
+ * - it's named in a particular way (decided by given callback)
135
+ * - it's imported from valid Testing Library module (depends on aggressive
136
+ * reporting)
130
137
*/
131
138
function isTestingLibraryUtil (
132
139
node : TSESTree . Identifier ,
133
- isUtilCallback : ( identifierNode : TSESTree . Identifier ) => boolean
140
+ isUtilCallback : (
141
+ identifierNodeName : string ,
142
+ originalNodeName ?: string
143
+ ) => boolean
134
144
) : boolean {
135
- if ( ! isUtilCallback ( node ) ) {
145
+ const referenceNode = getReferenceNode ( node ) ;
146
+ const referenceNodeIdentifier = getPropertyIdentifierNode ( referenceNode ) ;
147
+ const importedUtilSpecifier = getImportedUtilSpecifier (
148
+ referenceNodeIdentifier
149
+ ) ;
150
+
151
+ const originalNodeName =
152
+ isImportSpecifier ( importedUtilSpecifier ) &&
153
+ importedUtilSpecifier . local . name !== importedUtilSpecifier . imported . name
154
+ ? importedUtilSpecifier . imported . name
155
+ : undefined ;
156
+
157
+ if ( ! isUtilCallback ( node . name , originalNodeName ) ) {
136
158
return false ;
137
159
}
138
160
139
- const referenceNode = getReferenceNode ( node ) ;
140
- const referenceNodeIdentifier = getPropertyIdentifierNode ( referenceNode ) ;
161
+ if ( isAggressiveModuleReportingEnabled ( ) ) {
162
+ return true ;
163
+ }
141
164
142
- return (
143
- isAggressiveModuleReportingEnabled ( ) ||
144
- isNodeComingFromTestingLibrary ( referenceNodeIdentifier )
145
- ) ;
165
+ return isNodeComingFromTestingLibrary ( referenceNodeIdentifier ) ;
146
166
}
147
167
148
168
/**
@@ -272,8 +292,8 @@ export function detectTestingLibraryUtils<
272
292
* coming from Testing Library will be considered as valid.
273
293
*/
274
294
const isAsyncUtil : IsAsyncUtilFn = ( node ) => {
275
- return isTestingLibraryUtil ( node , ( identifierNode ) =>
276
- ASYNC_UTILS . includes ( identifierNode . name )
295
+ return isTestingLibraryUtil ( node , ( identifierNodeName ) =>
296
+ ASYNC_UTILS . includes ( identifierNodeName )
277
297
) ;
278
298
} ;
279
299
@@ -347,13 +367,27 @@ export function detectTestingLibraryUtils<
347
367
* only those nodes coming from Testing Library will be considered as valid.
348
368
*/
349
369
const isRenderUtil : IsRenderUtilFn = ( node ) => {
350
- return isTestingLibraryUtil ( node , ( identifierNode ) => {
351
- if ( isAggressiveRenderReportingEnabled ( ) ) {
352
- return identifierNode . name . toLowerCase ( ) . includes ( RENDER_NAME ) ;
370
+ return isTestingLibraryUtil (
371
+ node ,
372
+ ( identifierNodeName , originalNodeName ) => {
373
+ if ( isAggressiveRenderReportingEnabled ( ) ) {
374
+ return identifierNodeName . toLowerCase ( ) . includes ( RENDER_NAME ) ;
375
+ }
376
+
377
+ return [ RENDER_NAME , ...customRenders ] . some ( ( validRenderName ) => {
378
+ let isMatch = false ;
379
+
380
+ if ( validRenderName === identifierNodeName ) {
381
+ isMatch = true ;
382
+ }
383
+
384
+ if ( ! ! originalNodeName && validRenderName === originalNodeName ) {
385
+ isMatch = true ;
386
+ }
387
+ return isMatch ;
388
+ } ) ;
353
389
}
354
-
355
- return [ RENDER_NAME , ...customRenders ] . includes ( identifierNode . name ) ;
356
- } ) ;
390
+ ) ;
357
391
} ;
358
392
359
393
/**
@@ -402,25 +436,34 @@ export function detectTestingLibraryUtils<
402
436
specifierName
403
437
) => {
404
438
const node = getCustomModuleImportNode ( ) ?? getTestingLibraryImportNode ( ) ;
439
+
405
440
if ( ! node ) {
406
441
return null ;
407
442
}
443
+
408
444
if ( isImportDeclaration ( node ) ) {
409
- const namedExport = node . specifiers . find (
410
- ( n ) => isImportSpecifier ( n ) && n . imported . name === specifierName
411
- ) ;
445
+ const namedExport = node . specifiers . find ( ( n ) => {
446
+ return (
447
+ isImportSpecifier ( n ) &&
448
+ [ n . imported . name , n . local . name ] . includes ( specifierName )
449
+ ) ;
450
+ } ) ;
451
+
412
452
// it is "import { foo [as alias] } from 'baz'""
413
453
if ( namedExport ) {
414
454
return namedExport ;
415
455
}
456
+
416
457
// it could be "import * as rtl from 'baz'"
417
458
return node . specifiers . find ( ( n ) => isImportNamespaceSpecifier ( n ) ) ;
418
459
} else {
419
460
const requireNode = node . parent as TSESTree . VariableDeclarator ;
461
+
420
462
if ( ASTUtils . isIdentifier ( requireNode . id ) ) {
421
463
// this is const rtl = require('foo')
422
464
return requireNode . id ;
423
465
}
466
+
424
467
// this should be const { something } = require('foo')
425
468
const destructuring = requireNode . id as TSESTree . ObjectPattern ;
426
469
const property = destructuring . properties . find (
@@ -429,27 +472,48 @@ export function detectTestingLibraryUtils<
429
472
ASTUtils . isIdentifier ( n . key ) &&
430
473
n . key . name === specifierName
431
474
) ;
475
+ if ( ! property ) {
476
+ return undefined ;
477
+ }
432
478
return ( property as TSESTree . Property ) . key as TSESTree . Identifier ;
433
479
}
434
480
} ;
435
481
482
+ const getImportedUtilSpecifier = (
483
+ node : TSESTree . MemberExpression | TSESTree . Identifier
484
+ ) : TSESTree . ImportClause | TSESTree . Identifier | undefined => {
485
+ const identifierName : string | undefined = getPropertyIdentifierNode ( node )
486
+ . name ;
487
+
488
+ return findImportedUtilSpecifier ( identifierName ) ;
489
+ } ;
490
+
436
491
/**
437
492
* Determines if file inspected meets all conditions to be reported by rules or not.
438
493
*/
439
494
const canReportErrors : CanReportErrorsFn = ( ) => {
440
495
return isTestingLibraryImported ( ) && isValidFilename ( ) ;
441
496
} ;
497
+
442
498
/**
443
- * Takes a MemberExpression or an Identifier and verifies if its name comes from the import in TL
444
- * @param node a MemberExpression (in "foo.property" it would be property) or an Identifier
499
+ * Determines whether a node is imported from a valid Testing Library module
500
+ *
501
+ * This method will try to find any import matching the given node name,
502
+ * and also make sure the name is a valid match in case it's been renamed.
445
503
*/
446
504
const isNodeComingFromTestingLibrary : IsNodeComingFromTestingLibraryFn = (
447
505
node
448
506
) => {
507
+ const importNode = getImportedUtilSpecifier ( node ) ;
508
+
509
+ if ( ! importNode ) {
510
+ return false ;
511
+ }
512
+
449
513
const identifierName : string | undefined = getPropertyIdentifierNode ( node )
450
514
. name ;
451
515
452
- return ! ! findImportedUtilSpecifier ( identifierName ) ;
516
+ return hasImportMatch ( importNode , identifierName ) ;
453
517
} ;
454
518
455
519
const helpers : DetectionHelpers = {
0 commit comments