@@ -70,50 +70,35 @@ function getCallType(
70
70
isIdentifier ( node . callee . object , 'test' ) &&
71
71
isPropertyAccessor ( node . callee , 'step' )
72
72
) {
73
- return { messageId : 'testStep' } ;
73
+ return { messageId : 'testStep' , node } ;
74
74
}
75
75
76
76
const expectType = getExpectType ( node ) ;
77
77
if ( ! expectType ) return ;
78
78
79
- // expect.poll
80
- if ( expectType === 'poll' ) {
81
- return { messageId : 'expectPoll' } ;
82
- }
83
-
84
- // expect with awaitable matcher
85
79
const [ lastMatcher ] = getMatchers ( node ) . slice ( - 1 ) ;
86
- const matcherName = getStringValue ( lastMatcher ) ;
87
-
88
- if ( awaitableMatchers . has ( matcherName ) ) {
89
- return { data : { matcherName } , messageId : 'expect' } ;
90
- }
91
- }
80
+ const grandparent = lastMatcher . parent . parent ;
92
81
93
- function isPromiseAll ( node : Rule . Node ) {
94
- return node . type === 'ArrayExpression' &&
95
- node . parent . type === 'CallExpression' &&
96
- node . parent . callee . type === 'MemberExpression' &&
97
- isIdentifier ( node . parent . callee . object , 'Promise' ) &&
98
- isIdentifier ( node . parent . callee . property , 'all' )
99
- ? node . parent
100
- : null ;
101
- }
82
+ // If the grandparent is not a CallExpression, then it's an incomplete
83
+ // expect statement, and we don't need to check it.
84
+ if ( grandparent . type !== 'CallExpression' ) return ;
102
85
103
- function checkValidity ( node : Rule . Node ) : ESTree . Node | undefined {
104
- if ( validTypes . has ( node . parent . type ) ) return ;
86
+ const matcherName = getStringValue ( lastMatcher ) ;
105
87
106
- const promiseAll = isPromiseAll ( node . parent ) ;
107
- return promiseAll
108
- ? checkValidity ( promiseAll )
109
- : node . parent . type === 'MemberExpression' ||
110
- ( node . parent . type === 'CallExpression' && node . parent . callee === node )
111
- ? checkValidity ( node . parent )
112
- : node ;
88
+ // The node needs to be checked if it's an expect.poll expression or an
89
+ // awaitable matcher.
90
+ if ( expectType === 'poll' || awaitableMatchers . has ( matcherName ) ) {
91
+ return {
92
+ data : { matcherName } ,
93
+ messageId : expectType === 'poll' ? 'expectPoll' : 'expect' ,
94
+ node : grandparent ,
95
+ } ;
96
+ }
113
97
}
114
98
115
99
export default {
116
100
create ( context ) {
101
+ const sourceCode = context . sourceCode ?? context . getSourceCode ( ) ;
117
102
const options = context . options [ 0 ] || { } ;
118
103
const awaitableMatchers = new Set ( [
119
104
...expectPlaywrightMatchers ,
@@ -122,12 +107,54 @@ export default {
122
107
...( options . customMatchers || [ ] ) ,
123
108
] ) ;
124
109
110
+ function checkValidity ( node : Rule . Node ) {
111
+ // If the parent is a valid type (e.g. return or await), we don't need to
112
+ // check any further.
113
+ if ( validTypes . has ( node . parent . type ) ) return true ;
114
+
115
+ // If the parent is an array, we need to check the grandparent to see if
116
+ // it's a Promise.all, or a variable.
117
+ if ( node . parent . type === 'ArrayExpression' ) {
118
+ return checkValidity ( node . parent ) ;
119
+ }
120
+
121
+ // If the parent is a call expression, we need to check the grandparent
122
+ // to see if it's a Promise.all.
123
+ if (
124
+ node . parent . type === 'CallExpression' &&
125
+ node . parent . callee . type === 'MemberExpression' &&
126
+ isIdentifier ( node . parent . callee . object , 'Promise' ) &&
127
+ isIdentifier ( node . parent . callee . property , 'all' )
128
+ ) {
129
+ return true ;
130
+ }
131
+
132
+ // If the parent is a variable declarator, we need to check the scope to
133
+ // find where it is referenced. When we find the reference, we can
134
+ // re-check validity.
135
+ if ( node . parent . type === 'VariableDeclarator' ) {
136
+ const scope = sourceCode . getScope ( node . parent . parent ) ;
137
+
138
+ for ( const ref of scope . references ) {
139
+ const refParent = ( ref . identifier as Rule . Node ) . parent ;
140
+
141
+ // If the parent of the reference is valid, we can immediately return
142
+ // true. Otherwise, we'll check the validity of the parent to continue
143
+ // the loop.
144
+ if ( validTypes . has ( refParent . type ) ) return true ;
145
+ if ( checkValidity ( refParent ) ) return true ;
146
+ }
147
+ }
148
+
149
+ return false ;
150
+ }
151
+
125
152
return {
126
153
CallExpression ( node ) {
127
154
const result = getCallType ( node , awaitableMatchers ) ;
128
- const reportNode = result ? checkValidity ( node ) : undefined ;
155
+ const isValid = result ? checkValidity ( result . node ) : false ;
129
156
130
- if ( result && reportNode ) {
157
+ if ( result && ! isValid ) {
131
158
context . report ( {
132
159
data : result . data ,
133
160
fix : ( fixer ) => fixer . insertTextBefore ( node , 'await ' ) ,
0 commit comments