eslint-plugin-react-x 5.19.0 → 5.19.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.
Files changed (2) hide show
  1. package/dist/index.js +217 -134
  2. package/package.json +16 -16
package/dist/index.js CHANGED
@@ -4,12 +4,12 @@ import { Check, Compare, Extract, Traverse } from "@eslint-react/ast";
4
4
  import * as core from "@eslint-react/core";
5
5
  import { merge } from "@eslint-react/eslint";
6
6
  import { AST_NODE_TYPES } from "@typescript-eslint/types";
7
+ import { P, isMatching, match } from "ts-pattern";
7
8
  import { isAssignmentTargetEqual, isInitializedFromReact, resolve, resolveEnclosingAssignmentTarget, resolveObjectType } 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
11
  import { findParentAttribute, getElementFullType, hasAttribute } from "@eslint-react/jsx";
11
12
  import { compare } from "compare-versions";
12
- import { P, isMatching, match } from "ts-pattern";
13
13
  import { getConstrainedTypeAtLocation } from "@typescript-eslint/type-utils";
14
14
  import { unionConstituents } from "ts-api-utils";
15
15
  import "typescript";
@@ -145,7 +145,7 @@ const rules$6 = {
145
145
  //#endregion
146
146
  //#region package.json
147
147
  var name$6 = "eslint-plugin-react-x";
148
- var version = "5.19.0";
148
+ var version = "5.19.1";
149
149
 
150
150
  //#endregion
151
151
  //#region src/utils/create-rule.ts
@@ -1143,6 +1143,97 @@ const MUTATING_ARRAY_METHODS = /* @__PURE__ */ new Set([
1143
1143
  "unshift"
1144
1144
  ]);
1145
1145
  /**
1146
+ * Collect every write target in an assignment, including destructuring
1147
+ * patterns such as `[local, globalValue] = source`.
1148
+ */
1149
+ function getAssignmentTargets(node) {
1150
+ const target = Extract.unwrap(node);
1151
+ switch (target.type) {
1152
+ case AST_NODE_TYPES.Identifier:
1153
+ case AST_NODE_TYPES.MemberExpression: return [target];
1154
+ case AST_NODE_TYPES.ArrayPattern: return target.elements.flatMap((element) => element == null ? [] : getAssignmentTargets(element));
1155
+ case AST_NODE_TYPES.AssignmentPattern: return getAssignmentTargets(target.left);
1156
+ case AST_NODE_TYPES.ObjectPattern: return target.properties.flatMap((property) => {
1157
+ if (property.type === AST_NODE_TYPES.RestElement) return getAssignmentTargets(property.argument);
1158
+ return getAssignmentTargets(property.value);
1159
+ });
1160
+ case AST_NODE_TYPES.RestElement: return getAssignmentTargets(target.argument);
1161
+ default: return [];
1162
+ }
1163
+ }
1164
+ /** Resolve a direct call target, following simple function aliases. */
1165
+ function resolveToFunction(context, node, seen = /* @__PURE__ */ new Set()) {
1166
+ const expression = Extract.unwrap(node);
1167
+ if (Check.isFunction(expression)) return expression;
1168
+ if (!Check.isIdentifier(expression) || seen.has(expression)) return null;
1169
+ seen.add(expression);
1170
+ const resolved = resolve(context, expression);
1171
+ if (resolved == null) return null;
1172
+ return resolveToFunction(context, resolved, seen);
1173
+ }
1174
+
1175
+ //#endregion
1176
+ //#region src/rules/globals/collect.ts
1177
+ function createGlobalsCollector() {
1178
+ const facts = {
1179
+ callEdges: [],
1180
+ methodCalls: [],
1181
+ writes: []
1182
+ };
1183
+ function getEnclosingFunction(node) {
1184
+ return Traverse.findParent(node, Check.isFunction);
1185
+ }
1186
+ function pushWrite(node, target) {
1187
+ const enclosingFunction = getEnclosingFunction(node);
1188
+ if (enclosingFunction == null) return;
1189
+ facts.writes.push({
1190
+ enclosingFunction,
1191
+ node,
1192
+ target
1193
+ });
1194
+ }
1195
+ return {
1196
+ facts,
1197
+ visitor: {
1198
+ AssignmentExpression(node) {
1199
+ for (const target of getAssignmentTargets(node.left)) pushWrite(node, target);
1200
+ },
1201
+ CallExpression(node) {
1202
+ const caller = getEnclosingFunction(node);
1203
+ if (caller != null) facts.callEdges.push({
1204
+ callee: node.callee,
1205
+ caller
1206
+ });
1207
+ const callee = Extract.unwrap(node.callee);
1208
+ if (callee.type !== AST_NODE_TYPES.MemberExpression) return;
1209
+ const method = Extract.getCalleeName(node);
1210
+ if (method == null || !MUTATING_ARRAY_METHODS.has(method)) return;
1211
+ if (caller == null) return;
1212
+ facts.methodCalls.push({
1213
+ enclosingFunction: caller,
1214
+ method,
1215
+ node,
1216
+ receiver: callee.object
1217
+ });
1218
+ },
1219
+ UnaryExpression(node) {
1220
+ if (node.operator !== "delete") return;
1221
+ const argument = Extract.unwrap(node.argument);
1222
+ if (argument.type !== AST_NODE_TYPES.MemberExpression) return;
1223
+ pushWrite(node, argument);
1224
+ },
1225
+ UpdateExpression(node) {
1226
+ const argument = Extract.unwrap(node.argument);
1227
+ if (argument.type !== AST_NODE_TYPES.Identifier && argument.type !== AST_NODE_TYPES.MemberExpression) return;
1228
+ pushWrite(node, argument);
1229
+ }
1230
+ }
1231
+ };
1232
+ }
1233
+
1234
+ //#endregion
1235
+ //#region src/rules/globals/origins.ts
1236
+ /**
1146
1237
  * Return whether an identifier is an unresolved global or is declared in the
1147
1238
  * global/module scope.
1148
1239
  */
@@ -1174,34 +1265,85 @@ function resolveGlobalOrigin(context, node, seen = /* @__PURE__ */ new Set()) {
1174
1265
  if (initializer.type !== AST_NODE_TYPES.Identifier && initializer.type !== AST_NODE_TYPES.MemberExpression) return null;
1175
1266
  return resolveGlobalOrigin(context, initializer, seen);
1176
1267
  }
1268
+
1269
+ //#endregion
1270
+ //#region src/rules/globals/effects.ts
1177
1271
  /**
1178
- * Collect every write target in an assignment, including destructuring
1179
- * patterns such as `[local, globalValue] = source`.
1272
+ * Classify the collected writes and mutating method calls, keeping only the
1273
+ * ones that reach a global/module binding, grouped by the function that
1274
+ * performs them.
1180
1275
  */
1181
- function getAssignmentTargets(node) {
1182
- const target = Extract.unwrap(node);
1183
- switch (target.type) {
1184
- case AST_NODE_TYPES.Identifier:
1185
- case AST_NODE_TYPES.MemberExpression: return [target];
1186
- case AST_NODE_TYPES.ArrayPattern: return target.elements.flatMap((element) => element == null ? [] : getAssignmentTargets(element));
1187
- case AST_NODE_TYPES.AssignmentPattern: return getAssignmentTargets(target.left);
1188
- case AST_NODE_TYPES.ObjectPattern: return target.properties.flatMap((property) => {
1189
- if (property.type === AST_NODE_TYPES.RestElement) return getAssignmentTargets(property.argument);
1190
- return getAssignmentTargets(property.value);
1276
+ function inferGlobalMutations(context, facts) {
1277
+ const directEffects = /* @__PURE__ */ new Map();
1278
+ function pushEffect(enclosingFunction, effect) {
1279
+ const effects = directEffects.get(enclosingFunction) ?? [];
1280
+ effects.push(effect);
1281
+ directEffects.set(enclosingFunction, effects);
1282
+ }
1283
+ for (const write of facts.writes) {
1284
+ if (Check.isIdentifier(write.target)) {
1285
+ if (!isGlobalVariable(context, write.target)) continue;
1286
+ pushEffect(write.enclosingFunction, {
1287
+ kind: "global",
1288
+ name: write.target.name,
1289
+ method: null,
1290
+ node: write.node
1291
+ });
1292
+ continue;
1293
+ }
1294
+ if (resolveGlobalOrigin(context, write.target.object) == null) continue;
1295
+ pushEffect(write.enclosingFunction, {
1296
+ kind: "property",
1297
+ name: context.sourceCode.getText(write.target),
1298
+ method: null,
1299
+ node: write.node
1300
+ });
1301
+ }
1302
+ for (const call of facts.methodCalls) {
1303
+ const origin = resolveGlobalOrigin(context, call.receiver);
1304
+ if (origin == null) continue;
1305
+ pushEffect(call.enclosingFunction, {
1306
+ kind: "method",
1307
+ name: origin.name,
1308
+ method: call.method,
1309
+ node: call.node
1191
1310
  });
1192
- case AST_NODE_TYPES.RestElement: return getAssignmentTargets(target.argument);
1193
- default: return [];
1194
1311
  }
1312
+ return directEffects;
1195
1313
  }
1196
- /** Resolve a direct call target, following simple function aliases. */
1197
- function resolveToFunction(context, node, seen = /* @__PURE__ */ new Set()) {
1198
- const expression = Extract.unwrap(node);
1199
- if (Check.isFunction(expression)) return expression;
1200
- if (!Check.isIdentifier(expression) || seen.has(expression)) return null;
1201
- seen.add(expression);
1202
- const resolved = resolve(context, expression);
1203
- if (resolved == null) return null;
1204
- return resolveToFunction(context, resolved, seen);
1314
+ /** Resolve the collected call edges into a function-to-function call graph. */
1315
+ function inferCallGraph(context, callEdges) {
1316
+ const callGraph = /* @__PURE__ */ new Map();
1317
+ for (const edge of callEdges) {
1318
+ const callee = resolveToFunction(context, edge.callee);
1319
+ if (callee == null) continue;
1320
+ const callees = callGraph.get(edge.caller) ?? /* @__PURE__ */ new Set();
1321
+ callees.add(callee);
1322
+ callGraph.set(edge.caller, callees);
1323
+ }
1324
+ return callGraph;
1325
+ }
1326
+ /**
1327
+ * Like the SPEC's function signatures, these summaries keep creation of an
1328
+ * effect separate from applying it in a component or hook render: walk the
1329
+ * call graph from each render function and gather every reachable effect once.
1330
+ */
1331
+ function collectReachableEffects(renderFunctions, directEffects, callGraph) {
1332
+ const visited = /* @__PURE__ */ new Set();
1333
+ const reported = /* @__PURE__ */ new Set();
1334
+ const reachable = [];
1335
+ function applyFunctionEffects(func) {
1336
+ if (visited.has(func)) return;
1337
+ visited.add(func);
1338
+ for (const effect of directEffects.get(func) ?? []) {
1339
+ if (reported.has(effect)) continue;
1340
+ reported.add(effect);
1341
+ reachable.push(effect);
1342
+ }
1343
+ for (const callee of callGraph.get(func) ?? []) applyFunctionEffects(callee);
1344
+ }
1345
+ for (const func of renderFunctions) applyFunctionEffects(func);
1346
+ return reachable;
1205
1347
  }
1206
1348
 
1207
1349
  //#endregion
@@ -1223,87 +1365,25 @@ var globals_default = createRule({
1223
1365
  defaultOptions: []
1224
1366
  });
1225
1367
  function create$49(context) {
1226
- const hc = core.getHookCollector(context);
1227
- const fc = core.getFunctionComponentCollector(context);
1228
- const directEffects = /* @__PURE__ */ new Map();
1229
- const callGraph = /* @__PURE__ */ new Map();
1230
- function getEnclosingFunction(node) {
1231
- return Traverse.findParent(node, Check.isFunction);
1232
- }
1233
- function recordEffect(node, messageId, data) {
1234
- const enclosing = getEnclosingFunction(node);
1235
- if (enclosing == null) return;
1236
- const effects = directEffects.get(enclosing) ?? [];
1237
- effects.push({
1238
- data,
1239
- messageId,
1240
- node
1241
- });
1242
- directEffects.set(enclosing, effects);
1243
- }
1244
- function recordWrite(node, target) {
1245
- if (Check.isIdentifier(target)) {
1246
- if (!isGlobalVariable(context, target)) return;
1247
- recordEffect(node, "mutatingGlobal", { name: target.name });
1248
- return;
1249
- }
1250
- if (resolveGlobalOrigin(context, target.object) == null) return;
1251
- recordEffect(node, "mutatingGlobalProperty", { name: context.sourceCode.getText(target) });
1252
- }
1253
- function recordCallEdge(node) {
1254
- const caller = getEnclosingFunction(node);
1255
- if (caller == null) return;
1256
- const callee = resolveToFunction(context, node.callee);
1257
- if (callee == null) return;
1258
- const callees = callGraph.get(caller) ?? /* @__PURE__ */ new Set();
1259
- callees.add(callee);
1260
- callGraph.set(caller, callees);
1261
- }
1262
- return merge(hc.visitor, fc.visitor, {
1263
- AssignmentExpression(node) {
1264
- for (const target of getAssignmentTargets(node.left)) recordWrite(node, target);
1265
- },
1266
- CallExpression(node) {
1267
- recordCallEdge(node);
1268
- const callee = Extract.unwrap(node.callee);
1269
- if (callee.type !== AST_NODE_TYPES.MemberExpression) return;
1270
- const method = Extract.getCalleeName(node);
1271
- if (method == null || !MUTATING_ARRAY_METHODS.has(method)) return;
1272
- const origin = resolveGlobalOrigin(context, callee.object);
1273
- if (origin == null) return;
1274
- recordEffect(node, "mutatingGlobalArrayMethod", {
1275
- name: origin.name,
1276
- method
1368
+ const hooks = core.getHookCollector(context);
1369
+ const comps = core.getFunctionComponentCollector(context);
1370
+ const globs = createGlobalsCollector();
1371
+ return merge(hooks.visitor, comps.visitor, globs.visitor, { "Program:exit"(program) {
1372
+ const renderFunctions = [...comps.api.getAllComponents(program), ...hooks.api.getAllHooks(program)].map(({ node }) => node);
1373
+ const directEffects = inferGlobalMutations(context, globs.facts);
1374
+ const callGraph = inferCallGraph(context, globs.facts.callEdges);
1375
+ for (const effect of collectReachableEffects(renderFunctions, directEffects, callGraph)) {
1376
+ const data = effect.method == null ? { name: effect.name } : {
1377
+ name: effect.name,
1378
+ method: effect.method
1379
+ };
1380
+ context.report({
1381
+ data,
1382
+ messageId: match(effect).with({ kind: "global" }, () => "mutatingGlobal").with({ kind: "method" }, () => "mutatingGlobalArrayMethod").with({ kind: "property" }, () => "mutatingGlobalProperty").exhaustive(),
1383
+ node: effect.node
1277
1384
  });
1278
- },
1279
- "Program:exit"(program) {
1280
- const renderFunctions = [...fc.api.getAllComponents(program), ...hc.api.getAllHooks(program)];
1281
- const visited = /* @__PURE__ */ new Set();
1282
- const reported = /* @__PURE__ */ new Set();
1283
- function applyFunctionEffects(func) {
1284
- if (visited.has(func)) return;
1285
- visited.add(func);
1286
- for (const effect of directEffects.get(func) ?? []) {
1287
- if (reported.has(effect)) continue;
1288
- reported.add(effect);
1289
- context.report(effect);
1290
- }
1291
- for (const callee of callGraph.get(func) ?? []) applyFunctionEffects(callee);
1292
- }
1293
- for (const { node } of renderFunctions) applyFunctionEffects(node);
1294
- },
1295
- UnaryExpression(node) {
1296
- if (node.operator !== "delete") return;
1297
- const argument = Extract.unwrap(node.argument);
1298
- if (argument.type !== AST_NODE_TYPES.MemberExpression) return;
1299
- recordWrite(node, argument);
1300
- },
1301
- UpdateExpression(node) {
1302
- const argument = Extract.unwrap(node.argument);
1303
- if (argument.type !== AST_NODE_TYPES.Identifier && argument.type !== AST_NODE_TYPES.MemberExpression) return;
1304
- recordWrite(node, argument);
1305
1385
  }
1306
- });
1386
+ } });
1307
1387
  }
1308
1388
 
1309
1389
  //#endregion
@@ -1314,7 +1394,7 @@ function create$49(context) {
1314
1394
  * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
1315
1395
  * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
1316
1396
  */
1317
- const MUTATING_METHODS = /* @__PURE__ */ new Set([
1397
+ const KNOWN_MUTATING_METHODS = /* @__PURE__ */ new Set([
1318
1398
  "add",
1319
1399
  "clear",
1320
1400
  "copyWithin",
@@ -1330,9 +1410,9 @@ const MUTATING_METHODS = /* @__PURE__ */ new Set([
1330
1410
  "unshift"
1331
1411
  ]);
1332
1412
  /**
1333
- * Known navigation hooks.
1413
+ * Known mutating hooks.
1334
1414
  */
1335
- const NAVIGATION_HOOKS = /* @__PURE__ */ new Set([
1415
+ const KNOWN_MUTATING_HOOKS = /* @__PURE__ */ new Set([
1336
1416
  "useHistory",
1337
1417
  "useNavigate",
1338
1418
  "useNavigation",
@@ -1346,6 +1426,20 @@ function isNodeWithin(node, ancestor) {
1346
1426
  }
1347
1427
  return false;
1348
1428
  }
1429
+ function isComponentPropsDefinition(def, components) {
1430
+ if (def.type !== DefinitionType.Parameter) return false;
1431
+ const fn = def.node;
1432
+ if (!Check.isFunction(fn)) return false;
1433
+ const firstParam = fn.params.at(0);
1434
+ if (firstParam == null || !isNodeWithin(def.name, firstParam)) return false;
1435
+ return components.includes(fn);
1436
+ }
1437
+ function getStateHookName(context, init) {
1438
+ const { additionalStateHooks } = getSettingsFromContext(context);
1439
+ if (core.isUseStateLikeCall(init, additionalStateHooks)) return Extract.getCalleeName(init) ?? "useState";
1440
+ if (core.isUseReducerCall(context, init)) return "useReducer";
1441
+ return null;
1442
+ }
1349
1443
  function resolveToFunctionNode(context, node, seen = /* @__PURE__ */ new Set()) {
1350
1444
  const expr = Extract.unwrap(node);
1351
1445
  if (Check.isFunction(expr)) return expr;
@@ -1390,7 +1484,7 @@ function isInitializedFromUseRef(context, node) {
1390
1484
  function isKnownNonMutatingMethodCall(context, node) {
1391
1485
  const callee = Extract.unwrap(node.callee);
1392
1486
  return Check.isExpression(callee) && isInitializedFromCall(context, callee, (init) => {
1393
- return NAVIGATION_HOOKS.values().some((hook) => core.isAPICall(hook)(context, init));
1487
+ return KNOWN_MUTATING_HOOKS.values().some((hook) => core.isAPICall(hook)(context, init));
1394
1488
  });
1395
1489
  }
1396
1490
  function isRefLikeChain(context, node) {
@@ -1435,7 +1529,7 @@ function createImmutabilityCollector() {
1435
1529
  const callee = Extract.unwrap(node.callee);
1436
1530
  if (callee.type === AST_NODE_TYPES.MemberExpression) {
1437
1531
  const method = Extract.getCalleeName(node);
1438
- if (method != null && MUTATING_METHODS.has(method)) {
1532
+ if (method != null && KNOWN_MUTATING_METHODS.has(method)) {
1439
1533
  const root = Extract.getIdentifierAt(callee.object, 0);
1440
1534
  if (root != null) pushMutation("value", node, callee.object, root);
1441
1535
  }
@@ -1484,36 +1578,23 @@ function createImmutabilityCollector() {
1484
1578
 
1485
1579
  //#endregion
1486
1580
  //#region src/rules/immutability/origins.ts
1487
- function isComponentPropsDefinition(context, def) {
1488
- if (def.type !== DefinitionType.Parameter) return false;
1489
- const fn = def.node;
1490
- if (!Check.isFunction(fn)) return false;
1491
- const firstParam = fn.params.at(0);
1492
- if (firstParam == null || !isNodeWithin(def.name, firstParam)) return false;
1493
- return core.isFunctionComponentDefinition(context, fn, core.DEFAULT_COMPONENT_DETECTION_HINT);
1494
- }
1495
- function getStateHookName(context, init) {
1496
- const { additionalStateHooks } = getSettingsFromContext(context);
1497
- if (core.isUseStateLikeCall(init, additionalStateHooks)) return Extract.getCalleeName(init) ?? "useState";
1498
- if (core.isUseReducerCall(context, init)) return "useReducer";
1499
- return null;
1500
- }
1501
1581
  /**
1502
1582
  * Classify whether a variable ultimately holds a value that must be treated as
1503
1583
  * immutable: a component's props, a state value returned from `useState`-like or
1504
1584
  * `useReducer` calls, or a shallow copy (spread literal) of either.
1505
1585
  * @param context The rule context.
1506
1586
  * @param variable The variable to classify.
1587
+ * @param components The confirmed function component nodes in the file.
1507
1588
  * @param seen Variables already visited during spread recursion.
1508
1589
  * @returns The frozen origin, or `null` when the variable is not derived from one.
1509
1590
  */
1510
- function classifyFrozenOrigin(context, variable, seen = /* @__PURE__ */ new Set()) {
1591
+ function classifyFrozenOrigin(context, variable, components, seen = /* @__PURE__ */ new Set()) {
1511
1592
  if (seen.has(variable)) return null;
1512
1593
  seen.add(variable);
1513
1594
  const origin = resolveVariableOrigin(context, variable);
1514
1595
  const def = origin.defs.length === 1 ? origin.defs[0] : null;
1515
1596
  if (def == null) return null;
1516
- if (isComponentPropsDefinition(context, def)) return {
1597
+ if (isComponentPropsDefinition(def, components)) return {
1517
1598
  kind: "props",
1518
1599
  name: origin.name
1519
1600
  };
@@ -1542,7 +1623,7 @@ function classifyFrozenOrigin(context, variable, seen = /* @__PURE__ */ new Set(
1542
1623
  if (!Check.isIdentifier(argument)) continue;
1543
1624
  const source = findVariable(context.sourceCode.getScope(argument), argument);
1544
1625
  if (source == null) continue;
1545
- const inner = classifyFrozenOrigin(context, source, seen);
1626
+ const inner = classifyFrozenOrigin(context, source, components, seen);
1546
1627
  if (inner != null) return {
1547
1628
  kind: "shallow-copy",
1548
1629
  name: origin.name,
@@ -1591,7 +1672,7 @@ function getMutatedObject(mutation) {
1591
1672
  if (mutation.node.type === AST_NODE_TYPES.CallExpression) return target;
1592
1673
  return target.type === AST_NODE_TYPES.MemberExpression ? Extract.unwrap(target.object) : target;
1593
1674
  }
1594
- function inferDirectMutations(context, mutations) {
1675
+ function inferDirectMutations(context, mutations, components) {
1595
1676
  const directMutations = [];
1596
1677
  for (const mutation of mutations) {
1597
1678
  if (mutation.kind !== "value") continue;
@@ -1599,7 +1680,7 @@ function inferDirectMutations(context, mutations) {
1599
1680
  if (isRefMutation(context, mutation)) continue;
1600
1681
  const variable = findVariable(context.sourceCode.getScope(mutation.root), mutation.root);
1601
1682
  if (variable == null) continue;
1602
- const origin = classifyFrozenOrigin(context, variable);
1683
+ const origin = classifyFrozenOrigin(context, variable, components);
1603
1684
  if (origin == null) continue;
1604
1685
  switch (origin.kind) {
1605
1686
  case "props":
@@ -1648,17 +1729,18 @@ var immutability_default = createRule({
1648
1729
  });
1649
1730
  function create$48(context) {
1650
1731
  const hooks = core.getHookCollector(context);
1651
- const collector = createImmutabilityCollector();
1652
- return merge(hooks.visitor, collector.visitor, { "Program:exit"(program) {
1653
- for (const hook of hooks.api.getAllHooks(program)) for (const expression of hook.rets) if (expression != null) collector.facts.sinks.push({
1732
+ const comps = core.getFunctionComponentCollector(context);
1733
+ const immut = createImmutabilityCollector();
1734
+ return merge(hooks.visitor, comps.visitor, immut.visitor, { "Program:exit"(program) {
1735
+ for (const hook of hooks.api.getAllHooks(program)) for (const expression of hook.rets) if (expression != null) immut.facts.sinks.push({
1654
1736
  kind: "hook-return",
1655
1737
  expression
1656
1738
  });
1657
1739
  const reportedMutations = /* @__PURE__ */ new Set();
1658
- const mutableFunctions = inferMutableFunctions(context, collector.facts.mutations);
1740
+ const mutableFunctions = inferMutableFunctions(context, immut.facts.mutations);
1659
1741
  if (mutableFunctions.size > 0) {
1660
1742
  const reportedSinks = /* @__PURE__ */ new Set();
1661
- for (const sink of collector.facts.sinks) {
1743
+ for (const sink of immut.facts.sinks) {
1662
1744
  const expression = sink.expression;
1663
1745
  if (reportedSinks.has(expression)) continue;
1664
1746
  const fn = resolveToFunctionNode(context, expression);
@@ -1679,7 +1761,8 @@ function create$48(context) {
1679
1761
  });
1680
1762
  }
1681
1763
  }
1682
- for (const mutation of inferDirectMutations(context, collector.facts.mutations)) {
1764
+ const funcs = comps.api.getAllComponents(program).map((comp) => comp.node);
1765
+ for (const mutation of inferDirectMutations(context, immut.facts.mutations, funcs)) {
1683
1766
  if (reportedMutations.has(mutation.node)) continue;
1684
1767
  reportedMutations.add(mutation.node);
1685
1768
  context.report({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-x",
3
- "version": "5.19.0",
3
+ "version": "5.19.1",
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",
@@ -36,17 +36,17 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@eslint-react/ast": "5.19.0",
40
- "@eslint-react/core": "5.19.0",
41
- "@eslint-react/eslint": "5.19.0",
42
- "@eslint-react/jsx": "5.19.0",
43
- "@eslint-react/shared": "5.19.0",
44
- "@eslint-react/var": "5.19.0",
45
- "@typescript-eslint/scope-manager": "^8.69.0",
46
- "@typescript-eslint/type-utils": "^8.69.0",
47
- "@typescript-eslint/types": "^8.69.0",
48
- "@typescript-eslint/typescript-estree": "^8.69.0",
49
- "@typescript-eslint/utils": "^8.69.0",
39
+ "@eslint-react/ast": "5.19.1",
40
+ "@eslint-react/core": "5.19.1",
41
+ "@eslint-react/eslint": "5.19.1",
42
+ "@eslint-react/jsx": "5.19.1",
43
+ "@eslint-react/shared": "5.19.1",
44
+ "@eslint-react/var": "5.19.1",
45
+ "@typescript-eslint/scope-manager": "^8.70.0",
46
+ "@typescript-eslint/type-utils": "^8.70.0",
47
+ "@typescript-eslint/types": "^8.70.0",
48
+ "@typescript-eslint/typescript-estree": "^8.70.0",
49
+ "@typescript-eslint/utils": "^8.70.0",
50
50
  "compare-versions": "^6.1.1",
51
51
  "string-ts": "^2.3.1",
52
52
  "ts-api-utils": "^2.5.0",
@@ -55,12 +55,12 @@
55
55
  "devDependencies": {
56
56
  "@local/configs": "0.0.0",
57
57
  "@local/eff": "0.0.0",
58
- "@types/react": "^19.2.18",
59
- "@types/react-dom": "^19.2.7",
58
+ "@types/react": "^19.3.0",
59
+ "@types/react-dom": "^19.3.0",
60
60
  "dedent": "^1.7.2",
61
61
  "eslint": "^10.10.0",
62
- "react": "^19.2.8",
63
- "react-dom": "^19.2.8",
62
+ "react": "^19.3.0",
63
+ "react-dom": "^19.3.0",
64
64
  "tsdown": "^0.23.0",
65
65
  "tsl": "^1.0.30",
66
66
  "tsl-dx": "^0.13.3",