oxlint-plugin-react-doctor 0.2.11-dev.d917f62 → 0.2.11-dev.e7a998a

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 (3) hide show
  1. package/dist/index.d.ts +340 -0
  2. package/dist/index.js +1709 -394
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ import path from "node:path";
2
2
  import { analyze } from "eslint-scope";
3
3
  import * as eslintVisitorKeys from "eslint-visitor-keys";
4
4
  import fs from "node:fs";
5
+ import { parseSync } from "oxc-parser";
5
6
  //#region src/plugin/utils/is-testlike-filename.ts
6
7
  const NON_PRODUCTION_PATH_SEGMENTS = [
7
8
  "/test/",
@@ -444,6 +445,13 @@ const REACT_HOC_NAMES = new Set([
444
445
  "React.memo",
445
446
  "React.forwardRef"
446
447
  ]);
448
+ const MEMOIZING_HOOK_NAMES = new Set(["useMemo", "useCallback"]);
449
+ const COMPONENT_HOC_WRAPPER_NAMES = new Set([
450
+ "memo",
451
+ "forwardRef",
452
+ "observer",
453
+ "lazy"
454
+ ]);
447
455
  const SUBSCRIPTION_METHOD_NAMES = new Set([
448
456
  "subscribe",
449
457
  "addEventListener",
@@ -1026,7 +1034,7 @@ const altText = defineRule({
1026
1034
  });
1027
1035
  //#endregion
1028
1036
  //#region src/plugin/rules/a11y/anchor-ambiguous-text.ts
1029
- const buildMessage$29 = (text) => `\`${text}\` is ambiguous link text — describe the destination instead (e.g. "View pricing details").`;
1037
+ const buildMessage$30 = (text) => `\`${text}\` is ambiguous link text — describe the destination instead (e.g. "View pricing details").`;
1030
1038
  const DEFAULT_AMBIGUOUS = [
1031
1039
  "click here",
1032
1040
  "here",
@@ -1083,14 +1091,14 @@ const anchorAmbiguousText = defineRule({
1083
1091
  const normalized = normalizeText(accessibleText);
1084
1092
  if (ambiguousSet.has(normalized)) context.report({
1085
1093
  node: node.openingElement.name,
1086
- message: buildMessage$29(normalized)
1094
+ message: buildMessage$30(normalized)
1087
1095
  });
1088
1096
  } };
1089
1097
  }
1090
1098
  });
1091
1099
  //#endregion
1092
1100
  //#region src/plugin/rules/a11y/anchor-has-content.ts
1093
- const MESSAGE$47 = "Anchor must have accessible content — provide visible text, `aria-label`, or `aria-labelledby`.";
1101
+ const MESSAGE$49 = "Anchor must have accessible content — provide visible text, `aria-label`, or `aria-labelledby`.";
1094
1102
  const anchorHasContent = defineRule({
1095
1103
  id: "anchor-has-content",
1096
1104
  tags: ["react-jsx-only"],
@@ -1105,7 +1113,7 @@ const anchorHasContent = defineRule({
1105
1113
  for (const attribute of ["title", "aria-label"]) if (hasJsxPropIgnoreCase(opening.attributes, attribute)) return;
1106
1114
  context.report({
1107
1115
  node: opening.name,
1108
- message: MESSAGE$47
1116
+ message: MESSAGE$49
1109
1117
  });
1110
1118
  } })
1111
1119
  });
@@ -1498,7 +1506,7 @@ const parseJsxValue = (value) => {
1498
1506
  };
1499
1507
  //#endregion
1500
1508
  //#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
1501
- const MESSAGE$46 = "An element with `aria-activedescendant` must be tabbable — add `tabIndex={0}` so it can receive focus.";
1509
+ const MESSAGE$48 = "An element with `aria-activedescendant` must be tabbable — add `tabIndex={0}` so it can receive focus.";
1502
1510
  const ariaActivedescendantHasTabindex = defineRule({
1503
1511
  id: "aria-activedescendant-has-tabindex",
1504
1512
  tags: ["react-jsx-only"],
@@ -1515,14 +1523,14 @@ const ariaActivedescendantHasTabindex = defineRule({
1515
1523
  if (tabIndexValue === null || tabIndexValue >= -1) return;
1516
1524
  context.report({
1517
1525
  node: node.name,
1518
- message: MESSAGE$46
1526
+ message: MESSAGE$48
1519
1527
  });
1520
1528
  return;
1521
1529
  }
1522
1530
  if (isInteractiveElement(tag, node)) return;
1523
1531
  context.report({
1524
1532
  node: node.name,
1525
- message: MESSAGE$46
1533
+ message: MESSAGE$48
1526
1534
  });
1527
1535
  } })
1528
1536
  });
@@ -1662,7 +1670,7 @@ const ARIA_PROPERTIES = new Map([
1662
1670
  const isValidAriaProperty = (name) => ARIA_PROPERTIES.has(name);
1663
1671
  //#endregion
1664
1672
  //#region src/plugin/rules/a11y/aria-props.ts
1665
- const buildMessage$28 = (name) => `\`${name}\` is not a valid ARIA property — check WAI-ARIA spec.`;
1673
+ const buildMessage$29 = (name) => `\`${name}\` is not a valid ARIA property — check WAI-ARIA spec.`;
1666
1674
  const ariaProps = defineRule({
1667
1675
  id: "aria-props",
1668
1676
  tags: ["react-jsx-only"],
@@ -1675,7 +1683,7 @@ const ariaProps = defineRule({
1675
1683
  if (!name || !name.startsWith("aria-")) return;
1676
1684
  if (!isValidAriaProperty(name)) context.report({
1677
1685
  node: node.name,
1678
- message: buildMessage$28(name)
1686
+ message: buildMessage$29(name)
1679
1687
  });
1680
1688
  } })
1681
1689
  });
@@ -1826,7 +1834,7 @@ const buildExpectedDescription = (propType) => {
1826
1834
  case "token-list": return `a space-separated list of: ${propType.tokens.join(", ")}`;
1827
1835
  }
1828
1836
  };
1829
- const buildMessage$27 = (propName, propType) => `\`${propName}\` value must be ${buildExpectedDescription(propType)}.`;
1837
+ const buildMessage$28 = (propName, propType) => `\`${propName}\` value must be ${buildExpectedDescription(propType)}.`;
1830
1838
  const allowNoneValue = (propType) => {
1831
1839
  switch (propType.kind) {
1832
1840
  case "boolean":
@@ -1959,13 +1967,13 @@ const ariaProptypes = defineRule({
1959
1967
  if (!node.value) {
1960
1968
  if (!allowNoneValue(propType)) context.report({
1961
1969
  node,
1962
- message: buildMessage$27(propName, propType)
1970
+ message: buildMessage$28(propName, propType)
1963
1971
  });
1964
1972
  return;
1965
1973
  }
1966
1974
  if (!isValidValueForType(propType, node.value)) context.report({
1967
1975
  node,
1968
- message: buildMessage$27(propName, propType)
1976
+ message: buildMessage$28(propName, propType)
1969
1977
  });
1970
1978
  } })
1971
1979
  });
@@ -2277,7 +2285,7 @@ const ariaRole = defineRule({
2277
2285
  });
2278
2286
  //#endregion
2279
2287
  //#region src/plugin/rules/a11y/aria-unsupported-elements.ts
2280
- const buildMessage$26 = (tag, attribute) => `\`<${tag}>\` does not support \`${attribute}\` — reserved HTML elements don't accept ARIA attributes.`;
2288
+ const buildMessage$27 = (tag, attribute) => `\`<${tag}>\` does not support \`${attribute}\` — reserved HTML elements don't accept ARIA attributes.`;
2281
2289
  const ariaUnsupportedElements = defineRule({
2282
2290
  id: "aria-unsupported-elements",
2283
2291
  tags: ["react-jsx-only"],
@@ -2294,7 +2302,7 @@ const ariaUnsupportedElements = defineRule({
2294
2302
  if (!attrName) continue;
2295
2303
  if (attrName.startsWith("aria-") || attrName === "role") context.report({
2296
2304
  node: attribute,
2297
- message: buildMessage$26(tag, attrName)
2305
+ message: buildMessage$27(tag, attrName)
2298
2306
  });
2299
2307
  }
2300
2308
  } })
@@ -2327,7 +2335,7 @@ const BUILTIN_GLOBAL_NAMESPACE_NAMES = new Set([
2327
2335
  "BigInt",
2328
2336
  "Reflect"
2329
2337
  ]);
2330
- const MUTATING_ARRAY_METHODS$1 = new Set([
2338
+ const MUTATING_ARRAY_METHODS = new Set([
2331
2339
  "push",
2332
2340
  "pop",
2333
2341
  "shift",
@@ -2338,6 +2346,12 @@ const MUTATING_ARRAY_METHODS$1 = new Set([
2338
2346
  "fill",
2339
2347
  "copyWithin"
2340
2348
  ]);
2349
+ const MUTATING_COLLECTION_METHODS = new Set([
2350
+ "add",
2351
+ "clear",
2352
+ "delete",
2353
+ "set"
2354
+ ]);
2341
2355
  const CHAINABLE_ITERATION_METHODS = new Set([
2342
2356
  "map",
2343
2357
  "filter",
@@ -2549,7 +2563,7 @@ const INTENTIONAL_SEQUENCING_CALLEE_NAMES = new Set([
2549
2563
  * (`FUNCTION_LIKE_TYPES.has(node.type)`) and as a type-guard. The
2550
2564
  * type-guard form covers both shapes without callers paying a cast.
2551
2565
  */
2552
- const isFunctionLike$1 = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")));
2566
+ const isFunctionLike$2 = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")));
2553
2567
  //#endregion
2554
2568
  //#region src/plugin/utils/is-inline-function-expression.ts
2555
2569
  /**
@@ -2572,7 +2586,7 @@ const findFirstAwaitOutsideNestedFunctions = (block) => {
2572
2586
  let firstAwait = null;
2573
2587
  walkAst(block, (child) => {
2574
2588
  if (firstAwait) return false;
2575
- if (child !== block && isFunctionLike$1(child)) return false;
2589
+ if (child !== block && isFunctionLike$2(child)) return false;
2576
2590
  if (isNodeOfType(child, "AwaitExpression")) firstAwait = child;
2577
2591
  });
2578
2592
  return firstAwait;
@@ -3027,13 +3041,13 @@ const asyncDeferAwait = defineRule({
3027
3041
  const inspectAllStatementBlocks = (functionBody) => {
3028
3042
  if (!functionBody) return;
3029
3043
  walkAst(functionBody, (descendant) => {
3030
- if (isFunctionLike$1(descendant)) return false;
3044
+ if (isFunctionLike$2(descendant)) return false;
3031
3045
  if (isNodeOfType(descendant, "BlockStatement")) inspectStatements(descendant.body ?? []);
3032
3046
  else if (isNodeOfType(descendant, "SwitchCase")) inspectStatements(descendant.consequent ?? []);
3033
3047
  });
3034
3048
  };
3035
3049
  const enterFunction = (node) => {
3036
- if (!isFunctionLike$1(node)) return;
3050
+ if (!isFunctionLike$2(node)) return;
3037
3051
  if (!node.async) return;
3038
3052
  if (!isNodeOfType(node.body, "BlockStatement")) return;
3039
3053
  inspectAllStatementBlocks(node.body);
@@ -3164,7 +3178,7 @@ const asyncParallel = defineRule({
3164
3178
  });
3165
3179
  //#endregion
3166
3180
  //#region src/plugin/rules/a11y/autocomplete-valid.ts
3167
- const buildMessage$25 = (value) => `\`autoComplete\` value \`${value}\` is not a known HTML autofill token.`;
3181
+ const buildMessage$26 = (value) => `\`autoComplete\` value \`${value}\` is not a known HTML autofill token.`;
3168
3182
  const AUTOFILL_TOKENS = new Set([
3169
3183
  "off",
3170
3184
  "on",
@@ -3252,7 +3266,7 @@ const autocompleteValid = defineRule({
3252
3266
  if (!AUTOFILL_TOKENS.has(token)) {
3253
3267
  context.report({
3254
3268
  node: attribute,
3255
- message: buildMessage$25(value)
3269
+ message: buildMessage$26(value)
3256
3270
  });
3257
3271
  return;
3258
3272
  }
@@ -3527,7 +3541,7 @@ const isPureEventBlockerHandler = (attribute) => {
3527
3541
  //#endregion
3528
3542
  //#region src/plugin/rules/a11y/click-events-have-key-events.ts
3529
3543
  const PRESENTATION_ROLES$1 = new Set(["presentation", "none"]);
3530
- const MESSAGE$45 = "Visible non-interactive elements with click handlers must have a corresponding keyboard listener (`onKeyUp`, `onKeyDown`, or `onKeyPress`).";
3544
+ const MESSAGE$47 = "Visible non-interactive elements with click handlers must have a corresponding keyboard listener (`onKeyUp`, `onKeyDown`, or `onKeyPress`).";
3531
3545
  const KEY_HANDLERS = [
3532
3546
  "onKeyUp",
3533
3547
  "onKeyDown",
@@ -3558,7 +3572,7 @@ const clickEventsHaveKeyEvents = defineRule({
3558
3572
  if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
3559
3573
  context.report({
3560
3574
  node: node.name,
3561
- message: MESSAGE$45
3575
+ message: MESSAGE$47
3562
3576
  });
3563
3577
  } };
3564
3578
  }
@@ -3669,7 +3683,7 @@ const stripParenExpression = (node) => {
3669
3683
  };
3670
3684
  //#endregion
3671
3685
  //#region src/plugin/rules/a11y/control-has-associated-label.ts
3672
- const MESSAGE$44 = "A control must be associated with a text label — add visible text, `aria-label`, or `aria-labelledby`.";
3686
+ const MESSAGE$46 = "A control must be associated with a text label — add visible text, `aria-label`, or `aria-labelledby`.";
3673
3687
  const DEFAULT_IGNORE_ELEMENTS = ["link", "canvas"];
3674
3688
  const DEFAULT_LABELLING_PROPS = [
3675
3689
  "alt",
@@ -3829,7 +3843,7 @@ const controlHasAssociatedLabel = defineRule({
3829
3843
  for (const child of node.children) if (checkChildForLabel(child, 1, checkContext)) return;
3830
3844
  context.report({
3831
3845
  node: opening,
3832
- message: MESSAGE$44
3846
+ message: MESSAGE$46
3833
3847
  });
3834
3848
  } };
3835
3849
  }
@@ -4153,14 +4167,21 @@ const isEs6Component = (node) => {
4153
4167
  };
4154
4168
  //#endregion
4155
4169
  //#region src/plugin/rules/react-builtins/display-name.ts
4156
- const MESSAGE$43 = "Component is missing a `displayName` — assign one for easier debugging.";
4170
+ const MESSAGE$45 = "Component is missing a `displayName` — assign one for easier debugging.";
4171
+ const DEFAULT_ADDITIONAL_HOCS = [
4172
+ "observer",
4173
+ "lazy",
4174
+ "withTracking"
4175
+ ];
4157
4176
  const resolveSettings$45 = (settings) => {
4158
4177
  const reactDoctor = settings?.["react-doctor"];
4159
4178
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.displayName ?? {} : {};
4179
+ const additionalHoCs = new Set(ruleSettings.additionalHoCs ?? DEFAULT_ADDITIONAL_HOCS);
4160
4180
  return {
4161
4181
  ignoreTranspilerName: ruleSettings.ignoreTranspilerName ?? false,
4162
4182
  checkContextObjects: ruleSettings.checkContextObjects ?? false,
4163
- reactVersion: ruleSettings.reactVersion ?? ""
4183
+ reactVersion: ruleSettings.reactVersion ?? "",
4184
+ additionalHoCs
4164
4185
  };
4165
4186
  };
4166
4187
  const isReactVersionAtLeast$1 = (version, major, minor) => {
@@ -4233,29 +4254,28 @@ const isCreateContextCall = (node) => {
4233
4254
  if (isNodeOfType(callee, "Identifier")) return callee.name === "createContext";
4234
4255
  return isNodeOfType(callee, "MemberExpression") && getStaticMemberName(callee) === "createContext";
4235
4256
  };
4236
- const isObserverCall = (node) => {
4237
- if (!isNodeOfType(node, "CallExpression")) return false;
4238
- const callee = node.callee;
4239
- if (isNodeOfType(callee, "Identifier")) return callee.name === "observer";
4240
- return isNodeOfType(callee, "MemberExpression") && getStaticMemberName(callee) === "observer";
4241
- };
4242
- const getCallName = (node) => {
4243
- if (!isNodeOfType(node, "CallExpression")) return null;
4244
- const callee = node.callee;
4245
- if (isNodeOfType(callee, "Identifier")) return callee.name;
4246
- if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName(callee);
4247
- return null;
4248
- };
4249
4257
  const isNamedFunctionLike = (node) => (isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && Boolean(node.id?.name);
4250
4258
  const firstCallArgument = (node) => {
4251
4259
  if (!isNodeOfType(node, "CallExpression")) return null;
4252
4260
  const first = node.arguments[0];
4253
4261
  return first ? first : null;
4254
4262
  };
4255
- const isDisplayNameHoC = (node) => {
4256
- const callName = getCallName(node);
4257
- return callName === "memo" || callName === "forwardRef";
4263
+ const resolveHoCCalleeName = (node, additionalHoCs) => {
4264
+ if (!isNodeOfType(node, "CallExpression")) return null;
4265
+ const callee = node.callee;
4266
+ if (isNodeOfType(callee, "Identifier")) {
4267
+ if (callee.name === "memo" || callee.name === "forwardRef") return callee.name;
4268
+ if (additionalHoCs.has(callee.name)) return callee.name;
4269
+ return null;
4270
+ }
4271
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) {
4272
+ const propertyName = callee.property.name;
4273
+ if (propertyName === "memo" || propertyName === "forwardRef") return propertyName;
4274
+ if (additionalHoCs.has(propertyName)) return propertyName;
4275
+ }
4276
+ return null;
4258
4277
  };
4278
+ const isDisplayNameHoC = (node, additionalHoCs) => resolveHoCCalleeName(node, additionalHoCs) !== null;
4259
4279
  const supportsComposedForwardRefDisplayName = (version) => {
4260
4280
  if (!version) return false;
4261
4281
  if (isReactVersionAtLeast$1(version, 15, 7)) return true;
@@ -4263,17 +4283,17 @@ const supportsComposedForwardRefDisplayName = (version) => {
4263
4283
  return Boolean(match && Number(match[1]) >= 11);
4264
4284
  };
4265
4285
  const shouldReportHoCDisplayName = (node, settings) => {
4266
- if (!isDisplayNameHoC(node)) return false;
4286
+ if (!isDisplayNameHoC(node, settings.additionalHoCs)) return false;
4267
4287
  if (!containsJsx$1(node)) return false;
4268
4288
  const assignedName = getAssignedName(node);
4269
4289
  const programRoot = findProgramRoot(node);
4270
4290
  if (assignedName && programRoot && hasDisplayNameAssignment(assignedName, programRoot)) return false;
4271
- const callName = getCallName(node);
4291
+ const callName = resolveHoCCalleeName(node, settings.additionalHoCs);
4272
4292
  const firstArgument = firstCallArgument(node);
4273
4293
  if (!firstArgument) return false;
4274
- if (callName === "forwardRef" && isNodeOfType(node.parent, "CallExpression") && getCallName(node.parent) === "memo" && firstCallArgument(node.parent) === node && supportsComposedForwardRefDisplayName(settings.reactVersion)) return false;
4294
+ if (callName === "forwardRef" && isNodeOfType(node.parent, "CallExpression") && resolveHoCCalleeName(node.parent, settings.additionalHoCs) === "memo" && firstCallArgument(node.parent) === node && supportsComposedForwardRefDisplayName(settings.reactVersion)) return false;
4275
4295
  if (callName === "memo" && isNodeOfType(firstArgument, "CallExpression")) {
4276
- if (getCallName(firstArgument) !== "forwardRef") return false;
4296
+ if (resolveHoCCalleeName(firstArgument, settings.additionalHoCs) !== "forwardRef") return false;
4277
4297
  return !supportsComposedForwardRefDisplayName(settings.reactVersion);
4278
4298
  }
4279
4299
  if (isNamedFunctionLike(firstArgument)) return false;
@@ -4349,7 +4369,7 @@ const displayName = defineRule({
4349
4369
  const reportAt = (node) => {
4350
4370
  context.report({
4351
4371
  node,
4352
- message: MESSAGE$43
4372
+ message: MESSAGE$45
4353
4373
  });
4354
4374
  };
4355
4375
  return {
@@ -4427,10 +4447,6 @@ const displayName = defineRule({
4427
4447
  reportAt(node);
4428
4448
  return;
4429
4449
  }
4430
- if (isObserverCall(node) && containsJsx$1(node)) {
4431
- reportAt(node);
4432
- return;
4433
- }
4434
4450
  if (shouldReportHoCDisplayName(node, settings)) {
4435
4451
  reportAt(node);
4436
4452
  return;
@@ -4472,7 +4488,7 @@ const displayName = defineRule({
4472
4488
  //#region src/plugin/utils/walk-inside-statement-blocks.ts
4473
4489
  const walkInsideStatementBlocks = (node, visitor) => {
4474
4490
  if (!node || typeof node !== "object") return;
4475
- if (isFunctionLike$1(node)) return;
4491
+ if (isFunctionLike$2(node)) return;
4476
4492
  visitor(node);
4477
4493
  const nodeRecord = node;
4478
4494
  for (const key of Object.keys(nodeRecord)) {
@@ -4560,7 +4576,7 @@ const containsReleaseLikeCall = (node, knownCleanupFunctionNames, knownBoundSubs
4560
4576
  let didFindRelease = false;
4561
4577
  walkAst(node, (child) => {
4562
4578
  if (didFindRelease) return false;
4563
- if (child !== node && isFunctionLike$1(child) && !isIteratorCallbackArgument(child)) return false;
4579
+ if (child !== node && isFunctionLike$2(child) && !isIteratorCallbackArgument(child)) return false;
4564
4580
  if (isReleaseLikeCall(child, knownCleanupFunctionNames, knownBoundSubscriptionNames)) {
4565
4581
  didFindRelease = true;
4566
4582
  return false;
@@ -4569,7 +4585,7 @@ const containsReleaseLikeCall = (node, knownCleanupFunctionNames, knownBoundSubs
4569
4585
  return didFindRelease;
4570
4586
  };
4571
4587
  const isCleanupFunctionLike = (node, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
4572
- if (!isFunctionLike$1(node)) return false;
4588
+ if (!isFunctionLike$2(node)) return false;
4573
4589
  return containsReleaseLikeCall(node.body, knownCleanupFunctionNames, knownBoundSubscriptionNames);
4574
4590
  };
4575
4591
  const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
@@ -4811,7 +4827,7 @@ const recordReference = (state, identifier, flag) => {
4811
4827
  };
4812
4828
  const isFunctionBodyBlock = (block) => {
4813
4829
  if (!block.parent) return false;
4814
- return isFunctionLike$1(block.parent);
4830
+ return isFunctionLike$2(block.parent);
4815
4831
  };
4816
4832
  const isCatchClauseBlock = (block) => block.parent !== null && block.parent !== void 0 && block.parent.type === "CatchClause";
4817
4833
  const handleVariableDeclaration = (declaration, state) => {
@@ -4966,7 +4982,7 @@ const walkParameterReferences = (pattern, state) => {
4966
4982
  if (isNodeOfType(pattern, "RestElement")) walkParameterReferences(pattern.argument, state);
4967
4983
  };
4968
4984
  const walk = (node, state) => {
4969
- if (isFunctionLike$1(node)) {
4985
+ if (isFunctionLike$2(node)) {
4970
4986
  if (isNodeOfType(node, "FunctionDeclaration") && node.id) handleFunctionDeclaration(node, state);
4971
4987
  setNodeScope(node, state);
4972
4988
  const fnScope = pushScope(node.type === "ArrowFunctionExpression" ? "arrow-function" : "function", node, state);
@@ -5212,7 +5228,7 @@ const closureCaptures = (functionNode, scopes) => {
5212
5228
  const out = [];
5213
5229
  const seen = /* @__PURE__ */ new Set();
5214
5230
  const visit = (node) => {
5215
- if (node !== functionNode && isFunctionLike$1(node)) {
5231
+ if (node !== functionNode && isFunctionLike$2(node)) {
5216
5232
  const innerCaptures = closureCaptures(node, scopes);
5217
5233
  for (const reference of innerCaptures) if (reference.resolvedSymbol && !isDescendantScope(reference.resolvedSymbol.scope, functionScope)) {
5218
5234
  if (!seen.has(reference.id)) {
@@ -5284,7 +5300,7 @@ const TRANSPARENT_WRAPPER_TYPES = new Set([
5284
5300
  "ParenthesizedExpression",
5285
5301
  "ChainExpression"
5286
5302
  ]);
5287
- const unwrapExpression = (node) => {
5303
+ const unwrapExpression$1 = (node) => {
5288
5304
  let current = node;
5289
5305
  while (TRANSPARENT_WRAPPER_TYPES.has(current.type)) {
5290
5306
  const inner = current.expression;
@@ -5391,7 +5407,7 @@ const symbolHasStableHookOrigin = (symbol) => {
5391
5407
  if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return false;
5392
5408
  const initializerRaw = declarator.init;
5393
5409
  if (!initializerRaw) return false;
5394
- const initializer = unwrapExpression(initializerRaw);
5410
+ const initializer = unwrapExpression$1(initializerRaw);
5395
5411
  if (symbol.kind === "const") {
5396
5412
  if (isNodeOfType(initializer, "Literal") && (initializer.value === null || typeof initializer.value === "number" || typeof initializer.value === "string" || typeof initializer.value === "boolean")) return true;
5397
5413
  if (isNodeOfType(initializer, "TemplateLiteral") && getStaticTemplateLiteralValue(initializer) !== null) return true;
@@ -5411,13 +5427,13 @@ const symbolHasStableHookOrigin = (symbol) => {
5411
5427
  return false;
5412
5428
  };
5413
5429
  const symbolHasUseEffectEventOrigin = (symbol) => {
5414
- const initializer = symbol.initializer ? unwrapExpression(symbol.initializer) : null;
5430
+ const initializer = symbol.initializer ? unwrapExpression$1(symbol.initializer) : null;
5415
5431
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
5416
5432
  return getHookName(initializer.callee) === "useEffectEvent";
5417
5433
  };
5418
5434
  const getFunctionValueNode = (symbol) => {
5419
5435
  if (symbol.kind === "function" && isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return symbol.declarationNode;
5420
- const initializer = symbol.initializer ? unwrapExpression(symbol.initializer) : null;
5436
+ const initializer = symbol.initializer ? unwrapExpression$1(symbol.initializer) : null;
5421
5437
  if (initializer && (isNodeOfType(initializer, "FunctionExpression") || isNodeOfType(initializer, "ArrowFunctionExpression"))) return initializer;
5422
5438
  return null;
5423
5439
  };
@@ -5552,23 +5568,23 @@ const computeDepKey = (reference) => {
5552
5568
  return fullName;
5553
5569
  };
5554
5570
  const computeDeclaredDepKey = (entry) => {
5555
- const stripped = unwrapExpression(entry);
5571
+ const stripped = unwrapExpression$1(entry);
5556
5572
  if (isNodeOfType(stripped, "Identifier")) return stripped.name;
5557
5573
  if (isNodeOfType(stripped, "MemberExpression")) return stringifyMemberChain(stripped);
5558
5574
  return null;
5559
5575
  };
5560
5576
  const depsArrayContainsIdentifier = (depsArgument, identifierName) => {
5561
5577
  if (!depsArgument) return false;
5562
- const strippedDepsArgument = unwrapExpression(depsArgument);
5578
+ const strippedDepsArgument = unwrapExpression$1(depsArgument);
5563
5579
  if (!isNodeOfType(strippedDepsArgument, "ArrayExpression")) return false;
5564
5580
  return strippedDepsArgument.elements.some((element) => {
5565
5581
  if (!element) return false;
5566
- const strippedElement = unwrapExpression(element);
5582
+ const strippedElement = unwrapExpression$1(element);
5567
5583
  return isNodeOfType(strippedElement, "Identifier") && strippedElement.name === identifierName;
5568
5584
  });
5569
5585
  };
5570
5586
  const stringifyMemberChain = (node) => {
5571
- const stripped = unwrapExpression(node);
5587
+ const stripped = unwrapExpression$1(node);
5572
5588
  if (isNodeOfType(stripped, "Identifier")) return stripped.name;
5573
5589
  if (isNodeOfType(stripped, "ThisExpression")) return "this";
5574
5590
  if (isNodeOfType(stripped, "MemberExpression")) {
@@ -5610,13 +5626,13 @@ const hasBroaderDeclaredDependency = (declaredKey, declaredKeys) => {
5610
5626
  return false;
5611
5627
  };
5612
5628
  const getMemberRootIdentifier = (node) => {
5613
- const stripped = unwrapExpression(node);
5629
+ const stripped = unwrapExpression$1(node);
5614
5630
  if (isNodeOfType(stripped, "Identifier")) return stripped;
5615
5631
  if (isNodeOfType(stripped, "MemberExpression")) return getMemberRootIdentifier(stripped.object);
5616
5632
  return null;
5617
5633
  };
5618
5634
  const hasComputedMemberExpression = (node) => {
5619
- const stripped = unwrapExpression(node);
5635
+ const stripped = unwrapExpression$1(node);
5620
5636
  if (!isNodeOfType(stripped, "MemberExpression")) return false;
5621
5637
  if (stripped.computed) return true;
5622
5638
  return hasComputedMemberExpression(stripped.object);
@@ -5637,7 +5653,7 @@ const isRegExpLiteral = (node) => {
5637
5653
  };
5638
5654
  const isUnstableInitializer = (node) => {
5639
5655
  if (!node) return false;
5640
- const stripped = unwrapExpression(node);
5656
+ const stripped = unwrapExpression$1(node);
5641
5657
  if (isRegExpLiteral(stripped)) return true;
5642
5658
  if (isNodeOfType(stripped, "ConditionalExpression")) return isUnstableInitializer(stripped.consequent) || isUnstableInitializer(stripped.alternate);
5643
5659
  if (isNodeOfType(stripped, "LogicalExpression")) return isUnstableInitializer(stripped.left) || isUnstableInitializer(stripped.right);
@@ -5793,7 +5809,7 @@ const hasMemberCallForRoot = (node, rootName) => {
5793
5809
  const visit = (current) => {
5794
5810
  if (didFindMemberCall) return;
5795
5811
  if (isNodeOfType(current, "CallExpression")) {
5796
- if (getMemberRootIdentifier(unwrapExpression(current.callee))?.name === rootName) {
5812
+ if (getMemberRootIdentifier(unwrapExpression$1(current.callee))?.name === rootName) {
5797
5813
  didFindMemberCall = true;
5798
5814
  return;
5799
5815
  }
@@ -5855,7 +5871,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
5855
5871
  return;
5856
5872
  }
5857
5873
  const depsArgumentRaw = node.arguments[depsArgumentIndex];
5858
- const callbackExpression = unwrapExpression(callbackArgument);
5874
+ const callbackExpression = unwrapExpression$1(callbackArgument);
5859
5875
  let callbackToAnalyze = null;
5860
5876
  const forcedCaptureKeys = /* @__PURE__ */ new Set();
5861
5877
  if (isNodeOfType(callbackExpression, "ArrowFunctionExpression") || isNodeOfType(callbackExpression, "FunctionExpression")) callbackToAnalyze = callbackExpression;
@@ -5863,7 +5879,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
5863
5879
  const callbackSymbol = context.scopes.symbolFor(callbackExpression);
5864
5880
  const functionValueNode = callbackSymbol ? getFunctionValueNode(callbackSymbol) : null;
5865
5881
  if (functionValueNode) callbackToAnalyze = functionValueNode;
5866
- else if (callbackSymbol?.initializer && isNodeOfType(unwrapExpression(callbackSymbol.initializer), "CallExpression")) forcedCaptureKeys.add(callbackExpression.name);
5882
+ else if (callbackSymbol?.initializer && isNodeOfType(unwrapExpression$1(callbackSymbol.initializer), "CallExpression")) forcedCaptureKeys.add(callbackExpression.name);
5867
5883
  else if (depsArgumentRaw) {
5868
5884
  if (depsArrayContainsIdentifier(depsArgumentRaw, callbackExpression.name)) return;
5869
5885
  context.report({
@@ -5920,7 +5936,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
5920
5936
  });
5921
5937
  return;
5922
5938
  }
5923
- const depsArgument = unwrapExpression(depsArgumentRaw);
5939
+ const depsArgument = unwrapExpression$1(depsArgumentRaw);
5924
5940
  if (isNodeOfType(depsArgument, "Literal") && depsArgument.value === null || isNodeOfType(depsArgument, "Identifier") && depsArgument.name === "undefined") {
5925
5941
  if (isAutoDependenciesHook(hookName)) return;
5926
5942
  if (HOOKS_REQUIRING_DEPS_ARRAY.has(hookName)) {
@@ -5961,11 +5977,11 @@ If the missing value is recreated every render, move it inside the hook or stabi
5961
5977
  for (const forcedCaptureKey of forcedCaptureKeys) captureKeys.add(forcedCaptureKey);
5962
5978
  const hasLiteralDepElement = depsArgument.elements.some((element) => {
5963
5979
  if (!element) return false;
5964
- return isLiteralOrEmptyTemplate(unwrapExpression(element));
5980
+ return isLiteralOrEmptyTemplate(unwrapExpression$1(element));
5965
5981
  });
5966
5982
  const hasNonStringLiteralDep = depsArgument.elements.some((element) => {
5967
5983
  if (!element) return false;
5968
- return isNonStringLiteral(unwrapExpression(element));
5984
+ return isNonStringLiteral(unwrapExpression$1(element));
5969
5985
  });
5970
5986
  if (hasNonStringLiteralDep) context.report({
5971
5987
  node: depsArgument,
@@ -5986,7 +6002,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
5986
6002
  });
5987
6003
  continue;
5988
6004
  }
5989
- const stripped = unwrapExpression(elementNode);
6005
+ const stripped = unwrapExpression$1(elementNode);
5990
6006
  if (isLiteralOrEmptyTemplate(stripped)) continue;
5991
6007
  if (isNodeOfType(stripped, "Identifier")) {
5992
6008
  const depSymbol = context.scopes.symbolFor(stripped);
@@ -6206,7 +6222,7 @@ const flattenJsxName$1 = (name) => {
6206
6222
  return "";
6207
6223
  };
6208
6224
  const isSupportedJsxName = (name) => isNodeOfType(name, "JSXIdentifier") || isNodeOfType(name, "JSXMemberExpression");
6209
- const buildMessage$24 = (propName, message) => message ?? `Prop \`${propName}\` is forbidden on this component.`;
6225
+ const buildMessage$25 = (propName, message) => message ?? `Prop \`${propName}\` is forbidden on this component.`;
6210
6226
  const forbidComponentProps = defineRule({
6211
6227
  id: "forbid-component-props",
6212
6228
  severity: "warn",
@@ -6232,7 +6248,7 @@ const forbidComponentProps = defineRule({
6232
6248
  if (!isForbiddenForTag(entry, tag)) continue;
6233
6249
  context.report({
6234
6250
  node: attribute,
6235
- message: buildMessage$24(propName, entry.message)
6251
+ message: buildMessage$25(propName, entry.message)
6236
6252
  });
6237
6253
  break;
6238
6254
  }
@@ -6242,7 +6258,7 @@ const forbidComponentProps = defineRule({
6242
6258
  });
6243
6259
  //#endregion
6244
6260
  //#region src/plugin/rules/react-builtins/forbid-dom-props.ts
6245
- const buildMessage$23 = (propName, customMessage) => customMessage ?? `Prop \`${propName}\` is forbidden on DOM nodes.`;
6261
+ const buildMessage$24 = (propName, customMessage) => customMessage ?? `Prop \`${propName}\` is forbidden on DOM nodes.`;
6246
6262
  const resolveSettings$43 = (settings) => {
6247
6263
  const reactDoctor = settings?.["react-doctor"];
6248
6264
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidDomProps ?? {} : {};
@@ -6280,7 +6296,7 @@ const forbidDomProps = defineRule({
6280
6296
  if (disallowedFor && disallowedFor.size > 0 && !disallowedFor.has(elementName)) continue;
6281
6297
  context.report({
6282
6298
  node: attribute.name,
6283
- message: buildMessage$23(propName, descriptor.message)
6299
+ message: buildMessage$24(propName, descriptor.message)
6284
6300
  });
6285
6301
  }
6286
6302
  } };
@@ -6350,7 +6366,7 @@ const isReactFunctionCall = (node, expectedCall) => {
6350
6366
  };
6351
6367
  //#endregion
6352
6368
  //#region src/plugin/rules/react-builtins/forbid-elements.ts
6353
- const buildMessage$22 = (element, customHelp) => customHelp ? `<${element}> is forbidden — ${customHelp}` : `<${element}> is forbidden.`;
6369
+ const buildMessage$23 = (element, customHelp) => customHelp ? `<${element}> is forbidden — ${customHelp}` : `<${element}> is forbidden.`;
6354
6370
  const resolveSettings$42 = (settings) => {
6355
6371
  const reactDoctor = settings?.["react-doctor"];
6356
6372
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidElements ?? {} : {};
@@ -6374,7 +6390,7 @@ const forbidElements = defineRule({
6374
6390
  if (!fullName || !forbidMap.has(fullName)) return;
6375
6391
  context.report({
6376
6392
  node: node.name,
6377
- message: buildMessage$22(fullName, forbidMap.get(fullName))
6393
+ message: buildMessage$23(fullName, forbidMap.get(fullName))
6378
6394
  });
6379
6395
  },
6380
6396
  CallExpression(node) {
@@ -6394,7 +6410,7 @@ const forbidElements = defineRule({
6394
6410
  if (!elementName || !forbidMap.has(elementName)) return;
6395
6411
  context.report({
6396
6412
  node: firstArgument,
6397
- message: buildMessage$22(elementName, forbidMap.get(elementName))
6413
+ message: buildMessage$23(elementName, forbidMap.get(elementName))
6398
6414
  });
6399
6415
  }
6400
6416
  };
@@ -6402,7 +6418,7 @@ const forbidElements = defineRule({
6402
6418
  });
6403
6419
  //#endregion
6404
6420
  //#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
6405
- const MESSAGE$42 = "Components wrapped with `forwardRef` must accept a `ref` parameter — drop `forwardRef` if you don't need a ref.";
6421
+ const MESSAGE$44 = "Components wrapped with `forwardRef` must accept a `ref` parameter — drop `forwardRef` if you don't need a ref.";
6406
6422
  const forwardRefUsesRef = defineRule({
6407
6423
  id: "forward-ref-uses-ref",
6408
6424
  severity: "warn",
@@ -6421,13 +6437,13 @@ const forwardRefUsesRef = defineRule({
6421
6437
  if (isNodeOfType(onlyParam, "RestElement")) return;
6422
6438
  context.report({
6423
6439
  node: inner,
6424
- message: MESSAGE$42
6440
+ message: MESSAGE$44
6425
6441
  });
6426
6442
  } })
6427
6443
  });
6428
6444
  //#endregion
6429
6445
  //#region src/plugin/rules/a11y/heading-has-content.ts
6430
- const MESSAGE$41 = "Heading elements must contain accessible text content (or `aria-label` / `aria-labelledby`).";
6446
+ const MESSAGE$43 = "Heading elements must contain accessible text content (or `aria-label` / `aria-labelledby`).";
6431
6447
  const DEFAULT_HEADING_TAGS = [
6432
6448
  "h1",
6433
6449
  "h2",
@@ -6459,7 +6475,7 @@ const headingHasContent = defineRule({
6459
6475
  if (isHiddenFromScreenReader(node, context.settings)) return;
6460
6476
  context.report({
6461
6477
  node,
6462
- message: MESSAGE$41
6478
+ message: MESSAGE$43
6463
6479
  });
6464
6480
  } };
6465
6481
  }
@@ -6595,7 +6611,7 @@ const hooksNoNanInDeps = defineRule({
6595
6611
  });
6596
6612
  //#endregion
6597
6613
  //#region src/plugin/rules/a11y/html-has-lang.ts
6598
- const MESSAGE$40 = "`<html>` element must have a non-empty `lang` attribute.";
6614
+ const MESSAGE$42 = "`<html>` element must have a non-empty `lang` attribute.";
6599
6615
  const resolveSettings$39 = (settings) => {
6600
6616
  const reactDoctor = settings?.["react-doctor"];
6601
6617
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
@@ -6642,7 +6658,7 @@ const htmlHasLang = defineRule({
6642
6658
  if (!lang) {
6643
6659
  context.report({
6644
6660
  node: node.name,
6645
- message: MESSAGE$40
6661
+ message: MESSAGE$42
6646
6662
  });
6647
6663
  return;
6648
6664
  }
@@ -6650,13 +6666,13 @@ const htmlHasLang = defineRule({
6650
6666
  if (verdict === "missing" || verdict === "empty") {
6651
6667
  context.report({
6652
6668
  node: lang,
6653
- message: MESSAGE$40
6669
+ message: MESSAGE$42
6654
6670
  });
6655
6671
  return;
6656
6672
  }
6657
6673
  if (hasSpread && !lang) context.report({
6658
6674
  node: node.name,
6659
- message: MESSAGE$40
6675
+ message: MESSAGE$42
6660
6676
  });
6661
6677
  } };
6662
6678
  }
@@ -6696,7 +6712,7 @@ const BLOCK_LEVEL_ELEMENTS = new Set([
6696
6712
  "table",
6697
6713
  "ul"
6698
6714
  ]);
6699
- const buildMessage$21 = (childTagName) => `Block-level \`<${childTagName}>\` cannot appear inside a \`<p>\` — the HTML parser auto-closes the paragraph at the start of \`<${childTagName}>\`, splitting your DOM in ways the renderer never expressed and triggering hydration mismatches.`;
6715
+ const buildMessage$22 = (childTagName) => `Block-level \`<${childTagName}>\` cannot appear inside a \`<p>\` — the HTML parser auto-closes the paragraph at the start of \`<${childTagName}>\`, splitting your DOM in ways the renderer never expressed and triggering hydration mismatches.`;
6700
6716
  const isParagraphElement = (candidate) => {
6701
6717
  if (!isNodeOfType(candidate, "JSXElement")) return false;
6702
6718
  const opening = candidate.openingElement;
@@ -6724,7 +6740,7 @@ const htmlNoInvalidParagraphChild = defineRule({
6724
6740
  if (!findEnclosingParagraph(node)) return;
6725
6741
  context.report({
6726
6742
  node: node.name,
6727
- message: buildMessage$21(childTagName)
6743
+ message: buildMessage$22(childTagName)
6728
6744
  });
6729
6745
  } })
6730
6746
  });
@@ -6744,7 +6760,7 @@ const ROW_GROUPS = new Set([
6744
6760
  "tbody",
6745
6761
  "tfoot"
6746
6762
  ]);
6747
- const buildMessage$20 = (childTag, expectedParent, actualParent) => `Improper table nesting — \`<${childTag}>\` must be a direct child of ${expectedParent}, but its nearest host ancestor is \`<${actualParent}>\`. Browsers auto-rewrite invalid table structure, producing a DOM that doesn't match the JSX (broken hydration, broken \`>\` selectors, broken accessibility tree).`;
6763
+ const buildMessage$21 = (childTag, expectedParent, actualParent) => `Improper table nesting — \`<${childTag}>\` must be a direct child of ${expectedParent}, but its nearest host ancestor is \`<${actualParent}>\`. Browsers auto-rewrite invalid table structure, producing a DOM that doesn't match the JSX (broken hydration, broken \`>\` selectors, broken accessibility tree).`;
6748
6764
  const buildNestedTableMessage = () => "Improper table nesting — `<table>` cannot be a direct descendant of another table element. Tables can only nest inside a `<td>` or `<th>` cell of an outer table.";
6749
6765
  const getHostTagName = (jsxElement) => {
6750
6766
  if (!isNodeOfType(jsxElement, "JSXElement")) return null;
@@ -6812,28 +6828,28 @@ const htmlNoInvalidTableNesting = defineRule({
6812
6828
  if (ROW_GROUPS.has(tagName)) {
6813
6829
  if (actualParent !== "table") context.report({
6814
6830
  node: node.openingElement.name,
6815
- message: buildMessage$20(tagName, "`<table>`", actualParent)
6831
+ message: buildMessage$21(tagName, "`<table>`", actualParent)
6816
6832
  });
6817
6833
  return;
6818
6834
  }
6819
6835
  if (tagName === "tr") {
6820
6836
  if (!ROW_GROUPS.has(actualParent) && actualParent !== "table") context.report({
6821
6837
  node: node.openingElement.name,
6822
- message: buildMessage$20(tagName, "`<thead>`, `<tbody>`, or `<tfoot>`", actualParent)
6838
+ message: buildMessage$21(tagName, "`<thead>`, `<tbody>`, or `<tfoot>`", actualParent)
6823
6839
  });
6824
6840
  return;
6825
6841
  }
6826
6842
  if (tagName === "td" || tagName === "th") {
6827
6843
  if (actualParent !== "tr") context.report({
6828
6844
  node: node.openingElement.name,
6829
- message: buildMessage$20(tagName, "`<tr>`", actualParent)
6845
+ message: buildMessage$21(tagName, "`<tr>`", actualParent)
6830
6846
  });
6831
6847
  }
6832
6848
  } })
6833
6849
  });
6834
6850
  //#endregion
6835
6851
  //#region src/plugin/rules/correctness/html-no-nested-interactive.ts
6836
- const buildMessage$19 = (tagName) => `Improper nesting of \`<${tagName}>\` inside another \`<${tagName}>\` — the HTML parser auto-closes the outer element, splitting your DOM in ways the renderer never expressed and breaking event delegation, focus, and accessibility.`;
6852
+ const buildMessage$20 = (tagName) => `Improper nesting of \`<${tagName}>\` inside another \`<${tagName}>\` — the HTML parser auto-closes the outer element, splitting your DOM in ways the renderer never expressed and breaking event delegation, focus, and accessibility.`;
6837
6853
  const isJsxElementWithTagName = (candidate, tagName) => {
6838
6854
  if (!isNodeOfType(candidate, "JSXElement")) return false;
6839
6855
  const opening = candidate.openingElement;
@@ -6861,13 +6877,13 @@ const htmlNoNestedInteractive = defineRule({
6861
6877
  if (!findEnclosingSameTag(node, tagName)) return;
6862
6878
  context.report({
6863
6879
  node: node.name,
6864
- message: buildMessage$19(tagName)
6880
+ message: buildMessage$20(tagName)
6865
6881
  });
6866
6882
  } })
6867
6883
  });
6868
6884
  //#endregion
6869
6885
  //#region src/plugin/rules/a11y/iframe-has-title.ts
6870
- const MESSAGE$39 = "`<iframe>` element must have a non-empty `title` attribute for assistive technology.";
6886
+ const MESSAGE$41 = "`<iframe>` element must have a non-empty `title` attribute for assistive technology.";
6871
6887
  const evaluateTitleValue = (value) => {
6872
6888
  if (!value) return "missing";
6873
6889
  if (isNodeOfType(value, "Literal")) {
@@ -6906,14 +6922,14 @@ const iframeHasTitle = defineRule({
6906
6922
  if (!titleAttr) {
6907
6923
  if (hasSpread || tag === "iframe") context.report({
6908
6924
  node: node.name,
6909
- message: MESSAGE$39
6925
+ message: MESSAGE$41
6910
6926
  });
6911
6927
  return;
6912
6928
  }
6913
6929
  const verdict = evaluateTitleValue(titleAttr.value);
6914
6930
  if (verdict === "missing" || verdict === "empty") context.report({
6915
6931
  node: titleAttr,
6916
- message: MESSAGE$39
6932
+ message: MESSAGE$41
6917
6933
  });
6918
6934
  } })
6919
6935
  });
@@ -7016,7 +7032,7 @@ const iframeMissingSandbox = defineRule({
7016
7032
  });
7017
7033
  //#endregion
7018
7034
  //#region src/plugin/rules/a11y/img-redundant-alt.ts
7019
- const MESSAGE$38 = "`alt` text contains redundant words like \"image\" / \"photo\" / \"picture\" — describe the content instead.";
7035
+ const MESSAGE$40 = "`alt` text contains redundant words like \"image\" / \"photo\" / \"picture\" — describe the content instead.";
7020
7036
  const DEFAULT_COMPONENTS = ["img"];
7021
7037
  const DEFAULT_REDUNDANT_WORDS = [
7022
7038
  "image",
@@ -7078,7 +7094,7 @@ const imgRedundantAlt = defineRule({
7078
7094
  if (!altAttribute) return;
7079
7095
  if (altValueRedundant(altAttribute, settings.words)) context.report({
7080
7096
  node: altAttribute,
7081
- message: MESSAGE$38
7097
+ message: MESSAGE$40
7082
7098
  });
7083
7099
  } };
7084
7100
  }
@@ -7273,7 +7289,7 @@ const isAtomFromJotai = (callExpression) => {
7273
7289
  if (!isImportedFromModule(callExpression, localName, "jotai")) return false;
7274
7290
  return getImportedNameFromModule(callExpression, localName, "jotai") === "atom";
7275
7291
  };
7276
- const isFunctionLike = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression")));
7292
+ const isFunctionLike$1 = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression")));
7277
7293
  const getFirstParameterName = (fn) => {
7278
7294
  const parameters = fn.params ?? [];
7279
7295
  if (parameters.length !== 1) return null;
@@ -7391,7 +7407,7 @@ const jotaiDerivedAtomReturnsFreshObject = defineRule({
7391
7407
  const args = node.arguments ?? [];
7392
7408
  if (args.length === 0) return;
7393
7409
  const reader = args[0];
7394
- if (!isFunctionLike(reader)) return;
7410
+ if (!isFunctionLike$1(reader)) return;
7395
7411
  const getParameterName = getFirstParameterName(reader);
7396
7412
  if (!getParameterName) return;
7397
7413
  const freshReturn = getFreshReturnForFunction(reader);
@@ -7407,7 +7423,6 @@ const jotaiDerivedAtomReturnsFreshObject = defineRule({
7407
7423
  //#endregion
7408
7424
  //#region src/plugin/rules/jotai/jotai-select-atom-in-render-body.ts
7409
7425
  const JOTAI_SELECT_ATOM_SOURCES = ["jotai/utils", "jotai"];
7410
- const MEMOIZING_HOOK_NAMES = new Set(["useMemo", "useCallback"]);
7411
7426
  const COMPONENT_NAME_PATTERN = /^[A-Z]/;
7412
7427
  const HOOK_NAME_PATTERN = /^use[A-Z]/;
7413
7428
  const isFunctionLikeNode = (node) => isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression");
@@ -7645,7 +7660,7 @@ const isInsideLoopContext = (node) => {
7645
7660
  let current = node.parent;
7646
7661
  while (current) {
7647
7662
  if (isNodeOfType(current, "ForStatement") || isNodeOfType(current, "ForInStatement") || isNodeOfType(current, "ForOfStatement") || isNodeOfType(current, "WhileStatement") || isNodeOfType(current, "DoWhileStatement")) return true;
7648
- if (isFunctionLike$1(current)) {
7663
+ if (isFunctionLike$2(current)) {
7649
7664
  if (isIteratorCallback(current)) return true;
7650
7665
  return false;
7651
7666
  }
@@ -7999,7 +8014,7 @@ const jsHoistIntl = defineRule({
7999
8014
  let cursor = node.parent ?? null;
8000
8015
  let inFunctionBody = false;
8001
8016
  while (cursor) {
8002
- if (isFunctionLike$1(cursor)) {
8017
+ if (isFunctionLike$2(cursor)) {
8003
8018
  inFunctionBody = true;
8004
8019
  const fnParent = cursor.parent;
8005
8020
  if (fnParent && isNodeOfType(fnParent, "CallExpression") && fnParent.arguments?.[0] === cursor) {
@@ -9344,7 +9359,7 @@ const findVariableInitializer = (referenceNode, bindingName) => {
9344
9359
  };
9345
9360
  //#endregion
9346
9361
  //#region src/plugin/rules/react-builtins/jsx-max-depth.ts
9347
- const buildMessage$18 = (depth, max) => `JSX nesting depth ${depth} exceeds maximum ${max}.`;
9362
+ const buildMessage$19 = (depth, max) => `JSX nesting depth ${depth} exceeds maximum ${max}.`;
9348
9363
  const DEFAULT_MAX_DEPTH = 14;
9349
9364
  const resolveSettings$30 = (settings) => {
9350
9365
  const reactDoctor = settings?.["react-doctor"];
@@ -9411,7 +9426,7 @@ const jsxMaxDepth = defineRule({
9411
9426
  const total = computeJsxAncestorDepth(node) + computeChildrenDepth(node.children ?? [], /* @__PURE__ */ new Set());
9412
9427
  if (total > max) context.report({
9413
9428
  node,
9414
- message: buildMessage$18(total, max)
9429
+ message: buildMessage$19(total, max)
9415
9430
  });
9416
9431
  };
9417
9432
  return {
@@ -9426,7 +9441,7 @@ const jsxMaxDepth = defineRule({
9426
9441
  });
9427
9442
  //#endregion
9428
9443
  //#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
9429
- const MESSAGE$37 = "Comment-like text in JSX must live inside `{/* … */}` — bare `//` or `/*` becomes literal text.";
9444
+ const MESSAGE$39 = "Comment-like text in JSX must live inside `{/* … */}` — bare `//` or `/*` becomes literal text.";
9430
9445
  const LITERAL_TEXT_TAGS = new Set([
9431
9446
  "code",
9432
9447
  "pre",
@@ -9461,11 +9476,20 @@ const jsxNoCommentTextnodes = defineRule({
9461
9476
  if (isInsideLiteralTextTag(node)) return;
9462
9477
  context.report({
9463
9478
  node,
9464
- message: MESSAGE$37
9479
+ message: MESSAGE$39
9465
9480
  });
9466
9481
  } })
9467
9482
  });
9468
9483
  //#endregion
9484
+ //#region src/plugin/utils/is-canonical-react-namespace-name.ts
9485
+ const isCanonicalReactNamespaceName = (namespaceName) => {
9486
+ if (namespaceName === "React") return true;
9487
+ if (namespaceName === "react") return true;
9488
+ if (namespaceName.startsWith("_react")) return true;
9489
+ if (namespaceName.startsWith("_React")) return true;
9490
+ return false;
9491
+ };
9492
+ //#endregion
9469
9493
  //#region src/plugin/utils/is-inside-function-scope.ts
9470
9494
  const FUNCTION_NODE_TYPES$1 = new Set([
9471
9495
  "FunctionDeclaration",
@@ -9483,7 +9507,12 @@ const isInsideFunctionScope = (node) => {
9483
9507
  };
9484
9508
  //#endregion
9485
9509
  //#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
9486
- const MESSAGE$36 = "Context `value` prop is constructed inline — wrap with `useMemo`/`useCallback` or hoist a constant to avoid re-renders.";
9510
+ const MESSAGE$38 = "Context `value` prop is constructed inline — wrap with `useMemo`/`useCallback` or hoist a constant to avoid re-renders.";
9511
+ const CONTEXT_MODULES$1 = [
9512
+ "react",
9513
+ "use-context-selector",
9514
+ "react-tracked"
9515
+ ];
9487
9516
  const isConstructedValue = (expression) => {
9488
9517
  const stripped = stripParenExpression(expression);
9489
9518
  if (isNodeOfType(stripped, "ObjectExpression") || isNodeOfType(stripped, "ArrayExpression") || isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "FunctionExpression") || isNodeOfType(stripped, "ClassExpression") || isNodeOfType(stripped, "NewExpression") || isNodeOfType(stripped, "JSXElement") || isNodeOfType(stripped, "JSXFragment")) return true;
@@ -9491,10 +9520,57 @@ const isConstructedValue = (expression) => {
9491
9520
  if (isNodeOfType(stripped, "LogicalExpression")) return isConstructedValue(stripped.left) || isConstructedValue(stripped.right);
9492
9521
  return false;
9493
9522
  };
9494
- const isProviderName = (node) => {
9523
+ const isProviderMemberName = (node) => {
9495
9524
  if (!isNodeOfType(node, "JSXMemberExpression")) return false;
9496
9525
  return node.property.name === "Provider";
9497
9526
  };
9527
+ const isCreateContextCallExpression = (expression) => {
9528
+ const stripped = stripParenExpression(expression);
9529
+ if (!isNodeOfType(stripped, "CallExpression")) return false;
9530
+ const callee = stripped.callee;
9531
+ if (isNodeOfType(callee, "Identifier")) {
9532
+ for (const moduleName of CONTEXT_MODULES$1) if (getImportedNameFromModule(callee, callee.name, moduleName) === "createContext") return true;
9533
+ return false;
9534
+ }
9535
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed) {
9536
+ const namespaceIdentifier = callee.object;
9537
+ const propertyIdentifier = callee.property;
9538
+ if (!isNodeOfType(namespaceIdentifier, "Identifier")) return false;
9539
+ if (!isNodeOfType(propertyIdentifier, "Identifier")) return false;
9540
+ if (propertyIdentifier.name !== "createContext") return false;
9541
+ if (isCanonicalReactNamespaceName(namespaceIdentifier.name)) return true;
9542
+ for (const moduleName of CONTEXT_MODULES$1) {
9543
+ if (getImportedNameFromModule(namespaceIdentifier, namespaceIdentifier.name, moduleName) === null) continue;
9544
+ return true;
9545
+ }
9546
+ }
9547
+ return false;
9548
+ };
9549
+ const collectContextBindings = (programRoot) => {
9550
+ const bindings = /* @__PURE__ */ new Set();
9551
+ if (!isNodeOfType(programRoot, "Program")) return bindings;
9552
+ for (const topLevel of programRoot.body ?? []) {
9553
+ let declaration = topLevel;
9554
+ if (isNodeOfType(topLevel, "ExportNamedDeclaration") && topLevel.declaration) declaration = topLevel.declaration;
9555
+ if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) continue;
9556
+ for (const declarator of declaration.declarations ?? []) {
9557
+ if (!isNodeOfType(declarator, "VariableDeclarator")) continue;
9558
+ if (!isNodeOfType(declarator.id, "Identifier")) continue;
9559
+ if (!declarator.init) continue;
9560
+ if (!isAstNode(declarator.init)) continue;
9561
+ if (!isCreateContextCallExpression(declarator.init)) continue;
9562
+ bindings.add(declarator.id.name);
9563
+ }
9564
+ }
9565
+ return bindings;
9566
+ };
9567
+ const isCreateContextBindingJsxName = (node, contextBindings) => {
9568
+ if (!isNodeOfType(node, "JSXIdentifier")) return false;
9569
+ if (!contextBindings.has(node.name)) return false;
9570
+ const binding = findVariableInitializer(node, node.name);
9571
+ if (!binding) return false;
9572
+ return binding.scopeOwner.type === "Program";
9573
+ };
9498
9574
  const jsxNoConstructedContextValues = defineRule({
9499
9575
  id: "jsx-no-constructed-context-values",
9500
9576
  tags: ["react-jsx-only"],
@@ -9503,26 +9579,35 @@ const jsxNoConstructedContextValues = defineRule({
9503
9579
  category: "Performance",
9504
9580
  create: (context) => {
9505
9581
  const isTestlikeFile = isTestlikeFilename(context.filename);
9506
- return { JSXOpeningElement(node) {
9507
- if (isTestlikeFile) return;
9508
- if (!isProviderName(node.name)) return;
9509
- if (!isInsideFunctionScope(node)) return;
9510
- for (const attribute of node.attributes) {
9511
- if (!isNodeOfType(attribute, "JSXAttribute")) continue;
9512
- if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
9513
- if (attribute.name.name !== "value") continue;
9514
- const attributeValue = attribute.value;
9515
- if (!attributeValue) continue;
9516
- if (!isNodeOfType(attributeValue, "JSXExpressionContainer")) continue;
9517
- const innerExpression = attributeValue.expression;
9518
- if (!innerExpression || innerExpression.type === "JSXEmptyExpression") continue;
9519
- if (!isConstructedValue(innerExpression)) continue;
9520
- context.report({
9521
- node: attribute,
9522
- message: MESSAGE$36
9523
- });
9582
+ let contextBindings = /* @__PURE__ */ new Set();
9583
+ return {
9584
+ Program(node) {
9585
+ contextBindings = collectContextBindings(node);
9586
+ },
9587
+ JSXOpeningElement(node) {
9588
+ if (isTestlikeFile) return;
9589
+ const nameNode = node.name;
9590
+ const isLegacyProvider = isProviderMemberName(nameNode);
9591
+ const isReact19Shorthand = isCreateContextBindingJsxName(nameNode, contextBindings);
9592
+ if (!isLegacyProvider && !isReact19Shorthand) return;
9593
+ if (!isInsideFunctionScope(node)) return;
9594
+ for (const attribute of node.attributes) {
9595
+ if (!isNodeOfType(attribute, "JSXAttribute")) continue;
9596
+ if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
9597
+ if (attribute.name.name !== "value") continue;
9598
+ const attributeValue = attribute.value;
9599
+ if (!attributeValue) continue;
9600
+ if (!isNodeOfType(attributeValue, "JSXExpressionContainer")) continue;
9601
+ const innerExpression = attributeValue.expression;
9602
+ if (!innerExpression || innerExpression.type === "JSXEmptyExpression") continue;
9603
+ if (!isConstructedValue(innerExpression)) continue;
9604
+ context.report({
9605
+ node: attribute,
9606
+ message: MESSAGE$38
9607
+ });
9608
+ }
9524
9609
  }
9525
- } };
9610
+ };
9526
9611
  }
9527
9612
  });
9528
9613
  //#endregion
@@ -9548,7 +9633,8 @@ const jsxNoDuplicateProps = defineRule({
9548
9633
  //#endregion
9549
9634
  //#region src/plugin/utils/build-same-file-memo-registry.ts
9550
9635
  const HOC_NAMES_FOR_MEMOISATION = new Set([
9551
- ...REACT_HOC_NAMES,
9636
+ "memo",
9637
+ "React.memo",
9552
9638
  "observer",
9553
9639
  "observable",
9554
9640
  "lazy",
@@ -9602,7 +9688,7 @@ const isJsxAttributeOnIntrinsicHtmlElement = (attribute) => {
9602
9688
  };
9603
9689
  //#endregion
9604
9690
  //#region src/plugin/rules/react-builtins/jsx-no-jsx-as-prop.ts
9605
- const MESSAGE$35 = "JSX prop receives JSX created on every render — extract it or memoize to avoid re-renders.";
9691
+ const MESSAGE$37 = "JSX prop receives JSX created on every render — extract it or memoize to avoid re-renders.";
9606
9692
  const KNOWN_SLOT_PROP_NAMES = new Set([
9607
9693
  "icon",
9608
9694
  "Icon",
@@ -9870,7 +9956,7 @@ const jsxNoJsxAsProp = defineRule({
9870
9956
  if (!isJsxProducingExpression(expressionNode) && !followsRenderLocalJsxBinding(expressionNode, node)) return;
9871
9957
  context.report({
9872
9958
  node,
9873
- message: MESSAGE$35
9959
+ message: MESSAGE$37
9874
9960
  });
9875
9961
  }
9876
9962
  };
@@ -10158,7 +10244,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
10158
10244
  ];
10159
10245
  //#endregion
10160
10246
  //#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
10161
- const MESSAGE$34 = "JSX prop receives a new Array on every render — extract it or memoize to avoid re-renders.";
10247
+ const MESSAGE$36 = "JSX prop receives a new Array on every render — extract it or memoize to avoid re-renders.";
10162
10248
  const isDataArrayPropName = (propName) => {
10163
10249
  if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
10164
10250
  for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -10241,7 +10327,7 @@ const jsxNoNewArrayAsProp = defineRule({
10241
10327
  if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
10242
10328
  context.report({
10243
10329
  node,
10244
- message: MESSAGE$34
10330
+ message: MESSAGE$36
10245
10331
  });
10246
10332
  }
10247
10333
  };
@@ -10499,7 +10585,7 @@ const SAFE_RECEIVER_NAMES = new Set([
10499
10585
  ]);
10500
10586
  //#endregion
10501
10587
  //#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
10502
- const MESSAGE$33 = "JSX prop receives a new Function on every render — extract it or memoize (`useCallback`) to avoid re-renders.";
10588
+ const MESSAGE$35 = "JSX prop receives a new Function on every render — extract it or memoize (`useCallback`) to avoid re-renders.";
10503
10589
  const isAccessorPredicateName = (propName) => {
10504
10590
  for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
10505
10591
  if (propName.length <= prefix.length) continue;
@@ -10704,7 +10790,7 @@ const jsxNoNewFunctionAsProp = defineRule({
10704
10790
  if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
10705
10791
  context.report({
10706
10792
  node,
10707
- message: MESSAGE$33
10793
+ message: MESSAGE$35
10708
10794
  });
10709
10795
  }
10710
10796
  };
@@ -10924,7 +11010,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
10924
11010
  ];
10925
11011
  //#endregion
10926
11012
  //#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
10927
- const MESSAGE$32 = "JSX prop receives a new Object on every render — extract it or memoize to avoid re-renders.";
11013
+ const MESSAGE$34 = "JSX prop receives a new Object on every render — extract it or memoize to avoid re-renders.";
10928
11014
  const isConfigObjectPropName = (propName) => {
10929
11015
  if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
10930
11016
  for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -11011,7 +11097,7 @@ const jsxNoNewObjectAsProp = defineRule({
11011
11097
  if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
11012
11098
  context.report({
11013
11099
  node,
11014
- message: MESSAGE$32
11100
+ message: MESSAGE$34
11015
11101
  });
11016
11102
  }
11017
11103
  };
@@ -11019,7 +11105,7 @@ const jsxNoNewObjectAsProp = defineRule({
11019
11105
  });
11020
11106
  //#endregion
11021
11107
  //#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
11022
- const MESSAGE$31 = "React 19 disallows `javascript:` URLs as a security precaution — use an event handler instead.";
11108
+ const MESSAGE$33 = "React 19 disallows `javascript:` URLs as a security precaution — use an event handler instead.";
11023
11109
  const JAVASCRIPT_URL_PATTERN = /j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;
11024
11110
  const resolveSettings$29 = (settings) => {
11025
11111
  const reactDoctor = settings?.["react-doctor"];
@@ -11059,7 +11145,7 @@ const jsxNoScriptUrl = defineRule({
11059
11145
  if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
11060
11146
  if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
11061
11147
  node: attribute,
11062
- message: MESSAGE$31
11148
+ message: MESSAGE$33
11063
11149
  });
11064
11150
  }
11065
11151
  } };
@@ -11347,7 +11433,7 @@ const jsxNoTargetBlank = defineRule({
11347
11433
  });
11348
11434
  //#endregion
11349
11435
  //#region src/plugin/rules/react-builtins/jsx-no-undef.ts
11350
- const buildMessage$17 = (name) => `\`${name}\` is not defined in this scope.`;
11436
+ const buildMessage$18 = (name) => `\`${name}\` is not defined in this scope.`;
11351
11437
  const KNOWN_GLOBALS = new Set([
11352
11438
  "globalThis",
11353
11439
  "window",
@@ -11382,7 +11468,7 @@ const jsxNoUndef = defineRule({
11382
11468
  if (findVariableInitializer(node, rootIdentifier)) return;
11383
11469
  context.report({
11384
11470
  node: node.name,
11385
- message: buildMessage$17(rootIdentifier)
11471
+ message: buildMessage$18(rootIdentifier)
11386
11472
  });
11387
11473
  } })
11388
11474
  });
@@ -11481,7 +11567,7 @@ const jsxNoUselessFragment = defineRule({
11481
11567
  });
11482
11568
  //#endregion
11483
11569
  //#region src/plugin/rules/react-builtins/jsx-pascal-case.ts
11484
- const buildMessage$16 = (componentName, allowAllCaps) => allowAllCaps ? `JSX component \`${componentName}\` must be in PascalCase or SCREAMING_SNAKE_CASE.` : `JSX component \`${componentName}\` must be in PascalCase.`;
11570
+ const buildMessage$17 = (componentName, allowAllCaps) => allowAllCaps ? `JSX component \`${componentName}\` must be in PascalCase or SCREAMING_SNAKE_CASE.` : `JSX component \`${componentName}\` must be in PascalCase.`;
11485
11571
  const resolveSettings$26 = (settings) => {
11486
11572
  const reactDoctor = settings?.["react-doctor"];
11487
11573
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPascalCase ?? {} : {};
@@ -11597,7 +11683,7 @@ const jsxPascalCase = defineRule({
11597
11683
  if (!isPascal && !isAllCaps) {
11598
11684
  context.report({
11599
11685
  node,
11600
- message: buildMessage$16(segment, settings.allowAllCaps)
11686
+ message: buildMessage$17(segment, settings.allowAllCaps)
11601
11687
  });
11602
11688
  return;
11603
11689
  }
@@ -11649,7 +11735,7 @@ const jsxPropsNoSpreadMulti = defineRule({
11649
11735
  });
11650
11736
  //#endregion
11651
11737
  //#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
11652
- const MESSAGE$30 = "JSX prop spreading is forbidden — list each prop explicitly.";
11738
+ const MESSAGE$32 = "JSX prop spreading is forbidden — list each prop explicitly.";
11653
11739
  const resolveSettings$25 = (settings) => {
11654
11740
  const reactDoctor = settings?.["react-doctor"];
11655
11741
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
@@ -11689,7 +11775,7 @@ const jsxPropsNoSpreading = defineRule({
11689
11775
  }
11690
11776
  context.report({
11691
11777
  node: attribute,
11692
- message: MESSAGE$30
11778
+ message: MESSAGE$32
11693
11779
  });
11694
11780
  }
11695
11781
  } };
@@ -11844,7 +11930,7 @@ const labelHasAssociatedControl = defineRule({
11844
11930
  });
11845
11931
  //#endregion
11846
11932
  //#region src/plugin/rules/a11y/lang.ts
11847
- const MESSAGE$29 = "`<html lang>` value must be a valid IANA / BCP-47 language tag (e.g. `en`, `en-US`).";
11933
+ const MESSAGE$31 = "`<html lang>` value must be a valid IANA / BCP-47 language tag (e.g. `en`, `en-US`).";
11848
11934
  const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
11849
11935
  "aa",
11850
11936
  "ab",
@@ -12055,7 +12141,7 @@ const lang = defineRule({
12055
12141
  if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
12056
12142
  context.report({
12057
12143
  node: langAttr,
12058
- message: MESSAGE$29
12144
+ message: MESSAGE$31
12059
12145
  });
12060
12146
  return;
12061
12147
  }
@@ -12064,13 +12150,13 @@ const lang = defineRule({
12064
12150
  if (value === null) return;
12065
12151
  if (!isValidLangTag(value)) context.report({
12066
12152
  node: langAttr,
12067
- message: MESSAGE$29
12153
+ message: MESSAGE$31
12068
12154
  });
12069
12155
  } })
12070
12156
  });
12071
12157
  //#endregion
12072
12158
  //#region src/plugin/rules/a11y/media-has-caption.ts
12073
- const MESSAGE$28 = "`<audio>` / `<video>` must have a `<track kind=\"captions\">` child for users who can't hear audio.";
12159
+ const MESSAGE$30 = "`<audio>` / `<video>` must have a `<track kind=\"captions\">` child for users who can't hear audio.";
12074
12160
  const DEFAULT_AUDIO = ["audio"];
12075
12161
  const DEFAULT_VIDEO = ["video"];
12076
12162
  const DEFAULT_TRACK = ["track"];
@@ -12110,7 +12196,7 @@ const mediaHasCaption = defineRule({
12110
12196
  if (!parent || !isNodeOfType(parent, "JSXElement")) {
12111
12197
  context.report({
12112
12198
  node: node.name,
12113
- message: MESSAGE$28
12199
+ message: MESSAGE$30
12114
12200
  });
12115
12201
  return;
12116
12202
  }
@@ -12127,7 +12213,7 @@ const mediaHasCaption = defineRule({
12127
12213
  return kindValue.value.toLowerCase() === "captions";
12128
12214
  })) context.report({
12129
12215
  node: node.name,
12130
- message: MESSAGE$28
12216
+ message: MESSAGE$30
12131
12217
  });
12132
12218
  } };
12133
12219
  }
@@ -12818,7 +12904,7 @@ const collectChainedGetHandlerBodies = (initNode) => {
12818
12904
  };
12819
12905
  const resolveBodiesFromExpression = (expression, resolveBinding, remainingDepth) => {
12820
12906
  if (remainingDepth <= 0) return [];
12821
- if (isFunctionLike$1(expression)) return expression.body ? [expression.body] : [];
12907
+ if (isFunctionLike$2(expression)) return expression.body ? [expression.body] : [];
12822
12908
  if (isNodeOfType(expression, "CallExpression")) {
12823
12909
  for (const callArgument of expression.arguments ?? []) {
12824
12910
  if (isNodeOfType(callArgument, "ArrowFunctionExpression") || isNodeOfType(callArgument, "FunctionExpression")) {
@@ -12941,7 +13027,7 @@ const nextjsNoUseSearchParamsWithoutSuspense = defineRule({
12941
13027
  });
12942
13028
  //#endregion
12943
13029
  //#region src/plugin/rules/a11y/no-access-key.ts
12944
- const MESSAGE$27 = "`accessKey` should not be used — accessKeys conflict with screen reader and OS-level shortcuts.";
13030
+ const MESSAGE$29 = "`accessKey` should not be used — accessKeys conflict with screen reader and OS-level shortcuts.";
12945
13031
  const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
12946
13032
  const noAccessKey = defineRule({
12947
13033
  id: "no-access-key",
@@ -12957,7 +13043,7 @@ const noAccessKey = defineRule({
12957
13043
  if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
12958
13044
  context.report({
12959
13045
  node: accessKey,
12960
- message: MESSAGE$27
13046
+ message: MESSAGE$29
12961
13047
  });
12962
13048
  return;
12963
13049
  }
@@ -12967,7 +13053,7 @@ const noAccessKey = defineRule({
12967
13053
  if (isUndefinedIdentifier(expression)) return;
12968
13054
  context.report({
12969
13055
  node: accessKey,
12970
- message: MESSAGE$27
13056
+ message: MESSAGE$29
12971
13057
  });
12972
13058
  }
12973
13059
  } })
@@ -13276,7 +13362,7 @@ const getEffectFn = (analysis, node) => {
13276
13362
  if (isNodeOfType(fn, "ArrowFunctionExpression") || isNodeOfType(fn, "FunctionExpression")) return fn;
13277
13363
  if (isNodeOfType(fn, "Identifier")) {
13278
13364
  const definitionNode = getRef(analysis, fn)?.resolved?.defs[0]?.node;
13279
- if (definitionNode && isFunctionLike$1(definitionNode)) return definitionNode;
13365
+ if (definitionNode && isFunctionLike$2(definitionNode)) return definitionNode;
13280
13366
  if (definitionNode && isNodeOfType(definitionNode, "VariableDeclarator")) {
13281
13367
  const initializer = definitionNode.init;
13282
13368
  if (isNodeOfType(initializer, "ArrowFunctionExpression") || isNodeOfType(initializer, "FunctionExpression")) return initializer;
@@ -13369,14 +13455,14 @@ const getUseStateDecl = (analysis, ref) => {
13369
13455
  return node ?? null;
13370
13456
  };
13371
13457
  const isCleanupReturnArgument = (analysis, node) => {
13372
- if (isFunctionLike$1(node)) return true;
13458
+ if (isFunctionLike$2(node)) return true;
13373
13459
  if (isNodeOfType(node, "MemberExpression")) return true;
13374
13460
  if (isNodeOfType(node, "Identifier")) {
13375
13461
  const definitionNode = getRef(analysis, node)?.resolved?.defs[0]?.node;
13376
- if (definitionNode && isFunctionLike$1(definitionNode)) return true;
13462
+ if (definitionNode && isFunctionLike$2(definitionNode)) return true;
13377
13463
  if (definitionNode && isNodeOfType(definitionNode, "VariableDeclarator")) {
13378
13464
  const initializer = definitionNode.init;
13379
- return isFunctionLike$1(initializer);
13465
+ return isFunctionLike$2(initializer);
13380
13466
  }
13381
13467
  }
13382
13468
  if (isNodeOfType(node, "ConditionalExpression")) return isCleanupReturnArgument(analysis, node.consequent) || isCleanupReturnArgument(analysis, node.alternate);
@@ -13386,7 +13472,7 @@ const hasCleanupReturn = (analysis, node, visited = /* @__PURE__ */ new WeakSet(
13386
13472
  if (visited.has(node)) return false;
13387
13473
  visited.add(node);
13388
13474
  if (isNodeOfType(node, "ReturnStatement") && node.argument != null) return isCleanupReturnArgument(analysis, node.argument);
13389
- if (!isNodeOfType(node, "BlockStatement") && isFunctionLike$1(node)) return false;
13475
+ if (!isNodeOfType(node, "BlockStatement") && isFunctionLike$2(node)) return false;
13390
13476
  const record = node;
13391
13477
  for (const [key, value] of Object.entries(record)) {
13392
13478
  if (key === "parent") continue;
@@ -13398,7 +13484,7 @@ const hasCleanupReturn = (analysis, node, visited = /* @__PURE__ */ new WeakSet(
13398
13484
  };
13399
13485
  const hasCleanup = (analysis, node) => {
13400
13486
  const fn = getEffectFn(analysis, node);
13401
- if (!isFunctionLike$1(fn)) return false;
13487
+ if (!isFunctionLike$2(fn)) return false;
13402
13488
  if (!isNodeOfType(fn.body, "BlockStatement")) return false;
13403
13489
  return hasCleanupReturn(analysis, fn.body);
13404
13490
  };
@@ -13440,7 +13526,7 @@ const noAdjustStateOnPropChange = defineRule({
13440
13526
  });
13441
13527
  //#endregion
13442
13528
  //#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
13443
- const MESSAGE$26 = "Focusable elements must not have `aria-hidden=\"true\"` — focus would skip the hidden subtree, confusing keyboard users.";
13529
+ const MESSAGE$28 = "Focusable elements must not have `aria-hidden=\"true\"` — focus would skip the hidden subtree, confusing keyboard users.";
13444
13530
  const noAriaHiddenOnFocusable = defineRule({
13445
13531
  id: "no-aria-hidden-on-focusable",
13446
13532
  tags: ["react-jsx-only"],
@@ -13466,7 +13552,7 @@ const noAriaHiddenOnFocusable = defineRule({
13466
13552
  const isImplicitlyFocusable = isInteractiveElement(tag, node);
13467
13553
  if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
13468
13554
  node: ariaHidden,
13469
- message: MESSAGE$26
13555
+ message: MESSAGE$28
13470
13556
  });
13471
13557
  } })
13472
13558
  });
@@ -13746,7 +13832,7 @@ const isInsideStaticPlaceholderMap = (node) => {
13746
13832
  let current = node;
13747
13833
  while (current.parent) {
13748
13834
  const parent = current.parent;
13749
- if (isFunctionLike$1(current) && isNodeOfType(parent, "CallExpression") && parent.arguments.includes(current)) {
13835
+ if (isFunctionLike$2(current) && isNodeOfType(parent, "CallExpression") && parent.arguments.includes(current)) {
13750
13836
  const callee = parent.callee;
13751
13837
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && (callee.property.name === "map" || callee.property.name === "flatMap" || callee.property.name === "forEach")) return isStaticPlaceholderReceiver(callee.object);
13752
13838
  if (isArrayFromCall(parent) && parent.arguments.length >= 2 && parent.arguments[1] === current) return isArrayFromLengthObjectCall(parent);
@@ -13765,7 +13851,7 @@ const findIteratorItemName$1 = (node) => {
13765
13851
  let current = node;
13766
13852
  while (current.parent) {
13767
13853
  const parent = current.parent;
13768
- if (isFunctionLike$1(current) && isNodeOfType(parent, "CallExpression") && parent.arguments.includes(current)) {
13854
+ if (isFunctionLike$2(current) && isNodeOfType(parent, "CallExpression") && parent.arguments.includes(current)) {
13769
13855
  const callee = parent.callee;
13770
13856
  const isIteratorMethodCall = isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && (callee.property.name === "map" || callee.property.name === "flatMap" || callee.property.name === "forEach");
13771
13857
  const isArrayFromCallback = isArrayFromCall(parent) && parent.arguments.length >= 2 && parent.arguments[1] === current;
@@ -13833,7 +13919,7 @@ const noArrayIndexAsKey = defineRule({
13833
13919
  });
13834
13920
  //#endregion
13835
13921
  //#region src/plugin/rules/react-builtins/no-array-index-key.ts
13836
- const MESSAGE$25 = "Array index in `key` doesn't uniquely identify the element — re-renders may use stale state.";
13922
+ const MESSAGE$27 = "Array index in `key` doesn't uniquely identify the element — re-renders may use stale state.";
13837
13923
  const SECOND_INDEX_METHODS = new Set([
13838
13924
  "every",
13839
13925
  "filter",
@@ -14035,7 +14121,7 @@ const noArrayIndexKey = defineRule({
14035
14121
  }
14036
14122
  context.report({
14037
14123
  node: keyAttribute,
14038
- message: MESSAGE$25
14124
+ message: MESSAGE$27
14039
14125
  });
14040
14126
  },
14041
14127
  CallExpression(node) {
@@ -14055,7 +14141,7 @@ const noArrayIndexKey = defineRule({
14055
14141
  if (propName !== "key") continue;
14056
14142
  if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
14057
14143
  node: property,
14058
- message: MESSAGE$25
14144
+ message: MESSAGE$27
14059
14145
  });
14060
14146
  }
14061
14147
  }
@@ -14063,7 +14149,7 @@ const noArrayIndexKey = defineRule({
14063
14149
  });
14064
14150
  //#endregion
14065
14151
  //#region src/plugin/rules/a11y/no-autofocus.ts
14066
- const MESSAGE$24 = "`autoFocus` should not be used — it disrupts users who expect the page focus to remain at the top of the document on load.";
14152
+ const MESSAGE$26 = "`autoFocus` should not be used — it disrupts users who expect the page focus to remain at the top of the document on load.";
14067
14153
  const resolveSettings$21 = (settings) => {
14068
14154
  const reactDoctor = settings?.["react-doctor"];
14069
14155
  return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
@@ -14118,7 +14204,7 @@ const noAutofocus = defineRule({
14118
14204
  }
14119
14205
  context.report({
14120
14206
  node: autoFocusAttribute,
14121
- message: MESSAGE$24
14207
+ message: MESSAGE$26
14122
14208
  });
14123
14209
  } };
14124
14210
  }
@@ -14609,7 +14695,7 @@ const noChainStateUpdates = defineRule({
14609
14695
  });
14610
14696
  //#endregion
14611
14697
  //#region src/plugin/rules/react-builtins/no-children-prop.ts
14612
- const MESSAGE$23 = "Avoid passing children using a `children` prop — nest them between the JSX tags or pass them as additional `React.createElement` arguments instead.";
14698
+ const MESSAGE$25 = "Avoid passing children using a `children` prop — nest them between the JSX tags or pass them as additional `React.createElement` arguments instead.";
14613
14699
  const noChildrenProp = defineRule({
14614
14700
  id: "no-children-prop",
14615
14701
  severity: "warn",
@@ -14620,7 +14706,7 @@ const noChildrenProp = defineRule({
14620
14706
  if (node.name.name !== "children") return;
14621
14707
  context.report({
14622
14708
  node: node.name,
14623
- message: MESSAGE$23
14709
+ message: MESSAGE$25
14624
14710
  });
14625
14711
  },
14626
14712
  CallExpression(node) {
@@ -14633,7 +14719,7 @@ const noChildrenProp = defineRule({
14633
14719
  const propertyKey = property.key;
14634
14720
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
14635
14721
  node: propertyKey,
14636
- message: MESSAGE$23
14722
+ message: MESSAGE$25
14637
14723
  });
14638
14724
  }
14639
14725
  }
@@ -14641,7 +14727,7 @@ const noChildrenProp = defineRule({
14641
14727
  });
14642
14728
  //#endregion
14643
14729
  //#region src/plugin/rules/react-builtins/no-clone-element.ts
14644
- const MESSAGE$22 = "`React.cloneElement` is uncommon and leads to fragile components.";
14730
+ const MESSAGE$24 = "`React.cloneElement` is uncommon and leads to fragile components.";
14645
14731
  const noCloneElement = defineRule({
14646
14732
  id: "no-clone-element",
14647
14733
  severity: "warn",
@@ -14653,7 +14739,7 @@ const noCloneElement = defineRule({
14653
14739
  if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
14654
14740
  if (isImportedFromModule(node, "cloneElement", "react")) context.report({
14655
14741
  node: callee,
14656
- message: MESSAGE$22
14742
+ message: MESSAGE$24
14657
14743
  });
14658
14744
  return;
14659
14745
  }
@@ -14666,14 +14752,231 @@ const noCloneElement = defineRule({
14666
14752
  if (!isImportedFromModule(node, callee.object.name, "react")) return;
14667
14753
  context.report({
14668
14754
  node: callee,
14669
- message: MESSAGE$22
14755
+ message: MESSAGE$24
14670
14756
  });
14671
14757
  }
14672
14758
  } })
14673
14759
  });
14674
14760
  //#endregion
14761
+ //#region src/plugin/utils/component-or-hook-display-name.ts
14762
+ const hocWrapperCalleeName = (callee) => {
14763
+ if (isNodeOfType(callee, "Identifier")) return callee.name;
14764
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
14765
+ return null;
14766
+ };
14767
+ const displayNameFromFunctionBinding = (functionNode) => {
14768
+ let current = functionNode;
14769
+ for (;;) {
14770
+ const parent = current.parent;
14771
+ if (parent && isNodeOfType(parent, "CallExpression") && parent.arguments?.[0] === current) {
14772
+ const calleeName = hocWrapperCalleeName(parent.callee);
14773
+ if (calleeName && COMPONENT_HOC_WRAPPER_NAMES.has(calleeName)) {
14774
+ current = parent;
14775
+ continue;
14776
+ }
14777
+ }
14778
+ break;
14779
+ }
14780
+ const binding = current.parent;
14781
+ if (binding && isNodeOfType(binding, "VariableDeclarator") && isNodeOfType(binding.id, "Identifier") && binding.init === current) return isReactComponentOrHookName(binding.id.name) ? binding.id.name : null;
14782
+ return null;
14783
+ };
14784
+ const componentOrHookDisplayNameForFunction = (functionNode) => {
14785
+ if ((isNodeOfType(functionNode, "FunctionDeclaration") || isNodeOfType(functionNode, "FunctionExpression")) && functionNode.id) return isReactComponentOrHookName(functionNode.id.name) ? functionNode.id.name : null;
14786
+ return displayNameFromFunctionBinding(functionNode);
14787
+ };
14788
+ const nearestEnclosingFunction = (node) => {
14789
+ let cursor = node.parent;
14790
+ while (cursor) {
14791
+ if (isFunctionLike$2(cursor)) return cursor;
14792
+ cursor = cursor.parent ?? null;
14793
+ }
14794
+ return null;
14795
+ };
14796
+ //#endregion
14797
+ //#region src/plugin/utils/enclosing-component-or-hook-name.ts
14798
+ const enclosingComponentOrHookName = (node) => {
14799
+ const functionNode = nearestEnclosingFunction(node);
14800
+ return functionNode ? componentOrHookDisplayNameForFunction(functionNode) : null;
14801
+ };
14802
+ //#endregion
14803
+ //#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
14804
+ const MESSAGE$23 = "createContext() called inside a component or hook — every render creates a brand new Context object, resetting every consumer and disconnecting Provider/Consumer pairs. Move createContext to module scope (outside the component) so the Context identity is stable across renders.";
14805
+ const CONTEXT_MODULES = [
14806
+ "react",
14807
+ "use-context-selector",
14808
+ "react-tracked"
14809
+ ];
14810
+ const isCreateContextCallee = (callee) => {
14811
+ if (isNodeOfType(callee, "Identifier")) {
14812
+ for (const moduleName of CONTEXT_MODULES) if (getImportedNameFromModule(callee, callee.name, moduleName) === "createContext") return true;
14813
+ return false;
14814
+ }
14815
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed) {
14816
+ const namespaceIdentifier = callee.object;
14817
+ const propertyIdentifier = callee.property;
14818
+ if (!isNodeOfType(namespaceIdentifier, "Identifier")) return false;
14819
+ if (!isNodeOfType(propertyIdentifier, "Identifier")) return false;
14820
+ if (propertyIdentifier.name !== "createContext") return false;
14821
+ const namespaceName = namespaceIdentifier.name;
14822
+ if (isCanonicalReactNamespaceName(namespaceName)) return true;
14823
+ for (const moduleName of CONTEXT_MODULES) if (isImportedFromModule(namespaceIdentifier, namespaceName, moduleName)) return true;
14824
+ return false;
14825
+ }
14826
+ return false;
14827
+ };
14828
+ const noCreateContextInRender = defineRule({
14829
+ id: "no-create-context-in-render",
14830
+ severity: "error",
14831
+ category: "Correctness",
14832
+ recommendation: "Move `createContext(...)` to module scope so its identity is stable across renders.",
14833
+ create: (context) => ({ CallExpression(node) {
14834
+ if (!isCreateContextCallee(node.callee)) return;
14835
+ const componentOrHookName = enclosingComponentOrHookName(node);
14836
+ if (!componentOrHookName) return;
14837
+ context.report({
14838
+ node,
14839
+ message: `${MESSAGE$23} (called inside "${componentOrHookName}")`
14840
+ });
14841
+ } })
14842
+ });
14843
+ //#endregion
14844
+ //#region src/plugin/rules/state-and-effects/no-create-store-in-render.ts
14845
+ const STORE_FACTORIES = [
14846
+ {
14847
+ module: "zustand",
14848
+ exportedName: "create",
14849
+ humanLabel: "zustand.create"
14850
+ },
14851
+ {
14852
+ module: "zustand",
14853
+ exportedName: "createStore",
14854
+ humanLabel: "zustand.createStore"
14855
+ },
14856
+ {
14857
+ module: "zustand/vanilla",
14858
+ exportedName: "createStore",
14859
+ humanLabel: "zustand.createStore"
14860
+ },
14861
+ {
14862
+ module: "zustand/vanilla",
14863
+ exportedName: "create",
14864
+ humanLabel: "zustand.create"
14865
+ },
14866
+ {
14867
+ module: "redux",
14868
+ exportedName: "createStore",
14869
+ humanLabel: "redux.createStore"
14870
+ },
14871
+ {
14872
+ module: "@reduxjs/toolkit",
14873
+ exportedName: "configureStore",
14874
+ humanLabel: "@reduxjs/toolkit.configureStore"
14875
+ },
14876
+ {
14877
+ module: "@reduxjs/toolkit",
14878
+ exportedName: "createSlice",
14879
+ humanLabel: "createSlice"
14880
+ },
14881
+ {
14882
+ module: "jotai",
14883
+ exportedName: "atom",
14884
+ humanLabel: "jotai.atom"
14885
+ },
14886
+ {
14887
+ module: "jotai/vanilla",
14888
+ exportedName: "atom",
14889
+ humanLabel: "jotai.atom"
14890
+ },
14891
+ {
14892
+ module: "jotai",
14893
+ exportedName: "createStore",
14894
+ humanLabel: "jotai.createStore"
14895
+ },
14896
+ {
14897
+ module: "valtio",
14898
+ exportedName: "proxy",
14899
+ humanLabel: "valtio.proxy"
14900
+ },
14901
+ {
14902
+ module: "valtio/vanilla",
14903
+ exportedName: "proxy",
14904
+ humanLabel: "valtio.proxy"
14905
+ },
14906
+ {
14907
+ module: "mobx",
14908
+ exportedName: "observable",
14909
+ humanLabel: "mobx.observable"
14910
+ },
14911
+ {
14912
+ module: "mobx",
14913
+ exportedName: "makeAutoObservable",
14914
+ humanLabel: "mobx.makeAutoObservable"
14915
+ },
14916
+ {
14917
+ module: "mobx",
14918
+ exportedName: "makeObservable",
14919
+ humanLabel: "mobx.makeObservable"
14920
+ },
14921
+ {
14922
+ module: "nanostores",
14923
+ exportedName: "atom",
14924
+ humanLabel: "nanostores.atom"
14925
+ },
14926
+ {
14927
+ module: "nanostores",
14928
+ exportedName: "map",
14929
+ humanLabel: "nanostores.map"
14930
+ },
14931
+ {
14932
+ module: "@xstate/store",
14933
+ exportedName: "createStore",
14934
+ humanLabel: "@xstate/store.createStore"
14935
+ }
14936
+ ];
14937
+ const STORE_FACTORY_LOOKUP = /* @__PURE__ */ new Map();
14938
+ for (const factory of STORE_FACTORIES) {
14939
+ const bucket = STORE_FACTORY_LOOKUP.get(factory.exportedName) ?? [];
14940
+ STORE_FACTORY_LOOKUP.set(factory.exportedName, [...bucket, factory]);
14941
+ }
14942
+ const resolveStoreFactoryForCallee = (callee) => {
14943
+ if (isNodeOfType(callee, "Identifier")) {
14944
+ const localName = callee.name;
14945
+ for (const factoryBucket of STORE_FACTORY_LOOKUP.values()) for (const factory of factoryBucket) if (getImportedNameFromModule(callee, localName, factory.module) === factory.exportedName) return factory;
14946
+ return null;
14947
+ }
14948
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed) {
14949
+ const namespaceIdentifier = callee.object;
14950
+ const propertyIdentifier = callee.property;
14951
+ if (!isNodeOfType(namespaceIdentifier, "Identifier")) return null;
14952
+ if (!isNodeOfType(propertyIdentifier, "Identifier")) return null;
14953
+ const propertyName = propertyIdentifier.name;
14954
+ const factoryBucket = STORE_FACTORY_LOOKUP.get(propertyName);
14955
+ if (!factoryBucket) return null;
14956
+ for (const factory of factoryBucket) if (isImportedFromModule(namespaceIdentifier, namespaceIdentifier.name, factory.module)) return factory;
14957
+ return null;
14958
+ }
14959
+ return null;
14960
+ };
14961
+ const noCreateStoreInRender = defineRule({
14962
+ id: "no-create-store-in-render",
14963
+ severity: "error",
14964
+ category: "Correctness",
14965
+ recommendation: "Hoist the store/atom/observable construction to module scope — render functions and hooks must not allocate state containers.",
14966
+ create: (context) => ({ CallExpression(node) {
14967
+ const factory = resolveStoreFactoryForCallee(node.callee);
14968
+ if (!factory) return;
14969
+ const componentOrHookName = enclosingComponentOrHookName(node);
14970
+ if (!componentOrHookName) return;
14971
+ context.report({
14972
+ node,
14973
+ message: `\`${factory.humanLabel}(...)\` called inside "${componentOrHookName}" allocates a fresh state container on every render — subscribers disconnect, identities (action creators, reducer reference, store instance) churn, and persisted state resets. Hoist the call to module scope.`
14974
+ });
14975
+ } })
14976
+ });
14977
+ //#endregion
14675
14978
  //#region src/plugin/rules/react-builtins/no-danger.ts
14676
- const MESSAGE$21 = "Do not use `dangerouslySetInnerHTML` — it injects raw HTML and is a common XSS vector.";
14979
+ const MESSAGE$22 = "Do not use `dangerouslySetInnerHTML` — it injects raw HTML and is a common XSS vector.";
14677
14980
  const noDanger = defineRule({
14678
14981
  id: "no-danger",
14679
14982
  severity: "warn",
@@ -14684,7 +14987,7 @@ const noDanger = defineRule({
14684
14987
  if (!propAttribute) return;
14685
14988
  context.report({
14686
14989
  node: propAttribute.name,
14687
- message: MESSAGE$21
14990
+ message: MESSAGE$22
14688
14991
  });
14689
14992
  },
14690
14993
  CallExpression(node) {
@@ -14696,7 +14999,7 @@ const noDanger = defineRule({
14696
14999
  const propertyKey = property.key;
14697
15000
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
14698
15001
  node: propertyKey,
14699
- message: MESSAGE$21
15002
+ message: MESSAGE$22
14700
15003
  });
14701
15004
  }
14702
15005
  }
@@ -14704,7 +15007,7 @@ const noDanger = defineRule({
14704
15007
  });
14705
15008
  //#endregion
14706
15009
  //#region src/plugin/rules/react-builtins/no-danger-with-children.ts
14707
- const MESSAGE$20 = "Only set one of `children` or `dangerouslySetInnerHTML` — React throws a runtime warning when both are present.";
15010
+ const MESSAGE$21 = "Only set one of `children` or `dangerouslySetInnerHTML` — React throws a runtime warning when both are present.";
14708
15011
  const isLineBreak = (child) => {
14709
15012
  if (!isNodeOfType(child, "JSXText")) return false;
14710
15013
  return child.value.trim().length === 0 && child.value.includes("\n");
@@ -14773,7 +15076,7 @@ const noDangerWithChildren = defineRule({
14773
15076
  if (!hasChildrenProp && !hasNestedChildren) return;
14774
15077
  if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
14775
15078
  node: opening,
14776
- message: MESSAGE$20
15079
+ message: MESSAGE$21
14777
15080
  });
14778
15081
  },
14779
15082
  CallExpression(node) {
@@ -14785,7 +15088,7 @@ const noDangerWithChildren = defineRule({
14785
15088
  if (!propsShape.hasDangerously) return;
14786
15089
  if (node.arguments.length >= 3 || propsShape.hasChildren) context.report({
14787
15090
  node,
14788
- message: MESSAGE$20
15091
+ message: MESSAGE$21
14789
15092
  });
14790
15093
  }
14791
15094
  })
@@ -15160,7 +15463,7 @@ const extractDestructuredPropNames = (params) => {
15160
15463
  };
15161
15464
  const getInlineFunctionNode = (node) => {
15162
15465
  if (!node) return null;
15163
- if (isFunctionLike$1(node)) return node;
15466
+ if (isFunctionLike$2(node)) return node;
15164
15467
  if (!isNodeOfType(node, "CallExpression")) return null;
15165
15468
  for (const argument of node.arguments ?? []) {
15166
15469
  const inlineFunctionNode = getInlineFunctionNode(argument);
@@ -15171,7 +15474,7 @@ const getInlineFunctionNode = (node) => {
15171
15474
  const getNearestComponentFunction = (node) => {
15172
15475
  let cursor = node.parent ?? null;
15173
15476
  while (cursor) {
15174
- if (isFunctionLike$1(cursor)) return cursor;
15477
+ if (isFunctionLike$2(cursor)) return cursor;
15175
15478
  cursor = cursor.parent ?? null;
15176
15479
  }
15177
15480
  return null;
@@ -15352,7 +15655,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
15352
15655
  //#endregion
15353
15656
  //#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
15354
15657
  const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
15355
- const MESSAGE$19 = "Do not use `this.setState` in `componentDidMount`.";
15658
+ const MESSAGE$20 = "Do not use `this.setState` in `componentDidMount`.";
15356
15659
  const resolveSettings$20 = (settings) => {
15357
15660
  const reactDoctor = settings?.["react-doctor"];
15358
15661
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidMountSetState ?? {} : {}).mode ?? "allowed" };
@@ -15370,7 +15673,7 @@ const noDidMountSetState = defineRule({
15370
15673
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$2, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
15371
15674
  context.report({
15372
15675
  node: node.callee,
15373
- message: MESSAGE$19
15676
+ message: MESSAGE$20
15374
15677
  });
15375
15678
  } };
15376
15679
  }
@@ -15378,7 +15681,7 @@ const noDidMountSetState = defineRule({
15378
15681
  //#endregion
15379
15682
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
15380
15683
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
15381
- const MESSAGE$18 = "Do not use `this.setState` in `componentDidUpdate` — it can cause infinite loops.";
15684
+ const MESSAGE$19 = "Do not use `this.setState` in `componentDidUpdate` — it can cause infinite loops.";
15382
15685
  const resolveSettings$19 = (settings) => {
15383
15686
  const reactDoctor = settings?.["react-doctor"];
15384
15687
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidUpdateSetState ?? {} : {}).mode ?? "allowed" };
@@ -15396,7 +15699,7 @@ const noDidUpdateSetState = defineRule({
15396
15699
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
15397
15700
  context.report({
15398
15701
  node: node.callee,
15399
- message: MESSAGE$18
15702
+ message: MESSAGE$19
15400
15703
  });
15401
15704
  } };
15402
15705
  }
@@ -15419,7 +15722,7 @@ const isStateMemberExpression = (node) => {
15419
15722
  };
15420
15723
  //#endregion
15421
15724
  //#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
15422
- const MESSAGE$17 = "Never mutate `this.state` directly.";
15725
+ const MESSAGE$18 = "Never mutate `this.state` directly.";
15423
15726
  const shouldIgnoreMutation = (node) => {
15424
15727
  let isConstructor = false;
15425
15728
  let isInsideCallExpression = false;
@@ -15441,7 +15744,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
15441
15744
  if (shouldIgnoreMutation(reportNode)) return;
15442
15745
  context.report({
15443
15746
  node: reportNode,
15444
- message: MESSAGE$17
15747
+ message: MESSAGE$18
15445
15748
  });
15446
15749
  };
15447
15750
  const noDirectMutationState = defineRule({
@@ -15497,7 +15800,7 @@ const collectFunctionLocalBindings = (functionNode) => {
15497
15800
  const walkComponentRespectingShadows = (node, shadowedStateNames, visit) => {
15498
15801
  if (!node || typeof node !== "object") return;
15499
15802
  let nextShadowedStateNames = shadowedStateNames;
15500
- if (isFunctionLike$1(node)) {
15803
+ if (isFunctionLike$2(node)) {
15501
15804
  const localBindings = collectFunctionLocalBindings(node);
15502
15805
  if (localBindings.size > 0) {
15503
15806
  const merged = new Set(shadowedStateNames);
@@ -15543,7 +15846,7 @@ const noDirectStateMutation = defineRule({
15543
15846
  if (!isNodeOfType(callee, "MemberExpression")) return;
15544
15847
  if (!isNodeOfType(callee.property, "Identifier")) return;
15545
15848
  const methodName = callee.property.name;
15546
- if (!MUTATING_ARRAY_METHODS$1.has(methodName)) return;
15849
+ if (!MUTATING_ARRAY_METHODS.has(methodName)) return;
15547
15850
  const rootName = getRootIdentifierName(callee.object);
15548
15851
  if (!rootName || !stateValueToSetter.has(rootName)) return;
15549
15852
  if (currentlyShadowed.has(rootName)) return;
@@ -15604,7 +15907,7 @@ const noDisabledZoom = defineRule({
15604
15907
  });
15605
15908
  //#endregion
15606
15909
  //#region src/plugin/rules/a11y/no-distracting-elements.ts
15607
- const buildMessage$15 = (tag) => `\`<${tag}>\` is distracting and should not be used — replace with semantic, accessible markup.`;
15910
+ const buildMessage$16 = (tag) => `\`<${tag}>\` is distracting and should not be used — replace with semantic, accessible markup.`;
15608
15911
  const DEFAULT_DISTRACTING = ["marquee", "blink"];
15609
15912
  const resolveSettings$18 = (settings) => {
15610
15913
  const reactDoctor = settings?.["react-doctor"];
@@ -15624,7 +15927,7 @@ const noDistractingElements = defineRule({
15624
15927
  const tag = getElementType(node, context.settings);
15625
15928
  if (distractingTags.has(tag)) context.report({
15626
15929
  node: node.name,
15627
- message: buildMessage$15(tag)
15930
+ message: buildMessage$16(tag)
15628
15931
  });
15629
15932
  } };
15630
15933
  }
@@ -16064,6 +16367,69 @@ const noEffectEventInDeps = defineRule({
16064
16367
  }
16065
16368
  });
16066
16369
  //#endregion
16370
+ //#region src/plugin/rules/state-and-effects/no-effect-with-fresh-deps.ts
16371
+ const classifyFreshDependency = (expression) => {
16372
+ const stripped = stripParenExpression(expression);
16373
+ if (isNodeOfType(stripped, "ObjectExpression")) return "object";
16374
+ if (isNodeOfType(stripped, "ArrayExpression")) return "array";
16375
+ if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "FunctionExpression")) return "function";
16376
+ if (isNodeOfType(stripped, "JSXElement") || isNodeOfType(stripped, "JSXFragment")) return "JSX";
16377
+ if (isNodeOfType(stripped, "NewExpression")) return "instance";
16378
+ return null;
16379
+ };
16380
+ const resolveDependencyFreshness = (dep) => {
16381
+ const directKind = classifyFreshDependency(dep);
16382
+ if (directKind) return {
16383
+ kind: directKind,
16384
+ viaBindingName: null
16385
+ };
16386
+ const stripped = stripParenExpression(dep);
16387
+ if (!isNodeOfType(stripped, "Identifier")) return null;
16388
+ const binding = findVariableInitializer(stripped, stripped.name);
16389
+ if (!binding || !binding.initializer) return null;
16390
+ if (binding.scopeOwner.type === "Program") return null;
16391
+ const declarator = binding.bindingIdentifier.parent;
16392
+ if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== binding.initializer) return null;
16393
+ const indirectKind = classifyFreshDependency(binding.initializer);
16394
+ if (!indirectKind) return null;
16395
+ return {
16396
+ kind: indirectKind,
16397
+ viaBindingName: stripped.name
16398
+ };
16399
+ };
16400
+ const noEffectWithFreshDeps = defineRule({
16401
+ id: "no-effect-with-fresh-deps",
16402
+ severity: "error",
16403
+ category: "State & Effects",
16404
+ recommendation: "Move the constructed value into the hook body (so it's recomputed during render) and instead depend on its primitive inputs, or wrap the value in useMemo / useCallback so its reference is stable.",
16405
+ create: (context) => ({ CallExpression(node) {
16406
+ if (!isHookCall$1(node, HOOKS_WITH_DEPS)) return;
16407
+ const args = node.arguments ?? [];
16408
+ if (args.length < 2) return;
16409
+ const depsNode = args[1];
16410
+ if (!depsNode) return;
16411
+ const stripped = stripParenExpression(depsNode);
16412
+ if (!isNodeOfType(stripped, "ArrayExpression")) return;
16413
+ const calleeNode = node.callee;
16414
+ let hookName;
16415
+ if (isNodeOfType(calleeNode, "Identifier")) hookName = calleeNode.name;
16416
+ else if (isNodeOfType(calleeNode, "MemberExpression") && isNodeOfType(calleeNode.property, "Identifier")) hookName = calleeNode.property.name;
16417
+ else hookName = "hook";
16418
+ const elements = stripped.elements ?? [];
16419
+ for (const element of elements) {
16420
+ if (!element) continue;
16421
+ if (isNodeOfType(element, "SpreadElement")) continue;
16422
+ const freshness = resolveDependencyFreshness(element);
16423
+ if (!freshness) continue;
16424
+ const message = freshness.viaBindingName ? `${hookName} dep array element \`${freshness.viaBindingName}\` is a render-local ${freshness.kind} (declared in the same component scope); \`===\` will always fail because the binding is re-allocated each render. Hoist it to module scope or wrap it in useMemo/useCallback.` : `${hookName} dep array contains a freshly-allocated ${freshness.kind}; \`===\` will always fail on this element so the hook runs every render. Move the value into the hook body or memoize it with useMemo/useCallback so its reference is stable.`;
16425
+ context.report({
16426
+ node: element,
16427
+ message
16428
+ });
16429
+ }
16430
+ } })
16431
+ });
16432
+ //#endregion
16067
16433
  //#region src/plugin/rules/security/no-eval.ts
16068
16434
  const noEval = defineRule({
16069
16435
  id: "no-eval",
@@ -16954,7 +17320,7 @@ const ALLOWED_NAMESPACES = new Set([
16954
17320
  "ReactDOM",
16955
17321
  "ReactDom"
16956
17322
  ]);
16957
- const MESSAGE$16 = "Unexpected call to `findDOMNode` — removed in React 19.";
17323
+ const MESSAGE$17 = "Unexpected call to `findDOMNode` — removed in React 19.";
16958
17324
  const noFindDomNode = defineRule({
16959
17325
  id: "no-find-dom-node",
16960
17326
  severity: "warn",
@@ -16964,7 +17330,7 @@ const noFindDomNode = defineRule({
16964
17330
  if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
16965
17331
  context.report({
16966
17332
  node: callee,
16967
- message: MESSAGE$16
17333
+ message: MESSAGE$17
16968
17334
  });
16969
17335
  return;
16970
17336
  }
@@ -16975,7 +17341,7 @@ const noFindDomNode = defineRule({
16975
17341
  if (callee.property.name !== "findDOMNode") return;
16976
17342
  context.report({
16977
17343
  node: callee.property,
16978
- message: MESSAGE$16
17344
+ message: MESSAGE$17
16979
17345
  });
16980
17346
  }
16981
17347
  } })
@@ -17465,7 +17831,7 @@ const noInlinePropOnMemoComponent = defineRule({
17465
17831
  });
17466
17832
  //#endregion
17467
17833
  //#region src/plugin/rules/a11y/no-interactive-element-to-noninteractive-role.ts
17468
- const buildMessage$14 = (tag, role) => `Interactive element \`<${tag}>\` cannot have non-interactive role \`${role}\`.`;
17834
+ const buildMessage$15 = (tag, role) => `Interactive element \`<${tag}>\` cannot have non-interactive role \`${role}\`.`;
17469
17835
  const PRESENTATION_ROLES = ["presentation", "none"];
17470
17836
  const DEFAULT_ALLOWED_ROLES$1 = {
17471
17837
  tr: ["none", "presentation"],
@@ -17509,7 +17875,7 @@ const noInteractiveElementToNoninteractiveRole = defineRule({
17509
17875
  if (!isNonInteractiveRole(firstRole) && !PRESENTATION_ROLES.includes(firstRole)) return;
17510
17876
  context.report({
17511
17877
  node: roleAttribute,
17512
- message: buildMessage$14(elementType, firstRole)
17878
+ message: buildMessage$15(elementType, firstRole)
17513
17879
  });
17514
17880
  } };
17515
17881
  }
@@ -17710,7 +18076,7 @@ const isInsideClassBody = (node) => {
17710
18076
  let current = node.parent;
17711
18077
  while (current) {
17712
18078
  if (isNodeOfType(current, "ClassBody")) return true;
17713
- if (isFunctionLike$1(current)) return false;
18079
+ if (isFunctionLike$2(current)) return false;
17714
18080
  current = current.parent;
17715
18081
  }
17716
18082
  return false;
@@ -17953,7 +18319,7 @@ const noMoment = defineRule({
17953
18319
  });
17954
18320
  //#endregion
17955
18321
  //#region src/plugin/rules/react-builtins/no-multi-comp.ts
17956
- const buildMessage$13 = (componentName) => `Declare only one React component per file. Found extra component: ${componentName}.`;
18322
+ const buildMessage$14 = (componentName) => `Declare only one React component per file. Found extra component: ${componentName}.`;
17957
18323
  const resolveSettings$16 = (settings) => {
17958
18324
  const reactDoctor = settings?.["react-doctor"];
17959
18325
  return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
@@ -18274,7 +18640,7 @@ const noMultiComp = defineRule({
18274
18640
  if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
18275
18641
  for (const component of flagged.slice(1)) context.report({
18276
18642
  node: component.reportNode,
18277
- message: buildMessage$13(component.name)
18643
+ message: buildMessage$14(component.name)
18278
18644
  });
18279
18645
  } };
18280
18646
  }
@@ -18351,25 +18717,315 @@ const noMutableInDeps = defineRule({
18351
18717
  }
18352
18718
  });
18353
18719
  //#endregion
18354
- //#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
18355
- const MESSAGE$15 = "Reducer mutates its current state and returns the same reference. Return a copied object or array so React can observe the update.";
18356
- const MUTATING_ARRAY_METHODS = new Set([
18357
- "copyWithin",
18358
- "fill",
18359
- "pop",
18360
- "push",
18361
- "reverse",
18362
- "shift",
18363
- "sort",
18364
- "splice",
18365
- "unshift"
18366
- ]);
18367
- const MUTATING_COLLECTION_METHODS = new Set([
18368
- "add",
18369
- "clear",
18370
- "delete",
18371
- "set"
18720
+ //#region src/plugin/rules/state-and-effects/utils/lodash-mutator-call.ts
18721
+ const LODASH_MUTATOR_NAMES = new Set([
18722
+ "set",
18723
+ "unset",
18724
+ "update",
18725
+ "merge",
18726
+ "defaults",
18727
+ "defaultsDeep",
18728
+ "assign",
18729
+ "assignIn",
18730
+ "pull",
18731
+ "pullAll",
18732
+ "pullAllBy",
18733
+ "pullAt",
18734
+ "remove",
18735
+ "fill"
18372
18736
  ]);
18737
+ const LODASH_MUTATING_MODULES = new Set(["lodash", "lodash-es"]);
18738
+ const isLodashMutatingImport = (sourceValue) => {
18739
+ if (LODASH_MUTATING_MODULES.has(sourceValue)) return true;
18740
+ if (sourceValue.startsWith("lodash/fp") || sourceValue.startsWith("lodash-es/fp")) return false;
18741
+ if (sourceValue.startsWith("lodash/") || sourceValue.startsWith("lodash-es/")) return true;
18742
+ return false;
18743
+ };
18744
+ const isLodashMutatorCall = (callExpression) => {
18745
+ if (!isNodeOfType(callExpression, "CallExpression")) return false;
18746
+ const callee = callExpression.callee;
18747
+ if (isNodeOfType(callee, "Identifier")) {
18748
+ if (!LODASH_MUTATOR_NAMES.has(callee.name)) return false;
18749
+ const initializer = findVariableInitializer(callee, callee.name)?.initializer;
18750
+ if (!initializer) return false;
18751
+ if (!isNodeOfType(initializer, "ImportSpecifier") && !isNodeOfType(initializer, "ImportDefaultSpecifier") && !isNodeOfType(initializer, "ImportNamespaceSpecifier")) return false;
18752
+ const importDeclaration = initializer.parent;
18753
+ if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration")) return false;
18754
+ const sourceValue = importDeclaration.source?.value;
18755
+ if (typeof sourceValue !== "string") return false;
18756
+ return isLodashMutatingImport(sourceValue);
18757
+ }
18758
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed) {
18759
+ const propertyName = getStaticMemberPropertyName(callee);
18760
+ if (!propertyName || !LODASH_MUTATOR_NAMES.has(propertyName)) return false;
18761
+ const receiver = callee.object;
18762
+ if (!isNodeOfType(receiver, "Identifier")) return false;
18763
+ const initializer = findVariableInitializer(receiver, receiver.name)?.initializer;
18764
+ if (!initializer) return false;
18765
+ if (!isNodeOfType(initializer, "ImportNamespaceSpecifier") && !isNodeOfType(initializer, "ImportDefaultSpecifier")) return false;
18766
+ const importDeclaration = initializer.parent;
18767
+ if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration")) return false;
18768
+ const sourceValue = importDeclaration.source?.value;
18769
+ if (typeof sourceValue !== "string") return false;
18770
+ return isLodashMutatingImport(sourceValue);
18771
+ }
18772
+ return false;
18773
+ };
18774
+ //#endregion
18775
+ //#region src/plugin/utils/find-exported-function-body.ts
18776
+ const isFunctionLike = (node) => {
18777
+ if (!node) return false;
18778
+ return isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression");
18779
+ };
18780
+ const findExportedFunctionBody = (programRoot, exportedName) => {
18781
+ if (!isNodeOfType(programRoot, "Program")) return null;
18782
+ const localBindings = /* @__PURE__ */ new Map();
18783
+ const namedExports = /* @__PURE__ */ new Map();
18784
+ let defaultExport = null;
18785
+ let defaultExportIdentifierName = null;
18786
+ const recordVariableDeclaration = (declaration) => {
18787
+ for (const declarator of declaration.declarations ?? []) {
18788
+ if (!isNodeOfType(declarator, "VariableDeclarator")) continue;
18789
+ if (!isNodeOfType(declarator.id, "Identifier")) continue;
18790
+ const initializer = declarator.init ? stripParenExpression(declarator.init) : null;
18791
+ if (initializer && isFunctionLike(initializer)) localBindings.set(declarator.id.name, initializer);
18792
+ }
18793
+ };
18794
+ for (const statement of programRoot.body ?? []) {
18795
+ if (isNodeOfType(statement, "VariableDeclaration")) {
18796
+ recordVariableDeclaration(statement);
18797
+ continue;
18798
+ }
18799
+ if (isNodeOfType(statement, "FunctionDeclaration") && statement.id) {
18800
+ localBindings.set(statement.id.name, statement);
18801
+ continue;
18802
+ }
18803
+ if (isNodeOfType(statement, "ExportNamedDeclaration")) {
18804
+ const declaration = statement.declaration;
18805
+ if (declaration && isNodeOfType(declaration, "VariableDeclaration")) {
18806
+ recordVariableDeclaration(declaration);
18807
+ for (const declarator of declaration.declarations ?? []) {
18808
+ if (!isNodeOfType(declarator, "VariableDeclarator")) continue;
18809
+ if (!isNodeOfType(declarator.id, "Identifier")) continue;
18810
+ namedExports.set(declarator.id.name, declarator.id.name);
18811
+ }
18812
+ } else if (declaration && isNodeOfType(declaration, "FunctionDeclaration") && declaration.id) {
18813
+ localBindings.set(declaration.id.name, declaration);
18814
+ namedExports.set(declaration.id.name, declaration.id.name);
18815
+ }
18816
+ for (const specifier of statement.specifiers ?? []) {
18817
+ if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
18818
+ const local = specifier.local;
18819
+ const exported = specifier.exported;
18820
+ if (!isNodeOfType(local, "Identifier")) continue;
18821
+ const exportedNameSpec = isNodeOfType(exported, "Identifier") ? exported.name : isNodeOfType(exported, "Literal") && typeof exported.value === "string" ? exported.value : null;
18822
+ if (!exportedNameSpec) continue;
18823
+ namedExports.set(exportedNameSpec, local.name);
18824
+ }
18825
+ continue;
18826
+ }
18827
+ if (isNodeOfType(statement, "ExportDefaultDeclaration")) {
18828
+ const declaration = statement.declaration;
18829
+ if (!declaration) continue;
18830
+ if (isNodeOfType(declaration, "FunctionDeclaration") && declaration.id) {
18831
+ localBindings.set(declaration.id.name, declaration);
18832
+ defaultExport = declaration;
18833
+ continue;
18834
+ }
18835
+ if (isFunctionLike(declaration)) {
18836
+ defaultExport = declaration;
18837
+ continue;
18838
+ }
18839
+ if (isNodeOfType(declaration, "Identifier")) {
18840
+ defaultExportIdentifierName = declaration.name;
18841
+ continue;
18842
+ }
18843
+ }
18844
+ }
18845
+ if (exportedName === "default") {
18846
+ if (defaultExport) return defaultExport;
18847
+ if (defaultExportIdentifierName) {
18848
+ const binding = localBindings.get(defaultExportIdentifierName);
18849
+ if (binding) return binding;
18850
+ }
18851
+ }
18852
+ const localName = namedExports.get(exportedName);
18853
+ if (!localName) return null;
18854
+ return localBindings.get(localName) ?? null;
18855
+ };
18856
+ const resolveImportedExportName = (importSpecifier) => {
18857
+ if (isNodeOfType(importSpecifier, "ImportSpecifier")) {
18858
+ const imported = importSpecifier.imported;
18859
+ if (isNodeOfType(imported, "Identifier")) return imported.name;
18860
+ if (isNodeOfType(imported, "Literal") && typeof imported.value === "string") return imported.value;
18861
+ return null;
18862
+ }
18863
+ if (isNodeOfType(importSpecifier, "ImportDefaultSpecifier")) return "default";
18864
+ return null;
18865
+ };
18866
+ const findReExportSourcesForName = (programRoot, exportedName) => {
18867
+ if (!isNodeOfType(programRoot, "Program")) return [];
18868
+ const exportAllSources = [];
18869
+ for (const statement of programRoot.body ?? []) {
18870
+ if (isNodeOfType(statement, "ExportNamedDeclaration") && statement.source) {
18871
+ const sourceValue = statement.source.value;
18872
+ if (typeof sourceValue !== "string") continue;
18873
+ for (const specifier of statement.specifiers ?? []) {
18874
+ if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
18875
+ const exported = specifier.exported;
18876
+ if ((isNodeOfType(exported, "Identifier") ? exported.name : isNodeOfType(exported, "Literal") && typeof exported.value === "string" ? exported.value : null) === exportedName) return [sourceValue];
18877
+ }
18878
+ }
18879
+ if (isNodeOfType(statement, "ExportAllDeclaration") && statement.source) {
18880
+ const sourceValue = statement.source.value;
18881
+ if (typeof sourceValue === "string") exportAllSources.push(sourceValue);
18882
+ }
18883
+ }
18884
+ return exportAllSources;
18885
+ };
18886
+ //#endregion
18887
+ //#region src/plugin/utils/attach-parent-references.ts
18888
+ const attachParentReferences = (root) => {
18889
+ const visit = (node, parent) => {
18890
+ const writableNode = node;
18891
+ writableNode.parent = parent;
18892
+ const nodeRecord = node;
18893
+ for (const key of Object.keys(nodeRecord)) {
18894
+ if (key === "parent") continue;
18895
+ const child = nodeRecord[key];
18896
+ if (Array.isArray(child)) {
18897
+ for (const item of child) if (isAstNode(item)) visit(item, node);
18898
+ } else if (isAstNode(child)) visit(child, node);
18899
+ }
18900
+ };
18901
+ visit(root, null);
18902
+ };
18903
+ //#endregion
18904
+ //#region src/plugin/utils/parse-source-file.ts
18905
+ const FILENAME_TO_LANG = {
18906
+ ".ts": "ts",
18907
+ ".tsx": "tsx",
18908
+ ".js": "js",
18909
+ ".jsx": "jsx",
18910
+ ".mjs": "js",
18911
+ ".cjs": "js",
18912
+ ".mts": "ts",
18913
+ ".cts": "ts"
18914
+ };
18915
+ const resolveLang = (filename) => {
18916
+ return FILENAME_TO_LANG[path.extname(filename).toLowerCase()] ?? "tsx";
18917
+ };
18918
+ const parseCache = /* @__PURE__ */ new Map();
18919
+ const parseSourceFile = (absoluteFilePath) => {
18920
+ let fileStat;
18921
+ try {
18922
+ fileStat = fs.statSync(absoluteFilePath);
18923
+ } catch {
18924
+ return null;
18925
+ }
18926
+ if (!fileStat.isFile()) return null;
18927
+ if (fileStat.size > 2e6) return null;
18928
+ const cached = parseCache.get(absoluteFilePath);
18929
+ if (cached && cached.mtimeMs === fileStat.mtimeMs && cached.size === fileStat.size) return cached.program;
18930
+ if (absoluteFilePath.endsWith(".d.ts") || absoluteFilePath.endsWith(".d.mts") || absoluteFilePath.endsWith(".d.cts")) {
18931
+ parseCache.set(absoluteFilePath, {
18932
+ mtimeMs: fileStat.mtimeMs,
18933
+ size: fileStat.size,
18934
+ program: null
18935
+ });
18936
+ return null;
18937
+ }
18938
+ let sourceText;
18939
+ try {
18940
+ sourceText = fs.readFileSync(absoluteFilePath, "utf8");
18941
+ } catch {
18942
+ parseCache.set(absoluteFilePath, {
18943
+ mtimeMs: fileStat.mtimeMs,
18944
+ size: fileStat.size,
18945
+ program: null
18946
+ });
18947
+ return null;
18948
+ }
18949
+ let parsedProgram = null;
18950
+ try {
18951
+ const result = parseSync(absoluteFilePath, sourceText, {
18952
+ astType: "ts",
18953
+ lang: resolveLang(absoluteFilePath)
18954
+ });
18955
+ if (!result.errors.some((parseError) => parseError.severity === "Error")) {
18956
+ parsedProgram = result.program;
18957
+ attachParentReferences(parsedProgram);
18958
+ }
18959
+ } catch {
18960
+ parsedProgram = null;
18961
+ }
18962
+ parseCache.set(absoluteFilePath, {
18963
+ mtimeMs: fileStat.mtimeMs,
18964
+ size: fileStat.size,
18965
+ program: parsedProgram
18966
+ });
18967
+ return parsedProgram;
18968
+ };
18969
+ //#endregion
18970
+ //#region src/plugin/rules/state-and-effects/utils/resolve-reducer-function.ts
18971
+ const resolveFunctionExportInFile = (filePath, exportedName, visitedFilePaths) => {
18972
+ if (visitedFilePaths.size >= 4) return null;
18973
+ if (visitedFilePaths.has(filePath)) return null;
18974
+ visitedFilePaths.add(filePath);
18975
+ const actualFilePath = resolveBarrelExportFilePath(filePath, exportedName) ?? filePath;
18976
+ const programRoot = parseSourceFile(actualFilePath);
18977
+ if (!programRoot) return null;
18978
+ const exported = findExportedFunctionBody(programRoot, exportedName);
18979
+ if (exported) return exported;
18980
+ for (const reExportSource of findReExportSourcesForName(programRoot, exportedName)) {
18981
+ const nextFilePath = resolveRelativeImportPath(actualFilePath, reExportSource);
18982
+ if (!nextFilePath) continue;
18983
+ const resolved = resolveFunctionExportInFile(nextFilePath, exportedName, visitedFilePaths);
18984
+ if (resolved) return resolved;
18985
+ }
18986
+ return null;
18987
+ };
18988
+ const resolveCrossFileFunctionExport = (fromFilename, source, exportedName) => {
18989
+ const resolvedFilePath = resolveRelativeImportPath(fromFilename, source);
18990
+ if (!resolvedFilePath) return null;
18991
+ return resolveFunctionExportInFile(resolvedFilePath, exportedName, /* @__PURE__ */ new Set());
18992
+ };
18993
+ const resolveReducerFunction = (node, currentFilename) => {
18994
+ if (!node) return null;
18995
+ const unwrappedNode = stripParenExpression(node);
18996
+ if (isFunctionLike$2(unwrappedNode)) return {
18997
+ functionNode: unwrappedNode,
18998
+ crossFileSourceDisplay: null
18999
+ };
19000
+ if (!isNodeOfType(unwrappedNode, "Identifier")) return null;
19001
+ const initializer = findVariableInitializer(unwrappedNode, unwrappedNode.name)?.initializer;
19002
+ if (!initializer) return null;
19003
+ const unwrappedInitializer = stripParenExpression(initializer);
19004
+ if (isFunctionLike$2(unwrappedInitializer)) return {
19005
+ functionNode: unwrappedInitializer,
19006
+ crossFileSourceDisplay: null
19007
+ };
19008
+ if (isNodeOfType(initializer, "ImportSpecifier") || isNodeOfType(initializer, "ImportDefaultSpecifier")) {
19009
+ if (!currentFilename) return null;
19010
+ const importDeclaration = initializer.parent;
19011
+ if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration")) return null;
19012
+ const sourceValue = importDeclaration.source?.value;
19013
+ if (typeof sourceValue !== "string") return null;
19014
+ if (!sourceValue.startsWith(".") && !sourceValue.startsWith("/")) return null;
19015
+ const exportedName = resolveImportedExportName(initializer);
19016
+ if (!exportedName) return null;
19017
+ const crossFileFunction = resolveCrossFileFunctionExport(currentFilename, sourceValue, exportedName);
19018
+ if (!crossFileFunction) return null;
19019
+ return {
19020
+ functionNode: crossFileFunction,
19021
+ crossFileSourceDisplay: sourceValue
19022
+ };
19023
+ }
19024
+ return null;
19025
+ };
19026
+ //#endregion
19027
+ //#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
19028
+ const MESSAGE$16 = "Reducer mutates its current state and returns the same reference. Return a copied object or array so React can observe the update.";
18373
19029
  const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
18374
19030
  "copyWithin",
18375
19031
  "fill",
@@ -18388,7 +19044,6 @@ const cloneReducerPathState = (state) => ({
18388
19044
  mutableStateSourceNames: new Set(state.mutableStateSourceNames),
18389
19045
  mutations: [...state.mutations]
18390
19046
  });
18391
- const isFunctionLikeAstNode = (node) => Boolean(node && (isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression")));
18392
19047
  const isSpecifierImportedFromReact = (node) => {
18393
19048
  const parent = node.parent ?? null;
18394
19049
  return parent !== null && isNodeOfType(parent, "ImportDeclaration") && parent.source.value === "react";
@@ -18415,19 +19070,16 @@ const isCallToImportedReactUseReducer = (node) => {
18415
19070
  const binding = findVariableInitializer(callee.object, callee.object.name);
18416
19071
  return Boolean(binding?.initializer && isReactNamespaceOrDefaultImportSpecifier(binding.initializer));
18417
19072
  };
18418
- const resolveSameFileReducerFunction = (node) => {
18419
- if (!node) return null;
18420
- const unwrappedNode = stripParenExpression(node);
18421
- if (isFunctionLikeAstNode(unwrappedNode)) return unwrappedNode;
18422
- if (!isNodeOfType(unwrappedNode, "Identifier")) return null;
18423
- const initializer = findVariableInitializer(unwrappedNode, unwrappedNode.name)?.initializer;
18424
- if (!initializer) return null;
18425
- const unwrappedInitializer = stripParenExpression(initializer);
18426
- return isFunctionLikeAstNode(unwrappedInitializer) ? unwrappedInitializer : null;
18427
- };
18428
19073
  const isStaticMethodCallOnNamedObject = (node, objectName, methodNames) => {
18429
19074
  const unwrappedNode = stripParenExpression(node);
18430
- return Boolean(isNodeOfType(unwrappedNode, "CallExpression") && isNodeOfType(unwrappedNode.callee, "MemberExpression") && isNodeOfType(unwrappedNode.callee.object, "Identifier") && unwrappedNode.callee.object.name === objectName && methodNames.has(getStaticMemberPropertyName(unwrappedNode.callee) ?? ""));
19075
+ if (!isNodeOfType(unwrappedNode, "CallExpression")) return false;
19076
+ if (!isNodeOfType(unwrappedNode.callee, "MemberExpression")) return false;
19077
+ const calleeObject = unwrappedNode.callee.object;
19078
+ if (!isNodeOfType(calleeObject, "Identifier")) return false;
19079
+ if (calleeObject.name !== objectName) return false;
19080
+ if (!methodNames.has(getStaticMemberPropertyName(unwrappedNode.callee) ?? "")) return false;
19081
+ if (findVariableInitializer(calleeObject, calleeObject.name)) return false;
19082
+ return true;
18431
19083
  };
18432
19084
  const isExpressionRootedInMutableReducerStateSource = (node, state) => {
18433
19085
  let current = stripParenExpression(node);
@@ -18463,11 +19115,11 @@ const canExpressionReturnOriginalReducerStateReference = (node, state) => {
18463
19115
  return false;
18464
19116
  };
18465
19117
  const collectReducerStateMutationsInExpressionOrStatement = (node, state) => {
18466
- if (isFunctionLikeAstNode(node)) return [];
19118
+ if (isFunctionLike$2(node)) return [];
18467
19119
  const mutations = [];
18468
19120
  walkAst(node, (child) => {
18469
19121
  const unwrappedChild = stripParenExpression(child);
18470
- if (child !== node && isFunctionLikeAstNode(unwrappedChild)) return false;
19122
+ if (child !== node && isFunctionLike$2(unwrappedChild)) return false;
18471
19123
  if (isNodeOfType(unwrappedChild, "AssignmentExpression")) {
18472
19124
  if (isNodeOfType(stripParenExpression(unwrappedChild.left), "MemberExpression") && isExpressionRootedInMutableReducerStateSource(unwrappedChild.left, state)) mutations.push({ node: unwrappedChild });
18473
19125
  return;
@@ -18486,6 +19138,10 @@ const collectReducerStateMutationsInExpressionOrStatement = (node, state) => {
18486
19138
  mutations.push({ node: unwrappedChild });
18487
19139
  return;
18488
19140
  }
19141
+ if (firstArgument && isExpressionRootedInMutableReducerStateSource(firstArgument, state) && isLodashMutatorCall(unwrappedChild)) {
19142
+ mutations.push({ node: unwrappedChild });
19143
+ return;
19144
+ }
18489
19145
  if (!isNodeOfType(unwrappedChild.callee, "MemberExpression")) return;
18490
19146
  const methodName = getStaticMemberPropertyName(unwrappedChild.callee);
18491
19147
  if (!methodName || !MUTATING_ARRAY_METHODS.has(methodName) && !MUTATING_COLLECTION_METHODS.has(methodName)) return;
@@ -18512,18 +19168,47 @@ const restoreOuterIdentityForBlockScopedNames = (blockState, outerState, blockSc
18512
19168
  }
18513
19169
  return nextState;
18514
19170
  };
19171
+ const recordDestructuredAliasNames = (pattern, state) => {
19172
+ if (isNodeOfType(pattern, "ObjectPattern")) {
19173
+ for (const property of pattern.properties ?? []) {
19174
+ if (!isNodeOfType(property, "Property")) continue;
19175
+ const valueNode = property.value;
19176
+ let leafIdentifier = null;
19177
+ if (isNodeOfType(valueNode, "Identifier")) leafIdentifier = valueNode;
19178
+ else if (isNodeOfType(valueNode, "AssignmentPattern") && isNodeOfType(valueNode.left, "Identifier")) leafIdentifier = valueNode.left;
19179
+ if (!leafIdentifier) continue;
19180
+ state.mutableStateSourceNames.add(leafIdentifier.name);
19181
+ }
19182
+ return;
19183
+ }
19184
+ if (isNodeOfType(pattern, "ArrayPattern")) for (const element of pattern.elements ?? []) {
19185
+ if (!element) continue;
19186
+ if (isNodeOfType(element, "Identifier")) {
19187
+ state.mutableStateSourceNames.add(element.name);
19188
+ continue;
19189
+ }
19190
+ if (isNodeOfType(element, "AssignmentPattern") && isNodeOfType(element.left, "Identifier")) {
19191
+ state.mutableStateSourceNames.add(element.left.name);
19192
+ continue;
19193
+ }
19194
+ if (isNodeOfType(element, "RestElement") && isNodeOfType(element.argument, "Identifier")) {}
19195
+ }
19196
+ };
18515
19197
  const updateReducerStateIdentityForVariableDeclaration = (declaration, state) => {
18516
19198
  for (const declarator of declaration.declarations ?? []) {
18517
- if (!isNodeOfType(declarator.id, "Identifier")) continue;
18518
- const name = declarator.id.name;
18519
- state.originalStateReferenceNames.delete(name);
18520
- state.mutableStateSourceNames.delete(name);
18521
- if (isExpressionOriginalReducerStateReference(declarator.init, state)) {
18522
- state.originalStateReferenceNames.add(name);
18523
- state.mutableStateSourceNames.add(name);
19199
+ if (isNodeOfType(declarator.id, "Identifier")) {
19200
+ const name = declarator.id.name;
19201
+ state.originalStateReferenceNames.delete(name);
19202
+ state.mutableStateSourceNames.delete(name);
19203
+ if (isExpressionOriginalReducerStateReference(declarator.init, state)) {
19204
+ state.originalStateReferenceNames.add(name);
19205
+ state.mutableStateSourceNames.add(name);
19206
+ continue;
19207
+ }
19208
+ if (isExpressionReachableFromOriginalReducerState(declarator.init, state)) state.mutableStateSourceNames.add(name);
18524
19209
  continue;
18525
19210
  }
18526
- if (isExpressionReachableFromOriginalReducerState(declarator.init, state)) state.mutableStateSourceNames.add(name);
19211
+ if ((isNodeOfType(declarator.id, "ObjectPattern") || isNodeOfType(declarator.id, "ArrayPattern")) && isExpressionReachableFromOriginalReducerState(declarator.init, state)) recordDestructuredAliasNames(declarator.id, state);
18527
19212
  }
18528
19213
  };
18529
19214
  const updateReducerStateIdentityForIdentifierAssignment = (assignment, state) => {
@@ -18538,24 +19223,40 @@ const updateReducerStateIdentityForIdentifierAssignment = (assignment, state) =>
18538
19223
  }
18539
19224
  if (isExpressionReachableFromOriginalReducerState(assignment.right, state)) state.mutableStateSourceNames.add(name);
18540
19225
  };
18541
- const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, reportedNodes) => {
18542
- if (!isFunctionLikeAstNode(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return;
19226
+ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, reportedNodes, options) => {
19227
+ if (!isFunctionLike$2(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return;
18543
19228
  const firstParam = functionNode.params?.[0];
18544
19229
  const stateName = isNodeOfType(firstParam, "Identifier") ? firstParam.name : isNodeOfType(firstParam, "AssignmentPattern") && isNodeOfType(firstParam.left, "Identifier") ? firstParam.left.name : null;
18545
19230
  if (!stateName) return;
18546
19231
  const reportReducerStateMutations = (mutations) => {
19232
+ if (mutations.length === 0) return;
19233
+ if (options.crossFileConsumerCallSite && options.crossFileSourceDisplay) {
19234
+ if (reportedNodes.has(options.crossFileConsumerCallSite)) return;
19235
+ reportedNodes.add(options.crossFileConsumerCallSite);
19236
+ context.report({
19237
+ node: options.crossFileConsumerCallSite,
19238
+ message: `${MESSAGE$16} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
19239
+ });
19240
+ return;
19241
+ }
18547
19242
  for (const mutation of mutations) {
18548
19243
  if (reportedNodes.has(mutation.node)) continue;
18549
19244
  reportedNodes.add(mutation.node);
18550
19245
  context.report({
18551
19246
  node: mutation.node,
18552
- message: MESSAGE$15
19247
+ message: MESSAGE$16
18553
19248
  });
18554
19249
  }
18555
19250
  };
19251
+ let pathBudgetExceeded = false;
18556
19252
  const analyzeReducerStatementListByPath = (statements, initialState) => {
19253
+ if (pathBudgetExceeded) return [cloneReducerPathState(initialState)];
18557
19254
  let activeStates = [cloneReducerPathState(initialState)];
18558
19255
  for (const statement of statements) {
19256
+ if (activeStates.length > 1e3) {
19257
+ pathBudgetExceeded = true;
19258
+ break;
19259
+ }
18559
19260
  const nextStates = [];
18560
19261
  for (const activeState of activeStates) {
18561
19262
  if (isNodeOfType(statement, "ReturnStatement")) {
@@ -18624,18 +19325,23 @@ const noMutatingReducerState = defineRule({
18624
19325
  create: (context) => {
18625
19326
  const analyzedReducers = /* @__PURE__ */ new WeakSet();
18626
19327
  const reportedNodes = /* @__PURE__ */ new WeakSet();
19328
+ const currentFilename = context.filename;
18627
19329
  return { CallExpression(node) {
18628
19330
  if (!isCallToImportedReactUseReducer(node)) return;
18629
- const reducerFunction = resolveSameFileReducerFunction(node.arguments?.[0]);
18630
- if (!reducerFunction || analyzedReducers.has(reducerFunction)) return;
18631
- analyzedReducers.add(reducerFunction);
18632
- analyzeReactUseReducerFunctionForStateMutation(context, reducerFunction, reportedNodes);
19331
+ const resolved = resolveReducerFunction(node.arguments?.[0], currentFilename);
19332
+ if (!resolved) return;
19333
+ if (analyzedReducers.has(resolved.functionNode)) return;
19334
+ analyzedReducers.add(resolved.functionNode);
19335
+ analyzeReactUseReducerFunctionForStateMutation(context, resolved.functionNode, reportedNodes, {
19336
+ crossFileConsumerCallSite: resolved.crossFileSourceDisplay ? node : null,
19337
+ crossFileSourceDisplay: resolved.crossFileSourceDisplay
19338
+ });
18633
19339
  } };
18634
19340
  }
18635
19341
  });
18636
19342
  //#endregion
18637
19343
  //#region src/plugin/rules/react-builtins/no-namespace.ts
18638
- const buildMessage$12 = (componentName) => `React component \`${componentName}\` must not be in a namespace — React doesn't support them.`;
19344
+ const buildMessage$13 = (componentName) => `React component \`${componentName}\` must not be in a namespace — React doesn't support them.`;
18639
19345
  const noNamespace = defineRule({
18640
19346
  id: "no-namespace",
18641
19347
  severity: "warn",
@@ -18647,7 +19353,7 @@ const noNamespace = defineRule({
18647
19353
  const fullName = `${namespaced.namespace.name}:${namespaced.name.name}`;
18648
19354
  context.report({
18649
19355
  node: namespaced,
18650
- message: buildMessage$12(fullName)
19356
+ message: buildMessage$13(fullName)
18651
19357
  });
18652
19358
  },
18653
19359
  CallExpression(node) {
@@ -18658,7 +19364,7 @@ const noNamespace = defineRule({
18658
19364
  if (!firstArgument.value.includes(":")) return;
18659
19365
  context.report({
18660
19366
  node: firstArgument,
18661
- message: buildMessage$12(firstArgument.value)
19367
+ message: buildMessage$13(firstArgument.value)
18662
19368
  });
18663
19369
  }
18664
19370
  })
@@ -18702,7 +19408,7 @@ const noNestedComponentDefinition = defineRule({
18702
19408
  });
18703
19409
  //#endregion
18704
19410
  //#region src/plugin/rules/a11y/no-noninteractive-element-interactions.ts
18705
- const buildMessage$11 = (tag) => `Non-interactive element \`<${tag}>\` should not have interactive event handlers — convert to a semantic interactive element or add an interactive role.`;
19411
+ const buildMessage$12 = (tag) => `Non-interactive element \`<${tag}>\` should not have interactive event handlers — convert to a semantic interactive element or add an interactive role.`;
18706
19412
  const INTERACTIVE_HANDLERS = [
18707
19413
  "onClick",
18708
19414
  "onMouseDown",
@@ -18728,13 +19434,13 @@ const noNoninteractiveElementInteractions = defineRule({
18728
19434
  }
18729
19435
  context.report({
18730
19436
  node: node.name,
18731
- message: buildMessage$11(tag)
19437
+ message: buildMessage$12(tag)
18732
19438
  });
18733
19439
  } })
18734
19440
  });
18735
19441
  //#endregion
18736
19442
  //#region src/plugin/rules/a11y/no-noninteractive-element-to-interactive-role.ts
18737
- const buildMessage$10 = (tag, role) => `Non-interactive element \`<${tag}>\` cannot have interactive role \`${role}\` — use a semantic interactive element instead.`;
19443
+ const buildMessage$11 = (tag, role) => `Non-interactive element \`<${tag}>\` cannot have interactive role \`${role}\` — use a semantic interactive element instead.`;
18738
19444
  const DEFAULT_ALLOWED_ROLES = {
18739
19445
  ul: [
18740
19446
  "menu",
@@ -18798,14 +19504,14 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
18798
19504
  if (!isInteractiveRole(firstRole)) return;
18799
19505
  context.report({
18800
19506
  node: roleAttribute,
18801
- message: buildMessage$10(elementType, firstRole)
19507
+ message: buildMessage$11(elementType, firstRole)
18802
19508
  });
18803
19509
  } };
18804
19510
  }
18805
19511
  });
18806
19512
  //#endregion
18807
19513
  //#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
18808
- const MESSAGE$14 = "Don't add `tabIndex` to non-interactive elements — keyboard users would have no expected behavior on focus.";
19514
+ const MESSAGE$15 = "Don't add `tabIndex` to non-interactive elements — keyboard users would have no expected behavior on focus.";
18809
19515
  const resolveSettings$14 = (settings) => {
18810
19516
  const reactDoctor = settings?.["react-doctor"];
18811
19517
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noNoninteractiveTabindex ?? {} : {};
@@ -18832,7 +19538,7 @@ const noNoninteractiveTabindex = defineRule({
18832
19538
  if (numeric === null) {
18833
19539
  if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues) context.report({
18834
19540
  node: tabIndex,
18835
- message: MESSAGE$14
19541
+ message: MESSAGE$15
18836
19542
  });
18837
19543
  return;
18838
19544
  }
@@ -18845,7 +19551,7 @@ const noNoninteractiveTabindex = defineRule({
18845
19551
  if (!roleAttribute) {
18846
19552
  context.report({
18847
19553
  node: tabIndex,
18848
- message: MESSAGE$14
19554
+ message: MESSAGE$15
18849
19555
  });
18850
19556
  return;
18851
19557
  }
@@ -18859,7 +19565,7 @@ const noNoninteractiveTabindex = defineRule({
18859
19565
  }
18860
19566
  context.report({
18861
19567
  node: tabIndex,
18862
- message: MESSAGE$14
19568
+ message: MESSAGE$15
18863
19569
  });
18864
19570
  } };
18865
19571
  }
@@ -19417,7 +20123,7 @@ const getComponentNameFromClassProperty = (node) => {
19417
20123
  if (!isUppercaseName(declarator.id.name)) return null;
19418
20124
  return declarator.id.name;
19419
20125
  };
19420
- const buildMessage$9 = (componentName) => `${componentName}.propTypes — React 19 no longer runs \`propTypes\` checks, so invalid props pass silently. Move the prop contract to TypeScript types and add explicit runtime validation only where data can actually be invalid`;
20126
+ const buildMessage$10 = (componentName) => `${componentName}.propTypes — React 19 no longer runs \`propTypes\` checks, so invalid props pass silently. Move the prop contract to TypeScript types and add explicit runtime validation only where data can actually be invalid`;
19421
20127
  const noPropTypes = defineRule({
19422
20128
  id: "no-prop-types",
19423
20129
  requires: ["react:19"],
@@ -19431,7 +20137,7 @@ const noPropTypes = defineRule({
19431
20137
  if (!componentName) return;
19432
20138
  context.report({
19433
20139
  node: node.left,
19434
- message: buildMessage$9(componentName)
20140
+ message: buildMessage$10(componentName)
19435
20141
  });
19436
20142
  },
19437
20143
  PropertyDefinition(node) {
@@ -19439,7 +20145,7 @@ const noPropTypes = defineRule({
19439
20145
  if (!componentName) return;
19440
20146
  context.report({
19441
20147
  node: node.key,
19442
- message: buildMessage$9(componentName)
20148
+ message: buildMessage$10(componentName)
19443
20149
  });
19444
20150
  }
19445
20151
  })
@@ -19476,8 +20182,94 @@ const noPureBlackBackground = defineRule({
19476
20182
  })
19477
20183
  });
19478
20184
  //#endregion
20185
+ //#region src/plugin/rules/correctness/no-random-key.ts
20186
+ const ALWAYS_FRESH_DIRECT_CALLEES = new Set([
20187
+ "nanoid",
20188
+ "uuid",
20189
+ "uuidv4",
20190
+ "uuidV4",
20191
+ "v4",
20192
+ "cuid",
20193
+ "cuid2",
20194
+ "createId",
20195
+ "ulid",
20196
+ "objectid",
20197
+ "ObjectId",
20198
+ "shortid"
20199
+ ]);
20200
+ const ALWAYS_FRESH_MEMBER_RECEIVERS = new Map([
20201
+ ["Math", new Set(["random"])],
20202
+ ["Date", new Set(["now"])],
20203
+ ["performance", new Set(["now"])],
20204
+ ["crypto", new Set([
20205
+ "randomUUID",
20206
+ "getRandomValues",
20207
+ "randomBytes"
20208
+ ])]
20209
+ ]);
20210
+ const isAlwaysFreshExpression = (expression) => {
20211
+ const stripped = stripParenExpression(expression);
20212
+ if (isNodeOfType(stripped, "NewExpression")) {
20213
+ if (isNodeOfType(stripped.callee, "Identifier") && stripped.callee.name === "Date") return "new Date()";
20214
+ }
20215
+ if (!isNodeOfType(stripped, "CallExpression")) return null;
20216
+ const callee = stripped.callee;
20217
+ if (isNodeOfType(callee, "Identifier")) {
20218
+ if (!ALWAYS_FRESH_DIRECT_CALLEES.has(callee.name)) return null;
20219
+ const binding = findVariableInitializer(callee, callee.name);
20220
+ if (binding?.initializer && !isNodeOfType(binding.initializer, "ImportSpecifier") && !isNodeOfType(binding.initializer, "ImportDefaultSpecifier") && !isNodeOfType(binding.initializer, "ImportNamespaceSpecifier")) return null;
20221
+ return `${callee.name}()`;
20222
+ }
20223
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed) {
20224
+ const receiver = callee.object;
20225
+ const property = callee.property;
20226
+ if (!isNodeOfType(property, "Identifier")) return null;
20227
+ if (isNodeOfType(receiver, "Identifier")) {
20228
+ if (ALWAYS_FRESH_MEMBER_RECEIVERS.get(receiver.name)?.has(property.name)) return `${receiver.name}.${property.name}()`;
20229
+ }
20230
+ }
20231
+ return null;
20232
+ };
20233
+ const variableLabelForUpdateArgument = (argument) => {
20234
+ if (!argument) return "counter";
20235
+ const stripped = stripParenExpression(argument);
20236
+ if (isNodeOfType(stripped, "Identifier")) return stripped.name;
20237
+ if (isNodeOfType(stripped, "MemberExpression") && !stripped.computed && isNodeOfType(stripped.property, "Identifier")) return stripped.property.name;
20238
+ return "counter";
20239
+ };
20240
+ const looksLikeFreshUpdateExpression = (expression) => {
20241
+ const stripped = stripParenExpression(expression);
20242
+ if (isNodeOfType(stripped, "UpdateExpression")) {
20243
+ const label = variableLabelForUpdateArgument(stripped.argument);
20244
+ return stripped.prefix ? `${stripped.operator}${label}` : `${label}${stripped.operator}`;
20245
+ }
20246
+ if (isNodeOfType(stripped, "AssignmentExpression") && (stripped.operator === "+=" || stripped.operator === "-=")) return `${stripped.operator} side-effect`;
20247
+ return null;
20248
+ };
20249
+ const noRandomKey = defineRule({
20250
+ id: "no-random-key",
20251
+ severity: "error",
20252
+ category: "Correctness",
20253
+ recommendation: "Use a stable identifier from the item itself (`item.id`, a hash of the content, or the item's index when the list order is stable). Never derive the key from a fresh-each-call API.",
20254
+ create: (context) => ({ JSXAttribute(node) {
20255
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
20256
+ if (node.name.name !== "key") return;
20257
+ if (!node.value) return;
20258
+ if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
20259
+ const inner = node.value.expression;
20260
+ if (!inner) return;
20261
+ if (inner.type === "JSXEmptyExpression") return;
20262
+ const freshDescription = isAlwaysFreshExpression(inner) ?? looksLikeFreshUpdateExpression(inner);
20263
+ if (!freshDescription) return;
20264
+ context.report({
20265
+ node: node.value,
20266
+ message: `\`key={${freshDescription}}\` produces a new value on every render. Every list item is treated as a brand-new component — React unmounts and remounts the entire subtree, resetting state/focus/scroll and defeating reconciliation. Use a stable id from the item itself.`
20267
+ });
20268
+ } })
20269
+ });
20270
+ //#endregion
19479
20271
  //#region src/plugin/rules/react-builtins/no-react-children.ts
19480
- const MESSAGE$13 = "`React.Children` is uncommon and leads to fragile components.";
20272
+ const MESSAGE$14 = "`React.Children` is uncommon and leads to fragile components.";
19481
20273
  const isChildrenIdentifier = (node, contextNode) => {
19482
20274
  if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
19483
20275
  return isImportedFromModule(contextNode, "Children", "react");
@@ -19502,13 +20294,13 @@ const noReactChildren = defineRule({
19502
20294
  if (isChildrenIdentifier(memberObject, node)) {
19503
20295
  context.report({
19504
20296
  node: calleeOuter,
19505
- message: MESSAGE$13
20297
+ message: MESSAGE$14
19506
20298
  });
19507
20299
  return;
19508
20300
  }
19509
20301
  if (isReactNamespaceMember(memberObject, node)) context.report({
19510
20302
  node: calleeOuter,
19511
- message: MESSAGE$13
20303
+ message: MESSAGE$14
19512
20304
  });
19513
20305
  } })
19514
20306
  });
@@ -19704,7 +20496,7 @@ const getTagsForRole = (role) => {
19704
20496
  };
19705
20497
  //#endregion
19706
20498
  //#region src/plugin/rules/a11y/no-redundant-roles.ts
19707
- const buildMessage$8 = (tag, role) => `\`<${tag}>\` already has implicit role \`${role}\` — remove the redundant \`role\` attribute.`;
20499
+ const buildMessage$9 = (tag, role) => `\`<${tag}>\` already has implicit role \`${role}\` — remove the redundant \`role\` attribute.`;
19708
20500
  const resolveSettings$13 = (settings) => {
19709
20501
  const reactDoctor = settings?.["react-doctor"];
19710
20502
  return { exceptions: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noRedundantRoles ?? {} : {}).exceptions ?? {} };
@@ -19727,14 +20519,14 @@ const noRedundantRoles = defineRule({
19727
20519
  const allowedHere = settings.exceptions[tag] ?? [];
19728
20520
  if (implicitRoles.includes(role) && !allowedHere.includes(role)) context.report({
19729
20521
  node: roleAttr,
19730
- message: buildMessage$8(tag, role)
20522
+ message: buildMessage$9(tag, role)
19731
20523
  });
19732
20524
  } };
19733
20525
  }
19734
20526
  });
19735
20527
  //#endregion
19736
20528
  //#region src/plugin/rules/react-builtins/no-redundant-should-component-update.ts
19737
- const buildMessage$7 = (className) => `${className} does not need \`shouldComponentUpdate\` when extending \`React.PureComponent\`.`;
20529
+ const buildMessage$8 = (className) => `${className} does not need \`shouldComponentUpdate\` when extending \`React.PureComponent\`.`;
19738
20530
  const isPureComponentSuper = (superClass) => {
19739
20531
  if (!superClass) return false;
19740
20532
  if (isNodeOfType(superClass, "Identifier")) return superClass.name === "PureComponent";
@@ -19766,7 +20558,7 @@ const noRedundantShouldComponentUpdate = defineRule({
19766
20558
  const className = classNode.id?.name ?? "<anonymous class>";
19767
20559
  context.report({
19768
20560
  node: reportNode,
19769
- message: buildMessage$7(className)
20561
+ message: buildMessage$8(className)
19770
20562
  });
19771
20563
  };
19772
20564
  return {
@@ -19825,7 +20617,7 @@ const noRenderPropChildren = defineRule({
19825
20617
  });
19826
20618
  //#endregion
19827
20619
  //#region src/plugin/rules/react-builtins/no-render-return-value.ts
19828
- const MESSAGE$12 = "Do not use the return value from `ReactDOM.render`.";
20620
+ const MESSAGE$13 = "Do not use the return value from `ReactDOM.render`.";
19829
20621
  const isReactDomRenderCall = (node) => {
19830
20622
  if (!isNodeOfType(node.callee, "MemberExpression")) return false;
19831
20623
  if (!isNodeOfType(node.callee.object, "Identifier")) return false;
@@ -19848,7 +20640,7 @@ const noRenderReturnValue = defineRule({
19848
20640
  if (!isUsedAsReturnValue(node.parent)) return;
19849
20641
  context.report({
19850
20642
  node: node.callee,
19851
- message: MESSAGE$12
20643
+ message: MESSAGE$13
19852
20644
  });
19853
20645
  } })
19854
20646
  });
@@ -20243,7 +21035,7 @@ const isTanStackServerFnHandler = (node) => {
20243
21035
  const isInsideServerOnlyScope = (node) => {
20244
21036
  let currentNode = node.parent ?? null;
20245
21037
  while (currentNode) {
20246
- if (isFunctionLike$1(currentNode)) {
21038
+ if (isFunctionLike$2(currentNode)) {
20247
21039
  if (hasUseServerDirective(currentNode) || isTanStackServerFnHandler(currentNode)) return true;
20248
21040
  }
20249
21041
  currentNode = currentNode.parent ?? null;
@@ -20308,7 +21100,7 @@ const getParentComponent = (node) => {
20308
21100
  };
20309
21101
  //#endregion
20310
21102
  //#region src/plugin/rules/react-builtins/no-set-state.ts
20311
- const MESSAGE$11 = "Do not use `this.setState` in components.";
21103
+ const MESSAGE$12 = "Do not use `this.setState` in components.";
20312
21104
  const noSetState = defineRule({
20313
21105
  id: "no-set-state",
20314
21106
  severity: "warn",
@@ -20322,7 +21114,7 @@ const noSetState = defineRule({
20322
21114
  if (!getParentComponent(node)) return;
20323
21115
  context.report({
20324
21116
  node: node.callee,
20325
- message: MESSAGE$11
21117
+ message: MESSAGE$12
20326
21118
  });
20327
21119
  } })
20328
21120
  });
@@ -20481,7 +21273,7 @@ const isAbstractRole = (openingElement, settings) => {
20481
21273
  };
20482
21274
  //#endregion
20483
21275
  //#region src/plugin/rules/a11y/no-static-element-interactions.ts
20484
- const MESSAGE$10 = "Static HTML elements with event handlers require a role — add `role=\"…\"` or use a semantic HTML element instead.";
21276
+ const MESSAGE$11 = "Static HTML elements with event handlers require a role — add `role=\"…\"` or use a semantic HTML element instead.";
20485
21277
  const DEFAULT_HANDLERS = [
20486
21278
  "onClick",
20487
21279
  "onMouseDown",
@@ -20540,7 +21332,7 @@ const noStaticElementInteractions = defineRule({
20540
21332
  if (!roleAttribute || !roleAttribute.value) {
20541
21333
  context.report({
20542
21334
  node: node.name,
20543
- message: MESSAGE$10
21335
+ message: MESSAGE$11
20544
21336
  });
20545
21337
  return;
20546
21338
  }
@@ -20550,14 +21342,14 @@ const noStaticElementInteractions = defineRule({
20550
21342
  if (firstRole && (isInteractiveRole(firstRole) || isNonInteractiveRole(firstRole))) return;
20551
21343
  context.report({
20552
21344
  node: node.name,
20553
- message: MESSAGE$10
21345
+ message: MESSAGE$11
20554
21346
  });
20555
21347
  return;
20556
21348
  }
20557
21349
  if (isNodeOfType(attributeValue, "JSXExpressionContainer") && settings.allowExpressionValues) return;
20558
21350
  context.report({
20559
21351
  node: node.name,
20560
- message: MESSAGE$10
21352
+ message: MESSAGE$11
20561
21353
  });
20562
21354
  } };
20563
21355
  }
@@ -20613,7 +21405,7 @@ const noStringRefs = defineRule({
20613
21405
  });
20614
21406
  //#endregion
20615
21407
  //#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
20616
- const MESSAGE$9 = "Stateless functional components shouldn't use `this` — read props/context from function parameters.";
21408
+ const MESSAGE$10 = "Stateless functional components shouldn't use `this` — read props/context from function parameters.";
20617
21409
  const isInsideClassMethod = (node, customClassFactoryNames) => {
20618
21410
  let ancestor = node.parent;
20619
21411
  while (ancestor) {
@@ -20681,7 +21473,7 @@ const noThisInSfc = defineRule({
20681
21473
  if (!looksLikeFunctionComponent(enclosingFunction)) return;
20682
21474
  context.report({
20683
21475
  node,
20684
- message: MESSAGE$9
21476
+ message: MESSAGE$10
20685
21477
  });
20686
21478
  } };
20687
21479
  }
@@ -20863,7 +21655,7 @@ const ESCAPED_VERSIONS = {
20863
21655
  ">": "`&gt;` / `&#62;`",
20864
21656
  "}": "`&#125;` (or wrap the literal in `{'}'}`)"
20865
21657
  };
20866
- const buildMessage$6 = (character) => `\`${character}\` in JSX text can be confused with markup — escape with ${ESCAPED_VERSIONS[character]}.`;
21658
+ const buildMessage$7 = (character) => `\`${character}\` in JSX text can be confused with markup — escape with ${ESCAPED_VERSIONS[character]}.`;
20867
21659
  const noUnescapedEntities = defineRule({
20868
21660
  id: "no-unescaped-entities",
20869
21661
  severity: "warn",
@@ -20874,7 +21666,7 @@ const noUnescapedEntities = defineRule({
20874
21666
  for (const character of value) if (character in ESCAPED_VERSIONS) {
20875
21667
  context.report({
20876
21668
  node,
20877
- message: buildMessage$6(character)
21669
+ message: buildMessage$7(character)
20878
21670
  });
20879
21671
  return;
20880
21672
  }
@@ -21895,7 +22687,7 @@ const SAFER_REPLACEMENT = {
21895
22687
  componentWillUpdate: "componentDidUpdate",
21896
22688
  UNSAFE_componentWillUpdate: "componentDidUpdate"
21897
22689
  };
21898
- const buildMessage$5 = (methodName) => `Unsafe lifecycle method \`${methodName}\` — use \`${SAFER_REPLACEMENT[methodName] ?? "an alternative lifecycle method"}\` instead.`;
22690
+ const buildMessage$6 = (methodName) => `Unsafe lifecycle method \`${methodName}\` — use \`${SAFER_REPLACEMENT[methodName] ?? "an alternative lifecycle method"}\` instead.`;
21899
22691
  const resolveSettings$9 = (settings) => {
21900
22692
  const reactDoctor = settings?.["react-doctor"];
21901
22693
  return { checkAliases: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noUnsafe ?? {} : {}).checkAliases ?? false };
@@ -21943,7 +22735,7 @@ const noUnsafe = defineRule({
21943
22735
  if (!getParentComponent(node)) return;
21944
22736
  context.report({
21945
22737
  node: node.key,
21946
- message: buildMessage$5(name)
22738
+ message: buildMessage$6(name)
21947
22739
  });
21948
22740
  },
21949
22741
  Property(node) {
@@ -21954,7 +22746,7 @@ const noUnsafe = defineRule({
21954
22746
  if (isEs5Component(ancestor)) {
21955
22747
  context.report({
21956
22748
  node: node.key,
21957
- message: buildMessage$5(name)
22749
+ message: buildMessage$6(name)
21958
22750
  });
21959
22751
  return;
21960
22752
  }
@@ -21966,7 +22758,7 @@ const noUnsafe = defineRule({
21966
22758
  });
21967
22759
  //#endregion
21968
22760
  //#region src/plugin/rules/react-builtins/no-unstable-nested-components.ts
21969
- const buildMessage$4 = (parentName, isInProp, allowAsProps) => {
22761
+ const buildMessage$5 = (parentName, isInProp, allowAsProps) => {
21970
22762
  let message = "Don't define components inside another component";
21971
22763
  if (parentName) message += ` (\`${parentName}\`)`;
21972
22764
  message += " — extract it to module scope.";
@@ -22035,7 +22827,7 @@ const isReactClassComponent = (classNode) => {
22035
22827
  const findEnclosingComponent = (node) => {
22036
22828
  let walker = node.parent;
22037
22829
  while (walker) {
22038
- if (isFunctionLike$1(walker)) {
22830
+ if (isFunctionLike$2(walker)) {
22039
22831
  const componentName = inferFunctionLikeName(walker);
22040
22832
  if (componentName && isReactComponentName(componentName) && expressionContainsJsxOrCreateElement(walker)) return {
22041
22833
  component: walker,
@@ -22201,7 +22993,7 @@ const noUnstableNestedComponents = defineRule({
22201
22993
  if (!enclosing) return;
22202
22994
  context.report({
22203
22995
  node: reportNode,
22204
- message: buildMessage$4(enclosing.name, propInfo !== null, settings.allowAsProps)
22996
+ message: buildMessage$5(enclosing.name, propInfo !== null, settings.allowAsProps)
22205
22997
  });
22206
22998
  };
22207
22999
  const checkFunctionLike = (node) => {
@@ -22236,15 +23028,6 @@ const noUnstableNestedComponents = defineRule({
22236
23028
  }
22237
23029
  });
22238
23030
  //#endregion
22239
- //#region src/plugin/utils/is-canonical-react-namespace-name.ts
22240
- const isCanonicalReactNamespaceName = (namespaceName) => {
22241
- if (namespaceName === "React") return true;
22242
- if (namespaceName === "react") return true;
22243
- if (namespaceName.startsWith("_react")) return true;
22244
- if (namespaceName.startsWith("_React")) return true;
22245
- return false;
22246
- };
22247
- //#endregion
22248
23031
  //#region src/plugin/rules/performance/no-usememo-simple-expression.ts
22249
23032
  const isSimpleExpression = (node) => {
22250
23033
  if (!node) return false;
@@ -22333,7 +23116,7 @@ const noWideLetterSpacing = defineRule({
22333
23116
  //#endregion
22334
23117
  //#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
22335
23118
  const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
22336
- const MESSAGE$8 = "Do not use `this.setState` in `componentWillUpdate` — schedule the update via `componentDidUpdate` instead.";
23119
+ const MESSAGE$9 = "Do not use `this.setState` in `componentWillUpdate` — schedule the update via `componentDidUpdate` instead.";
22337
23120
  const resolveSettings$7 = (settings) => {
22338
23121
  const reactDoctor = settings?.["react-doctor"];
22339
23122
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
@@ -22366,7 +23149,7 @@ const noWillUpdateSetState = defineRule({
22366
23149
  if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
22367
23150
  context.report({
22368
23151
  node: node.callee,
22369
- message: MESSAGE$8
23152
+ message: MESSAGE$9
22370
23153
  });
22371
23154
  } };
22372
23155
  }
@@ -22979,7 +23762,7 @@ const REACT_HOOK_NAMES = new Set([
22979
23762
  "useSyncExternalStore",
22980
23763
  "useTransition"
22981
23764
  ]);
22982
- const buildMessage$3 = (importedNames) => `Import ${importedNames.map((innerName) => `\`${innerName}\``).join(", ")} from \`preact/hooks\` (or \`preact/compat\`) — importing hooks from \`react\` in a pure-Preact project loads a second copy of Preact's hook state and triggers \`__H\` undefined errors.`;
23765
+ const buildMessage$4 = (importedNames) => `Import ${importedNames.map((innerName) => `\`${innerName}\``).join(", ")} from \`preact/hooks\` (or \`preact/compat\`) — importing hooks from \`react\` in a pure-Preact project loads a second copy of Preact's hook state and triggers \`__H\` undefined errors.`;
22983
23766
  const preactNoReactHooksImport = defineRule({
22984
23767
  id: "preact-no-react-hooks-import",
22985
23768
  requires: ["pure-preact"],
@@ -23002,7 +23785,7 @@ const preactNoReactHooksImport = defineRule({
23002
23785
  });
23003
23786
  context.report({
23004
23787
  node,
23005
- message: buildMessage$3(importedNames)
23788
+ message: buildMessage$4(importedNames)
23006
23789
  });
23007
23790
  } })
23008
23791
  });
@@ -23059,7 +23842,7 @@ const preactNoRenderArguments = defineRule({
23059
23842
  });
23060
23843
  //#endregion
23061
23844
  //#region src/plugin/rules/preact/preact-prefer-ondblclick.ts
23062
- const MESSAGE$7 = "Preact follows DOM event naming — use `onDblClick` (lowercase second word). React's `onDoubleClick` handler never fires in Preact core.";
23845
+ const MESSAGE$8 = "Preact follows DOM event naming — use `onDblClick` (lowercase second word). React's `onDoubleClick` handler never fires in Preact core.";
23063
23846
  const preactPreferOndblclick = defineRule({
23064
23847
  id: "preact-prefer-ondblclick",
23065
23848
  requires: ["pure-preact"],
@@ -23073,7 +23856,7 @@ const preactPreferOndblclick = defineRule({
23073
23856
  if (!onDoubleClickAttribute) return;
23074
23857
  context.report({
23075
23858
  node: onDoubleClickAttribute,
23076
- message: MESSAGE$7
23859
+ message: MESSAGE$8
23077
23860
  });
23078
23861
  } })
23079
23862
  });
@@ -23181,7 +23964,7 @@ const preferEs6Class = defineRule({
23181
23964
  });
23182
23965
  //#endregion
23183
23966
  //#region src/plugin/rules/react-builtins/prefer-function-component.ts
23184
- const MESSAGE$6 = "Class component should be written as a function component — use hooks instead.";
23967
+ const MESSAGE$7 = "Class component should be written as a function component — use hooks instead.";
23185
23968
  const resolveSettings$4 = (settings) => {
23186
23969
  const reactDoctor = settings?.["react-doctor"];
23187
23970
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.preferFunctionComponent ?? {} : {};
@@ -23219,7 +24002,7 @@ const preferFunctionComponent = defineRule({
23219
24002
  const reportNode = node.id ?? node;
23220
24003
  context.report({
23221
24004
  node: reportNode,
23222
- message: MESSAGE$6
24005
+ message: MESSAGE$7
23223
24006
  });
23224
24007
  };
23225
24008
  return {
@@ -23276,6 +24059,276 @@ const preferHtmlDialog = defineRule({
23276
24059
  } })
23277
24060
  });
23278
24061
  //#endregion
24062
+ //#region src/plugin/utils/function-returns-object-literal.ts
24063
+ const unwrapExpression = (node) => {
24064
+ let current = node;
24065
+ for (;;) {
24066
+ if ((current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSNonNullExpression") && "expression" in current && isAstNode(current.expression)) {
24067
+ current = current.expression;
24068
+ continue;
24069
+ }
24070
+ return current;
24071
+ }
24072
+ };
24073
+ const doesFunctionReturnsObjectLiteral = (functionNode) => {
24074
+ if (functionNode.type === "ArrowFunctionExpression" && "body" in functionNode) {
24075
+ const body = functionNode.body;
24076
+ if (body && body.type !== "BlockStatement") return unwrapExpression(body).type === "ObjectExpression";
24077
+ }
24078
+ const body = functionNode.body;
24079
+ if (!body || body.type !== "BlockStatement") return false;
24080
+ let returnsObject = false;
24081
+ const visit = (node) => {
24082
+ if (returnsObject) return;
24083
+ if (node.type === "ReturnStatement" && "argument" in node && node.argument != null) {
24084
+ if (unwrapExpression(node.argument).type === "ObjectExpression") returnsObject = true;
24085
+ return;
24086
+ }
24087
+ const nodeRecord = node;
24088
+ for (const key of Object.keys(nodeRecord)) {
24089
+ if (key === "parent") continue;
24090
+ const child = nodeRecord[key];
24091
+ if (Array.isArray(child)) for (const item of child) {
24092
+ if (!isAstNode(item)) continue;
24093
+ if (FUNCTION_LIKE_TYPES$1.has(item.type)) continue;
24094
+ visit(item);
24095
+ if (returnsObject) return;
24096
+ }
24097
+ else if (isAstNode(child)) {
24098
+ if (FUNCTION_LIKE_TYPES$1.has(child.type)) continue;
24099
+ visit(child);
24100
+ }
24101
+ }
24102
+ };
24103
+ visit(body);
24104
+ return returnsObject;
24105
+ };
24106
+ //#endregion
24107
+ //#region src/plugin/utils/enclosing-component-or-hook-scope.ts
24108
+ const enclosingComponentOrHookScope = (startNode, ownScopeFor) => {
24109
+ const functionNode = nearestEnclosingFunction(startNode);
24110
+ if (!functionNode) return null;
24111
+ const displayName = componentOrHookDisplayNameForFunction(functionNode);
24112
+ if (!displayName) return null;
24113
+ if (!isReactHookName(displayName) && doesFunctionReturnsObjectLiteral(functionNode)) return null;
24114
+ const bodyScope = ownScopeFor(functionNode);
24115
+ if (!bodyScope) return null;
24116
+ return {
24117
+ functionNode,
24118
+ bodyScope,
24119
+ displayName
24120
+ };
24121
+ };
24122
+ //#endregion
24123
+ //#region src/plugin/rules/architecture/prefer-module-scope-pure-function.ts
24124
+ const isAssignedToComponentMember = (functionNode) => {
24125
+ const parent = functionNode.parent;
24126
+ if (!parent) return false;
24127
+ return isNodeOfType(parent, "AssignmentExpression") && isNodeOfType(parent.left, "MemberExpression");
24128
+ };
24129
+ const hasComponentLocalCaptures = (functionNode, bodyScope, scopes) => {
24130
+ const captures = closureCaptures(functionNode, scopes);
24131
+ for (const capture of captures) {
24132
+ const symbol = capture.resolvedSymbol;
24133
+ if (!symbol) continue;
24134
+ if (isDescendantScope(symbol.scope, bodyScope)) return true;
24135
+ }
24136
+ return false;
24137
+ };
24138
+ const preferModuleScopePureFunction = defineRule({
24139
+ id: "prefer-module-scope-pure-function",
24140
+ tags: ["test-noise"],
24141
+ severity: "warn",
24142
+ category: "Architecture",
24143
+ recommendation: "Move the function to module scope (above the component). It doesn't reference any local state, so the per-render allocation is wasted.",
24144
+ create: (context) => {
24145
+ const report = (functionNode, name, componentName) => {
24146
+ context.report({
24147
+ node: functionNode,
24148
+ message: `\`${name}\` inside \`${componentName}\` doesn't close over any local state. Move it to module scope so it isn't reallocated every render and the component file stays focused on rendering logic.`
24149
+ });
24150
+ };
24151
+ const checkNamedFunction = (functionNode, bindingName) => {
24152
+ if (isAssignedToComponentMember(functionNode)) return;
24153
+ const component = enclosingComponentOrHookScope(functionNode, context.scopes.ownScopeFor);
24154
+ if (!component) return;
24155
+ const ownScope = context.scopes.ownScopeFor(functionNode);
24156
+ if (!ownScope) return;
24157
+ if (ownScope === component.bodyScope) return;
24158
+ if (!isDescendantScope(ownScope, component.bodyScope)) return;
24159
+ if (hasComponentLocalCaptures(functionNode, component.bodyScope, context.scopes)) return;
24160
+ report(functionNode, bindingName, component.displayName);
24161
+ };
24162
+ return {
24163
+ VariableDeclarator(node) {
24164
+ if (!isNodeOfType(node.id, "Identifier")) return;
24165
+ const initializer = node.init;
24166
+ if (!initializer) return;
24167
+ if (!isNodeOfType(initializer, "ArrowFunctionExpression") && !isNodeOfType(initializer, "FunctionExpression")) return;
24168
+ const bindingName = node.id.name;
24169
+ if (/^[A-Z]/.test(bindingName)) return;
24170
+ checkNamedFunction(initializer, bindingName);
24171
+ },
24172
+ FunctionDeclaration(node) {
24173
+ if (!node.id?.name) return;
24174
+ const bindingName = node.id.name;
24175
+ if (/^[A-Z]/.test(bindingName)) return;
24176
+ checkNamedFunction(node, bindingName);
24177
+ }
24178
+ };
24179
+ }
24180
+ });
24181
+ //#endregion
24182
+ //#region src/plugin/rules/architecture/prefer-module-scope-static-value.ts
24183
+ const MUTATING_RECEIVER_METHOD_NAMES = new Set([...MUTATING_ARRAY_METHODS, ...MUTATING_COLLECTION_METHODS]);
24184
+ const isMutationContext = (referenceIdentifier) => {
24185
+ const parent = referenceIdentifier.parent;
24186
+ if (!parent) return false;
24187
+ if (isNodeOfType(parent, "AssignmentExpression") && parent.left === referenceIdentifier) return true;
24188
+ if (isNodeOfType(parent, "UpdateExpression") && parent.argument === referenceIdentifier) return true;
24189
+ if (isNodeOfType(parent, "MemberExpression") && parent.object === referenceIdentifier) {
24190
+ const grandparent = parent.parent;
24191
+ if (!grandparent) return false;
24192
+ if (isNodeOfType(grandparent, "AssignmentExpression") && grandparent.left === parent) return true;
24193
+ if (isNodeOfType(grandparent, "UpdateExpression") && grandparent.argument === parent) return true;
24194
+ if (isNodeOfType(grandparent, "UnaryExpression") && grandparent.operator === "delete" && grandparent.argument === parent) return true;
24195
+ if (isNodeOfType(grandparent, "CallExpression") && grandparent.callee === parent && !parent.computed && isNodeOfType(parent.property, "Identifier") && MUTATING_RECEIVER_METHOD_NAMES.has(parent.property.name)) return true;
24196
+ }
24197
+ return false;
24198
+ };
24199
+ const isBindingMutatedAfterInit = (declaratorNode, bodyScope, scopes) => {
24200
+ if (!isNodeOfType(declaratorNode.id, "Identifier")) return false;
24201
+ const symbol = scopes.symbolFor(declaratorNode.id);
24202
+ if (!symbol) return false;
24203
+ for (const reference of symbol.references) {
24204
+ if (reference.identifier === declaratorNode.id) continue;
24205
+ if (reference.identifier === declaratorNode.init) continue;
24206
+ if (!isDescendantScope(reference.scope, bodyScope) && reference.scope !== bodyScope) continue;
24207
+ if (isMutationContext(reference.identifier)) return true;
24208
+ }
24209
+ return false;
24210
+ };
24211
+ const hasComponentLocalReferences = (expression, bodyScope, scopes) => {
24212
+ let foundLocal = false;
24213
+ walkAst(expression, (node) => {
24214
+ if (foundLocal) return false;
24215
+ if (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression")) {
24216
+ foundLocal = true;
24217
+ return false;
24218
+ }
24219
+ const reference = scopes.referenceFor(node);
24220
+ if (reference?.resolvedSymbol && isDescendantScope(reference.resolvedSymbol.scope, bodyScope)) {
24221
+ foundLocal = true;
24222
+ return false;
24223
+ }
24224
+ });
24225
+ return foundLocal;
24226
+ };
24227
+ const isHoistableValueExpression = (expression) => {
24228
+ const stripped = stripParenExpression(expression);
24229
+ return isNodeOfType(stripped, "ArrayExpression") || isNodeOfType(stripped, "ObjectExpression");
24230
+ };
24231
+ const preferModuleScopeStaticValue = defineRule({
24232
+ id: "prefer-module-scope-static-value",
24233
+ tags: ["test-noise"],
24234
+ severity: "warn",
24235
+ category: "Architecture",
24236
+ recommendation: "Move the constant to module scope (above the component). It doesn't reference any local state, so the per-render allocation is wasted and any memoised consumer sees a fresh reference each render.",
24237
+ create: (context) => ({ VariableDeclarator(node) {
24238
+ if (!isNodeOfType(node.id, "Identifier")) return;
24239
+ const initializer = node.init;
24240
+ if (!initializer) return;
24241
+ if (!isHoistableValueExpression(initializer)) return;
24242
+ const component = enclosingComponentOrHookScope(node, context.scopes.ownScopeFor);
24243
+ if (!component) return;
24244
+ if (hasComponentLocalReferences(initializer, component.bodyScope, context.scopes)) return;
24245
+ if (isBindingMutatedAfterInit(node, component.bodyScope, context.scopes)) return;
24246
+ const bindingName = node.id.name;
24247
+ context.report({
24248
+ node,
24249
+ message: `\`${bindingName}\` inside \`${component.displayName}\` doesn't depend on any local state. Move it to module scope so the allocation happens once and memoised consumers see a stable reference.`
24250
+ });
24251
+ } })
24252
+ });
24253
+ //#endregion
24254
+ //#region src/plugin/rules/performance/prefer-stable-empty-fallback.ts
24255
+ const isEmptyArrayLiteral$1 = (expression) => {
24256
+ const stripped = stripParenExpression(expression);
24257
+ return isNodeOfType(stripped, "ArrayExpression") && (stripped.elements ?? []).length === 0;
24258
+ };
24259
+ const isEmptyObjectLiteral = (expression) => {
24260
+ const stripped = stripParenExpression(expression);
24261
+ return isNodeOfType(stripped, "ObjectExpression") && (stripped.properties ?? []).length === 0;
24262
+ };
24263
+ const isStableNonEmptyExpression = (expression) => {
24264
+ const stripped = stripParenExpression(expression);
24265
+ if (isNodeOfType(stripped, "Identifier")) return true;
24266
+ if (isNodeOfType(stripped, "ThisExpression")) return true;
24267
+ if (isNodeOfType(stripped, "MemberExpression")) {
24268
+ if (stripped.computed) return false;
24269
+ const object = stripped.object;
24270
+ if (!object) return false;
24271
+ return isStableNonEmptyExpression(object);
24272
+ }
24273
+ return false;
24274
+ };
24275
+ const matchEmptyFallbackInLogicalExpression = (expression) => {
24276
+ const stripped = stripParenExpression(expression);
24277
+ if (!isNodeOfType(stripped, "LogicalExpression")) return null;
24278
+ if (stripped.operator !== "||" && stripped.operator !== "??") return null;
24279
+ const left = stripped.left;
24280
+ const right = stripped.right;
24281
+ if (!left || !right) return null;
24282
+ if (isEmptyArrayLiteral$1(right) && isStableNonEmptyExpression(left)) return {
24283
+ emptyKind: "array",
24284
+ emptyNode: right,
24285
+ nonEmptyExpression: left
24286
+ };
24287
+ if (isEmptyObjectLiteral(right) && isStableNonEmptyExpression(left)) return {
24288
+ emptyKind: "object",
24289
+ emptyNode: right,
24290
+ nonEmptyExpression: left
24291
+ };
24292
+ return null;
24293
+ };
24294
+ const buildMessage$3 = (emptyKind) => {
24295
+ return `Fallback \`${emptyKind === "array" ? "[]" : "{}"}\` allocates a fresh ${emptyKind} on every render where the left-hand value is falsy — the memoised child sees a different reference and re-renders. Hoist a module-level constant (e.g. \`${emptyKind === "array" ? "const EMPTY_ITEMS: Item[] = []" : "const EMPTY_CONFIG: Config = {}"}\`) and use it as the fallback.`;
24296
+ };
24297
+ const preferStableEmptyFallback = defineRule({
24298
+ id: "prefer-stable-empty-fallback",
24299
+ tags: ["react-jsx-only", "test-noise"],
24300
+ severity: "warn",
24301
+ category: "Performance",
24302
+ disabledBy: ["react-compiler"],
24303
+ recommendation: "Hoist a module-level `const EMPTY = []` (or `{}`) and use that as the `||` / `??` fallback so the consumer sees a stable reference.",
24304
+ create: (context) => {
24305
+ let memoRegistry = null;
24306
+ return {
24307
+ Program(node) {
24308
+ memoRegistry = buildSameFileMemoRegistry(node);
24309
+ },
24310
+ JSXAttribute(node) {
24311
+ if (!isInsideFunctionScope(node)) return;
24312
+ if (isJsxAttributeOnIntrinsicHtmlElement(node)) return;
24313
+ if (!node.value) return;
24314
+ if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
24315
+ const innerExpression = node.value.expression;
24316
+ if (!innerExpression) return;
24317
+ if (innerExpression.type === "JSXEmptyExpression") return;
24318
+ const parentJsxOpening = node.parent;
24319
+ const openingName = parentJsxOpening && isNodeOfType(parentJsxOpening, "JSXOpeningElement") ? parentJsxOpening.name : null;
24320
+ if (memoStatusForJsxOpeningName(memoRegistry, openingName) !== "memoised") return;
24321
+ const fallback = matchEmptyFallbackInLogicalExpression(innerExpression);
24322
+ if (!fallback) return;
24323
+ context.report({
24324
+ node: fallback.emptyNode,
24325
+ message: buildMessage$3(fallback.emptyKind)
24326
+ });
24327
+ }
24328
+ };
24329
+ }
24330
+ });
24331
+ //#endregion
23279
24332
  //#region src/plugin/rules/a11y/prefer-tag-over-role.ts
23280
24333
  const buildMessage$2 = (role, tag) => `Prefer the semantic \`<${tag}>\` element over \`role="${role}"\` on a generic tag.`;
23281
24334
  const preferTagOverRole = defineRule({
@@ -23826,91 +24879,216 @@ const reactCompilerNoManualMemoization = defineRule({
23826
24879
  } })
23827
24880
  });
23828
24881
  //#endregion
23829
- //#region src/plugin/utils/has-binding-named.ts
23830
- const hasBindingNamed = (root, bindingName) => {
23831
- const collected = /* @__PURE__ */ new Set();
23832
- const visit = (node) => {
23833
- switch (node.type) {
23834
- case "VariableDeclarator":
23835
- if ("id" in node && node.id) collectPatternNames(node.id, collected);
23836
- break;
23837
- case "FunctionDeclaration":
23838
- case "FunctionExpression":
23839
- case "ClassDeclaration":
23840
- case "ClassExpression":
23841
- if ("id" in node && node.id && node.id.type === "Identifier") {
23842
- const idNode = node.id;
23843
- if (typeof idNode.name === "string") collected.add(idNode.name);
23844
- }
23845
- break;
23846
- case "ArrowFunctionExpression": break;
23847
- case "ImportDefaultSpecifier":
23848
- case "ImportNamespaceSpecifier":
23849
- case "ImportSpecifier":
23850
- if ("local" in node && node.local && node.local.type === "Identifier") {
23851
- const local = node.local;
23852
- if (typeof local.name === "string") collected.add(local.name);
23853
- }
23854
- break;
23855
- case "TSImportEqualsDeclaration":
23856
- case "TSEnumDeclaration":
23857
- case "TSTypeAliasDeclaration":
23858
- case "TSInterfaceDeclaration":
23859
- case "TSModuleDeclaration": {
23860
- const idNode = node.id;
23861
- if (idNode && idNode.type === "Identifier") {
23862
- const idObject = idNode;
23863
- if (typeof idObject.name === "string") collected.add(idObject.name);
23864
- }
23865
- break;
23866
- }
23867
- default: break;
23868
- }
23869
- if ("params" in node && Array.isArray(node.params)) for (const param of node.params) collectPatternNames(param, collected);
23870
- if (collected.has(bindingName)) return;
23871
- const nodeRecord = node;
23872
- for (const key of Object.keys(nodeRecord)) {
23873
- if (key === "parent") continue;
23874
- const child = nodeRecord[key];
23875
- if (Array.isArray(child)) {
23876
- for (const item of child) if (isAstNode(item)) visit(item);
23877
- } else if (isAstNode(child)) visit(child);
23878
- if (collected.has(bindingName)) return;
23879
- }
23880
- };
23881
- visit(root);
23882
- return collected.has(bindingName);
23883
- };
23884
- //#endregion
23885
24882
  //#region src/plugin/rules/react-builtins/react-in-jsx-scope.ts
23886
- const MESSAGE$5 = "`React` must be in scope when using JSX (the classic JSX transform expands `<a/>` to `React.createElement('a')`).";
24883
+ const MESSAGE$6 = "`React` must be in scope when using JSX (the classic JSX transform expands `<a/>` to `React.createElement('a')`).";
23887
24884
  const reactInJsxScope = defineRule({
23888
24885
  id: "react-in-jsx-scope",
23889
24886
  severity: "warn",
23890
24887
  defaultEnabled: false,
23891
24888
  recommendation: "If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import `React` at the top of the file.",
23892
- create: (context) => {
23893
- let didCheckBindingForFile = false;
23894
- let isReactBound = false;
23895
- const ensureBindingChecked = (jsxNode) => {
23896
- if (didCheckBindingForFile) return isReactBound;
23897
- didCheckBindingForFile = true;
23898
- const programRoot = findProgramRoot(jsxNode);
23899
- isReactBound = programRoot ? hasBindingNamed(programRoot, "React") : false;
23900
- return isReactBound;
24889
+ create: (context) => ({
24890
+ JSXOpeningElement(node) {
24891
+ if (findVariableInitializer(node, "React")) return;
24892
+ context.report({
24893
+ node: node.name,
24894
+ message: MESSAGE$6
24895
+ });
24896
+ },
24897
+ JSXFragment(node) {
24898
+ if (findVariableInitializer(node, "React")) return;
24899
+ context.report({
24900
+ node: node.openingFragment,
24901
+ message: MESSAGE$6
24902
+ });
24903
+ }
24904
+ })
24905
+ });
24906
+ //#endregion
24907
+ //#region src/plugin/utils/collect-react-redux-selector-aliases.ts
24908
+ const REACT_REDUX_MODULE = "react-redux";
24909
+ const collectReactReduxSelectorAliases = (programRoot) => {
24910
+ const aliases = /* @__PURE__ */ new Set();
24911
+ if (!isNodeOfType(programRoot, "Program")) return aliases;
24912
+ for (const topLevel of programRoot.body ?? []) {
24913
+ if (!isNodeOfType(topLevel, "ImportDeclaration")) continue;
24914
+ if (typeof topLevel.source?.value !== "string") continue;
24915
+ if (topLevel.source.value !== REACT_REDUX_MODULE) continue;
24916
+ for (const specifier of topLevel.specifiers ?? []) {
24917
+ if (!isNodeOfType(specifier, "ImportSpecifier")) continue;
24918
+ const imported = specifier.imported;
24919
+ if ((imported && "name" in imported && typeof imported.name === "string" ? imported.name : imported && "value" in imported && typeof imported.value === "string" ? imported.value : null) !== "useSelector") continue;
24920
+ const local = specifier.local;
24921
+ if (isNodeOfType(local, "Identifier")) aliases.add(local.name);
24922
+ }
24923
+ }
24924
+ const collectDeclarations = (node) => {
24925
+ if (!isNodeOfType(node, "VariableDeclaration")) return;
24926
+ for (const declarator of node.declarations ?? []) {
24927
+ if (!isNodeOfType(declarator, "VariableDeclarator")) continue;
24928
+ if (!isNodeOfType(declarator.id, "Identifier")) continue;
24929
+ if (!declarator.init) continue;
24930
+ const initialiser = stripParenExpression(declarator.init);
24931
+ if (!isNodeOfType(initialiser, "Identifier")) continue;
24932
+ if (!aliases.has(initialiser.name)) continue;
24933
+ aliases.add(declarator.id.name);
24934
+ }
24935
+ };
24936
+ for (const topLevel of programRoot.body ?? []) if (isNodeOfType(topLevel, "VariableDeclaration")) collectDeclarations(topLevel);
24937
+ else if (isNodeOfType(topLevel, "ExportNamedDeclaration") && topLevel.declaration) collectDeclarations(topLevel.declaration);
24938
+ return aliases;
24939
+ };
24940
+ const isUseSelectorIdentifier = (calleeNode, aliases) => {
24941
+ if (!isNodeOfType(calleeNode, "Identifier")) return false;
24942
+ if (aliases.has(calleeNode.name)) return true;
24943
+ if (calleeNode.name !== "useSelector") return false;
24944
+ return isImportedFromModule(calleeNode, calleeNode.name, REACT_REDUX_MODULE);
24945
+ };
24946
+ //#endregion
24947
+ //#region src/plugin/rules/state-and-effects/utils/inline-use-selector-function.ts
24948
+ const inlineUseSelectorFunction = (callNode, aliases) => {
24949
+ if (!isUseSelectorIdentifier(callNode.callee, aliases)) return null;
24950
+ const args = callNode.arguments ?? [];
24951
+ if (args.length === 0 || args.length >= 2) return null;
24952
+ const selectorArgument = stripParenExpression(args[0]);
24953
+ if (isNodeOfType(selectorArgument, "ArrowFunctionExpression") || isNodeOfType(selectorArgument, "FunctionExpression")) return selectorArgument;
24954
+ return null;
24955
+ };
24956
+ //#endregion
24957
+ //#region src/plugin/rules/state-and-effects/redux-useselector-inline-derivation.ts
24958
+ const ALLOCATING_ARRAY_METHODS = new Set([
24959
+ "filter",
24960
+ "map",
24961
+ "flatMap",
24962
+ "slice",
24963
+ "concat",
24964
+ "toSorted",
24965
+ "toReversed",
24966
+ "toSpliced",
24967
+ "with"
24968
+ ]);
24969
+ const ALLOCATING_NAMESPACE_CALLS = new Map([["Object", new Set([
24970
+ "keys",
24971
+ "values",
24972
+ "entries",
24973
+ "fromEntries",
24974
+ "assign"
24975
+ ])], ["Array", new Set(["from", "of"])]]);
24976
+ const MESSAGE_DERIVATION = (methodName) => `useSelector callback derives a new array via \`.${methodName}(...)\` on every store update — the default \`===\` equality check always fails on a fresh allocation, re-rendering the component on every dispatched action. Select the raw slice (\`useSelector(s => s.users)\`) and derive with \`useMemo\`, or hoist the derivation into a memoised \`createSelector\` from \`reselect\`.`;
24977
+ const MESSAGE_NAMESPACE = (namespace, methodName) => `useSelector callback returns a fresh collection from \`${namespace}.${methodName}(...)\` on every store update — the default \`===\` equality check always fails, re-rendering on every dispatched action. Select the raw slice and derive with \`useMemo\` or \`reselect\`.`;
24978
+ const getAllocatingCallSiteDescription = (expression) => {
24979
+ const stripped = stripParenExpression(expression);
24980
+ if (!isNodeOfType(stripped, "CallExpression")) return null;
24981
+ const callee = stripped.callee;
24982
+ if (!isNodeOfType(callee, "MemberExpression")) return null;
24983
+ if (callee.computed) return null;
24984
+ if (!isNodeOfType(callee.property, "Identifier")) return null;
24985
+ const methodName = callee.property.name;
24986
+ if (isNodeOfType(callee.object, "Identifier")) {
24987
+ const namespaceName = callee.object.name;
24988
+ if (ALLOCATING_NAMESPACE_CALLS.get(namespaceName)?.has(methodName)) return {
24989
+ kind: "namespace",
24990
+ namespace: namespaceName,
24991
+ method: methodName
23901
24992
  };
24993
+ }
24994
+ if (ALLOCATING_ARRAY_METHODS.has(methodName)) return {
24995
+ kind: "method",
24996
+ method: methodName
24997
+ };
24998
+ return null;
24999
+ };
25000
+ const findReturnedAllocatingCall = (expression) => {
25001
+ const stripped = stripParenExpression(expression);
25002
+ const direct = getAllocatingCallSiteDescription(stripped);
25003
+ if (direct) return {
25004
+ ...direct,
25005
+ node: stripped
25006
+ };
25007
+ if (isNodeOfType(stripped, "ConditionalExpression")) return findReturnedAllocatingCall(stripped.consequent) ?? findReturnedAllocatingCall(stripped.alternate);
25008
+ if (isNodeOfType(stripped, "LogicalExpression")) return findReturnedAllocatingCall(stripped.left) ?? findReturnedAllocatingCall(stripped.right);
25009
+ if (isNodeOfType(stripped, "SequenceExpression")) {
25010
+ const lastExpression = stripped.expressions[stripped.expressions.length - 1];
25011
+ return lastExpression ? findReturnedAllocatingCall(lastExpression) : null;
25012
+ }
25013
+ return null;
25014
+ };
25015
+ const reduxUseselectorInlineDerivation = defineRule({
25016
+ id: "redux-useselector-inline-derivation",
25017
+ severity: "warn",
25018
+ category: "Performance",
25019
+ disabledBy: ["react-compiler"],
25020
+ recommendation: "Select the raw slice and derive with `useMemo`, or use `createSelector` from `reselect`.",
25021
+ create: (context) => {
25022
+ let aliases = /* @__PURE__ */ new Set();
23902
25023
  return {
23903
- JSXOpeningElement(node) {
23904
- if (ensureBindingChecked(node)) return;
23905
- context.report({
23906
- node: node.name,
23907
- message: MESSAGE$5
25024
+ Program(node) {
25025
+ aliases = collectReactReduxSelectorAliases(node);
25026
+ },
25027
+ CallExpression(node) {
25028
+ const selectorArgument = inlineUseSelectorFunction(node, aliases);
25029
+ if (!selectorArgument) return;
25030
+ const body = selectorArgument.body;
25031
+ if (!body) return;
25032
+ const returnedExpressions = [];
25033
+ if (isNodeOfType(body, "BlockStatement")) walkAst(body, (node) => {
25034
+ if (node !== body && isFunctionLike$2(node)) return false;
25035
+ if (isNodeOfType(node, "ReturnStatement")) {
25036
+ if (node.argument) returnedExpressions.push(node.argument);
25037
+ return false;
25038
+ }
23908
25039
  });
25040
+ else returnedExpressions.push(body);
25041
+ for (const returnedExpression of returnedExpressions) {
25042
+ const allocatingCall = findReturnedAllocatingCall(returnedExpression);
25043
+ if (!allocatingCall) continue;
25044
+ const reportMessage = allocatingCall.kind === "method" ? MESSAGE_DERIVATION(allocatingCall.method) : MESSAGE_NAMESPACE(allocatingCall.namespace, allocatingCall.method);
25045
+ context.report({
25046
+ node: allocatingCall.node,
25047
+ message: reportMessage
25048
+ });
25049
+ return;
25050
+ }
25051
+ }
25052
+ };
25053
+ }
25054
+ });
25055
+ //#endregion
25056
+ //#region src/plugin/rules/state-and-effects/redux-useselector-returns-new-collection.ts
25057
+ const MESSAGE$5 = "useSelector returning a new object/array re-renders on every dispatched action — the default `===` equality check always fails on a fresh reference. Either return a primitive, split into multiple useSelector calls, or pass `shallowEqual` (or a custom equality fn) as the second argument.";
25058
+ const isConciseBodyReturningCollection = (functionNode) => {
25059
+ if (!isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression")) return false;
25060
+ const rawBody = functionNode.body;
25061
+ if (!rawBody) return false;
25062
+ if (!isNodeOfType(rawBody, "BlockStatement")) {
25063
+ const conciseExpression = stripParenExpression(rawBody);
25064
+ return isNodeOfType(conciseExpression, "ObjectExpression") || isNodeOfType(conciseExpression, "ArrayExpression");
25065
+ }
25066
+ const statements = rawBody.body ?? [];
25067
+ if (statements.length === 0) return false;
25068
+ const lastStatement = statements[statements.length - 1];
25069
+ if (!isNodeOfType(lastStatement, "ReturnStatement")) return false;
25070
+ if (!lastStatement.argument) return false;
25071
+ const returnedExpression = stripParenExpression(lastStatement.argument);
25072
+ return isNodeOfType(returnedExpression, "ObjectExpression") || isNodeOfType(returnedExpression, "ArrayExpression");
25073
+ };
25074
+ const reduxUseselectorReturnsNewCollection = defineRule({
25075
+ id: "redux-useselector-returns-new-collection",
25076
+ severity: "warn",
25077
+ category: "Performance",
25078
+ disabledBy: ["react-compiler"],
25079
+ recommendation: "Return a primitive, split into multiple useSelector calls, or pass `shallowEqual` from `react-redux` as the second argument.",
25080
+ create: (context) => {
25081
+ let aliases = /* @__PURE__ */ new Set();
25082
+ return {
25083
+ Program(node) {
25084
+ aliases = collectReactReduxSelectorAliases(node);
23909
25085
  },
23910
- JSXFragment(node) {
23911
- if (ensureBindingChecked(node)) return;
25086
+ CallExpression(node) {
25087
+ const selectorArgument = inlineUseSelectorFunction(node, aliases);
25088
+ if (!selectorArgument) return;
25089
+ if (!isConciseBodyReturningCollection(selectorArgument)) return;
23912
25090
  context.report({
23913
- node: node.openingFragment,
25091
+ node,
23914
25092
  message: MESSAGE$5
23915
25093
  });
23916
25094
  }
@@ -24188,7 +25366,7 @@ const hasOwnAwait = (functionBody) => {
24188
25366
  let found = false;
24189
25367
  walkAst(functionBody, (child) => {
24190
25368
  if (found) return;
24191
- if (child !== functionBody && isFunctionLike$1(child)) return false;
25369
+ if (child !== functionBody && isFunctionLike$2(child)) return false;
24192
25370
  if (isNodeOfType(child, "AwaitExpression")) found = true;
24193
25371
  });
24194
25372
  return found;
@@ -24207,7 +25385,7 @@ const setterIsCalledInAsyncContext = (componentBody, setterName) => {
24207
25385
  let found = false;
24208
25386
  walkAst(componentBody, (child) => {
24209
25387
  if (found) return;
24210
- if (!isFunctionLike$1(child)) return;
25388
+ if (!isFunctionLike$2(child)) return;
24211
25389
  const functionBody = child.body;
24212
25390
  if (!(Boolean(child.async) || hasOwnAwait(functionBody))) return;
24213
25391
  if (callsIdentifier(functionBody, setterName)) found = true;
@@ -24661,6 +25839,33 @@ const rerenderFunctionalSetstate = defineRule({
24661
25839
  } })
24662
25840
  });
24663
25841
  //#endregion
25842
+ //#region src/plugin/rules/state-and-effects/rerender-lazy-ref-init.ts
25843
+ const rerenderLazyRefInit = defineRule({
25844
+ id: "rerender-lazy-ref-init",
25845
+ tags: ["test-noise"],
25846
+ severity: "warn",
25847
+ category: "Performance",
25848
+ recommendation: "Initialize lazily: `const ref = useRef<T | null>(null); if (ref.current === null) ref.current = expensiveCall();`",
25849
+ create: (context) => ({ CallExpression(node) {
25850
+ if (!isHookCall$1(node, "useRef") || !node.arguments?.length) return;
25851
+ const initializer = node.arguments[0];
25852
+ const isPlainCall = isNodeOfType(initializer, "CallExpression");
25853
+ const isNewCall = isNodeOfType(initializer, "NewExpression");
25854
+ if (!isPlainCall && !isNewCall) return;
25855
+ const callee = initializer.callee;
25856
+ const memberPropertyName = isNodeOfType(callee, "MemberExpression") && (isNodeOfType(callee.property, "Identifier") || isNodeOfType(callee.property, "PrivateIdentifier")) ? callee.property.name : null;
25857
+ const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : memberPropertyName ?? "fn";
25858
+ if (TRIVIAL_INITIALIZER_NAMES.has(calleeName)) return;
25859
+ if (isPlainCall && isReactHookName(calleeName)) return;
25860
+ const callShape = isNewCall ? `new ${calleeName}()` : `${calleeName}()`;
25861
+ const lazyFix = isNewCall ? `ref.current = new ${calleeName}();` : `ref.current = ${calleeName}();`;
25862
+ context.report({
25863
+ node: initializer,
25864
+ message: `useRef(${callShape}) allocates a fresh value on every render — useRef has no lazy-init form, so the allocation is discarded after the first render. Use \`const ref = useRef(null); if (ref.current === null) ${lazyFix}\` or \`useMemo\` instead.`
25865
+ });
25866
+ } })
25867
+ });
25868
+ //#endregion
24664
25869
  //#region src/plugin/rules/state-and-effects/rerender-lazy-state-init.ts
24665
25870
  const rerenderLazyStateInit = defineRule({
24666
25871
  id: "rerender-lazy-state-init",
@@ -30110,7 +31315,7 @@ const isUseEffectEventSymbol = (symbol) => {
30110
31315
  const findEnclosingComponentOrHookFunction = (node) => {
30111
31316
  let current = node.parent;
30112
31317
  while (current) {
30113
- if (isFunctionLike$1(current)) {
31318
+ if (isFunctionLike$2(current)) {
30114
31319
  const resolvedName = inferFunctionName(current);
30115
31320
  if (resolvedName !== null && isReactComponentOrHookName(resolvedName)) return current;
30116
31321
  }
@@ -30131,7 +31336,7 @@ const isCallbackArgumentForAllowedEffectEventHook = (functionNode, additionalEff
30131
31336
  const isInsideAllowedEffectEventCallback = (node, additionalEffectHooksRegex) => {
30132
31337
  let current = node.parent;
30133
31338
  while (current) {
30134
- if (isFunctionLike$1(current) && isCallbackArgumentForAllowedEffectEventHook(current, additionalEffectHooksRegex)) return true;
31339
+ if (isFunctionLike$2(current) && isCallbackArgumentForAllowedEffectEventHook(current, additionalEffectHooksRegex)) return true;
30135
31340
  current = current.parent ?? null;
30136
31341
  }
30137
31342
  return false;
@@ -30465,7 +31670,7 @@ const containsAuthCheck = (rootNodes, allowedFunctionNames, genericMethodNames)
30465
31670
  let foundAuthCall = false;
30466
31671
  for (const rootNode of rootNodes) walkAst(rootNode, (child) => {
30467
31672
  if (foundAuthCall) return;
30468
- if (isFunctionLike$1(child)) return false;
31673
+ if (isFunctionLike$2(child)) return false;
30469
31674
  if (!isNodeOfType(child, "CallExpression")) return;
30470
31675
  if (getAuthCallName(child, allowedFunctionNames, genericMethodNames)) foundAuthCall = true;
30471
31676
  });
@@ -33052,6 +34257,28 @@ const reactDoctorRules = [
33052
34257
  category: "Architecture"
33053
34258
  }
33054
34259
  },
34260
+ {
34261
+ key: "react-doctor/no-create-context-in-render",
34262
+ id: "no-create-context-in-render",
34263
+ source: "react-doctor",
34264
+ originallyExternal: false,
34265
+ rule: {
34266
+ ...noCreateContextInRender,
34267
+ framework: "global",
34268
+ category: "Correctness"
34269
+ }
34270
+ },
34271
+ {
34272
+ key: "react-doctor/no-create-store-in-render",
34273
+ id: "no-create-store-in-render",
34274
+ source: "react-doctor",
34275
+ originallyExternal: false,
34276
+ rule: {
34277
+ ...noCreateStoreInRender,
34278
+ framework: "global",
34279
+ category: "Correctness"
34280
+ }
34281
+ },
33055
34282
  {
33056
34283
  key: "react-doctor/no-danger",
33057
34284
  id: "no-danger",
@@ -33250,6 +34477,17 @@ const reactDoctorRules = [
33250
34477
  category: "State & Effects"
33251
34478
  }
33252
34479
  },
34480
+ {
34481
+ key: "react-doctor/no-effect-with-fresh-deps",
34482
+ id: "no-effect-with-fresh-deps",
34483
+ source: "react-doctor",
34484
+ originallyExternal: false,
34485
+ rule: {
34486
+ ...noEffectWithFreshDeps,
34487
+ framework: "global",
34488
+ category: "State & Effects"
34489
+ }
34490
+ },
33253
34491
  {
33254
34492
  key: "react-doctor/no-eval",
33255
34493
  id: "no-eval",
@@ -33745,6 +34983,17 @@ const reactDoctorRules = [
33745
34983
  category: "Architecture"
33746
34984
  }
33747
34985
  },
34986
+ {
34987
+ key: "react-doctor/no-random-key",
34988
+ id: "no-random-key",
34989
+ source: "react-doctor",
34990
+ originallyExternal: false,
34991
+ rule: {
34992
+ ...noRandomKey,
34993
+ framework: "global",
34994
+ category: "Correctness"
34995
+ }
34996
+ },
33748
34997
  {
33749
34998
  key: "react-doctor/no-react-children",
33750
34999
  id: "no-react-children",
@@ -34174,6 +35423,39 @@ const reactDoctorRules = [
34174
35423
  category: "Accessibility"
34175
35424
  }
34176
35425
  },
35426
+ {
35427
+ key: "react-doctor/prefer-module-scope-pure-function",
35428
+ id: "prefer-module-scope-pure-function",
35429
+ source: "react-doctor",
35430
+ originallyExternal: false,
35431
+ rule: {
35432
+ ...preferModuleScopePureFunction,
35433
+ framework: "global",
35434
+ category: "Architecture"
35435
+ }
35436
+ },
35437
+ {
35438
+ key: "react-doctor/prefer-module-scope-static-value",
35439
+ id: "prefer-module-scope-static-value",
35440
+ source: "react-doctor",
35441
+ originallyExternal: false,
35442
+ rule: {
35443
+ ...preferModuleScopeStaticValue,
35444
+ framework: "global",
35445
+ category: "Architecture"
35446
+ }
35447
+ },
35448
+ {
35449
+ key: "react-doctor/prefer-stable-empty-fallback",
35450
+ id: "prefer-stable-empty-fallback",
35451
+ source: "react-doctor",
35452
+ originallyExternal: false,
35453
+ rule: {
35454
+ ...preferStableEmptyFallback,
35455
+ framework: "global",
35456
+ category: "Performance"
35457
+ }
35458
+ },
34177
35459
  {
34178
35460
  key: "react-doctor/prefer-tag-over-role",
34179
35461
  id: "prefer-tag-over-role",
@@ -34306,6 +35588,28 @@ const reactDoctorRules = [
34306
35588
  category: "Correctness"
34307
35589
  }
34308
35590
  },
35591
+ {
35592
+ key: "react-doctor/redux-useselector-inline-derivation",
35593
+ id: "redux-useselector-inline-derivation",
35594
+ source: "react-doctor",
35595
+ originallyExternal: false,
35596
+ rule: {
35597
+ ...reduxUseselectorInlineDerivation,
35598
+ framework: "global",
35599
+ category: "Performance"
35600
+ }
35601
+ },
35602
+ {
35603
+ key: "react-doctor/redux-useselector-returns-new-collection",
35604
+ id: "redux-useselector-returns-new-collection",
35605
+ source: "react-doctor",
35606
+ originallyExternal: false,
35607
+ rule: {
35608
+ ...reduxUseselectorReturnsNewCollection,
35609
+ framework: "global",
35610
+ category: "Performance"
35611
+ }
35612
+ },
34309
35613
  {
34310
35614
  key: "react-doctor/rendering-animate-svg-wrapper",
34311
35615
  id: "rendering-animate-svg-wrapper",
@@ -34449,6 +35753,17 @@ const reactDoctorRules = [
34449
35753
  category: "Performance"
34450
35754
  }
34451
35755
  },
35756
+ {
35757
+ key: "react-doctor/rerender-lazy-ref-init",
35758
+ id: "rerender-lazy-ref-init",
35759
+ source: "react-doctor",
35760
+ originallyExternal: false,
35761
+ rule: {
35762
+ ...rerenderLazyRefInit,
35763
+ framework: "global",
35764
+ category: "Performance"
35765
+ }
35766
+ },
34452
35767
  {
34453
35768
  key: "react-doctor/rerender-lazy-state-init",
34454
35769
  id: "rerender-lazy-state-init",
@@ -35273,7 +36588,7 @@ const appendNode = (builder, block, node) => {
35273
36588
  };
35274
36589
  const mapDescendantsToBlock = (builder, node, block) => {
35275
36590
  builder.nodeBlock.set(node, block);
35276
- if (isFunctionLike$1(node)) return;
36591
+ if (isFunctionLike$2(node)) return;
35277
36592
  const record = node;
35278
36593
  for (const key of Object.keys(record)) {
35279
36594
  if (key === "parent") continue;
@@ -35611,7 +36926,7 @@ const analyzeControlFlow = (program) => {
35611
36926
  body: program.body
35612
36927
  });
35613
36928
  const visit = (node) => {
35614
- if (isFunctionLike$1(node)) {
36929
+ if (isFunctionLike$2(node)) {
35615
36930
  const body = node.body;
35616
36931
  if (body) buildFor(node, body);
35617
36932
  }
@@ -35628,7 +36943,7 @@ const analyzeControlFlow = (program) => {
35628
36943
  const enclosingFunction = (node) => {
35629
36944
  let current = node;
35630
36945
  while (current) {
35631
- if (isFunctionLike$1(current)) return current;
36946
+ if (isFunctionLike$2(current)) return current;
35632
36947
  if (isNodeOfType(current, "Program")) return current;
35633
36948
  current = current.parent ?? null;
35634
36949
  }