1
- import {
2
- ESLintUtils ,
3
- TSESTree ,
4
- ASTUtils ,
5
- } from '@typescript-eslint/experimental-utils' ;
6
- import { getDocsUrl } from '../utils' ;
1
+ import { TSESTree , ASTUtils } from '@typescript-eslint/experimental-utils' ;
2
+ import { createTestingLibraryRule } from '../create-testing-library-rule' ;
7
3
import {
8
4
isImportSpecifier ,
9
5
isMemberExpression ,
10
6
findClosestCallExpressionNode ,
7
+ isCallExpression ,
8
+ isImportNamespaceSpecifier ,
9
+ isObjectPattern ,
10
+ isProperty ,
11
11
} from '../node-utils' ;
12
12
13
13
export const RULE_NAME = 'prefer-wait-for' ;
14
- export type MessageIds = 'preferWaitForMethod' | 'preferWaitForImport' ;
14
+ export type MessageIds =
15
+ | 'preferWaitForMethod'
16
+ | 'preferWaitForImport'
17
+ | 'preferWaitForRequire' ;
15
18
type Options = [ ] ;
16
19
17
20
const DEPRECATED_METHODS = [ 'wait' , 'waitForElement' , 'waitForDomChange' ] ;
18
21
19
- export default ESLintUtils . RuleCreator ( getDocsUrl ) < Options , MessageIds > ( {
22
+ export default createTestingLibraryRule < Options , MessageIds > ( {
20
23
name : RULE_NAME ,
21
24
meta : {
22
25
type : 'suggestion' ,
@@ -29,14 +32,43 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
29
32
preferWaitForMethod :
30
33
'`{{ methodName }}` is deprecated in favour of `waitFor`' ,
31
34
preferWaitForImport : 'import `waitFor` instead of deprecated async utils' ,
35
+ preferWaitForRequire :
36
+ 'require `waitFor` instead of deprecated async utils' ,
32
37
} ,
33
38
34
39
fixable : 'code' ,
35
40
schema : [ ] ,
36
41
} ,
37
42
defaultOptions : [ ] ,
38
43
39
- create ( context ) {
44
+ create ( context , _ , helpers ) {
45
+ let addWaitFor = false ;
46
+
47
+ const reportRequire = ( node : TSESTree . ObjectPattern ) => {
48
+ context . report ( {
49
+ node : node ,
50
+ messageId : 'preferWaitForRequire' ,
51
+ fix ( fixer ) {
52
+ const excludedImports = [ ...DEPRECATED_METHODS , 'waitFor' ] ;
53
+
54
+ const newAllRequired = node . properties
55
+ . filter (
56
+ ( s ) =>
57
+ isProperty ( s ) &&
58
+ ASTUtils . isIdentifier ( s . key ) &&
59
+ ! excludedImports . includes ( s . key . name )
60
+ )
61
+ . map (
62
+ ( s ) => ( ( s as TSESTree . Property ) . key as TSESTree . Identifier ) . name
63
+ ) ;
64
+
65
+ newAllRequired . push ( 'waitFor' ) ;
66
+
67
+ return fixer . replaceText ( node , `{ ${ newAllRequired . join ( ',' ) } }` ) ;
68
+ } ,
69
+ } ) ;
70
+ } ;
71
+
40
72
const reportImport = ( node : TSESTree . ImportDeclaration ) => {
41
73
context . report ( {
42
74
node : node ,
@@ -115,46 +147,57 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
115
147
} ;
116
148
117
149
return {
118
- 'ImportDeclaration[source.value=/testing-library/]' (
119
- node : TSESTree . ImportDeclaration
120
- ) {
121
- const deprecatedImportSpecifiers = node . specifiers . filter (
122
- ( specifier ) =>
123
- isImportSpecifier ( specifier ) &&
124
- specifier . imported &&
125
- DEPRECATED_METHODS . includes ( specifier . imported . name )
126
- ) ;
127
-
128
- deprecatedImportSpecifiers . forEach ( ( importSpecifier , i ) => {
129
- if ( i === 0 ) {
130
- reportImport ( node ) ;
131
- }
132
-
133
- context
134
- . getDeclaredVariables ( importSpecifier )
135
- . forEach ( ( variable ) =>
136
- variable . references . forEach ( ( reference ) =>
137
- reportWait ( reference . identifier )
138
- )
139
- ) ;
140
- } ) ;
150
+ 'CallExpression > MemberExpression' ( node : TSESTree . MemberExpression ) {
151
+ const isDeprecatedMethod =
152
+ ASTUtils . isIdentifier ( node . property ) &&
153
+ DEPRECATED_METHODS . includes ( node . property . name ) ;
154
+ if ( ! isDeprecatedMethod ) {
155
+ // the method does not match a deprecated method
156
+ return ;
157
+ }
158
+ if ( ! helpers . isNodeComingFromTestingLibrary ( node ) ) {
159
+ // the method does not match from the imported elements from TL (even from custom)
160
+ return ;
161
+ }
162
+ addWaitFor = true ;
163
+ reportWait ( node . property as TSESTree . Identifier ) ; // compiler is not picking up correctly, it should have inferred it is an identifier
141
164
} ,
142
- 'ImportDeclaration[source.value=/testing-library/] > ImportNamespaceSpecifier' (
143
- node : TSESTree . ImportNamespaceSpecifier
144
- ) {
145
- context . getDeclaredVariables ( node ) . forEach ( ( variable ) =>
146
- variable . references . forEach ( ( reference ) => {
147
- if (
148
- isMemberExpression ( reference . identifier . parent ) &&
149
- ASTUtils . isIdentifier ( reference . identifier . parent . property ) &&
150
- DEPRECATED_METHODS . includes (
151
- reference . identifier . parent . property . name
152
- )
153
- ) {
154
- reportWait ( reference . identifier . parent . property ) ;
155
- }
156
- } )
157
- ) ;
165
+ 'CallExpression > Identifier' ( node : TSESTree . Identifier ) {
166
+ if ( ! DEPRECATED_METHODS . includes ( node . name ) ) {
167
+ return ;
168
+ }
169
+
170
+ if ( ! helpers . isNodeComingFromTestingLibrary ( node ) ) {
171
+ return ;
172
+ }
173
+ addWaitFor = true ;
174
+ reportWait ( node ) ;
175
+ } ,
176
+ 'Program:exit' ( ) {
177
+ if ( ! addWaitFor ) {
178
+ return ;
179
+ }
180
+ // now that all usages of deprecated methods were replaced, remove the extra imports
181
+ const testingLibraryNode =
182
+ helpers . getCustomModuleImportNode ( ) ??
183
+ helpers . getTestingLibraryImportNode ( ) ;
184
+ if ( isCallExpression ( testingLibraryNode ) ) {
185
+ const parent = testingLibraryNode . parent as TSESTree . VariableDeclarator ;
186
+ if ( ! isObjectPattern ( parent . id ) ) {
187
+ // if there is no destructuring, there is nothing to replace
188
+ return ;
189
+ }
190
+ reportRequire ( parent . id ) ;
191
+ } else {
192
+ if (
193
+ testingLibraryNode . specifiers . length === 1 &&
194
+ isImportNamespaceSpecifier ( testingLibraryNode . specifiers [ 0 ] )
195
+ ) {
196
+ // if we import everything, there is nothing to replace
197
+ return ;
198
+ }
199
+ reportImport ( testingLibraryNode ) ;
200
+ }
158
201
} ,
159
202
} ;
160
203
} ,
0 commit comments