1
1
import { TSESLint , TSESTree } from '@typescript-eslint/experimental-utils' ;
2
+ import { isLiteral } from './node-utils' ;
2
3
3
4
export type TestingLibrarySettings = {
4
5
'testing-library/module' ?: string ;
5
- 'testing-library/file-name ' ?: string ;
6
+ 'testing-library/filename-pattern ' ?: string ;
6
7
} ;
7
8
8
9
export type TestingLibraryContext <
@@ -24,13 +25,20 @@ export type EnhancedRuleCreate<
24
25
detectionHelpers : Readonly < DetectionHelpers >
25
26
) => TRuleListener ;
26
27
28
+ type ModuleImportation =
29
+ | TSESTree . ImportDeclaration
30
+ | TSESTree . CallExpression
31
+ | null ;
32
+
27
33
export type DetectionHelpers = {
34
+ getTestingLibraryImportNode : ( ) => ModuleImportation ;
35
+ getCustomModuleImportNode : ( ) => ModuleImportation ;
28
36
getIsTestingLibraryImported : ( ) => boolean ;
29
- getIsValidFileName : ( ) => boolean ;
37
+ getIsValidFilename : ( ) => boolean ;
30
38
canReportErrors : ( ) => boolean ;
31
39
} ;
32
40
33
- const DEFAULT_FILE_NAME_PATTERN = '^.*\\.(test|spec)\\.[jt]sx?$' ;
41
+ const DEFAULT_FILENAME_PATTERN = '^.*\\.(test|spec)\\.[jt]sx?$' ;
34
42
35
43
/**
36
44
* Enhances a given rule `create` with helpers to detect Testing Library utils.
@@ -44,17 +52,23 @@ export function detectTestingLibraryUtils<
44
52
context : TestingLibraryContext < TOptions , TMessageIds > ,
45
53
optionsWithDefault : Readonly < TOptions >
46
54
) : TSESLint . RuleListener => {
47
- let isImportingTestingLibraryModule = false ;
48
- let isImportingCustomModule = false ;
55
+ let importedTestingLibraryNode : ModuleImportation = null ;
56
+ let importedCustomModuleNode : ModuleImportation = null ;
49
57
50
58
// Init options based on shared ESLint settings
51
59
const customModule = context . settings [ 'testing-library/module' ] ;
52
- const fileNamePattern =
53
- context . settings [ 'testing-library/file-name ' ] ??
54
- DEFAULT_FILE_NAME_PATTERN ;
60
+ const filenamePattern =
61
+ context . settings [ 'testing-library/filename-pattern ' ] ??
62
+ DEFAULT_FILENAME_PATTERN ;
55
63
56
64
// Helpers for Testing Library detection.
57
65
const helpers : DetectionHelpers = {
66
+ getTestingLibraryImportNode ( ) {
67
+ return importedTestingLibraryNode ;
68
+ } ,
69
+ getCustomModuleImportNode ( ) {
70
+ return importedCustomModuleNode ;
71
+ } ,
58
72
/**
59
73
* Gets if Testing Library is considered as imported or not.
60
74
*
@@ -72,50 +86,85 @@ export function detectTestingLibraryUtils<
72
86
return true ;
73
87
}
74
88
75
- return isImportingTestingLibraryModule || isImportingCustomModule ;
89
+ return ! ! importedTestingLibraryNode || ! ! importedCustomModuleNode ;
76
90
} ,
77
91
78
92
/**
79
- * Gets if name of the file being analyzed is valid or not.
93
+ * Gets if filename being analyzed is valid or not.
80
94
*
81
- * This is based on "testing-library/file-name " setting.
95
+ * This is based on "testing-library/filename-pattern " setting.
82
96
*/
83
- getIsValidFileName ( ) {
97
+ getIsValidFilename ( ) {
84
98
const fileName = context . getFilename ( ) ;
85
- return ! ! fileName . match ( fileNamePattern ) ;
99
+ return ! ! fileName . match ( filenamePattern ) ;
86
100
} ,
87
101
88
102
/**
89
103
* Wraps all conditions that must be met to report rules.
90
104
*/
91
105
canReportErrors ( ) {
92
- return this . getIsTestingLibraryImported ( ) && this . getIsValidFileName ( ) ;
106
+ return this . getIsTestingLibraryImported ( ) && this . getIsValidFilename ( ) ;
93
107
} ,
94
108
} ;
95
109
96
110
// Instructions for Testing Library detection.
97
111
const detectionInstructions : TSESLint . RuleListener = {
98
112
/**
99
113
* This ImportDeclaration rule listener will check if Testing Library related
100
- * modules are loaded . Since imports happen first thing in a file, it's
114
+ * modules are imported . Since imports happen first thing in a file, it's
101
115
* safe to use `isImportingTestingLibraryModule` and `isImportingCustomModule`
102
116
* since they will have corresponding value already updated when reporting other
103
117
* parts of the file.
104
118
*/
105
119
ImportDeclaration ( node : TSESTree . ImportDeclaration ) {
106
- if ( ! isImportingTestingLibraryModule ) {
107
- // check only if testing library import not found yet so we avoid
108
- // to override isImportingTestingLibraryModule after it's found
109
- isImportingTestingLibraryModule = / t e s t i n g - l i b r a r y / g. test (
110
- node . source . value as string
111
- ) ;
120
+ // check only if testing library import not found yet so we avoid
121
+ // to override importedTestingLibraryNode after it's found
122
+ if (
123
+ ! importedTestingLibraryNode &&
124
+ / t e s t i n g - l i b r a r y / g. test ( node . source . value as string )
125
+ ) {
126
+ importedTestingLibraryNode = node ;
127
+ }
128
+
129
+ // check only if custom module import not found yet so we avoid
130
+ // to override importedCustomModuleNode after it's found
131
+ if (
132
+ ! importedCustomModuleNode &&
133
+ String ( node . source . value ) . endsWith ( customModule )
134
+ ) {
135
+ importedCustomModuleNode = node ;
136
+ }
137
+ } ,
138
+
139
+ // Check if Testing Library related modules are loaded with required.
140
+ [ `CallExpression > Identifier[name="require"]` ] (
141
+ node : TSESTree . Identifier
142
+ ) {
143
+ const callExpression = node . parent as TSESTree . CallExpression ;
144
+ const { arguments : args } = callExpression ;
145
+
146
+ if (
147
+ ! importedTestingLibraryNode &&
148
+ args . some (
149
+ ( arg ) =>
150
+ isLiteral ( arg ) &&
151
+ typeof arg . value === 'string' &&
152
+ / t e s t i n g - l i b r a r y / g. test ( arg . value )
153
+ )
154
+ ) {
155
+ importedTestingLibraryNode = callExpression ;
112
156
}
113
157
114
- if ( ! isImportingCustomModule ) {
115
- // check only if custom module import not found yet so we avoid
116
- // to override isImportingCustomModule after it's found
117
- const importName = String ( node . source . value ) ;
118
- isImportingCustomModule = importName . endsWith ( customModule ) ;
158
+ if (
159
+ ! importedCustomModuleNode &&
160
+ args . some (
161
+ ( arg ) =>
162
+ isLiteral ( arg ) &&
163
+ typeof arg . value === 'string' &&
164
+ arg . value . endsWith ( customModule )
165
+ )
166
+ ) {
167
+ importedCustomModuleNode = callExpression ;
119
168
}
120
169
} ,
121
170
} ;
0 commit comments