eslint-plugin-jest 26.3.0 → 26.4.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/lib/rules/consistent-test-it.js +9 -8
- package/lib/rules/expect-expect.js +1 -1
- package/lib/rules/max-nested-describe.js +2 -2
- package/lib/rules/no-conditional-expect.js +2 -2
- package/lib/rules/no-conditional-in-test.js +2 -2
- package/lib/rules/no-done-callback.js +4 -2
- package/lib/rules/no-duplicate-hooks.js +22 -23
- package/lib/rules/no-export.js +1 -1
- package/lib/rules/no-focused-tests.js +14 -22
- package/lib/rules/no-hooks.js +4 -2
- package/lib/rules/no-identical-title.js +10 -7
- package/lib/rules/no-if.js +4 -2
- package/lib/rules/no-standalone-expect.js +2 -2
- package/lib/rules/no-test-prefixes.js +12 -20
- package/lib/rules/no-test-return-statement.js +4 -1
- package/lib/rules/prefer-expect-assertions.js +2 -2
- package/lib/rules/prefer-hooks-in-order.js +5 -3
- package/lib/rules/prefer-hooks-on-top.js +2 -2
- package/lib/rules/prefer-lowercase-title.js +11 -22
- package/lib/rules/prefer-snapshot-hint.js +2 -2
- package/lib/rules/prefer-todo.js +24 -7
- package/lib/rules/require-hook.js +2 -2
- package/lib/rules/require-top-level-describe.js +9 -4
- package/lib/rules/utils/parseJestFnCall.js +306 -0
- package/lib/rules/utils.js +40 -252
- package/lib/rules/valid-describe-callback.js +4 -2
- package/lib/rules/valid-expect-in-promise.js +9 -11
- package/lib/rules/valid-title.js +7 -6
- package/package.json +1 -1
|
@@ -12,7 +12,7 @@ var _utils2 = require("./utils");
|
|
|
12
12
|
const isJestFnCall = (node, scope) => {
|
|
13
13
|
var _getNodeName;
|
|
14
14
|
|
|
15
|
-
if ((0, _utils2.
|
|
15
|
+
if ((0, _utils2.parseJestFnCall)(node, scope)) {
|
|
16
16
|
return true;
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -100,7 +100,7 @@ var _default = (0, _utils2.createRule)({
|
|
|
100
100
|
},
|
|
101
101
|
|
|
102
102
|
CallExpression(node) {
|
|
103
|
-
if (!(0, _utils2.
|
|
103
|
+
if (!(0, _utils2.isTypeOfJestFnCall)(node, context.getScope(), ['describe']) || node.arguments.length < 2) {
|
|
104
104
|
return;
|
|
105
105
|
}
|
|
106
106
|
|
|
@@ -47,8 +47,13 @@ var _default = (0, _utils.createRule)({
|
|
|
47
47
|
return {
|
|
48
48
|
CallExpression(node) {
|
|
49
49
|
const scope = context.getScope();
|
|
50
|
+
const jestFnCall = (0, _utils.parseJestFnCall)(node, scope);
|
|
50
51
|
|
|
51
|
-
if (
|
|
52
|
+
if (!jestFnCall) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (jestFnCall.type === 'describe') {
|
|
52
57
|
numberOfDescribeBlocks++;
|
|
53
58
|
|
|
54
59
|
if (numberOfDescribeBlocks === 1) {
|
|
@@ -70,7 +75,7 @@ var _default = (0, _utils.createRule)({
|
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
if (numberOfDescribeBlocks === 0) {
|
|
73
|
-
if (
|
|
78
|
+
if (jestFnCall.type === 'test') {
|
|
74
79
|
context.report({
|
|
75
80
|
node,
|
|
76
81
|
messageId: 'unexpectedTestCase'
|
|
@@ -78,7 +83,7 @@ var _default = (0, _utils.createRule)({
|
|
|
78
83
|
return;
|
|
79
84
|
}
|
|
80
85
|
|
|
81
|
-
if (
|
|
86
|
+
if (jestFnCall.type === 'hook') {
|
|
82
87
|
context.report({
|
|
83
88
|
node,
|
|
84
89
|
messageId: 'unexpectedHook'
|
|
@@ -89,7 +94,7 @@ var _default = (0, _utils.createRule)({
|
|
|
89
94
|
},
|
|
90
95
|
|
|
91
96
|
'CallExpression:exit'(node) {
|
|
92
|
-
if ((0, _utils.
|
|
97
|
+
if ((0, _utils.isTypeOfJestFnCall)(node, context.getScope(), ['describe'])) {
|
|
93
98
|
numberOfDescribeBlocks--;
|
|
94
99
|
}
|
|
95
100
|
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getNodeChain = getNodeChain;
|
|
7
|
+
exports.scopeHasLocalReference = exports.parseJestFnCall = exports.isTypeOfJestFnCall = void 0;
|
|
8
|
+
|
|
9
|
+
var _utils = require("@typescript-eslint/utils");
|
|
10
|
+
|
|
11
|
+
var _utils2 = require("../utils");
|
|
12
|
+
|
|
13
|
+
const isTypeOfJestFnCall = (node, scope, types) => {
|
|
14
|
+
const jestFnCall = parseJestFnCall(node, scope);
|
|
15
|
+
return jestFnCall !== null && types.includes(jestFnCall.type);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
exports.isTypeOfJestFnCall = isTypeOfJestFnCall;
|
|
19
|
+
|
|
20
|
+
function getNodeChain(node) {
|
|
21
|
+
if ((0, _utils2.isSupportedAccessor)(node)) {
|
|
22
|
+
return [node];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
switch (node.type) {
|
|
26
|
+
case _utils.AST_NODE_TYPES.TaggedTemplateExpression:
|
|
27
|
+
return getNodeChain(node.tag);
|
|
28
|
+
|
|
29
|
+
case _utils.AST_NODE_TYPES.MemberExpression:
|
|
30
|
+
return [...getNodeChain(node.object), ...getNodeChain(node.property)];
|
|
31
|
+
|
|
32
|
+
case _utils.AST_NODE_TYPES.NewExpression:
|
|
33
|
+
case _utils.AST_NODE_TYPES.CallExpression:
|
|
34
|
+
return getNodeChain(node.callee);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const determineJestFnType = name => {
|
|
41
|
+
// if (name === 'expect') {
|
|
42
|
+
// return 'expect';
|
|
43
|
+
// }
|
|
44
|
+
if (name === 'jest') {
|
|
45
|
+
return 'jest';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (_utils2.DescribeAlias.hasOwnProperty(name)) {
|
|
49
|
+
return 'describe';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (_utils2.TestCaseName.hasOwnProperty(name)) {
|
|
53
|
+
return 'test';
|
|
54
|
+
}
|
|
55
|
+
/* istanbul ignore else */
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if (_utils2.HookName.hasOwnProperty(name)) {
|
|
59
|
+
return 'hook';
|
|
60
|
+
}
|
|
61
|
+
/* istanbul ignore next */
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
return 'unknown';
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const ValidJestFnCallChains = ['afterAll', 'afterEach', 'beforeAll', 'beforeEach', 'describe', 'describe.each', 'describe.only', 'describe.only.each', 'describe.skip', 'describe.skip.each', 'fdescribe', 'fdescribe.each', 'xdescribe', 'xdescribe.each', 'it', 'it.concurrent', 'it.concurrent.each', 'it.concurrent.only.each', 'it.concurrent.skip.each', 'it.each', 'it.failing', 'it.only', 'it.only.each', 'it.skip', 'it.skip.each', 'it.todo', 'fit', 'fit.each', 'xit', 'xit.each', 'test', 'test.concurrent', 'test.concurrent.each', 'test.concurrent.only.each', 'test.concurrent.skip.each', 'test.each', 'test.only', 'test.only.each', 'test.skip', 'test.skip.each', 'test.todo', 'xtest', 'xtest.each', // todo: check if actually valid (not in docs)
|
|
68
|
+
'test.concurrent.skip', 'test.concurrent.only', 'it.concurrent.skip', 'it.concurrent.only'];
|
|
69
|
+
|
|
70
|
+
const parseJestFnCall = (node, scope) => {
|
|
71
|
+
var _node$parent, _node$parent2, _resolved$original;
|
|
72
|
+
|
|
73
|
+
// ensure that we're at the "top" of the function call chain otherwise when
|
|
74
|
+
// parsing e.g. x().y.z(), we'll incorrectly find & parse "x()" even though
|
|
75
|
+
// the full chain is not a valid jest function call chain
|
|
76
|
+
if (((_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.type) === _utils.AST_NODE_TYPES.CallExpression || ((_node$parent2 = node.parent) === null || _node$parent2 === void 0 ? void 0 : _node$parent2.type) === _utils.AST_NODE_TYPES.MemberExpression) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const chain = getNodeChain(node);
|
|
81
|
+
|
|
82
|
+
if (chain.length === 0) {
|
|
83
|
+
return null;
|
|
84
|
+
} // ensure that the only call expression in the chain is at the end
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if (chain.slice(0, chain.length - 1).some(nod => {
|
|
88
|
+
var _nod$parent;
|
|
89
|
+
|
|
90
|
+
return ((_nod$parent = nod.parent) === null || _nod$parent === void 0 ? void 0 : _nod$parent.type) === _utils.AST_NODE_TYPES.CallExpression;
|
|
91
|
+
})) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const [first, ...rest] = chain;
|
|
96
|
+
const lastNode = chain[chain.length - 1]; // if we're an `each()`, ensure we're the outer CallExpression (i.e `.each()()`)
|
|
97
|
+
|
|
98
|
+
if ((0, _utils2.isSupportedAccessor)(lastNode, 'each')) {
|
|
99
|
+
if (node.callee.type !== _utils.AST_NODE_TYPES.CallExpression && node.callee.type !== _utils.AST_NODE_TYPES.TaggedTemplateExpression) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const resolved = resolveToJestFn(scope, (0, _utils2.getAccessorValue)(first)); // we're not a jest function
|
|
105
|
+
|
|
106
|
+
if (!resolved) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const name = (_resolved$original = resolved.original) !== null && _resolved$original !== void 0 ? _resolved$original : resolved.local;
|
|
111
|
+
const links = [name, ...rest.map(link => (0, _utils2.getAccessorValue)(link))];
|
|
112
|
+
|
|
113
|
+
if (name !== 'jest' && !ValidJestFnCallChains.includes(links.join('.'))) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
name,
|
|
119
|
+
type: determineJestFnType(name),
|
|
120
|
+
head: { ...resolved,
|
|
121
|
+
node: first
|
|
122
|
+
},
|
|
123
|
+
members: rest
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
exports.parseJestFnCall = parseJestFnCall;
|
|
128
|
+
|
|
129
|
+
const describeImportDefAsImport = def => {
|
|
130
|
+
if (def.parent.type === _utils.AST_NODE_TYPES.TSImportEqualsDeclaration) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (def.node.type !== _utils.AST_NODE_TYPES.ImportSpecifier) {
|
|
135
|
+
return null;
|
|
136
|
+
} // we only care about value imports
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if (def.parent.importKind === 'type') {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
source: def.parent.source.value,
|
|
145
|
+
imported: def.node.imported.name,
|
|
146
|
+
local: def.node.local.name
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Attempts to find the node that represents the import source for the
|
|
151
|
+
* given expression node, if it looks like it's an import.
|
|
152
|
+
*
|
|
153
|
+
* If no such node can be found (e.g. because the expression doesn't look
|
|
154
|
+
* like an import), then `null` is returned instead.
|
|
155
|
+
*/
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
const findImportSourceNode = node => {
|
|
159
|
+
if (node.type === _utils.AST_NODE_TYPES.AwaitExpression) {
|
|
160
|
+
if (node.argument.type === _utils.AST_NODE_TYPES.ImportExpression) {
|
|
161
|
+
return node.argument.source;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (node.type === _utils.AST_NODE_TYPES.CallExpression && (0, _utils2.isIdentifier)(node.callee, 'require')) {
|
|
168
|
+
var _node$arguments$;
|
|
169
|
+
|
|
170
|
+
return (_node$arguments$ = node.arguments[0]) !== null && _node$arguments$ !== void 0 ? _node$arguments$ : null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return null;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const describeVariableDefAsImport = def => {
|
|
177
|
+
var _def$name$parent;
|
|
178
|
+
|
|
179
|
+
// make sure that we've actually being assigned a value
|
|
180
|
+
if (!def.node.init) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const sourceNode = findImportSourceNode(def.node.init);
|
|
185
|
+
|
|
186
|
+
if (!sourceNode || !(0, _utils2.isStringNode)(sourceNode)) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (((_def$name$parent = def.name.parent) === null || _def$name$parent === void 0 ? void 0 : _def$name$parent.type) !== _utils.AST_NODE_TYPES.Property) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (!(0, _utils2.isSupportedAccessor)(def.name.parent.key)) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
source: (0, _utils2.getStringValue)(sourceNode),
|
|
200
|
+
imported: (0, _utils2.getAccessorValue)(def.name.parent.key),
|
|
201
|
+
local: def.name.name
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
/**
|
|
205
|
+
* Attempts to describe a definition as an import if possible.
|
|
206
|
+
*
|
|
207
|
+
* If the definition is an import binding, it's described as you'd expect.
|
|
208
|
+
* If the definition is a variable, then we try and determine if it's either
|
|
209
|
+
* a dynamic `import()` or otherwise a call to `require()`.
|
|
210
|
+
*
|
|
211
|
+
* If it's neither of these, `null` is returned to indicate that the definition
|
|
212
|
+
* is not describable as an import of any kind.
|
|
213
|
+
*/
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
const describePossibleImportDef = def => {
|
|
217
|
+
if (def.type === 'Variable') {
|
|
218
|
+
return describeVariableDefAsImport(def);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (def.type === 'ImportBinding') {
|
|
222
|
+
return describeImportDefAsImport(def);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return null;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const collectReferences = scope => {
|
|
229
|
+
const locals = new Set();
|
|
230
|
+
const imports = new Map();
|
|
231
|
+
const unresolved = new Set();
|
|
232
|
+
let currentScope = scope;
|
|
233
|
+
|
|
234
|
+
while (currentScope !== null) {
|
|
235
|
+
for (const ref of currentScope.variables) {
|
|
236
|
+
if (ref.defs.length === 0) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const def = ref.defs[ref.defs.length - 1];
|
|
241
|
+
const importDetails = describePossibleImportDef(def);
|
|
242
|
+
|
|
243
|
+
if (importDetails) {
|
|
244
|
+
imports.set(importDetails.local, importDetails);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
locals.add(ref.name);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
for (const ref of currentScope.through) {
|
|
252
|
+
unresolved.add(ref.identifier.name);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
currentScope = currentScope.upper;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
locals,
|
|
260
|
+
imports,
|
|
261
|
+
unresolved
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const resolveToJestFn = (scope, identifier) => {
|
|
266
|
+
const references = collectReferences(scope);
|
|
267
|
+
const maybeImport = references.imports.get(identifier);
|
|
268
|
+
|
|
269
|
+
if (maybeImport) {
|
|
270
|
+
// the identifier is imported from @jest/globals,
|
|
271
|
+
// so return the original import name
|
|
272
|
+
if (maybeImport.source === '@jest/globals') {
|
|
273
|
+
return {
|
|
274
|
+
original: maybeImport.imported,
|
|
275
|
+
local: maybeImport.local,
|
|
276
|
+
type: 'import'
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return null;
|
|
281
|
+
} // the identifier was found as a local variable or function declaration
|
|
282
|
+
// meaning it's not a function from jest
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
if (references.locals.has(identifier)) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
original: null,
|
|
291
|
+
local: identifier,
|
|
292
|
+
type: 'global'
|
|
293
|
+
};
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const scopeHasLocalReference = (scope, referenceName) => {
|
|
297
|
+
const references = collectReferences(scope);
|
|
298
|
+
return (// referenceName was found as a local variable or function declaration.
|
|
299
|
+
references.locals.has(referenceName) || // referenceName was found as an imported identifier
|
|
300
|
+
references.imports.has(referenceName) || // referenceName was not found as an unresolved reference,
|
|
301
|
+
// meaning it is likely not an implicit global reference.
|
|
302
|
+
!references.unresolved.has(referenceName)
|
|
303
|
+
);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
exports.scopeHasLocalReference = scopeHasLocalReference;
|
package/lib/rules/utils.js
CHANGED
|
@@ -3,9 +3,33 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
+
var _exportNames = {
|
|
7
|
+
createRule: true,
|
|
8
|
+
followTypeAssertionChain: true,
|
|
9
|
+
isStringNode: true,
|
|
10
|
+
getStringValue: true,
|
|
11
|
+
hasOnlyOneArgument: true,
|
|
12
|
+
isIdentifier: true,
|
|
13
|
+
isSupportedAccessor: true,
|
|
14
|
+
getAccessorValue: true,
|
|
15
|
+
isExpectCall: true,
|
|
16
|
+
isExpectMember: true,
|
|
17
|
+
ModifierName: true,
|
|
18
|
+
EqualityMatcher: true,
|
|
19
|
+
isParsedEqualityMatcherCall: true,
|
|
20
|
+
parseExpectCall: true,
|
|
21
|
+
DescribeAlias: true,
|
|
22
|
+
TestCaseName: true,
|
|
23
|
+
HookName: true,
|
|
24
|
+
DescribeProperty: true,
|
|
25
|
+
TestCaseProperty: true,
|
|
26
|
+
getNodeName: true,
|
|
27
|
+
isFunction: true,
|
|
28
|
+
getTestCallExpressionsFromDeclaredVariables: true
|
|
29
|
+
};
|
|
6
30
|
exports.getAccessorValue = exports.followTypeAssertionChain = exports.createRule = exports.TestCaseProperty = exports.TestCaseName = exports.ModifierName = exports.HookName = exports.EqualityMatcher = exports.DescribeProperty = exports.DescribeAlias = void 0;
|
|
7
31
|
exports.getNodeName = getNodeName;
|
|
8
|
-
exports.
|
|
32
|
+
exports.parseExpectCall = exports.isSupportedAccessor = exports.isStringNode = exports.isParsedEqualityMatcherCall = exports.isIdentifier = exports.isFunction = exports.isExpectMember = exports.isExpectCall = exports.hasOnlyOneArgument = exports.getTestCallExpressionsFromDeclaredVariables = exports.getStringValue = void 0;
|
|
9
33
|
|
|
10
34
|
var _path = require("path");
|
|
11
35
|
|
|
@@ -13,6 +37,19 @@ var _utils = require("@typescript-eslint/utils");
|
|
|
13
37
|
|
|
14
38
|
var _package = require("../../package.json");
|
|
15
39
|
|
|
40
|
+
var _parseJestFnCall = require("./utils/parseJestFnCall");
|
|
41
|
+
|
|
42
|
+
Object.keys(_parseJestFnCall).forEach(function (key) {
|
|
43
|
+
if (key === "default" || key === "__esModule") return;
|
|
44
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
45
|
+
if (key in exports && exports[key] === _parseJestFnCall[key]) return;
|
|
46
|
+
Object.defineProperty(exports, key, {
|
|
47
|
+
enumerable: true,
|
|
48
|
+
get: function () {
|
|
49
|
+
return _parseJestFnCall[key];
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
16
53
|
const REPO_URL = 'https://github.com/jest-community/eslint-plugin-jest';
|
|
17
54
|
|
|
18
55
|
const createRule = _utils.ESLintUtils.RuleCreator(name => {
|
|
@@ -384,261 +421,12 @@ const isFunction = node => node.type === _utils.AST_NODE_TYPES.FunctionExpressio
|
|
|
384
421
|
|
|
385
422
|
exports.isFunction = isFunction;
|
|
386
423
|
|
|
387
|
-
const isHookCall = (node, scope) => {
|
|
388
|
-
let name = findFirstCallPropertyName(node, []);
|
|
389
|
-
|
|
390
|
-
if (!name) {
|
|
391
|
-
return false;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
name = resolveToJestFn(scope, name);
|
|
395
|
-
return name !== null && HookName.hasOwnProperty(name);
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
exports.isHookCall = isHookCall;
|
|
399
|
-
|
|
400
424
|
const getTestCallExpressionsFromDeclaredVariables = (declaredVariables, scope) => {
|
|
401
425
|
return declaredVariables.reduce((acc, {
|
|
402
426
|
references
|
|
403
427
|
}) => acc.concat(references.map(({
|
|
404
428
|
identifier
|
|
405
|
-
}) => identifier.parent).filter(node => !!node && node.type === _utils.AST_NODE_TYPES.CallExpression &&
|
|
406
|
-
};
|
|
407
|
-
/**
|
|
408
|
-
* Checks if the given `node` is a *call* to a test case function that would
|
|
409
|
-
* result in tests being run by `jest`.
|
|
410
|
-
*
|
|
411
|
-
* Note that `.each()` does not count as a call in this context, as it will not
|
|
412
|
-
* result in `jest` running any tests.
|
|
413
|
-
*/
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
exports.getTestCallExpressionsFromDeclaredVariables = getTestCallExpressionsFromDeclaredVariables;
|
|
417
|
-
|
|
418
|
-
const isTestCaseCall = (node, scope) => {
|
|
419
|
-
let name = findFirstCallPropertyName(node, Object.keys(TestCaseProperty));
|
|
420
|
-
|
|
421
|
-
if (!name) {
|
|
422
|
-
return false;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
name = resolveToJestFn(scope, name);
|
|
426
|
-
return name !== null && TestCaseName.hasOwnProperty(name);
|
|
427
|
-
};
|
|
428
|
-
|
|
429
|
-
exports.isTestCaseCall = isTestCaseCall;
|
|
430
|
-
|
|
431
|
-
const findFirstCallPropertyName = (node, properties) => {
|
|
432
|
-
if (isIdentifier(node.callee)) {
|
|
433
|
-
return node.callee.name;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
const callee = node.callee.type === _utils.AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === _utils.AST_NODE_TYPES.CallExpression ? node.callee.callee : node.callee;
|
|
437
|
-
|
|
438
|
-
if (callee.type === _utils.AST_NODE_TYPES.MemberExpression && isSupportedAccessor(callee.property) && properties.includes(getAccessorValue(callee.property))) {
|
|
439
|
-
// if we're an `each()`, ensure we're the outer CallExpression (i.e `.each()()`)
|
|
440
|
-
if (getAccessorValue(callee.property) === 'each' && node.callee.type !== _utils.AST_NODE_TYPES.TaggedTemplateExpression && node.callee.type !== _utils.AST_NODE_TYPES.CallExpression) {
|
|
441
|
-
return null;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
const nod = callee.object.type === _utils.AST_NODE_TYPES.MemberExpression ? callee.object.object : callee.object;
|
|
445
|
-
|
|
446
|
-
if (isSupportedAccessor(nod)) {
|
|
447
|
-
return getAccessorValue(nod);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
return null;
|
|
452
|
-
};
|
|
453
|
-
/**
|
|
454
|
-
* Checks if the given `node` is a *call* to a `describe` function that would
|
|
455
|
-
* result in a `describe` block being created by `jest`.
|
|
456
|
-
*
|
|
457
|
-
* Note that `.each()` does not count as a call in this context, as it will not
|
|
458
|
-
* result in `jest` creating any `describe` blocks.
|
|
459
|
-
*/
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const isDescribeCall = (node, scope) => {
|
|
463
|
-
let name = findFirstCallPropertyName(node, Object.keys(DescribeProperty));
|
|
464
|
-
|
|
465
|
-
if (!name) {
|
|
466
|
-
return false;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
name = resolveToJestFn(scope, name);
|
|
470
|
-
return name !== null && DescribeAlias.hasOwnProperty(name);
|
|
471
|
-
};
|
|
472
|
-
|
|
473
|
-
exports.isDescribeCall = isDescribeCall;
|
|
474
|
-
|
|
475
|
-
const describeImportDefAsImport = def => {
|
|
476
|
-
if (def.parent.type === _utils.AST_NODE_TYPES.TSImportEqualsDeclaration) {
|
|
477
|
-
return null;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if (def.node.type !== _utils.AST_NODE_TYPES.ImportSpecifier) {
|
|
481
|
-
return null;
|
|
482
|
-
} // we only care about value imports
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
if (def.parent.importKind === 'type') {
|
|
486
|
-
return null;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
return {
|
|
490
|
-
source: def.parent.source.value,
|
|
491
|
-
imported: def.node.imported.name,
|
|
492
|
-
local: def.node.local.name
|
|
493
|
-
};
|
|
494
|
-
};
|
|
495
|
-
/**
|
|
496
|
-
* Attempts to find the node that represents the import source for the
|
|
497
|
-
* given expression node, if it looks like it's an import.
|
|
498
|
-
*
|
|
499
|
-
* If no such node can be found (e.g. because the expression doesn't look
|
|
500
|
-
* like an import), then `null` is returned instead.
|
|
501
|
-
*/
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
const findImportSourceNode = node => {
|
|
505
|
-
if (node.type === _utils.AST_NODE_TYPES.AwaitExpression) {
|
|
506
|
-
if (node.argument.type === _utils.AST_NODE_TYPES.ImportExpression) {
|
|
507
|
-
return node.argument.source;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
return null;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
if (node.type === _utils.AST_NODE_TYPES.CallExpression && isIdentifier(node.callee, 'require')) {
|
|
514
|
-
var _node$arguments$;
|
|
515
|
-
|
|
516
|
-
return (_node$arguments$ = node.arguments[0]) !== null && _node$arguments$ !== void 0 ? _node$arguments$ : null;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
return null;
|
|
429
|
+
}) => identifier.parent).filter(node => !!node && node.type === _utils.AST_NODE_TYPES.CallExpression && (0, _parseJestFnCall.isTypeOfJestFnCall)(node, scope, ['test']))), []);
|
|
520
430
|
};
|
|
521
431
|
|
|
522
|
-
|
|
523
|
-
var _def$name$parent;
|
|
524
|
-
|
|
525
|
-
// make sure that we've actually being assigned a value
|
|
526
|
-
if (!def.node.init) {
|
|
527
|
-
return null;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const sourceNode = findImportSourceNode(def.node.init);
|
|
531
|
-
|
|
532
|
-
if (!sourceNode || !isStringNode(sourceNode)) {
|
|
533
|
-
return null;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
if (((_def$name$parent = def.name.parent) === null || _def$name$parent === void 0 ? void 0 : _def$name$parent.type) !== _utils.AST_NODE_TYPES.Property) {
|
|
537
|
-
return null;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
if (!isSupportedAccessor(def.name.parent.key)) {
|
|
541
|
-
return null;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
return {
|
|
545
|
-
source: getStringValue(sourceNode),
|
|
546
|
-
imported: getAccessorValue(def.name.parent.key),
|
|
547
|
-
local: def.name.name
|
|
548
|
-
};
|
|
549
|
-
};
|
|
550
|
-
/**
|
|
551
|
-
* Attempts to describe a definition as an import if possible.
|
|
552
|
-
*
|
|
553
|
-
* If the definition is an import binding, it's described as you'd expect.
|
|
554
|
-
* If the definition is a variable, then we try and determine if it's either
|
|
555
|
-
* a dynamic `import()` or otherwise a call to `require()`.
|
|
556
|
-
*
|
|
557
|
-
* If it's neither of these, `null` is returned to indicate that the definition
|
|
558
|
-
* is not describable as an import of any kind.
|
|
559
|
-
*/
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
const describePossibleImportDef = def => {
|
|
563
|
-
if (def.type === 'Variable') {
|
|
564
|
-
return describeVariableDefAsImport(def);
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
if (def.type === 'ImportBinding') {
|
|
568
|
-
return describeImportDefAsImport(def);
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
return null;
|
|
572
|
-
};
|
|
573
|
-
|
|
574
|
-
const collectReferences = scope => {
|
|
575
|
-
const locals = new Set();
|
|
576
|
-
const imports = new Map();
|
|
577
|
-
const unresolved = new Set();
|
|
578
|
-
let currentScope = scope;
|
|
579
|
-
|
|
580
|
-
while (currentScope !== null) {
|
|
581
|
-
for (const ref of currentScope.variables) {
|
|
582
|
-
if (ref.defs.length === 0) {
|
|
583
|
-
continue;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
const def = ref.defs[ref.defs.length - 1];
|
|
587
|
-
const importDetails = describePossibleImportDef(def);
|
|
588
|
-
|
|
589
|
-
if (importDetails) {
|
|
590
|
-
imports.set(importDetails.local, importDetails);
|
|
591
|
-
continue;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
locals.add(ref.name);
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
for (const ref of currentScope.through) {
|
|
598
|
-
unresolved.add(ref.identifier.name);
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
currentScope = currentScope.upper;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
return {
|
|
605
|
-
locals,
|
|
606
|
-
imports,
|
|
607
|
-
unresolved
|
|
608
|
-
};
|
|
609
|
-
};
|
|
610
|
-
|
|
611
|
-
const scopeHasLocalReference = (scope, referenceName) => {
|
|
612
|
-
const references = collectReferences(scope);
|
|
613
|
-
return (// referenceName was found as a local variable or function declaration.
|
|
614
|
-
references.locals.has(referenceName) || // referenceName was found as an imported identifier
|
|
615
|
-
references.imports.has(referenceName) || // referenceName was not found as an unresolved reference,
|
|
616
|
-
// meaning it is likely not an implicit global reference.
|
|
617
|
-
!references.unresolved.has(referenceName)
|
|
618
|
-
);
|
|
619
|
-
};
|
|
620
|
-
|
|
621
|
-
exports.scopeHasLocalReference = scopeHasLocalReference;
|
|
622
|
-
|
|
623
|
-
const resolveToJestFn = (scope, identifier) => {
|
|
624
|
-
const references = collectReferences(scope);
|
|
625
|
-
const maybeImport = references.imports.get(identifier);
|
|
626
|
-
|
|
627
|
-
if (maybeImport) {
|
|
628
|
-
// the identifier is imported from @jest/globals,
|
|
629
|
-
// so return the original import name
|
|
630
|
-
if (maybeImport.source === '@jest/globals') {
|
|
631
|
-
return maybeImport.imported;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
return null;
|
|
635
|
-
} // the identifier was found as a local variable or function declaration
|
|
636
|
-
// meaning it's not a function from jest
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
if (references.locals.has(identifier)) {
|
|
640
|
-
return null;
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
return identifier;
|
|
644
|
-
};
|
|
432
|
+
exports.getTestCallExpressionsFromDeclaredVariables = getTestCallExpressionsFromDeclaredVariables;
|
|
@@ -41,7 +41,9 @@ var _default = (0, _utils2.createRule)({
|
|
|
41
41
|
create(context) {
|
|
42
42
|
return {
|
|
43
43
|
CallExpression(node) {
|
|
44
|
-
|
|
44
|
+
const jestFnCall = (0, _utils2.parseJestFnCall)(node, context.getScope());
|
|
45
|
+
|
|
46
|
+
if ((jestFnCall === null || jestFnCall === void 0 ? void 0 : jestFnCall.type) !== 'describe') {
|
|
45
47
|
return;
|
|
46
48
|
}
|
|
47
49
|
|
|
@@ -77,7 +79,7 @@ var _default = (0, _utils2.createRule)({
|
|
|
77
79
|
});
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
if (
|
|
82
|
+
if (jestFnCall.members.every(s => (0, _utils2.getAccessorValue)(s) !== 'each') && callback.params.length) {
|
|
81
83
|
context.report({
|
|
82
84
|
messageId: 'unexpectedDescribeArgument',
|
|
83
85
|
loc: paramsLocation(callback.params)
|