eslint-plugin-react-x 5.23.5 → 5.24.0-beta.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 +754 -614
  2. package/package.json +8 -7
package/dist/index.js CHANGED
@@ -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.23.5";
148
+ var version = "5.24.0-beta.1";
149
149
 
150
150
  //#endregion
151
151
  //#region src/utils/create-rule.ts
@@ -162,8 +162,8 @@ var error_boundaries_default = createRule({
162
162
  type: "problem",
163
163
  docs: { description: "Validates usage of Error Boundaries instead of try/catch for errors in child components." },
164
164
  messages: {
165
- tryCatchWithJsx: "Use an Error Boundary to catch errors in child components. Try/catch can't catch errors during React's rendering process.",
166
- tryCatchWithUse: "Use an Error Boundary instead of try/catch around the 'use' hook. The 'use' hook suspends the component, and its errors can only be caught by Error Boundaries."
165
+ "try-catch-with-jsx": "Use an Error Boundary to catch errors in child components. Try/catch can't catch errors during React's rendering process.",
166
+ "try-catch-with-use": "Use an Error Boundary instead of try/catch around the 'use' hook. The 'use' hook suspends the component, and its errors can only be caught by Error Boundaries."
167
167
  },
168
168
  schema: []
169
169
  },
@@ -192,7 +192,7 @@ function create$50(context) {
192
192
  const func = Traverse.findParent(stmt, (n) => funcs.some((f) => f.node === n));
193
193
  if (stmt != null && func != null && !reported.has(stmt)) {
194
194
  context.report({
195
- messageId: "tryCatchWithUse",
195
+ messageId: "try-catch-with-use",
196
196
  node: stmt
197
197
  });
198
198
  reported.add(stmt);
@@ -204,7 +204,7 @@ function create$50(context) {
204
204
  const stmt = Traverse.findEnclosingTryBlock(ret);
205
205
  if (stmt != null && !reported.has(stmt)) {
206
206
  context.report({
207
- messageId: "tryCatchWithJsx",
207
+ messageId: "try-catch-with-jsx",
208
208
  node: stmt
209
209
  });
210
210
  reported.add(stmt);
@@ -1155,7 +1155,7 @@ function resolveToFunction(context, node, seen = /* @__PURE__ */ new Set()) {
1155
1155
 
1156
1156
  //#endregion
1157
1157
  //#region src/rules/globals/collect.ts
1158
- function createFactCollector$3() {
1158
+ function createFactCollector$6() {
1159
1159
  const facts = {
1160
1160
  callEdges: [],
1161
1161
  methodCalls: [],
@@ -1343,9 +1343,9 @@ var globals_default = createRule({
1343
1343
  type: "problem",
1344
1344
  docs: { description: "Validates against assignment/mutation of globals during render, part of ensuring that side effects must run outside of render." },
1345
1345
  messages: {
1346
- mutatingGlobal: "Do not mutate '{{name}}' during render. Global variables exist outside React's control and make rendering impure.",
1347
- mutatingGlobalArrayMethod: "Do not call '{{method}}()' on '{{name}}' during render. Mutating global arrays during render makes rendering impure.",
1348
- mutatingGlobalProperty: "Do not mutate '{{name}}' during render. Modifying global objects during render makes rendering impure."
1346
+ "mutating-global": "Do not mutate '{{name}}' during render. Global variables exist outside React's control and make rendering impure.",
1347
+ "mutating-global-array-method": "Do not call '{{method}}()' on '{{name}}' during render. Mutating global arrays during render makes rendering impure.",
1348
+ "mutating-global-property": "Do not mutate '{{name}}' during render. Modifying global objects during render makes rendering impure."
1349
1349
  },
1350
1350
  schema: []
1351
1351
  },
@@ -1356,7 +1356,7 @@ var globals_default = createRule({
1356
1356
  function create$49(context) {
1357
1357
  const hooks = core.getHookCollector(context);
1358
1358
  const comps = core.getFunctionComponentCollector(context);
1359
- const facts = createFactCollector$3();
1359
+ const facts = createFactCollector$6();
1360
1360
  return merge(hooks.visitor, comps.visitor, facts.visitor, { "Program:exit"(program) {
1361
1361
  const renderFunctions = [...comps.api.getAllComponents(program), ...hooks.api.getAllHooks(program)].map(({ node }) => node);
1362
1362
  const directEffects = inferGlobalMutations(context, facts.facts);
@@ -1368,7 +1368,7 @@ function create$49(context) {
1368
1368
  };
1369
1369
  context.report({
1370
1370
  data,
1371
- messageId: match(effect).with({ kind: "global" }, () => "mutatingGlobal").with({ kind: "method" }, () => "mutatingGlobalArrayMethod").with({ kind: "property" }, () => "mutatingGlobalProperty").exhaustive(),
1371
+ messageId: match(effect).with({ kind: "global" }, () => "mutating-global").with({ kind: "method" }, () => "mutating-global-array-method").with({ kind: "property" }, () => "mutating-global-property").exhaustive(),
1372
1372
  node: effect.node
1373
1373
  });
1374
1374
  }
@@ -1451,9 +1451,9 @@ function isRefLikeName$1(name) {
1451
1451
  return name === "ref" || name.endsWith("Ref");
1452
1452
  }
1453
1453
  function hasRefLikeNameInChain(node) {
1454
- const unwrapped = Extract.unwrap(node);
1455
- if (!Check.isExpression(unwrapped)) return false;
1456
- return Extract.getMemberChain(unwrapped).some((member) => Check.isIdentifier(member) && isRefLikeName$1(member.name));
1454
+ const expr = Extract.unwrap(node);
1455
+ if (!Check.isExpression(expr)) return false;
1456
+ return Extract.getMemberChain(expr).some((member) => Check.isIdentifier(member) && isRefLikeName$1(member.name));
1457
1457
  }
1458
1458
  function isInitializedFromCall(context, node, isCall) {
1459
1459
  const root = Extract.getMemberChain(node).at(0);
@@ -1482,7 +1482,7 @@ function isRefLikeChain(context, node) {
1482
1482
 
1483
1483
  //#endregion
1484
1484
  //#region src/rules/immutability/collect.ts
1485
- function createFactCollector$2() {
1485
+ function createFactCollector$5() {
1486
1486
  const facts = {
1487
1487
  mutations: [],
1488
1488
  sinks: []
@@ -1746,9 +1746,9 @@ var immutability_default = createRule({
1746
1746
  type: "problem",
1747
1747
  docs: { description: "Validates against mutating props, state, and other immutable values, including through functions passed into frozen contexts such as JSX props, hook arguments, and hook return values." },
1748
1748
  messages: {
1749
- default: "This function may (indirectly) reassign or modify '{{name}}' after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.",
1750
- direct: "Do not mutate '{{name}}' directly. {{detail}}",
1751
- mutates: "This modifies '{{name}}'."
1749
+ "direct-mutation": "Do not mutate '{{name}}' directly. {{detail}}",
1750
+ "indirect-mutation": "This function may (indirectly) reassign or modify '{{name}}' after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.",
1751
+ "mutation-site": "This modifies '{{name}}'."
1752
1752
  },
1753
1753
  schema: []
1754
1754
  },
@@ -1759,7 +1759,7 @@ var immutability_default = createRule({
1759
1759
  function create$48(context) {
1760
1760
  const hooks = core.getHookCollector(context);
1761
1761
  const comps = core.getFunctionComponentCollector(context);
1762
- const facts = createFactCollector$2();
1762
+ const facts = createFactCollector$5();
1763
1763
  return merge(hooks.visitor, comps.visitor, facts.visitor, { "Program:exit"(program) {
1764
1764
  for (const hook of hooks.api.getAllHooks(program)) for (const expression of hook.rets) if (expression != null) facts.facts.sinks.push({
1765
1765
  kind: "hook-return",
@@ -1780,12 +1780,12 @@ function create$48(context) {
1780
1780
  reportedMutations.add(mutation.node);
1781
1781
  context.report({
1782
1782
  data: { name: mutation.name },
1783
- messageId: "default",
1783
+ messageId: "indirect-mutation",
1784
1784
  node: expression
1785
1785
  });
1786
1786
  context.report({
1787
1787
  data: { name: mutation.name },
1788
- messageId: "mutates",
1788
+ messageId: "mutation-site",
1789
1789
  node: mutation.node
1790
1790
  });
1791
1791
  }
@@ -1799,7 +1799,7 @@ function create$48(context) {
1799
1799
  name: mutation.name,
1800
1800
  detail: mutation.detail
1801
1801
  },
1802
- messageId: "direct",
1802
+ messageId: "direct-mutation",
1803
1803
  node: mutation.node
1804
1804
  });
1805
1805
  }
@@ -3026,27 +3026,6 @@ const constNull = constant(null);
3026
3026
  */
3027
3027
  const constUndefined = constant(void 0);
3028
3028
  /**
3029
- * Returns no meaningful value when called.
3030
- *
3031
- * **When to use**
3032
- *
3033
- * Use when you need a thunk that is called only for its effect and has no
3034
- * meaningful return value.
3035
- *
3036
- * **Example** (Returning void from a thunk)
3037
- *
3038
- * ```ts
3039
- * import { Function } from "effect"
3040
- * import * as assert from "node:assert"
3041
- *
3042
- * assert.deepStrictEqual(Function.constVoid(), undefined)
3043
- * ```
3044
- *
3045
- * @category constants
3046
- * @since 2.0.0
3047
- */
3048
- const constVoid = constUndefined;
3049
- /**
3050
3029
  * Composes two functions, `ab` and `bc` into a single function that takes in an argument `a` of type `A` and returns a result of type `C`.
3051
3030
  * The result is obtained by first applying the `ab` function to `a` and then applying the `bc` function to the result of `ab`.
3052
3031
  *
@@ -3412,10 +3391,10 @@ const INDEX_PARAM_POSITIONS = /* @__PURE__ */ new Map([
3412
3391
  */
3413
3392
  function getNestedReturnStatements$1(node) {
3414
3393
  const statements = [];
3415
- const boundaryNode = Check.isFunction(node) ? node : Traverse.findParent(node, Check.isFunction);
3394
+ const boundary = Check.isFunction(node) ? node : Traverse.findParent(node, Check.isFunction);
3416
3395
  simpleTraverse(node, { enter(node) {
3417
3396
  if (node.type !== AST_NODE_TYPES.ReturnStatement) return;
3418
- if (Traverse.findParent(node, Check.isFunction, (n) => n === boundaryNode) != null) return;
3397
+ if (Traverse.findParent(node, Check.isFunction, (n) => n === boundary) != null) return;
3419
3398
  statements.push(node);
3420
3399
  } });
3421
3400
  return statements;
@@ -3430,7 +3409,7 @@ var no_missing_key_default = createRule({
3430
3409
  docs: { description: "Disallows missing 'key' on items in list rendering." },
3431
3410
  messages: {
3432
3411
  default: "Missing 'key' for element when rendering list.",
3433
- unexpectedFragmentSyntax: "Use fragment component instead of '<>' because it does not support `key`."
3412
+ "unexpected-fragment-syntax": "Use fragment component instead of '<>' because it does not support `key`."
3434
3413
  },
3435
3414
  schema: []
3436
3415
  },
@@ -3468,7 +3447,7 @@ function create$24(context) {
3468
3447
  node
3469
3448
  }];
3470
3449
  case AST_NODE_TYPES.JSXFragment: return [{
3471
- messageId: "unexpectedFragmentSyntax",
3450
+ messageId: "unexpected-fragment-syntax",
3472
3451
  node
3473
3452
  }];
3474
3453
  case AST_NODE_TYPES.ConditionalExpression: return [...visitItemExpression(node.consequent), ...visitItemExpression(node.alternate)];
@@ -3554,8 +3533,8 @@ var no_misused_capture_owner_stack_default = createRule({
3554
3533
  type: "problem",
3555
3534
  docs: { description: "Prevents incorrect usage of 'captureOwnerStack'." },
3556
3535
  messages: {
3557
- missingDevelopmentOnlyCheck: `Don't call 'captureOwnerStack' directly. Use 'if (process.env.NODE_ENV !== "production") {...}' to conditionally access it.`,
3558
- useNamespaceImport: "Don't use named imports of 'captureOwnerStack' in files that are bundled for development and production. Use a namespace import instead."
3536
+ "missing-development-only-check": `Don't call 'captureOwnerStack' directly. Use 'if (process.env.NODE_ENV !== "production") {...}' to conditionally access it.`,
3537
+ "use-namespace-import": "Don't use named imports of 'captureOwnerStack' in files that are bundled for development and production. Use a namespace import instead."
3559
3538
  },
3560
3539
  schema: []
3561
3540
  },
@@ -3570,7 +3549,7 @@ function create$23(context) {
3570
3549
  CallExpression(node) {
3571
3550
  if (!core.isCaptureOwnerStackCall(context, node)) return;
3572
3551
  if (Traverse.findParent(node, (n) => isDevelopmentOnlyCheck(context, n)) == null) context.report({
3573
- messageId: "missingDevelopmentOnlyCheck",
3552
+ messageId: "missing-development-only-check",
3574
3553
  node
3575
3554
  });
3576
3555
  },
@@ -3580,7 +3559,7 @@ function create$23(context) {
3580
3559
  if (specifier.type !== AST_NODE_TYPES.ImportSpecifier) continue;
3581
3560
  if (!Check.isIdentifier(specifier.imported, "captureOwnerStack")) continue;
3582
3561
  context.report({
3583
- messageId: "useNamespaceImport",
3562
+ messageId: "use-namespace-import",
3584
3563
  node: specifier
3585
3564
  });
3586
3565
  }
@@ -4082,7 +4061,7 @@ var no_unstable_context_value_default = createRule({
4082
4061
  meta: {
4083
4062
  type: "problem",
4084
4063
  docs: { description: "Prevents non-stable values (i.e., object literals) from being used as a value for 'Context.Provider'." },
4085
- messages: { unstableContextValue: "A/an '{{kind}}' passed as the value prop to the context provider should not be constructed. It will change on every render. {{suggestion}}" },
4064
+ messages: { "unstable-context-value": "A/an '{{kind}}' passed as the value prop to the context provider should not be constructed. It will change on every render. {{suggestion}}" },
4086
4065
  schema: []
4087
4066
  },
4088
4067
  name: RULE_NAME$13,
@@ -4125,7 +4104,7 @@ function create$13(context) {
4125
4104
  kind: getHumanReadableKind$1(constructionNode),
4126
4105
  suggestion
4127
4106
  },
4128
- messageId: "unstableContextValue",
4107
+ messageId: "unstable-context-value",
4129
4108
  node: constructionNode
4130
4109
  });
4131
4110
  }
@@ -5289,7 +5268,7 @@ function isAfterTerminatingNonNullGuard(node, getNullBranch) {
5289
5268
  * reachable during render and are not collected. All inference is deferred to
5290
5269
  * `Program:exit`.
5291
5270
  */
5292
- function createFactCollector$1() {
5271
+ function createFactCollector$4() {
5293
5272
  const facts = {
5294
5273
  bindings: [],
5295
5274
  callEdges: [],
@@ -5722,10 +5701,10 @@ var refs_default = createRule({
5722
5701
  type: "problem",
5723
5702
  docs: { description: "Validates correct usage of refs by checking that 'ref.current' is not read or written during render." },
5724
5703
  messages: {
5725
- duplicateRefInit: "Ref is initialized more than once during render. Only a single 'if (ref.current == null)' initialization is allowed; move any additional initialization into an effect or event handler.",
5726
- readDuringRender: "Cannot access refs during render",
5727
- refPassedToFunction: "Passing a ref to a function may read its value during render",
5728
- writeDuringRender: "Cannot update ref during render"
5704
+ "duplicate-ref-init": "Ref is initialized more than once during render. Only a single 'if (ref.current == null)' initialization is allowed; move any additional initialization into an effect or event handler.",
5705
+ "read-during-render": "Cannot access refs during render",
5706
+ "ref-passed-to-function": "Passing a ref to a function may read its value during render",
5707
+ "write-during-render": "Cannot update ref during render"
5729
5708
  },
5730
5709
  schema: []
5731
5710
  },
@@ -5736,7 +5715,7 @@ var refs_default = createRule({
5736
5715
  function create$6(context) {
5737
5716
  const hooks = core.getHookCollector(context);
5738
5717
  const comps = core.getFunctionComponentCollector(context);
5739
- const facts = createFactCollector$1();
5718
+ const facts = createFactCollector$4();
5740
5719
  return merge(hooks.visitor, comps.visitor, facts.visitor, { "Program:exit"(program) {
5741
5720
  const boundaries = /* @__PURE__ */ new Set([...comps.api.getAllComponents(program).map((component) => component.node), ...hooks.api.getAllHooks(program).map((hook) => hook.node)]);
5742
5721
  const resolver = createBindingResolver(context, facts.facts);
@@ -5744,7 +5723,7 @@ function create$6(context) {
5744
5723
  const reachability = collectReachableFunctions(boundaries, callGraph);
5745
5724
  const violations = [...inferRefViolations(facts.facts.refAccesses, boundaries, reachability, resolver), ...inferRefPassViolations(facts.facts.passSites, boundaries, reachability, resolver)];
5746
5725
  for (const violation of violations) context.report({
5747
- messageId: match(violation).with({ kind: "duplicate-init" }, () => "duplicateRefInit").with({ kind: "pass" }, () => "refPassedToFunction").with({ kind: "read" }, () => "readDuringRender").with({ kind: "write" }, () => "writeDuringRender").exhaustive(),
5726
+ messageId: match(violation).with({ kind: "duplicate-init" }, () => "duplicate-ref-init").with({ kind: "pass" }, () => "ref-passed-to-function").with({ kind: "read" }, () => "read-during-render").with({ kind: "write" }, () => "write-during-render").exhaustive(),
5748
5727
  node: violation.node
5749
5728
  });
5750
5729
  } });
@@ -7885,7 +7864,43 @@ function last(array) {
7885
7864
  }
7886
7865
 
7887
7866
  //#endregion
7888
- //#region src/rules/set-state-in-effect/lib.ts
7867
+ //#region src/rules/set-state-in-effect/helpers.ts
7868
+ const IDENTIFIER_VISITOR_KEYS = {
7869
+ [AST_NODE_TYPES.ArrayExpression]: ["elements"],
7870
+ [AST_NODE_TYPES.ArrayPattern]: ["elements"],
7871
+ [AST_NODE_TYPES.AssignmentExpression]: ["left", "right"],
7872
+ [AST_NODE_TYPES.AssignmentPattern]: ["left", "right"],
7873
+ [AST_NODE_TYPES.AwaitExpression]: ["argument"],
7874
+ [AST_NODE_TYPES.BinaryExpression]: ["left", "right"],
7875
+ [AST_NODE_TYPES.CallExpression]: ["callee", "arguments"],
7876
+ [AST_NODE_TYPES.ChainExpression]: ["expression"],
7877
+ [AST_NODE_TYPES.ConditionalExpression]: [
7878
+ "test",
7879
+ "consequent",
7880
+ "alternate"
7881
+ ],
7882
+ [AST_NODE_TYPES.ForInStatement]: ["left", "right"],
7883
+ [AST_NODE_TYPES.ForOfStatement]: ["left", "right"],
7884
+ [AST_NODE_TYPES.ImportExpression]: ["source"],
7885
+ [AST_NODE_TYPES.LogicalExpression]: ["left", "right"],
7886
+ [AST_NODE_TYPES.MemberExpression]: ["object", "property"],
7887
+ [AST_NODE_TYPES.NewExpression]: ["callee", "arguments"],
7888
+ [AST_NODE_TYPES.ObjectExpression]: ["properties"],
7889
+ [AST_NODE_TYPES.ObjectPattern]: ["properties"],
7890
+ [AST_NODE_TYPES.Property]: ["value"],
7891
+ [AST_NODE_TYPES.SequenceExpression]: ["expressions"],
7892
+ [AST_NODE_TYPES.SpreadElement]: ["argument"],
7893
+ [AST_NODE_TYPES.TaggedTemplateExpression]: ["tag", "quasi"],
7894
+ [AST_NODE_TYPES.TemplateLiteral]: ["expressions"],
7895
+ [AST_NODE_TYPES.TSAsExpression]: ["expression"],
7896
+ [AST_NODE_TYPES.TSInstantiationExpression]: ["expression"],
7897
+ [AST_NODE_TYPES.TSNonNullExpression]: ["expression"],
7898
+ [AST_NODE_TYPES.TSSatisfiesExpression]: ["expression"],
7899
+ [AST_NODE_TYPES.TSTypeAssertion]: ["expression"],
7900
+ [AST_NODE_TYPES.UnaryExpression]: ["argument"],
7901
+ [AST_NODE_TYPES.UpdateExpression]: ["argument"],
7902
+ [AST_NODE_TYPES.YieldExpression]: ["argument"]
7903
+ };
7889
7904
  /**
7890
7905
  * Get all nested identifiers in a expression like node
7891
7906
  * @param node The node to get the nested identifiers from
@@ -7893,79 +7908,14 @@ function last(array) {
7893
7908
  */
7894
7909
  function getNestedIdentifiers(node) {
7895
7910
  const identifiers = [];
7896
- if (Check.isIdentifier(node)) identifiers.push(node);
7897
- if ("arguments" in node) {
7898
- const chunk = node.arguments.flatMap(getNestedIdentifiers);
7899
- identifiers.push(...chunk);
7900
- }
7901
- if ("elements" in node) {
7902
- const chunk = node.elements.filter((x) => x != null).flatMap(getNestedIdentifiers);
7903
- identifiers.push(...chunk);
7904
- }
7905
- if ("properties" in node) {
7906
- const chunk = node.properties.flatMap(getNestedIdentifiers);
7907
- identifiers.push(...chunk);
7908
- }
7909
- if ("expressions" in node) {
7910
- const chunk = node.expressions.flatMap(getNestedIdentifiers);
7911
- identifiers.push(...chunk);
7912
- }
7913
- if ("left" in node) {
7914
- const chunk = getNestedIdentifiers(node.left);
7915
- identifiers.push(...chunk);
7916
- }
7917
- if ("right" in node) {
7918
- const chunk = getNestedIdentifiers(node.right);
7919
- identifiers.push(...chunk);
7920
- }
7921
- if (node.type === AST_NODE_TYPES.Property) {
7922
- const chunk = getNestedIdentifiers(node.value);
7923
- identifiers.push(...chunk);
7924
- }
7925
- if (node.type === AST_NODE_TYPES.SpreadElement) {
7926
- const chunk = getNestedIdentifiers(node.argument);
7927
- identifiers.push(...chunk);
7928
- }
7929
- if (node.type === AST_NODE_TYPES.MemberExpression) {
7930
- identifiers.push(...getNestedIdentifiers(node.object));
7931
- if (node.computed) identifiers.push(...getNestedIdentifiers(node.property));
7932
- }
7933
- if (node.type === AST_NODE_TYPES.UnaryExpression) {
7934
- const chunk = getNestedIdentifiers(node.argument);
7935
- identifiers.push(...chunk);
7936
- }
7937
- if (node.type === AST_NODE_TYPES.ChainExpression) {
7938
- const chunk = getNestedIdentifiers(node.expression);
7939
- identifiers.push(...chunk);
7940
- }
7941
- if (node.type === AST_NODE_TYPES.TSNonNullExpression) {
7942
- const chunk = getNestedIdentifiers(node.expression);
7943
- identifiers.push(...chunk);
7944
- }
7945
- if (node.type === AST_NODE_TYPES.TSAsExpression) {
7946
- const chunk = getNestedIdentifiers(node.expression);
7947
- identifiers.push(...chunk);
7948
- }
7949
- if (node.type === AST_NODE_TYPES.TSSatisfiesExpression) {
7950
- const chunk = getNestedIdentifiers(node.expression);
7951
- identifiers.push(...chunk);
7952
- }
7953
- if (node.type === AST_NODE_TYPES.ConditionalExpression) {
7954
- identifiers.push(...getNestedIdentifiers(node.test));
7955
- identifiers.push(...getNestedIdentifiers(node.consequent));
7956
- identifiers.push(...getNestedIdentifiers(node.alternate));
7957
- }
7958
- if (node.type === AST_NODE_TYPES.AwaitExpression) identifiers.push(...getNestedIdentifiers(node.argument));
7959
- if (node.type === AST_NODE_TYPES.YieldExpression && node.argument != null) identifiers.push(...getNestedIdentifiers(node.argument));
7960
- if (node.type === AST_NODE_TYPES.UpdateExpression) identifiers.push(...getNestedIdentifiers(node.argument));
7961
- if (node.type === AST_NODE_TYPES.CallExpression || node.type === AST_NODE_TYPES.NewExpression) identifiers.push(...getNestedIdentifiers(node.callee));
7962
- if (node.type === AST_NODE_TYPES.TaggedTemplateExpression) {
7963
- identifiers.push(...getNestedIdentifiers(node.tag));
7964
- identifiers.push(...getNestedIdentifiers(node.quasi));
7965
- }
7966
- if (node.type === AST_NODE_TYPES.ImportExpression) identifiers.push(...getNestedIdentifiers(node.source));
7967
- if (node.type === AST_NODE_TYPES.TSTypeAssertion) identifiers.push(...getNestedIdentifiers(node.expression));
7968
- if (node.type === AST_NODE_TYPES.TSInstantiationExpression) identifiers.push(...getNestedIdentifiers(node.expression));
7911
+ simpleTraverse(node, {
7912
+ enter(node, parent) {
7913
+ if (node.type !== AST_NODE_TYPES.Identifier) return;
7914
+ if (parent?.type === AST_NODE_TYPES.MemberExpression && !parent.computed && parent.property === node) return;
7915
+ identifiers.push(node);
7916
+ },
7917
+ visitorKeys: IDENTIFIER_VISITOR_KEYS
7918
+ });
7969
7919
  return identifiers;
7970
7920
  }
7971
7921
  function isHookDecl(node) {
@@ -7976,6 +7926,168 @@ function isHookDecl(node) {
7976
7926
  const name = Extract.getCalleeName(init);
7977
7927
  return name != null && core.isHookName(name);
7978
7928
  }
7929
+ function isThenCall(node) {
7930
+ return Extract.unwrap(node.callee).type === AST_NODE_TYPES.MemberExpression && Extract.getCalleeName(node) === "then";
7931
+ }
7932
+ function isTerminatingStatement(node) {
7933
+ if (node.type === AST_NODE_TYPES.ReturnStatement || node.type === AST_NODE_TYPES.ThrowStatement) return true;
7934
+ if (node.type === AST_NODE_TYPES.BlockStatement) return node.body.some(isTerminatingStatement);
7935
+ return false;
7936
+ }
7937
+ /**
7938
+ * Get the display name of a setState call reference for reporting.
7939
+ * @param node The setState call node (CallExpression or Identifier)
7940
+ * @param getText The source text getter
7941
+ * @returns The fully qualified name of the call
7942
+ */
7943
+ function getCallName(node, getText) {
7944
+ if (node.type === AST_NODE_TYPES.CallExpression) return Extract.getFullyQualifiedName(node.callee, getText);
7945
+ return Extract.getFullyQualifiedName(node, getText);
7946
+ }
7947
+ /**
7948
+ * Get the actual CallExpression node from a setState call reference.
7949
+ * When the node is an Identifier that is the callee of a CallExpression,
7950
+ * returns the parent CallExpression; otherwise returns the node itself.
7951
+ * @param node The setState call node (CallExpression or Identifier)
7952
+ * @returns The actual CallExpression node
7953
+ */
7954
+ function getSetStateCallExpression(node) {
7955
+ return Check.isIdentifier(node) && node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee === node ? node.parent : node;
7956
+ }
7957
+
7958
+ //#endregion
7959
+ //#region src/rules/set-state-in-effect/collect.ts
7960
+ function createFactCollector$3(additionalStateHooks, additionalEffectHooks) {
7961
+ const facts = {
7962
+ calls: [],
7963
+ setStateReferences: [],
7964
+ setupIdentifiers: []
7965
+ };
7966
+ const functionEntries = [];
7967
+ const setupFnRef = { current: null };
7968
+ function isUseStateCall(node) {
7969
+ return core.isUseStateLikeCall(node, additionalStateHooks);
7970
+ }
7971
+ function isUseEffectCall(node) {
7972
+ return core.isUseEffectLikeCall(node, additionalEffectHooks);
7973
+ }
7974
+ function isUseEffectSetupCallback(node) {
7975
+ return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseEffectCall(node.parent);
7976
+ }
7977
+ function getFunctionKind(node) {
7978
+ const parent = Traverse.findParent(node, not(Check.isTypeExpression)) ?? node.parent;
7979
+ switch (true) {
7980
+ case node.async:
7981
+ case parent.type === AST_NODE_TYPES.CallExpression && isThenCall(parent): return "deferred";
7982
+ case node.type !== AST_NODE_TYPES.FunctionDeclaration && parent.type === AST_NODE_TYPES.CallExpression && parent.callee === node: return "immediate";
7983
+ case isUseEffectSetupCallback(node): return "setup";
7984
+ default: return "other";
7985
+ }
7986
+ }
7987
+ return {
7988
+ facts,
7989
+ visitor: {
7990
+ ":function"(node) {
7991
+ const kind = getFunctionKind(node);
7992
+ functionEntries.push({
7993
+ kind,
7994
+ node
7995
+ });
7996
+ if (kind === "setup") setupFnRef.current = node;
7997
+ },
7998
+ ":function:exit"(node) {
7999
+ const { kind } = functionEntries.at(-1) ?? {};
8000
+ if (kind === "setup" && setupFnRef.current === node) setupFnRef.current = null;
8001
+ functionEntries.pop();
8002
+ },
8003
+ CallExpression(node) {
8004
+ const entry = functionEntries.at(-1);
8005
+ if (entry == null || entry.node.async) return;
8006
+ if (isUseStateCall(node)) return;
8007
+ if (isUseEffectCall(node)) {
8008
+ if (!Check.isFunction(node.arguments.at(0))) facts.setupIdentifiers.push(...getNestedIdentifiers(node));
8009
+ return;
8010
+ }
8011
+ facts.calls.push({
8012
+ enclosingFunction: entry.node,
8013
+ enclosingFunctionKind: entry.kind,
8014
+ node,
8015
+ setupFunction: setupFnRef.current
8016
+ });
8017
+ },
8018
+ Identifier(node) {
8019
+ if (node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee === node) return;
8020
+ switch (node.parent.type) {
8021
+ case AST_NODE_TYPES.ArrowFunctionExpression:
8022
+ facts.setStateReferences.push({
8023
+ kind: "callback-body",
8024
+ node
8025
+ });
8026
+ return;
8027
+ case AST_NODE_TYPES.CallExpression:
8028
+ if (node === node.parent.arguments.at(0)) facts.setStateReferences.push({
8029
+ kind: "call-argument",
8030
+ node
8031
+ });
8032
+ return;
8033
+ }
8034
+ }
8035
+ }
8036
+ };
8037
+ }
8038
+
8039
+ //#endregion
8040
+ //#region src/rules/set-state-in-effect/origins.ts
8041
+ /**
8042
+ * Create a resolver that answers whether an identifier or call originates from
8043
+ * a `useState`-like hook's setter function (the element at index 1 of the
8044
+ * returned tuple).
8045
+ * @param context The rule context
8046
+ * @returns The setState provenance resolver
8047
+ */
8048
+ function createSetStateResolver$1(context) {
8049
+ const { additionalStateHooks } = getSettingsFromContext(context);
8050
+ function isUseStateCall(node) {
8051
+ return core.isUseStateLikeCall(node, additionalStateHooks);
8052
+ }
8053
+ function isSetStateId(id, at) {
8054
+ const initNode = resolveOrigin(context, id);
8055
+ if (initNode == null) return false;
8056
+ if (initNode.type !== AST_NODE_TYPES.CallExpression) return false;
8057
+ if (!isUseStateCall(initNode)) return false;
8058
+ const variableNodeParent = initNode.parent;
8059
+ if (!("id" in variableNodeParent) || variableNodeParent.id?.type !== AST_NODE_TYPES.ArrayPattern) return true;
8060
+ return variableNodeParent.id.elements.findIndex((e) => e != null && Check.isIdentifier(e, id.name)) === at;
8061
+ }
8062
+ function isSetStateCall(node) {
8063
+ const callee = Extract.unwrap(node.callee);
8064
+ switch (callee.type) {
8065
+ case AST_NODE_TYPES.CallExpression: {
8066
+ const innerCallee = Extract.unwrap(callee.callee);
8067
+ if (innerCallee.type !== AST_NODE_TYPES.MemberExpression) return false;
8068
+ if (!("name" in innerCallee.object)) return false;
8069
+ const isAt = Extract.getCalleeName(callee) === "at";
8070
+ const [index] = callee.arguments;
8071
+ if (!isAt || index == null) return false;
8072
+ const indexScope = context.sourceCode.getScope(node);
8073
+ return getStaticValue(index, indexScope)?.value === 1 && isSetStateId(innerCallee.object);
8074
+ }
8075
+ case AST_NODE_TYPES.Identifier: return isSetStateId(callee, 1);
8076
+ case AST_NODE_TYPES.MemberExpression: {
8077
+ if (!("name" in callee.object)) return false;
8078
+ const property = callee.property;
8079
+ const propertyScope = context.sourceCode.getScope(node);
8080
+ return getStaticValue(property, propertyScope)?.value === 1 && isSetStateId(callee.object, 1);
8081
+ }
8082
+ default: return false;
8083
+ }
8084
+ }
8085
+ return {
8086
+ isSetStateCall,
8087
+ isSetStateId,
8088
+ isUseStateCall
8089
+ };
8090
+ }
7979
8091
  function isInitializedFromRef(context, name, initialScope, seen = /* @__PURE__ */ new Set()) {
7980
8092
  const { additionalRefHooks } = getSettingsFromContext(context);
7981
8093
  if (seen.has(name)) return false;
@@ -8011,19 +8123,35 @@ function isInitializedFromRef(context, name, initialScope, seen = /* @__PURE__ *
8011
8123
  return false;
8012
8124
  }
8013
8125
  /**
8014
- * Get the name that identifies the source of a `.current` member access.
8015
- * @param node The object of the member expression
8016
- * @returns The identifier name, the last property name of a member chain, or null
8126
+ * Check if the setState call is using a ref value, which is safe to use in an effect (ex: `setState(ref.current.scrollTop)`)
8127
+ * @param context The rule context
8128
+ * @param node The setState call argument to check
8129
+ * @returns `true` if the argument value is derived from a ref
8017
8130
  */
8018
- function getRefSourceName(node) {
8019
- switch (node.type) {
8020
- case AST_NODE_TYPES.Identifier: return node.name;
8021
- case AST_NODE_TYPES.MemberExpression: {
8022
- const property = Extract.unwrap(node.property);
8023
- return !node.computed && property.type === AST_NODE_TYPES.Identifier ? property.name : null;
8131
+ function isArgumentUsingRefValue(context, node) {
8132
+ const isUsingRefValue = (n) => {
8133
+ switch (n.type) {
8134
+ case AST_NODE_TYPES.Identifier: return isInitializedFromRef(context, n.name, context.sourceCode.getScope(n));
8135
+ case AST_NODE_TYPES.MemberExpression: return isUsingRefValue(n.object);
8136
+ case AST_NODE_TYPES.CallExpression: return isUsingRefValue(n.callee) || getNestedIdentifiers(n).some(isUsingRefValue);
8137
+ case AST_NODE_TYPES.BinaryExpression:
8138
+ case AST_NODE_TYPES.LogicalExpression: return isUsingRefValue(n.left) || isUsingRefValue(n.right);
8139
+ case AST_NODE_TYPES.UnaryExpression:
8140
+ case AST_NODE_TYPES.UpdateExpression: return isUsingRefValue(n.argument);
8141
+ case AST_NODE_TYPES.ConditionalExpression: return isUsingRefValue(n.consequent) || isUsingRefValue(n.alternate);
8142
+ case AST_NODE_TYPES.SequenceExpression: return n.expressions.some(isUsingRefValue);
8143
+ case AST_NODE_TYPES.TemplateLiteral: return n.expressions.some(isUsingRefValue);
8144
+ case AST_NODE_TYPES.TaggedTemplateExpression: return isUsingRefValue(n.tag) || isUsingRefValue(n.quasi);
8145
+ case AST_NODE_TYPES.ObjectExpression: return n.properties.some(isUsingRefValue);
8146
+ case AST_NODE_TYPES.Property: return isUsingRefValue(n.value);
8147
+ case AST_NODE_TYPES.ArrayExpression: return n.elements.some((element) => element != null && isUsingRefValue(element));
8148
+ case AST_NODE_TYPES.SpreadElement: return isUsingRefValue(n.argument);
8149
+ case AST_NODE_TYPES.AssignmentExpression: return isUsingRefValue(n.right);
8150
+ default: return false;
8024
8151
  }
8025
- default: return null;
8026
- }
8152
+ };
8153
+ if (isUsingRefValue(node)) return true;
8154
+ return Check.isFunction(node) && context.sourceCode.getScope(node.body).references.some((r) => isUsingRefValue(r.identifier));
8027
8155
  }
8028
8156
  /**
8029
8157
  * Check if a setState call is inside a conditional block whose test expression
@@ -8056,23 +8184,133 @@ function isRefGatedContext(context, node) {
8056
8184
  }
8057
8185
  return false;
8058
8186
  }
8059
- function isTerminatingStatement(node) {
8060
- if (node.type === AST_NODE_TYPES.ReturnStatement || node.type === AST_NODE_TYPES.ThrowStatement) return true;
8061
- if (node.type === AST_NODE_TYPES.BlockStatement) return node.body.some(isTerminatingStatement);
8062
- return false;
8063
- }
8064
8187
  function isRefInExpression(context, node) {
8065
8188
  return getNestedIdentifiers(node).some((id) => isInitializedFromRef(context, id.name, context.sourceCode.getScope(id)));
8066
8189
  }
8067
8190
  /**
8068
- * Get the actual CallExpression node from a setState call reference.
8069
- * When the node is an Identifier that is the callee of a CallExpression,
8070
- * returns the parent CallExpression; otherwise returns the node itself.
8071
- * @param node The setState call node (CallExpression or Identifier)
8072
- * @returns The actual CallExpression node
8191
+ * Get the name that identifies the source of a `.current` member access.
8192
+ * @param node The object of the member expression
8193
+ * @returns The identifier name, the last property name of a member chain, or null
8073
8194
  */
8074
- function getSetStateCallExpression(node) {
8075
- return Check.isIdentifier(node) && node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee === node ? node.parent : node;
8195
+ function getRefSourceName(node) {
8196
+ switch (node.type) {
8197
+ case AST_NODE_TYPES.Identifier: return node.name;
8198
+ case AST_NODE_TYPES.MemberExpression: {
8199
+ const property = Extract.unwrap(node.property);
8200
+ return !node.computed && property.type === AST_NODE_TYPES.Identifier ? property.name : null;
8201
+ }
8202
+ default: return null;
8203
+ }
8204
+ }
8205
+
8206
+ //#endregion
8207
+ //#region src/rules/set-state-in-effect/effects.ts
8208
+ /**
8209
+ * Infer the setState calls that run synchronously during an effect setup from
8210
+ * the collected facts: calls made directly in the setup body (or an IIFE within
8211
+ * it), and calls reached indirectly through local functions, hook callbacks, or
8212
+ * setup identifiers passed to the effect.
8213
+ * @param context The rule context
8214
+ * @param facts The collected facts
8215
+ * @returns The violations to report, in report order
8216
+ */
8217
+ function inferViolations$2(context, facts) {
8218
+ const { additionalEffectHooks } = getSettingsFromContext(context);
8219
+ const resolver = createSetStateResolver$1(context);
8220
+ const getText = (n) => context.sourceCode.getText(n);
8221
+ const isUseEffectCall = (node) => core.isUseEffectLikeCall(node, additionalEffectHooks);
8222
+ const setStateCallsByFn = /* @__PURE__ */ new Map();
8223
+ const setStateInHookCallbacks = /* @__PURE__ */ new Map();
8224
+ const setStateInEffectArg = /* @__PURE__ */ new Map();
8225
+ const setStateInEffectSetup = /* @__PURE__ */ new Map();
8226
+ const trackedFnCalls = [];
8227
+ const violations = [];
8228
+ for (const { enclosingFunction, enclosingFunctionKind, node, setupFunction } of facts.calls) {
8229
+ if (resolver.isUseStateCall(node) || isThenCall(node)) continue;
8230
+ if (!resolver.isSetStateCall(node)) {
8231
+ if (setupFunction != null && enclosingFunction === setupFunction) trackedFnCalls.push(node);
8232
+ continue;
8233
+ }
8234
+ if (enclosingFunctionKind === "deferred") continue;
8235
+ if (setupFunction != null && (enclosingFunction === setupFunction || enclosingFunctionKind === "immediate" && Traverse.findParent(enclosingFunction, Check.isFunction) === setupFunction)) {
8236
+ const args0 = node.arguments.at(0);
8237
+ if (args0 == null) continue;
8238
+ if (isArgumentUsingRefValue(context, args0)) continue;
8239
+ if (isRefGatedContext(context, node)) continue;
8240
+ violations.push({
8241
+ name: getText(Extract.unwrap(node.callee)),
8242
+ node
8243
+ });
8244
+ continue;
8245
+ }
8246
+ const init = Traverse.findParent(node, isHookDecl)?.init;
8247
+ if (init == null) getOrInsertComputed(setStateCallsByFn, enclosingFunction, () => []).push(node);
8248
+ else getOrInsertComputed(setStateInHookCallbacks, init, () => []).push(node);
8249
+ }
8250
+ for (const { kind, node } of facts.setStateReferences) {
8251
+ if (!resolver.isSetStateId(node, 1)) continue;
8252
+ if (kind === "callback-body") {
8253
+ const parent = node.parent.parent;
8254
+ if (parent?.type !== AST_NODE_TYPES.CallExpression) continue;
8255
+ if (!core.isUseMemoCall(context, parent)) continue;
8256
+ const init = Traverse.findParent(parent, isHookDecl)?.init;
8257
+ if (init != null) getOrInsertComputed(setStateInEffectArg, init, () => []).push(node);
8258
+ continue;
8259
+ }
8260
+ const parent = node.parent;
8261
+ if (parent.type !== AST_NODE_TYPES.CallExpression) continue;
8262
+ if (core.isUseCallbackCall(context, parent)) {
8263
+ const init = Traverse.findParent(parent, isHookDecl)?.init;
8264
+ if (init != null) getOrInsertComputed(setStateInEffectArg, init, () => []).push(node);
8265
+ continue;
8266
+ }
8267
+ if (isUseEffectCall(parent)) getOrInsertComputed(setStateInEffectSetup, parent, () => []).push(node);
8268
+ }
8269
+ const isExemptSetStateCall = (setStateCall) => {
8270
+ const callNode = getSetStateCallExpression(setStateCall);
8271
+ if (isRefGatedContext(context, callNode)) return true;
8272
+ if (callNode.type !== AST_NODE_TYPES.CallExpression) return false;
8273
+ const args0 = callNode.arguments.at(0);
8274
+ return args0 != null && isArgumentUsingRefValue(context, args0);
8275
+ };
8276
+ const getSetStateCalls = (id) => {
8277
+ const variable = findVariable(context.sourceCode.getScope(id), id);
8278
+ if (variable != null && variable.defs.some((def) => def.type === DefinitionType.Parameter)) return [];
8279
+ const node = resolveOrigin(context, id);
8280
+ switch (node?.type) {
8281
+ case AST_NODE_TYPES.ArrowFunctionExpression:
8282
+ case AST_NODE_TYPES.FunctionDeclaration:
8283
+ case AST_NODE_TYPES.FunctionExpression: return setStateCallsByFn.get(node) ?? [];
8284
+ case AST_NODE_TYPES.CallExpression: return setStateInHookCallbacks.get(node) ?? setStateInEffectArg.get(node) ?? [];
8285
+ }
8286
+ return [];
8287
+ };
8288
+ for (const [, calls] of setStateInEffectSetup) for (const call of calls) {
8289
+ if (isExemptSetStateCall(call)) continue;
8290
+ violations.push({
8291
+ name: call.name,
8292
+ node: call
8293
+ });
8294
+ }
8295
+ for (const { callee } of trackedFnCalls) {
8296
+ const unwrappedCallee = Extract.unwrap(callee);
8297
+ if (!Check.isIdentifier(unwrappedCallee)) continue;
8298
+ for (const setStateCall of getSetStateCalls(unwrappedCallee)) {
8299
+ if (isExemptSetStateCall(setStateCall)) continue;
8300
+ violations.push({
8301
+ name: getCallName(setStateCall, getText),
8302
+ node: setStateCall
8303
+ });
8304
+ }
8305
+ }
8306
+ for (const id of facts.setupIdentifiers) for (const setStateCall of getSetStateCalls(id)) {
8307
+ if (isExemptSetStateCall(setStateCall)) continue;
8308
+ violations.push({
8309
+ name: getCallName(setStateCall, getText),
8310
+ node: setStateCall
8311
+ });
8312
+ }
8313
+ return violations;
8076
8314
  }
8077
8315
 
8078
8316
  //#endregion
@@ -8092,51 +8330,121 @@ var set_state_in_effect_default = createRule({
8092
8330
  function create$5(context) {
8093
8331
  if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
8094
8332
  const { additionalEffectHooks, additionalStateHooks } = getSettingsFromContext(context);
8095
- const functionEntries = [];
8096
- const setupFnRef = { current: null };
8097
- const setupFnIds = [];
8098
- const trackedFnCalls = [];
8099
- const setStateCallsByFn = /* @__PURE__ */ new WeakMap();
8100
- const setStateInEffectArg = /* @__PURE__ */ new WeakMap();
8101
- const setStateInEffectSetup = /* @__PURE__ */ new Map();
8102
- const setStateInHookCallbacks = /* @__PURE__ */ new WeakMap();
8103
- const getText = (n) => context.sourceCode.getText(n);
8104
- const onSetupFunctionEnter = (node) => {
8105
- setupFnRef.current = node;
8106
- };
8107
- const onSetupFunctionExit = (node) => {
8108
- if (setupFnRef.current === node) setupFnRef.current = null;
8109
- };
8110
- function isThenCall(node) {
8111
- return Extract.unwrap(node.callee).type === AST_NODE_TYPES.MemberExpression && Extract.getCalleeName(node) === "then";
8333
+ const facts = createFactCollector$3(additionalStateHooks, additionalEffectHooks);
8334
+ return merge(facts.visitor, { "Program:exit"() {
8335
+ for (const violation of inferViolations$2(context, facts.facts)) context.report({
8336
+ data: { name: violation.name },
8337
+ messageId: "default",
8338
+ node: violation.node
8339
+ });
8340
+ } });
8341
+ }
8342
+
8343
+ //#endregion
8344
+ //#region src/rules/set-state-in-render/helpers.ts
8345
+ function isComponentOrHookLikeFunction(node) {
8346
+ const id = core.getFunctionId(node);
8347
+ if (id == null) return false;
8348
+ if (Check.isIdentifier(id)) return core.isFunctionComponentName(id.name) || core.isHookName(id.name);
8349
+ if (id.type === AST_NODE_TYPES.MemberExpression && Check.isIdentifier(id.property)) return core.isFunctionComponentName(id.property.name) || core.isHookName(id.property.name);
8350
+ return false;
8351
+ }
8352
+ function isInsideConditional(node, stopAt) {
8353
+ let current = node.parent;
8354
+ while (current != null && current !== stopAt) {
8355
+ switch (current.type) {
8356
+ case AST_NODE_TYPES.IfStatement:
8357
+ case AST_NODE_TYPES.ConditionalExpression:
8358
+ case AST_NODE_TYPES.LogicalExpression:
8359
+ case AST_NODE_TYPES.SwitchStatement:
8360
+ case AST_NODE_TYPES.SwitchCase: return true;
8361
+ }
8362
+ current = current.parent;
8112
8363
  }
8364
+ return false;
8365
+ }
8366
+
8367
+ //#endregion
8368
+ //#region src/rules/set-state-in-render/collect.ts
8369
+ function createFactCollector$2(additionalStateHooks) {
8370
+ const facts = {
8371
+ calls: [],
8372
+ earlyReturns: []
8373
+ };
8374
+ const functionEntries = [];
8375
+ const componentFnRef = { current: null };
8113
8376
  function isUseStateCall(node) {
8114
8377
  return core.isUseStateLikeCall(node, additionalStateHooks);
8115
8378
  }
8116
- function isUseEffectCall(node) {
8117
- return core.isUseEffectLikeCall(node, additionalEffectHooks);
8118
- }
8119
- function isUseEffectSetupCallback(node) {
8120
- return node.parent?.type === AST_NODE_TYPES.CallExpression && node.parent.callee !== node && isUseEffectCall(node.parent);
8121
- }
8122
- function getCallName(node) {
8123
- if (node.type === AST_NODE_TYPES.CallExpression) return Extract.getFullyQualifiedName(node.callee, getText);
8124
- return Extract.getFullyQualifiedName(node, getText);
8125
- }
8126
- function getCallKind(node) {
8127
- return match(node).when(isUseStateCall, () => "useState").when(isUseEffectCall, () => "useEffect").when(isSetStateCall, () => "setState").when(isThenCall, () => "then").otherwise(() => "other");
8128
- }
8129
8379
  function getFunctionKind(node) {
8380
+ if (isComponentOrHookLikeFunction(node)) return "component";
8130
8381
  const parent = Traverse.findParent(node, not(Check.isTypeExpression)) ?? node.parent;
8131
- switch (true) {
8132
- case node.async:
8133
- case parent.type === AST_NODE_TYPES.CallExpression && isThenCall(parent): return "deferred";
8134
- case node.type !== AST_NODE_TYPES.FunctionDeclaration && parent.type === AST_NODE_TYPES.CallExpression && parent.callee === node: return "immediate";
8135
- case isUseEffectSetupCallback(node): return "setup";
8136
- default: return "other";
8382
+ if (parent.type === AST_NODE_TYPES.CallExpression && parent.callee !== node) return "callback";
8383
+ return "other";
8384
+ }
8385
+ return {
8386
+ facts,
8387
+ visitor: {
8388
+ ":function"(node) {
8389
+ const kind = getFunctionKind(node);
8390
+ functionEntries.push({
8391
+ kind,
8392
+ node
8393
+ });
8394
+ if (kind === "component") componentFnRef.current = node;
8395
+ },
8396
+ ":function:exit"(node) {
8397
+ if (functionEntries.at(-1)?.kind === "component" && componentFnRef.current === node) componentFnRef.current = null;
8398
+ functionEntries.pop();
8399
+ },
8400
+ CallExpression(node) {
8401
+ const entry = functionEntries.at(-1);
8402
+ if (entry == null) return;
8403
+ if (isUseStateCall(node)) return;
8404
+ facts.calls.push({
8405
+ componentFunction: componentFnRef.current,
8406
+ enclosingFunction: entry.node,
8407
+ enclosingFunctionKind: entry.kind,
8408
+ node
8409
+ });
8410
+ },
8411
+ ReturnStatement(node) {
8412
+ const componentFn = componentFnRef.current;
8413
+ if (componentFn == null) return;
8414
+ const entry = functionEntries.at(-1);
8415
+ if (entry == null || entry.node !== componentFn) return;
8416
+ if (componentFn.body.type !== AST_NODE_TYPES.BlockStatement) return;
8417
+ const body = componentFn.body.body;
8418
+ let stmt = node;
8419
+ while (stmt.parent !== componentFn.body) {
8420
+ if (stmt.parent == null) return;
8421
+ stmt = stmt.parent;
8422
+ }
8423
+ const idx = body.indexOf(stmt);
8424
+ if (idx !== -1 && idx < body.length - 1) facts.earlyReturns.push({
8425
+ componentFunction: componentFn,
8426
+ node
8427
+ });
8428
+ }
8137
8429
  }
8430
+ };
8431
+ }
8432
+
8433
+ //#endregion
8434
+ //#region src/rules/set-state-in-render/origins.ts
8435
+ /**
8436
+ * Create a resolver that answers whether an identifier or call originates from
8437
+ * a `useState`-like hook's setter function (the element at index 1 of the
8438
+ * returned tuple).
8439
+ * @param context The rule context
8440
+ * @returns The setState provenance resolver
8441
+ */
8442
+ function createSetStateResolver(context) {
8443
+ const { additionalStateHooks } = getSettingsFromContext(context);
8444
+ function isUseStateCall(node) {
8445
+ return core.isUseStateLikeCall(node, additionalStateHooks);
8138
8446
  }
8139
- function isIdFromUseStateCall(id, at) {
8447
+ function isSetStateId(id, at) {
8140
8448
  const initNode = resolveOrigin(context, id);
8141
8449
  if (initNode == null) return false;
8142
8450
  if (initNode.type !== AST_NODE_TYPES.CallExpression) return false;
@@ -8156,194 +8464,53 @@ function create$5(context) {
8156
8464
  const [index] = callee.arguments;
8157
8465
  if (!isAt || index == null) return false;
8158
8466
  const indexScope = context.sourceCode.getScope(node);
8159
- return getStaticValue(index, indexScope)?.value === 1 && isIdFromUseStateCall(innerCallee.object);
8467
+ return getStaticValue(index, indexScope)?.value === 1 && isSetStateId(innerCallee.object);
8160
8468
  }
8161
- case AST_NODE_TYPES.Identifier: return isIdFromUseStateCall(callee, 1);
8469
+ case AST_NODE_TYPES.Identifier: return isSetStateId(callee, 1);
8162
8470
  case AST_NODE_TYPES.MemberExpression: {
8163
8471
  if (!("name" in callee.object)) return false;
8164
8472
  const property = callee.property;
8165
8473
  const propertyScope = context.sourceCode.getScope(node);
8166
- return getStaticValue(property, propertyScope)?.value === 1 && isIdFromUseStateCall(callee.object, 1);
8474
+ return getStaticValue(property, propertyScope)?.value === 1 && isSetStateId(callee.object, 1);
8167
8475
  }
8168
8476
  default: return false;
8169
8477
  }
8170
8478
  }
8171
- function isArgumentUsingRefValue(context, node) {
8172
- const isUsingRefValue = (n) => {
8173
- switch (n.type) {
8174
- case AST_NODE_TYPES.Identifier: return isInitializedFromRef(context, n.name, context.sourceCode.getScope(n));
8175
- case AST_NODE_TYPES.MemberExpression: return isUsingRefValue(n.object);
8176
- case AST_NODE_TYPES.CallExpression: return isUsingRefValue(n.callee) || getNestedIdentifiers(n).some(isUsingRefValue);
8177
- case AST_NODE_TYPES.BinaryExpression:
8178
- case AST_NODE_TYPES.LogicalExpression: return isUsingRefValue(n.left) || isUsingRefValue(n.right);
8179
- case AST_NODE_TYPES.UnaryExpression:
8180
- case AST_NODE_TYPES.UpdateExpression: return isUsingRefValue(n.argument);
8181
- case AST_NODE_TYPES.ConditionalExpression: return isUsingRefValue(n.consequent) || isUsingRefValue(n.alternate);
8182
- case AST_NODE_TYPES.SequenceExpression: return n.expressions.some(isUsingRefValue);
8183
- case AST_NODE_TYPES.AssignmentExpression: return isUsingRefValue(n.right);
8184
- default: return false;
8185
- }
8186
- };
8187
- if (isUsingRefValue(node)) return true;
8188
- return Check.isFunction(node) && context.sourceCode.getScope(node.body).references.some((r) => isUsingRefValue(r.identifier));
8189
- }
8190
8479
  return {
8191
- ":function"(node) {
8192
- const kind = getFunctionKind(node);
8193
- functionEntries.push({
8194
- kind,
8195
- node
8196
- });
8197
- if (kind === "setup") onSetupFunctionEnter(node);
8198
- },
8199
- ":function:exit"(node) {
8200
- const { kind } = functionEntries.at(-1) ?? {};
8201
- if (kind === "setup") onSetupFunctionExit(node);
8202
- functionEntries.pop();
8203
- },
8204
- CallExpression(node) {
8205
- const setupFunction = setupFnRef.current;
8206
- const entry = functionEntries.at(-1);
8207
- if (entry == null || entry.node.async) return;
8208
- match(getCallKind(node)).with("setState", () => {
8209
- switch (true) {
8210
- case entry.kind === "deferred":
8211
- case entry.node.async: break;
8212
- case entry.node === setupFunction:
8213
- case entry.kind === "immediate" && Traverse.findParent(entry.node, Check.isFunction) === setupFunction: {
8214
- const args0 = node.arguments.at(0);
8215
- if (args0 == null) return;
8216
- if (isArgumentUsingRefValue(context, args0)) return;
8217
- if (isRefGatedContext(context, node)) return;
8218
- context.report({
8219
- data: { name: context.sourceCode.getText(Extract.unwrap(node.callee)) },
8220
- messageId: "default",
8221
- node
8222
- });
8223
- return;
8224
- }
8225
- default: {
8226
- const init = Traverse.findParent(node, isHookDecl)?.init;
8227
- if (init == null) getOrInsertComputed(setStateCallsByFn, entry.node, () => []).push(node);
8228
- else getOrInsertComputed(setStateInHookCallbacks, init, () => []).push(node);
8229
- }
8230
- }
8231
- }).with("useEffect", () => {
8232
- if (Check.isFunction(node.arguments.at(0))) return;
8233
- setupFnIds.push(...getNestedIdentifiers(node));
8234
- }).with("other", () => {
8235
- if (entry.node !== setupFunction) return;
8236
- trackedFnCalls.push(node);
8237
- }).otherwise(constVoid);
8238
- },
8239
- Identifier(node) {
8240
- if (node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee === node) return;
8241
- if (!isIdFromUseStateCall(node, 1)) return;
8242
- switch (node.parent.type) {
8243
- case AST_NODE_TYPES.ArrowFunctionExpression: {
8244
- const parent = node.parent.parent;
8245
- if (parent.type !== AST_NODE_TYPES.CallExpression) break;
8246
- if (!core.isUseMemoCall(context, parent)) break;
8247
- const init = Traverse.findParent(parent, isHookDecl)?.init;
8248
- if (init != null) getOrInsertComputed(setStateInEffectArg, init, () => []).push(node);
8249
- break;
8250
- }
8251
- case AST_NODE_TYPES.CallExpression:
8252
- if (node !== node.parent.arguments.at(0)) break;
8253
- if (core.isUseCallbackCall(context, node.parent)) {
8254
- const init = Traverse.findParent(node.parent, isHookDecl)?.init;
8255
- if (init != null) getOrInsertComputed(setStateInEffectArg, init, () => []).push(node);
8256
- break;
8257
- }
8258
- if (isUseEffectCall(node.parent)) getOrInsertComputed(setStateInEffectSetup, node.parent, () => []).push(node);
8259
- }
8260
- },
8261
- "Program:exit"() {
8262
- const isExemptSetStateCall = (setStateCall) => {
8263
- const callNode = getSetStateCallExpression(setStateCall);
8264
- if (isRefGatedContext(context, callNode)) return true;
8265
- if (callNode.type !== AST_NODE_TYPES.CallExpression) return false;
8266
- const args0 = callNode.arguments.at(0);
8267
- return args0 != null && isArgumentUsingRefValue(context, args0);
8268
- };
8269
- const getSetStateCalls = (context, id) => {
8270
- const variable = findVariable(context.sourceCode.getScope(id), id);
8271
- if (variable != null && variable.defs.some((def) => def.type === DefinitionType.Parameter)) return [];
8272
- const node = resolveOrigin(context, id);
8273
- switch (node?.type) {
8274
- case AST_NODE_TYPES.ArrowFunctionExpression:
8275
- case AST_NODE_TYPES.FunctionDeclaration:
8276
- case AST_NODE_TYPES.FunctionExpression: return setStateCallsByFn.get(node) ?? [];
8277
- case AST_NODE_TYPES.CallExpression: return setStateInHookCallbacks.get(node) ?? setStateInEffectArg.get(node) ?? [];
8278
- }
8279
- return [];
8280
- };
8281
- for (const [, calls] of setStateInEffectSetup) for (const call of calls) {
8282
- if (isExemptSetStateCall(call)) continue;
8283
- context.report({
8284
- data: { name: call.name },
8285
- messageId: "default",
8286
- node: call
8287
- });
8288
- }
8289
- for (const { callee } of trackedFnCalls) {
8290
- const unwrappedCallee = Extract.unwrap(callee);
8291
- if (!Check.isIdentifier(unwrappedCallee)) continue;
8292
- const setStateCalls = getSetStateCalls(context, unwrappedCallee);
8293
- for (const setStateCall of setStateCalls) {
8294
- if (isExemptSetStateCall(setStateCall)) continue;
8295
- context.report({
8296
- data: { name: getCallName(setStateCall) },
8297
- messageId: "default",
8298
- node: setStateCall
8299
- });
8300
- }
8301
- }
8302
- for (const id of setupFnIds) {
8303
- const setStateCalls = getSetStateCalls(context, id);
8304
- for (const setStateCall of setStateCalls) {
8305
- if (isExemptSetStateCall(setStateCall)) continue;
8306
- context.report({
8307
- data: { name: getCallName(setStateCall) },
8308
- messageId: "default",
8309
- node: setStateCall
8310
- });
8311
- }
8312
- }
8313
- }
8480
+ isSetStateCall,
8481
+ isSetStateId,
8482
+ isUseStateCall
8314
8483
  };
8315
8484
  }
8316
8485
 
8317
8486
  //#endregion
8318
- //#region src/rules/set-state-in-render/lib.ts
8319
- function isInsideConditional(node, stopAt) {
8320
- let current = node.parent;
8321
- while (current != null && current !== stopAt) {
8322
- switch (current.type) {
8323
- case AST_NODE_TYPES.IfStatement:
8324
- case AST_NODE_TYPES.ConditionalExpression:
8325
- case AST_NODE_TYPES.LogicalExpression:
8326
- case AST_NODE_TYPES.SwitchStatement:
8327
- case AST_NODE_TYPES.SwitchCase: return true;
8328
- }
8329
- current = current.parent;
8330
- }
8331
- return false;
8332
- }
8333
- function isInsideEventHandler(node, stopAt) {
8334
- let current = node.parent;
8335
- while (current != null && current !== stopAt) {
8336
- if (Check.isFunction(current) && current !== stopAt) return true;
8337
- current = current.parent;
8487
+ //#region src/rules/set-state-in-render/effects.ts
8488
+ /**
8489
+ * Infer the setState calls that run unconditionally during render from the
8490
+ * collected facts: calls made directly in a component function body that are
8491
+ * neither guarded by a conditional nor positioned after an early return.
8492
+ * @param context The rule context
8493
+ * @param facts The collected facts
8494
+ * @returns The violations to report, in report order
8495
+ */
8496
+ function inferViolations$1(context, facts) {
8497
+ const resolver = createSetStateResolver(context);
8498
+ const getText = (n) => context.sourceCode.getText(n);
8499
+ const earlyReturnPositions = /* @__PURE__ */ new Map();
8500
+ for (const { componentFunction, node } of facts.earlyReturns) getOrInsertComputed(earlyReturnPositions, componentFunction, () => []).push(node.range[0]);
8501
+ const violations = [];
8502
+ for (const { componentFunction, enclosingFunction, node } of facts.calls) {
8503
+ if (componentFunction == null) continue;
8504
+ if (enclosingFunction !== componentFunction) continue;
8505
+ if (!resolver.isSetStateCall(node)) continue;
8506
+ if (isInsideConditional(node, componentFunction)) continue;
8507
+ if (earlyReturnPositions.get(componentFunction)?.some((pos) => pos < node.range[0]) === true) continue;
8508
+ violations.push({
8509
+ name: getText(Extract.unwrap(node.callee)),
8510
+ node
8511
+ });
8338
8512
  }
8339
- return false;
8340
- }
8341
- function isComponentOrHookLikeFunction(node) {
8342
- const id = core.getFunctionId(node);
8343
- if (id == null) return false;
8344
- if (Check.isIdentifier(id)) return core.isFunctionComponentName(id.name) || core.isHookName(id.name);
8345
- if (id.type === AST_NODE_TYPES.MemberExpression && Check.isIdentifier(id.property)) return core.isFunctionComponentName(id.property.name) || core.isHookName(id.property.name);
8346
- return false;
8513
+ return violations;
8347
8514
  }
8348
8515
 
8349
8516
  //#endregion
@@ -8362,103 +8529,19 @@ var set_state_in_render_default = createRule({
8362
8529
  });
8363
8530
  function create$4(context) {
8364
8531
  const { additionalStateHooks } = getSettingsFromContext(context);
8365
- const functionEntries = [];
8366
- const componentFnRef = { current: null };
8367
- const componentHasEarlyReturn = { current: false };
8368
- function isUseStateCall(node) {
8369
- return core.isUseStateLikeCall(node, additionalStateHooks);
8370
- }
8371
- function isIdFromUseStateCall(topLevelId, at) {
8372
- const initNode = resolveOrigin(context, topLevelId);
8373
- if (initNode == null) return false;
8374
- if (initNode.type !== AST_NODE_TYPES.CallExpression) return false;
8375
- if (!isUseStateCall(initNode)) return false;
8376
- const initNodeParent = initNode.parent;
8377
- if (!("id" in initNodeParent) || initNodeParent.id?.type !== AST_NODE_TYPES.ArrayPattern) return true;
8378
- return initNodeParent.id.elements.findIndex((e) => e != null && Check.isIdentifier(e, topLevelId.name)) === at;
8379
- }
8380
- function isSetStateCall(node) {
8381
- const callee = Extract.unwrap(node.callee);
8382
- switch (callee.type) {
8383
- case AST_NODE_TYPES.CallExpression: {
8384
- const innerCallee = Extract.unwrap(callee.callee);
8385
- if (innerCallee.type !== AST_NODE_TYPES.MemberExpression) return false;
8386
- if (!("name" in innerCallee.object)) return false;
8387
- const isAt = Extract.getCalleeName(callee) === "at";
8388
- const [index] = callee.arguments;
8389
- if (!isAt || index == null) return false;
8390
- const indexScope = context.sourceCode.getScope(node);
8391
- return getStaticValue(index, indexScope)?.value === 1 && isIdFromUseStateCall(innerCallee.object);
8392
- }
8393
- case AST_NODE_TYPES.Identifier: return isIdFromUseStateCall(callee, 1);
8394
- case AST_NODE_TYPES.MemberExpression: {
8395
- if (!("name" in callee.object)) return false;
8396
- const property = callee.property;
8397
- const propertyScope = context.sourceCode.getScope(node);
8398
- return getStaticValue(property, propertyScope)?.value === 1 && isIdFromUseStateCall(callee.object, 1);
8399
- }
8400
- default: return false;
8401
- }
8402
- }
8403
- function getFunctionKind(node) {
8404
- if (isComponentOrHookLikeFunction(node)) return "component";
8405
- const parent = Traverse.findParent(node, not(Check.isTypeExpression)) ?? node.parent;
8406
- if (parent.type === AST_NODE_TYPES.CallExpression && parent.callee !== node) return "callback";
8407
- return "other";
8408
- }
8409
- return {
8410
- ":function"(node) {
8411
- const kind = getFunctionKind(node);
8412
- functionEntries.push({
8413
- kind,
8414
- node
8415
- });
8416
- if (kind === "component") {
8417
- componentFnRef.current = node;
8418
- componentHasEarlyReturn.current = false;
8419
- }
8420
- },
8421
- ":function:exit"(node) {
8422
- if (functionEntries.at(-1)?.kind === "component" && componentFnRef.current === node) {
8423
- componentFnRef.current = null;
8424
- componentHasEarlyReturn.current = false;
8425
- }
8426
- functionEntries.pop();
8427
- },
8428
- CallExpression(node) {
8429
- const componentFn = componentFnRef.current;
8430
- if (componentFn == null) return;
8431
- if (!isSetStateCall(node)) return;
8432
- if (isInsideEventHandler(node, componentFn)) return;
8433
- if (isInsideConditional(node, componentFn)) return;
8434
- if (componentHasEarlyReturn.current) return;
8435
- context.report({
8436
- data: { name: context.sourceCode.getText(Extract.unwrap(node.callee)) },
8437
- messageId: "default",
8438
- node
8439
- });
8440
- },
8441
- ReturnStatement(node) {
8442
- const componentFn = componentFnRef.current;
8443
- if (componentFn == null) return;
8444
- const entry = functionEntries.at(-1);
8445
- if (entry == null || entry.node !== componentFn) return;
8446
- if (componentFn.body.type !== AST_NODE_TYPES.BlockStatement) return;
8447
- const body = componentFn.body.body;
8448
- let stmt = node;
8449
- while (stmt.parent !== componentFn.body) {
8450
- if (stmt.parent == null) return;
8451
- stmt = stmt.parent;
8452
- }
8453
- const idx = body.indexOf(stmt);
8454
- if (idx !== -1 && idx < body.length - 1) componentHasEarlyReturn.current = true;
8455
- }
8456
- };
8532
+ const facts = createFactCollector$2(additionalStateHooks);
8533
+ return merge(facts.visitor, { "Program:exit"() {
8534
+ for (const violation of inferViolations$1(context, facts.facts)) context.report({
8535
+ data: { name: violation.name },
8536
+ messageId: "default",
8537
+ node: violation.node
8538
+ });
8539
+ } });
8457
8540
  }
8458
8541
 
8459
8542
  //#endregion
8460
8543
  //#region src/rules/static-components/collect.ts
8461
- function createFactCollector() {
8544
+ function createFactCollector$1() {
8462
8545
  const facts = { componentUsages: [] };
8463
8546
  return {
8464
8547
  facts,
@@ -8606,7 +8689,7 @@ var static_components_default = createRule({
8606
8689
  docs: { description: "Validates that components are static, not recreated every render." },
8607
8690
  messages: {
8608
8691
  default: "Cannot create components during render. Components created during render will reset their state each time they are created. Declare components outside of render.",
8609
- createdHere: "The component is created during render here."
8692
+ "created-here": "The component is created during render here."
8610
8693
  },
8611
8694
  schema: []
8612
8695
  },
@@ -8618,7 +8701,7 @@ function create$3(context) {
8618
8701
  const hint = core.FunctionComponentDetectionHint.DoNotIncludeJsxWithNumberValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithNullValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithStringValue | core.FunctionComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | core.FunctionComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx | core.FunctionComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayPatternElement | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayExpressionElement | core.FunctionComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback;
8619
8702
  const fc = core.getFunctionComponentCollector(context, { hint });
8620
8703
  const cc = core.getClassComponentCollector(context);
8621
- const facts = createFactCollector();
8704
+ const facts = createFactCollector$1();
8622
8705
  return merge(fc.visitor, cc.visitor, facts.visitor, { "Program:exit"(program) {
8623
8706
  const componentNodes = [...fc.api.getAllComponents(program), ...cc.api.getAllComponents(program)].map((component) => component.node);
8624
8707
  const isInsideRender = createRenderBoundaryChecker(componentNodes);
@@ -8633,7 +8716,7 @@ function create$3(context) {
8633
8716
  reportedCreations.add(effect.creationNode);
8634
8717
  context.report({
8635
8718
  data: { name: effect.name },
8636
- messageId: "createdHere",
8719
+ messageId: "created-here",
8637
8720
  node: effect.creationNode
8638
8721
  });
8639
8722
  }
@@ -8717,14 +8800,17 @@ function create$2(context) {
8717
8800
  }
8718
8801
 
8719
8802
  //#endregion
8720
- //#region src/rules/use-memo/lib.ts
8721
- function isDeclaredInsideCallback(variable, callback) {
8722
- let scope = variable.scope;
8723
- while (scope != null) {
8724
- if (scope.block === callback) return true;
8725
- scope = scope.upper;
8726
- }
8727
- return false;
8803
+ //#region src/rules/use-memo/helpers.ts
8804
+ /**
8805
+ * Check whether the result of a `useMemo` call is used (assigned, returned,
8806
+ * passed as an argument, etc.) rather than discarded as a bare statement.
8807
+ * @param node The `useMemo` call expression
8808
+ * @returns `true` when the call result flows into a surrounding expression
8809
+ */
8810
+ function isResultUsed(node) {
8811
+ let parent = node.parent;
8812
+ while (Check.isTypeExpression(parent)) parent = parent.parent;
8813
+ return parent.type === AST_NODE_TYPES.VariableDeclarator || parent.type === AST_NODE_TYPES.AssignmentExpression || parent.type === AST_NODE_TYPES.AssignmentPattern || parent.type === AST_NODE_TYPES.Property || parent.type === AST_NODE_TYPES.ReturnStatement || parent.type === AST_NODE_TYPES.JSXExpressionContainer || parent.type === AST_NODE_TYPES.CallExpression || parent.type === AST_NODE_TYPES.NewExpression || parent.type === AST_NODE_TYPES.ArrayExpression || parent.type === AST_NODE_TYPES.ConditionalExpression || parent.type === AST_NODE_TYPES.LogicalExpression || parent.type === AST_NODE_TYPES.SequenceExpression || parent.type === AST_NODE_TYPES.SpreadElement || parent.type === AST_NODE_TYPES.TemplateLiteral || parent.type === AST_NODE_TYPES.BinaryExpression || parent.type === AST_NODE_TYPES.UnaryExpression || parent.type === AST_NODE_TYPES.MemberExpression || parent.type === AST_NODE_TYPES.TaggedTemplateExpression || parent.type === AST_NODE_TYPES.ChainExpression || parent.type === AST_NODE_TYPES.ArrowFunctionExpression || parent.type === AST_NODE_TYPES.ForOfStatement || parent.type === AST_NODE_TYPES.ForInStatement;
8728
8814
  }
8729
8815
  /**
8730
8816
  * Gets the nested return statements in the node that are within the same function
@@ -8733,108 +8819,168 @@ function isDeclaredInsideCallback(variable, callback) {
8733
8819
  */
8734
8820
  function getNestedReturnStatements(node) {
8735
8821
  const statements = [];
8736
- const boundaryNode = Check.isFunction(node) ? node : Traverse.findParent(node, Check.isFunction);
8822
+ const boundary = Check.isFunction(node) ? node : Traverse.findParent(node, Check.isFunction);
8737
8823
  simpleTraverse(node, { enter(node) {
8738
8824
  if (node.type !== AST_NODE_TYPES.ReturnStatement) return;
8739
- if (Traverse.findParent(node, Check.isFunction, (n) => n === boundaryNode) != null) return;
8825
+ if (Traverse.findParent(node, Check.isFunction, (n) => n === boundary) != null) return;
8740
8826
  statements.push(node);
8741
8827
  } });
8742
8828
  return statements;
8743
8829
  }
8744
8830
 
8745
8831
  //#endregion
8746
- //#region src/rules/use-memo/use-memo.ts
8747
- const RULE_NAME$1 = "use-memo";
8748
- var use_memo_default = createRule({
8749
- meta: {
8750
- type: "problem",
8751
- docs: { description: "Validates that 'useMemo' is called with a callback that returns a value." },
8752
- messages: {
8753
- mustReturnAValue: "useMemo() callbacks must return a value.\n\nThis useMemo() callback doesn't return a value. useMemo() is for computing and caching values, not for arbitrary side effects.",
8754
- noAsyncOrGeneratorFunctions: "useMemo() callbacks may not be async or generator functions.\n\nuseMemo() callbacks are called once and must synchronously return a value.",
8755
- noParameters: "useMemo() callbacks may not accept parameters.\n\nuseMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.",
8756
- noReassigningOuterVariables: "useMemo() callbacks may not reassign variables declared outside of the callback.\n\nuseMemo() callbacks must be pure functions and cannot reassign variables defined outside of the callback function.",
8757
- resultMustBeUsed: "useMemo() result is unused.\n\nThis useMemo() value is unused. useMemo() is for computing and caching values, not for arbitrary side effects."
8758
- },
8759
- schema: []
8760
- },
8761
- name: RULE_NAME$1,
8762
- create: create$1,
8763
- defaultOptions: []
8764
- });
8765
- function create$1(context) {
8766
- if (!context.sourceCode.text.includes("useMemo")) return {};
8767
- function validateNoOuterVariableReassignment(callback) {
8768
- const violations = [];
8769
- if (callback.body == null) return violations;
8770
- function checkWriteTarget(node, target) {
8832
+ //#region src/rules/use-memo/collect.ts
8833
+ function createFactCollector(isUseMemoCall) {
8834
+ const facts = { calls: [] };
8835
+ function collectWrites(callback) {
8836
+ const writes = [];
8837
+ function pushWrite(node, target) {
8771
8838
  if (!Check.isIdentifier(target)) return;
8772
8839
  if (Traverse.findParent(node, Check.isFunction, (n) => n === callback) != null) return;
8773
- const scope = context.sourceCode.getScope(target);
8774
- const variable = findVariable(scope, target);
8775
- if (variable != null && variable.defs.length > 0 && isDeclaredInsideCallback(variable, callback)) return;
8776
- violations.push({
8777
- messageId: "noReassigningOuterVariables",
8778
- node: target
8840
+ writes.push({
8841
+ node,
8842
+ target
8779
8843
  });
8780
8844
  }
8781
8845
  simpleTraverse(callback.body, { enter(node) {
8782
8846
  switch (node.type) {
8783
8847
  case AST_NODE_TYPES.AssignmentExpression:
8784
- for (const target of Extract.getAssignmentTargets(node.left)) checkWriteTarget(node, target);
8848
+ for (const target of Extract.getAssignmentTargets(node.left)) pushWrite(node, target);
8785
8849
  return;
8786
8850
  case AST_NODE_TYPES.ForInStatement:
8787
8851
  case AST_NODE_TYPES.ForOfStatement:
8788
8852
  if (node.left.type === AST_NODE_TYPES.VariableDeclaration) return;
8789
- for (const target of Extract.getAssignmentTargets(node.left)) checkWriteTarget(node, target);
8853
+ for (const target of Extract.getAssignmentTargets(node.left)) pushWrite(node, target);
8790
8854
  return;
8791
8855
  }
8792
8856
  } });
8793
- return violations;
8857
+ return writes;
8794
8858
  }
8795
- return { CallExpression(node) {
8796
- if (!core.isUseMemoCall(context, node)) return;
8797
- let parent = node.parent;
8798
- while (Check.isTypeExpression(parent)) parent = parent.parent;
8799
- if (!(parent.type === AST_NODE_TYPES.VariableDeclarator || parent.type === AST_NODE_TYPES.AssignmentExpression || parent.type === AST_NODE_TYPES.AssignmentPattern || parent.type === AST_NODE_TYPES.Property || parent.type === AST_NODE_TYPES.ReturnStatement || parent.type === AST_NODE_TYPES.JSXExpressionContainer || parent.type === AST_NODE_TYPES.CallExpression || parent.type === AST_NODE_TYPES.NewExpression || parent.type === AST_NODE_TYPES.ArrayExpression || parent.type === AST_NODE_TYPES.ConditionalExpression || parent.type === AST_NODE_TYPES.LogicalExpression || parent.type === AST_NODE_TYPES.SequenceExpression || parent.type === AST_NODE_TYPES.SpreadElement || parent.type === AST_NODE_TYPES.TemplateLiteral || parent.type === AST_NODE_TYPES.BinaryExpression || parent.type === AST_NODE_TYPES.UnaryExpression || parent.type === AST_NODE_TYPES.MemberExpression || parent.type === AST_NODE_TYPES.TaggedTemplateExpression || parent.type === AST_NODE_TYPES.ChainExpression || parent.type === AST_NODE_TYPES.ArrowFunctionExpression || parent.type === AST_NODE_TYPES.ForOfStatement || parent.type === AST_NODE_TYPES.ForInStatement)) {
8800
- context.report({
8801
- messageId: "resultMustBeUsed",
8802
- node
8859
+ return {
8860
+ facts,
8861
+ visitor: { CallExpression(node) {
8862
+ if (!isUseMemoCall(node)) return;
8863
+ const callbackArg = node.arguments[0] ?? null;
8864
+ const callback = callbackArg != null ? Extract.unwrap(callbackArg) : null;
8865
+ const callbackFunction = callback != null && Check.isFunction(callback) ? callback : null;
8866
+ facts.calls.push({
8867
+ callback: callbackFunction,
8868
+ callbackArg,
8869
+ isResultUsed: isResultUsed(node),
8870
+ node,
8871
+ returnStatements: callbackFunction != null ? getNestedReturnStatements(callbackFunction) : [],
8872
+ writes: callbackFunction != null ? collectWrites(callbackFunction) : []
8803
8873
  });
8804
- return;
8874
+ } }
8875
+ };
8876
+ }
8877
+
8878
+ //#endregion
8879
+ //#region src/rules/use-memo/origins.ts
8880
+ function isDeclaredInsideCallback(variable, callback) {
8881
+ let scope = variable.scope;
8882
+ while (scope != null) {
8883
+ if (scope.block === callback) return true;
8884
+ scope = scope.upper;
8885
+ }
8886
+ return false;
8887
+ }
8888
+ /**
8889
+ * Resolve whether an identifier written inside a `useMemo` callback refers to a
8890
+ * variable declared outside the callback. Unresolved identifiers and variables
8891
+ * without definitions are treated as outer variables.
8892
+ * @param context The rule context
8893
+ * @param target The written identifier
8894
+ * @param callback The `useMemo` callback the write occurs in
8895
+ * @returns `true` when the write reassigns an outer variable
8896
+ */
8897
+ function isOuterVariable(context, target, callback) {
8898
+ const scope = context.sourceCode.getScope(target);
8899
+ const variable = findVariable(scope, target);
8900
+ if (variable == null || variable.defs.length === 0) return true;
8901
+ return !isDeclaredInsideCallback(variable, callback);
8902
+ }
8903
+
8904
+ //#endregion
8905
+ //#region src/rules/use-memo/effects.ts
8906
+ /**
8907
+ * Infer the `useMemo` usage violations from the collected facts: unused
8908
+ * results, callbacks with parameters, async or generator callbacks, outer
8909
+ * variable reassignments, and callbacks that never return a value.
8910
+ * @param context The rule context
8911
+ * @param facts The collected facts
8912
+ * @returns The violations to report, in report order
8913
+ */
8914
+ function inferViolations(context, facts) {
8915
+ const violations = [];
8916
+ for (const fact of facts.calls) {
8917
+ if (!fact.isResultUsed) {
8918
+ violations.push({
8919
+ kind: "result-must-be-used",
8920
+ node: fact.node
8921
+ });
8922
+ continue;
8805
8923
  }
8806
- const [callbackArg] = node.arguments;
8807
- if (callbackArg == null) return;
8808
- const callback = Extract.unwrap(callbackArg);
8809
- if (!Check.isFunction(callback)) return;
8924
+ const { callback, callbackArg } = fact;
8925
+ if (callbackArg == null || callback == null) continue;
8810
8926
  if (callback.params.length > 0) {
8811
8927
  const firstParam = callback.params[0];
8812
- if (firstParam == null) return;
8813
- context.report({
8814
- messageId: "noParameters",
8928
+ if (firstParam == null) continue;
8929
+ violations.push({
8930
+ kind: "no-parameters",
8815
8931
  node: Check.isIdentifier(firstParam) ? firstParam : callback
8816
8932
  });
8817
8933
  }
8818
- if (callback.async || callback.generator) context.report({
8819
- messageId: "noAsyncOrGeneratorFunctions",
8934
+ if (callback.async || callback.generator) violations.push({
8935
+ kind: "no-async-or-generator-functions",
8820
8936
  node: callback
8821
8937
  });
8822
- for (const violation of validateNoOuterVariableReassignment(callback)) context.report(violation);
8823
- if (callback.type === AST_NODE_TYPES.ArrowFunctionExpression && callback.body.type !== AST_NODE_TYPES.BlockStatement) return;
8824
- if (callback.body.type !== AST_NODE_TYPES.BlockStatement) return;
8825
- const returnStatements = getNestedReturnStatements(callback);
8826
- if (returnStatements.length === 0) {
8827
- context.report({
8828
- messageId: "mustReturnAValue",
8829
- node: callbackArg
8938
+ for (const write of fact.writes) {
8939
+ if (!isOuterVariable(context, write.target, callback)) continue;
8940
+ violations.push({
8941
+ kind: "no-reassigning-outer-variables",
8942
+ node: write.target
8830
8943
  });
8831
- return;
8832
8944
  }
8833
- if (!returnStatements.some((stmt) => stmt.argument != null)) context.report({
8834
- messageId: "mustReturnAValue",
8945
+ if (callback.type === AST_NODE_TYPES.ArrowFunctionExpression && callback.body.type !== AST_NODE_TYPES.BlockStatement) continue;
8946
+ if (callback.body.type !== AST_NODE_TYPES.BlockStatement) continue;
8947
+ if (!fact.returnStatements.some((stmt) => stmt.argument != null)) violations.push({
8948
+ kind: "must-return-a-value",
8835
8949
  node: callbackArg
8836
8950
  });
8837
- } };
8951
+ }
8952
+ return violations;
8953
+ }
8954
+
8955
+ //#endregion
8956
+ //#region src/rules/use-memo/use-memo.ts
8957
+ const RULE_NAME$1 = "use-memo";
8958
+ var use_memo_default = createRule({
8959
+ meta: {
8960
+ type: "problem",
8961
+ docs: { description: "Validates that 'useMemo' is called with a callback that returns a value." },
8962
+ messages: {
8963
+ "must-return-a-value": "useMemo() callbacks must return a value.\n\nThis useMemo() callback doesn't return a value. useMemo() is for computing and caching values, not for arbitrary side effects.",
8964
+ "no-async-or-generator-functions": "useMemo() callbacks may not be async or generator functions.\n\nuseMemo() callbacks are called once and must synchronously return a value.",
8965
+ "no-parameters": "useMemo() callbacks may not accept parameters.\n\nuseMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.",
8966
+ "no-reassigning-outer-variables": "useMemo() callbacks may not reassign variables declared outside of the callback.\n\nuseMemo() callbacks must be pure functions and cannot reassign variables defined outside of the callback function.",
8967
+ "result-must-be-used": "useMemo() result is unused.\n\nThis useMemo() value is unused. useMemo() is for computing and caching values, not for arbitrary side effects."
8968
+ },
8969
+ schema: []
8970
+ },
8971
+ name: RULE_NAME$1,
8972
+ create: create$1,
8973
+ defaultOptions: []
8974
+ });
8975
+ function create$1(context) {
8976
+ if (!context.sourceCode.text.includes("useMemo")) return {};
8977
+ const facts = createFactCollector(core.isUseMemoCall(context));
8978
+ return merge(facts.visitor, { "Program:exit"() {
8979
+ for (const violation of inferViolations(context, facts.facts)) context.report({
8980
+ messageId: violation.kind,
8981
+ node: violation.node
8982
+ });
8983
+ } });
8838
8984
  }
8839
8985
 
8840
8986
  //#endregion
@@ -8844,6 +8990,59 @@ const LAZY_INIT_ALLOW_LIST = [
8844
8990
  "String",
8845
8991
  "Number"
8846
8992
  ];
8993
+ const EXPRESSION_VISITOR_KEYS = {
8994
+ [AST_NODE_TYPES.ArrayExpression]: ["elements"],
8995
+ [AST_NODE_TYPES.ArrayPattern]: ["elements"],
8996
+ [AST_NODE_TYPES.AssignmentExpression]: ["left", "right"],
8997
+ [AST_NODE_TYPES.AssignmentPattern]: ["left", "right"],
8998
+ [AST_NODE_TYPES.AwaitExpression]: ["argument"],
8999
+ [AST_NODE_TYPES.BinaryExpression]: ["left", "right"],
9000
+ [AST_NODE_TYPES.CallExpression]: ["callee", "arguments"],
9001
+ [AST_NODE_TYPES.ChainExpression]: ["expression"],
9002
+ [AST_NODE_TYPES.ConditionalExpression]: [
9003
+ "test",
9004
+ "consequent",
9005
+ "alternate"
9006
+ ],
9007
+ [AST_NODE_TYPES.Decorator]: ["expression"],
9008
+ [AST_NODE_TYPES.DoWhileStatement]: ["test"],
9009
+ [AST_NODE_TYPES.ExpressionStatement]: ["expression"],
9010
+ [AST_NODE_TYPES.ForInStatement]: ["left", "right"],
9011
+ [AST_NODE_TYPES.ForOfStatement]: ["left", "right"],
9012
+ [AST_NODE_TYPES.ForStatement]: ["test"],
9013
+ [AST_NODE_TYPES.IfStatement]: [
9014
+ "test",
9015
+ "consequent",
9016
+ "alternate"
9017
+ ],
9018
+ [AST_NODE_TYPES.ImportExpression]: ["source"],
9019
+ [AST_NODE_TYPES.JSXExpressionContainer]: ["expression"],
9020
+ [AST_NODE_TYPES.JSXSpreadChild]: ["expression"],
9021
+ [AST_NODE_TYPES.LogicalExpression]: ["left", "right"],
9022
+ [AST_NODE_TYPES.MemberExpression]: ["object", "property"],
9023
+ [AST_NODE_TYPES.NewExpression]: ["callee", "arguments"],
9024
+ [AST_NODE_TYPES.ObjectExpression]: ["properties"],
9025
+ [AST_NODE_TYPES.ObjectPattern]: ["properties"],
9026
+ [AST_NODE_TYPES.Property]: ["value"],
9027
+ [AST_NODE_TYPES.SequenceExpression]: ["expressions"],
9028
+ [AST_NODE_TYPES.SpreadElement]: ["argument"],
9029
+ [AST_NODE_TYPES.SwitchCase]: ["test", "consequent"],
9030
+ [AST_NODE_TYPES.TaggedTemplateExpression]: ["tag", "quasi"],
9031
+ [AST_NODE_TYPES.TemplateLiteral]: ["expressions"],
9032
+ [AST_NODE_TYPES.TSAsExpression]: ["expression"],
9033
+ [AST_NODE_TYPES.TSClassImplements]: ["expression"],
9034
+ [AST_NODE_TYPES.TSExportAssignment]: ["expression"],
9035
+ [AST_NODE_TYPES.TSExternalModuleReference]: ["expression"],
9036
+ [AST_NODE_TYPES.TSInstantiationExpression]: ["expression"],
9037
+ [AST_NODE_TYPES.TSInterfaceHeritage]: ["expression"],
9038
+ [AST_NODE_TYPES.TSNonNullExpression]: ["expression"],
9039
+ [AST_NODE_TYPES.TSSatisfiesExpression]: ["expression"],
9040
+ [AST_NODE_TYPES.TSTypeAssertion]: ["expression"],
9041
+ [AST_NODE_TYPES.UnaryExpression]: ["argument"],
9042
+ [AST_NODE_TYPES.UpdateExpression]: ["argument"],
9043
+ [AST_NODE_TYPES.WhileStatement]: ["test"],
9044
+ [AST_NODE_TYPES.YieldExpression]: ["argument"]
9045
+ };
8847
9046
  /**
8848
9047
  * Get all nested expressions of type T in an expression like node
8849
9048
  * @param type The type of the expression to retrieve within the node
@@ -8854,72 +9053,13 @@ function getNestedExpressionsOfType(type) {
8854
9053
  const isNodeOfType = Check.is(type);
8855
9054
  const recurse = (node) => {
8856
9055
  const expressions = [];
8857
- if (isNodeOfType(node)) expressions.push(node);
8858
- if ("arguments" in node) {
8859
- const chunk = node.arguments.flatMap(recurse);
8860
- expressions.push(...chunk);
8861
- }
8862
- if ("expression" in node && node.expression !== true && node.expression !== false) {
8863
- const chunk = recurse(node.expression);
8864
- expressions.push(...chunk);
8865
- }
8866
- if ("left" in node) {
8867
- const chunk = recurse(node.left);
8868
- expressions.push(...chunk);
8869
- }
8870
- if ("right" in node) {
8871
- const chunk = recurse(node.right);
8872
- expressions.push(...chunk);
8873
- }
8874
- if ("test" in node && node.test != null) {
8875
- const chunk = recurse(node.test);
8876
- expressions.push(...chunk);
8877
- }
8878
- if ("consequent" in node) {
8879
- const chunk = Array.isArray(node.consequent) ? node.consequent.flatMap(recurse) : recurse(node.consequent);
8880
- expressions.push(...chunk);
8881
- }
8882
- if ("alternate" in node && node.alternate != null) {
8883
- const chunk = Array.isArray(node.alternate) ? node.alternate.flatMap(recurse) : recurse(node.alternate);
8884
- expressions.push(...chunk);
8885
- }
8886
- if ("elements" in node) {
8887
- const chunk = node.elements.filter((x) => x != null).flatMap(recurse);
8888
- expressions.push(...chunk);
8889
- }
8890
- if ("properties" in node) {
8891
- const chunk = node.properties.flatMap(recurse);
8892
- expressions.push(...chunk);
8893
- }
8894
- if ("expressions" in node) {
8895
- const chunk = node.expressions.flatMap(recurse);
8896
- expressions.push(...chunk);
8897
- }
8898
- if (node.type === AST_NODE_TYPES.Property) {
8899
- const chunk = recurse(node.value);
8900
- expressions.push(...chunk);
8901
- }
8902
- if (node.type === AST_NODE_TYPES.SpreadElement) {
8903
- const chunk = recurse(node.argument);
8904
- expressions.push(...chunk);
8905
- }
8906
- if (node.type === AST_NODE_TYPES.MemberExpression) {
8907
- expressions.push(...recurse(node.object));
8908
- if (node.computed) expressions.push(...recurse(node.property));
8909
- }
8910
- if (node.type === AST_NODE_TYPES.UnaryExpression) {
8911
- const chunk = recurse(node.argument);
8912
- expressions.push(...chunk);
8913
- }
8914
- if (node.type === AST_NODE_TYPES.AwaitExpression) expressions.push(...recurse(node.argument));
8915
- if (node.type === AST_NODE_TYPES.YieldExpression && node.argument != null) expressions.push(...recurse(node.argument));
8916
- if (node.type === AST_NODE_TYPES.UpdateExpression) expressions.push(...recurse(node.argument));
8917
- if (node.type === AST_NODE_TYPES.CallExpression || node.type === AST_NODE_TYPES.NewExpression) expressions.push(...recurse(node.callee));
8918
- if (node.type === AST_NODE_TYPES.TaggedTemplateExpression) {
8919
- expressions.push(...recurse(node.tag));
8920
- expressions.push(...recurse(node.quasi));
8921
- }
8922
- if (node.type === AST_NODE_TYPES.ImportExpression) expressions.push(...recurse(node.source));
9056
+ simpleTraverse(node, {
9057
+ enter(node, parent) {
9058
+ if (parent?.type === AST_NODE_TYPES.MemberExpression && !parent.computed && parent.property === node) return;
9059
+ if (isNodeOfType(node)) expressions.push(node);
9060
+ },
9061
+ visitorKeys: EXPRESSION_VISITOR_KEYS
9062
+ });
8923
9063
  return expressions;
8924
9064
  };
8925
9065
  return recurse;
@@ -8968,9 +9108,9 @@ var use_state_default = createRule({
8968
9108
  type: "suggestion",
8969
9109
  docs: { description: "Enforces correct usage of 'useState', including destructuring, symmetric naming of the value and setter, and wrapping expensive initializers in a lazy initializer function." },
8970
9110
  messages: {
8971
- invalidAssignment: "useState should be destructured into a value and setter pair, e.g., const [state, setState] = useState(...).",
8972
- invalidInitialization: "To prevent re-computation, consider using lazy initial state for useState calls that involve function calls. Ex: 'useState(() => getValue())'.",
8973
- invalidSetterName: "The setter should be named 'set' followed by the capitalized state variable name, e.g., 'setState' for 'state'."
9111
+ "invalid-assignment": "useState should be destructured into a value and setter pair, e.g., const [state, setState] = useState(...).",
9112
+ "invalid-initialization": "To prevent re-computation, consider using lazy initial state for useState calls that involve function calls. Ex: 'useState(() => getValue())'.",
9113
+ "invalid-setter-name": "The setter should be named 'set' followed by the capitalized state variable name, e.g., 'setState' for 'state'."
8974
9114
  },
8975
9115
  schema
8976
9116
  },
@@ -8991,7 +9131,7 @@ function create(context) {
8991
9131
  if (LAZY_INIT_ALLOW_LIST.includes(expr.callee.name)) continue;
8992
9132
  if (Traverse.findParent(expr, (n) => core.isUseCall(context, n)) != null) continue;
8993
9133
  context.report({
8994
- messageId: "invalidInitialization",
9134
+ messageId: "invalid-initialization",
8995
9135
  node: expr
8996
9136
  });
8997
9137
  }
@@ -9001,7 +9141,7 @@ function create(context) {
9001
9141
  if (LAZY_INIT_ALLOW_LIST.includes(expr.callee.name)) continue;
9002
9142
  if (Traverse.findParent(expr, (n) => core.isUseCall(context, n)) != null) continue;
9003
9143
  context.report({
9004
- messageId: "invalidInitialization",
9144
+ messageId: "invalid-initialization",
9005
9145
  node: expr
9006
9146
  });
9007
9147
  }
@@ -9014,7 +9154,7 @@ function create(context) {
9014
9154
  if (parent.type !== AST_NODE_TYPES.VariableDeclarator) {
9015
9155
  if (!enforceAssignment) return;
9016
9156
  context.report({
9017
- messageId: "invalidAssignment",
9157
+ messageId: "invalid-assignment",
9018
9158
  node
9019
9159
  });
9020
9160
  return;
@@ -9023,7 +9163,7 @@ function create(context) {
9023
9163
  if (id?.type !== AST_NODE_TYPES.ArrayPattern) {
9024
9164
  if (!enforceAssignment) return;
9025
9165
  context.report({
9026
- messageId: "invalidAssignment",
9166
+ messageId: "invalid-assignment",
9027
9167
  node: id ?? node
9028
9168
  });
9029
9169
  return;
@@ -9032,7 +9172,7 @@ function create(context) {
9032
9172
  if (value == null) {
9033
9173
  if (!enforceAssignment) return;
9034
9174
  context.report({
9035
- messageId: "invalidAssignment",
9175
+ messageId: "invalid-assignment",
9036
9176
  node: id
9037
9177
  });
9038
9178
  return;
@@ -9040,20 +9180,20 @@ function create(context) {
9040
9180
  if (setter == null || !enforceSetterName) return;
9041
9181
  if (value.type !== AST_NODE_TYPES.Identifier) {
9042
9182
  context.report({
9043
- messageId: "invalidAssignment",
9183
+ messageId: "invalid-assignment",
9044
9184
  node: value
9045
9185
  });
9046
9186
  return;
9047
9187
  }
9048
9188
  if (setter.type !== AST_NODE_TYPES.Identifier || !setter.name.startsWith("set")) {
9049
9189
  context.report({
9050
- messageId: "invalidSetterName",
9190
+ messageId: "invalid-setter-name",
9051
9191
  node: setter
9052
9192
  });
9053
9193
  return;
9054
9194
  }
9055
9195
  if (snakeCase(setter.name) !== `set_${snakeCase(value.name)}`) context.report({
9056
- messageId: "invalidSetterName",
9196
+ messageId: "invalid-setter-name",
9057
9197
  node: setter
9058
9198
  });
9059
9199
  } };