@depup/eslint-plugin-jest 29.15.1-depup.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/LICENSE +21 -0
- package/README.md +31 -0
- package/changes.json +10 -0
- package/docs/rules/consistent-test-it.md +96 -0
- package/docs/rules/expect-expect.md +152 -0
- package/docs/rules/max-expects.md +78 -0
- package/docs/rules/max-nested-describe.md +134 -0
- package/docs/rules/no-alias-methods.md +57 -0
- package/docs/rules/no-commented-out-tests.md +68 -0
- package/docs/rules/no-conditional-expect.md +142 -0
- package/docs/rules/no-conditional-in-test.md +123 -0
- package/docs/rules/no-confusing-set-timeout.md +64 -0
- package/docs/rules/no-deprecated-functions.md +59 -0
- package/docs/rules/no-disabled-tests.md +72 -0
- package/docs/rules/no-done-callback.md +100 -0
- package/docs/rules/no-duplicate-hooks.md +79 -0
- package/docs/rules/no-error-equal.md +37 -0
- package/docs/rules/no-export.md +52 -0
- package/docs/rules/no-focused-tests.md +67 -0
- package/docs/rules/no-hooks.md +178 -0
- package/docs/rules/no-identical-title.md +59 -0
- package/docs/rules/no-interpolation-in-snapshots.md +67 -0
- package/docs/rules/no-jasmine-globals.md +69 -0
- package/docs/rules/no-large-snapshots.md +180 -0
- package/docs/rules/no-mocks-import.md +34 -0
- package/docs/rules/no-restricted-jest-methods.md +53 -0
- package/docs/rules/no-restricted-matchers.md +64 -0
- package/docs/rules/no-standalone-expect.md +106 -0
- package/docs/rules/no-test-prefixes.md +42 -0
- package/docs/rules/no-test-return-statement.md +50 -0
- package/docs/rules/no-unnecessary-assertion.md +41 -0
- package/docs/rules/no-unneeded-async-expect-function.md +41 -0
- package/docs/rules/no-untyped-mock-factory.md +74 -0
- package/docs/rules/padding-around-after-all-blocks.md +34 -0
- package/docs/rules/padding-around-after-each-blocks.md +38 -0
- package/docs/rules/padding-around-all.md +20 -0
- package/docs/rules/padding-around-before-all-blocks.md +37 -0
- package/docs/rules/padding-around-before-each-blocks.md +38 -0
- package/docs/rules/padding-around-describe-blocks.md +42 -0
- package/docs/rules/padding-around-expect-groups.md +44 -0
- package/docs/rules/padding-around-test-blocks.md +48 -0
- package/docs/rules/prefer-called-with.md +39 -0
- package/docs/rules/prefer-comparison-matcher.md +62 -0
- package/docs/rules/prefer-each.md +58 -0
- package/docs/rules/prefer-ending-with-an-expect.md +170 -0
- package/docs/rules/prefer-equality-matcher.md +36 -0
- package/docs/rules/prefer-expect-assertions.md +230 -0
- package/docs/rules/prefer-expect-resolves.md +62 -0
- package/docs/rules/prefer-hooks-in-order.md +137 -0
- package/docs/rules/prefer-hooks-on-top.md +124 -0
- package/docs/rules/prefer-importing-jest-globals.md +85 -0
- package/docs/rules/prefer-jest-mocked.md +40 -0
- package/docs/rules/prefer-lowercase-title.md +128 -0
- package/docs/rules/prefer-mock-promise-shorthand.md +41 -0
- package/docs/rules/prefer-mock-return-shorthand.md +50 -0
- package/docs/rules/prefer-snapshot-hint.md +192 -0
- package/docs/rules/prefer-spy-on.md +46 -0
- package/docs/rules/prefer-strict-equal.md +29 -0
- package/docs/rules/prefer-to-be.md +63 -0
- package/docs/rules/prefer-to-contain.md +53 -0
- package/docs/rules/prefer-to-have-been-called-times.md +43 -0
- package/docs/rules/prefer-to-have-been-called.md +35 -0
- package/docs/rules/prefer-to-have-length.md +41 -0
- package/docs/rules/prefer-todo.md +33 -0
- package/docs/rules/require-hook.md +187 -0
- package/docs/rules/require-to-throw-message.md +43 -0
- package/docs/rules/require-top-level-describe.md +86 -0
- package/docs/rules/unbound-method.md +61 -0
- package/docs/rules/valid-describe-callback.md +69 -0
- package/docs/rules/valid-expect-in-promise.md +79 -0
- package/docs/rules/valid-expect-with-promise.md +43 -0
- package/docs/rules/valid-expect.md +155 -0
- package/docs/rules/valid-mock-module-path.md +72 -0
- package/docs/rules/valid-title.md +319 -0
- package/index.d.ts +28 -0
- package/lib/globals.json +15 -0
- package/lib/index.js +85 -0
- package/lib/rules/consistent-test-it.js +101 -0
- package/lib/rules/expect-expect.js +102 -0
- package/lib/rules/max-expects.js +70 -0
- package/lib/rules/max-nested-describe.js +59 -0
- package/lib/rules/no-alias-methods.js +62 -0
- package/lib/rules/no-commented-out-tests.js +44 -0
- package/lib/rules/no-conditional-expect.js +80 -0
- package/lib/rules/no-conditional-in-test.js +63 -0
- package/lib/rules/no-confusing-set-timeout.js +61 -0
- package/lib/rules/no-deprecated-functions.js +81 -0
- package/lib/rules/no-disabled-tests.js +57 -0
- package/lib/rules/no-done-callback.js +120 -0
- package/lib/rules/no-duplicate-hooks.js +52 -0
- package/lib/rules/no-error-equal.js +41 -0
- package/lib/rules/no-export.js +61 -0
- package/lib/rules/no-focused-tests.js +63 -0
- package/lib/rules/no-hooks.js +51 -0
- package/lib/rules/no-identical-title.js +73 -0
- package/lib/rules/no-interpolation-in-snapshots.js +43 -0
- package/lib/rules/no-jasmine-globals.js +140 -0
- package/lib/rules/no-large-snapshots.js +100 -0
- package/lib/rules/no-mocks-import.js +46 -0
- package/lib/rules/no-restricted-jest-methods.js +53 -0
- package/lib/rules/no-restricted-matchers.js +60 -0
- package/lib/rules/no-standalone-expect.js +114 -0
- package/lib/rules/no-test-prefixes.js +48 -0
- package/lib/rules/no-test-return-statement.js +62 -0
- package/lib/rules/no-unnecessary-assertion.js +87 -0
- package/lib/rules/no-unneeded-async-expect-function.js +57 -0
- package/lib/rules/no-untyped-mock-factory.js +61 -0
- package/lib/rules/padding-around-after-all-blocks.js +17 -0
- package/lib/rules/padding-around-after-each-blocks.js +17 -0
- package/lib/rules/padding-around-all.js +15 -0
- package/lib/rules/padding-around-before-all-blocks.js +17 -0
- package/lib/rules/padding-around-before-each-blocks.js +17 -0
- package/lib/rules/padding-around-describe-blocks.js +17 -0
- package/lib/rules/padding-around-expect-groups.js +21 -0
- package/lib/rules/padding-around-test-blocks.js +17 -0
- package/lib/rules/prefer-called-with.js +47 -0
- package/lib/rules/prefer-comparison-matcher.js +111 -0
- package/lib/rules/prefer-each.js +77 -0
- package/lib/rules/prefer-ending-with-an-expect.js +102 -0
- package/lib/rules/prefer-equality-matcher.js +87 -0
- package/lib/rules/prefer-expect-assertions.js +237 -0
- package/lib/rules/prefer-expect-resolves.js +47 -0
- package/lib/rules/prefer-hooks-in-order.js +67 -0
- package/lib/rules/prefer-hooks-on-top.js +41 -0
- package/lib/rules/prefer-importing-jest-globals.js +126 -0
- package/lib/rules/prefer-jest-mocked.js +66 -0
- package/lib/rules/prefer-lowercase-title.js +128 -0
- package/lib/rules/prefer-mock-promise-shorthand.js +88 -0
- package/lib/rules/prefer-mock-return-shorthand.js +122 -0
- package/lib/rules/prefer-snapshot-hint.js +111 -0
- package/lib/rules/prefer-spy-on.js +80 -0
- package/lib/rules/prefer-strict-equal.js +46 -0
- package/lib/rules/prefer-to-be.js +98 -0
- package/lib/rules/prefer-to-contain.js +82 -0
- package/lib/rules/prefer-to-have-been-called-times.js +70 -0
- package/lib/rules/prefer-to-have-been-called.js +59 -0
- package/lib/rules/prefer-to-have-length.js +57 -0
- package/lib/rules/prefer-todo.js +72 -0
- package/lib/rules/require-hook.js +93 -0
- package/lib/rules/require-to-throw-message.js +45 -0
- package/lib/rules/require-top-level-describe.js +86 -0
- package/lib/rules/unbound-method.js +86 -0
- package/lib/rules/utils/accessors.js +127 -0
- package/lib/rules/utils/ast-utils.js +59 -0
- package/lib/rules/utils/detectJestVersion.js +24 -0
- package/lib/rules/utils/followTypeAssertionChain.js +10 -0
- package/lib/rules/utils/index.js +71 -0
- package/lib/rules/utils/misc.js +168 -0
- package/lib/rules/utils/padding.js +308 -0
- package/lib/rules/utils/parseJestFnCall.js +325 -0
- package/lib/rules/utils/ts.js +73 -0
- package/lib/rules/valid-describe-callback.js +94 -0
- package/lib/rules/valid-expect-in-promise.js +311 -0
- package/lib/rules/valid-expect-with-promise.js +54 -0
- package/lib/rules/valid-expect.js +302 -0
- package/lib/rules/valid-mock-module-path.js +90 -0
- package/lib/rules/valid-title.js +252 -0
- package/package.json +164 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.createPaddingRule = exports.StatementType = exports.PaddingType = void 0;
|
|
7
|
+
var _utils = require("@typescript-eslint/utils");
|
|
8
|
+
var astUtils = _interopRequireWildcard(require("./ast-utils"));
|
|
9
|
+
var _misc = require("./misc");
|
|
10
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
11
|
+
/**
|
|
12
|
+
* Require/fix newlines around jest functions
|
|
13
|
+
*
|
|
14
|
+
* Based on eslint/padding-line-between-statements by Toru Nagashima
|
|
15
|
+
* See: https://github.com/eslint/eslint/blob/master/lib/rules/padding-line-between-statements.js
|
|
16
|
+
*
|
|
17
|
+
* Some helpers borrowed from eslint ast-utils by Gyandeep Singh
|
|
18
|
+
* See: https://github.com/eslint/eslint/blob/master/lib/rules/utils/ast-utils.js
|
|
19
|
+
*/
|
|
20
|
+
// Statement types we'll respond to
|
|
21
|
+
let StatementType = exports.StatementType = /*#__PURE__*/function (StatementType) {
|
|
22
|
+
StatementType[StatementType["Any"] = 0] = "Any";
|
|
23
|
+
StatementType[StatementType["AfterAllToken"] = 1] = "AfterAllToken";
|
|
24
|
+
StatementType[StatementType["AfterEachToken"] = 2] = "AfterEachToken";
|
|
25
|
+
StatementType[StatementType["BeforeAllToken"] = 3] = "BeforeAllToken";
|
|
26
|
+
StatementType[StatementType["BeforeEachToken"] = 4] = "BeforeEachToken";
|
|
27
|
+
StatementType[StatementType["DescribeToken"] = 5] = "DescribeToken";
|
|
28
|
+
StatementType[StatementType["ExpectToken"] = 6] = "ExpectToken";
|
|
29
|
+
StatementType[StatementType["FdescribeToken"] = 7] = "FdescribeToken";
|
|
30
|
+
StatementType[StatementType["FitToken"] = 8] = "FitToken";
|
|
31
|
+
StatementType[StatementType["ItToken"] = 9] = "ItToken";
|
|
32
|
+
StatementType[StatementType["TestToken"] = 10] = "TestToken";
|
|
33
|
+
StatementType[StatementType["XdescribeToken"] = 11] = "XdescribeToken";
|
|
34
|
+
StatementType[StatementType["XitToken"] = 12] = "XitToken";
|
|
35
|
+
StatementType[StatementType["XtestToken"] = 13] = "XtestToken";
|
|
36
|
+
return StatementType;
|
|
37
|
+
}({});
|
|
38
|
+
// Padding type to apply between statements
|
|
39
|
+
let PaddingType = exports.PaddingType = /*#__PURE__*/function (PaddingType) {
|
|
40
|
+
PaddingType[PaddingType["Any"] = 0] = "Any";
|
|
41
|
+
PaddingType[PaddingType["Always"] = 1] = "Always";
|
|
42
|
+
return PaddingType;
|
|
43
|
+
}({}); // A configuration object for padding type and the two statement types
|
|
44
|
+
// Tracks position in scope and prevNode. Used to compare current and prev node
|
|
45
|
+
// and then to walk back up to the parent scope or down into the next one.
|
|
46
|
+
// And so on...
|
|
47
|
+
// Creates a StatementTester to test an ExpressionStatement's first token name
|
|
48
|
+
const createTokenTester = tokenName => {
|
|
49
|
+
return (node, sourceCode) => {
|
|
50
|
+
let activeNode = node;
|
|
51
|
+
if (activeNode.type === _utils.AST_NODE_TYPES.ExpressionStatement) {
|
|
52
|
+
// In the case of `await`, we actually care about its argument
|
|
53
|
+
if (activeNode.expression.type === _utils.AST_NODE_TYPES.AwaitExpression) {
|
|
54
|
+
activeNode = activeNode.expression.argument;
|
|
55
|
+
}
|
|
56
|
+
const token = sourceCode.getFirstToken(activeNode);
|
|
57
|
+
return token?.type === _utils.AST_TOKEN_TYPES.Identifier && token.value === tokenName;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// A mapping of StatementType to StatementTester for... testing statements
|
|
64
|
+
const statementTesters = {
|
|
65
|
+
[StatementType.Any]: () => true,
|
|
66
|
+
[StatementType.AfterAllToken]: createTokenTester('afterAll'),
|
|
67
|
+
[StatementType.AfterEachToken]: createTokenTester('afterEach'),
|
|
68
|
+
[StatementType.BeforeAllToken]: createTokenTester('beforeAll'),
|
|
69
|
+
[StatementType.BeforeEachToken]: createTokenTester('beforeEach'),
|
|
70
|
+
[StatementType.DescribeToken]: createTokenTester('describe'),
|
|
71
|
+
[StatementType.ExpectToken]: createTokenTester('expect'),
|
|
72
|
+
[StatementType.FdescribeToken]: createTokenTester('fdescribe'),
|
|
73
|
+
[StatementType.FitToken]: createTokenTester('fit'),
|
|
74
|
+
[StatementType.ItToken]: createTokenTester('it'),
|
|
75
|
+
[StatementType.TestToken]: createTokenTester('test'),
|
|
76
|
+
[StatementType.XdescribeToken]: createTokenTester('xdescribe'),
|
|
77
|
+
[StatementType.XitToken]: createTokenTester('xit'),
|
|
78
|
+
[StatementType.XtestToken]: createTokenTester('xtest')
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check and report statements for `PaddingType.Always` configuration.
|
|
83
|
+
* This autofix inserts a blank line between the given 2 statements.
|
|
84
|
+
* If the `prevNode` has trailing comments, it inserts a blank line after the
|
|
85
|
+
* trailing comments.
|
|
86
|
+
*/
|
|
87
|
+
const paddingAlwaysTester = (prevNode, nextNode, paddingContext) => {
|
|
88
|
+
const {
|
|
89
|
+
sourceCode,
|
|
90
|
+
ruleContext
|
|
91
|
+
} = paddingContext;
|
|
92
|
+
const paddingLines = astUtils.getPaddingLineSequences(prevNode, nextNode, sourceCode);
|
|
93
|
+
|
|
94
|
+
// We've got some padding lines. Great.
|
|
95
|
+
if (paddingLines.length > 0) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Missing padding line
|
|
100
|
+
ruleContext.report({
|
|
101
|
+
node: nextNode,
|
|
102
|
+
messageId: 'missingPadding',
|
|
103
|
+
fix(fixer) {
|
|
104
|
+
let prevToken = astUtils.getActualLastToken(sourceCode, prevNode);
|
|
105
|
+
const nextToken = sourceCode.getFirstTokenBetween(prevToken, nextNode, {
|
|
106
|
+
includeComments: true,
|
|
107
|
+
/**
|
|
108
|
+
* Skip the trailing comments of the previous node.
|
|
109
|
+
* This inserts a blank line after the last trailing comment.
|
|
110
|
+
*
|
|
111
|
+
* For example:
|
|
112
|
+
*
|
|
113
|
+
* foo(); // trailing comment.
|
|
114
|
+
* // comment.
|
|
115
|
+
* bar();
|
|
116
|
+
*
|
|
117
|
+
* Get fixed to:
|
|
118
|
+
*
|
|
119
|
+
* foo(); // trailing comment.
|
|
120
|
+
*
|
|
121
|
+
* // comment.
|
|
122
|
+
* bar();
|
|
123
|
+
*/
|
|
124
|
+
filter(token) {
|
|
125
|
+
if (astUtils.areTokensOnSameLine(prevToken, token)) {
|
|
126
|
+
prevToken = token;
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
}) || nextNode;
|
|
132
|
+
const insertText = astUtils.areTokensOnSameLine(prevToken, nextToken) ? '\n\n' : '\n';
|
|
133
|
+
return fixer.insertTextAfter(prevToken, insertText);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// A mapping of PaddingType to PaddingTester
|
|
139
|
+
const paddingTesters = {
|
|
140
|
+
[PaddingType.Any]: () => true,
|
|
141
|
+
[PaddingType.Always]: paddingAlwaysTester
|
|
142
|
+
};
|
|
143
|
+
const createScopeInfo = () => {
|
|
144
|
+
let scope = null;
|
|
145
|
+
|
|
146
|
+
// todo: explore seeing if we can refactor to a more TypeScript friendly structure
|
|
147
|
+
return {
|
|
148
|
+
get prevNode() {
|
|
149
|
+
return scope.prevNode;
|
|
150
|
+
},
|
|
151
|
+
set prevNode(node) {
|
|
152
|
+
scope.prevNode = node;
|
|
153
|
+
},
|
|
154
|
+
enter() {
|
|
155
|
+
scope = {
|
|
156
|
+
upper: scope,
|
|
157
|
+
prevNode: null
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
exit() {
|
|
161
|
+
scope = scope.upper;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Check whether the given node matches the statement type
|
|
168
|
+
*/
|
|
169
|
+
const nodeMatchesType = (node, statementType, paddingContext) => {
|
|
170
|
+
let innerStatementNode = node;
|
|
171
|
+
const {
|
|
172
|
+
sourceCode
|
|
173
|
+
} = paddingContext;
|
|
174
|
+
|
|
175
|
+
// Dig into LabeledStatement body until it's not that anymore
|
|
176
|
+
while (innerStatementNode.type === _utils.AST_NODE_TYPES.LabeledStatement) {
|
|
177
|
+
innerStatementNode = innerStatementNode.body;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// If it's an array recursively check if any of the statement types match
|
|
181
|
+
// the node
|
|
182
|
+
if (Array.isArray(statementType)) {
|
|
183
|
+
return statementType.some(type => nodeMatchesType(innerStatementNode, type, paddingContext));
|
|
184
|
+
}
|
|
185
|
+
return statementTesters[statementType](innerStatementNode, sourceCode);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Executes matching padding tester for last matched padding config for given
|
|
190
|
+
* nodes
|
|
191
|
+
*/
|
|
192
|
+
const testPadding = (prevNode, nextNode, paddingContext) => {
|
|
193
|
+
const {
|
|
194
|
+
configs
|
|
195
|
+
} = paddingContext;
|
|
196
|
+
const testType = type => paddingTesters[type](prevNode, nextNode, paddingContext);
|
|
197
|
+
for (let i = configs.length - 1; i >= 0; --i) {
|
|
198
|
+
const {
|
|
199
|
+
prevStatementType: prevType,
|
|
200
|
+
nextStatementType: nextType,
|
|
201
|
+
paddingType
|
|
202
|
+
} = configs[i];
|
|
203
|
+
if (nodeMatchesType(prevNode, prevType, paddingContext) && nodeMatchesType(nextNode, nextType, paddingContext)) {
|
|
204
|
+
testType(paddingType);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// There were no matching padding rules for the prevNode, nextNode,
|
|
210
|
+
// paddingType combination... so we'll use PaddingType.Any which is always ok
|
|
211
|
+
testType(PaddingType.Any);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Verify padding lines between the given node and the previous node.
|
|
216
|
+
*/
|
|
217
|
+
const verifyNode = (node, paddingContext) => {
|
|
218
|
+
const {
|
|
219
|
+
scopeInfo
|
|
220
|
+
} = paddingContext;
|
|
221
|
+
|
|
222
|
+
// NOTE: ESLint types use ESTree which provides a Node type, however
|
|
223
|
+
// ESTree.Node doesn't support the parent property which is added by
|
|
224
|
+
// ESLint during traversal. Our best bet is to ignore the property access
|
|
225
|
+
// here as it's the only place that it's checked.
|
|
226
|
+
|
|
227
|
+
if (!astUtils.isValidParent(node.parent.type)) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (scopeInfo.prevNode) {
|
|
231
|
+
testPadding(scopeInfo.prevNode, node, paddingContext);
|
|
232
|
+
}
|
|
233
|
+
scopeInfo.prevNode = node;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Creates an ESLint rule for a given set of padding Config objects.
|
|
238
|
+
*
|
|
239
|
+
* The algorithm is approximately this:
|
|
240
|
+
*
|
|
241
|
+
* For each 'scope' in the program
|
|
242
|
+
* - Enter the scope (store the parent scope and previous node)
|
|
243
|
+
* - For each statement in the scope
|
|
244
|
+
* - Check the current node and previous node against the Config objects
|
|
245
|
+
* - If the current node and previous node match a Config, check the padding.
|
|
246
|
+
* Otherwise, ignore it.
|
|
247
|
+
* - If the padding is missing (and required), report and fix
|
|
248
|
+
* - Store the current node as the previous
|
|
249
|
+
* - Repeat
|
|
250
|
+
* - Exit scope (return to parent scope and clear previous node)
|
|
251
|
+
*
|
|
252
|
+
* The items we're looking for with this rule are ExpressionStatement nodes
|
|
253
|
+
* where the first token is an Identifier with a name matching one of the Jest
|
|
254
|
+
* functions. It's not foolproof, of course, but it's probably good enough for
|
|
255
|
+
* almost all cases.
|
|
256
|
+
*
|
|
257
|
+
* The Config objects specify a padding type, a previous statement type, and a
|
|
258
|
+
* next statement type. Wildcard statement types and padding types are
|
|
259
|
+
* supported. The current node and previous node are checked against the
|
|
260
|
+
* statement types. If they match then the specified padding type is
|
|
261
|
+
* tested/enforced.
|
|
262
|
+
*
|
|
263
|
+
* See src/index.ts for examples of Config usage.
|
|
264
|
+
*/
|
|
265
|
+
const createPaddingRule = (name, description, configs, deprecated = false) => {
|
|
266
|
+
return (0, _misc.createRule)({
|
|
267
|
+
name,
|
|
268
|
+
meta: {
|
|
269
|
+
docs: {
|
|
270
|
+
description
|
|
271
|
+
},
|
|
272
|
+
fixable: 'whitespace',
|
|
273
|
+
deprecated,
|
|
274
|
+
messages: {
|
|
275
|
+
missingPadding: 'Expected blank line before this statement.'
|
|
276
|
+
},
|
|
277
|
+
schema: [],
|
|
278
|
+
type: 'suggestion'
|
|
279
|
+
},
|
|
280
|
+
defaultOptions: [],
|
|
281
|
+
create(context) {
|
|
282
|
+
const paddingContext = {
|
|
283
|
+
ruleContext: context,
|
|
284
|
+
sourceCode: context.sourceCode,
|
|
285
|
+
scopeInfo: createScopeInfo(),
|
|
286
|
+
configs
|
|
287
|
+
};
|
|
288
|
+
const {
|
|
289
|
+
scopeInfo
|
|
290
|
+
} = paddingContext;
|
|
291
|
+
return {
|
|
292
|
+
Program: scopeInfo.enter,
|
|
293
|
+
'Program:exit': scopeInfo.enter,
|
|
294
|
+
BlockStatement: scopeInfo.enter,
|
|
295
|
+
'BlockStatement:exit': scopeInfo.exit,
|
|
296
|
+
SwitchStatement: scopeInfo.enter,
|
|
297
|
+
'SwitchStatement:exit': scopeInfo.exit,
|
|
298
|
+
':statement': node => verifyNode(node, paddingContext),
|
|
299
|
+
SwitchCase(node) {
|
|
300
|
+
verifyNode(node, paddingContext);
|
|
301
|
+
scopeInfo.enter();
|
|
302
|
+
},
|
|
303
|
+
'SwitchCase:exit': scopeInfo.exit
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
exports.createPaddingRule = createPaddingRule;
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getNodeChain = getNodeChain;
|
|
7
|
+
exports.resolveScope = exports.parseJestFnCallWithReason = exports.parseJestFnCall = exports.isTypeOfJestFnCall = void 0;
|
|
8
|
+
var _utils = require("@typescript-eslint/utils");
|
|
9
|
+
var _utils2 = require("../utils");
|
|
10
|
+
const isTypeOfJestFnCall = (node, context, types) => {
|
|
11
|
+
const jestFnCall = parseJestFnCall(node, context);
|
|
12
|
+
return jestFnCall !== null && types.includes(jestFnCall.type);
|
|
13
|
+
};
|
|
14
|
+
exports.isTypeOfJestFnCall = isTypeOfJestFnCall;
|
|
15
|
+
const joinChains = (a, b) => a && b ? [...a, ...b] : null;
|
|
16
|
+
function getNodeChain(node) {
|
|
17
|
+
if ((0, _utils2.isSupportedAccessor)(node)) {
|
|
18
|
+
return [node];
|
|
19
|
+
}
|
|
20
|
+
switch (node.type) {
|
|
21
|
+
case _utils.AST_NODE_TYPES.TaggedTemplateExpression:
|
|
22
|
+
return getNodeChain(node.tag);
|
|
23
|
+
case _utils.AST_NODE_TYPES.MemberExpression:
|
|
24
|
+
return joinChains(getNodeChain(node.object), getNodeChain(node.property));
|
|
25
|
+
case _utils.AST_NODE_TYPES.CallExpression:
|
|
26
|
+
return getNodeChain(node.callee);
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const determineJestFnType = name => {
|
|
31
|
+
if (name === 'expect') {
|
|
32
|
+
return 'expect';
|
|
33
|
+
}
|
|
34
|
+
if (name === 'jest') {
|
|
35
|
+
return 'jest';
|
|
36
|
+
}
|
|
37
|
+
if (_utils2.DescribeAlias.hasOwnProperty(name)) {
|
|
38
|
+
return 'describe';
|
|
39
|
+
}
|
|
40
|
+
if (_utils2.TestCaseName.hasOwnProperty(name)) {
|
|
41
|
+
return 'test';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* istanbul ignore else */
|
|
45
|
+
if (_utils2.HookName.hasOwnProperty(name)) {
|
|
46
|
+
return 'hook';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* istanbul ignore next */
|
|
50
|
+
return 'unknown';
|
|
51
|
+
};
|
|
52
|
+
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.failing', 'it.concurrent.each', 'it.concurrent.failing.each', 'it.concurrent.failing.only.each', 'it.concurrent.failing.skip.each', 'it.concurrent.only.each', 'it.concurrent.skip.each', 'it.each', 'it.failing', 'it.failing.each', 'it.only', 'it.only.each', 'it.only.failing', 'it.only.failing.each', 'it.skip', 'it.skip.each', 'it.skip.failing', 'it.skip.failing.each', 'it.todo', 'fit', 'fit.each', 'fit.failing', 'fit.failing.each', 'xit', 'xit.each', 'xit.failing', 'xit.failing.each', 'test', 'test.concurrent', 'test.concurrent.failing', 'test.concurrent.each', 'test.concurrent.failing.each', 'test.concurrent.failing.only.each', 'test.concurrent.failing.skip.each', 'test.concurrent.only.each', 'test.concurrent.skip.each', 'test.each', 'test.failing', 'test.failing.each', 'test.only', 'test.only.each', 'test.only.failing', 'test.only.failing.each', 'test.skip', 'test.skip.each', 'test.skip.failing', 'test.skip.failing.each', 'test.todo', 'xtest', 'xtest.each', 'xtest.failing', 'xtest.failing.each'];
|
|
53
|
+
const resolvePossibleAliasedGlobal = (global, context) => {
|
|
54
|
+
const globalAliases = context.settings.jest?.globalAliases ?? {};
|
|
55
|
+
const alias = Object.entries(globalAliases).find(([, aliases]) => aliases.includes(global));
|
|
56
|
+
if (alias) {
|
|
57
|
+
return alias[0];
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
};
|
|
61
|
+
const parseJestFnCallCache = new WeakMap();
|
|
62
|
+
const parseJestFnCall = (node, context) => {
|
|
63
|
+
const jestFnCall = parseJestFnCallWithReason(node, context);
|
|
64
|
+
if (typeof jestFnCall === 'string') {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return jestFnCall;
|
|
68
|
+
};
|
|
69
|
+
exports.parseJestFnCall = parseJestFnCall;
|
|
70
|
+
const parseJestFnCallWithReason = (node, context) => {
|
|
71
|
+
let parsedJestFnCall = parseJestFnCallCache.get(node);
|
|
72
|
+
if (parsedJestFnCall) {
|
|
73
|
+
return parsedJestFnCall;
|
|
74
|
+
}
|
|
75
|
+
parsedJestFnCall = parseJestFnCallWithReasonInner(node, context);
|
|
76
|
+
parseJestFnCallCache.set(node, parsedJestFnCall);
|
|
77
|
+
return parsedJestFnCall;
|
|
78
|
+
};
|
|
79
|
+
exports.parseJestFnCallWithReason = parseJestFnCallWithReason;
|
|
80
|
+
const parseJestFnCallWithReasonInner = (node, context) => {
|
|
81
|
+
const chain = getNodeChain(node);
|
|
82
|
+
if (!chain?.length) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const [first, ...rest] = chain;
|
|
86
|
+
const lastLink = (0, _utils2.getAccessorValue)(chain[chain.length - 1]);
|
|
87
|
+
|
|
88
|
+
// if we're an `each()`, ensure we're the outer CallExpression (i.e `.each()()`)
|
|
89
|
+
if (lastLink === 'each') {
|
|
90
|
+
if (node.callee.type !== _utils.AST_NODE_TYPES.CallExpression && node.callee.type !== _utils.AST_NODE_TYPES.TaggedTemplateExpression) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (node.callee.type === _utils.AST_NODE_TYPES.TaggedTemplateExpression && lastLink !== 'each') {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const resolved = resolveToJestFn(context, first);
|
|
98
|
+
|
|
99
|
+
// we're not a jest function
|
|
100
|
+
if (!resolved) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const name = resolved.original ?? resolved.local;
|
|
104
|
+
const links = [name, ...rest.map(link => (0, _utils2.getAccessorValue)(link))];
|
|
105
|
+
if (name !== 'jest' && name !== 'expect' && !ValidJestFnCallChains.includes(links.join('.'))) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const parsedJestFnCall = {
|
|
109
|
+
name,
|
|
110
|
+
head: {
|
|
111
|
+
...resolved,
|
|
112
|
+
node: first
|
|
113
|
+
},
|
|
114
|
+
// every member node must have a member expression as their parent
|
|
115
|
+
// in order to be part of the call chain we're parsing
|
|
116
|
+
members: rest
|
|
117
|
+
};
|
|
118
|
+
const type = determineJestFnType(name);
|
|
119
|
+
if (type === 'expect') {
|
|
120
|
+
const result = parseJestExpectCall(parsedJestFnCall);
|
|
121
|
+
|
|
122
|
+
// if the `expect` call chain is not valid, only report on the topmost node
|
|
123
|
+
// since all members in the chain are likely to get flagged for some reason
|
|
124
|
+
if (typeof result === 'string' && (0, _utils2.findTopMostCallExpression)(node) !== node) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
if (result === 'matcher-not-found') {
|
|
128
|
+
if (node.parent.type === _utils.AST_NODE_TYPES.MemberExpression) {
|
|
129
|
+
return 'matcher-not-called';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// check that every link in the chain except the last is a member expression
|
|
136
|
+
if (chain.slice(0, chain.length - 1).some(nod => nod.parent.type !== _utils.AST_NODE_TYPES.MemberExpression)) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ensure that we're at the "top" of the function call chain otherwise when
|
|
141
|
+
// parsing e.g. x().y.z(), we'll incorrectly find & parse "x()" even though
|
|
142
|
+
// the full chain is not a valid jest function call chain
|
|
143
|
+
if (node.parent.type === _utils.AST_NODE_TYPES.CallExpression || node.parent.type === _utils.AST_NODE_TYPES.MemberExpression) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
...parsedJestFnCall,
|
|
148
|
+
type
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
const findModifiersAndMatcher = members => {
|
|
152
|
+
const modifiers = [];
|
|
153
|
+
for (const member of members) {
|
|
154
|
+
// check if the member is being called, which means it is the matcher
|
|
155
|
+
// (and also the end of the entire "expect" call chain)
|
|
156
|
+
if (member.parent.parent.type === _utils.AST_NODE_TYPES.CallExpression) {
|
|
157
|
+
return {
|
|
158
|
+
matcher: member,
|
|
159
|
+
args: member.parent.parent.arguments,
|
|
160
|
+
modifiers
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// otherwise, it should be a modifier
|
|
165
|
+
const name = (0, _utils2.getAccessorValue)(member);
|
|
166
|
+
if (modifiers.length === 0) {
|
|
167
|
+
// the first modifier can be any of the three modifiers
|
|
168
|
+
if (!_utils2.ModifierName.hasOwnProperty(name)) {
|
|
169
|
+
return 'modifier-unknown';
|
|
170
|
+
}
|
|
171
|
+
} else if (modifiers.length === 1) {
|
|
172
|
+
// the second modifier can only be "not"
|
|
173
|
+
if (name !== _utils2.ModifierName.not) {
|
|
174
|
+
return 'modifier-unknown';
|
|
175
|
+
}
|
|
176
|
+
const firstModifier = (0, _utils2.getAccessorValue)(modifiers[0]);
|
|
177
|
+
|
|
178
|
+
// and the first modifier has to be either "resolves" or "rejects"
|
|
179
|
+
if (firstModifier !== _utils2.ModifierName.resolves && firstModifier !== _utils2.ModifierName.rejects) {
|
|
180
|
+
return 'modifier-unknown';
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
return 'modifier-unknown';
|
|
184
|
+
}
|
|
185
|
+
modifiers.push(member);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// this will only really happen if there are no members
|
|
189
|
+
return 'matcher-not-found';
|
|
190
|
+
};
|
|
191
|
+
const parseJestExpectCall = typelessParsedJestFnCall => {
|
|
192
|
+
const modifiersAndMatcher = findModifiersAndMatcher(typelessParsedJestFnCall.members);
|
|
193
|
+
if (typeof modifiersAndMatcher === 'string') {
|
|
194
|
+
return modifiersAndMatcher;
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
...typelessParsedJestFnCall,
|
|
198
|
+
type: 'expect',
|
|
199
|
+
...modifiersAndMatcher
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
const describeImportDefAsImport = def => {
|
|
203
|
+
if (def.parent.type === _utils.AST_NODE_TYPES.TSImportEqualsDeclaration) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
if (def.node.type !== _utils.AST_NODE_TYPES.ImportSpecifier) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// we only care about value imports
|
|
211
|
+
if (def.parent.importKind === 'type') {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
source: def.parent.source.value,
|
|
216
|
+
imported: 'name' in def.node.imported ? def.node.imported.name : def.node.imported.value,
|
|
217
|
+
local: def.node.local.name
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Attempts to find the node that represents the import source for the
|
|
223
|
+
* given expression node, if it looks like it's an import.
|
|
224
|
+
*
|
|
225
|
+
* If no such node can be found (e.g. because the expression doesn't look
|
|
226
|
+
* like an import), then `null` is returned instead.
|
|
227
|
+
*/
|
|
228
|
+
const findImportSourceNode = node => {
|
|
229
|
+
if (node.type === _utils.AST_NODE_TYPES.AwaitExpression) {
|
|
230
|
+
if (node.argument.type === _utils.AST_NODE_TYPES.ImportExpression) {
|
|
231
|
+
return node.argument.source;
|
|
232
|
+
}
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
if (node.type === _utils.AST_NODE_TYPES.CallExpression && (0, _utils2.isIdentifier)(node.callee, 'require')) {
|
|
236
|
+
return node.arguments[0] ?? null;
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
};
|
|
240
|
+
const describeVariableDefAsImport = def => {
|
|
241
|
+
// make sure that we've actually being assigned a value
|
|
242
|
+
if (!def.node.init) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
const sourceNode = findImportSourceNode(def.node.init);
|
|
246
|
+
if (!sourceNode || !(0, _utils2.isStringNode)(sourceNode)) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
if (def.name.parent.type !== _utils.AST_NODE_TYPES.Property) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
if (!(0, _utils2.isSupportedAccessor)(def.name.parent.key)) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
source: (0, _utils2.getStringValue)(sourceNode),
|
|
257
|
+
imported: (0, _utils2.getAccessorValue)(def.name.parent.key),
|
|
258
|
+
local: def.name.name
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Attempts to describe a definition as an import if possible.
|
|
264
|
+
*
|
|
265
|
+
* If the definition is an import binding, it's described as you'd expect.
|
|
266
|
+
* If the definition is a variable, then we try and determine if it's either
|
|
267
|
+
* a dynamic `import()` or otherwise a call to `require()`.
|
|
268
|
+
*
|
|
269
|
+
* If it's neither of these, `null` is returned to indicate that the definition
|
|
270
|
+
* is not describable as an import of any kind.
|
|
271
|
+
*/
|
|
272
|
+
const describePossibleImportDef = def => {
|
|
273
|
+
if (def.type === 'Variable') {
|
|
274
|
+
return describeVariableDefAsImport(def);
|
|
275
|
+
}
|
|
276
|
+
if (def.type === 'ImportBinding') {
|
|
277
|
+
return describeImportDefAsImport(def);
|
|
278
|
+
}
|
|
279
|
+
return null;
|
|
280
|
+
};
|
|
281
|
+
const resolveScope = (scope, identifier) => {
|
|
282
|
+
let currentScope = scope;
|
|
283
|
+
while (currentScope !== null) {
|
|
284
|
+
const ref = currentScope.set.get(identifier);
|
|
285
|
+
if (ref && ref.defs.length > 0) {
|
|
286
|
+
const def = ref.defs[ref.defs.length - 1];
|
|
287
|
+
const importDetails = describePossibleImportDef(def);
|
|
288
|
+
if (importDetails?.local === identifier) {
|
|
289
|
+
return importDetails;
|
|
290
|
+
}
|
|
291
|
+
return 'local';
|
|
292
|
+
}
|
|
293
|
+
currentScope = currentScope.upper;
|
|
294
|
+
}
|
|
295
|
+
return null;
|
|
296
|
+
};
|
|
297
|
+
exports.resolveScope = resolveScope;
|
|
298
|
+
const resolveToJestFn = (context, accessor) => {
|
|
299
|
+
const identifier = (0, _utils2.getAccessorValue)(accessor);
|
|
300
|
+
const maybeImport = resolveScope(context.sourceCode.getScope(accessor), identifier);
|
|
301
|
+
|
|
302
|
+
// the identifier was found as a local variable or function declaration
|
|
303
|
+
// meaning it's not a function from jest
|
|
304
|
+
if (maybeImport === 'local') {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
if (maybeImport) {
|
|
308
|
+
const globalPackage = context.settings.jest?.globalPackage ?? '@jest/globals';
|
|
309
|
+
|
|
310
|
+
// the identifier is imported from our global package so return the original import name
|
|
311
|
+
if (maybeImport.source === globalPackage) {
|
|
312
|
+
return {
|
|
313
|
+
original: maybeImport.imported,
|
|
314
|
+
local: maybeImport.local,
|
|
315
|
+
type: 'import'
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
original: resolvePossibleAliasedGlobal(identifier, context),
|
|
322
|
+
local: identifier,
|
|
323
|
+
type: 'global'
|
|
324
|
+
};
|
|
325
|
+
};
|