eslint-plugin-no-mistakes 0.12.0 → 0.12.1
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-no-mistakes",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "ESLint and Oxlint rules for deterministic no-mistakes code analysis",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@typescript-eslint/parser": "^8.48.0",
|
|
25
25
|
"@vitest/coverage-v8": "^4.1.6",
|
|
26
|
-
"eslint": "^10.
|
|
27
|
-
"oxlint": "^1.
|
|
26
|
+
"eslint": "^10.4.0",
|
|
27
|
+
"oxlint": "^1.65.0",
|
|
28
28
|
"vitest": "^4.1.6"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const TEST_CALLEES = new Set(["it", "test", "describe"]);
|
|
4
4
|
const SETUP_CALLEES = new Set(["beforeEach", "afterEach", "beforeAll", "afterAll"]);
|
|
5
|
+
const PER_TEST_CALLEES = new Set(["beforeEach", "afterEach"]);
|
|
5
6
|
const MUTATING_METHODS = new Set(
|
|
6
7
|
"add clear copyWithin delete fill pop push reverse set shift sort splice unshift".split(" "),
|
|
7
8
|
);
|
|
@@ -25,15 +26,13 @@ function isTestCall(node) {
|
|
|
25
26
|
|
|
26
27
|
function setupCallbackKind(node) {
|
|
27
28
|
const name = calleeName(node.callee);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
function isSetupCall(node) {
|
|
36
|
-
return SETUP_CALLEES.has(calleeName(node.callee));
|
|
29
|
+
if (PER_TEST_CALLEES.has(name)) return "per-test";
|
|
30
|
+
if (name === "beforeAll") return "before-once";
|
|
31
|
+
if (name === "afterAll") return "once";
|
|
32
|
+
if (node.callee.type !== "MemberExpression" || !TEST_CALLEES.has(name)) return null;
|
|
33
|
+
if (PER_TEST_CALLEES.has(node.callee.property.name)) return "per-test";
|
|
34
|
+
if (node.callee.property.name === "beforeAll") return "before-once";
|
|
35
|
+
return node.callee.property.name === "afterAll" ? "once" : null;
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
function collectPatternNames(node, names = new Set()) {
|
|
@@ -101,7 +100,8 @@ function isInlineTestCallback(node) {
|
|
|
101
100
|
}
|
|
102
101
|
|
|
103
102
|
function isInlineSetupCallback(node) {
|
|
104
|
-
|
|
103
|
+
const p = node.parent;
|
|
104
|
+
return p?.type === "CallExpression" && SETUP_CALLEES.has(calleeName(p.callee));
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
function isCalledFunction(node) {
|
|
@@ -152,12 +152,13 @@ function firstNamedCallbackArgument(args) {
|
|
|
152
152
|
return args[0]?.type === "Identifier" ? args[0] : undefined;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
function createCleanupTracker() {
|
|
155
|
+
function createCleanupTracker(options = {}) {
|
|
156
156
|
const pathsBySuite = new Map();
|
|
157
157
|
const suiteStack = [];
|
|
158
|
-
let activeSuiteKey;
|
|
159
|
-
let replaySuiteKey;
|
|
158
|
+
let activeSuiteKey, replaySuiteKey;
|
|
160
159
|
let nextSuiteId = 0;
|
|
160
|
+
const ao = options.allowBeforeAllAssignments;
|
|
161
|
+
const sKinds = new Set(ao ? ["per-test", "before-once"] : ["per-test"]);
|
|
161
162
|
|
|
162
163
|
function currentSuiteKey() {
|
|
163
164
|
return replaySuiteKey ?? suiteStack.join("/");
|
|
@@ -175,7 +176,7 @@ function createCleanupTracker() {
|
|
|
175
176
|
|
|
176
177
|
return {
|
|
177
178
|
beginSetup(kind, suiteKey = currentSuiteKey()) {
|
|
178
|
-
activeSuiteKey = kind
|
|
179
|
+
activeSuiteKey = sKinds.has(kind) ? suiteKey : undefined;
|
|
179
180
|
},
|
|
180
181
|
clearReplaySuite() {
|
|
181
182
|
replaySuiteKey = undefined;
|
|
@@ -194,8 +195,7 @@ function createCleanupTracker() {
|
|
|
194
195
|
remember(path) {
|
|
195
196
|
if (!path || activeSuiteKey === undefined) return;
|
|
196
197
|
const paths = pathsBySuite.get(activeSuiteKey) ?? new Set();
|
|
197
|
-
paths.add(path);
|
|
198
|
-
pathsBySuite.set(activeSuiteKey, paths);
|
|
198
|
+
pathsBySuite.set(activeSuiteKey, paths.add(path));
|
|
199
199
|
},
|
|
200
200
|
setReplaySuite(suiteKey) {
|
|
201
201
|
replaySuiteKey = suiteKey;
|
|
@@ -214,7 +214,6 @@ module.exports = {
|
|
|
214
214
|
isInlineTestCallback,
|
|
215
215
|
isMutableInitializer,
|
|
216
216
|
calleeName,
|
|
217
|
-
isSetupCall,
|
|
218
217
|
isTestCall,
|
|
219
218
|
mutatingCallPropertyName,
|
|
220
219
|
mutatingCallTarget,
|
|
@@ -31,7 +31,7 @@ module.exports = rule(
|
|
|
31
31
|
{
|
|
32
32
|
type: "problem",
|
|
33
33
|
docs: { description: "disallow mutable module-scope test state", recommended: false },
|
|
34
|
-
schema: [],
|
|
34
|
+
schema: [{ type: "object", properties: { allowBeforeAllAssignments: { type: "boolean" } } }],
|
|
35
35
|
messages: {
|
|
36
36
|
shared:
|
|
37
37
|
"Shared mutable module-scope state between tests: use local variables inside each test instead.",
|
|
@@ -41,7 +41,8 @@ module.exports = rule(
|
|
|
41
41
|
const mutableTopLevel = new Set();
|
|
42
42
|
const pendingNamedCallbacks = [];
|
|
43
43
|
const pendingNamedSetupCallbacks = [];
|
|
44
|
-
const
|
|
44
|
+
const ruleOptions = context.options?.[0] ?? {};
|
|
45
|
+
const cleanupTracker = createCleanupTracker(ruleOptions);
|
|
45
46
|
const viMockTracker = createViMockTracker(context, mutableTopLevel);
|
|
46
47
|
const registryReports = createRegistryReports(
|
|
47
48
|
context,
|
|
@@ -166,7 +167,7 @@ module.exports = rule(
|
|
|
166
167
|
if (isTestCall(node)) {
|
|
167
168
|
testDepth += 1;
|
|
168
169
|
const callback = namedCallbackArgument(node.arguments);
|
|
169
|
-
if (callback) {
|
|
170
|
+
if (callback && !setupCallbackKind(node)) {
|
|
170
171
|
pendingNamedCallbacks.push({
|
|
171
172
|
declaration: resolveFunctionCallback(node, callback),
|
|
172
173
|
suiteKey: cleanupTracker.currentSuiteKey(),
|
|
@@ -19,6 +19,20 @@ const MOCK_METHODS = new Set([
|
|
|
19
19
|
"setSystemTime",
|
|
20
20
|
]);
|
|
21
21
|
|
|
22
|
+
const MOCK_CHAIN_METHODS = new Set([
|
|
23
|
+
"mockImplementation",
|
|
24
|
+
"mockImplementationOnce",
|
|
25
|
+
"mockReturnValue",
|
|
26
|
+
"mockReturnValueOnce",
|
|
27
|
+
"mockResolvedValue",
|
|
28
|
+
"mockResolvedValueOnce",
|
|
29
|
+
"mockRejectedValue",
|
|
30
|
+
"mockRejectedValueOnce",
|
|
31
|
+
"mockReset",
|
|
32
|
+
"mockRestore",
|
|
33
|
+
"mockClear",
|
|
34
|
+
]);
|
|
35
|
+
|
|
22
36
|
function isTestFile(filename) {
|
|
23
37
|
return TEST_FILE_PATTERN.test(filename.replace(/\\/g, "/"));
|
|
24
38
|
}
|
|
@@ -83,9 +97,13 @@ module.exports = rule(
|
|
|
83
97
|
},
|
|
84
98
|
(context) => {
|
|
85
99
|
let usesMocking = false;
|
|
100
|
+
function isMockChainCall(node) {
|
|
101
|
+
if (node.callee.type !== "MemberExpression") return false;
|
|
102
|
+
return MOCK_CHAIN_METHODS.has(propertyName(node.callee.property));
|
|
103
|
+
}
|
|
86
104
|
return {
|
|
87
105
|
CallExpression(node) {
|
|
88
|
-
if (isMockingCall(node, context)) usesMocking = true;
|
|
106
|
+
if (isMockingCall(node, context) || isMockChainCall(node)) usesMocking = true;
|
|
89
107
|
},
|
|
90
108
|
"Program:exit"(node) {
|
|
91
109
|
if (!isTestFile(context.filename)) return;
|