11import { ESLintUtils , TSESTree } from '@typescript-eslint/experimental-utils' ;
2- import { ReportFixFunction , Scope , RuleFix } from '@typescript-eslint/experimental-utils/dist/ts-eslint'
32import {
4- isIdentifier ,
3+ ReportFixFunction ,
4+ RuleFix ,
5+ Scope ,
6+ } from '@typescript-eslint/experimental-utils/dist/ts-eslint' ;
7+ import {
8+ isArrowFunctionExpression ,
59 isCallExpression ,
10+ isIdentifier ,
611 isMemberExpression ,
7- isArrowFunctionExpression ,
812 isObjectPattern ,
913 isProperty ,
1014} from '../node-utils' ;
@@ -15,138 +19,189 @@ export const RULE_NAME = 'prefer-find-by';
1519type Options = [ ] ;
1620export type MessageIds = 'preferFindBy' ;
1721
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+ }
1947
2048export default ESLintUtils . RuleCreator ( getDocsUrl ) < Options , MessageIds > ( {
2149 name : RULE_NAME ,
2250 meta : {
2351 type : 'suggestion' ,
2452 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' ,
2655 category : 'Best Practices' ,
2756 recommended : 'warn' ,
2857 } ,
2958 messages : {
30- preferFindBy : 'Prefer {{queryVariant}}{{queryMethod}} method over using await {{fullQuery}}'
59+ preferFindBy :
60+ 'Prefer {{queryVariant}}{{queryMethod}} method over using await {{fullQuery}}' ,
3161 } ,
3262 fixable : 'code' ,
33- schema : [ ]
63+ schema : [ ] ,
3464 } ,
3565 defaultOptions : [ ] ,
3666
3767 create ( context ) {
3868 const sourceCode = context . getSourceCode ( ) ;
3969
40-
41-
4270 /**
4371 * Reports the invalid usage of wait* plus getBy/QueryBy methods and automatically fixes the scenario
4472 * @param {TSESTree.CallExpression } node - The CallExpresion node that contains the wait* method
4573 * @param {'findBy' | 'findAllBy' } replacementParams.queryVariant - The variant method used to query: findBy/findByAll.
4674 * @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.
4775 * @param {ReportFixFunction } replacementParams.fix - Function that applies the fix to correct the code
4876 */
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+ ) {
5189 context . report ( {
5290 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+ } ,
5597 fix,
5698 } ) ;
5799 }
58100
59101 return {
60102 '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 ;
63108 }
64109 // ensure the only argument is an arrow function expression - if the arrow function is a block
65110 // we skip it
66- const argument = node . arguments [ 0 ]
111+ const argument = node . arguments [ 0 ] ;
67112 if ( ! isArrowFunctionExpression ( argument ) ) {
68- return
113+ return ;
69114 }
70115 if ( ! isCallExpression ( argument . body ) ) {
71- return
116+ return ;
72117 }
73118 // 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+ ) {
75125 // 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 ] ;
81131
82132 reportInvalidUsage ( node , {
83133 queryMethod,
84134 queryVariant,
85135 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 ;
91143 }
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+ ) {
93148 // 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 ;
98153
99154 reportInvalidUsage ( node , {
100155 queryMethod,
101156 queryVariant,
102157 fix ( fixer ) {
103- const findByMethod = `${ queryVariant } ${ queryMethod } `
104- const allFixes : RuleFix [ ] = [ ]
158+ const findByMethod = `${ queryVariant } ${ queryMethod } ` ;
159+ const allFixes : RuleFix [ ] = [ ] ;
105160 // 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 ) ) ;
108165
109166 // 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+ ) ;
111171 // I think it should always find it, otherwise code should not be valid (it'd be using undeclared variables)
112172 if ( ! definition ) {
113- return allFixes
173+ return allFixes ;
114174 }
115175 // check the declaration is part of a destructuring
116176 if ( isObjectPattern ( definition . parent . parent ) ) {
117- const allVariableDeclarations = definition . parent . parent
177+ const allVariableDeclarations = definition . parent . parent ;
118178 // 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 ;
121188 }
122189 // 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 ) ) ;
126197 }
127198
128- return allFixes
129- }
130- } )
131- return
199+ return allFixes ;
200+ } ,
201+ } ) ;
202+ return ;
132203 }
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