eslint-plugin-jest 26.1.1 → 26.5.3
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 +50 -2
- package/docs/rules/no-conditional-expect.md +1 -1
- package/docs/rules/no-deprecated-functions.md +1 -2
- package/docs/rules/no-identical-title.md +1 -1
- package/docs/rules/no-jasmine-globals.md +2 -2
- package/docs/rules/no-jest-import.md +1 -1
- package/docs/rules/no-large-snapshots.md +2 -2
- package/docs/rules/no-standalone-expect.md +1 -1
- package/docs/rules/prefer-comparison-matcher.md +1 -1
- package/docs/rules/prefer-equality-matcher.md +1 -1
- package/docs/rules/prefer-expect-assertions.md +1 -1
- package/docs/rules/prefer-hooks-in-order.md +133 -0
- package/docs/rules/prefer-hooks-on-top.md +1 -1
- package/docs/rules/prefer-lowercase-title.md +2 -2
- package/docs/rules/valid-expect.md +2 -2
- package/lib/rules/consistent-test-it.js +9 -8
- package/lib/rules/expect-expect.js +4 -4
- package/lib/rules/max-nested-describe.js +2 -2
- package/lib/rules/no-alias-methods.js +1 -1
- package/lib/rules/no-conditional-expect.js +3 -3
- package/lib/rules/no-conditional-in-test.js +2 -2
- package/lib/rules/no-deprecated-functions.js +1 -3
- package/lib/rules/no-disabled-tests.js +36 -61
- package/lib/rules/no-done-callback.js +6 -4
- package/lib/rules/no-duplicate-hooks.js +23 -23
- package/lib/rules/no-export.js +1 -1
- package/lib/rules/no-focused-tests.js +40 -43
- package/lib/rules/no-hooks.js +4 -2
- package/lib/rules/no-identical-title.js +10 -7
- package/lib/rules/no-if.js +6 -4
- package/lib/rules/no-restricted-matchers.js +39 -43
- package/lib/rules/no-standalone-expect.js +5 -5
- package/lib/rules/no-test-prefixes.js +12 -20
- package/lib/rules/no-test-return-statement.js +5 -2
- package/lib/rules/prefer-called-with.js +14 -13
- package/lib/rules/prefer-comparison-matcher.js +9 -4
- package/lib/rules/prefer-equality-matcher.js +15 -5
- package/lib/rules/prefer-expect-assertions.js +4 -2
- package/lib/rules/prefer-hooks-in-order.js +84 -0
- package/lib/rules/prefer-hooks-on-top.js +2 -2
- package/lib/rules/prefer-lowercase-title.js +12 -22
- package/lib/rules/prefer-snapshot-hint.js +34 -3
- package/lib/rules/prefer-strict-equal.js +1 -1
- package/lib/rules/prefer-to-be.js +1 -1
- package/lib/rules/prefer-todo.js +22 -7
- package/lib/rules/require-hook.js +7 -7
- package/lib/rules/require-top-level-describe.js +10 -4
- package/lib/rules/utils/accessors.js +135 -0
- package/lib/rules/{detectJestVersion.js → utils/detectJestVersion.js} +0 -0
- package/lib/rules/utils/followTypeAssertionChain.js +14 -0
- package/lib/rules/utils/index.js +83 -0
- package/lib/rules/utils/misc.js +120 -0
- package/lib/rules/utils/parseExpectCall.js +145 -0
- package/lib/rules/utils/parseJestFnCall.js +323 -0
- package/lib/rules/valid-describe-callback.js +4 -2
- package/lib/rules/valid-expect-in-promise.js +13 -15
- package/lib/rules/valid-title.js +8 -6
- package/package.json +9 -12
- package/lib/rules/utils.js +0 -513
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.createRule = exports.TestCaseName = exports.HookName = exports.DescribeAlias = void 0;
|
|
7
|
+
exports.getNodeName = getNodeName;
|
|
8
|
+
exports.replaceAccessorFixer = exports.isFunction = exports.hasOnlyOneArgument = exports.getTestCallExpressionsFromDeclaredVariables = void 0;
|
|
9
|
+
|
|
10
|
+
var _path = require("path");
|
|
11
|
+
|
|
12
|
+
var _utils = require("@typescript-eslint/utils");
|
|
13
|
+
|
|
14
|
+
var _package = require("../../../package.json");
|
|
15
|
+
|
|
16
|
+
var _accessors = require("./accessors");
|
|
17
|
+
|
|
18
|
+
var _parseJestFnCall = require("./parseJestFnCall");
|
|
19
|
+
|
|
20
|
+
const REPO_URL = 'https://github.com/jest-community/eslint-plugin-jest';
|
|
21
|
+
|
|
22
|
+
const createRule = _utils.ESLintUtils.RuleCreator(name => {
|
|
23
|
+
const ruleName = (0, _path.parse)(name).name;
|
|
24
|
+
return `${REPO_URL}/blob/v${_package.version}/docs/rules/${ruleName}.md`;
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Represents a `MemberExpression` with a "known" `property`.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
exports.createRule = createRule;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Guards that the given `call` has only one `argument`.
|
|
35
|
+
*
|
|
36
|
+
* @param {CallExpression} call
|
|
37
|
+
*
|
|
38
|
+
* @return {call is CallExpressionWithSingleArgument}
|
|
39
|
+
*/
|
|
40
|
+
const hasOnlyOneArgument = call => call.arguments.length === 1;
|
|
41
|
+
|
|
42
|
+
exports.hasOnlyOneArgument = hasOnlyOneArgument;
|
|
43
|
+
let DescribeAlias;
|
|
44
|
+
exports.DescribeAlias = DescribeAlias;
|
|
45
|
+
|
|
46
|
+
(function (DescribeAlias) {
|
|
47
|
+
DescribeAlias["describe"] = "describe";
|
|
48
|
+
DescribeAlias["fdescribe"] = "fdescribe";
|
|
49
|
+
DescribeAlias["xdescribe"] = "xdescribe";
|
|
50
|
+
})(DescribeAlias || (exports.DescribeAlias = DescribeAlias = {}));
|
|
51
|
+
|
|
52
|
+
let TestCaseName;
|
|
53
|
+
exports.TestCaseName = TestCaseName;
|
|
54
|
+
|
|
55
|
+
(function (TestCaseName) {
|
|
56
|
+
TestCaseName["fit"] = "fit";
|
|
57
|
+
TestCaseName["it"] = "it";
|
|
58
|
+
TestCaseName["test"] = "test";
|
|
59
|
+
TestCaseName["xit"] = "xit";
|
|
60
|
+
TestCaseName["xtest"] = "xtest";
|
|
61
|
+
})(TestCaseName || (exports.TestCaseName = TestCaseName = {}));
|
|
62
|
+
|
|
63
|
+
let HookName;
|
|
64
|
+
exports.HookName = HookName;
|
|
65
|
+
|
|
66
|
+
(function (HookName) {
|
|
67
|
+
HookName["beforeAll"] = "beforeAll";
|
|
68
|
+
HookName["beforeEach"] = "beforeEach";
|
|
69
|
+
HookName["afterAll"] = "afterAll";
|
|
70
|
+
HookName["afterEach"] = "afterEach";
|
|
71
|
+
})(HookName || (exports.HookName = HookName = {}));
|
|
72
|
+
|
|
73
|
+
const joinNames = (a, b) => a && b ? `${a}.${b}` : null;
|
|
74
|
+
|
|
75
|
+
function getNodeName(node) {
|
|
76
|
+
if ((0, _accessors.isSupportedAccessor)(node)) {
|
|
77
|
+
return (0, _accessors.getAccessorValue)(node);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
switch (node.type) {
|
|
81
|
+
case _utils.AST_NODE_TYPES.TaggedTemplateExpression:
|
|
82
|
+
return getNodeName(node.tag);
|
|
83
|
+
|
|
84
|
+
case _utils.AST_NODE_TYPES.MemberExpression:
|
|
85
|
+
return joinNames(getNodeName(node.object), getNodeName(node.property));
|
|
86
|
+
|
|
87
|
+
case _utils.AST_NODE_TYPES.NewExpression:
|
|
88
|
+
case _utils.AST_NODE_TYPES.CallExpression:
|
|
89
|
+
return getNodeName(node.callee);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const isFunction = node => node.type === _utils.AST_NODE_TYPES.FunctionExpression || node.type === _utils.AST_NODE_TYPES.ArrowFunctionExpression;
|
|
96
|
+
|
|
97
|
+
exports.isFunction = isFunction;
|
|
98
|
+
|
|
99
|
+
const getTestCallExpressionsFromDeclaredVariables = (declaredVariables, context) => {
|
|
100
|
+
return declaredVariables.reduce((acc, {
|
|
101
|
+
references
|
|
102
|
+
}) => acc.concat(references.map(({
|
|
103
|
+
identifier
|
|
104
|
+
}) => identifier.parent).filter(node => (node === null || node === void 0 ? void 0 : node.type) === _utils.AST_NODE_TYPES.CallExpression && (0, _parseJestFnCall.isTypeOfJestFnCall)(node, context, ['test']))), []);
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Replaces an accessor node with the given `text`, surrounding it in quotes if required.
|
|
108
|
+
*
|
|
109
|
+
* This ensures that fixes produce valid code when replacing both dot-based and
|
|
110
|
+
* bracket-based property accessors.
|
|
111
|
+
*/
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
exports.getTestCallExpressionsFromDeclaredVariables = getTestCallExpressionsFromDeclaredVariables;
|
|
115
|
+
|
|
116
|
+
const replaceAccessorFixer = (fixer, node, text) => {
|
|
117
|
+
return fixer.replaceText(node, node.type === _utils.AST_NODE_TYPES.Identifier ? text : `'${text}'`);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
exports.replaceAccessorFixer = replaceAccessorFixer;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.parseExpectCall = exports.isParsedEqualityMatcherCall = exports.isExpectMember = exports.isExpectCall = exports.ModifierName = exports.EqualityMatcher = void 0;
|
|
7
|
+
|
|
8
|
+
var _utils = require("@typescript-eslint/utils");
|
|
9
|
+
|
|
10
|
+
var _utils2 = require("../utils");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Checks if the given `node` is a valid `ExpectCall`.
|
|
14
|
+
*
|
|
15
|
+
* In order to be an `ExpectCall`, the `node` must:
|
|
16
|
+
* * be a `CallExpression`,
|
|
17
|
+
* * have an accessor named 'expect',
|
|
18
|
+
* * have a `parent`.
|
|
19
|
+
*
|
|
20
|
+
* @param {Node} node
|
|
21
|
+
*
|
|
22
|
+
* @return {node is ExpectCall}
|
|
23
|
+
*/
|
|
24
|
+
const isExpectCall = node => node.type === _utils.AST_NODE_TYPES.CallExpression && (0, _utils2.isSupportedAccessor)(node.callee, 'expect') && node.parent !== undefined;
|
|
25
|
+
|
|
26
|
+
exports.isExpectCall = isExpectCall;
|
|
27
|
+
|
|
28
|
+
const isExpectMember = (node, name) => node.type === _utils.AST_NODE_TYPES.MemberExpression && (0, _utils2.isSupportedAccessor)(node.property, name);
|
|
29
|
+
/**
|
|
30
|
+
* Represents all the jest matchers.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
exports.isExpectMember = isExpectMember;
|
|
35
|
+
let ModifierName;
|
|
36
|
+
exports.ModifierName = ModifierName;
|
|
37
|
+
|
|
38
|
+
(function (ModifierName) {
|
|
39
|
+
ModifierName["not"] = "not";
|
|
40
|
+
ModifierName["rejects"] = "rejects";
|
|
41
|
+
ModifierName["resolves"] = "resolves";
|
|
42
|
+
})(ModifierName || (exports.ModifierName = ModifierName = {}));
|
|
43
|
+
|
|
44
|
+
let EqualityMatcher;
|
|
45
|
+
exports.EqualityMatcher = EqualityMatcher;
|
|
46
|
+
|
|
47
|
+
(function (EqualityMatcher) {
|
|
48
|
+
EqualityMatcher["toBe"] = "toBe";
|
|
49
|
+
EqualityMatcher["toEqual"] = "toEqual";
|
|
50
|
+
EqualityMatcher["toStrictEqual"] = "toStrictEqual";
|
|
51
|
+
})(EqualityMatcher || (exports.EqualityMatcher = EqualityMatcher = {}));
|
|
52
|
+
|
|
53
|
+
const isParsedEqualityMatcherCall = (matcher, name) => (name ? matcher.name === name : EqualityMatcher.hasOwnProperty(matcher.name)) && matcher.arguments !== null && matcher.arguments.length === 1;
|
|
54
|
+
/**
|
|
55
|
+
* Represents a parsed expect matcher, such as `toBe`, `toContain`, and so on.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
exports.isParsedEqualityMatcherCall = isParsedEqualityMatcherCall;
|
|
60
|
+
|
|
61
|
+
const parseExpectMember = expectMember => ({
|
|
62
|
+
name: (0, _utils2.getAccessorValue)(expectMember.property),
|
|
63
|
+
node: expectMember
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const reparseAsMatcher = parsedMember => ({ ...parsedMember,
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The arguments being passed to this `Matcher`, if any.
|
|
70
|
+
*
|
|
71
|
+
* If this matcher isn't called, this will be `null`.
|
|
72
|
+
*/
|
|
73
|
+
arguments: parsedMember.node.parent.type === _utils.AST_NODE_TYPES.CallExpression ? parsedMember.node.parent.arguments : null
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* Re-parses the given `parsedMember` as a `ParsedExpectModifier`.
|
|
77
|
+
*
|
|
78
|
+
* If the given `parsedMember` does not have a `name` of a valid `Modifier`,
|
|
79
|
+
* an exception will be thrown.
|
|
80
|
+
*
|
|
81
|
+
* @param {ParsedExpectMember<ModifierName>} parsedMember
|
|
82
|
+
*
|
|
83
|
+
* @return {ParsedExpectModifier}
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
const reparseMemberAsModifier = parsedMember => {
|
|
88
|
+
if (isSpecificMember(parsedMember, ModifierName.not)) {
|
|
89
|
+
return parsedMember;
|
|
90
|
+
}
|
|
91
|
+
/* istanbul ignore if */
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
if (!isSpecificMember(parsedMember, ModifierName.resolves) && !isSpecificMember(parsedMember, ModifierName.rejects)) {
|
|
95
|
+
// ts doesn't think that the ModifierName.not check is the direct inverse as the above two checks
|
|
96
|
+
// todo: impossible at runtime, but can't be typed w/o negation support
|
|
97
|
+
throw new Error(`modifier name must be either "${ModifierName.resolves}" or "${ModifierName.rejects}" (got "${parsedMember.name}")`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const negation = isExpectMember(parsedMember.node.parent, ModifierName.not) ? parsedMember.node.parent : undefined;
|
|
101
|
+
return { ...parsedMember,
|
|
102
|
+
negation
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const isSpecificMember = (member, specific) => member.name === specific;
|
|
107
|
+
/**
|
|
108
|
+
* Checks if the given `ParsedExpectMember` should be re-parsed as an `ParsedExpectModifier`.
|
|
109
|
+
*
|
|
110
|
+
* @param {ParsedExpectMember} member
|
|
111
|
+
*
|
|
112
|
+
* @return {member is ParsedExpectMember<ModifierName>}
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
const shouldBeParsedExpectModifier = member => ModifierName.hasOwnProperty(member.name);
|
|
117
|
+
|
|
118
|
+
const parseExpectCall = expect => {
|
|
119
|
+
const expectation = {
|
|
120
|
+
expect
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if (!isExpectMember(expect.parent)) {
|
|
124
|
+
return expectation;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const parsedMember = parseExpectMember(expect.parent);
|
|
128
|
+
|
|
129
|
+
if (!shouldBeParsedExpectModifier(parsedMember)) {
|
|
130
|
+
expectation.matcher = reparseAsMatcher(parsedMember);
|
|
131
|
+
return expectation;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const modifier = expectation.modifier = reparseMemberAsModifier(parsedMember);
|
|
135
|
+
const memberNode = modifier.negation || modifier.node;
|
|
136
|
+
|
|
137
|
+
if (!isExpectMember(memberNode.parent)) {
|
|
138
|
+
return expectation;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
expectation.matcher = reparseAsMatcher(parseExpectMember(memberNode.parent));
|
|
142
|
+
return expectation;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
exports.parseExpectCall = parseExpectCall;
|
|
@@ -0,0 +1,323 @@
|
|
|
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, context, types) => {
|
|
14
|
+
const jestFnCall = parseJestFnCall(node, context);
|
|
15
|
+
return jestFnCall !== null && types.includes(jestFnCall.type);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
exports.isTypeOfJestFnCall = isTypeOfJestFnCall;
|
|
19
|
+
|
|
20
|
+
const joinChains = (a, b) => a && b ? [...a, ...b] : null;
|
|
21
|
+
|
|
22
|
+
function getNodeChain(node) {
|
|
23
|
+
if ((0, _utils2.isSupportedAccessor)(node)) {
|
|
24
|
+
return [node];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
switch (node.type) {
|
|
28
|
+
case _utils.AST_NODE_TYPES.TaggedTemplateExpression:
|
|
29
|
+
return getNodeChain(node.tag);
|
|
30
|
+
|
|
31
|
+
case _utils.AST_NODE_TYPES.MemberExpression:
|
|
32
|
+
return joinChains(getNodeChain(node.object), getNodeChain(node.property));
|
|
33
|
+
|
|
34
|
+
case _utils.AST_NODE_TYPES.CallExpression:
|
|
35
|
+
return getNodeChain(node.callee);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const determineJestFnType = name => {
|
|
42
|
+
// if (name === 'expect') {
|
|
43
|
+
// return 'expect';
|
|
44
|
+
// }
|
|
45
|
+
if (name === 'jest') {
|
|
46
|
+
return 'jest';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (_utils2.DescribeAlias.hasOwnProperty(name)) {
|
|
50
|
+
return 'describe';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (_utils2.TestCaseName.hasOwnProperty(name)) {
|
|
54
|
+
return 'test';
|
|
55
|
+
}
|
|
56
|
+
/* istanbul ignore else */
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if (_utils2.HookName.hasOwnProperty(name)) {
|
|
60
|
+
return 'hook';
|
|
61
|
+
}
|
|
62
|
+
/* istanbul ignore next */
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
return 'unknown';
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
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.only.failing', 'it.skip', 'it.skip.each', 'it.skip.failing', 'it.todo', 'fit', 'fit.each', 'fit.failing', 'xit', 'xit.each', 'xit.failing', 'test', 'test.concurrent', 'test.concurrent.each', 'test.concurrent.only.each', 'test.concurrent.skip.each', 'test.each', 'test.failing', 'test.only', 'test.only.each', 'test.only.failing', 'test.skip', 'test.skip.each', 'test.skip.failing', 'test.todo', 'xtest', 'xtest.each', 'xtest.failing'];
|
|
69
|
+
|
|
70
|
+
const resolvePossibleAliasedGlobal = (global, context) => {
|
|
71
|
+
var _context$settings$jes, _context$settings$jes2;
|
|
72
|
+
|
|
73
|
+
const globalAliases = (_context$settings$jes = (_context$settings$jes2 = context.settings.jest) === null || _context$settings$jes2 === void 0 ? void 0 : _context$settings$jes2.globalAliases) !== null && _context$settings$jes !== void 0 ? _context$settings$jes : {};
|
|
74
|
+
const alias = Object.entries(globalAliases).find(([, aliases]) => aliases.includes(global));
|
|
75
|
+
|
|
76
|
+
if (alias) {
|
|
77
|
+
return alias[0];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return null;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const parseJestFnCall = (node, context) => {
|
|
84
|
+
var _node$parent, _node$parent2, _resolved$original;
|
|
85
|
+
|
|
86
|
+
// ensure that we're at the "top" of the function call chain otherwise when
|
|
87
|
+
// parsing e.g. x().y.z(), we'll incorrectly find & parse "x()" even though
|
|
88
|
+
// the full chain is not a valid jest function call chain
|
|
89
|
+
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) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const chain = getNodeChain(node);
|
|
94
|
+
|
|
95
|
+
if (!(chain !== null && chain !== void 0 && chain.length)) {
|
|
96
|
+
return null;
|
|
97
|
+
} // check that every link in the chain except the last is a member expression
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if (chain.slice(0, chain.length - 1).some(nod => {
|
|
101
|
+
var _nod$parent;
|
|
102
|
+
|
|
103
|
+
return ((_nod$parent = nod.parent) === null || _nod$parent === void 0 ? void 0 : _nod$parent.type) !== _utils.AST_NODE_TYPES.MemberExpression;
|
|
104
|
+
})) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const [first, ...rest] = chain;
|
|
109
|
+
const lastLink = (0, _utils2.getAccessorValue)(chain[chain.length - 1]); // if we're an `each()`, ensure we're the outer CallExpression (i.e `.each()()`)
|
|
110
|
+
|
|
111
|
+
if (lastLink === 'each') {
|
|
112
|
+
if (node.callee.type !== _utils.AST_NODE_TYPES.CallExpression && node.callee.type !== _utils.AST_NODE_TYPES.TaggedTemplateExpression) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (node.callee.type === _utils.AST_NODE_TYPES.TaggedTemplateExpression && lastLink !== 'each') {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const resolved = resolveToJestFn(context, (0, _utils2.getAccessorValue)(first)); // we're not a jest function
|
|
122
|
+
|
|
123
|
+
if (!resolved) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const name = (_resolved$original = resolved.original) !== null && _resolved$original !== void 0 ? _resolved$original : resolved.local;
|
|
128
|
+
const links = [name, ...rest.map(link => (0, _utils2.getAccessorValue)(link))];
|
|
129
|
+
|
|
130
|
+
if (name !== 'jest' && !ValidJestFnCallChains.includes(links.join('.'))) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
name,
|
|
136
|
+
type: determineJestFnType(name),
|
|
137
|
+
head: { ...resolved,
|
|
138
|
+
node: first
|
|
139
|
+
},
|
|
140
|
+
members: rest
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
exports.parseJestFnCall = parseJestFnCall;
|
|
145
|
+
|
|
146
|
+
const describeImportDefAsImport = def => {
|
|
147
|
+
if (def.parent.type === _utils.AST_NODE_TYPES.TSImportEqualsDeclaration) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (def.node.type !== _utils.AST_NODE_TYPES.ImportSpecifier) {
|
|
152
|
+
return null;
|
|
153
|
+
} // we only care about value imports
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if (def.parent.importKind === 'type') {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
source: def.parent.source.value,
|
|
162
|
+
imported: def.node.imported.name,
|
|
163
|
+
local: def.node.local.name
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* Attempts to find the node that represents the import source for the
|
|
168
|
+
* given expression node, if it looks like it's an import.
|
|
169
|
+
*
|
|
170
|
+
* If no such node can be found (e.g. because the expression doesn't look
|
|
171
|
+
* like an import), then `null` is returned instead.
|
|
172
|
+
*/
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
const findImportSourceNode = node => {
|
|
176
|
+
if (node.type === _utils.AST_NODE_TYPES.AwaitExpression) {
|
|
177
|
+
if (node.argument.type === _utils.AST_NODE_TYPES.ImportExpression) {
|
|
178
|
+
return node.argument.source;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (node.type === _utils.AST_NODE_TYPES.CallExpression && (0, _utils2.isIdentifier)(node.callee, 'require')) {
|
|
185
|
+
var _node$arguments$;
|
|
186
|
+
|
|
187
|
+
return (_node$arguments$ = node.arguments[0]) !== null && _node$arguments$ !== void 0 ? _node$arguments$ : null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return null;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const describeVariableDefAsImport = def => {
|
|
194
|
+
var _def$name$parent;
|
|
195
|
+
|
|
196
|
+
// make sure that we've actually being assigned a value
|
|
197
|
+
if (!def.node.init) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const sourceNode = findImportSourceNode(def.node.init);
|
|
202
|
+
|
|
203
|
+
if (!sourceNode || !(0, _utils2.isStringNode)(sourceNode)) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (((_def$name$parent = def.name.parent) === null || _def$name$parent === void 0 ? void 0 : _def$name$parent.type) !== _utils.AST_NODE_TYPES.Property) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!(0, _utils2.isSupportedAccessor)(def.name.parent.key)) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
source: (0, _utils2.getStringValue)(sourceNode),
|
|
217
|
+
imported: (0, _utils2.getAccessorValue)(def.name.parent.key),
|
|
218
|
+
local: def.name.name
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* Attempts to describe a definition as an import if possible.
|
|
223
|
+
*
|
|
224
|
+
* If the definition is an import binding, it's described as you'd expect.
|
|
225
|
+
* If the definition is a variable, then we try and determine if it's either
|
|
226
|
+
* a dynamic `import()` or otherwise a call to `require()`.
|
|
227
|
+
*
|
|
228
|
+
* If it's neither of these, `null` is returned to indicate that the definition
|
|
229
|
+
* is not describable as an import of any kind.
|
|
230
|
+
*/
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
const describePossibleImportDef = def => {
|
|
234
|
+
if (def.type === 'Variable') {
|
|
235
|
+
return describeVariableDefAsImport(def);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (def.type === 'ImportBinding') {
|
|
239
|
+
return describeImportDefAsImport(def);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return null;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const collectReferences = scope => {
|
|
246
|
+
const locals = new Set();
|
|
247
|
+
const imports = new Map();
|
|
248
|
+
const unresolved = new Set();
|
|
249
|
+
let currentScope = scope;
|
|
250
|
+
|
|
251
|
+
while (currentScope !== null) {
|
|
252
|
+
for (const ref of currentScope.variables) {
|
|
253
|
+
if (ref.defs.length === 0) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const def = ref.defs[ref.defs.length - 1];
|
|
258
|
+
const importDetails = describePossibleImportDef(def);
|
|
259
|
+
|
|
260
|
+
if (importDetails) {
|
|
261
|
+
imports.set(importDetails.local, importDetails);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
locals.add(ref.name);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
for (const ref of currentScope.through) {
|
|
269
|
+
unresolved.add(ref.identifier.name);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
currentScope = currentScope.upper;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
locals,
|
|
277
|
+
imports,
|
|
278
|
+
unresolved
|
|
279
|
+
};
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const resolveToJestFn = (context, identifier) => {
|
|
283
|
+
const references = collectReferences(context.getScope());
|
|
284
|
+
const maybeImport = references.imports.get(identifier);
|
|
285
|
+
|
|
286
|
+
if (maybeImport) {
|
|
287
|
+
// the identifier is imported from @jest/globals,
|
|
288
|
+
// so return the original import name
|
|
289
|
+
if (maybeImport.source === '@jest/globals') {
|
|
290
|
+
return {
|
|
291
|
+
original: maybeImport.imported,
|
|
292
|
+
local: maybeImport.local,
|
|
293
|
+
type: 'import'
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return null;
|
|
298
|
+
} // the identifier was found as a local variable or function declaration
|
|
299
|
+
// meaning it's not a function from jest
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
if (references.locals.has(identifier)) {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
original: resolvePossibleAliasedGlobal(identifier, context),
|
|
308
|
+
local: identifier,
|
|
309
|
+
type: 'global'
|
|
310
|
+
};
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const scopeHasLocalReference = (scope, referenceName) => {
|
|
314
|
+
const references = collectReferences(scope);
|
|
315
|
+
return (// referenceName was found as a local variable or function declaration.
|
|
316
|
+
references.locals.has(referenceName) || // referenceName was found as an imported identifier
|
|
317
|
+
references.imports.has(referenceName) || // referenceName was not found as an unresolved reference,
|
|
318
|
+
// meaning it is likely not an implicit global reference.
|
|
319
|
+
!references.unresolved.has(referenceName)
|
|
320
|
+
);
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
exports.scopeHasLocalReference = scopeHasLocalReference;
|
|
@@ -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);
|
|
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)
|
|
@@ -52,12 +52,14 @@ const findTopMostCallExpression = node => {
|
|
|
52
52
|
return topMostCallExpression;
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
-
const isTestCaseCallWithCallbackArg = node => {
|
|
56
|
-
|
|
55
|
+
const isTestCaseCallWithCallbackArg = (node, context) => {
|
|
56
|
+
const jestCallFn = (0, _utils2.parseJestFnCall)(node, context);
|
|
57
|
+
|
|
58
|
+
if ((jestCallFn === null || jestCallFn === void 0 ? void 0 : jestCallFn.type) !== 'test') {
|
|
57
59
|
return false;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
const isJestEach = (0, _utils2.
|
|
62
|
+
const isJestEach = jestCallFn.members.some(s => (0, _utils2.getAccessorValue)(s) === 'each');
|
|
61
63
|
|
|
62
64
|
if (isJestEach && node.callee.type !== _utils.AST_NODE_TYPES.TaggedTemplateExpression) {
|
|
63
65
|
// isJestEach but not a TaggedTemplateExpression, so this must be
|
|
@@ -67,13 +69,9 @@ const isTestCaseCallWithCallbackArg = node => {
|
|
|
67
69
|
return true;
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return callback && (0, _utils2.isFunction)(callback) && callback.params.length === 1 + callbackArgIndex;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return false;
|
|
72
|
+
const [, callback] = node.arguments;
|
|
73
|
+
const callbackArgIndex = Number(isJestEach);
|
|
74
|
+
return callback && (0, _utils2.isFunction)(callback) && callback.params.length === 1 + callbackArgIndex;
|
|
77
75
|
};
|
|
78
76
|
|
|
79
77
|
const isPromiseMethodThatUsesValue = (node, identifier) => {
|
|
@@ -254,7 +252,7 @@ const findFirstBlockBodyUp = node => {
|
|
|
254
252
|
throw new Error(`Could not find BlockStatement - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`);
|
|
255
253
|
};
|
|
256
254
|
|
|
257
|
-
const isDirectlyWithinTestCaseCall = node => {
|
|
255
|
+
const isDirectlyWithinTestCaseCall = (node, context) => {
|
|
258
256
|
let parent = node;
|
|
259
257
|
|
|
260
258
|
while (parent) {
|
|
@@ -262,7 +260,7 @@ const isDirectlyWithinTestCaseCall = node => {
|
|
|
262
260
|
var _parent;
|
|
263
261
|
|
|
264
262
|
parent = parent.parent;
|
|
265
|
-
return
|
|
263
|
+
return ((_parent = parent) === null || _parent === void 0 ? void 0 : _parent.type) === _utils.AST_NODE_TYPES.CallExpression && (0, _utils2.isTypeOfJestFnCall)(parent, context, ['test']);
|
|
266
264
|
}
|
|
267
265
|
|
|
268
266
|
parent = parent.parent;
|
|
@@ -312,7 +310,7 @@ var _default = (0, _utils2.createRule)({
|
|
|
312
310
|
CallExpression(node) {
|
|
313
311
|
// there are too many ways that the done argument could be used with
|
|
314
312
|
// promises that contain expect that would make the promise safe for us
|
|
315
|
-
if (isTestCaseCallWithCallbackArg(node)) {
|
|
313
|
+
if (isTestCaseCallWithCallbackArg(node, context)) {
|
|
316
314
|
inTestCaseWithDoneCallback = true;
|
|
317
315
|
return;
|
|
318
316
|
} // if this call expression is a promise chain, add it to the stack with
|
|
@@ -336,7 +334,7 @@ var _default = (0, _utils2.createRule)({
|
|
|
336
334
|
// make promises containing expects safe in a test for us to be able to
|
|
337
335
|
// accurately check, so we just bail out completely if it's present
|
|
338
336
|
if (inTestCaseWithDoneCallback) {
|
|
339
|
-
if ((0, _utils2.
|
|
337
|
+
if ((0, _utils2.isTypeOfJestFnCall)(node, context, ['test'])) {
|
|
340
338
|
inTestCaseWithDoneCallback = false;
|
|
341
339
|
}
|
|
342
340
|
|
|
@@ -363,7 +361,7 @@ var _default = (0, _utils2.createRule)({
|
|
|
363
361
|
// because we're most likely in the body of a function being defined
|
|
364
362
|
// within the test, which we can't track
|
|
365
363
|
|
|
366
|
-
if (!parent || !isDirectlyWithinTestCaseCall(parent)) {
|
|
364
|
+
if (!parent || !isDirectlyWithinTestCaseCall(parent, context)) {
|
|
367
365
|
return;
|
|
368
366
|
}
|
|
369
367
|
|