eslint-plugin-jest 24.5.2 → 24.6.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/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/docs/rules/valid-expect-in-promise.md +55 -14
- package/lib/rules/no-large-snapshots.js +1 -3
- package/lib/rules/utils.js +3 -3
- package/lib/rules/valid-expect-in-promise.js +252 -68
- package/lib/rules/valid-expect.js +10 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [24.6.0](https://github.com/jest-community/eslint-plugin-jest/compare/v24.5.2...v24.6.0) (2021-10-09)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **valid-expect-in-promise:** re-implement rule ([#916](https://github.com/jest-community/eslint-plugin-jest/issues/916)) ([7a49c58](https://github.com/jest-community/eslint-plugin-jest/commit/7a49c5831e3d85a60c11e385203b8f83d98ad580))
|
|
7
|
+
|
|
1
8
|
## [24.5.2](https://github.com/jest-community/eslint-plugin-jest/compare/v24.5.1...v24.5.2) (2021-10-04)
|
|
2
9
|
|
|
3
10
|
|
package/README.md
CHANGED
|
@@ -193,7 +193,7 @@ installations requiring long-term consistency.
|
|
|
193
193
|
| [require-top-level-describe](docs/rules/require-top-level-describe.md) | Require test cases and hooks to be inside a `describe` block | | |
|
|
194
194
|
| [valid-describe](docs/rules/valid-describe.md) | Enforce valid `describe()` callback | ![recommended][] | |
|
|
195
195
|
| [valid-expect](docs/rules/valid-expect.md) | Enforce valid `expect()` usage | ![recommended][] | |
|
|
196
|
-
| [valid-expect-in-promise](docs/rules/valid-expect-in-promise.md) |
|
|
196
|
+
| [valid-expect-in-promise](docs/rules/valid-expect-in-promise.md) | Ensure promises that have expectations in their chain are valid | ![recommended][] | |
|
|
197
197
|
| [valid-title](docs/rules/valid-title.md) | Enforce valid titles | ![recommended][] | ![fixable][] |
|
|
198
198
|
|
|
199
199
|
<!-- end base rules list -->
|
|
@@ -1,31 +1,72 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Ensure promises that have expectations in their chain are valid (`valid-expect-in-promise`)
|
|
2
2
|
|
|
3
|
-
Ensure
|
|
4
|
-
promise
|
|
3
|
+
Ensure promises that include expectations are returned or awaited.
|
|
5
4
|
|
|
6
5
|
## Rule details
|
|
7
6
|
|
|
8
|
-
This rule
|
|
9
|
-
|
|
7
|
+
This rule flags any promises within the body of a test that include expectations
|
|
8
|
+
that have either not been returned or awaited.
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
The following pattern is considered warning:
|
|
10
|
+
The following patterns is considered warning:
|
|
14
11
|
|
|
15
12
|
```js
|
|
16
|
-
it('
|
|
17
|
-
|
|
18
|
-
expect(
|
|
13
|
+
it('promises a person', () => {
|
|
14
|
+
api.getPersonByName('bob').then(person => {
|
|
15
|
+
expect(person).toHaveProperty('name', 'Bob');
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('promises a counted person', () => {
|
|
20
|
+
const promise = api.getPersonByName('bob').then(person => {
|
|
21
|
+
expect(person).toHaveProperty('name', 'Bob');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
promise.then(() => {
|
|
25
|
+
expect(analytics.gottenPeopleCount).toBe(1);
|
|
19
26
|
});
|
|
20
27
|
});
|
|
28
|
+
|
|
29
|
+
it('promises multiple people', () => {
|
|
30
|
+
const firstPromise = api.getPersonByName('bob').then(person => {
|
|
31
|
+
expect(person).toHaveProperty('name', 'Bob');
|
|
32
|
+
});
|
|
33
|
+
const secondPromise = api.getPersonByName('alice').then(person => {
|
|
34
|
+
expect(person).toHaveProperty('name', 'Alice');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return Promise.any([firstPromise, secondPromise]);
|
|
38
|
+
});
|
|
21
39
|
```
|
|
22
40
|
|
|
23
41
|
The following pattern is not warning:
|
|
24
42
|
|
|
25
43
|
```js
|
|
26
|
-
it('
|
|
27
|
-
|
|
28
|
-
expect(
|
|
44
|
+
it('promises a person', async () => {
|
|
45
|
+
await api.getPersonByName('bob').then(person => {
|
|
46
|
+
expect(person).toHaveProperty('name', 'Bob');
|
|
29
47
|
});
|
|
30
48
|
});
|
|
49
|
+
|
|
50
|
+
it('promises a counted person', () => {
|
|
51
|
+
let promise = api.getPersonByName('bob').then(person => {
|
|
52
|
+
expect(person).toHaveProperty('name', 'Bob');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
promise = promise.then(() => {
|
|
56
|
+
expect(analytics.gottenPeopleCount).toBe(1);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return promise;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('promises multiple people', () => {
|
|
63
|
+
const firstPromise = api.getPersonByName('bob').then(person => {
|
|
64
|
+
expect(person).toHaveProperty('name', 'Bob');
|
|
65
|
+
});
|
|
66
|
+
const secondPromise = api.getPersonByName('alice').then(person => {
|
|
67
|
+
expect(person).toHaveProperty('name', 'Alice');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return Promise.allSettled([firstPromise, secondPromise]);
|
|
71
|
+
});
|
|
31
72
|
```
|
|
@@ -100,8 +100,6 @@ var _default = (0, _utils.createRule)({
|
|
|
100
100
|
|
|
101
101
|
return {
|
|
102
102
|
CallExpression(node) {
|
|
103
|
-
var _matcher$node$parent;
|
|
104
|
-
|
|
105
103
|
if (!(0, _utils.isExpectCall)(node)) {
|
|
106
104
|
return;
|
|
107
105
|
}
|
|
@@ -110,7 +108,7 @@ var _default = (0, _utils.createRule)({
|
|
|
110
108
|
matcher
|
|
111
109
|
} = (0, _utils.parseExpectCall)(node);
|
|
112
110
|
|
|
113
|
-
if ((matcher === null || matcher === void 0 ? void 0 :
|
|
111
|
+
if ((matcher === null || matcher === void 0 ? void 0 : matcher.node.parent.type) !== _experimentalUtils.AST_NODE_TYPES.CallExpression) {
|
|
114
112
|
return;
|
|
115
113
|
}
|
|
116
114
|
|
package/lib/rules/utils.js
CHANGED
|
@@ -233,7 +233,7 @@ const reparseAsMatcher = parsedMember => ({ ...parsedMember,
|
|
|
233
233
|
*
|
|
234
234
|
* If this matcher isn't called, this will be `null`.
|
|
235
235
|
*/
|
|
236
|
-
arguments: parsedMember.node.parent
|
|
236
|
+
arguments: parsedMember.node.parent.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? parsedMember.node.parent.arguments : null
|
|
237
237
|
});
|
|
238
238
|
/**
|
|
239
239
|
* Re-parses the given `parsedMember` as a `ParsedExpectModifier`.
|
|
@@ -260,7 +260,7 @@ const reparseMemberAsModifier = parsedMember => {
|
|
|
260
260
|
throw new Error(`modifier name must be either "${ModifierName.resolves}" or "${ModifierName.rejects}" (got "${parsedMember.name}")`);
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
-
const negation =
|
|
263
|
+
const negation = isExpectMember(parsedMember.node.parent, ModifierName.not) ? parsedMember.node.parent : undefined;
|
|
264
264
|
return { ...parsedMember,
|
|
265
265
|
negation
|
|
266
266
|
};
|
|
@@ -297,7 +297,7 @@ const parseExpectCall = expect => {
|
|
|
297
297
|
const modifier = expectation.modifier = reparseMemberAsModifier(parsedMember);
|
|
298
298
|
const memberNode = modifier.negation || modifier.node;
|
|
299
299
|
|
|
300
|
-
if (!
|
|
300
|
+
if (!isExpectMember(memberNode.parent)) {
|
|
301
301
|
return expectation;
|
|
302
302
|
}
|
|
303
303
|
|
|
@@ -9,96 +9,204 @@ var _experimentalUtils = require("@typescript-eslint/experimental-utils");
|
|
|
9
9
|
|
|
10
10
|
var _utils = require("./utils");
|
|
11
11
|
|
|
12
|
-
const isPromiseChainCall = node =>
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return isFullExpectCall(line.expression);
|
|
19
|
-
}
|
|
12
|
+
const isPromiseChainCall = node => {
|
|
13
|
+
if (node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.callee.property)) {
|
|
14
|
+
// promise methods should have at least 1 argument
|
|
15
|
+
if (node.arguments.length === 0) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
switch ((0, _utils.getAccessorValue)(node.callee.property)) {
|
|
20
|
+
case 'then':
|
|
21
|
+
return node.arguments.length < 3;
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
case 'catch':
|
|
24
|
+
case 'finally':
|
|
25
|
+
return node.arguments.length < 2;
|
|
26
|
+
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
return
|
|
29
|
+
return false;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
const
|
|
32
|
+
const findTopMostCallExpression = node => {
|
|
33
|
+
let topMostCallExpression = node;
|
|
34
|
+
let {
|
|
35
|
+
parent
|
|
36
|
+
} = node;
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
while (parent) {
|
|
39
|
+
if (parent.type === _experimentalUtils.AST_NODE_TYPES.CallExpression) {
|
|
40
|
+
topMostCallExpression = parent;
|
|
41
|
+
parent = parent.parent;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (parent.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
parent = parent.parent;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return topMostCallExpression;
|
|
46
53
|
};
|
|
47
54
|
|
|
48
|
-
const
|
|
49
|
-
|
|
55
|
+
const isTestCaseCallWithCallbackArg = node => {
|
|
56
|
+
if (!(0, _utils.isTestCaseCall)(node)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const isJestEach = (0, _utils.getNodeName)(node).endsWith('.each');
|
|
50
61
|
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
if (isJestEach && node.callee.type !== _experimentalUtils.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;
|
|
55
68
|
}
|
|
56
69
|
|
|
57
|
-
|
|
58
|
-
|
|
70
|
+
if (isJestEach || node.arguments.length >= 2) {
|
|
71
|
+
const [, callback] = node.arguments;
|
|
72
|
+
const callbackArgIndex = Number(isJestEach);
|
|
73
|
+
return callback && (0, _utils.isFunction)(callback) && callback.params.length === 1 + callbackArgIndex;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return false;
|
|
59
77
|
};
|
|
60
78
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
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 === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.argument.arguments.length > 0) {
|
|
89
|
+
const nodeName = (0, _utils.getNodeName)(node.argument);
|
|
90
|
+
|
|
91
|
+
if (['Promise.all', 'Promise.allSettled'].includes(nodeName)) {
|
|
92
|
+
const [firstArg] = node.argument.arguments;
|
|
64
93
|
|
|
65
|
-
|
|
66
|
-
|
|
94
|
+
if (firstArg.type === _experimentalUtils.AST_NODE_TYPES.ArrayExpression && firstArg.elements.some(nod => (0, _utils.isIdentifier)(nod, name))) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
67
97
|
}
|
|
68
98
|
|
|
69
|
-
|
|
99
|
+
if (['Promise.resolve', 'Promise.reject'].includes(nodeName) && node.argument.arguments.length === 1) {
|
|
100
|
+
return (0, _utils.isIdentifier)(node.argument.arguments[0], name);
|
|
101
|
+
}
|
|
70
102
|
}
|
|
71
103
|
|
|
72
|
-
return
|
|
104
|
+
return node.argument.type === _experimentalUtils.AST_NODE_TYPES.Identifier && (0, _utils.isIdentifier)(node.argument, name);
|
|
73
105
|
};
|
|
106
|
+
/**
|
|
107
|
+
* Attempts to determine if the runtime value represented by the given `identifier`
|
|
108
|
+
* is `await`ed or `return`ed within the given `body` of statements
|
|
109
|
+
*/
|
|
74
110
|
|
|
75
|
-
const isParentThenOrPromiseReturned = (node, testFunctionBody) => node.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement || isPromiseReturnedLater(node, testFunctionBody);
|
|
76
111
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
112
|
+
const isValueAwaitedOrReturned = (identifier, body) => {
|
|
113
|
+
const {
|
|
114
|
+
name
|
|
115
|
+
} = identifier;
|
|
116
|
+
|
|
117
|
+
for (const node of body) {
|
|
118
|
+
// skip all nodes that are before this identifier, because they'd probably
|
|
119
|
+
// be affecting a different runtime value (e.g. due to reassignment)
|
|
120
|
+
if (node.range[0] <= identifier.range[0]) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (node.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement) {
|
|
125
|
+
return isPromiseMethodThatUsesValue(node, identifier);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (node.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement) {
|
|
129
|
+
if (node.expression.type === _experimentalUtils.AST_NODE_TYPES.AwaitExpression) {
|
|
130
|
+
return isPromiseMethodThatUsesValue(node.expression, identifier);
|
|
131
|
+
} // (re)assignment changes the runtime value, so if we've not found an
|
|
132
|
+
// await or return already we act as if we've reached the end of the body
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
if (node.expression.type === _experimentalUtils.AST_NODE_TYPES.AssignmentExpression) {
|
|
136
|
+
var _getNodeName;
|
|
137
|
+
|
|
138
|
+
// unless we're assigning to the same identifier, in which case
|
|
139
|
+
// we might be chaining off the existing promise value
|
|
140
|
+
if ((0, _utils.isIdentifier)(node.expression.left, name) && (_getNodeName = (0, _utils.getNodeName)(node.expression.right)) !== null && _getNodeName !== void 0 && _getNodeName.startsWith(`${name}.`) && isPromiseChainCall(node.expression.right)) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
break;
|
|
83
145
|
}
|
|
84
146
|
}
|
|
85
147
|
|
|
86
|
-
|
|
87
|
-
|
|
148
|
+
if (node.type === _experimentalUtils.AST_NODE_TYPES.BlockStatement && isValueAwaitedOrReturned(identifier, node.body)) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return false;
|
|
88
154
|
};
|
|
89
155
|
|
|
90
|
-
const
|
|
156
|
+
const findFirstBlockBodyUp = node => {
|
|
157
|
+
let parent = node;
|
|
158
|
+
|
|
159
|
+
while (parent) {
|
|
160
|
+
if (parent.type === _experimentalUtils.AST_NODE_TYPES.BlockStatement) {
|
|
161
|
+
return parent.body;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
parent = parent.parent;
|
|
165
|
+
}
|
|
166
|
+
/* istanbul ignore next */
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
throw new Error(`Could not find BlockStatement - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const isDirectlyWithinTestCaseCall = node => {
|
|
173
|
+
let parent = node;
|
|
174
|
+
|
|
175
|
+
while (parent) {
|
|
176
|
+
if ((0, _utils.isFunction)(parent)) {
|
|
177
|
+
var _parent;
|
|
178
|
+
|
|
179
|
+
parent = parent.parent;
|
|
180
|
+
return !!(((_parent = parent) === null || _parent === void 0 ? void 0 : _parent.type) === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.isTestCaseCall)(parent));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
parent = parent.parent;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return false;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const isVariableAwaitedOrReturned = variable => {
|
|
190
|
+
const body = findFirstBlockBodyUp(variable); // it's pretty much impossible for us to track destructuring assignments,
|
|
191
|
+
// so we return true to bailout gracefully
|
|
192
|
+
|
|
193
|
+
if (!(0, _utils.isIdentifier)(variable.id)) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return isValueAwaitedOrReturned(variable.id, body);
|
|
198
|
+
};
|
|
91
199
|
|
|
92
200
|
var _default = (0, _utils.createRule)({
|
|
93
201
|
name: __filename,
|
|
94
202
|
meta: {
|
|
95
203
|
docs: {
|
|
96
204
|
category: 'Best Practices',
|
|
97
|
-
description: '
|
|
205
|
+
description: 'Ensure promises that have expectations in their chain are valid',
|
|
98
206
|
recommended: 'error'
|
|
99
207
|
},
|
|
100
208
|
messages: {
|
|
101
|
-
|
|
209
|
+
expectInFloatingPromise: "This promise should either be returned or awaited to ensure the expects in it's chain are called"
|
|
102
210
|
},
|
|
103
211
|
type: 'suggestion',
|
|
104
212
|
schema: []
|
|
@@ -106,30 +214,106 @@ var _default = (0, _utils.createRule)({
|
|
|
106
214
|
defaultOptions: [],
|
|
107
215
|
|
|
108
216
|
create(context) {
|
|
217
|
+
let inTestCaseWithDoneCallback = false; // an array of booleans representing each promise chain we enter, with the
|
|
218
|
+
// boolean value representing if we think a given chain contains an expect
|
|
219
|
+
// in it's body.
|
|
220
|
+
//
|
|
221
|
+
// since we only care about the inner-most chain, we represent the state in
|
|
222
|
+
// reverse with the inner-most being the first item, as that makes it
|
|
223
|
+
// slightly less code to assign to by not needing to know the length
|
|
224
|
+
|
|
225
|
+
const chains = [];
|
|
109
226
|
return {
|
|
110
227
|
CallExpression(node) {
|
|
111
|
-
|
|
228
|
+
// there are too many ways that the done argument could be used with
|
|
229
|
+
// promises that contain expect that would make the promise safe for us
|
|
230
|
+
if (isTestCaseCallWithCallbackArg(node)) {
|
|
231
|
+
inTestCaseWithDoneCallback = true;
|
|
112
232
|
return;
|
|
113
|
-
}
|
|
233
|
+
} // if this call expression is a promise chain, add it to the stack with
|
|
234
|
+
// value of "false", as we assume there are no expect calls initially
|
|
114
235
|
|
|
115
|
-
const testFunction = findTestFunction(node);
|
|
116
236
|
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
237
|
+
if (isPromiseChainCall(node)) {
|
|
238
|
+
chains.unshift(false);
|
|
239
|
+
return;
|
|
240
|
+
} // if we're within a promise chain, and this call expression looks like
|
|
241
|
+
// an expect call, mark the deepest chain as having an expect call
|
|
121
242
|
|
|
122
|
-
|
|
123
|
-
|
|
243
|
+
|
|
244
|
+
if (chains.length > 0 && (0, _utils.isExpectCall)(node)) {
|
|
245
|
+
chains[0] = true;
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
'CallExpression:exit'(node) {
|
|
250
|
+
// there are too many ways that the "done" argument could be used to
|
|
251
|
+
// make promises containing expects safe in a test for us to be able to
|
|
252
|
+
// accurately check, so we just bail out completely if it's present
|
|
253
|
+
if (inTestCaseWithDoneCallback) {
|
|
254
|
+
if ((0, _utils.isTestCaseCall)(node)) {
|
|
255
|
+
inTestCaseWithDoneCallback = false;
|
|
124
256
|
}
|
|
125
257
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (!isPromiseChainCall(node)) {
|
|
262
|
+
return;
|
|
263
|
+
} // since we're exiting this call expression (which is a promise chain)
|
|
264
|
+
// we remove it from the stack of chains, since we're unwinding
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
const hasExpectCall = chains.shift(); // if the promise chain we're exiting doesn't contain an expect,
|
|
268
|
+
// then we don't need to check it for anything
|
|
269
|
+
|
|
270
|
+
if (!hasExpectCall) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const {
|
|
275
|
+
parent
|
|
276
|
+
} = findTopMostCallExpression(node); // if we don't have a parent (which is technically impossible at runtime)
|
|
277
|
+
// or our parent is not directly within the test case, we stop checking
|
|
278
|
+
// because we're most likely in the body of a function being defined
|
|
279
|
+
// within the test, which we can't track
|
|
130
280
|
|
|
131
|
-
|
|
281
|
+
if (!parent || !isDirectlyWithinTestCaseCall(parent)) {
|
|
282
|
+
return;
|
|
132
283
|
}
|
|
284
|
+
|
|
285
|
+
switch (parent.type) {
|
|
286
|
+
case _experimentalUtils.AST_NODE_TYPES.VariableDeclarator:
|
|
287
|
+
{
|
|
288
|
+
if (isVariableAwaitedOrReturned(parent)) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
case _experimentalUtils.AST_NODE_TYPES.AssignmentExpression:
|
|
296
|
+
{
|
|
297
|
+
if (parent.left.type === _experimentalUtils.AST_NODE_TYPES.Identifier && isValueAwaitedOrReturned(parent.left, findFirstBlockBodyUp(parent))) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
case _experimentalUtils.AST_NODE_TYPES.ExpressionStatement:
|
|
305
|
+
break;
|
|
306
|
+
|
|
307
|
+
case _experimentalUtils.AST_NODE_TYPES.ReturnStatement:
|
|
308
|
+
case _experimentalUtils.AST_NODE_TYPES.AwaitExpression:
|
|
309
|
+
default:
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
context.report({
|
|
314
|
+
messageId: 'expectInFloatingPromise',
|
|
315
|
+
node: parent
|
|
316
|
+
});
|
|
133
317
|
}
|
|
134
318
|
|
|
135
319
|
};
|
|
@@ -26,19 +26,25 @@ const getPromiseCallExpressionNode = node => {
|
|
|
26
26
|
node = node.parent;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
if (node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.callee
|
|
29
|
+
if (node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.callee.object) && (0, _utils.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 && [_experimentalUtils.AST_NODE_TYPES.CallExpression, _experimentalUtils.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 === _experimentalUtils.AST_NODE_TYPES.CallExpression &&
|
|
47
|
+
if (grandParentNode && grandParentNode.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.isExpectMember)(grandParentNode.callee) && ['then', 'catch'].includes((0, _utils.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
|
}
|