1
1
import {
2
2
AST_NODE_TYPES ,
3
- TSESLint ,
4
3
TSESTree ,
5
4
} from '@typescript-eslint/experimental-utils' ;
6
5
import {
7
6
CallExpressionWithSingleArgument ,
8
7
KnownCallExpression ,
9
8
MaybeTypeCast ,
10
9
ModifierName ,
11
- NotNegatableParsedModifier ,
12
10
ParsedEqualityMatcherCall ,
13
11
ParsedExpectMatcher ,
14
12
createRule ,
@@ -57,103 +55,15 @@ type FixableIncludesCallExpression = KnownCallExpression<'includes'> &
57
55
* @param {CallExpression } node
58
56
*
59
57
* @return {node is FixableIncludesCallExpression }
60
- *
61
- * @todo support `['includes']()` syntax (remove last property.type check to begin)
62
- * @todo break out into `isMethodCall<Name extends string>(node: TSESTree.Node, method: Name)` util-fn
63
58
*/
64
59
const isFixableIncludesCallExpression = (
65
60
node : TSESTree . Node ,
66
61
) : node is FixableIncludesCallExpression =>
67
62
node . type === AST_NODE_TYPES . CallExpression &&
68
63
node . callee . type === AST_NODE_TYPES . MemberExpression &&
69
64
isSupportedAccessor ( node . callee . property , 'includes' ) &&
70
- node . callee . property . type === AST_NODE_TYPES . Identifier &&
71
65
hasOnlyOneArgument ( node ) ;
72
66
73
- const buildToContainFuncExpectation = ( negated : boolean ) =>
74
- negated ? `${ ModifierName . not } .toContain` : 'toContain' ;
75
-
76
- /**
77
- * Finds the first `.` character token between the `object` & `property` of the given `member` expression.
78
- *
79
- * @param {TSESTree.MemberExpression } member
80
- * @param {SourceCode } sourceCode
81
- *
82
- * @return {Token | null }
83
- */
84
- const findPropertyDotToken = (
85
- member : TSESTree . MemberExpression ,
86
- sourceCode : TSESLint . SourceCode ,
87
- ) =>
88
- sourceCode . getFirstTokenBetween (
89
- member . object ,
90
- member . property ,
91
- token => token . value === '.' ,
92
- ) ;
93
-
94
- const getNegationFixes = (
95
- node : FixableIncludesCallExpression ,
96
- modifier : NotNegatableParsedModifier ,
97
- matcher : ParsedBooleanEqualityMatcherCall ,
98
- sourceCode : TSESLint . SourceCode ,
99
- fixer : TSESLint . RuleFixer ,
100
- fileName : string ,
101
- ) => {
102
- const [ containArg ] = node . arguments ;
103
- const negationPropertyDot = findPropertyDotToken ( modifier . node , sourceCode ) ;
104
-
105
- const toContainFunc = buildToContainFuncExpectation (
106
- followTypeAssertionChain ( matcher . arguments [ 0 ] ) . value ,
107
- ) ;
108
-
109
- /* istanbul ignore if */
110
- if ( negationPropertyDot === null ) {
111
- throw new Error (
112
- `Unexpected null when attempting to fix ${ fileName } - please file a github issue at https://github.com/jest-community/eslint-plugin-jest` ,
113
- ) ;
114
- }
115
-
116
- return [
117
- fixer . remove ( negationPropertyDot ) ,
118
- fixer . remove ( modifier . node . property ) ,
119
- fixer . replaceText ( matcher . node . property , toContainFunc ) ,
120
- fixer . replaceText ( matcher . arguments [ 0 ] , sourceCode . getText ( containArg ) ) ,
121
- ] ;
122
- } ;
123
-
124
- const getCommonFixes = (
125
- node : FixableIncludesCallExpression ,
126
- sourceCode : TSESLint . SourceCode ,
127
- fileName : string ,
128
- ) : Array < TSESTree . Node | TSESTree . Token > => {
129
- const [ containArg ] = node . arguments ;
130
- const includesCallee = node . callee ;
131
-
132
- const propertyDot = findPropertyDotToken ( includesCallee , sourceCode ) ;
133
-
134
- const closingParenthesis = sourceCode . getTokenAfter ( containArg ) ;
135
- const openParenthesis = sourceCode . getTokenBefore ( containArg ) ;
136
-
137
- /* istanbul ignore if */
138
- if (
139
- propertyDot === null ||
140
- closingParenthesis === null ||
141
- openParenthesis === null
142
- ) {
143
- throw new Error (
144
- `Unexpected null when attempting to fix ${ fileName } - please file a github issue at https://github.com/jest-community/eslint-plugin-jest` ,
145
- ) ;
146
- }
147
-
148
- return [
149
- containArg ,
150
- includesCallee . property ,
151
- propertyDot ,
152
- closingParenthesis ,
153
- openParenthesis ,
154
- ] ;
155
- } ;
156
-
157
67
// expect(array.includes(<value>)[not.]{toBe,toEqual}(<boolean>)
158
68
export default createRule ( {
159
69
name : __filename ,
@@ -181,6 +91,7 @@ export default createRule({
181
91
const {
182
92
expect : {
183
93
arguments : [ includesCall ] ,
94
+ range : [ , expectCallEnd ] ,
184
95
} ,
185
96
matcher,
186
97
modifier,
@@ -199,42 +110,32 @@ export default createRule({
199
110
context . report ( {
200
111
fix ( fixer ) {
201
112
const sourceCode = context . getSourceCode ( ) ;
202
- const fileName = context . getFilename ( ) ;
203
-
204
- const fixArr = getCommonFixes (
205
- includesCall ,
206
- sourceCode ,
207
- fileName ,
208
- ) . map ( target => fixer . remove ( target ) ) ;
209
113
210
- if ( modifier ) {
211
- return getNegationFixes (
212
- includesCall ,
213
- modifier ,
214
- matcher ,
215
- sourceCode ,
216
- fixer ,
217
- fileName ,
218
- ) . concat ( fixArr ) ;
219
- }
220
-
221
- const toContainFunc = buildToContainFuncExpectation (
222
- ! followTypeAssertionChain ( matcher . arguments [ 0 ] ) . value ,
223
- ) ;
224
-
225
- const [ containArg ] = includesCall . arguments ;
226
-
227
- fixArr . push (
228
- fixer . replaceText ( matcher . node . property , toContainFunc ) ,
229
- ) ;
230
- fixArr . push (
114
+ // we need to negate the expectation if the current expected
115
+ // value is itself negated by the "not" modifier
116
+ const addNotModifier =
117
+ followTypeAssertionChain ( matcher . arguments [ 0 ] ) . value ===
118
+ ! ! modifier ;
119
+
120
+ return [
121
+ // remove the "includes" call entirely
122
+ fixer . removeRange ( [
123
+ includesCall . callee . property . range [ 0 ] - 1 ,
124
+ includesCall . range [ 1 ] ,
125
+ ] ) ,
126
+ // replace the current matcher with "toContain", adding "not" if needed
127
+ fixer . replaceTextRange (
128
+ [ expectCallEnd , matcher . node . range [ 1 ] ] ,
129
+ addNotModifier
130
+ ? `.${ ModifierName . not } .toContain`
131
+ : '.toContain' ,
132
+ ) ,
133
+ // replace the matcher argument with the value from the "includes"
231
134
fixer . replaceText (
232
135
matcher . arguments [ 0 ] ,
233
- sourceCode . getText ( containArg ) ,
136
+ sourceCode . getText ( includesCall . arguments [ 0 ] ) ,
234
137
) ,
235
- ) ;
236
-
237
- return fixArr ;
138
+ ] ;
238
139
} ,
239
140
messageId : 'useToContain' ,
240
141
node : ( modifier || matcher ) . node . property ,
0 commit comments