source: frontend/node_modules/eslint-plugin-jest/lib/rules/valid-expect-in-promise.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 2 weeks ago

Fix frontend appearance

  • Property mode set to 100644
File size: 12.3 KB
Line 
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.default = void 0;
7
8var _experimentalUtils = require("@typescript-eslint/experimental-utils");
9
10var _utils = require("./utils");
11
12const 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 }
18
19 switch ((0, _utils.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
32const findTopMostCallExpression = node => {
33 let topMostCallExpression = node;
34 let {
35 parent
36 } = node;
37
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;
53};
54
55const isTestCaseCallWithCallbackArg = node => {
56 if (!(0, _utils.isTestCaseCall)(node)) {
57 return false;
58 }
59
60 const isJestEach = (0, _utils.getNodeName)(node).endsWith('.each');
61
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;
68 }
69
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;
77};
78
79const 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;
93
94 if (firstArg.type === _experimentalUtils.AST_NODE_TYPES.ArrayExpression && firstArg.elements.some(nod => (0, _utils.isIdentifier)(nod, name))) {
95 return true;
96 }
97 }
98
99 if (['Promise.resolve', 'Promise.reject'].includes(nodeName) && node.argument.arguments.length === 1) {
100 return (0, _utils.isIdentifier)(node.argument.arguments[0], name);
101 }
102 }
103
104 return (0, _utils.isIdentifier)(node.argument, name);
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 */
110
111
112const isValueAwaitedInElements = (name, elements) => {
113 for (const element of elements) {
114 if (element.type === _experimentalUtils.AST_NODE_TYPES.AwaitExpression && (0, _utils.isIdentifier)(element.argument, name)) {
115 return true;
116 }
117
118 if (element.type === _experimentalUtils.AST_NODE_TYPES.ArrayExpression && isValueAwaitedInElements(name, element.elements)) {
119 return true;
120 }
121 }
122
123 return false;
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
131const isValueAwaitedInArguments = (name, call) => {
132 let node = call;
133
134 while (node) {
135 if (node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression) {
136 if (isValueAwaitedInElements(name, node.arguments)) {
137 return true;
138 }
139
140 node = node.callee;
141 }
142
143 if (node.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
144 break;
145 }
146
147 node = node.object;
148 }
149
150 return false;
151};
152
153const getLeftMostCallExpression = call => {
154 let leftMostCallExpression = call;
155 let node = call;
156
157 while (node) {
158 if (node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression) {
159 leftMostCallExpression = node;
160 node = node.callee;
161 }
162
163 if (node.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
164 break;
165 }
166
167 node = node.object;
168 }
169
170 return leftMostCallExpression;
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
177
178const isValueAwaitedOrReturned = (identifier, body) => {
179 const {
180 name
181 } = identifier;
182
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 === _experimentalUtils.AST_NODE_TYPES.ReturnStatement) {
191 return isPromiseMethodThatUsesValue(node, identifier);
192 }
193
194 if (node.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement) {
195 // it's possible that we're awaiting the value as an argument
196 if (node.expression.type === _experimentalUtils.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, _utils.isExpectCall)(leftMostCall) && leftMostCall.arguments.length > 0 && (0, _utils.isIdentifier)(leftMostCall.arguments[0], name)) {
204 const {
205 modifier
206 } = (0, _utils.parseExpectCall)(leftMostCall);
207
208 if ((modifier === null || modifier === void 0 ? void 0 : modifier.name) === _utils.ModifierName.resolves || (modifier === null || modifier === void 0 ? void 0 : modifier.name) === _utils.ModifierName.rejects) {
209 return true;
210 }
211 }
212 }
213
214 if (node.expression.type === _experimentalUtils.AST_NODE_TYPES.AwaitExpression && isPromiseMethodThatUsesValue(node.expression, identifier)) {
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 === _experimentalUtils.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, _utils.isIdentifier)(node.expression.left, name) && (_getNodeName = (0, _utils.getNodeName)(node.expression.right)) !== null && _getNodeName !== void 0 && _getNodeName.startsWith(`${name}.`) && isPromiseChainCall(node.expression.right)) {
226 continue;
227 }
228
229 break;
230 }
231 }
232
233 if (node.type === _experimentalUtils.AST_NODE_TYPES.BlockStatement && isValueAwaitedOrReturned(identifier, node.body)) {
234 return true;
235 }
236 }
237
238 return false;
239};
240
241const findFirstBlockBodyUp = node => {
242 let parent = node;
243
244 while (parent) {
245 if (parent.type === _experimentalUtils.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
257const isDirectlyWithinTestCaseCall = node => {
258 let parent = node;
259
260 while (parent) {
261 if ((0, _utils.isFunction)(parent)) {
262 var _parent;
263
264 parent = parent.parent;
265 return !!(((_parent = parent) === null || _parent === void 0 ? void 0 : _parent.type) === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.isTestCaseCall)(parent));
266 }
267
268 parent = parent.parent;
269 }
270
271 return false;
272};
273
274const 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, _utils.isIdentifier)(variable.id)) {
279 return true;
280 }
281
282 return isValueAwaitedOrReturned(variable.id, body);
283};
284
285var _default = (0, _utils.createRule)({
286 name: __filename,
287 meta: {
288 docs: {
289 category: 'Best Practices',
290 description: 'Ensure promises that have expectations in their chain are valid',
291 recommended: 'error'
292 },
293 messages: {
294 expectInFloatingPromise: "This promise should either be returned or awaited to ensure the expects in it's chain are called"
295 },
296 type: 'suggestion',
297 schema: []
298 },
299 defaultOptions: [],
300
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 = [];
311 return {
312 CallExpression(node) {
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;
317 return;
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
320
321
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
327
328
329 if (chains.length > 0 && (0, _utils.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, _utils.isTestCaseCall)(node)) {
340 inTestCaseWithDoneCallback = false;
341 }
342
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
351
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;
368 }
369
370 switch (parent.type) {
371 case _experimentalUtils.AST_NODE_TYPES.VariableDeclarator:
372 {
373 if (isVariableAwaitedOrReturned(parent)) {
374 return;
375 }
376
377 break;
378 }
379
380 case _experimentalUtils.AST_NODE_TYPES.AssignmentExpression:
381 {
382 if (parent.left.type === _experimentalUtils.AST_NODE_TYPES.Identifier && isValueAwaitedOrReturned(parent.left, findFirstBlockBodyUp(parent))) {
383 return;
384 }
385
386 break;
387 }
388
389 case _experimentalUtils.AST_NODE_TYPES.ExpressionStatement:
390 break;
391
392 case _experimentalUtils.AST_NODE_TYPES.ReturnStatement:
393 case _experimentalUtils.AST_NODE_TYPES.AwaitExpression:
394 default:
395 return;
396 }
397
398 context.report({
399 messageId: 'expectInFloatingPromise',
400 node: parent
401 });
402 }
403
404 };
405 }
406
407});
408
409exports.default = _default;
Note: See TracBrowser for help on using the repository browser.