eslint-plugin-no-mistakes 0.12.0 → 0.13.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-no-mistakes",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
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.3.0",
27
- "oxlint": "^1.64.0",
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
- return name === "beforeEach" || name === "afterEach"
29
- ? "per-test"
30
- : name === "beforeAll" || name === "afterAll"
31
- ? "once"
32
- : null;
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
- return node.parent?.type === "CallExpression" && isSetupCall(node.parent);
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 === "per-test" ? suiteKey : undefined;
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 cleanupTracker = createCleanupTracker();
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;