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

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

Fix frontend appearance

  • Property mode set to 100644
File size: 9.0 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
12/*
13 * This implementation is ported from from eslint-plugin-jasmine.
14 * MIT license, Tom Vincent.
15 */
16
17/**
18 * Async assertions might be called in Promise
19 * methods like `Promise.x(expect1)` or `Promise.x([expect1, expect2])`.
20 * If that's the case, Promise node have to be awaited or returned.
21 *
22 * @Returns CallExpressionNode
23 */
24const getPromiseCallExpressionNode = node => {
25 if (node.type === _experimentalUtils.AST_NODE_TYPES.ArrayExpression && node.parent && node.parent.type === _experimentalUtils.AST_NODE_TYPES.CallExpression) {
26 node = node.parent;
27 }
28
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 return node;
31 }
32
33 return null;
34};
35
36const 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};
41
42const getParentIfThenified = node => {
43 var _node$parent2;
44
45 const grandParentNode = (_node$parent2 = node.parent) === null || _node$parent2 === void 0 ? void 0 : _node$parent2.parent;
46
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) {
48 // Just in case `then`s are chained look one above.
49 return getParentIfThenified(grandParentNode);
50 }
51
52 return node;
53};
54
55const isAcceptableReturnNode = (node, allowReturn) => {
56 if (allowReturn && node.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement) {
57 return true;
58 }
59
60 if (node.type === _experimentalUtils.AST_NODE_TYPES.ConditionalExpression && node.parent) {
61 return isAcceptableReturnNode(node.parent, allowReturn);
62 }
63
64 return [_experimentalUtils.AST_NODE_TYPES.ArrowFunctionExpression, _experimentalUtils.AST_NODE_TYPES.AwaitExpression].includes(node.type);
65};
66
67const isNoAssertionsParentNode = node => node.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement || node.type === _experimentalUtils.AST_NODE_TYPES.AwaitExpression && node.parent !== undefined && node.parent.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement;
68
69const promiseArrayExceptionKey = ({
70 start,
71 end
72}) => `${start.line}:${start.column}-${end.line}:${end.column}`;
73
74const defaultAsyncMatchers = ['toReject', 'toResolve'];
75
76var _default = (0, _utils.createRule)({
77 name: __filename,
78 meta: {
79 docs: {
80 category: 'Best Practices',
81 description: 'Enforce valid `expect()` usage',
82 recommended: 'error'
83 },
84 messages: {
85 tooManyArgs: 'Expect takes at most {{ amount }} argument{{ s }}.',
86 notEnoughArgs: 'Expect requires at least {{ amount }} argument{{ s }}.',
87 modifierUnknown: 'Expect has no modifier named "{{ modifierName }}".',
88 matcherNotFound: 'Expect must have a corresponding matcher call.',
89 matcherNotCalled: 'Matchers must be called to assert.',
90 asyncMustBeAwaited: 'Async assertions must be awaited{{ orReturned }}.',
91 promisesWithAsyncAssertionsMustBeAwaited: 'Promises which return async assertions must be awaited{{ orReturned }}.'
92 },
93 type: 'suggestion',
94 schema: [{
95 type: 'object',
96 properties: {
97 alwaysAwait: {
98 type: 'boolean',
99 default: false
100 },
101 asyncMatchers: {
102 type: 'array',
103 items: {
104 type: 'string'
105 }
106 },
107 minArgs: {
108 type: 'number',
109 minimum: 1
110 },
111 maxArgs: {
112 type: 'number',
113 minimum: 1
114 }
115 },
116 additionalProperties: false
117 }]
118 },
119 defaultOptions: [{
120 alwaysAwait: false,
121 asyncMatchers: defaultAsyncMatchers,
122 minArgs: 1,
123 maxArgs: 1
124 }],
125
126 create(context, [{
127 alwaysAwait,
128 asyncMatchers = defaultAsyncMatchers,
129 minArgs = 1,
130 maxArgs = 1
131 }]) {
132 // Context state
133 const arrayExceptions = new Set();
134
135 const pushPromiseArrayException = loc => arrayExceptions.add(promiseArrayExceptionKey(loc));
136 /**
137 * Promise method that accepts an array of promises,
138 * (eg. Promise.all), will throw warnings for the each
139 * unawaited or non-returned promise. To avoid throwing
140 * multiple warnings, we check if there is a warning in
141 * the given location.
142 */
143
144
145 const promiseArrayExceptionExists = loc => arrayExceptions.has(promiseArrayExceptionKey(loc));
146
147 return {
148 CallExpression(node) {
149 if (!(0, _utils.isExpectCall)(node)) {
150 return;
151 }
152
153 const {
154 expect,
155 modifier,
156 matcher
157 } = (0, _utils.parseExpectCall)(node);
158
159 if (expect.arguments.length < minArgs) {
160 const expectLength = (0, _utils.getAccessorValue)(expect.callee).length;
161 const loc = {
162 start: {
163 column: node.loc.start.column + expectLength,
164 line: node.loc.start.line
165 },
166 end: {
167 column: node.loc.start.column + expectLength + 1,
168 line: node.loc.start.line
169 }
170 };
171 context.report({
172 messageId: 'notEnoughArgs',
173 data: {
174 amount: minArgs,
175 s: minArgs === 1 ? '' : 's'
176 },
177 node,
178 loc
179 });
180 }
181
182 if (expect.arguments.length > maxArgs) {
183 const {
184 start
185 } = expect.arguments[maxArgs].loc;
186 const {
187 end
188 } = expect.arguments[node.arguments.length - 1].loc;
189 const loc = {
190 start,
191 end: {
192 column: end.column - 1,
193 line: end.line
194 }
195 };
196 context.report({
197 messageId: 'tooManyArgs',
198 data: {
199 amount: maxArgs,
200 s: maxArgs === 1 ? '' : 's'
201 },
202 node,
203 loc
204 });
205 } // something was called on `expect()`
206
207
208 if (!matcher) {
209 if (modifier) {
210 context.report({
211 messageId: 'matcherNotFound',
212 node: modifier.node.property
213 });
214 }
215
216 return;
217 }
218
219 if ((0, _utils.isExpectMember)(matcher.node.parent)) {
220 context.report({
221 messageId: 'modifierUnknown',
222 data: {
223 modifierName: matcher.name
224 },
225 node: matcher.node.property
226 });
227 return;
228 }
229
230 if (!matcher.arguments) {
231 context.report({
232 messageId: 'matcherNotCalled',
233 node: matcher.node.property
234 });
235 }
236
237 const parentNode = matcher.node.parent;
238 const shouldBeAwaited = modifier && modifier.name !== _utils.ModifierName.not || asyncMatchers.includes(matcher.name);
239
240 if (!parentNode.parent || !shouldBeAwaited) {
241 return;
242 }
243 /**
244 * If parent node is an array expression, we'll report the warning,
245 * for the array object, not for each individual assertion.
246 */
247
248
249 const isParentArrayExpression = parentNode.parent.type === _experimentalUtils.AST_NODE_TYPES.ArrayExpression;
250 const orReturned = alwaysAwait ? '' : ' or returned';
251 /**
252 * An async assertion can be chained with `then` or `catch` statements.
253 * In that case our target CallExpression node is the one with
254 * the last `then` or `catch` statement.
255 */
256
257 const targetNode = getParentIfThenified(parentNode);
258 const finalNode = findPromiseCallExpressionNode(targetNode) || targetNode;
259
260 if (finalNode.parent && // If node is not awaited or returned
261 !isAcceptableReturnNode(finalNode.parent, !alwaysAwait) && // if we didn't warn user already
262 !promiseArrayExceptionExists(finalNode.loc)) {
263 context.report({
264 loc: finalNode.loc,
265 data: {
266 orReturned
267 },
268 messageId: finalNode === targetNode ? 'asyncMustBeAwaited' : 'promisesWithAsyncAssertionsMustBeAwaited',
269 node
270 });
271
272 if (isParentArrayExpression) {
273 pushPromiseArrayException(finalNode.loc);
274 }
275 }
276 },
277
278 // nothing called on "expect()"
279 'CallExpression:exit'(node) {
280 if ((0, _utils.isExpectCall)(node) && isNoAssertionsParentNode(node.parent)) {
281 context.report({
282 messageId: 'matcherNotFound',
283 node
284 });
285 }
286 }
287
288 };
289 }
290
291});
292
293exports.default = _default;
Note: See TracBrowser for help on using the repository browser.