eslint-plugin-jest 24.4.2 → 26.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -50
- package/docs/rules/expect-expect.md +42 -1
- package/docs/rules/max-nested-describe.md +4 -5
- package/docs/rules/no-conditional-expect.md +57 -3
- package/docs/rules/no-conditional-in-test.md +79 -0
- package/docs/rules/no-deprecated-functions.md +5 -0
- package/docs/rules/no-done-callback.md +3 -3
- package/docs/rules/no-if.md +5 -0
- package/docs/rules/no-standalone-expect.md +3 -3
- package/docs/rules/no-test-return-statement.md +1 -2
- package/docs/rules/prefer-comparison-matcher.md +55 -0
- package/docs/rules/prefer-equality-matcher.md +29 -0
- package/docs/rules/prefer-expect-assertions.md +126 -0
- package/docs/rules/prefer-expect-resolves.md +53 -0
- package/docs/rules/prefer-hooks-on-top.md +72 -48
- package/docs/rules/{lowercase-name.md → prefer-lowercase-title.md} +7 -7
- package/docs/rules/prefer-snapshot-hint.md +188 -0
- package/docs/rules/prefer-to-be.md +53 -0
- package/docs/rules/require-hook.md +187 -0
- package/docs/rules/require-top-level-describe.md +28 -0
- package/docs/rules/{valid-describe.md → valid-describe-callback.md} +1 -1
- package/docs/rules/valid-expect-in-promise.md +55 -14
- package/docs/rules/valid-expect.md +13 -0
- package/docs/rules/valid-title.md +30 -2
- package/lib/index.js +2 -3
- package/lib/processors/snapshot-processor.js +1 -1
- package/lib/rules/consistent-test-it.js +20 -20
- package/lib/rules/detectJestVersion.js +29 -0
- package/lib/rules/expect-expect.js +25 -11
- package/lib/rules/max-nested-describe.js +5 -5
- package/lib/rules/no-conditional-expect.js +9 -9
- package/lib/rules/no-conditional-in-test.js +60 -0
- package/lib/rules/no-deprecated-functions.js +14 -32
- package/lib/rules/no-done-callback.js +10 -10
- package/lib/rules/no-export.js +6 -6
- package/lib/rules/no-focused-tests.js +11 -11
- package/lib/rules/no-identical-title.js +3 -3
- package/lib/rules/no-if.js +13 -11
- package/lib/rules/no-interpolation-in-snapshots.js +6 -6
- package/lib/rules/no-jasmine-globals.js +10 -10
- package/lib/rules/no-large-snapshots.js +11 -11
- package/lib/rules/no-standalone-expect.js +14 -14
- package/lib/rules/no-test-prefixes.js +6 -6
- package/lib/rules/no-test-return-statement.js +8 -8
- package/lib/rules/prefer-comparison-matcher.js +139 -0
- package/lib/rules/prefer-equality-matcher.js +98 -0
- package/lib/rules/prefer-expect-assertions.js +93 -11
- package/lib/rules/prefer-expect-resolves.js +48 -0
- package/lib/rules/prefer-hooks-on-top.js +1 -1
- package/lib/rules/{lowercase-name.js → prefer-lowercase-title.js} +20 -1
- package/lib/rules/prefer-snapshot-hint.js +112 -0
- package/lib/rules/prefer-spy-on.js +9 -9
- package/lib/rules/prefer-to-be.js +136 -0
- package/lib/rules/prefer-to-contain.js +19 -67
- package/lib/rules/prefer-to-have-length.js +9 -14
- package/lib/rules/prefer-todo.js +9 -9
- package/lib/rules/require-hook.js +121 -0
- package/lib/rules/require-top-level-describe.js +40 -6
- package/lib/rules/utils.js +34 -30
- package/lib/rules/{valid-describe.js → valid-describe-callback.js} +9 -9
- package/lib/rules/valid-expect-in-promise.js +336 -67
- package/lib/rules/valid-expect.js +36 -19
- package/lib/rules/valid-title.js +61 -61
- package/package.json +40 -27
- package/CHANGELOG.md +0 -513
- package/docs/rules/no-expect-resolves.md +0 -47
- package/docs/rules/no-truthy-falsy.md +0 -53
- package/docs/rules/no-try-expect.md +0 -63
- package/docs/rules/prefer-inline-snapshots.md +0 -51
- package/docs/rules/prefer-to-be-null.md +0 -33
- package/docs/rules/prefer-to-be-undefined.md +0 -33
- package/lib/rules/no-expect-resolves.js +0 -40
- package/lib/rules/no-truthy-falsy.js +0 -58
- package/lib/rules/no-try-expect.js +0 -89
- package/lib/rules/prefer-inline-snapshots.js +0 -69
- package/lib/rules/prefer-to-be-null.js +0 -67
- package/lib/rules/prefer-to-be-undefined.js +0 -67
|
@@ -5,100 +5,293 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
7
|
|
|
8
|
-
var
|
|
8
|
+
var _utils = require("@typescript-eslint/utils");
|
|
9
9
|
|
|
10
|
-
var
|
|
10
|
+
var _utils2 = require("./utils");
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const isPromiseChainCall = node => {
|
|
13
|
+
if (node.type === _utils.AST_NODE_TYPES.CallExpression && node.callee.type === _utils.AST_NODE_TYPES.MemberExpression && (0, _utils2.isSupportedAccessor)(node.callee.property)) {
|
|
14
|
+
// promise methods should have at least 1 argument
|
|
15
|
+
if (node.arguments.length === 0) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
switch ((0, _utils2.getAccessorValue)(node.callee.property)) {
|
|
20
|
+
case 'then':
|
|
21
|
+
return node.arguments.length < 3;
|
|
22
|
+
|
|
23
|
+
case 'catch':
|
|
24
|
+
case 'finally':
|
|
25
|
+
return node.arguments.length < 2;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return false;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const findTopMostCallExpression = node => {
|
|
33
|
+
let topMostCallExpression = node;
|
|
34
|
+
let {
|
|
35
|
+
parent
|
|
36
|
+
} = node;
|
|
37
|
+
|
|
38
|
+
while (parent) {
|
|
39
|
+
if (parent.type === _utils.AST_NODE_TYPES.CallExpression) {
|
|
40
|
+
topMostCallExpression = parent;
|
|
41
|
+
parent = parent.parent;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (parent.type !== _utils.AST_NODE_TYPES.MemberExpression) {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
parent = parent.parent;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return topMostCallExpression;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const isTestCaseCallWithCallbackArg = node => {
|
|
56
|
+
if (!(0, _utils2.isTestCaseCall)(node)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const isJestEach = (0, _utils2.getNodeName)(node).endsWith('.each');
|
|
61
|
+
|
|
62
|
+
if (isJestEach && node.callee.type !== _utils.AST_NODE_TYPES.TaggedTemplateExpression) {
|
|
63
|
+
// isJestEach but not a TaggedTemplateExpression, so this must be
|
|
64
|
+
// the `jest.each([])()` syntax which this rule doesn't support due
|
|
65
|
+
// to its complexity (see jest-community/eslint-plugin-jest#710)
|
|
66
|
+
// so we return true to trigger bailout
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (isJestEach || node.arguments.length >= 2) {
|
|
71
|
+
const [, callback] = node.arguments;
|
|
72
|
+
const callbackArgIndex = Number(isJestEach);
|
|
73
|
+
return callback && (0, _utils2.isFunction)(callback) && callback.params.length === 1 + callbackArgIndex;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return false;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const isPromiseMethodThatUsesValue = (node, identifier) => {
|
|
80
|
+
const {
|
|
81
|
+
name
|
|
82
|
+
} = identifier;
|
|
83
|
+
|
|
84
|
+
if (node.argument === null) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (node.argument.type === _utils.AST_NODE_TYPES.CallExpression && node.argument.arguments.length > 0) {
|
|
89
|
+
const nodeName = (0, _utils2.getNodeName)(node.argument);
|
|
20
90
|
|
|
21
|
-
|
|
22
|
-
|
|
91
|
+
if (['Promise.all', 'Promise.allSettled'].includes(nodeName)) {
|
|
92
|
+
const [firstArg] = node.argument.arguments;
|
|
93
|
+
|
|
94
|
+
if (firstArg.type === _utils.AST_NODE_TYPES.ArrayExpression && firstArg.elements.some(nod => (0, _utils2.isIdentifier)(nod, name))) {
|
|
95
|
+
return true;
|
|
23
96
|
}
|
|
97
|
+
}
|
|
24
98
|
|
|
25
|
-
|
|
26
|
-
|
|
99
|
+
if (['Promise.resolve', 'Promise.reject'].includes(nodeName) && node.argument.arguments.length === 1) {
|
|
100
|
+
return (0, _utils2.isIdentifier)(node.argument.arguments[0], name);
|
|
101
|
+
}
|
|
27
102
|
}
|
|
28
103
|
|
|
29
|
-
return
|
|
104
|
+
return (0, _utils2.isIdentifier)(node.argument, name);
|
|
30
105
|
};
|
|
106
|
+
/**
|
|
107
|
+
* Attempts to determine if the runtime value represented by the given `identifier`
|
|
108
|
+
* is `await`ed within the given array of elements
|
|
109
|
+
*/
|
|
31
110
|
|
|
32
|
-
const isFullExpectCall = expression => expression.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.isExpectMember)(expression.callee);
|
|
33
111
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
112
|
+
const isValueAwaitedInElements = (name, elements) => {
|
|
113
|
+
for (const element of elements) {
|
|
114
|
+
if (element.type === _utils.AST_NODE_TYPES.AwaitExpression && (0, _utils2.isIdentifier)(element.argument, name)) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (element.type === _utils.AST_NODE_TYPES.ArrayExpression && isValueAwaitedInElements(name, element.elements)) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return false;
|
|
46
124
|
};
|
|
125
|
+
/**
|
|
126
|
+
* Attempts to determine if the runtime value represented by the given `identifier`
|
|
127
|
+
* is `await`ed as an argument along the given call expression
|
|
128
|
+
*/
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
const isValueAwaitedInArguments = (name, call) => {
|
|
132
|
+
let node = call;
|
|
133
|
+
|
|
134
|
+
while (node) {
|
|
135
|
+
if (node.type === _utils.AST_NODE_TYPES.CallExpression) {
|
|
136
|
+
if (isValueAwaitedInElements(name, node.arguments)) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
node = node.callee;
|
|
141
|
+
}
|
|
47
142
|
|
|
48
|
-
|
|
49
|
-
|
|
143
|
+
if (node.type !== _utils.AST_NODE_TYPES.MemberExpression) {
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
50
146
|
|
|
51
|
-
|
|
52
|
-
promiseName = (0, _utils.getAccessorValue)(node.expression.callee.object);
|
|
53
|
-
} else if (node.type === _experimentalUtils.AST_NODE_TYPES.VariableDeclarator && node.id.type === _experimentalUtils.AST_NODE_TYPES.Identifier) {
|
|
54
|
-
promiseName = node.id.name;
|
|
147
|
+
node = node.object;
|
|
55
148
|
}
|
|
56
149
|
|
|
57
|
-
|
|
58
|
-
return lastLineInTestFunc.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement && lastLineInTestFunc.argument && ('name' in lastLineInTestFunc.argument && lastLineInTestFunc.argument.name === promiseName || !promiseName);
|
|
150
|
+
return false;
|
|
59
151
|
};
|
|
60
152
|
|
|
61
|
-
const
|
|
153
|
+
const getLeftMostCallExpression = call => {
|
|
154
|
+
let leftMostCallExpression = call;
|
|
155
|
+
let node = call;
|
|
62
156
|
|
|
63
|
-
const findTestFunction = node => {
|
|
64
157
|
while (node) {
|
|
65
|
-
if (
|
|
66
|
-
|
|
158
|
+
if (node.type === _utils.AST_NODE_TYPES.CallExpression) {
|
|
159
|
+
leftMostCallExpression = node;
|
|
160
|
+
node = node.callee;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (node.type !== _utils.AST_NODE_TYPES.MemberExpression) {
|
|
164
|
+
break;
|
|
67
165
|
}
|
|
68
166
|
|
|
69
|
-
node = node.
|
|
167
|
+
node = node.object;
|
|
70
168
|
}
|
|
71
169
|
|
|
72
|
-
return
|
|
170
|
+
return leftMostCallExpression;
|
|
73
171
|
};
|
|
172
|
+
/**
|
|
173
|
+
* Attempts to determine if the runtime value represented by the given `identifier`
|
|
174
|
+
* is `await`ed or `return`ed within the given `body` of statements
|
|
175
|
+
*/
|
|
176
|
+
|
|
74
177
|
|
|
75
|
-
const
|
|
178
|
+
const isValueAwaitedOrReturned = (identifier, body) => {
|
|
179
|
+
const {
|
|
180
|
+
name
|
|
181
|
+
} = identifier;
|
|
76
182
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
183
|
+
for (const node of body) {
|
|
184
|
+
// skip all nodes that are before this identifier, because they'd probably
|
|
185
|
+
// be affecting a different runtime value (e.g. due to reassignment)
|
|
186
|
+
if (node.range[0] <= identifier.range[0]) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (node.type === _utils.AST_NODE_TYPES.ReturnStatement) {
|
|
191
|
+
return isPromiseMethodThatUsesValue(node, identifier);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (node.type === _utils.AST_NODE_TYPES.ExpressionStatement) {
|
|
195
|
+
// it's possible that we're awaiting the value as an argument
|
|
196
|
+
if (node.expression.type === _utils.AST_NODE_TYPES.CallExpression) {
|
|
197
|
+
if (isValueAwaitedInArguments(name, node.expression)) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const leftMostCall = getLeftMostCallExpression(node.expression);
|
|
202
|
+
|
|
203
|
+
if ((0, _utils2.isExpectCall)(leftMostCall) && leftMostCall.arguments.length > 0 && (0, _utils2.isIdentifier)(leftMostCall.arguments[0], name)) {
|
|
204
|
+
const {
|
|
205
|
+
modifier
|
|
206
|
+
} = (0, _utils2.parseExpectCall)(leftMostCall);
|
|
207
|
+
|
|
208
|
+
if ((modifier === null || modifier === void 0 ? void 0 : modifier.name) === _utils2.ModifierName.resolves || (modifier === null || modifier === void 0 ? void 0 : modifier.name) === _utils2.ModifierName.rejects) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (node.expression.type === _utils.AST_NODE_TYPES.AwaitExpression && isPromiseMethodThatUsesValue(node.expression, identifier)) {
|
|
82
215
|
return true;
|
|
216
|
+
} // (re)assignment changes the runtime value, so if we've not found an
|
|
217
|
+
// await or return already we act as if we've reached the end of the body
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
if (node.expression.type === _utils.AST_NODE_TYPES.AssignmentExpression) {
|
|
221
|
+
var _getNodeName;
|
|
222
|
+
|
|
223
|
+
// unless we're assigning to the same identifier, in which case
|
|
224
|
+
// we might be chaining off the existing promise value
|
|
225
|
+
if ((0, _utils2.isIdentifier)(node.expression.left, name) && (_getNodeName = (0, _utils2.getNodeName)(node.expression.right)) !== null && _getNodeName !== void 0 && _getNodeName.startsWith(`${name}.`) && isPromiseChainCall(node.expression.right)) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
break;
|
|
83
230
|
}
|
|
84
231
|
}
|
|
85
232
|
|
|
86
|
-
|
|
87
|
-
|
|
233
|
+
if (node.type === _utils.AST_NODE_TYPES.BlockStatement && isValueAwaitedOrReturned(identifier, node.body)) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return false;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const findFirstBlockBodyUp = node => {
|
|
242
|
+
let parent = node;
|
|
243
|
+
|
|
244
|
+
while (parent) {
|
|
245
|
+
if (parent.type === _utils.AST_NODE_TYPES.BlockStatement) {
|
|
246
|
+
return parent.body;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
parent = parent.parent;
|
|
250
|
+
}
|
|
251
|
+
/* istanbul ignore next */
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
throw new Error(`Could not find BlockStatement - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const isDirectlyWithinTestCaseCall = node => {
|
|
258
|
+
let parent = node;
|
|
259
|
+
|
|
260
|
+
while (parent) {
|
|
261
|
+
if ((0, _utils2.isFunction)(parent)) {
|
|
262
|
+
var _parent;
|
|
263
|
+
|
|
264
|
+
parent = parent.parent;
|
|
265
|
+
return !!(((_parent = parent) === null || _parent === void 0 ? void 0 : _parent.type) === _utils.AST_NODE_TYPES.CallExpression && (0, _utils2.isTestCaseCall)(parent));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
parent = parent.parent;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return false;
|
|
88
272
|
};
|
|
89
273
|
|
|
90
|
-
const
|
|
274
|
+
const isVariableAwaitedOrReturned = variable => {
|
|
275
|
+
const body = findFirstBlockBodyUp(variable); // it's pretty much impossible for us to track destructuring assignments,
|
|
276
|
+
// so we return true to bailout gracefully
|
|
277
|
+
|
|
278
|
+
if (!(0, _utils2.isIdentifier)(variable.id)) {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
91
281
|
|
|
92
|
-
|
|
282
|
+
return isValueAwaitedOrReturned(variable.id, body);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
var _default = (0, _utils2.createRule)({
|
|
93
286
|
name: __filename,
|
|
94
287
|
meta: {
|
|
95
288
|
docs: {
|
|
96
289
|
category: 'Best Practices',
|
|
97
|
-
description: '
|
|
290
|
+
description: 'Ensure promises that have expectations in their chain are valid',
|
|
98
291
|
recommended: 'error'
|
|
99
292
|
},
|
|
100
293
|
messages: {
|
|
101
|
-
|
|
294
|
+
expectInFloatingPromise: "This promise should either be returned or awaited to ensure the expects in it's chain are called"
|
|
102
295
|
},
|
|
103
296
|
type: 'suggestion',
|
|
104
297
|
schema: []
|
|
@@ -106,30 +299,106 @@ var _default = (0, _utils.createRule)({
|
|
|
106
299
|
defaultOptions: [],
|
|
107
300
|
|
|
108
301
|
create(context) {
|
|
302
|
+
let inTestCaseWithDoneCallback = false; // an array of booleans representing each promise chain we enter, with the
|
|
303
|
+
// boolean value representing if we think a given chain contains an expect
|
|
304
|
+
// in it's body.
|
|
305
|
+
//
|
|
306
|
+
// since we only care about the inner-most chain, we represent the state in
|
|
307
|
+
// reverse with the inner-most being the first item, as that makes it
|
|
308
|
+
// slightly less code to assign to by not needing to know the length
|
|
309
|
+
|
|
310
|
+
const chains = [];
|
|
109
311
|
return {
|
|
110
312
|
CallExpression(node) {
|
|
111
|
-
|
|
313
|
+
// there are too many ways that the done argument could be used with
|
|
314
|
+
// promises that contain expect that would make the promise safe for us
|
|
315
|
+
if (isTestCaseCallWithCallbackArg(node)) {
|
|
316
|
+
inTestCaseWithDoneCallback = true;
|
|
112
317
|
return;
|
|
113
|
-
}
|
|
318
|
+
} // if this call expression is a promise chain, add it to the stack with
|
|
319
|
+
// value of "false", as we assume there are no expect calls initially
|
|
114
320
|
|
|
115
|
-
const testFunction = findTestFunction(node);
|
|
116
321
|
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
322
|
+
if (isPromiseChainCall(node)) {
|
|
323
|
+
chains.unshift(false);
|
|
324
|
+
return;
|
|
325
|
+
} // if we're within a promise chain, and this call expression looks like
|
|
326
|
+
// an expect call, mark the deepest chain as having an expect call
|
|
121
327
|
|
|
122
|
-
|
|
123
|
-
|
|
328
|
+
|
|
329
|
+
if (chains.length > 0 && (0, _utils2.isExpectCall)(node)) {
|
|
330
|
+
chains[0] = true;
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
'CallExpression:exit'(node) {
|
|
335
|
+
// there are too many ways that the "done" argument could be used to
|
|
336
|
+
// make promises containing expects safe in a test for us to be able to
|
|
337
|
+
// accurately check, so we just bail out completely if it's present
|
|
338
|
+
if (inTestCaseWithDoneCallback) {
|
|
339
|
+
if ((0, _utils2.isTestCaseCall)(node)) {
|
|
340
|
+
inTestCaseWithDoneCallback = false;
|
|
124
341
|
}
|
|
125
342
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (!isPromiseChainCall(node)) {
|
|
347
|
+
return;
|
|
348
|
+
} // since we're exiting this call expression (which is a promise chain)
|
|
349
|
+
// we remove it from the stack of chains, since we're unwinding
|
|
350
|
+
|
|
130
351
|
|
|
131
|
-
|
|
352
|
+
const hasExpectCall = chains.shift(); // if the promise chain we're exiting doesn't contain an expect,
|
|
353
|
+
// then we don't need to check it for anything
|
|
354
|
+
|
|
355
|
+
if (!hasExpectCall) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const {
|
|
360
|
+
parent
|
|
361
|
+
} = findTopMostCallExpression(node); // if we don't have a parent (which is technically impossible at runtime)
|
|
362
|
+
// or our parent is not directly within the test case, we stop checking
|
|
363
|
+
// because we're most likely in the body of a function being defined
|
|
364
|
+
// within the test, which we can't track
|
|
365
|
+
|
|
366
|
+
if (!parent || !isDirectlyWithinTestCaseCall(parent)) {
|
|
367
|
+
return;
|
|
132
368
|
}
|
|
369
|
+
|
|
370
|
+
switch (parent.type) {
|
|
371
|
+
case _utils.AST_NODE_TYPES.VariableDeclarator:
|
|
372
|
+
{
|
|
373
|
+
if (isVariableAwaitedOrReturned(parent)) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
case _utils.AST_NODE_TYPES.AssignmentExpression:
|
|
381
|
+
{
|
|
382
|
+
if (parent.left.type === _utils.AST_NODE_TYPES.Identifier && isValueAwaitedOrReturned(parent.left, findFirstBlockBodyUp(parent))) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
case _utils.AST_NODE_TYPES.ExpressionStatement:
|
|
390
|
+
break;
|
|
391
|
+
|
|
392
|
+
case _utils.AST_NODE_TYPES.ReturnStatement:
|
|
393
|
+
case _utils.AST_NODE_TYPES.AwaitExpression:
|
|
394
|
+
default:
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
context.report({
|
|
399
|
+
messageId: 'expectInFloatingPromise',
|
|
400
|
+
node: parent
|
|
401
|
+
});
|
|
133
402
|
}
|
|
134
403
|
|
|
135
404
|
};
|
|
@@ -5,9 +5,9 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
7
|
|
|
8
|
-
var
|
|
8
|
+
var _utils = require("@typescript-eslint/utils");
|
|
9
9
|
|
|
10
|
-
var
|
|
10
|
+
var _utils2 = require("./utils");
|
|
11
11
|
|
|
12
12
|
/*
|
|
13
13
|
* This implementation is ported from from eslint-plugin-jasmine.
|
|
@@ -22,23 +22,29 @@ var _utils = require("./utils");
|
|
|
22
22
|
* @Returns CallExpressionNode
|
|
23
23
|
*/
|
|
24
24
|
const getPromiseCallExpressionNode = node => {
|
|
25
|
-
if (node.type ===
|
|
25
|
+
if (node.type === _utils.AST_NODE_TYPES.ArrayExpression && node.parent && node.parent.type === _utils.AST_NODE_TYPES.CallExpression) {
|
|
26
26
|
node = node.parent;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
if (node.type ===
|
|
29
|
+
if (node.type === _utils.AST_NODE_TYPES.CallExpression && node.callee.type === _utils.AST_NODE_TYPES.MemberExpression && (0, _utils2.isSupportedAccessor)(node.callee.object) && (0, _utils2.getAccessorValue)(node.callee.object) === 'Promise' && node.parent) {
|
|
30
30
|
return node;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
return null;
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
const findPromiseCallExpressionNode = node =>
|
|
36
|
+
const findPromiseCallExpressionNode = node => {
|
|
37
|
+
var _node$parent;
|
|
38
|
+
|
|
39
|
+
return (_node$parent = node.parent) !== null && _node$parent !== void 0 && _node$parent.parent && [_utils.AST_NODE_TYPES.CallExpression, _utils.AST_NODE_TYPES.ArrayExpression].includes(node.parent.type) ? getPromiseCallExpressionNode(node.parent) : null;
|
|
40
|
+
};
|
|
37
41
|
|
|
38
42
|
const getParentIfThenified = node => {
|
|
39
|
-
|
|
43
|
+
var _node$parent2;
|
|
44
|
+
|
|
45
|
+
const grandParentNode = (_node$parent2 = node.parent) === null || _node$parent2 === void 0 ? void 0 : _node$parent2.parent;
|
|
40
46
|
|
|
41
|
-
if (grandParentNode && grandParentNode.type ===
|
|
47
|
+
if (grandParentNode && grandParentNode.type === _utils.AST_NODE_TYPES.CallExpression && (0, _utils2.isExpectMember)(grandParentNode.callee) && ['then', 'catch'].includes((0, _utils2.getAccessorValue)(grandParentNode.callee.property)) && grandParentNode.parent) {
|
|
42
48
|
// Just in case `then`s are chained look one above.
|
|
43
49
|
return getParentIfThenified(grandParentNode);
|
|
44
50
|
}
|
|
@@ -47,25 +53,27 @@ const getParentIfThenified = node => {
|
|
|
47
53
|
};
|
|
48
54
|
|
|
49
55
|
const isAcceptableReturnNode = (node, allowReturn) => {
|
|
50
|
-
if (allowReturn && node.type ===
|
|
56
|
+
if (allowReturn && node.type === _utils.AST_NODE_TYPES.ReturnStatement) {
|
|
51
57
|
return true;
|
|
52
58
|
}
|
|
53
59
|
|
|
54
|
-
if (node.type ===
|
|
60
|
+
if (node.type === _utils.AST_NODE_TYPES.ConditionalExpression && node.parent) {
|
|
55
61
|
return isAcceptableReturnNode(node.parent, allowReturn);
|
|
56
62
|
}
|
|
57
63
|
|
|
58
|
-
return [
|
|
64
|
+
return [_utils.AST_NODE_TYPES.ArrowFunctionExpression, _utils.AST_NODE_TYPES.AwaitExpression].includes(node.type);
|
|
59
65
|
};
|
|
60
66
|
|
|
61
|
-
const isNoAssertionsParentNode = node => node.type ===
|
|
67
|
+
const isNoAssertionsParentNode = node => node.type === _utils.AST_NODE_TYPES.ExpressionStatement || node.type === _utils.AST_NODE_TYPES.AwaitExpression && node.parent !== undefined && node.parent.type === _utils.AST_NODE_TYPES.ExpressionStatement;
|
|
62
68
|
|
|
63
69
|
const promiseArrayExceptionKey = ({
|
|
64
70
|
start,
|
|
65
71
|
end
|
|
66
72
|
}) => `${start.line}:${start.column}-${end.line}:${end.column}`;
|
|
67
73
|
|
|
68
|
-
|
|
74
|
+
const defaultAsyncMatchers = ['toReject', 'toResolve'];
|
|
75
|
+
|
|
76
|
+
var _default = (0, _utils2.createRule)({
|
|
69
77
|
name: __filename,
|
|
70
78
|
meta: {
|
|
71
79
|
docs: {
|
|
@@ -90,6 +98,12 @@ var _default = (0, _utils.createRule)({
|
|
|
90
98
|
type: 'boolean',
|
|
91
99
|
default: false
|
|
92
100
|
},
|
|
101
|
+
asyncMatchers: {
|
|
102
|
+
type: 'array',
|
|
103
|
+
items: {
|
|
104
|
+
type: 'string'
|
|
105
|
+
}
|
|
106
|
+
},
|
|
93
107
|
minArgs: {
|
|
94
108
|
type: 'number',
|
|
95
109
|
minimum: 1
|
|
@@ -104,12 +118,14 @@ var _default = (0, _utils.createRule)({
|
|
|
104
118
|
},
|
|
105
119
|
defaultOptions: [{
|
|
106
120
|
alwaysAwait: false,
|
|
121
|
+
asyncMatchers: defaultAsyncMatchers,
|
|
107
122
|
minArgs: 1,
|
|
108
123
|
maxArgs: 1
|
|
109
124
|
}],
|
|
110
125
|
|
|
111
126
|
create(context, [{
|
|
112
127
|
alwaysAwait,
|
|
128
|
+
asyncMatchers = defaultAsyncMatchers,
|
|
113
129
|
minArgs = 1,
|
|
114
130
|
maxArgs = 1
|
|
115
131
|
}]) {
|
|
@@ -130,7 +146,7 @@ var _default = (0, _utils.createRule)({
|
|
|
130
146
|
|
|
131
147
|
return {
|
|
132
148
|
CallExpression(node) {
|
|
133
|
-
if (!(0,
|
|
149
|
+
if (!(0, _utils2.isExpectCall)(node)) {
|
|
134
150
|
return;
|
|
135
151
|
}
|
|
136
152
|
|
|
@@ -138,10 +154,10 @@ var _default = (0, _utils.createRule)({
|
|
|
138
154
|
expect,
|
|
139
155
|
modifier,
|
|
140
156
|
matcher
|
|
141
|
-
} = (0,
|
|
157
|
+
} = (0, _utils2.parseExpectCall)(node);
|
|
142
158
|
|
|
143
159
|
if (expect.arguments.length < minArgs) {
|
|
144
|
-
const expectLength = (0,
|
|
160
|
+
const expectLength = (0, _utils2.getAccessorValue)(expect.callee).length;
|
|
145
161
|
const loc = {
|
|
146
162
|
start: {
|
|
147
163
|
column: node.loc.start.column + expectLength,
|
|
@@ -200,7 +216,7 @@ var _default = (0, _utils.createRule)({
|
|
|
200
216
|
return;
|
|
201
217
|
}
|
|
202
218
|
|
|
203
|
-
if ((0,
|
|
219
|
+
if ((0, _utils2.isExpectMember)(matcher.node.parent)) {
|
|
204
220
|
context.report({
|
|
205
221
|
messageId: 'modifierUnknown',
|
|
206
222
|
data: {
|
|
@@ -219,8 +235,9 @@ var _default = (0, _utils.createRule)({
|
|
|
219
235
|
}
|
|
220
236
|
|
|
221
237
|
const parentNode = matcher.node.parent;
|
|
238
|
+
const shouldBeAwaited = modifier && modifier.name !== _utils2.ModifierName.not || asyncMatchers.includes(matcher.name);
|
|
222
239
|
|
|
223
|
-
if (!parentNode.parent || !
|
|
240
|
+
if (!parentNode.parent || !shouldBeAwaited) {
|
|
224
241
|
return;
|
|
225
242
|
}
|
|
226
243
|
/**
|
|
@@ -229,7 +246,7 @@ var _default = (0, _utils.createRule)({
|
|
|
229
246
|
*/
|
|
230
247
|
|
|
231
248
|
|
|
232
|
-
const isParentArrayExpression = parentNode.parent.type ===
|
|
249
|
+
const isParentArrayExpression = parentNode.parent.type === _utils.AST_NODE_TYPES.ArrayExpression;
|
|
233
250
|
const orReturned = alwaysAwait ? '' : ' or returned';
|
|
234
251
|
/**
|
|
235
252
|
* An async assertion can be chained with `then` or `catch` statements.
|
|
@@ -260,7 +277,7 @@ var _default = (0, _utils.createRule)({
|
|
|
260
277
|
|
|
261
278
|
// nothing called on "expect()"
|
|
262
279
|
'CallExpression:exit'(node) {
|
|
263
|
-
if ((0,
|
|
280
|
+
if ((0, _utils2.isExpectCall)(node) && isNoAssertionsParentNode(node.parent)) {
|
|
264
281
|
context.report({
|
|
265
282
|
messageId: 'matcherNotFound',
|
|
266
283
|
node
|