1
1
import { ESLintUtils , TSESTree } from '@typescript-eslint/experimental-utils' ;
2
- import { ReportFixFunction , Scope , RuleFix } from '@typescript-eslint/experimental-utils/dist/ts-eslint'
3
2
import {
4
- isIdentifier ,
3
+ ReportFixFunction ,
4
+ RuleFix ,
5
+ Scope ,
6
+ } from '@typescript-eslint/experimental-utils/dist/ts-eslint' ;
7
+ import {
8
+ isArrowFunctionExpression ,
5
9
isCallExpression ,
10
+ isIdentifier ,
6
11
isMemberExpression ,
7
- isArrowFunctionExpression ,
8
12
isObjectPattern ,
9
13
isProperty ,
10
14
} from '../node-utils' ;
@@ -15,138 +19,189 @@ export const RULE_NAME = 'prefer-find-by';
15
19
type Options = [ ] ;
16
20
export type MessageIds = 'preferFindBy' ;
17
21
18
- export const WAIT_METHODS = [ 'waitFor' , 'waitForElement' , 'wait' ]
22
+ export const WAIT_METHODS = [ 'waitFor' , 'waitForElement' , 'wait' ] ;
23
+
24
+ export function getFindByQueryVariant ( queryMethod : string ) {
25
+ return queryMethod . includes ( 'All' ) ? 'findAllBy' : 'findBy' ;
26
+ }
27
+
28
+ function findRenderDefinitionDeclaration (
29
+ scope : Scope . Scope | null ,
30
+ query : string
31
+ ) : TSESTree . Identifier | null {
32
+ if ( ! scope ) {
33
+ return null ;
34
+ }
35
+
36
+ const variable = scope . variables . find (
37
+ ( v : Scope . Variable ) => v . name === query
38
+ ) ;
39
+
40
+ if ( variable ) {
41
+ const def = variable . defs . find ( ( { name } ) => name . name === query ) ;
42
+ return def . name ;
43
+ }
44
+
45
+ return findRenderDefinitionDeclaration ( scope . upper , query ) ;
46
+ }
19
47
20
48
export default ESLintUtils . RuleCreator ( getDocsUrl ) < Options , MessageIds > ( {
21
49
name : RULE_NAME ,
22
50
meta : {
23
51
type : 'suggestion' ,
24
52
docs : {
25
- description : 'Suggest using find* instead of waitFor to wait for elements' ,
53
+ description :
54
+ 'Suggest using find* instead of waitFor to wait for elements' ,
26
55
category : 'Best Practices' ,
27
56
recommended : 'warn' ,
28
57
} ,
29
58
messages : {
30
- preferFindBy : 'Prefer {{queryVariant}}{{queryMethod}} method over using await {{fullQuery}}'
59
+ preferFindBy :
60
+ 'Prefer {{queryVariant}}{{queryMethod}} method over using await {{fullQuery}}' ,
31
61
} ,
32
62
fixable : 'code' ,
33
- schema : [ ]
63
+ schema : [ ] ,
34
64
} ,
35
65
defaultOptions : [ ] ,
36
66
37
67
create ( context ) {
38
68
const sourceCode = context . getSourceCode ( ) ;
39
69
40
-
41
-
42
70
/**
43
71
* Reports the invalid usage of wait* plus getBy/QueryBy methods and automatically fixes the scenario
44
72
* @param {TSESTree.CallExpression } node - The CallExpresion node that contains the wait* method
45
73
* @param {'findBy' | 'findAllBy' } replacementParams.queryVariant - The variant method used to query: findBy/findByAll.
46
74
* @param {string } replacementParams.queryMethod - Suffix string to build the query method (the query-part that comes after the "By"): LabelText, Placeholder, Text, Role, Title, etc.
47
75
* @param {ReportFixFunction } replacementParams.fix - Function that applies the fix to correct the code
48
76
*/
49
- function reportInvalidUsage ( node : TSESTree . CallExpression , { queryVariant, queryMethod, fix } : { queryVariant : 'findBy' | 'findAllBy' , queryMethod : string , fix : ReportFixFunction } ) {
50
-
77
+ function reportInvalidUsage (
78
+ node : TSESTree . CallExpression ,
79
+ {
80
+ queryVariant,
81
+ queryMethod,
82
+ fix,
83
+ } : {
84
+ queryVariant : 'findBy' | 'findAllBy' ;
85
+ queryMethod : string ;
86
+ fix : ReportFixFunction ;
87
+ }
88
+ ) {
51
89
context . report ( {
52
90
node,
53
- messageId : "preferFindBy" ,
54
- data : { queryVariant, queryMethod, fullQuery : sourceCode . getText ( node ) } ,
91
+ messageId : 'preferFindBy' ,
92
+ data : {
93
+ queryVariant,
94
+ queryMethod,
95
+ fullQuery : sourceCode . getText ( node ) ,
96
+ } ,
55
97
fix,
56
98
} ) ;
57
99
}
58
100
59
101
return {
60
102
'AwaitExpression > CallExpression' ( node : TSESTree . CallExpression ) {
61
- if ( ! isIdentifier ( node . callee ) || ! WAIT_METHODS . includes ( node . callee . name ) ) {
62
- return
103
+ if (
104
+ ! isIdentifier ( node . callee ) ||
105
+ ! WAIT_METHODS . includes ( node . callee . name )
106
+ ) {
107
+ return ;
63
108
}
64
109
// ensure the only argument is an arrow function expression - if the arrow function is a block
65
110
// we skip it
66
- const argument = node . arguments [ 0 ]
111
+ const argument = node . arguments [ 0 ] ;
67
112
if ( ! isArrowFunctionExpression ( argument ) ) {
68
- return
113
+ return ;
69
114
}
70
115
if ( ! isCallExpression ( argument . body ) ) {
71
- return
116
+ return ;
72
117
}
73
118
// ensure here it's one of the sync methods that we are calling
74
- if ( isMemberExpression ( argument . body . callee ) && isIdentifier ( argument . body . callee . property ) && isIdentifier ( argument . body . callee . object ) && SYNC_QUERIES_COMBINATIONS . includes ( argument . body . callee . property . name ) ) {
119
+ if (
120
+ isMemberExpression ( argument . body . callee ) &&
121
+ isIdentifier ( argument . body . callee . property ) &&
122
+ isIdentifier ( argument . body . callee . object ) &&
123
+ SYNC_QUERIES_COMBINATIONS . includes ( argument . body . callee . property . name )
124
+ ) {
75
125
// shape of () => screen.getByText
76
- const fullQueryMethod = argument . body . callee . property . name
77
- const caller = argument . body . callee . object . name
78
- const queryVariant = getFindByQueryVariant ( fullQueryMethod )
79
- const callArguments = argument . body . arguments
80
- const queryMethod = fullQueryMethod . split ( 'By' ) [ 1 ]
126
+ const fullQueryMethod = argument . body . callee . property . name ;
127
+ const caller = argument . body . callee . object . name ;
128
+ const queryVariant = getFindByQueryVariant ( fullQueryMethod ) ;
129
+ const callArguments = argument . body . arguments ;
130
+ const queryMethod = fullQueryMethod . split ( 'By' ) [ 1 ] ;
81
131
82
132
reportInvalidUsage ( node , {
83
133
queryMethod,
84
134
queryVariant,
85
135
fix ( fixer ) {
86
- const newCode = `${ caller } .${ queryVariant } ${ queryMethod } (${ callArguments . map ( ( node ) => sourceCode . getText ( node ) ) . join ( ', ' ) } )`
87
- return fixer . replaceText ( node , newCode )
88
- }
89
- } )
90
- return
136
+ const newCode = `${ caller } .${ queryVariant } ${ queryMethod } (${ callArguments
137
+ . map ( node => sourceCode . getText ( node ) )
138
+ . join ( ', ' ) } )`;
139
+ return fixer . replaceText ( node , newCode ) ;
140
+ } ,
141
+ } ) ;
142
+ return ;
91
143
}
92
- if ( isIdentifier ( argument . body . callee ) && SYNC_QUERIES_COMBINATIONS . includes ( argument . body . callee . name ) ) {
144
+ if (
145
+ isIdentifier ( argument . body . callee ) &&
146
+ SYNC_QUERIES_COMBINATIONS . includes ( argument . body . callee . name )
147
+ ) {
93
148
// shape of () => getByText
94
- const fullQueryMethod = argument . body . callee . name
95
- const queryMethod = fullQueryMethod . split ( 'By' ) [ 1 ]
96
- const queryVariant = getFindByQueryVariant ( fullQueryMethod )
97
- const callArguments = argument . body . arguments
149
+ const fullQueryMethod = argument . body . callee . name ;
150
+ const queryMethod = fullQueryMethod . split ( 'By' ) [ 1 ] ;
151
+ const queryVariant = getFindByQueryVariant ( fullQueryMethod ) ;
152
+ const callArguments = argument . body . arguments ;
98
153
99
154
reportInvalidUsage ( node , {
100
155
queryMethod,
101
156
queryVariant,
102
157
fix ( fixer ) {
103
- const findByMethod = `${ queryVariant } ${ queryMethod } `
104
- const allFixes : RuleFix [ ] = [ ]
158
+ const findByMethod = `${ queryVariant } ${ queryMethod } ` ;
159
+ const allFixes : RuleFix [ ] = [ ] ;
105
160
// this updates waitFor with findBy*
106
- const newCode = `${ findByMethod } (${ callArguments . map ( ( node ) => sourceCode . getText ( node ) ) . join ( ', ' ) } )`
107
- allFixes . push ( fixer . replaceText ( node , newCode ) )
161
+ const newCode = `${ findByMethod } (${ callArguments
162
+ . map ( node => sourceCode . getText ( node ) )
163
+ . join ( ', ' ) } )`;
164
+ allFixes . push ( fixer . replaceText ( node , newCode ) ) ;
108
165
109
166
// this adds the findBy* declaration - adding it to the list of destructured variables { findBy* } = render()
110
- const definition = findRenderDefinitionDeclaration ( context . getScope ( ) , fullQueryMethod )
167
+ const definition = findRenderDefinitionDeclaration (
168
+ context . getScope ( ) ,
169
+ fullQueryMethod
170
+ ) ;
111
171
// I think it should always find it, otherwise code should not be valid (it'd be using undeclared variables)
112
172
if ( ! definition ) {
113
- return allFixes
173
+ return allFixes ;
114
174
}
115
175
// check the declaration is part of a destructuring
116
176
if ( isObjectPattern ( definition . parent . parent ) ) {
117
- const allVariableDeclarations = definition . parent . parent
177
+ const allVariableDeclarations = definition . parent . parent ;
118
178
// verify if the findBy* method was already declared
119
- if ( allVariableDeclarations . properties . some ( ( p ) => isProperty ( p ) && isIdentifier ( p . key ) && p . key . name === findByMethod ) ) {
120
- return allFixes
179
+ if (
180
+ allVariableDeclarations . properties . some (
181
+ p =>
182
+ isProperty ( p ) &&
183
+ isIdentifier ( p . key ) &&
184
+ p . key . name === findByMethod
185
+ )
186
+ ) {
187
+ return allFixes ;
121
188
}
122
189
// the last character of a destructuring is always a " }", so we should replace it with the findBy* declaration
123
- const textDestructuring = sourceCode . getText ( allVariableDeclarations )
124
- const text = textDestructuring . substring ( 0 , textDestructuring . length - 2 ) + `, ${ findByMethod } }`
125
- allFixes . push ( fixer . replaceText ( allVariableDeclarations , text ) )
190
+ const textDestructuring = sourceCode . getText (
191
+ allVariableDeclarations
192
+ ) ;
193
+ const text =
194
+ textDestructuring . substring ( 0 , textDestructuring . length - 2 ) +
195
+ `, ${ findByMethod } }` ;
196
+ allFixes . push ( fixer . replaceText ( allVariableDeclarations , text ) ) ;
126
197
}
127
198
128
- return allFixes
129
- }
130
- } )
131
- return
199
+ return allFixes ;
200
+ } ,
201
+ } ) ;
202
+ return ;
132
203
}
133
- }
134
- }
135
- }
136
- } )
137
-
138
- export function getFindByQueryVariant ( queryMethod : string ) {
139
- return queryMethod . includes ( 'All' ) ? 'findAllBy' : 'findBy'
140
- }
141
-
142
- function findRenderDefinitionDeclaration ( scope : Scope . Scope | null , query : string ) : TSESTree . Identifier | null {
143
- if ( ! scope ) {
144
- return null
145
- }
146
- let variable = scope . variables . find ( ( v : Scope . Variable ) => v . name === query )
147
- if ( variable ) {
148
- const def = variable . defs . find ( ( { name } ) => name . name === query )
149
- return def . name
150
- }
151
- return findRenderDefinitionDeclaration ( scope . upper , query )
152
- }
204
+ } ,
205
+ } ;
206
+ } ,
207
+ } ) ;
0 commit comments