eslint-plugin-react-x 5.14.0 → 5.14.2

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.
Files changed (2) hide show
  1. package/dist/index.js +125 -82
  2. package/package.json +7 -7
package/dist/index.js CHANGED
@@ -5,9 +5,9 @@ import * as core from "@eslint-react/core";
5
5
  import { merge } from "@eslint-react/eslint";
6
6
  import { JsxDetectionHint, findParentAttribute, getElementFullType, hasAttribute } from "@eslint-react/jsx";
7
7
  import { AST_NODE_TYPES } from "@typescript-eslint/types";
8
+ import { computeObjectType, isAssignmentTargetEqual, isInitializedFromReact, resolve, resolveEnclosingAssignmentTarget } from "@eslint-react/var";
8
9
  import { DefinitionType, ScopeType } from "@typescript-eslint/scope-manager";
9
10
  import { findVariable, getStaticValue } from "@typescript-eslint/utils/ast-utils";
10
- import { computeObjectType, isAssignmentTargetEqual, isInitializedFromReact, resolve, resolveEnclosingAssignmentTarget } from "@eslint-react/var";
11
11
  import { P, isMatching, match } from "ts-pattern";
12
12
  import { compare } from "compare-versions";
13
13
  import { getConstrainedTypeAtLocation } from "@typescript-eslint/type-utils";
@@ -144,7 +144,7 @@ const rules$6 = {
144
144
  //#endregion
145
145
  //#region package.json
146
146
  var name$6 = "eslint-plugin-react-x";
147
- var version = "5.14.0";
147
+ var version = "5.14.2";
148
148
 
149
149
  //#endregion
150
150
  //#region src/utils/create-rule.ts
@@ -1157,6 +1157,67 @@ const MUTATING_ARRAY_METHODS = /* @__PURE__ */ new Set([
1157
1157
  "splice",
1158
1158
  "unshift"
1159
1159
  ]);
1160
+ /**
1161
+ * Return whether an identifier is an unresolved global or is declared in the
1162
+ * global/module scope.
1163
+ */
1164
+ function isGlobalVariable(context, node) {
1165
+ const variable = findVariable(context.sourceCode.getScope(node), node);
1166
+ if (variable == null || variable.defs.length === 0) return true;
1167
+ return variable.scope.type === ScopeType.global || variable.scope.type === ScopeType.module;
1168
+ }
1169
+ /**
1170
+ * Resolve an object expression to the global/module binding it aliases.
1171
+ *
1172
+ * This intentionally follows only stable `const alias = value` declarations.
1173
+ * It gives the rule useful Alias effects without pretending to perform full
1174
+ * control-flow analysis for reassigned locals.
1175
+ */
1176
+ function resolveGlobalOrigin(context, node, seen = /* @__PURE__ */ new Set()) {
1177
+ const expression = Extract.unwrap(node);
1178
+ if (seen.has(expression)) return null;
1179
+ seen.add(expression);
1180
+ if (expression.type === AST_NODE_TYPES.MemberExpression) return resolveGlobalOrigin(context, expression.object, seen);
1181
+ if (expression.type !== AST_NODE_TYPES.Identifier) return null;
1182
+ if (isGlobalVariable(context, expression)) return expression;
1183
+ const variable = findVariable(context.sourceCode.getScope(expression), expression);
1184
+ const definition = variable?.defs.length === 1 ? variable.defs[0] : null;
1185
+ if (definition?.type !== DefinitionType.Variable) return null;
1186
+ if (definition.node.id.type !== AST_NODE_TYPES.Identifier || definition.node.init == null) return null;
1187
+ if (definition.node.parent.kind !== "const") return null;
1188
+ const initializer = Extract.unwrap(definition.node.init);
1189
+ if (initializer.type !== AST_NODE_TYPES.Identifier && initializer.type !== AST_NODE_TYPES.MemberExpression) return null;
1190
+ return resolveGlobalOrigin(context, initializer, seen);
1191
+ }
1192
+ /**
1193
+ * Collect every write target in an assignment, including destructuring
1194
+ * patterns such as `[local, globalValue] = source`.
1195
+ */
1196
+ function getAssignmentTargets(node) {
1197
+ const target = Extract.unwrap(node);
1198
+ switch (target.type) {
1199
+ case AST_NODE_TYPES.Identifier:
1200
+ case AST_NODE_TYPES.MemberExpression: return [target];
1201
+ case AST_NODE_TYPES.ArrayPattern: return target.elements.flatMap((element) => element == null ? [] : getAssignmentTargets(element));
1202
+ case AST_NODE_TYPES.AssignmentPattern: return getAssignmentTargets(target.left);
1203
+ case AST_NODE_TYPES.ObjectPattern: return target.properties.flatMap((property) => {
1204
+ if (property.type === AST_NODE_TYPES.RestElement) return getAssignmentTargets(property.argument);
1205
+ return getAssignmentTargets(property.value);
1206
+ });
1207
+ case AST_NODE_TYPES.RestElement: return getAssignmentTargets(target.argument);
1208
+ default: return [];
1209
+ }
1210
+ }
1211
+ /** Resolve a direct call target, following simple function aliases. */
1212
+ function resolveToFunction(context, node, seen = /* @__PURE__ */ new Set()) {
1213
+ const expression = Extract.unwrap(node);
1214
+ if (Check.isFunction(expression)) return expression;
1215
+ if (expression.type !== AST_NODE_TYPES.Identifier || seen.has(expression)) return null;
1216
+ seen.add(expression);
1217
+ const resolved = resolve(context, expression);
1218
+ if (resolved == null) return null;
1219
+ return resolveToFunction(context, resolved, seen);
1220
+ }
1160
1221
 
1161
1222
  //#endregion
1162
1223
  //#region src/rules/globals/globals.ts
@@ -1179,101 +1240,83 @@ var globals_default = createRule({
1179
1240
  function create$49(context) {
1180
1241
  const hc = core.getHookCollector(context);
1181
1242
  const fc = core.getFunctionComponentCollector(context);
1182
- const violations = [];
1183
- /**
1184
- * Return true when `id` refers to a variable defined in the global or module
1185
- * scope (i.e. outside any function), or when it has no known definition.
1186
- */
1187
- function isGlobalOrModuleVariable(id) {
1188
- const variable = findVariable(context.sourceCode.getScope(id), id);
1189
- if (variable == null) return true;
1190
- if (variable.defs.length === 0) return true;
1191
- const scopeType = variable.scope.type;
1192
- return scopeType === ScopeType.global || scopeType === ScopeType.module;
1193
- }
1194
- /**
1195
- * Return the nearest enclosing function for `node`.
1196
- */
1243
+ const directEffects = /* @__PURE__ */ new Map();
1244
+ const callGraph = /* @__PURE__ */ new Map();
1197
1245
  function getEnclosingFunction(node) {
1198
1246
  return Traverse.findParent(node, Check.isFunction);
1199
1247
  }
1200
- /**
1201
- * Record a violation that will be filtered at Program:exit.
1202
- */
1203
- function recordViolation(node, messageId, data) {
1204
- const func = getEnclosingFunction(node);
1205
- if (func == null) return;
1206
- violations.push({
1248
+ function recordEffect(node, messageId, data) {
1249
+ const enclosing = getEnclosingFunction(node);
1250
+ if (enclosing == null) return;
1251
+ const effects = directEffects.get(enclosing) ?? [];
1252
+ effects.push({
1207
1253
  data,
1208
- func,
1209
1254
  messageId,
1210
1255
  node
1211
1256
  });
1257
+ directEffects.set(enclosing, effects);
1258
+ }
1259
+ function recordWrite(node, target) {
1260
+ if (target.type === AST_NODE_TYPES.Identifier) {
1261
+ if (!isGlobalVariable(context, target)) return;
1262
+ recordEffect(node, "mutatingGlobal", { name: target.name });
1263
+ return;
1264
+ }
1265
+ if (resolveGlobalOrigin(context, target.object) == null) return;
1266
+ recordEffect(node, "mutatingGlobalProperty", { name: context.sourceCode.getText(target) });
1267
+ }
1268
+ function recordCallEdge(node) {
1269
+ const caller = getEnclosingFunction(node);
1270
+ if (caller == null) return;
1271
+ const callee = resolveToFunction(context, node.callee);
1272
+ if (callee == null) return;
1273
+ const callees = callGraph.get(caller) ?? /* @__PURE__ */ new Set();
1274
+ callees.add(callee);
1275
+ callGraph.set(caller, callees);
1212
1276
  }
1213
1277
  return merge(hc.visitor, fc.visitor, {
1214
- /**
1215
- * Detect `renderCount++`, `renderCount--`
1216
- */
1217
- UpdateExpression(node) {
1218
- const arg = Extract.unwrap(node.argument);
1219
- if (arg.type === AST_NODE_TYPES.Identifier) {
1220
- if (!isGlobalOrModuleVariable(arg)) return;
1221
- recordViolation(node, "mutatingGlobal", { name: arg.name });
1222
- return;
1223
- }
1224
- if (arg.type === AST_NODE_TYPES.MemberExpression) {
1225
- const rootId = Extract.getRootIdentifier(arg);
1226
- if (rootId == null) return;
1227
- if (!isGlobalOrModuleVariable(rootId)) return;
1228
- recordViolation(node, "mutatingGlobalProperty", { name: context.sourceCode.getText(arg) });
1229
- }
1230
- },
1231
- /**
1232
- * Detect `renderCount = 1`, `window.currentUser = id`, `cache[id] = value`
1233
- */
1234
1278
  AssignmentExpression(node) {
1235
- const left = Extract.unwrap(node.left);
1236
- if (left.type === AST_NODE_TYPES.Identifier) {
1237
- if (!isGlobalOrModuleVariable(left)) return;
1238
- recordViolation(node, "mutatingGlobal", { name: left.name });
1239
- return;
1240
- }
1241
- if (left.type === AST_NODE_TYPES.MemberExpression) {
1242
- const rootId = Extract.getRootIdentifier(left);
1243
- if (rootId == null) return;
1244
- if (!isGlobalOrModuleVariable(rootId)) return;
1245
- recordViolation(node, "mutatingGlobalProperty", { name: context.sourceCode.getText(left) });
1246
- }
1279
+ for (const target of getAssignmentTargets(node.left)) recordWrite(node, target);
1247
1280
  },
1248
- /**
1249
- * Detect `events.push(event)`, `cache.sort()`, etc.
1250
- */
1251
1281
  CallExpression(node) {
1282
+ recordCallEdge(node);
1252
1283
  const callee = Extract.unwrap(node.callee);
1253
1284
  if (callee.type !== AST_NODE_TYPES.MemberExpression) return;
1254
- const { object, property } = callee;
1255
- if (property.type !== AST_NODE_TYPES.Identifier) return;
1256
- if (!MUTATING_ARRAY_METHODS.has(property.name)) return;
1257
- const rootId = Extract.getRootIdentifier(object);
1258
- if (rootId == null) return;
1259
- if (!isGlobalOrModuleVariable(rootId)) return;
1260
- recordViolation(node, "mutatingGlobalArrayMethod", {
1261
- name: rootId.name,
1262
- method: property.name
1285
+ const method = Extract.getPropertyName(callee.property);
1286
+ if (method == null || !MUTATING_ARRAY_METHODS.has(method)) return;
1287
+ const origin = resolveGlobalOrigin(context, callee.object);
1288
+ if (origin == null) return;
1289
+ recordEffect(node, "mutatingGlobalArrayMethod", {
1290
+ name: origin.name,
1291
+ method
1263
1292
  });
1264
1293
  },
1265
- "Program:exit"(node) {
1266
- const comps = fc.api.getAllComponents(node);
1267
- const hooks = hc.api.getAllHooks(node);
1268
- const funcs = [...comps, ...hooks];
1269
- for (const { data, func, messageId, node } of violations) {
1270
- if (!funcs.some((f) => f.node === func)) continue;
1271
- context.report({
1272
- data,
1273
- messageId,
1274
- node
1275
- });
1294
+ "Program:exit"(program) {
1295
+ const renderFunctions = [...fc.api.getAllComponents(program), ...hc.api.getAllHooks(program)];
1296
+ const visited = /* @__PURE__ */ new Set();
1297
+ const reported = /* @__PURE__ */ new Set();
1298
+ function applyFunctionEffects(func) {
1299
+ if (visited.has(func)) return;
1300
+ visited.add(func);
1301
+ for (const effect of directEffects.get(func) ?? []) {
1302
+ if (reported.has(effect)) continue;
1303
+ reported.add(effect);
1304
+ context.report(effect);
1305
+ }
1306
+ for (const callee of callGraph.get(func) ?? []) applyFunctionEffects(callee);
1276
1307
  }
1308
+ for (const { node } of renderFunctions) applyFunctionEffects(node);
1309
+ },
1310
+ UnaryExpression(node) {
1311
+ if (node.operator !== "delete") return;
1312
+ const argument = Extract.unwrap(node.argument);
1313
+ if (argument.type !== AST_NODE_TYPES.MemberExpression) return;
1314
+ recordWrite(node, argument);
1315
+ },
1316
+ UpdateExpression(node) {
1317
+ const argument = Extract.unwrap(node.argument);
1318
+ if (argument.type !== AST_NODE_TYPES.Identifier && argument.type !== AST_NODE_TYPES.MemberExpression) return;
1319
+ recordWrite(node, argument);
1277
1320
  }
1278
1321
  });
1279
1322
  }
@@ -7547,9 +7590,9 @@ function create$2(context) {
7547
7590
  });
7548
7591
  },
7549
7592
  "Program:exit"(node) {
7550
- const components = fc.api.getAllComponents(node);
7593
+ const comps = fc.api.getAllComponents(node);
7551
7594
  const hooks = hc.api.getAllHooks(node);
7552
- const funcs = [...components, ...hooks];
7595
+ const funcs = [...comps, ...hooks];
7553
7596
  for (const { func, node } of evalCalls) {
7554
7597
  if (!funcs.some((f) => f.node === func)) continue;
7555
7598
  context.report({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-x",
3
- "version": "5.14.0",
3
+ "version": "5.14.2",
4
4
  "description": "A set of composable ESLint rules for libraries and frameworks that use React as a UI runtime.",
5
5
  "keywords": [
6
6
  "react",
@@ -45,12 +45,12 @@
45
45
  "string-ts": "^2.3.1",
46
46
  "ts-api-utils": "^2.5.0",
47
47
  "ts-pattern": "^5.9.0",
48
- "@eslint-react/ast": "5.14.0",
49
- "@eslint-react/eslint": "5.14.0",
50
- "@eslint-react/jsx": "5.14.0",
51
- "@eslint-react/core": "5.14.0",
52
- "@eslint-react/shared": "5.14.0",
53
- "@eslint-react/var": "5.14.0"
48
+ "@eslint-react/ast": "5.14.2",
49
+ "@eslint-react/core": "5.14.2",
50
+ "@eslint-react/shared": "5.14.2",
51
+ "@eslint-react/eslint": "5.14.2",
52
+ "@eslint-react/jsx": "5.14.2",
53
+ "@eslint-react/var": "5.14.2"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/react": "^19.2.17",