eslint-plugin-no-mistakes 0.11.0 → 0.12.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.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "ESLint and Oxlint rules for deterministic no-mistakes code analysis",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/helpers.js CHANGED
@@ -134,14 +134,28 @@ function selectorValueNode(attribute) {
134
134
  return expression && expression.type !== "JSXEmptyExpression" ? expression : null;
135
135
  }
136
136
 
137
- const LOCAL_BINDING_TYPES = new Set(["Variable", "Parameter", "CatchClause", "FunctionName"]);
137
+ const LOCAL_BINDING_TYPES = new Set([
138
+ "Variable",
139
+ "Parameter",
140
+ "CatchClause",
141
+ "FunctionName",
142
+ "ImportBinding",
143
+ "ClassName",
144
+ ]);
138
145
 
139
146
  function isFetchShadowed(scope) {
140
147
  while (scope) {
141
- const variable = scope.variables.find((v) => v.name === "fetch");
148
+ const get = scope.set?.get;
149
+ const variable = typeof get === "function" ? get.call(scope.set, "fetch") : null;
142
150
  if (variable) {
143
151
  return variable.defs.some((def) => LOCAL_BINDING_TYPES.has(def.type));
144
152
  }
153
+ if (!scope.set || typeof get !== "function") {
154
+ const fallback = scope.variables?.find((item) => item.name === "fetch");
155
+ if (fallback) {
156
+ return fallback.defs.some((def) => LOCAL_BINDING_TYPES.has(def.type));
157
+ }
158
+ }
145
159
  scope = scope.upper;
146
160
  }
147
161
  return false;
@@ -17,7 +17,9 @@ function collectReturnBranches(node, branches) {
17
17
  return;
18
18
  }
19
19
  if (node.type === "ReturnStatement") {
20
- branches.push(...jsxBranches(node.argument));
20
+ for (const branch of jsxBranches(node.argument)) {
21
+ branches.push(branch);
22
+ }
21
23
  return;
22
24
  }
23
25
  if (node.type === "ClassDeclaration" || node.type === "ClassExpression") {
@@ -101,7 +103,11 @@ function childNodes(node) {
101
103
  continue;
102
104
  }
103
105
  if (Array.isArray(value)) {
104
- children.push(...value.filter(isAstNode));
106
+ for (const child of value) {
107
+ if (isAstNode(child)) {
108
+ children.push(child);
109
+ }
110
+ }
105
111
  } else if (isAstNode(value)) {
106
112
  children.push(value);
107
113
  }
@@ -25,7 +25,11 @@ function propertyName(node, computed = false) {
25
25
  function isGlobalSetTimeout(node, context) {
26
26
  let scope = context.sourceCode.getScope(node);
27
27
  while (scope) {
28
- const variable = scope.variables.find((candidate) => candidate.name === "setTimeout");
28
+ const get = scope.set?.get;
29
+ const variable =
30
+ typeof get === "function"
31
+ ? get.call(scope.set, "setTimeout")
32
+ : scope.variables.find((candidate) => candidate.name === "setTimeout");
29
33
  if (variable) return variable.defs.length === 0;
30
34
  scope = scope.upper;
31
35
  }
@@ -23,17 +23,24 @@ function isInsideUncalledNestedFunction(node, testDepth, setupDepth) {
23
23
  return false;
24
24
  }
25
25
 
26
- function isModuleMutable(context, mutableTopLevel, node, name) {
26
+ function isModuleMutable({ context, mutableTopLevel, node, name }) {
27
27
  let scope = context.sourceCode.getScope(node);
28
28
  while (scope) {
29
- const variable = scope.variables.find((candidate) => candidate.name === name);
30
- if (variable) {
31
- return (
32
- mutableTopLevel.has(variable.name) &&
33
- (variable.scope.type === "module" || variable.scope.block.type === "Program")
34
- );
29
+ const get = scope.set?.get;
30
+ const variable = typeof get === "function" ? get.call(scope.set, name) : null;
31
+ const resolvedVariable =
32
+ variable ||
33
+ (typeof get !== "function" ? scope.variables?.find((item) => item.name === name) : null);
34
+
35
+ if (!resolvedVariable) {
36
+ scope = scope.upper;
37
+ continue;
35
38
  }
36
- scope = scope.upper;
39
+
40
+ return (
41
+ mutableTopLevel.has(resolvedVariable.name) &&
42
+ (resolvedVariable.scope.type === "module" || resolvedVariable.scope.block.type === "Program")
43
+ );
37
44
  }
38
45
  return false;
39
46
  }
@@ -112,7 +119,7 @@ function createRegistryReports(context, mutableTopLevel, cleanupTracker, isCaptu
112
119
  testDepth > 0 &&
113
120
  setupDepth === 0 &&
114
121
  !isCaptured(name) &&
115
- isModuleMutable(context, mutableTopLevel, node, name)
122
+ isModuleMutable({ context, mutableTopLevel, node, name })
116
123
  ) {
117
124
  pending.push({ node, path, suiteKey: cleanupTracker.currentSuiteKey() });
118
125
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  const { rule } = require("../helpers");
4
+
4
5
  const {
5
6
  createRegistryReports,
6
7
  createViMockTracker,
@@ -9,6 +10,7 @@ const {
9
10
  isResetAssignment,
10
11
  walkSharedMutations,
11
12
  } = require("./test-no-shared-state-analysis");
13
+
12
14
  const {
13
15
  calleeName,
14
16
  collectPatternNames,
@@ -56,10 +58,9 @@ module.exports = rule(
56
58
  testDepth > 0 &&
57
59
  setupDepth === 0 &&
58
60
  !viMockTracker.isCaptured(name) &&
59
- isModuleMutable(context, mutableTopLevel, node, name)
60
- ) {
61
+ isModuleMutable({ context, mutableTopLevel, node, name })
62
+ )
61
63
  context.report({ node, messageId: "shared" });
62
- }
63
64
  }
64
65
 
65
66
  function reportAssignment(node) {
@@ -73,10 +74,9 @@ module.exports = rule(
73
74
  name &&
74
75
  setupDepth > 0 &&
75
76
  mutableTopLevel.has(name) &&
76
- isModuleMutable(context, mutableTopLevel, node, name)
77
- ) {
77
+ isModuleMutable({ context, mutableTopLevel, node, name })
78
+ )
78
79
  cleanupTracker.remember(path);
79
- }
80
80
  }
81
81
 
82
82
  function rememberCall(node) {
@@ -97,16 +97,36 @@ module.exports = rule(
97
97
  rememberSetupCleanup(node, mutationRootName(node.left), mutationPath(node.left.object));
98
98
  }
99
99
 
100
+ const mutationWalk = {
101
+ onAssignment: (assignment) => {
102
+ rememberAssignmentCleanup(assignment);
103
+ reportAssignment(assignment);
104
+ },
105
+ onCall: rememberCall,
106
+ onUpdate: (update) => reportIfShared(update, mutationRootName(update.argument)),
107
+ };
108
+
100
109
  function resolveFunctionCallback(node, callback) {
101
110
  let scope = context.sourceCode.getScope(node);
102
111
  while (scope) {
103
- const variable = scope.variables.find((candidate) => candidate.name === callback.name);
104
- const declaration = variable?.defs[0]?.node;
112
+ const get = scope.set?.get;
113
+ const resolvedVariable =
114
+ (typeof get === "function" ? get.call(scope.set, callback.name) : null) ||
115
+ (typeof get !== "function"
116
+ ? scope.variables?.find((item) => item.name === callback.name)
117
+ : null);
118
+
119
+ if (!resolvedVariable) {
120
+ scope = scope.upper;
121
+ continue;
122
+ }
123
+
124
+ const declaration = resolvedVariable.defs[0]?.node;
105
125
  if (declaration?.type === "FunctionDeclaration") return declaration;
106
126
  if (declaration?.type === "VariableDeclarator" && isFunctionNode(declaration.init)) {
107
127
  return declaration.init;
108
128
  }
109
- scope = scope.upper;
129
+ return null;
110
130
  }
111
131
  return null;
112
132
  }
@@ -127,7 +147,7 @@ module.exports = rule(
127
147
  const previousSetupDepth = setupDepth;
128
148
  setupDepth = 1;
129
149
  cleanupTracker.beginSetup(kind, suiteKey);
130
- checkSharedMutations(declaration.body);
150
+ walkSharedMutations(declaration.body, mutationWalk);
131
151
  setupDepth = previousSetupDepth;
132
152
  cleanupTracker.endSetup();
133
153
  }
@@ -135,7 +155,7 @@ module.exports = rule(
135
155
  for (const { declaration, suiteKey } of pendingNamedCallbacks) {
136
156
  if (!declaration) continue;
137
157
  cleanupTracker.setReplaySuite(suiteKey);
138
- checkSharedMutations(declaration.body);
158
+ walkSharedMutations(declaration.body, mutationWalk);
139
159
  cleanupTracker.clearReplaySuite();
140
160
  }
141
161
  registryReports.flush();
@@ -154,15 +174,13 @@ module.exports = rule(
154
174
  }
155
175
  }
156
176
  const setupKind = setupCallbackKind(node);
157
- if (setupKind) {
158
- const callback = firstNamedCallbackArgument(node.arguments);
159
- if (callback) {
160
- pendingNamedSetupCallbacks.push({
161
- declaration: resolveFunctionCallback(node, callback),
162
- suiteKey: cleanupTracker.currentSuiteKey(),
163
- kind: setupKind,
164
- });
165
- }
177
+ const setupCallback = setupKind && firstNamedCallbackArgument(node.arguments);
178
+ if (setupCallback) {
179
+ pendingNamedSetupCallbacks.push({
180
+ declaration: resolveFunctionCallback(node, setupCallback),
181
+ suiteKey: cleanupTracker.currentSuiteKey(),
182
+ kind: setupKind,
183
+ });
166
184
  }
167
185
  if (setupKind) {
168
186
  setupDepth += 1;
@@ -185,24 +203,9 @@ module.exports = rule(
185
203
  cleanupTracker.endSetup();
186
204
  }
187
205
  const isInsideNested = isInsideUncalledNestedFunction(node, testDepth, setupDepth);
188
- if (!isInsideNested) {
189
- rememberCall(node);
190
- }
206
+ if (!isInsideNested) rememberCall(node);
191
207
  if (calleeName(node.callee) === "describe") cleanupTracker.exitSuite();
192
208
  },
193
209
  };
194
-
195
- function checkSharedMutations(node) {
196
- walkSharedMutations(node, {
197
- onAssignment: (assignment) => {
198
- rememberAssignmentCleanup(assignment);
199
- reportAssignment(assignment);
200
- },
201
- onCall: (call) => {
202
- rememberCall(call);
203
- },
204
- onUpdate: (update) => reportIfShared(update, mutationRootName(update.argument)),
205
- });
206
- }
207
210
  },
208
211
  );