eslint-plugin-react-x 5.23.5 → 5.24.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +745 -611
  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.0";
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
  }
@@ -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]: ["arguments", "callee"],
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]: ["arguments", "callee"],
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,29 @@ 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.AssignmentExpression: return isUsingRefValue(n.right);
8144
+ default: return false;
8024
8145
  }
8025
- default: return null;
8026
- }
8146
+ };
8147
+ if (isUsingRefValue(node)) return true;
8148
+ return Check.isFunction(node) && context.sourceCode.getScope(node.body).references.some((r) => isUsingRefValue(r.identifier));
8027
8149
  }
8028
8150
  /**
8029
8151
  * Check if a setState call is inside a conditional block whose test expression
@@ -8056,23 +8178,133 @@ function isRefGatedContext(context, node) {
8056
8178
  }
8057
8179
  return false;
8058
8180
  }
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
8181
  function isRefInExpression(context, node) {
8065
8182
  return getNestedIdentifiers(node).some((id) => isInitializedFromRef(context, id.name, context.sourceCode.getScope(id)));
8066
8183
  }
8067
8184
  /**
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
8185
+ * Get the name that identifies the source of a `.current` member access.
8186
+ * @param node The object of the member expression
8187
+ * @returns The identifier name, the last property name of a member chain, or null
8073
8188
  */
8074
- function getSetStateCallExpression(node) {
8075
- return Check.isIdentifier(node) && node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee === node ? node.parent : node;
8189
+ function getRefSourceName(node) {
8190
+ switch (node.type) {
8191
+ case AST_NODE_TYPES.Identifier: return node.name;
8192
+ case AST_NODE_TYPES.MemberExpression: {
8193
+ const property = Extract.unwrap(node.property);
8194
+ return !node.computed && property.type === AST_NODE_TYPES.Identifier ? property.name : null;
8195
+ }
8196
+ default: return null;
8197
+ }
8198
+ }
8199
+
8200
+ //#endregion
8201
+ //#region src/rules/set-state-in-effect/effects.ts
8202
+ /**
8203
+ * Infer the setState calls that run synchronously during an effect setup from
8204
+ * the collected facts: calls made directly in the setup body (or an IIFE within
8205
+ * it), and calls reached indirectly through local functions, hook callbacks, or
8206
+ * setup identifiers passed to the effect.
8207
+ * @param context The rule context
8208
+ * @param facts The collected facts
8209
+ * @returns The violations to report, in report order
8210
+ */
8211
+ function inferViolations$2(context, facts) {
8212
+ const { additionalEffectHooks } = getSettingsFromContext(context);
8213
+ const resolver = createSetStateResolver$1(context);
8214
+ const getText = (n) => context.sourceCode.getText(n);
8215
+ const isUseEffectCall = (node) => core.isUseEffectLikeCall(node, additionalEffectHooks);
8216
+ const setStateCallsByFn = /* @__PURE__ */ new Map();
8217
+ const setStateInHookCallbacks = /* @__PURE__ */ new Map();
8218
+ const setStateInEffectArg = /* @__PURE__ */ new Map();
8219
+ const setStateInEffectSetup = /* @__PURE__ */ new Map();
8220
+ const trackedFnCalls = [];
8221
+ const violations = [];
8222
+ for (const { enclosingFunction, enclosingFunctionKind, node, setupFunction } of facts.calls) {
8223
+ if (resolver.isUseStateCall(node) || isThenCall(node)) continue;
8224
+ if (!resolver.isSetStateCall(node)) {
8225
+ if (setupFunction != null && enclosingFunction === setupFunction) trackedFnCalls.push(node);
8226
+ continue;
8227
+ }
8228
+ if (enclosingFunctionKind === "deferred") continue;
8229
+ if (setupFunction != null && (enclosingFunction === setupFunction || enclosingFunctionKind === "immediate" && Traverse.findParent(enclosingFunction, Check.isFunction) === setupFunction)) {
8230
+ const args0 = node.arguments.at(0);
8231
+ if (args0 == null) continue;
8232
+ if (isArgumentUsingRefValue(context, args0)) continue;
8233
+ if (isRefGatedContext(context, node)) continue;
8234
+ violations.push({
8235
+ name: getText(Extract.unwrap(node.callee)),
8236
+ node
8237
+ });
8238
+ continue;
8239
+ }
8240
+ const init = Traverse.findParent(node, isHookDecl)?.init;
8241
+ if (init == null) getOrInsertComputed(setStateCallsByFn, enclosingFunction, () => []).push(node);
8242
+ else getOrInsertComputed(setStateInHookCallbacks, init, () => []).push(node);
8243
+ }
8244
+ for (const { kind, node } of facts.setStateReferences) {
8245
+ if (!resolver.isSetStateId(node, 1)) continue;
8246
+ if (kind === "callback-body") {
8247
+ const parent = node.parent.parent;
8248
+ if (parent?.type !== AST_NODE_TYPES.CallExpression) continue;
8249
+ if (!core.isUseMemoCall(context, parent)) continue;
8250
+ const init = Traverse.findParent(parent, isHookDecl)?.init;
8251
+ if (init != null) getOrInsertComputed(setStateInEffectArg, init, () => []).push(node);
8252
+ continue;
8253
+ }
8254
+ const parent = node.parent;
8255
+ if (parent.type !== AST_NODE_TYPES.CallExpression) continue;
8256
+ if (core.isUseCallbackCall(context, parent)) {
8257
+ const init = Traverse.findParent(parent, isHookDecl)?.init;
8258
+ if (init != null) getOrInsertComputed(setStateInEffectArg, init, () => []).push(node);
8259
+ continue;
8260
+ }
8261
+ if (isUseEffectCall(parent)) getOrInsertComputed(setStateInEffectSetup, parent, () => []).push(node);
8262
+ }
8263
+ const isExemptSetStateCall = (setStateCall) => {
8264
+ const callNode = getSetStateCallExpression(setStateCall);
8265
+ if (isRefGatedContext(context, callNode)) return true;
8266
+ if (callNode.type !== AST_NODE_TYPES.CallExpression) return false;
8267
+ const args0 = callNode.arguments.at(0);
8268
+ return args0 != null && isArgumentUsingRefValue(context, args0);
8269
+ };
8270
+ const getSetStateCalls = (id) => {
8271
+ const variable = findVariable(context.sourceCode.getScope(id), id);
8272
+ if (variable != null && variable.defs.some((def) => def.type === DefinitionType.Parameter)) return [];
8273
+ const node = resolveOrigin(context, id);
8274
+ switch (node?.type) {
8275
+ case AST_NODE_TYPES.ArrowFunctionExpression:
8276
+ case AST_NODE_TYPES.FunctionDeclaration:
8277
+ case AST_NODE_TYPES.FunctionExpression: return setStateCallsByFn.get(node) ?? [];
8278
+ case AST_NODE_TYPES.CallExpression: return setStateInHookCallbacks.get(node) ?? setStateInEffectArg.get(node) ?? [];
8279
+ }
8280
+ return [];
8281
+ };
8282
+ for (const [, calls] of setStateInEffectSetup) for (const call of calls) {
8283
+ if (isExemptSetStateCall(call)) continue;
8284
+ violations.push({
8285
+ name: call.name,
8286
+ node: call
8287
+ });
8288
+ }
8289
+ for (const { callee } of trackedFnCalls) {
8290
+ const unwrappedCallee = Extract.unwrap(callee);
8291
+ if (!Check.isIdentifier(unwrappedCallee)) continue;
8292
+ for (const setStateCall of getSetStateCalls(unwrappedCallee)) {
8293
+ if (isExemptSetStateCall(setStateCall)) continue;
8294
+ violations.push({
8295
+ name: getCallName(setStateCall, getText),
8296
+ node: setStateCall
8297
+ });
8298
+ }
8299
+ }
8300
+ for (const id of facts.setupIdentifiers) for (const setStateCall of getSetStateCalls(id)) {
8301
+ if (isExemptSetStateCall(setStateCall)) continue;
8302
+ violations.push({
8303
+ name: getCallName(setStateCall, getText),
8304
+ node: setStateCall
8305
+ });
8306
+ }
8307
+ return violations;
8076
8308
  }
8077
8309
 
8078
8310
  //#endregion
@@ -8092,51 +8324,121 @@ var set_state_in_effect_default = createRule({
8092
8324
  function create$5(context) {
8093
8325
  if (!/use\w*Effect/u.test(context.sourceCode.text)) return {};
8094
8326
  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";
8327
+ const facts = createFactCollector$3(additionalStateHooks, additionalEffectHooks);
8328
+ return merge(facts.visitor, { "Program:exit"() {
8329
+ for (const violation of inferViolations$2(context, facts.facts)) context.report({
8330
+ data: { name: violation.name },
8331
+ messageId: "default",
8332
+ node: violation.node
8333
+ });
8334
+ } });
8335
+ }
8336
+
8337
+ //#endregion
8338
+ //#region src/rules/set-state-in-render/helpers.ts
8339
+ function isComponentOrHookLikeFunction(node) {
8340
+ const id = core.getFunctionId(node);
8341
+ if (id == null) return false;
8342
+ if (Check.isIdentifier(id)) return core.isFunctionComponentName(id.name) || core.isHookName(id.name);
8343
+ if (id.type === AST_NODE_TYPES.MemberExpression && Check.isIdentifier(id.property)) return core.isFunctionComponentName(id.property.name) || core.isHookName(id.property.name);
8344
+ return false;
8345
+ }
8346
+ function isInsideConditional(node, stopAt) {
8347
+ let current = node.parent;
8348
+ while (current != null && current !== stopAt) {
8349
+ switch (current.type) {
8350
+ case AST_NODE_TYPES.IfStatement:
8351
+ case AST_NODE_TYPES.ConditionalExpression:
8352
+ case AST_NODE_TYPES.LogicalExpression:
8353
+ case AST_NODE_TYPES.SwitchStatement:
8354
+ case AST_NODE_TYPES.SwitchCase: return true;
8355
+ }
8356
+ current = current.parent;
8112
8357
  }
8358
+ return false;
8359
+ }
8360
+
8361
+ //#endregion
8362
+ //#region src/rules/set-state-in-render/collect.ts
8363
+ function createFactCollector$2(additionalStateHooks) {
8364
+ const facts = {
8365
+ calls: [],
8366
+ earlyReturns: []
8367
+ };
8368
+ const functionEntries = [];
8369
+ const componentFnRef = { current: null };
8113
8370
  function isUseStateCall(node) {
8114
8371
  return core.isUseStateLikeCall(node, additionalStateHooks);
8115
8372
  }
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
8373
  function getFunctionKind(node) {
8374
+ if (isComponentOrHookLikeFunction(node)) return "component";
8130
8375
  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";
8376
+ if (parent.type === AST_NODE_TYPES.CallExpression && parent.callee !== node) return "callback";
8377
+ return "other";
8378
+ }
8379
+ return {
8380
+ facts,
8381
+ visitor: {
8382
+ ":function"(node) {
8383
+ const kind = getFunctionKind(node);
8384
+ functionEntries.push({
8385
+ kind,
8386
+ node
8387
+ });
8388
+ if (kind === "component") componentFnRef.current = node;
8389
+ },
8390
+ ":function:exit"(node) {
8391
+ if (functionEntries.at(-1)?.kind === "component" && componentFnRef.current === node) componentFnRef.current = null;
8392
+ functionEntries.pop();
8393
+ },
8394
+ CallExpression(node) {
8395
+ const entry = functionEntries.at(-1);
8396
+ if (entry == null) return;
8397
+ if (isUseStateCall(node)) return;
8398
+ facts.calls.push({
8399
+ componentFunction: componentFnRef.current,
8400
+ enclosingFunction: entry.node,
8401
+ enclosingFunctionKind: entry.kind,
8402
+ node
8403
+ });
8404
+ },
8405
+ ReturnStatement(node) {
8406
+ const componentFn = componentFnRef.current;
8407
+ if (componentFn == null) return;
8408
+ const entry = functionEntries.at(-1);
8409
+ if (entry == null || entry.node !== componentFn) return;
8410
+ if (componentFn.body.type !== AST_NODE_TYPES.BlockStatement) return;
8411
+ const body = componentFn.body.body;
8412
+ let stmt = node;
8413
+ while (stmt.parent !== componentFn.body) {
8414
+ if (stmt.parent == null) return;
8415
+ stmt = stmt.parent;
8416
+ }
8417
+ const idx = body.indexOf(stmt);
8418
+ if (idx !== -1 && idx < body.length - 1) facts.earlyReturns.push({
8419
+ componentFunction: componentFn,
8420
+ node
8421
+ });
8422
+ }
8137
8423
  }
8424
+ };
8425
+ }
8426
+
8427
+ //#endregion
8428
+ //#region src/rules/set-state-in-render/origins.ts
8429
+ /**
8430
+ * Create a resolver that answers whether an identifier or call originates from
8431
+ * a `useState`-like hook's setter function (the element at index 1 of the
8432
+ * returned tuple).
8433
+ * @param context The rule context
8434
+ * @returns The setState provenance resolver
8435
+ */
8436
+ function createSetStateResolver(context) {
8437
+ const { additionalStateHooks } = getSettingsFromContext(context);
8438
+ function isUseStateCall(node) {
8439
+ return core.isUseStateLikeCall(node, additionalStateHooks);
8138
8440
  }
8139
- function isIdFromUseStateCall(id, at) {
8441
+ function isSetStateId(id, at) {
8140
8442
  const initNode = resolveOrigin(context, id);
8141
8443
  if (initNode == null) return false;
8142
8444
  if (initNode.type !== AST_NODE_TYPES.CallExpression) return false;
@@ -8156,194 +8458,53 @@ function create$5(context) {
8156
8458
  const [index] = callee.arguments;
8157
8459
  if (!isAt || index == null) return false;
8158
8460
  const indexScope = context.sourceCode.getScope(node);
8159
- return getStaticValue(index, indexScope)?.value === 1 && isIdFromUseStateCall(innerCallee.object);
8461
+ return getStaticValue(index, indexScope)?.value === 1 && isSetStateId(innerCallee.object);
8160
8462
  }
8161
- case AST_NODE_TYPES.Identifier: return isIdFromUseStateCall(callee, 1);
8463
+ case AST_NODE_TYPES.Identifier: return isSetStateId(callee, 1);
8162
8464
  case AST_NODE_TYPES.MemberExpression: {
8163
8465
  if (!("name" in callee.object)) return false;
8164
8466
  const property = callee.property;
8165
8467
  const propertyScope = context.sourceCode.getScope(node);
8166
- return getStaticValue(property, propertyScope)?.value === 1 && isIdFromUseStateCall(callee.object, 1);
8468
+ return getStaticValue(property, propertyScope)?.value === 1 && isSetStateId(callee.object, 1);
8167
8469
  }
8168
8470
  default: return false;
8169
8471
  }
8170
8472
  }
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
8473
  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
- }
8474
+ isSetStateCall,
8475
+ isSetStateId,
8476
+ isUseStateCall
8314
8477
  };
8315
8478
  }
8316
8479
 
8317
8480
  //#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;
8481
+ //#region src/rules/set-state-in-render/effects.ts
8482
+ /**
8483
+ * Infer the setState calls that run unconditionally during render from the
8484
+ * collected facts: calls made directly in a component function body that are
8485
+ * neither guarded by a conditional nor positioned after an early return.
8486
+ * @param context The rule context
8487
+ * @param facts The collected facts
8488
+ * @returns The violations to report, in report order
8489
+ */
8490
+ function inferViolations$1(context, facts) {
8491
+ const resolver = createSetStateResolver(context);
8492
+ const getText = (n) => context.sourceCode.getText(n);
8493
+ const earlyReturnPositions = /* @__PURE__ */ new Map();
8494
+ for (const { componentFunction, node } of facts.earlyReturns) getOrInsertComputed(earlyReturnPositions, componentFunction, () => []).push(node.range[0]);
8495
+ const violations = [];
8496
+ for (const { componentFunction, enclosingFunction, node } of facts.calls) {
8497
+ if (componentFunction == null) continue;
8498
+ if (enclosingFunction !== componentFunction) continue;
8499
+ if (!resolver.isSetStateCall(node)) continue;
8500
+ if (isInsideConditional(node, componentFunction)) continue;
8501
+ if (earlyReturnPositions.get(componentFunction)?.some((pos) => pos < node.range[0]) === true) continue;
8502
+ violations.push({
8503
+ name: getText(Extract.unwrap(node.callee)),
8504
+ node
8505
+ });
8338
8506
  }
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;
8507
+ return violations;
8347
8508
  }
8348
8509
 
8349
8510
  //#endregion
@@ -8362,103 +8523,19 @@ var set_state_in_render_default = createRule({
8362
8523
  });
8363
8524
  function create$4(context) {
8364
8525
  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
- };
8526
+ const facts = createFactCollector$2(additionalStateHooks);
8527
+ return merge(facts.visitor, { "Program:exit"() {
8528
+ for (const violation of inferViolations$1(context, facts.facts)) context.report({
8529
+ data: { name: violation.name },
8530
+ messageId: "default",
8531
+ node: violation.node
8532
+ });
8533
+ } });
8457
8534
  }
8458
8535
 
8459
8536
  //#endregion
8460
8537
  //#region src/rules/static-components/collect.ts
8461
- function createFactCollector() {
8538
+ function createFactCollector$1() {
8462
8539
  const facts = { componentUsages: [] };
8463
8540
  return {
8464
8541
  facts,
@@ -8606,7 +8683,7 @@ var static_components_default = createRule({
8606
8683
  docs: { description: "Validates that components are static, not recreated every render." },
8607
8684
  messages: {
8608
8685
  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."
8686
+ "created-here": "The component is created during render here."
8610
8687
  },
8611
8688
  schema: []
8612
8689
  },
@@ -8618,7 +8695,7 @@ function create$3(context) {
8618
8695
  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
8696
  const fc = core.getFunctionComponentCollector(context, { hint });
8620
8697
  const cc = core.getClassComponentCollector(context);
8621
- const facts = createFactCollector();
8698
+ const facts = createFactCollector$1();
8622
8699
  return merge(fc.visitor, cc.visitor, facts.visitor, { "Program:exit"(program) {
8623
8700
  const componentNodes = [...fc.api.getAllComponents(program), ...cc.api.getAllComponents(program)].map((component) => component.node);
8624
8701
  const isInsideRender = createRenderBoundaryChecker(componentNodes);
@@ -8633,7 +8710,7 @@ function create$3(context) {
8633
8710
  reportedCreations.add(effect.creationNode);
8634
8711
  context.report({
8635
8712
  data: { name: effect.name },
8636
- messageId: "createdHere",
8713
+ messageId: "created-here",
8637
8714
  node: effect.creationNode
8638
8715
  });
8639
8716
  }
@@ -8717,14 +8794,17 @@ function create$2(context) {
8717
8794
  }
8718
8795
 
8719
8796
  //#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;
8797
+ //#region src/rules/use-memo/helpers.ts
8798
+ /**
8799
+ * Check whether the result of a `useMemo` call is used (assigned, returned,
8800
+ * passed as an argument, etc.) rather than discarded as a bare statement.
8801
+ * @param node The `useMemo` call expression
8802
+ * @returns `true` when the call result flows into a surrounding expression
8803
+ */
8804
+ function isResultUsed(node) {
8805
+ let parent = node.parent;
8806
+ while (Check.isTypeExpression(parent)) parent = parent.parent;
8807
+ 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
8808
  }
8729
8809
  /**
8730
8810
  * Gets the nested return statements in the node that are within the same function
@@ -8733,108 +8813,168 @@ function isDeclaredInsideCallback(variable, callback) {
8733
8813
  */
8734
8814
  function getNestedReturnStatements(node) {
8735
8815
  const statements = [];
8736
- const boundaryNode = Check.isFunction(node) ? node : Traverse.findParent(node, Check.isFunction);
8816
+ const boundary = Check.isFunction(node) ? node : Traverse.findParent(node, Check.isFunction);
8737
8817
  simpleTraverse(node, { enter(node) {
8738
8818
  if (node.type !== AST_NODE_TYPES.ReturnStatement) return;
8739
- if (Traverse.findParent(node, Check.isFunction, (n) => n === boundaryNode) != null) return;
8819
+ if (Traverse.findParent(node, Check.isFunction, (n) => n === boundary) != null) return;
8740
8820
  statements.push(node);
8741
8821
  } });
8742
8822
  return statements;
8743
8823
  }
8744
8824
 
8745
8825
  //#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) {
8826
+ //#region src/rules/use-memo/collect.ts
8827
+ function createFactCollector(isUseMemoCall) {
8828
+ const facts = { calls: [] };
8829
+ function collectWrites(callback) {
8830
+ const writes = [];
8831
+ function pushWrite(node, target) {
8771
8832
  if (!Check.isIdentifier(target)) return;
8772
8833
  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
8834
+ writes.push({
8835
+ node,
8836
+ target
8779
8837
  });
8780
8838
  }
8781
8839
  simpleTraverse(callback.body, { enter(node) {
8782
8840
  switch (node.type) {
8783
8841
  case AST_NODE_TYPES.AssignmentExpression:
8784
- for (const target of Extract.getAssignmentTargets(node.left)) checkWriteTarget(node, target);
8842
+ for (const target of Extract.getAssignmentTargets(node.left)) pushWrite(node, target);
8785
8843
  return;
8786
8844
  case AST_NODE_TYPES.ForInStatement:
8787
8845
  case AST_NODE_TYPES.ForOfStatement:
8788
8846
  if (node.left.type === AST_NODE_TYPES.VariableDeclaration) return;
8789
- for (const target of Extract.getAssignmentTargets(node.left)) checkWriteTarget(node, target);
8847
+ for (const target of Extract.getAssignmentTargets(node.left)) pushWrite(node, target);
8790
8848
  return;
8791
8849
  }
8792
8850
  } });
8793
- return violations;
8851
+ return writes;
8794
8852
  }
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
8853
+ return {
8854
+ facts,
8855
+ visitor: { CallExpression(node) {
8856
+ if (!isUseMemoCall(node)) return;
8857
+ const callbackArg = node.arguments[0] ?? null;
8858
+ const callback = callbackArg != null ? Extract.unwrap(callbackArg) : null;
8859
+ const callbackFunction = callback != null && Check.isFunction(callback) ? callback : null;
8860
+ facts.calls.push({
8861
+ callback: callbackFunction,
8862
+ callbackArg,
8863
+ isResultUsed: isResultUsed(node),
8864
+ node,
8865
+ returnStatements: callbackFunction != null ? getNestedReturnStatements(callbackFunction) : [],
8866
+ writes: callbackFunction != null ? collectWrites(callbackFunction) : []
8803
8867
  });
8804
- return;
8868
+ } }
8869
+ };
8870
+ }
8871
+
8872
+ //#endregion
8873
+ //#region src/rules/use-memo/origins.ts
8874
+ function isDeclaredInsideCallback(variable, callback) {
8875
+ let scope = variable.scope;
8876
+ while (scope != null) {
8877
+ if (scope.block === callback) return true;
8878
+ scope = scope.upper;
8879
+ }
8880
+ return false;
8881
+ }
8882
+ /**
8883
+ * Resolve whether an identifier written inside a `useMemo` callback refers to a
8884
+ * variable declared outside the callback. Unresolved identifiers and variables
8885
+ * without definitions are treated as outer variables.
8886
+ * @param context The rule context
8887
+ * @param target The written identifier
8888
+ * @param callback The `useMemo` callback the write occurs in
8889
+ * @returns `true` when the write reassigns an outer variable
8890
+ */
8891
+ function isOuterVariable(context, target, callback) {
8892
+ const scope = context.sourceCode.getScope(target);
8893
+ const variable = findVariable(scope, target);
8894
+ if (variable == null || variable.defs.length === 0) return true;
8895
+ return !isDeclaredInsideCallback(variable, callback);
8896
+ }
8897
+
8898
+ //#endregion
8899
+ //#region src/rules/use-memo/effects.ts
8900
+ /**
8901
+ * Infer the `useMemo` usage violations from the collected facts: unused
8902
+ * results, callbacks with parameters, async or generator callbacks, outer
8903
+ * variable reassignments, and callbacks that never return a value.
8904
+ * @param context The rule context
8905
+ * @param facts The collected facts
8906
+ * @returns The violations to report, in report order
8907
+ */
8908
+ function inferViolations(context, facts) {
8909
+ const violations = [];
8910
+ for (const fact of facts.calls) {
8911
+ if (!fact.isResultUsed) {
8912
+ violations.push({
8913
+ kind: "result-must-be-used",
8914
+ node: fact.node
8915
+ });
8916
+ continue;
8805
8917
  }
8806
- const [callbackArg] = node.arguments;
8807
- if (callbackArg == null) return;
8808
- const callback = Extract.unwrap(callbackArg);
8809
- if (!Check.isFunction(callback)) return;
8918
+ const { callback, callbackArg } = fact;
8919
+ if (callbackArg == null || callback == null) continue;
8810
8920
  if (callback.params.length > 0) {
8811
8921
  const firstParam = callback.params[0];
8812
- if (firstParam == null) return;
8813
- context.report({
8814
- messageId: "noParameters",
8922
+ if (firstParam == null) continue;
8923
+ violations.push({
8924
+ kind: "no-parameters",
8815
8925
  node: Check.isIdentifier(firstParam) ? firstParam : callback
8816
8926
  });
8817
8927
  }
8818
- if (callback.async || callback.generator) context.report({
8819
- messageId: "noAsyncOrGeneratorFunctions",
8928
+ if (callback.async || callback.generator) violations.push({
8929
+ kind: "no-async-or-generator-functions",
8820
8930
  node: callback
8821
8931
  });
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
8932
+ for (const write of fact.writes) {
8933
+ if (!isOuterVariable(context, write.target, callback)) continue;
8934
+ violations.push({
8935
+ kind: "no-reassigning-outer-variables",
8936
+ node: write.target
8830
8937
  });
8831
- return;
8832
8938
  }
8833
- if (!returnStatements.some((stmt) => stmt.argument != null)) context.report({
8834
- messageId: "mustReturnAValue",
8939
+ if (callback.type === AST_NODE_TYPES.ArrowFunctionExpression && callback.body.type !== AST_NODE_TYPES.BlockStatement) continue;
8940
+ if (callback.body.type !== AST_NODE_TYPES.BlockStatement) continue;
8941
+ if (!fact.returnStatements.some((stmt) => stmt.argument != null)) violations.push({
8942
+ kind: "must-return-a-value",
8835
8943
  node: callbackArg
8836
8944
  });
8837
- } };
8945
+ }
8946
+ return violations;
8947
+ }
8948
+
8949
+ //#endregion
8950
+ //#region src/rules/use-memo/use-memo.ts
8951
+ const RULE_NAME$1 = "use-memo";
8952
+ var use_memo_default = createRule({
8953
+ meta: {
8954
+ type: "problem",
8955
+ docs: { description: "Validates that 'useMemo' is called with a callback that returns a value." },
8956
+ messages: {
8957
+ "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.",
8958
+ "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.",
8959
+ "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.",
8960
+ "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.",
8961
+ "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."
8962
+ },
8963
+ schema: []
8964
+ },
8965
+ name: RULE_NAME$1,
8966
+ create: create$1,
8967
+ defaultOptions: []
8968
+ });
8969
+ function create$1(context) {
8970
+ if (!context.sourceCode.text.includes("useMemo")) return {};
8971
+ const facts = createFactCollector(core.isUseMemoCall(context));
8972
+ return merge(facts.visitor, { "Program:exit"() {
8973
+ for (const violation of inferViolations(context, facts.facts)) context.report({
8974
+ messageId: violation.kind,
8975
+ node: violation.node
8976
+ });
8977
+ } });
8838
8978
  }
8839
8979
 
8840
8980
  //#endregion
@@ -8844,6 +8984,59 @@ const LAZY_INIT_ALLOW_LIST = [
8844
8984
  "String",
8845
8985
  "Number"
8846
8986
  ];
8987
+ const EXPRESSION_VISITOR_KEYS = {
8988
+ [AST_NODE_TYPES.ArrayExpression]: ["elements"],
8989
+ [AST_NODE_TYPES.ArrayPattern]: ["elements"],
8990
+ [AST_NODE_TYPES.AssignmentExpression]: ["left", "right"],
8991
+ [AST_NODE_TYPES.AssignmentPattern]: ["left", "right"],
8992
+ [AST_NODE_TYPES.AwaitExpression]: ["argument"],
8993
+ [AST_NODE_TYPES.BinaryExpression]: ["left", "right"],
8994
+ [AST_NODE_TYPES.CallExpression]: ["arguments", "callee"],
8995
+ [AST_NODE_TYPES.ChainExpression]: ["expression"],
8996
+ [AST_NODE_TYPES.ConditionalExpression]: [
8997
+ "test",
8998
+ "consequent",
8999
+ "alternate"
9000
+ ],
9001
+ [AST_NODE_TYPES.Decorator]: ["expression"],
9002
+ [AST_NODE_TYPES.DoWhileStatement]: ["test"],
9003
+ [AST_NODE_TYPES.ExpressionStatement]: ["expression"],
9004
+ [AST_NODE_TYPES.ForInStatement]: ["left", "right"],
9005
+ [AST_NODE_TYPES.ForOfStatement]: ["left", "right"],
9006
+ [AST_NODE_TYPES.ForStatement]: ["test"],
9007
+ [AST_NODE_TYPES.IfStatement]: [
9008
+ "test",
9009
+ "consequent",
9010
+ "alternate"
9011
+ ],
9012
+ [AST_NODE_TYPES.ImportExpression]: ["source"],
9013
+ [AST_NODE_TYPES.JSXExpressionContainer]: ["expression"],
9014
+ [AST_NODE_TYPES.JSXSpreadChild]: ["expression"],
9015
+ [AST_NODE_TYPES.LogicalExpression]: ["left", "right"],
9016
+ [AST_NODE_TYPES.MemberExpression]: ["object", "property"],
9017
+ [AST_NODE_TYPES.NewExpression]: ["arguments", "callee"],
9018
+ [AST_NODE_TYPES.ObjectExpression]: ["properties"],
9019
+ [AST_NODE_TYPES.ObjectPattern]: ["properties"],
9020
+ [AST_NODE_TYPES.Property]: ["value"],
9021
+ [AST_NODE_TYPES.SequenceExpression]: ["expressions"],
9022
+ [AST_NODE_TYPES.SpreadElement]: ["argument"],
9023
+ [AST_NODE_TYPES.SwitchCase]: ["test", "consequent"],
9024
+ [AST_NODE_TYPES.TaggedTemplateExpression]: ["tag", "quasi"],
9025
+ [AST_NODE_TYPES.TemplateLiteral]: ["expressions"],
9026
+ [AST_NODE_TYPES.TSAsExpression]: ["expression"],
9027
+ [AST_NODE_TYPES.TSClassImplements]: ["expression"],
9028
+ [AST_NODE_TYPES.TSExportAssignment]: ["expression"],
9029
+ [AST_NODE_TYPES.TSExternalModuleReference]: ["expression"],
9030
+ [AST_NODE_TYPES.TSInstantiationExpression]: ["expression"],
9031
+ [AST_NODE_TYPES.TSInterfaceHeritage]: ["expression"],
9032
+ [AST_NODE_TYPES.TSNonNullExpression]: ["expression"],
9033
+ [AST_NODE_TYPES.TSSatisfiesExpression]: ["expression"],
9034
+ [AST_NODE_TYPES.TSTypeAssertion]: ["expression"],
9035
+ [AST_NODE_TYPES.UnaryExpression]: ["argument"],
9036
+ [AST_NODE_TYPES.UpdateExpression]: ["argument"],
9037
+ [AST_NODE_TYPES.WhileStatement]: ["test"],
9038
+ [AST_NODE_TYPES.YieldExpression]: ["argument"]
9039
+ };
8847
9040
  /**
8848
9041
  * Get all nested expressions of type T in an expression like node
8849
9042
  * @param type The type of the expression to retrieve within the node
@@ -8854,72 +9047,13 @@ function getNestedExpressionsOfType(type) {
8854
9047
  const isNodeOfType = Check.is(type);
8855
9048
  const recurse = (node) => {
8856
9049
  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));
9050
+ simpleTraverse(node, {
9051
+ enter(node, parent) {
9052
+ if (parent?.type === AST_NODE_TYPES.MemberExpression && !parent.computed && parent.property === node) return;
9053
+ if (isNodeOfType(node)) expressions.push(node);
9054
+ },
9055
+ visitorKeys: EXPRESSION_VISITOR_KEYS
9056
+ });
8923
9057
  return expressions;
8924
9058
  };
8925
9059
  return recurse;
@@ -8968,9 +9102,9 @@ var use_state_default = createRule({
8968
9102
  type: "suggestion",
8969
9103
  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
9104
  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'."
9105
+ "invalid-assignment": "useState should be destructured into a value and setter pair, e.g., const [state, setState] = useState(...).",
9106
+ "invalid-initialization": "To prevent re-computation, consider using lazy initial state for useState calls that involve function calls. Ex: 'useState(() => getValue())'.",
9107
+ "invalid-setter-name": "The setter should be named 'set' followed by the capitalized state variable name, e.g., 'setState' for 'state'."
8974
9108
  },
8975
9109
  schema
8976
9110
  },
@@ -8991,7 +9125,7 @@ function create(context) {
8991
9125
  if (LAZY_INIT_ALLOW_LIST.includes(expr.callee.name)) continue;
8992
9126
  if (Traverse.findParent(expr, (n) => core.isUseCall(context, n)) != null) continue;
8993
9127
  context.report({
8994
- messageId: "invalidInitialization",
9128
+ messageId: "invalid-initialization",
8995
9129
  node: expr
8996
9130
  });
8997
9131
  }
@@ -9001,7 +9135,7 @@ function create(context) {
9001
9135
  if (LAZY_INIT_ALLOW_LIST.includes(expr.callee.name)) continue;
9002
9136
  if (Traverse.findParent(expr, (n) => core.isUseCall(context, n)) != null) continue;
9003
9137
  context.report({
9004
- messageId: "invalidInitialization",
9138
+ messageId: "invalid-initialization",
9005
9139
  node: expr
9006
9140
  });
9007
9141
  }
@@ -9014,7 +9148,7 @@ function create(context) {
9014
9148
  if (parent.type !== AST_NODE_TYPES.VariableDeclarator) {
9015
9149
  if (!enforceAssignment) return;
9016
9150
  context.report({
9017
- messageId: "invalidAssignment",
9151
+ messageId: "invalid-assignment",
9018
9152
  node
9019
9153
  });
9020
9154
  return;
@@ -9023,7 +9157,7 @@ function create(context) {
9023
9157
  if (id?.type !== AST_NODE_TYPES.ArrayPattern) {
9024
9158
  if (!enforceAssignment) return;
9025
9159
  context.report({
9026
- messageId: "invalidAssignment",
9160
+ messageId: "invalid-assignment",
9027
9161
  node: id ?? node
9028
9162
  });
9029
9163
  return;
@@ -9032,7 +9166,7 @@ function create(context) {
9032
9166
  if (value == null) {
9033
9167
  if (!enforceAssignment) return;
9034
9168
  context.report({
9035
- messageId: "invalidAssignment",
9169
+ messageId: "invalid-assignment",
9036
9170
  node: id
9037
9171
  });
9038
9172
  return;
@@ -9040,20 +9174,20 @@ function create(context) {
9040
9174
  if (setter == null || !enforceSetterName) return;
9041
9175
  if (value.type !== AST_NODE_TYPES.Identifier) {
9042
9176
  context.report({
9043
- messageId: "invalidAssignment",
9177
+ messageId: "invalid-assignment",
9044
9178
  node: value
9045
9179
  });
9046
9180
  return;
9047
9181
  }
9048
9182
  if (setter.type !== AST_NODE_TYPES.Identifier || !setter.name.startsWith("set")) {
9049
9183
  context.report({
9050
- messageId: "invalidSetterName",
9184
+ messageId: "invalid-setter-name",
9051
9185
  node: setter
9052
9186
  });
9053
9187
  return;
9054
9188
  }
9055
9189
  if (snakeCase(setter.name) !== `set_${snakeCase(value.name)}`) context.report({
9056
- messageId: "invalidSetterName",
9190
+ messageId: "invalid-setter-name",
9057
9191
  node: setter
9058
9192
  });
9059
9193
  } };