oxlint-plugin-react-doctor 0.8.1-dev.7818922 → 0.8.1-dev.7c9dbed

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 +2 -48
  2. package/dist/index.js +2488 -687
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -762,7 +762,7 @@ const EXTERNAL_SYNC_MEMBER_METHOD_NAMES = new Set([
762
762
  "put",
763
763
  "patch"
764
764
  ]);
765
- const EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS = new Set([
765
+ new Set([
766
766
  ...FETCH_MEMBER_OBJECTS,
767
767
  "api",
768
768
  "client",
@@ -2622,7 +2622,7 @@ const PREFER_ALT = "Screen readers skip a decorative image more reliably with `a
2622
2622
  const MESSAGE_OBJECT = "Screen reader users cannot use this `<object>` because assistive tech cannot describe it, so add `alt`, `aria-label`, `aria-labelledby`, `title`, or inner fallback text.";
2623
2623
  const MESSAGE_AREA = "Blind users can't use this `<area>` of the image map because screen readers can't describe it, so add `alt`, `aria-label`, or `aria-labelledby`.";
2624
2624
  const MESSAGE_INPUT_IMAGE = "Blind users can't use this image button because screen readers can't describe it, so add `alt`, `aria-label`, or `aria-labelledby`.";
2625
- const resolveSettings$53 = (settings) => {
2625
+ const resolveSettings$52 = (settings) => {
2626
2626
  const reactDoctor = settings?.["react-doctor"];
2627
2627
  return typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.altText ?? {} : {};
2628
2628
  };
@@ -2745,7 +2745,7 @@ const altText = defineRule({
2745
2745
  category: "Accessibility",
2746
2746
  create: (context) => {
2747
2747
  if (isGeneratedImageRenderContext(context)) return {};
2748
- const settings = resolveSettings$53(context.settings);
2748
+ const settings = resolveSettings$52(context.settings);
2749
2749
  const checkImg = !settings.elements || settings.elements.includes("img");
2750
2750
  const checkObject = !settings.elements || settings.elements.includes("object");
2751
2751
  const checkArea = !settings.elements || settings.elements.includes("area");
@@ -2807,7 +2807,7 @@ const DEFAULT_AMBIGUOUS = [
2807
2807
  "a link",
2808
2808
  "learn more"
2809
2809
  ];
2810
- const resolveSettings$52 = (settings) => {
2810
+ const resolveSettings$51 = (settings) => {
2811
2811
  const reactDoctor = settings?.["react-doctor"];
2812
2812
  return { words: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.anchorAmbiguousText ?? {} : {}).words ?? DEFAULT_AMBIGUOUS };
2813
2813
  };
@@ -2857,7 +2857,7 @@ const anchorAmbiguousText = defineRule({
2857
2857
  category: "Accessibility",
2858
2858
  create: (context) => {
2859
2859
  if (isTestlikeFilename(context.filename)) return {};
2860
- const settings = resolveSettings$52(context.settings);
2860
+ const settings = resolveSettings$51(context.settings);
2861
2861
  const ambiguousSet = new Set(settings.words.map((word) => word.toLowerCase()));
2862
2862
  return { JSXElement(node) {
2863
2863
  if (getElementType(node.openingElement, context.settings) !== "a") return;
@@ -3225,7 +3225,7 @@ const anchorHasContent = defineRule({
3225
3225
  const MESSAGE_MISSING_HREF = "Keyboard users can't reach this link because it has no `href`, so add a real `href` (or use `<button>` for actions).";
3226
3226
  const MESSAGE_INCORRECT_HREF = "Keyboard users can't reach this link because its `href` goes nowhere (`#`, `javascript:`, or empty), so point it at a real destination.";
3227
3227
  const MESSAGE_CANT_BE_ANCHOR = "Keyboard users can't trigger this link because it's a click handler with no real `href`, so use `<button>` instead.";
3228
- const resolveSettings$51 = (settings) => {
3228
+ const resolveSettings$50 = (settings) => {
3229
3229
  const reactDoctor = settings?.["react-doctor"];
3230
3230
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.anchorIsValid ?? {} : {};
3231
3231
  const jsxA11y = settings?.["jsx-a11y"];
@@ -3268,7 +3268,7 @@ const anchorIsValid = defineRule({
3268
3268
  recommendation: "Give links a real destination. Use `<button>` for in-page actions.",
3269
3269
  category: "Accessibility",
3270
3270
  create: (context) => {
3271
- const settings = resolveSettings$51(context.settings);
3271
+ const settings = resolveSettings$50(context.settings);
3272
3272
  const fileHasJsxA11ySettings = hasJsxA11ySettings(context.settings);
3273
3273
  const isTestlikeFile = isTestlikeFilename(context.filename);
3274
3274
  return { JSXOpeningElement(node) {
@@ -4181,7 +4181,7 @@ const ariaProptypes = defineRule({
4181
4181
  //#endregion
4182
4182
  //#region src/plugin/rules/a11y/aria-role.ts
4183
4183
  const buildBaseMessage = (suffix) => `This \`role\` is not a valid ARIA role, so assistive tech cannot expose it correctly. Use a real, non-abstract role.${suffix}`;
4184
- const resolveSettings$50 = (settings) => {
4184
+ const resolveSettings$49 = (settings) => {
4185
4185
  const reactDoctor = settings?.["react-doctor"];
4186
4186
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.ariaRole ?? {} : {};
4187
4187
  return {
@@ -4197,7 +4197,7 @@ const ariaRole = defineRule({
4197
4197
  recommendation: "Use a real, non-abstract ARIA role so assistive tech can expose the element correctly.",
4198
4198
  category: "Accessibility",
4199
4199
  create: (context) => {
4200
- const settings = resolveSettings$50(context.settings);
4200
+ const settings = resolveSettings$49(context.settings);
4201
4201
  return { JSXOpeningElement(node) {
4202
4202
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
4203
4203
  if (!roleAttribute) return;
@@ -7115,7 +7115,7 @@ const FORM_CONTROL_TAGS = new Set([
7115
7115
  "select",
7116
7116
  "form"
7117
7117
  ]);
7118
- const resolveSettings$49 = (settings) => {
7118
+ const resolveSettings$48 = (settings) => {
7119
7119
  const reactDoctor = settings?.["react-doctor"];
7120
7120
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.autocompleteValid ?? {} : {};
7121
7121
  return { inputComponents: new Set(ruleSettings.inputComponents ?? []) };
@@ -7128,7 +7128,7 @@ const autocompleteValid = defineRule({
7128
7128
  recommendation: "Use a valid autofill token in `autoComplete` so browsers can fill the right field reliably.",
7129
7129
  category: "Accessibility",
7130
7130
  create: skipNonProductionFiles((context) => {
7131
- const settings = resolveSettings$49(context.settings);
7131
+ const settings = resolveSettings$48(context.settings);
7132
7132
  return { JSXOpeningElement: (node) => {
7133
7133
  const tag = getElementType(node, context.settings);
7134
7134
  if (!FORM_CONTROL_TAGS.has(tag) && !settings.inputComponents.has(tag)) return;
@@ -7247,7 +7247,7 @@ const isCreateElementCall = (node) => {
7247
7247
  //#region src/plugin/rules/react-builtins/button-has-type.ts
7248
7248
  const MISSING_MESSAGE$2 = "Your users can submit the form by accident because a `<button>` with no `type` defaults to submit.";
7249
7249
  const INVALID_MESSAGE = "This button has an invalid `type`, so the browser may treat it like a submit button.";
7250
- const resolveSettings$48 = (settings) => {
7250
+ const resolveSettings$47 = (settings) => {
7251
7251
  const reactDoctor = settings?.["react-doctor"];
7252
7252
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.buttonHasType ?? {} : {};
7253
7253
  return {
@@ -7485,7 +7485,7 @@ const buttonHasType = defineRule({
7485
7485
  severity: "warn",
7486
7486
  recommendation: "Set an explicit button `type` so plain buttons do not submit forms by accident: `type=\"button\"`, `\"submit\"`, or `\"reset\"`.",
7487
7487
  create: (context) => {
7488
- const settings = resolveSettings$48(context.settings);
7488
+ const settings = resolveSettings$47(context.settings);
7489
7489
  const isTestlikeFile = isTestlikeFilename(context.filename);
7490
7490
  return {
7491
7491
  JSXOpeningElement(node) {
@@ -7566,7 +7566,7 @@ const buttonHasType = defineRule({
7566
7566
  //#region src/plugin/rules/react-builtins/checked-requires-onchange-or-readonly.ts
7567
7567
  const MISSING_MESSAGE$1 = "Your users can't toggle this input because `checked` has no `onChange`.";
7568
7568
  const EXCLUSIVE_MESSAGE = "This input mixes `checked` with `defaultChecked`, so React can't tell whether it is controlled or uncontrolled.";
7569
- const resolveSettings$47 = (settings) => {
7569
+ const resolveSettings$46 = (settings) => {
7570
7570
  const reactDoctor = settings?.["react-doctor"];
7571
7571
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.checkedRequiresOnchangeOrReadonly ?? {} : {};
7572
7572
  return {
@@ -7667,7 +7667,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
7667
7667
  recommendation: "Add `onChange`, `readOnly`, or `defaultChecked` so React knows whether the checkbox is editable, display-only, or uncontrolled.",
7668
7668
  category: "Correctness",
7669
7669
  create: (context) => {
7670
- const settings = resolveSettings$47(context.settings);
7670
+ const settings = resolveSettings$46(context.settings);
7671
7671
  const reportFromPresence = (presence) => {
7672
7672
  if (presence.checkedNode && presence.defaultCheckedNode && !presence.bothCheckedAndDefaultForwarded && !settings.ignoreExclusiveCheckedAttribute) context.report({
7673
7673
  node: presence.checkedNode,
@@ -9008,6 +9008,16 @@ const hasVisibleBindingNamed = (node, bindingName, scopes) => {
9008
9008
  }
9009
9009
  };
9010
9010
  //#endregion
9011
+ //#region src/plugin/utils/is-global-match-media-call.ts
9012
+ const isGlobalMatchMediaCall = (node, scopes) => {
9013
+ if (!isNodeOfType(node, "CallExpression")) return false;
9014
+ const callee = stripParenExpression(node.callee);
9015
+ if (isNodeOfType(callee, "Identifier")) return callee.name === "matchMedia" && scopes.isGlobalReference(callee);
9016
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "matchMedia") return false;
9017
+ const receiver = stripParenExpression(callee.object);
9018
+ return isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && scopes.isGlobalReference(receiver);
9019
+ };
9020
+ //#endregion
9011
9021
  //#region src/plugin/utils/is-proven-browser-api-receiver.ts
9012
9022
  const DOM_EVENT_TARGET_TYPE_NAMES = new Set([
9013
9023
  "AbortSignal",
@@ -9154,8 +9164,10 @@ const getProvenDomEventTargetPrototypeOwnerNames = (rawExpression, scopes, visit
9154
9164
  if (memberName === "body" || memberName === "documentElement") return getDomPrototypeOwnerNamesForType("HTMLElement");
9155
9165
  }
9156
9166
  if (isNodeOfType(expression, "CallExpression")) {
9167
+ if (isGlobalMatchMediaCall(expression, scopes)) return getDomPrototypeOwnerNamesForType("MediaQueryList");
9157
9168
  const callee = stripParenExpression(expression.callee);
9158
9169
  const methodName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
9170
+ if (methodName === "matchMedia" && isNodeOfType(callee, "MemberExpression") && getProvenDomEventTargetPrototypeOwnerNames(callee.object, scopes, visitedSymbolIds).includes("Window")) return getDomPrototypeOwnerNamesForType("MediaQueryList");
9159
9171
  if (methodName === "createElement") return getDomPrototypeOwnerNamesForType("HTMLElement");
9160
9172
  if (methodName === "createElementNS") return getDomPrototypeOwnerNamesForType("SVGElement");
9161
9173
  if (methodName && DOM_EVENT_TARGET_FACTORY_METHOD_NAMES.has(methodName)) return getDomPrototypeOwnerNamesForType("Element");
@@ -10368,7 +10380,7 @@ const WRAPPER_LABEL_PROP = "label";
10368
10380
  const SELECT_ELEMENT = "select";
10369
10381
  const DEFAULT_DEPTH = 5;
10370
10382
  const MAX_DEPTH = 25;
10371
- const resolveSettings$46 = (settings) => {
10383
+ const resolveSettings$45 = (settings) => {
10372
10384
  const reactDoctor = settings?.["react-doctor"];
10373
10385
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.controlHasAssociatedLabel ?? {} : {};
10374
10386
  return {
@@ -10701,7 +10713,7 @@ const controlHasAssociatedLabel = defineRule({
10701
10713
  recommendation: "Give every interactive control a label screen readers can read.",
10702
10714
  category: "Accessibility",
10703
10715
  create: (context) => {
10704
- const settings = resolveSettings$46(context.settings);
10716
+ const settings = resolveSettings$45(context.settings);
10705
10717
  const isTestlikeFile = isTestlikeFilename(context.filename);
10706
10718
  const iconComponentNames = /* @__PURE__ */ new Set();
10707
10719
  const iconNamespaceNames = /* @__PURE__ */ new Set();
@@ -12653,7 +12665,7 @@ const DEFAULT_ADDITIONAL_HOCS = [
12653
12665
  "lazy",
12654
12666
  "withTracking"
12655
12667
  ];
12656
- const resolveSettings$45 = (settings) => {
12668
+ const resolveSettings$44 = (settings) => {
12657
12669
  const reactDoctor = settings?.["react-doctor"];
12658
12670
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.displayName ?? {} : {};
12659
12671
  const additionalHoCs = new Set(ruleSettings.additionalHoCs ?? DEFAULT_ADDITIONAL_HOCS);
@@ -12835,7 +12847,7 @@ const displayName = defineRule({
12835
12847
  recommendation: "Give each component a `displayName` so DevTools shows a clear name.",
12836
12848
  category: "Architecture",
12837
12849
  create: (context) => {
12838
- const settings = resolveSettings$45(context.settings);
12850
+ const settings = resolveSettings$44(context.settings);
12839
12851
  const ignoreNamed = !settings.ignoreTranspilerName;
12840
12852
  const reportAt = (node) => {
12841
12853
  context.report({
@@ -13815,6 +13827,116 @@ const findRenderPhaseComponentOrHook = (node, scopes) => {
13815
13827
  return null;
13816
13828
  };
13817
13829
  //#endregion
13830
+ //#region src/plugin/utils/get-final-sequence-expression-value.ts
13831
+ const getFinalSequenceExpressionValue = (expression) => {
13832
+ let finalExpression = stripParenExpression(expression);
13833
+ while (isNodeOfType(finalExpression, "SequenceExpression")) {
13834
+ const sequenceResult = finalExpression.expressions.at(-1);
13835
+ if (!sequenceResult) break;
13836
+ finalExpression = stripParenExpression(sequenceResult);
13837
+ }
13838
+ return finalExpression;
13839
+ };
13840
+ //#endregion
13841
+ //#region src/plugin/utils/collect-expression-path-coverage-nodes.ts
13842
+ const collectExpressionPathCoverageNodes = (owner, matchingNodes, context, expressionBoundary = owner) => {
13843
+ const coverageNodes = /* @__PURE__ */ new Set();
13844
+ const pendingNodes = matchingNodes.filter((matchingNode) => {
13845
+ if (context.cfg.enclosingFunction(matchingNode) !== owner) return false;
13846
+ let currentNode = matchingNode;
13847
+ while (currentNode && currentNode !== expressionBoundary) currentNode = currentNode.parent ?? null;
13848
+ return currentNode === expressionBoundary;
13849
+ });
13850
+ const visitedNodes = new Set(pendingNodes);
13851
+ const coveredBranchesByConditional = /* @__PURE__ */ new Map();
13852
+ while (pendingNodes.length > 0) {
13853
+ const coverageCandidate = pendingNodes.pop();
13854
+ if (!coverageCandidate) break;
13855
+ if (coverageCandidate === expressionBoundary) {
13856
+ coverageNodes.add(coverageCandidate);
13857
+ continue;
13858
+ }
13859
+ let currentChild = coverageCandidate;
13860
+ let currentParent = currentChild.parent ?? null;
13861
+ let conditionalExpression = null;
13862
+ let conditionalBranch = null;
13863
+ let isBlockedByNonExhaustiveExpression = false;
13864
+ while (currentParent && currentParent !== expressionBoundary) {
13865
+ if (isNodeOfType(currentParent, "ConditionalExpression")) {
13866
+ const isConsequent = currentParent.consequent === currentChild;
13867
+ const isAlternate = currentParent.alternate === currentChild;
13868
+ if (isConsequent || isAlternate) {
13869
+ const staticTestValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.test));
13870
+ if (staticTestValue !== null) {
13871
+ if (staticTestValue !== isConsequent) {
13872
+ isBlockedByNonExhaustiveExpression = true;
13873
+ break;
13874
+ }
13875
+ currentChild = currentParent;
13876
+ currentParent = currentChild.parent ?? null;
13877
+ continue;
13878
+ }
13879
+ conditionalExpression = currentParent;
13880
+ conditionalBranch = isConsequent ? "consequent" : "alternate";
13881
+ break;
13882
+ }
13883
+ }
13884
+ if (isNodeOfType(currentParent, "LogicalExpression") && currentParent.right === currentChild) {
13885
+ const staticLeftValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.left));
13886
+ if (!(currentParent.operator === "&&" && staticLeftValue === true || currentParent.operator === "||" && staticLeftValue === false)) {
13887
+ isBlockedByNonExhaustiveExpression = true;
13888
+ break;
13889
+ }
13890
+ }
13891
+ if (isNodeOfType(currentParent, "AssignmentPattern") && currentParent.right === currentChild) {
13892
+ isBlockedByNonExhaustiveExpression = true;
13893
+ break;
13894
+ }
13895
+ currentChild = currentParent;
13896
+ currentParent = currentChild.parent ?? null;
13897
+ }
13898
+ if (isBlockedByNonExhaustiveExpression) continue;
13899
+ if (!conditionalExpression || !conditionalBranch) {
13900
+ coverageNodes.add(coverageCandidate);
13901
+ continue;
13902
+ }
13903
+ const coveredBranches = coveredBranchesByConditional.get(conditionalExpression) ?? /* @__PURE__ */ new Set();
13904
+ coveredBranches.add(conditionalBranch);
13905
+ coveredBranchesByConditional.set(conditionalExpression, coveredBranches);
13906
+ if (coveredBranches.has("alternate") && coveredBranches.has("consequent") && !visitedNodes.has(conditionalExpression)) {
13907
+ visitedNodes.add(conditionalExpression);
13908
+ pendingNodes.push(conditionalExpression);
13909
+ }
13910
+ }
13911
+ return coverageNodes;
13912
+ };
13913
+ //#endregion
13914
+ //#region src/plugin/utils/do-nodes-cover-every-path-from-function-entry.ts
13915
+ const doNodesCoverEveryPathFromFunctionEntry = (owner, matchingNodes, context, options = {}) => {
13916
+ const functionCfg = context.cfg.cfgFor(owner);
13917
+ if (!functionCfg) return false;
13918
+ const matchingBlocks = new Set([...collectExpressionPathCoverageNodes(owner, matchingNodes, context)].flatMap((matchingNode) => {
13919
+ const matchingBlock = functionCfg.blockOf(matchingNode);
13920
+ return matchingBlock ? [matchingBlock] : [];
13921
+ }));
13922
+ if (matchingBlocks.size === 0) return false;
13923
+ const visitedBlocks = new Set([functionCfg.entry]);
13924
+ const pendingBlocks = [functionCfg.entry];
13925
+ while (pendingBlocks.length > 0) {
13926
+ const currentBlock = pendingBlocks.pop();
13927
+ if (!currentBlock) break;
13928
+ if (matchingBlocks.has(currentBlock)) continue;
13929
+ for (const edge of currentBlock.successors) {
13930
+ if (options.ignoreThrowEdges && edge.kind === "throw") continue;
13931
+ if (edge.to === functionCfg.exit) return false;
13932
+ if (visitedBlocks.has(edge.to)) continue;
13933
+ visitedBlocks.add(edge.to);
13934
+ pendingBlocks.push(edge.to);
13935
+ }
13936
+ }
13937
+ return true;
13938
+ };
13939
+ //#endregion
13818
13940
  //#region src/plugin/utils/get-function-binding-name.ts
13819
13941
  const getFunctionBindingIdentifier$1 = (functionNode) => {
13820
13942
  if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
@@ -13852,14 +13974,19 @@ const isAstDescendant = (inner, outer) => {
13852
13974
  };
13853
13975
  //#endregion
13854
13976
  //#region src/plugin/utils/react-ref-origin.ts
13855
- const resolveReactRefSymbol = (memberExpression, scopes) => {
13977
+ const REACT_REF_API_NAMES = new Set(["createRef", "useRef"]);
13978
+ const resolveReactRefSymbol = (memberExpression, scopes, options = {}) => {
13856
13979
  const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
13857
13980
  if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticPropertyName(memberExpression) !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
13858
13981
  const symbol = resolveConstIdentifierAlias(receiver, scopes);
13859
13982
  if (!symbol?.initializer) return null;
13860
13983
  const initializer = stripParenExpression(symbol.initializer);
13861
13984
  if (!isNodeOfType(initializer, "CallExpression")) return null;
13862
- return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
13985
+ return isReactApiCall(initializer, options.includeCreateRef ? REACT_REF_API_NAMES : "useRef", scopes, {
13986
+ allowGlobalReactNamespace: true,
13987
+ allowUnboundBareCalls: options.allowUnboundBareCalls,
13988
+ resolveNamedAliases: options.resolveNamedAliases
13989
+ }) ? symbol : null;
13863
13990
  };
13864
13991
  const resolveReactRefCurrentOriginSymbol = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13865
13992
  const expression = stripParenExpression(node);
@@ -13958,9 +14085,35 @@ const isNodeReachableWithinFunction = (node, context) => {
13958
14085
  return false;
13959
14086
  };
13960
14087
  //#endregion
14088
+ //#region src/plugin/utils/is-within-assignment-target.ts
14089
+ const isWithinAssignmentTarget = (identifier) => {
14090
+ let currentNode = identifier;
14091
+ let parentNode = currentNode.parent;
14092
+ while (parentNode) {
14093
+ if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
14094
+ if (isNodeOfType(parentNode, "UpdateExpression") || isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "delete") return parentNode.argument === currentNode;
14095
+ if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
14096
+ currentNode = parentNode;
14097
+ parentNode = currentNode.parent;
14098
+ }
14099
+ return false;
14100
+ };
14101
+ //#endregion
13961
14102
  //#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
13962
14103
  const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
13963
14104
  const REPLAYABLE_ITERATOR_COLLECTION_CACHE = /* @__PURE__ */ new WeakMap();
14105
+ const REPLAY_ENTRY_DROPPING_ARRAY_METHOD_NAMES = new Set([
14106
+ "pop",
14107
+ "shift",
14108
+ "splice",
14109
+ "fill",
14110
+ "copyWithin"
14111
+ ]);
14112
+ const REPLAY_ENTRY_DROPPING_COLLECTION_METHOD_NAMES = new Set([
14113
+ "clear",
14114
+ "delete",
14115
+ "set"
14116
+ ]);
13964
14117
  const REACT_REF_EFFECT_ANALYSIS_CACHE = /* @__PURE__ */ new WeakMap();
13965
14118
  const RESOURCE_NOUN_BY_KIND = {
13966
14119
  subscribe: "subscription",
@@ -13994,13 +14147,111 @@ const resolveExpressionKey = (expression, context, visitedSymbolIds = /* @__PURE
13994
14147
  return objectKey ? `${objectKey}.${unwrappedExpression.property.name}` : null;
13995
14148
  }
13996
14149
  if (isNodeOfType(unwrappedExpression, "ThisExpression")) return "this";
13997
- if (isNodeOfType(unwrappedExpression, "Literal") && (typeof unwrappedExpression.value === "string" || typeof unwrappedExpression.value === "number")) return `literal:${String(unwrappedExpression.value)}`;
14150
+ if (isNodeOfType(unwrappedExpression, "Literal") && (typeof unwrappedExpression.value === "string" || typeof unwrappedExpression.value === "number" || typeof unwrappedExpression.value === "boolean")) return `literal:${String(unwrappedExpression.value)}`;
13998
14151
  if (isFunctionLike$1(unwrappedExpression)) {
13999
14152
  const rangeStart = getRangeStart(unwrappedExpression);
14000
14153
  return rangeStart === null ? null : `function:${rangeStart}`;
14001
14154
  }
14002
14155
  return null;
14003
14156
  };
14157
+ const resolveForEachProjection = (expression, context) => {
14158
+ if (!expression) return null;
14159
+ let currentExpression = stripParenExpression(expression);
14160
+ const memberNames = [];
14161
+ while (isNodeOfType(currentExpression, "MemberExpression") && !currentExpression.computed) {
14162
+ if (!isNodeOfType(currentExpression.property, "Identifier")) return null;
14163
+ memberNames.unshift(currentExpression.property.name);
14164
+ currentExpression = stripParenExpression(currentExpression.object);
14165
+ }
14166
+ if (!isNodeOfType(currentExpression, "Identifier")) return null;
14167
+ const symbol = context.scopes.symbolFor(currentExpression);
14168
+ if (!symbol || symbol.kind !== "parameter") return null;
14169
+ let callbackNode = symbol.bindingIdentifier.parent;
14170
+ while (callbackNode && !isFunctionLike$1(callbackNode)) callbackNode = callbackNode.parent;
14171
+ if (!callbackNode || !isFunctionLike$1(callbackNode)) return null;
14172
+ const forEachCall = findEnclosingForEachCall(callbackNode);
14173
+ if (!forEachCall) return null;
14174
+ const forEachCallee = stripParenExpression(forEachCall.callee);
14175
+ if (!isNodeOfType(forEachCallee, "MemberExpression")) return null;
14176
+ const collectionKey = resolveExpressionKey(forEachCallee.object, context);
14177
+ if (!collectionKey) return null;
14178
+ const firstParameter = callbackNode.params[0];
14179
+ const assignmentPattern = isNodeOfType(symbol.bindingIdentifier.parent, "AssignmentPattern") && symbol.bindingIdentifier.parent.left === symbol.bindingIdentifier ? symbol.bindingIdentifier.parent : null;
14180
+ const propertyName = isNodeOfType(firstParameter, "ObjectPattern") ? getDestructuredBindingPropertyName(symbol.bindingIdentifier) : null;
14181
+ const defaultValueKey = assignmentPattern ? resolveExpressionKey(assignmentPattern.right, context) : null;
14182
+ if (assignmentPattern && !defaultValueKey) return null;
14183
+ const parameterProjection = firstParameter === symbol.bindingIdentifier ? "value" : propertyName && defaultValueKey ? `${propertyName}=default:${defaultValueKey}` : propertyName;
14184
+ if (!parameterProjection) return null;
14185
+ return {
14186
+ collectionKey,
14187
+ projectionKey: [parameterProjection, ...memberNames].join(".")
14188
+ };
14189
+ };
14190
+ const resolveForEachProjectionKey = (expression, context) => {
14191
+ const projection = resolveForEachProjection(expression, context);
14192
+ return projection ? `forEach:${projection.collectionKey}:${projection.projectionKey}` : null;
14193
+ };
14194
+ const resolveResourceIdentityKey = (expression, context) => resolveForEachProjectionKey(expression, context) ?? resolveExpressionKey(expression, context);
14195
+ const resolveEventListenerCaptureValueIdentityKey = (expression, context) => {
14196
+ if (!expression) return null;
14197
+ const directIdentityKey = resolveResourceIdentityKey(expression, context);
14198
+ if (directIdentityKey) return directIdentityKey;
14199
+ const unwrappedExpression = stripParenExpression(expression);
14200
+ if (!isNodeOfType(unwrappedExpression, "BinaryExpression") && !isNodeOfType(unwrappedExpression, "LogicalExpression")) return null;
14201
+ const leftIdentityKey = resolveEventListenerCaptureValueIdentityKey(unwrappedExpression.left, context);
14202
+ const rightIdentityKey = resolveEventListenerCaptureValueIdentityKey(unwrappedExpression.right, context);
14203
+ return leftIdentityKey && rightIdentityKey ? `${unwrappedExpression.type}:${unwrappedExpression.operator}:${leftIdentityKey}:${rightIdentityKey}` : null;
14204
+ };
14205
+ const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqueOptionsIdentity) => {
14206
+ const capture = resolveEventListenerCapture(optionsNode, { allowIndeterminateEntries: true });
14207
+ if (capture !== null) return `capture:${String(capture)}`;
14208
+ if (!optionsNode) return null;
14209
+ const unwrappedOptions = stripParenExpression(optionsNode);
14210
+ if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) {
14211
+ const optionsKey = allowOpaqueOptionsIdentity ? resolveEventListenerCaptureValueIdentityKey(unwrappedOptions, context) : null;
14212
+ return optionsKey ? `options:${optionsKey}` : null;
14213
+ }
14214
+ let captureKey = "capture:false";
14215
+ for (const property of unwrappedOptions.properties ?? []) {
14216
+ if (!isNodeOfType(property, "Property")) {
14217
+ captureKey = null;
14218
+ continue;
14219
+ }
14220
+ const propertyName = getStaticPropertyKeyName(property);
14221
+ if (propertyName === null || !property.computed && propertyName === "__proto__") {
14222
+ captureKey = null;
14223
+ continue;
14224
+ }
14225
+ if (propertyName === "capture") {
14226
+ const propertyValueKey = resolveEventListenerCaptureValueIdentityKey(property.value, context);
14227
+ captureKey = propertyValueKey ? `capture-value:${propertyValueKey}` : null;
14228
+ }
14229
+ }
14230
+ return captureKey;
14231
+ };
14232
+ const resolveEventListenerCaptureProjection = (optionsNode, context) => {
14233
+ if (!optionsNode) return null;
14234
+ const unwrappedOptions = stripParenExpression(optionsNode);
14235
+ if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) return resolveForEachProjection(unwrappedOptions, context);
14236
+ let captureProjection = null;
14237
+ for (const property of unwrappedOptions.properties ?? []) {
14238
+ if (!isNodeOfType(property, "Property")) {
14239
+ captureProjection = null;
14240
+ continue;
14241
+ }
14242
+ const propertyName = getStaticPropertyKeyName(property);
14243
+ if (propertyName === null || !property.computed && propertyName === "__proto__") {
14244
+ captureProjection = null;
14245
+ continue;
14246
+ }
14247
+ if (propertyName === "capture") captureProjection = resolveForEachProjection(property.value, context);
14248
+ }
14249
+ return captureProjection;
14250
+ };
14251
+ const doEventListenerCapturesMatch = (registrationOptions, releaseOptions, context, allowOpaqueOptionsIdentity = false) => {
14252
+ const registrationCaptureKey = resolveEventListenerCaptureIdentityKey(registrationOptions, context, allowOpaqueOptionsIdentity);
14253
+ return registrationCaptureKey !== null && registrationCaptureKey === resolveEventListenerCaptureIdentityKey(releaseOptions, context, allowOpaqueOptionsIdentity);
14254
+ };
14004
14255
  const findAssignedResourceKey = (resourceNode, context) => {
14005
14256
  let currentNode = resourceNode;
14006
14257
  let parentNode = currentNode.parent;
@@ -14012,6 +14263,30 @@ const findAssignedResourceKey = (resourceNode, context) => {
14012
14263
  if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.right === currentNode) return resolveExpressionKey(parentNode.left, context);
14013
14264
  return null;
14014
14265
  };
14266
+ const resolveStableMediaQueryListenerIdentityKey = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
14267
+ if (!expression) return null;
14268
+ const unwrappedExpression = stripParenExpression(expression);
14269
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
14270
+ const symbol = context.scopes.symbolFor(unwrappedExpression);
14271
+ if (!symbol || visitedSymbolIds.has(symbol.id) || !symbol.references.every((reference) => reference.flag === "read" && !isWithinAssignmentTarget(reference.identifier))) return null;
14272
+ const initializer = getDirectUnreassignedInitializer(symbol);
14273
+ if (!initializer) return `symbol:${symbol.id}`;
14274
+ const unwrappedInitializer = stripParenExpression(initializer);
14275
+ if (!isNodeOfType(unwrappedInitializer, "Identifier")) return `symbol:${symbol.id}`;
14276
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
14277
+ nextVisitedSymbolIds.add(symbol.id);
14278
+ return resolveStableMediaQueryListenerIdentityKey(unwrappedInitializer, context, nextVisitedSymbolIds) ?? `symbol:${symbol.id}`;
14279
+ }
14280
+ if (isFunctionLike$1(unwrappedExpression)) {
14281
+ const rangeStart = getRangeStart(unwrappedExpression);
14282
+ return rangeStart === null ? null : `function:${rangeStart}`;
14283
+ }
14284
+ return null;
14285
+ };
14286
+ const isProvenLegacyMediaQueryListMethodCall = (callNode, methodName, context) => {
14287
+ const callee = stripParenExpression(callNode.callee);
14288
+ return callNode.arguments?.length === 1 && isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName && getProvenDomEventTargetPrototypeOwnerNames(callee.object, context.scopes).includes("MediaQueryList");
14289
+ };
14015
14290
  const getCallRegistrationDetails = (callNode, context) => {
14016
14291
  const callee = stripParenExpression(callNode.callee);
14017
14292
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return {
@@ -14020,11 +14295,17 @@ const getCallRegistrationDetails = (callNode, context) => {
14020
14295
  eventKey: null,
14021
14296
  handlerKey: null
14022
14297
  };
14298
+ if (isProvenLegacyMediaQueryListMethodCall(callNode, "addListener", context)) return {
14299
+ receiverKey: resolveStableMediaQueryListenerIdentityKey(callee.object, context),
14300
+ registrationVerbName: callee.property.name,
14301
+ eventKey: null,
14302
+ handlerKey: resolveStableMediaQueryListenerIdentityKey(callNode.arguments?.[0], context)
14303
+ };
14023
14304
  return {
14024
- receiverKey: resolveExpressionKey(callee.object, context),
14305
+ receiverKey: resolveResourceIdentityKey(callee.object, context),
14025
14306
  registrationVerbName: callee.property.name,
14026
- eventKey: resolveExpressionKey(callNode.arguments?.[0], context),
14027
- handlerKey: resolveExpressionKey(callNode.arguments?.[1], context)
14307
+ eventKey: resolveResourceIdentityKey(callNode.arguments?.[0], context),
14308
+ handlerKey: resolveResourceIdentityKey(callNode.arguments?.[1], context)
14028
14309
  };
14029
14310
  };
14030
14311
  const findSubscribeLikeUsages = (callback, context) => {
@@ -14124,30 +14405,6 @@ const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, conte
14124
14405
  }
14125
14406
  return matchingBlocks.size > 0;
14126
14407
  };
14127
- const doMatchingNodesCoverEveryPathFromFunctionEntry = (owner, matchingNodes, context) => {
14128
- const functionCfg = context.cfg.cfgFor(owner);
14129
- if (!functionCfg) return false;
14130
- const matchingBlocks = new Set(matchingNodes.flatMap((matchingNode) => {
14131
- if (context.cfg.enclosingFunction(matchingNode) !== owner) return [];
14132
- const matchingBlock = functionCfg.blockOf(matchingNode);
14133
- return matchingBlock ? [matchingBlock] : [];
14134
- }));
14135
- if (matchingBlocks.size === 0) return false;
14136
- const visitedBlocks = new Set([functionCfg.entry]);
14137
- const pendingBlocks = [functionCfg.entry];
14138
- while (pendingBlocks.length > 0) {
14139
- const currentBlock = pendingBlocks.pop();
14140
- if (!currentBlock) break;
14141
- if (matchingBlocks.has(currentBlock)) continue;
14142
- for (const edge of currentBlock.successors) {
14143
- if (edge.to === functionCfg.exit) return false;
14144
- if (visitedBlocks.has(edge.to)) continue;
14145
- visitedBlocks.add(edge.to);
14146
- pendingBlocks.push(edge.to);
14147
- }
14148
- }
14149
- return true;
14150
- };
14151
14408
  const removeSynchronouslyReleasedUsages = (callback, usages, context) => {
14152
14409
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
14153
14410
  if (!isNodeOfType(callback.body, "BlockStatement")) return usages;
@@ -14390,18 +14647,6 @@ const findPushedResourceCollectionKey = (usage, context) => {
14390
14647
  return memberNode.property.name === "forEach" || memberNode.property.name === "push";
14391
14648
  }) ? resolveExpressionKey(pushCallee.object, context) : null;
14392
14649
  };
14393
- const isWithinAssignmentTarget = (identifier) => {
14394
- let currentNode = identifier;
14395
- let parentNode = currentNode.parent;
14396
- while (parentNode) {
14397
- if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
14398
- if (isNodeOfType(parentNode, "UpdateExpression") || isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "delete") return parentNode.argument === currentNode;
14399
- if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
14400
- currentNode = parentNode;
14401
- parentNode = currentNode.parent;
14402
- }
14403
- return false;
14404
- };
14405
14650
  const resolveStableValue = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
14406
14651
  if (!expression) return null;
14407
14652
  const unwrappedExpression = stripParenExpression(expression);
@@ -14449,8 +14694,8 @@ const isSynchronousIteratorCallback = (functionNode) => {
14449
14694
  return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments?.[0] === functionNode;
14450
14695
  };
14451
14696
  const findEnclosingForEachCall = (node) => {
14452
- const callbackNode = findEnclosingFunction$1(node);
14453
- if (!callbackNode) return null;
14697
+ const callbackNode = isFunctionLike$1(node) ? node : findEnclosingFunction$1(node);
14698
+ if (!callbackNode || !isFunctionLike$1(callbackNode) || callbackNode.async || callbackNode.generator) return null;
14454
14699
  const callNode = callbackNode.parent;
14455
14700
  if (!isNodeOfType(callNode, "CallExpression") || callNode.arguments?.[0] !== callbackNode) return null;
14456
14701
  const callee = stripParenExpression(callNode.callee);
@@ -14525,7 +14770,7 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
14525
14770
  const helperFunction = isNodeOfType(stableHelperFunction, "Identifier") ? resolveSingleAssignedCleanupFunction(stableHelperFunction, usage, context) : stableHelperFunction;
14526
14771
  if (helperFunction && isFunctionLike$1(helperFunction) && !helperFunction.async && !helperFunction.generator && doesCleanupFunctionReleaseUsage(helperFunction, usage, context, new Set(visitedFunctions))) matchingLoopOrHelperAnchors.push(cleanupCall);
14527
14772
  });
14528
- return didCleanupFunctionMatch || doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, matchingLoopOrHelperAnchors, context);
14773
+ return didCleanupFunctionMatch || doNodesCoverEveryPathFromFunctionEntry(cleanupFunction, matchingLoopOrHelperAnchors, context);
14529
14774
  };
14530
14775
  const doesBoundCleanupReleaseUsage = (expression, usage, context) => {
14531
14776
  const callExpression = stripParenExpression(expression);
@@ -14554,7 +14799,7 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
14554
14799
  walkInsideStatementBlocks(callback.body, (child) => {
14555
14800
  if (isNodeOfType(child, "ReturnStatement") && child.argument && doesReturnedValueReleaseUsage(stripParenExpression(child.argument))) matchingCleanupReturns.push(child);
14556
14801
  });
14557
- return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
14802
+ return doNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
14558
14803
  };
14559
14804
  const doesTestRequireLiveExpressionKey = (test, expressionKey, context) => {
14560
14805
  if (resolveExpressionKey(test, context) === expressionKey) return true;
@@ -14578,7 +14823,24 @@ const findLiveExpressionGuardForRelease = (releaseCall, owner, expressionKey, co
14578
14823
  return null;
14579
14824
  };
14580
14825
  const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => usage.handleKey === null ? null : findLiveExpressionGuardForRelease(releaseCall, owner, usage.handleKey, context);
14581
- const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
14826
+ const hasExecutionBoundaryNotSharedWithUsage = (node, usageNode, owner) => {
14827
+ const usageAncestors = /* @__PURE__ */ new Set();
14828
+ let usageAncestor = usageNode;
14829
+ while (usageAncestor && usageAncestor !== owner) {
14830
+ usageAncestors.add(usageAncestor);
14831
+ usageAncestor = usageAncestor.parent ?? null;
14832
+ }
14833
+ let descendant = node;
14834
+ let ancestor = descendant.parent ?? null;
14835
+ while (ancestor && ancestor !== owner) {
14836
+ const guardedSubtree = isNodeOfType(ancestor, "IfStatement") && (ancestor.consequent === descendant || ancestor.alternate === descendant) || isNodeOfType(ancestor, "ConditionalExpression") && (ancestor.consequent === descendant || ancestor.alternate === descendant) || isNodeOfType(ancestor, "LogicalExpression") && ancestor.right === descendant || isNodeOfType(ancestor, "AssignmentPattern") && ancestor.right === descendant || (isNodeOfType(ancestor, "ForStatement") || isNodeOfType(ancestor, "ForInStatement") || isNodeOfType(ancestor, "ForOfStatement") || isNodeOfType(ancestor, "WhileStatement") || isNodeOfType(ancestor, "DoWhileStatement")) && ancestor.body === descendant ? descendant : isNodeOfType(ancestor, "SwitchCase") ? ancestor : null;
14837
+ if (guardedSubtree && !usageAncestors.has(guardedSubtree)) return true;
14838
+ descendant = ancestor;
14839
+ ancestor = descendant.parent ?? null;
14840
+ }
14841
+ return false;
14842
+ };
14843
+ const hasRerunReleaseBeforeUsage = (callback, usage, context, allowUnreleasedPathsWithoutUsage = false) => {
14582
14844
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
14583
14845
  const functionCfg = context.cfg.cfgFor(callback);
14584
14846
  const usageBlock = functionCfg?.blockOf(usage.node);
@@ -14589,7 +14851,8 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
14589
14851
  if (!isNodeOfType(child, "CallExpression")) return;
14590
14852
  const releaseStart = getRangeStart(child);
14591
14853
  const handleGuard = findDirectHandleGuardForRelease(child, callback, usage, context);
14592
- if (releaseStart === null || releaseStart >= usageStart || functionCfg.blockOf(child) !== usageBlock && !handleGuard) return;
14854
+ const releaseBlock = functionCfg.blockOf(child);
14855
+ if (releaseStart === null || releaseStart >= usageStart || releaseBlock !== usageBlock && !handleGuard || !handleGuard && hasExecutionBoundaryNotSharedWithUsage(child, usage.node, callback)) return;
14593
14856
  if (doesReleaseCallMatchUsage(child, usage, context)) {
14594
14857
  matchingReleaseAnchors.push(handleGuard ?? child);
14595
14858
  return;
@@ -14597,7 +14860,7 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
14597
14860
  const helperFunction = resolveStableValue(child.callee, context);
14598
14861
  if (helperFunction && isFunctionLike$1(helperFunction) && doesCleanupFunctionReleaseUsage(helperFunction, usage, context)) matchingReleaseAnchors.push(handleGuard ?? child);
14599
14862
  });
14600
- return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
14863
+ return allowUnreleasedPathsWithoutUsage ? doMatchingNodesCoverEveryPathBeforeUsage(usage.node, matchingReleaseAnchors, callback, context) : doNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
14601
14864
  };
14602
14865
  const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
14603
14866
  const componentFunction = findEnclosingFunction$1(callback);
@@ -14617,7 +14880,7 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
14617
14880
  });
14618
14881
  return didFindUnmountCleanup;
14619
14882
  };
14620
- const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
14883
+ const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasStableUnmountCleanupForUsage(callback, usage, context) && hasRerunReleaseBeforeUsage(callback, usage, context, usage.registrationVerbName === "setTimeout");
14621
14884
  const collectBlockingBooleanStates = (expression, blockedExpressionValue, guardNode, context) => {
14622
14885
  const unwrappedExpression = stripParenExpression(expression);
14623
14886
  if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, guardNode, context);
@@ -14848,7 +15111,7 @@ const hasGuardedRefOwnedNestedCleanup = (callback, usage, cleanupReturns, contex
14848
15111
  const usageFunction = findEnclosingFunction$1(usage.node);
14849
15112
  const usageExpression = findTransparentExpressionRoot(usage.node);
14850
15113
  const usageAssignment = usageExpression.parent;
14851
- if (usage.kind !== "subscribe" && usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usageAssignment, "AssignmentExpression") || usageAssignment.operator !== "=" || usageAssignment.right !== usageExpression || !resolveReactRefSymbol(stripParenExpression(usageAssignment.left), context.scopes) || !collectSynchronouslyEffectInvokedFunctions(callback).has(usageFunction) || !cleanupReturnsReleaseUsage(cleanupReturns, usage, context) || !doMatchingNodesCoverEveryPathFromFunctionEntry(callback, cleanupReturns, context)) return false;
15114
+ if (usage.kind !== "subscribe" && usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usageAssignment, "AssignmentExpression") || usageAssignment.operator !== "=" || usageAssignment.right !== usageExpression || !resolveReactRefSymbol(stripParenExpression(usageAssignment.left), context.scopes) || !collectSynchronouslyEffectInvokedFunctions(callback).has(usageFunction) || !cleanupReturnsReleaseUsage(cleanupReturns, usage, context) || !doNodesCoverEveryPathFromFunctionEntry(callback, cleanupReturns, context)) return false;
14852
15115
  const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
14853
15116
  if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
14854
15117
  const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
@@ -14896,7 +15159,7 @@ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) =>
14896
15159
  });
14897
15160
  }
14898
15161
  });
14899
- if (!doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, globalReleaseProofs.map((releaseProof) => releaseProof.anchor), context)) return false;
15162
+ if (!doNodesCoverEveryPathFromFunctionEntry(cleanupFunction, globalReleaseProofs.map((releaseProof) => releaseProof.anchor), context)) return false;
14900
15163
  globalReleaseProofsByCleanup.set(cleanupFunction, globalReleaseProofs);
14901
15164
  }
14902
15165
  const handleAssignments = handleSymbol.references.filter((reference) => isWithinAssignmentTarget(reference.identifier));
@@ -15099,9 +15362,7 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
15099
15362
  const nodeParameterKey = resolveExpressionKey(usageFunction.params?.[0], context);
15100
15363
  const releaseReceiverKey = resolveExpressionKey(releaseCallee.object, context);
15101
15364
  if (registrationReceiverKey === null || registrationReceiverKey !== nodeParameterKey || releaseReceiverKey === null || usage.eventKey === null || usage.eventKey !== resolveExpressionKey(releaseCall.arguments?.[0], context) || usage.handlerKey === null || usage.handlerKey !== resolveExpressionKey(releaseCall.arguments?.[1], context)) return false;
15102
- const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
15103
- const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
15104
- if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
15365
+ if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], releaseCall.arguments?.[2], context)) return false;
15105
15366
  const releaseStart = getRangeStart(releaseCall);
15106
15367
  const matchingOwnershipAssignments = [];
15107
15368
  const usageFunctionBody = usageFunction.body;
@@ -15111,7 +15372,127 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
15111
15372
  });
15112
15373
  const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
15113
15374
  const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
15114
- return doMatchingNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
15375
+ return doNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
15376
+ };
15377
+ const findDirectExhaustiveForEachCleanupFunction = (releaseNode, requiredCollectionKeys, context) => {
15378
+ let currentNode = findTransparentExpressionRoot(releaseNode);
15379
+ const visitedFunctions = /* @__PURE__ */ new Set();
15380
+ const replayedCollectionKeys = /* @__PURE__ */ new Set();
15381
+ while (true) {
15382
+ const ownerFunction = findEnclosingFunction$1(currentNode);
15383
+ if (!ownerFunction || !isFunctionLike$1(ownerFunction) || visitedFunctions.has(ownerFunction)) return null;
15384
+ visitedFunctions.add(ownerFunction);
15385
+ const isDirectConciseBody = ownerFunction.body === currentNode;
15386
+ const statementNode = currentNode.parent;
15387
+ const isDirectBlockStatement = isNodeOfType(ownerFunction.body, "BlockStatement") && isNodeOfType(statementNode, "ExpressionStatement") && statementNode.parent === ownerFunction.body;
15388
+ if (!isDirectConciseBody && !isDirectBlockStatement || !doNodesCoverEveryPathFromFunctionEntry(ownerFunction, [isDirectBlockStatement ? statementNode : currentNode], context)) return null;
15389
+ const forEachCall = findEnclosingForEachCall(ownerFunction);
15390
+ if (!forEachCall) return replayedCollectionKeys.size === requiredCollectionKeys.size && isReturnedEffectCleanupFunction(ownerFunction, context) ? ownerFunction : null;
15391
+ const forEachCallee = stripParenExpression(forEachCall.callee);
15392
+ if (!isNodeOfType(forEachCallee, "MemberExpression")) return null;
15393
+ const collectionKey = resolveExpressionKey(forEachCallee.object, context);
15394
+ if (!collectionKey || !requiredCollectionKeys.has(collectionKey)) return null;
15395
+ replayedCollectionKeys.add(collectionKey);
15396
+ currentNode = findTransparentExpressionRoot(forEachCall);
15397
+ }
15398
+ };
15399
+ const collectReplayOwnerFunctions = (usageNode) => {
15400
+ const ownerFunctions = /* @__PURE__ */ new Set();
15401
+ let currentNode = usageNode;
15402
+ while (true) {
15403
+ const ownerFunction = findEnclosingFunction$1(currentNode);
15404
+ if (!ownerFunction || !isFunctionLike$1(ownerFunction) || ownerFunctions.has(ownerFunction)) break;
15405
+ ownerFunctions.add(ownerFunction);
15406
+ const forEachCall = findEnclosingForEachCall(ownerFunction);
15407
+ if (!forEachCall) break;
15408
+ currentNode = forEachCall;
15409
+ }
15410
+ return ownerFunctions;
15411
+ };
15412
+ const hasCollectionMutationBeforeRelease = (usageNode, releaseNode, collectionKeys, context) => {
15413
+ const usageStart = getRangeStart(usageNode);
15414
+ const releaseStart = getRangeStart(releaseNode);
15415
+ if (usageStart === null || releaseStart === null) return true;
15416
+ const setupOwnerFunctions = collectReplayOwnerFunctions(usageNode);
15417
+ const cleanupOwnerFunctions = collectReplayOwnerFunctions(releaseNode);
15418
+ let programNode = usageNode;
15419
+ while (programNode.parent) programNode = programNode.parent;
15420
+ let didFindMutation = false;
15421
+ walkAst(programNode, (child) => {
15422
+ if (didFindMutation) return false;
15423
+ const childStart = getRangeStart(child);
15424
+ if (childStart === null) return;
15425
+ const ownerFunction = context.cfg.enclosingFunction(child);
15426
+ if (!ownerFunction) return;
15427
+ const isAfterRegistration = setupOwnerFunctions.has(ownerFunction) && childStart > usageStart;
15428
+ const isBeforeRelease = cleanupOwnerFunctions.has(ownerFunction) && childStart < releaseStart;
15429
+ if (!isAfterRegistration && !isBeforeRelease) return;
15430
+ if (isNodeOfType(child, "AssignmentExpression")) {
15431
+ const assignmentKey = resolveExpressionKey(child.left, context);
15432
+ const assignmentTarget = stripParenExpression(child.left);
15433
+ if (assignmentKey && [...collectionKeys].some((collectionKey) => assignmentKey === collectionKey || assignmentKey === `${collectionKey}.length`) || isNodeOfType(assignmentTarget, "MemberExpression") && assignmentTarget.computed && collectionKeys.has(resolveExpressionKey(assignmentTarget.object, context) ?? "")) {
15434
+ didFindMutation = true;
15435
+ return false;
15436
+ }
15437
+ return;
15438
+ }
15439
+ if (isNodeOfType(child, "UnaryExpression") && child.operator === "delete") {
15440
+ const deletedMember = stripParenExpression(child.argument);
15441
+ if (!isNodeOfType(deletedMember, "MemberExpression")) return;
15442
+ if (collectionKeys.has(resolveExpressionKey(deletedMember.object, context) ?? "")) {
15443
+ didFindMutation = true;
15444
+ return false;
15445
+ }
15446
+ return;
15447
+ }
15448
+ if (isNodeOfType(child, "UpdateExpression")) {
15449
+ const updatedKey = resolveExpressionKey(child.argument, context);
15450
+ if (updatedKey && [...collectionKeys].some((collectionKey) => updatedKey === `${collectionKey}.length`)) {
15451
+ didFindMutation = true;
15452
+ return false;
15453
+ }
15454
+ return;
15455
+ }
15456
+ if (!isNodeOfType(child, "CallExpression")) return;
15457
+ const callee = stripParenExpression(child.callee);
15458
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || !REPLAY_ENTRY_DROPPING_ARRAY_METHOD_NAMES.has(callee.property.name) && !REPLAY_ENTRY_DROPPING_COLLECTION_METHOD_NAMES.has(callee.property.name) || !collectionKeys.has(resolveExpressionKey(callee.object, context) ?? "")) return;
15459
+ didFindMutation = true;
15460
+ return false;
15461
+ });
15462
+ return didFindMutation;
15463
+ };
15464
+ const usesUnaryListenerSignature = (registrationCall, releaseCall) => getCalleeName$1(registrationCall) === "addListener" && registrationCall.arguments?.length === 1 && releaseCall.arguments?.length === 1;
15465
+ const hasSafeForEachProjectionCleanup = (registrationCall, releaseCall, context) => {
15466
+ const registrationCallee = stripParenExpression(registrationCall.callee);
15467
+ const releaseCallee = stripParenExpression(releaseCall.callee);
15468
+ if (!isNodeOfType(registrationCallee, "MemberExpression") || !isNodeOfType(releaseCallee, "MemberExpression")) return true;
15469
+ const registrationVerbName = getCalleeName$1(registrationCall);
15470
+ const releaseVerbName = getCalleeName$1(releaseCall);
15471
+ const releaseHandler = releaseCall.arguments?.[usesUnaryListenerSignature(registrationCall, releaseCall) ? 0 : 1];
15472
+ const releaseFunction = findEnclosingFunction$1(releaseCall);
15473
+ const registrationEventKey = resolveResourceIdentityKey(registrationCall.arguments?.[0], context);
15474
+ const releaseEventKey = resolveResourceIdentityKey(releaseCall.arguments?.[0], context);
15475
+ const doesHandlerlessOffReleaseEveryRegistration = releaseVerbName === "off" && !releaseHandler && (releaseCall.arguments?.length === 0 || registrationEventKey !== null && registrationEventKey === releaseEventKey);
15476
+ if (Boolean(releaseFunction && isFunctionLike$1(releaseFunction) && isReturnedEffectCleanupFunction(releaseFunction, context) && doNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseCall], context)) && (releaseVerbName !== null && UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName) || doesHandlerlessOffReleaseEveryRegistration)) return true;
15477
+ const projections = [
15478
+ registrationCallee.object,
15479
+ registrationCall.arguments?.[0],
15480
+ registrationCall.arguments?.[1],
15481
+ releaseCallee.object,
15482
+ releaseCall.arguments?.[0],
15483
+ releaseCall.arguments?.[1]
15484
+ ].flatMap((expression) => {
15485
+ const projection = resolveForEachProjection(expression, context);
15486
+ return projection ? [projection] : [];
15487
+ });
15488
+ if (registrationVerbName === "addEventListener" && releaseVerbName === "removeEventListener") for (const optionsNode of [registrationCall.arguments?.[2], releaseCall.arguments?.[2]]) {
15489
+ const captureProjection = resolveEventListenerCaptureProjection(optionsNode, context);
15490
+ if (captureProjection) projections.push(captureProjection);
15491
+ }
15492
+ if (projections.length === 0) return true;
15493
+ const collectionKeys = new Set(projections.map((projection) => projection.collectionKey));
15494
+ if (!findDirectExhaustiveForEachCleanupFunction(releaseCall, collectionKeys, context)) return false;
15495
+ return !hasCollectionMutationBeforeRelease(registrationCall, releaseCall, collectionKeys, context);
15115
15496
  };
15116
15497
  const doesReleaseCallMatchUsage = (node, usage, context) => {
15117
15498
  const callNode = isNodeOfType(node, "ChainExpression") ? node.expression : node;
@@ -15128,8 +15509,8 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15128
15509
  const releaseVerbName = getReleaseVerbName(callNode);
15129
15510
  if (!releaseVerbName) return false;
15130
15511
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
15131
- const releaseReceiverKey = resolveExpressionKey(callee.object, context);
15132
- const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
15512
+ const releaseReceiverKey = resolveResourceIdentityKey(callee.object, context);
15513
+ const releaseEventKey = resolveResourceIdentityKey(callNode.arguments?.[0], context);
15133
15514
  const pairedReleaseVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
15134
15515
  const pushedResourceCollectionKey = findPushedResourceCollectionKey(usage, context);
15135
15516
  const releaseReceiverForOfStatement = findForOfStatementForIteratorExpression(callee.object, context);
@@ -15139,7 +15520,13 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15139
15520
  if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
15140
15521
  if (usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub" || releaseVerbName === "close" || releaseVerbName === "unwatch" || releaseVerbName === "unlisten" || BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(releaseVerbName))) return true;
15141
15522
  if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
15523
+ if (usage.registrationVerbName === "addListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1) return isProvenLegacyMediaQueryListMethodCall(usage.node, "addListener", context) && releaseVerbName === "removeListener" && isProvenLegacyMediaQueryListMethodCall(callNode, "removeListener", context) && usage.receiverKey !== null && resolveStableMediaQueryListenerIdentityKey(callee.object, context) === usage.receiverKey && usage.handlerKey !== null && resolveStableMediaQueryListenerIdentityKey(callNode.arguments?.[0], context) === usage.handlerKey;
15142
15524
  if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
15525
+ if (usage.registrationVerbName === "addEventListener" && releaseVerbName === "removeEventListener" && isNodeOfType(usage.node, "CallExpression")) {
15526
+ if (!isNodeOfType(stripParenExpression(usage.node.callee), "MemberExpression")) return false;
15527
+ if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], callNode.arguments?.[2], context, true)) return false;
15528
+ }
15529
+ if (isNodeOfType(usage.node, "CallExpression") && !hasSafeForEachProjectionCleanup(usage.node, callNode, context)) return false;
15143
15530
  if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) return false;
15144
15531
  if (usage.registrationVerbName === "subscribe" && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub") && usage.handleKey !== null && resolveExpressionKey(callNode.arguments?.[0], context) === usage.handleKey) return true;
15145
15532
  const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
@@ -15149,6 +15536,9 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15149
15536
  if (isAssignmentFormForOfIteratorReference(usageEventArgument, context) || isAssignmentFormForOfIteratorReference(releaseEventArgument, context)) return false;
15150
15537
  if (usage.eventKey !== null && releaseEventKey !== null && usage.eventKey !== releaseEventKey) {
15151
15538
  if (!isNodeOfType(usage.node, "CallExpression")) return false;
15539
+ const registrationEventProjectionKey = resolveForEachProjectionKey(usage.node.arguments?.[0], context);
15540
+ const releaseEventProjectionKey = resolveForEachProjectionKey(callNode.arguments?.[0], context);
15541
+ if ((registrationEventProjectionKey !== null || releaseEventProjectionKey !== null) && registrationEventProjectionKey !== releaseEventProjectionKey) return false;
15152
15542
  const usageForOfStatement = findForOfStatementForIteratorExpression(usageEventArgument, context);
15153
15543
  const releaseForOfStatement = findForOfStatementForIteratorExpression(releaseEventArgument, context);
15154
15544
  if (usageForOfStatement === null !== (releaseForOfStatement === null)) return false;
@@ -15162,9 +15552,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15162
15552
  const registrationCallee = stripParenExpression(usage.node.callee);
15163
15553
  if (!isNodeOfType(registrationCallee, "MemberExpression")) return false;
15164
15554
  if (!isStableLoopReceiver(registrationCallee.object, context) || !isStableLoopReceiver(callee.object, context) || registrationHandlerSymbolId === null || registrationHandlerSymbolId !== releaseHandlerSymbolId) return false;
15165
- const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
15166
- const releaseCapture = resolveEventListenerCapture(callNode.arguments?.[2], { allowIndeterminateEntries: true });
15167
- if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
15555
+ if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], callNode.arguments?.[2], context)) return false;
15168
15556
  if (!isDirectExhaustiveForOfRelease(callNode, releaseForOfStatement)) return false;
15169
15557
  }
15170
15558
  }
@@ -15173,27 +15561,28 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15173
15561
  return isNodeOfType(handlerArgument, "Literal") && handlerArgument.value === null;
15174
15562
  }
15175
15563
  if (releaseVerbName === "removeEventListener" || releaseVerbName === "removeListener" || releaseVerbName === "off") {
15176
- const usesUnaryListenerSignature = usage.registrationVerbName === "addListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1 && callNode.arguments?.length === 1;
15177
- const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
15564
+ const usesUnaryListenerSignatureForCalls = isNodeOfType(usage.node, "CallExpression") && usesUnaryListenerSignature(usage.node, callNode);
15565
+ const releaseHandler = usesUnaryListenerSignatureForCalls ? callNode.arguments?.[0] : callNode.arguments?.[1];
15178
15566
  if (!releaseHandler) return releaseVerbName === "off";
15179
- const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
15180
- const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignature ? 0 : 1] : null;
15181
- return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
15567
+ const expectedHandlerKey = usesUnaryListenerSignatureForCalls ? usage.eventKey : usage.handlerKey;
15568
+ const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignatureForCalls ? 0 : 1] : null;
15569
+ return expectedHandlerKey !== null && resolveResourceIdentityKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
15182
15570
  }
15183
15571
  if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
15184
15572
  return true;
15185
15573
  };
15186
15574
  const matchesPairedReleaseVerb = (releaseVerbName, pairedVerbNames) => pairedVerbNames.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName);
15187
15575
  const isReturnedEffectCleanupFunction = (functionNode, context) => {
15188
- let currentNode = functionNode;
15189
- let parentNode = currentNode.parent;
15190
- while (isNodeOfType(parentNode, "ChainExpression") || isNodeOfType(parentNode, "TSAsExpression") || isNodeOfType(parentNode, "TSNonNullExpression")) {
15191
- currentNode = parentNode;
15192
- parentNode = currentNode.parent;
15193
- }
15194
- const effectCallback = isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode ? findEnclosingFunction$1(parentNode) : isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode ? parentNode : null;
15195
- const effectCall = effectCallback?.parent;
15196
- return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isReactHookCall(effectCall, CLEANUP_EFFECT_HOOK_NAMES, context.scopes));
15576
+ const effectCallback = findEnclosingFunction$1(functionNode);
15577
+ if (!effectCallback || !isFunctionLike$1(effectCallback)) return false;
15578
+ const effectCall = effectCallback.parent;
15579
+ if (!isNodeOfType(effectCall, "CallExpression") || !isReactHookCall(effectCall, CLEANUP_EFFECT_HOOK_NAMES, context.scopes)) return false;
15580
+ if (!isNodeOfType(effectCallback.body, "BlockStatement")) return resolveStableValue(effectCallback.body, context) === functionNode;
15581
+ let isReturned = false;
15582
+ walkInsideStatementBlocks(effectCallback.body, (child) => {
15583
+ if (isNodeOfType(child, "ReturnStatement") && child.argument && resolveStableValue(child.argument, context) === functionNode) isReturned = true;
15584
+ });
15585
+ return isReturned;
15197
15586
  };
15198
15587
  const isPotentiallyReachableFunction = (functionNode, context) => {
15199
15588
  if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode, context)) return true;
@@ -15283,7 +15672,7 @@ const hasEffectCleanupInvocation = (storage, usage, context) => {
15283
15672
  if (!cleanupFunction || !cleanupFunctionInvokesRef(cleanupFunction)) return;
15284
15673
  matchingReturns.push(child);
15285
15674
  });
15286
- return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15675
+ return doNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15287
15676
  };
15288
15677
  let didFindInvocation = false;
15289
15678
  walkAst(componentFunction.body, (child) => {
@@ -15335,12 +15724,10 @@ const isRetainedDisposerRefRelease = (releaseNode, usage, context) => {
15335
15724
  return findRetainedDisposerStorages(disposerFunction, usage, context).some((storage) => isRetainedDisposerStorageEstablished(storage, usage, context) && !hasUnsafeRetainedDisposerOverwrite(storage, usage, context) && (hasEffectCleanupInvocation(storage, usage, context) || hasCallbackRefReplacementInvocation(storage, usage, context)));
15336
15725
  };
15337
15726
  const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
15338
- if (usage.kind !== "subscribe" || usage.registrationVerbName !== "addEventListener" || usage.receiverKey === null || usage.eventKey === null || !isNodeOfType(usage.node, "CallExpression") || !isFunctionLike$1(releaseFunction) || releaseFunction.async || releaseFunction.generator || !isNodeOfType(releaseFunction.body, "BlockStatement") || !doMatchingNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseNode], context)) return false;
15339
- const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
15727
+ if (usage.kind !== "subscribe" || usage.registrationVerbName !== "addEventListener" || usage.receiverKey === null || usage.eventKey === null || !isNodeOfType(usage.node, "CallExpression") || !isFunctionLike$1(releaseFunction) || releaseFunction.async || releaseFunction.generator || !isNodeOfType(releaseFunction.body, "BlockStatement") || !doNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseNode], context)) return false;
15340
15728
  const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
15341
15729
  if (!isNodeOfType(releaseCall, "CallExpression")) return false;
15342
- const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
15343
- if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
15730
+ if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], releaseCall.arguments?.[2], context)) return false;
15344
15731
  const ownerFunction = findEnclosingFunction$1(releaseFunction);
15345
15732
  if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
15346
15733
  const triggerRegistrations = [];
@@ -15565,7 +15952,7 @@ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retaine
15565
15952
  walkInsideStatementBlocks(effectCallback.body, (child) => {
15566
15953
  if (isNodeOfType(child, "ReturnStatement") && child.argument && matchesReturnedCleanup(stripParenExpression(child.argument))) matchingReturns.push(child);
15567
15954
  });
15568
- return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15955
+ return doNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15569
15956
  };
15570
15957
  const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
15571
15958
  const componentFunction = findEnclosingFunction$1(retainedFunction);
@@ -15608,15 +15995,6 @@ const findUnconditionalReturnStatement = (expression, ownerFunction) => {
15608
15995
  const returnStatement = expressionRoot.parent;
15609
15996
  return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === expressionRoot && findEnclosingFunction$1(returnStatement) === ownerFunction ? returnStatement : null;
15610
15997
  };
15611
- const getFinalSequenceExpressionValue = (expression) => {
15612
- let finalExpression = stripParenExpression(expression);
15613
- while (isNodeOfType(finalExpression, "SequenceExpression")) {
15614
- const sequenceResult = finalExpression.expressions.at(-1);
15615
- if (!sequenceResult) break;
15616
- finalExpression = stripParenExpression(sequenceResult);
15617
- }
15618
- return finalExpression;
15619
- };
15620
15998
  const doesResourceResultEscape = (resourceNode, allowReturnedResourceEscape, allowConciseReturnEscape, context) => {
15621
15999
  if (!allowReturnedResourceEscape) return false;
15622
16000
  let currentNode = resourceNode;
@@ -15865,7 +16243,7 @@ const isReactRefCallbackCleanupOwnedByEffect = (retainedFunction, cleanupFunctio
15865
16243
  if (child !== returnedCleanupFunction.body && isFunctionLike$1(child)) return false;
15866
16244
  if (isNodeOfType(child, "CallExpression") && resolveRefOwnedCleanupFunction(child.callee, context) === cleanupFunction) matchingCalls.push(child);
15867
16245
  });
15868
- return doMatchingNodesCoverEveryPathFromFunctionEntry(returnedCleanupFunction, matchingCalls, context);
16246
+ return doNodesCoverEveryPathFromFunctionEntry(returnedCleanupFunction, matchingCalls, context);
15869
16247
  };
15870
16248
  const matchingReturns = [];
15871
16249
  walkInsideStatementBlocks(retainedFunction.body, (child) => {
@@ -18668,7 +19046,7 @@ const compileGlob = (pattern) => {
18668
19046
  };
18669
19047
  //#endregion
18670
19048
  //#region src/plugin/rules/react-builtins/forbid-component-props.ts
18671
- const resolveSettings$44 = (settings) => {
19049
+ const resolveSettings$43 = (settings) => {
18672
19050
  const reactDoctor = settings?.["react-doctor"];
18673
19051
  return { forbid: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidComponentProps ?? {} : {}).forbid ?? [] };
18674
19052
  };
@@ -18720,7 +19098,7 @@ const forbidComponentProps = defineRule({
18720
19098
  recommendation: "Configure blocked component props so callers cannot bypass the component API contract.",
18721
19099
  category: "Architecture",
18722
19100
  create: (context) => {
18723
- const entries = resolveSettings$44(context.settings).forbid.map(normalizeEntry);
19101
+ const entries = resolveSettings$43(context.settings).forbid.map(normalizeEntry);
18724
19102
  return { JSXOpeningElement(node) {
18725
19103
  if (entries.length === 0) return;
18726
19104
  if (!isSupportedJsxName(node.name)) return;
@@ -18749,7 +19127,7 @@ const forbidComponentProps = defineRule({
18749
19127
  //#endregion
18750
19128
  //#region src/plugin/rules/react-builtins/forbid-dom-props.ts
18751
19129
  const buildMessage$24 = (propName, customMessage) => customMessage ?? `Your project blocks the \`${propName}\` prop on plain HTML tags, so this bypasses the agreed DOM API contract.`;
18752
- const resolveSettings$43 = (settings) => {
19130
+ const resolveSettings$42 = (settings) => {
18753
19131
  const reactDoctor = settings?.["react-doctor"];
18754
19132
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidDomProps ?? {} : {};
18755
19133
  const map = /* @__PURE__ */ new Map();
@@ -18771,7 +19149,7 @@ const forbidDomProps = defineRule({
18771
19149
  recommendation: "Configure blocked DOM props so plain HTML tags stay on the agreed DOM API surface.",
18772
19150
  category: "Architecture",
18773
19151
  create: (context) => {
18774
- const forbidMap = resolveSettings$43(context.settings);
19152
+ const forbidMap = resolveSettings$42(context.settings);
18775
19153
  return { JSXOpeningElement(node) {
18776
19154
  if (forbidMap.size === 0) return;
18777
19155
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
@@ -18810,11 +19188,12 @@ const forbidDomProps = defineRule({
18810
19188
  * carrying their own near-identical inlined implementation.
18811
19189
  */
18812
19190
  const flattenCalleeName = (callee) => {
18813
- if (isNodeOfType(callee, "Identifier")) return callee.name;
18814
- if (isNodeOfType(callee, "MemberExpression") && !callee.computed) {
18815
- const objectName = flattenCalleeName(callee.object);
19191
+ const unwrappedCallee = stripParenExpression(callee);
19192
+ if (isNodeOfType(unwrappedCallee, "Identifier")) return unwrappedCallee.name;
19193
+ if (isNodeOfType(unwrappedCallee, "MemberExpression") && !unwrappedCallee.computed) {
19194
+ const objectName = flattenCalleeName(unwrappedCallee.object);
18816
19195
  if (!objectName) return null;
18817
- if (isNodeOfType(callee.property, "Identifier")) return `${objectName}.${callee.property.name}`;
19196
+ if (isNodeOfType(unwrappedCallee.property, "Identifier")) return `${objectName}.${unwrappedCallee.property.name}`;
18818
19197
  }
18819
19198
  return null;
18820
19199
  };
@@ -18833,7 +19212,7 @@ const isReactFunctionCall = (node, expectedCall) => {
18833
19212
  //#endregion
18834
19213
  //#region src/plugin/rules/react-builtins/forbid-elements.ts
18835
19214
  const buildMessage$23 = (element, customHelp) => customHelp ? `Your project blocks \`<${element}>\` here. ${customHelp}` : `Your project blocks \`<${element}>\` here, so code stays on the approved UI surface.`;
18836
- const resolveSettings$42 = (settings) => {
19215
+ const resolveSettings$41 = (settings) => {
18837
19216
  const reactDoctor = settings?.["react-doctor"];
18838
19217
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidElements ?? {} : {};
18839
19218
  const map = /* @__PURE__ */ new Map();
@@ -18849,7 +19228,7 @@ const forbidElements = defineRule({
18849
19228
  recommendation: "Configure blocked elements so code stays on the approved UI primitives.",
18850
19229
  category: "Architecture",
18851
19230
  create: (context) => {
18852
- const forbidMap = resolveSettings$42(context.settings);
19231
+ const forbidMap = resolveSettings$41(context.settings);
18853
19232
  return {
18854
19233
  JSXOpeningElement(node) {
18855
19234
  if (forbidMap.size === 0) return;
@@ -18958,7 +19337,7 @@ const DEFAULT_HEADING_TAGS = [
18958
19337
  "h5",
18959
19338
  "h6"
18960
19339
  ];
18961
- const resolveSettings$41 = (settings) => {
19340
+ const resolveSettings$40 = (settings) => {
18962
19341
  const reactDoctor = settings?.["react-doctor"];
18963
19342
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.headingHasContent ?? {} : {};
18964
19343
  return { headingTags: new Set([...DEFAULT_HEADING_TAGS, ...ruleSettings.components ?? []]) };
@@ -18971,7 +19350,7 @@ const headingHasContent = defineRule({
18971
19350
  recommendation: "Put readable text in every heading.",
18972
19351
  category: "Accessibility",
18973
19352
  create: (context) => {
18974
- const settings = resolveSettings$41(context.settings);
19353
+ const settings = resolveSettings$40(context.settings);
18975
19354
  return { JSXOpeningElement(node) {
18976
19355
  const elementType = getElementType(node, context.settings);
18977
19356
  if (!settings.headingTags.has(elementType)) return;
@@ -18992,7 +19371,7 @@ const headingHasContent = defineRule({
18992
19371
  //#region src/plugin/rules/react-builtins/hook-use-state.ts
18993
19372
  const REQUIRE_DESTRUCTURE_MESSAGE = "`useState` should be destructured as `[value, setValue]` so readers can see the state value and setter together.";
18994
19373
  const NAMING_CONVENTION_MESSAGE = "This `useState` setter does not match its value name, so updates are harder to trace.";
18995
- const resolveSettings$40 = (settings) => {
19374
+ const resolveSettings$39 = (settings) => {
18996
19375
  const reactDoctor = settings?.["react-doctor"];
18997
19376
  return { allowDestructuredState: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.hookUseState ?? {} : {}).allowDestructuredState ?? false };
18998
19377
  };
@@ -19019,7 +19398,7 @@ const hookUseState = defineRule({
19019
19398
  recommendation: "Destructure `useState` as `const [thing, setThing] = useState(…)` so state reads and writes stay visible together.",
19020
19399
  category: "Architecture",
19021
19400
  create: (context) => {
19022
- const { allowDestructuredState } = resolveSettings$40(context.settings);
19401
+ const { allowDestructuredState } = resolveSettings$39(context.settings);
19023
19402
  return { CallExpression(node) {
19024
19403
  const callExpressionNode = node;
19025
19404
  if (!isReactFunctionCall(callExpressionNode, "useState")) return;
@@ -19152,7 +19531,7 @@ const hooksNoNanInDeps = defineRule({
19152
19531
  //#endregion
19153
19532
  //#region src/plugin/rules/a11y/html-has-lang.ts
19154
19533
  const MESSAGE$52 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
19155
- const resolveSettings$39 = (settings) => {
19534
+ const resolveSettings$38 = (settings) => {
19156
19535
  const reactDoctor = settings?.["react-doctor"];
19157
19536
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
19158
19537
  };
@@ -19189,7 +19568,7 @@ const htmlHasLang = defineRule({
19189
19568
  recommendation: "Set `<html lang=\"…\">` so screen readers know the page language.",
19190
19569
  category: "Accessibility",
19191
19570
  create: (context) => {
19192
- const settings = resolveSettings$39(context.settings);
19571
+ const settings = resolveSettings$38(context.settings);
19193
19572
  const tagSet = new Set(settings.htmlTags);
19194
19573
  return { JSXOpeningElement(node) {
19195
19574
  const tag = getElementType(node, context.settings);
@@ -19705,7 +20084,7 @@ const DEFAULT_REDUNDANT_WORDS = [
19705
20084
  "photo",
19706
20085
  "picture"
19707
20086
  ];
19708
- const resolveSettings$38 = (settings) => {
20087
+ const resolveSettings$37 = (settings) => {
19709
20088
  const reactDoctor = settings?.["react-doctor"];
19710
20089
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.imgRedundantAlt ?? {} : {};
19711
20090
  return {
@@ -19753,7 +20132,7 @@ const imgRedundantAlt = defineRule({
19753
20132
  category: "Accessibility",
19754
20133
  create: (context) => {
19755
20134
  if (isGeneratedImageRenderContext(context)) return {};
19756
- const settings = resolveSettings$38(context.settings);
20135
+ const settings = resolveSettings$37(context.settings);
19757
20136
  return { JSXOpeningElement(node) {
19758
20137
  const tag = getElementType(node, context.settings);
19759
20138
  if (!settings.components.includes(tag)) return;
@@ -20061,7 +20440,7 @@ const COMPOSITE_ITEM_ROLES = new Set([
20061
20440
  "row",
20062
20441
  "gridcell"
20063
20442
  ]);
20064
- const resolveSettings$37 = (settings) => {
20443
+ const resolveSettings$36 = (settings) => {
20065
20444
  const reactDoctor = settings?.["react-doctor"];
20066
20445
  return { tabbable: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.interactiveSupportsFocus ?? {} : {}).tabbable ?? DEFAULT_TABBABLE_ROLES };
20067
20446
  };
@@ -20073,7 +20452,7 @@ const interactiveSupportsFocus = defineRule({
20073
20452
  recommendation: "Add keyboard focus support so users can reach interactive elements without a pointer.",
20074
20453
  category: "Accessibility",
20075
20454
  create: (context) => {
20076
- const settings = resolveSettings$37(context.settings);
20455
+ const settings = resolveSettings$36(context.settings);
20077
20456
  const tabbableSet = new Set(settings.tabbable);
20078
20457
  return { JSXOpeningElement(node) {
20079
20458
  if (isLocalTestScaffoldJsx(node, context)) return;
@@ -22214,25 +22593,49 @@ const jsLengthCheckFirst = defineRule({
22214
22593
  });
22215
22594
  //#endregion
22216
22595
  //#region src/plugin/utils/is-proven-global-namespace-reference.ts
22596
+ const GLOBAL_SELF_PROPERTY_NAMES = new Set([
22597
+ "global",
22598
+ "globalThis",
22599
+ "self",
22600
+ "window"
22601
+ ]);
22217
22602
  const isProvenGlobalObjectReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
22218
- const strippedExpression = stripParenExpression(expression);
22219
- if (!isNodeOfType(strippedExpression, "Identifier")) return false;
22220
- if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
22221
- const symbol = scopes.symbolFor(strippedExpression);
22222
- if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
22223
- visitedSymbolIds.add(symbol.id);
22224
- return isProvenGlobalObjectReference(symbol.initializer, scopes, visitedSymbolIds);
22603
+ let currentExpression = expression;
22604
+ while (true) {
22605
+ const strippedExpression = stripParenExpression(currentExpression);
22606
+ if (!isNodeOfType(strippedExpression, "Identifier")) return false;
22607
+ if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
22608
+ const symbol = scopes.symbolFor(strippedExpression);
22609
+ if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
22610
+ const declaration = symbol.declarationNode;
22611
+ const initializer = declaration.init;
22612
+ if (!initializer) return false;
22613
+ const isDirectIdentifierAlias = isNodeOfType(declaration.id, "Identifier") && declaration.id === symbol.bindingIdentifier && initializer === symbol.initializer;
22614
+ const bindingProperty = symbol.bindingIdentifier.parent;
22615
+ const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
22616
+ const isDirectGlobalSelfAlias = isNodeOfType(declaration.id, "ObjectPattern") && isNodeOfType(bindingProperty, "Property") && bindingProperty.value === symbol.bindingIdentifier && bindingProperty.parent === declaration.id && destructuredPropertyName !== null && GLOBAL_SELF_PROPERTY_NAMES.has(destructuredPropertyName);
22617
+ if (!isDirectIdentifierAlias && !isDirectGlobalSelfAlias) return false;
22618
+ visitedSymbolIds.add(symbol.id);
22619
+ currentExpression = initializer;
22620
+ }
22225
22621
  };
22226
22622
  const isProvenGlobalNamespaceReference = (expression, namespaceName, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
22227
- const strippedExpression = stripParenExpression(expression);
22228
- if (isNodeOfType(strippedExpression, "Identifier")) {
22623
+ let currentExpression = expression;
22624
+ while (true) {
22625
+ const strippedExpression = stripParenExpression(currentExpression);
22626
+ if (!isNodeOfType(strippedExpression, "Identifier")) return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
22229
22627
  if (strippedExpression.name === namespaceName && scopes.isGlobalReference(strippedExpression)) return true;
22230
22628
  const symbol = scopes.symbolFor(strippedExpression);
22231
- if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
22629
+ if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
22232
22630
  visitedSymbolIds.add(symbol.id);
22233
- return isProvenGlobalNamespaceReference(symbol.initializer, namespaceName, scopes, visitedSymbolIds);
22631
+ const declaration = symbol.declarationNode;
22632
+ const bindingIdentifier = symbol.bindingIdentifier;
22633
+ const bindingProperty = bindingIdentifier.parent;
22634
+ if (getDestructuredBindingPropertyName(bindingIdentifier) === namespaceName && isNodeOfType(declaration, "VariableDeclarator") && isNodeOfType(declaration.id, "ObjectPattern") && isNodeOfType(bindingProperty, "Property") && bindingProperty.value === bindingIdentifier && bindingProperty.parent === declaration.id && declaration.init) return isProvenGlobalObjectReference(declaration.init, scopes);
22635
+ const directInitializer = getDirectConstInitializer(symbol);
22636
+ if (!directInitializer) return false;
22637
+ currentExpression = directInitializer;
22234
22638
  }
22235
- return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
22236
22639
  };
22237
22640
  //#endregion
22238
22641
  //#region src/plugin/rules/js-performance/js-min-max-loop.ts
@@ -23345,7 +23748,7 @@ const jsTosortedImmutable = defineRule({
23345
23748
  const NEVER_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
23346
23749
  const ALWAYS_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
23347
23750
  const FALSE_OMITTED_MESSAGE = (attributeName) => `\`${attributeName}={false}\` does nothing, so the explicit false value adds noise without changing output.`;
23348
- const resolveSettings$36 = (settings) => {
23751
+ const resolveSettings$35 = (settings) => {
23349
23752
  const reactDoctor = settings?.["react-doctor"];
23350
23753
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxBooleanValue ?? {} : {};
23351
23754
  return {
@@ -23363,7 +23766,7 @@ const jsxBooleanValue = defineRule({
23363
23766
  recommendation: "Use one boolean-attribute style so equivalent true props scan the same across the codebase.",
23364
23767
  category: "Architecture",
23365
23768
  create: (context) => {
23366
- const settings = resolveSettings$36(context.settings);
23769
+ const settings = resolveSettings$35(context.settings);
23367
23770
  const alwaysSet = new Set(settings.always);
23368
23771
  const neverSet = new Set(settings.never);
23369
23772
  return { JSXAttribute(node) {
@@ -23417,7 +23820,7 @@ const jsxBooleanValue = defineRule({
23417
23820
  const UNNECESSARY_BRACES_MESSAGE = "These curly braces wrap a literal value, so they add JSX noise without changing output.";
23418
23821
  const REQUIRED_BRACES_MESSAGE = "This JSX value needs `{ }` so React reads it as an expression instead of text.";
23419
23822
  const isAllowedMode = (value) => value === "always" || value === "never" || value === "ignore";
23420
- const resolveSettings$35 = (settings) => {
23823
+ const resolveSettings$34 = (settings) => {
23421
23824
  const reactDoctor = settings?.["react-doctor"];
23422
23825
  const ruleSettingsRaw = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxCurlyBracePresence : void 0;
23423
23826
  if (isAllowedMode(ruleSettingsRaw)) return {
@@ -23513,7 +23916,7 @@ const jsxCurlyBracePresence = defineRule({
23513
23916
  recommendation: "Use one JSX literal style so equivalent markup scans the same without extra JSX noise.",
23514
23917
  category: "Architecture",
23515
23918
  create: (context) => {
23516
- const settings = resolveSettings$35(context.settings);
23919
+ const settings = resolveSettings$34(context.settings);
23517
23920
  return {
23518
23921
  JSXAttribute(node) {
23519
23922
  const value = node.value;
@@ -23596,7 +23999,7 @@ const containsJsxElement = (root) => {
23596
23999
  //#region src/plugin/rules/react-builtins/jsx-filename-extension.ts
23597
24000
  const JSX_NOT_ALLOWED = (extension) => `This file contains JSX but uses a \`${extension}\` name, so the filename no longer signals JSX to readers or tooling conventions.`;
23598
24001
  const EXTENSION_ONLY_FOR_JSX = (extension) => `\`${extension}\` files are reserved for JSX here, so using that extension without JSX makes file-type conventions less useful.`;
23599
- const resolveSettings$34 = (settings) => {
24002
+ const resolveSettings$33 = (settings) => {
23600
24003
  const reactDoctor = settings?.["react-doctor"];
23601
24004
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFilenameExtension ?? {} : {};
23602
24005
  return {
@@ -23618,7 +24021,7 @@ const jsxFilenameExtension = defineRule({
23618
24021
  recommendation: "Name JSX files with the configured extension so file names accurately signal which files contain JSX.",
23619
24022
  category: "Architecture",
23620
24023
  create: (context) => {
23621
- const settings = resolveSettings$34(context.settings);
24024
+ const settings = resolveSettings$33(context.settings);
23622
24025
  const allowedExtensions = normalizeExtensions(settings.extensions);
23623
24026
  const filename = normalizeFilename(context.filename ?? "fixture.tsx");
23624
24027
  const extensionOnly = path.extname(filename).slice(1);
@@ -23659,7 +24062,7 @@ const jsxFilenameExtension = defineRule({
23659
24062
  //#region src/plugin/rules/react-builtins/jsx-fragments.ts
23660
24063
  const SYNTAX_MESSAGE = "`<React.Fragment>` is used where shorthand fragments are configured, so similar wrappers look different across the codebase.";
23661
24064
  const ELEMENT_MESSAGE = "Fragment shorthand is used where explicit fragments are configured, so similar wrappers look different across the codebase.";
23662
- const resolveSettings$33 = (settings) => {
24065
+ const resolveSettings$32 = (settings) => {
23663
24066
  const reactDoctor = settings?.["react-doctor"];
23664
24067
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFragments ?? {} : {}).mode ?? "syntax" };
23665
24068
  };
@@ -23671,7 +24074,7 @@ const jsxFragments = defineRule({
23671
24074
  recommendation: "Use one fragment style so identical wrappers do not look different across files.",
23672
24075
  category: "Architecture",
23673
24076
  create: (context) => {
23674
- const { mode } = resolveSettings$33(context.settings);
24077
+ const { mode } = resolveSettings$32(context.settings);
23675
24078
  return {
23676
24079
  JSXElement(node) {
23677
24080
  if (mode !== "syntax") return;
@@ -23715,7 +24118,7 @@ const buildPropRegex = (handlerPropPrefix) => {
23715
24118
  const escaped = tokens.map((token) => token.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("|");
23716
24119
  return new RegExp(`^(${escaped})[A-Z].*$`);
23717
24120
  };
23718
- const resolveSettings$32 = (settings) => {
24121
+ const resolveSettings$31 = (settings) => {
23719
24122
  const reactDoctor = settings?.["react-doctor"];
23720
24123
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxHandlerNames ?? {} : {};
23721
24124
  let handlerPrefix = DEFAULT_HANDLER_PREFIX;
@@ -23791,7 +24194,7 @@ const jsxHandlerNames = defineRule({
23791
24194
  recommendation: "Use the `on…` prefix for event-handler props and `handle…` for handlers so readers can trace event flow.",
23792
24195
  category: "Architecture",
23793
24196
  create: (context) => {
23794
- const settings = resolveSettings$32(context.settings);
24197
+ const settings = resolveSettings$31(context.settings);
23795
24198
  return { JSXAttribute(node) {
23796
24199
  if (settings.ignoreComponentNames.length > 0) {
23797
24200
  const opening = node.parent;
@@ -23866,6 +24269,19 @@ const hasJsxKeyAttribute = (openingElement) => {
23866
24269
  return false;
23867
24270
  };
23868
24271
  //#endregion
24272
+ //#region src/plugin/utils/is-component-function.ts
24273
+ const isFunctionAssignedToComponent = (functionNode) => {
24274
+ let cursor = functionNode.parent ?? null;
24275
+ while (isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
24276
+ if (isNodeOfType(cursor, "VariableDeclarator") && isNodeOfType(cursor.id, "Identifier") && isUppercaseName(cursor.id.name)) return true;
24277
+ return isNodeOfType(cursor, "ExportDefaultDeclaration");
24278
+ };
24279
+ const isComponentFunction$1 = (node) => {
24280
+ if (!isFunctionLike$1(node)) return false;
24281
+ if (isNodeOfType(node, "FunctionDeclaration")) return !node.id || node.id.name === "default" || isUppercaseName(node.id.name) || isNodeOfType(node.parent, "ExportDefaultDeclaration");
24282
+ return isFunctionAssignedToComponent(node);
24283
+ };
24284
+ //#endregion
23869
24285
  //#region src/plugin/utils/is-const-declared-binding.ts
23870
24286
  const BINDING_PATTERN_ANCESTOR_TYPES = new Set([
23871
24287
  "VariableDeclarator",
@@ -23910,11 +24326,17 @@ const ITERATOR_METHOD_NAMES$1 = new Set([
23910
24326
  "flatMap",
23911
24327
  "from"
23912
24328
  ]);
24329
+ const RENDERING_CALL_NAMES = new Set([
24330
+ "createPortal",
24331
+ "hydrate",
24332
+ "hydrateRoot",
24333
+ "render"
24334
+ ]);
23913
24335
  const MISSING_KEY_ARRAY = "Your users can see the wrong data when this array reorders.";
23914
24336
  const MISSING_KEY_ITERATOR = "Your users can see the wrong data when this list reorders.";
23915
- const KEY_AFTER_SPREAD = "Place this `key` before the `{...spread}` so the JSX transform can always extract it.";
24337
+ const KEY_BEFORE_SPREAD = "Place this `key` after the `{...spread}` so the spread cannot override it.";
23916
24338
  const DUPLICATE_KEY = (keyValue) => `Your users can see the wrong data because two elements share the key "${keyValue}".`;
23917
- const resolveSettings$31 = (settings) => {
24339
+ const resolveSettings$30 = (settings) => {
23918
24340
  const reactDoctor = settings?.["react-doctor"];
23919
24341
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxKey ?? {} : {};
23920
24342
  return {
@@ -23928,6 +24350,33 @@ const findArrayVariableDeclarator = (arrayExpression) => {
23928
24350
  if (ancestor && isNodeOfType(ancestor, "VariableDeclarator") && ancestor.init === wrapped && isNodeOfType(ancestor.id, "Identifier")) return ancestor;
23929
24351
  return null;
23930
24352
  };
24353
+ const isArrayNestedInObjectProperty = (arrayExpression) => {
24354
+ let current = findTransparentExpressionRoot(arrayExpression);
24355
+ while (current.parent) {
24356
+ const parent = current.parent;
24357
+ if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current) || isNodeOfType(parent, "LogicalExpression") && (parent.left === current || parent.right === current)) {
24358
+ current = findTransparentExpressionRoot(parent);
24359
+ continue;
24360
+ }
24361
+ return isNodeOfType(parent, "Property") && parent.value === current;
24362
+ }
24363
+ return false;
24364
+ };
24365
+ const isArrayPassedToNonRenderingCall = (arrayExpression, scopes) => {
24366
+ const transparentArray = findTransparentExpressionRoot(arrayExpression);
24367
+ const callExpression = transparentArray.parent;
24368
+ if (!callExpression || !isNodeOfType(callExpression, "CallExpression") || !callExpression.arguments.some((argument) => argument === transparentArray)) return false;
24369
+ const argumentIndex = callExpression.arguments.findIndex((argument) => argument === transparentArray);
24370
+ if (isReactApiCall(callExpression, "createElement", scopes, {
24371
+ allowGlobalReactNamespace: true,
24372
+ allowUnboundBareCalls: true,
24373
+ resolveNamedAliases: true
24374
+ })) return argumentIndex < 2;
24375
+ const callee = stripParenExpression(callExpression.callee);
24376
+ if (isNodeOfType(callee, "Identifier")) return !RENDERING_CALL_NAMES.has(callee.name);
24377
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return !RENDERING_CALL_NAMES.has(callee.property.name);
24378
+ return true;
24379
+ };
23931
24380
  const isIdentityIteratorCallback = (callback) => {
23932
24381
  const callbackFunction = stripParenExpression(callback);
23933
24382
  if (!isNodeOfType(callbackFunction, "ArrowFunctionExpression") && !isNodeOfType(callbackFunction, "FunctionExpression")) return true;
@@ -24024,7 +24473,7 @@ const findNamedCallbackIteratorCall = (functionNode) => {
24024
24473
  namedCallbackIteratorCallCache.set(functionNode, iteratorCall);
24025
24474
  return iteratorCall;
24026
24475
  };
24027
- const findEnclosingIteratorContext = (jsxNode) => {
24476
+ const findEnclosingIteratorContext = (jsxNode, scopes) => {
24028
24477
  let current = jsxNode;
24029
24478
  let isOutsideContainingFunction = false;
24030
24479
  let didSeeReturnStatement = false;
@@ -24046,6 +24495,8 @@ const findEnclosingIteratorContext = (jsxNode) => {
24046
24495
  isOutsideContainingFunction = true;
24047
24496
  } else if (isNodeOfType(parent, "ArrayExpression")) {
24048
24497
  if (isOutsideContainingFunction) return null;
24498
+ if (isArrayNestedInObjectProperty(parent)) return null;
24499
+ if (isArrayPassedToNonRenderingCall(parent, scopes)) return null;
24049
24500
  const arrayParent = parent.parent;
24050
24501
  if (arrayParent && isNodeOfType(arrayParent, "Property")) return null;
24051
24502
  if (arrayParent && isNodeOfType(arrayParent, "ArrayExpression")) return null;
@@ -24127,9 +24578,15 @@ const isWithinChildrenToArray = (jsxNode) => {
24127
24578
  const isKeyPropertyName = (propertyKey) => {
24128
24579
  if (isNodeOfType(propertyKey, "Identifier")) return propertyKey.name === "key";
24129
24580
  if (isNodeOfType(propertyKey, "Literal")) return String(propertyKey.value) === "key";
24130
- return true;
24581
+ return false;
24131
24582
  };
24132
24583
  const isThisPropsMember = (expression) => isNodeOfType(expression, "MemberExpression") && !expression.computed && isNodeOfType(expression.object, "ThisExpression") && isNodeOfType(expression.property, "Identifier") && expression.property.name === "props";
24584
+ const isDirectComponentPropsParameter = (bindingIdentifier, functionNode) => {
24585
+ if (!isFunctionLike$1(functionNode) || !isComponentFunction$1(functionNode)) return false;
24586
+ const firstParameter = functionNode.params?.[0];
24587
+ if (!firstParameter) return false;
24588
+ return (isNodeOfType(firstParameter, "AssignmentPattern") ? firstParameter.left : firstParameter) === bindingIdentifier;
24589
+ };
24133
24590
  const isFunctionParameterPropsRest = (bindingIdentifier) => {
24134
24591
  const restElement = bindingIdentifier.parent;
24135
24592
  if (!restElement || !isNodeOfType(restElement, "RestElement")) return false;
@@ -24139,7 +24596,7 @@ const isFunctionParameterPropsRest = (bindingIdentifier) => {
24139
24596
  const parameterNode = patternParent && isNodeOfType(patternParent, "AssignmentPattern") && patternParent.left === objectPattern ? patternParent : objectPattern;
24140
24597
  const functionNode = parameterNode.parent;
24141
24598
  if (!functionNode || !isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "FunctionDeclaration")) return false;
24142
- return functionNode.params.some((param) => param === parameterNode);
24599
+ return isComponentFunction$1(functionNode) && functionNode.params[0] === parameterNode;
24143
24600
  };
24144
24601
  const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
24145
24602
  const restElement = bindingIdentifier.parent;
@@ -24152,14 +24609,14 @@ const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
24152
24609
  }
24153
24610
  return false;
24154
24611
  };
24155
- const isBindingObjectMutated = (scopeOwner, bindingName) => {
24156
- let didFindMutation = false;
24612
+ const isBindingAssignedKey = (scopeOwner, bindingName) => {
24613
+ let didFindKeyAssignment = false;
24157
24614
  walkAst(scopeOwner, (node) => {
24158
- if (didFindMutation) return false;
24615
+ if (didFindKeyAssignment) return false;
24159
24616
  if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression")) {
24160
24617
  const assignedObject = stripParenExpression(node.left.object);
24161
- if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName) {
24162
- didFindMutation = true;
24618
+ if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName && isKeyPropertyName(node.left.property)) {
24619
+ didFindKeyAssignment = true;
24163
24620
  return false;
24164
24621
  }
24165
24622
  }
@@ -24167,59 +24624,63 @@ const isBindingObjectMutated = (scopeOwner, bindingName) => {
24167
24624
  const callee = node.callee;
24168
24625
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Object" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "assign") {
24169
24626
  const firstArgument = node.arguments[0];
24170
- if (firstArgument && isNodeOfType(firstArgument, "Identifier") && firstArgument.name === bindingName) {
24171
- didFindMutation = true;
24172
- return false;
24627
+ if (firstArgument && isNodeOfType(firstArgument, "Identifier") && firstArgument.name === bindingName) for (const sourceArgument of node.arguments.slice(1)) {
24628
+ const source = stripParenExpression(sourceArgument);
24629
+ if (!isNodeOfType(source, "ObjectExpression")) continue;
24630
+ if (source.properties.some((property) => isNodeOfType(property, "Property") && isKeyPropertyName(property.key))) {
24631
+ didFindKeyAssignment = true;
24632
+ return false;
24633
+ }
24173
24634
  }
24174
24635
  }
24175
24636
  }
24176
24637
  });
24177
- return didFindMutation;
24638
+ return didFindKeyAssignment;
24178
24639
  };
24179
- const spreadExpressionCanCarryKey = (expression, depth) => {
24640
+ const spreadExpressionHasKey = (expression, depth) => {
24180
24641
  const inner = stripParenExpression(expression);
24181
24642
  if (isNodeOfType(inner, "ObjectExpression")) {
24182
24643
  for (const property of inner.properties) {
24183
24644
  if (isNodeOfType(property, "SpreadElement")) {
24184
- if (depth >= 3) return true;
24185
- if (spreadExpressionCanCarryKey(property.argument, depth + 1)) return true;
24645
+ if (depth >= 3) continue;
24646
+ if (spreadExpressionHasKey(property.argument, depth + 1)) return true;
24186
24647
  continue;
24187
24648
  }
24188
- if (!isNodeOfType(property, "Property")) return true;
24189
- if (property.computed) return true;
24649
+ if (!isNodeOfType(property, "Property")) continue;
24190
24650
  if (isKeyPropertyName(property.key)) return true;
24191
24651
  }
24192
24652
  return false;
24193
24653
  }
24194
24654
  if (isNodeOfType(inner, "ConditionalExpression")) {
24195
- if (depth >= 3) return true;
24196
- return spreadExpressionCanCarryKey(inner.consequent, depth + 1) || spreadExpressionCanCarryKey(inner.alternate, depth + 1);
24655
+ if (depth >= 3) return false;
24656
+ return spreadExpressionHasKey(inner.consequent, depth + 1) || spreadExpressionHasKey(inner.alternate, depth + 1);
24197
24657
  }
24198
24658
  if (isNodeOfType(inner, "LogicalExpression")) {
24199
- if (depth >= 3) return true;
24200
- if (inner.operator === "&&") return spreadExpressionCanCarryKey(inner.right, depth + 1);
24201
- return spreadExpressionCanCarryKey(inner.left, depth + 1) || spreadExpressionCanCarryKey(inner.right, depth + 1);
24659
+ if (depth >= 3) return false;
24660
+ if (inner.operator === "&&") return spreadExpressionHasKey(inner.right, depth + 1);
24661
+ return spreadExpressionHasKey(inner.left, depth + 1) || spreadExpressionHasKey(inner.right, depth + 1);
24202
24662
  }
24203
24663
  if (isNodeOfType(inner, "Literal")) return false;
24204
24664
  if (isThisPropsMember(inner)) return false;
24205
24665
  if (isNodeOfType(inner, "Identifier")) {
24206
24666
  if (inner.name === "undefined") return false;
24207
- if (depth >= 3) return true;
24667
+ if (depth >= 3) return false;
24208
24668
  const binding = findVariableInitializer(inner, inner.name);
24209
- if (!binding) return true;
24669
+ if (!binding) return false;
24210
24670
  if (isRestBindingWithKeyExtracted(binding.bindingIdentifier)) return false;
24211
24671
  if (isFunctionParameterPropsRest(binding.bindingIdentifier)) return false;
24212
- if (!isConstDeclaredBinding(binding) || !binding.initializer) return true;
24213
- if (isBindingObjectMutated(binding.scopeOwner, inner.name)) return true;
24214
- return spreadExpressionCanCarryKey(binding.initializer, depth + 1);
24672
+ if (isDirectComponentPropsParameter(binding.bindingIdentifier, binding.scopeOwner)) return false;
24673
+ if (!isConstDeclaredBinding(binding) || !binding.initializer) return false;
24674
+ if (isBindingAssignedKey(binding.scopeOwner, inner.name)) return true;
24675
+ return spreadExpressionHasKey(binding.initializer, depth + 1);
24215
24676
  }
24216
- return true;
24677
+ return false;
24217
24678
  };
24218
- const spreadCanOverwriteKey = (spreadAttribute) => spreadExpressionCanCarryKey(spreadAttribute.argument, 0);
24679
+ const spreadCanOverwriteKey = (spreadAttribute) => spreadExpressionHasKey(spreadAttribute.argument, 0);
24219
24680
  const checkKeyBeforeSpread = (context, openingElement) => {
24220
24681
  let keyIndex = null;
24221
24682
  let keyAttribute = null;
24222
- let firstKeyCarryingSpreadIndex = null;
24683
+ let lastKeyCarryingSpreadIndex = null;
24223
24684
  for (let attributeIndex = 0; attributeIndex < openingElement.attributes.length; attributeIndex++) {
24224
24685
  const attribute = openingElement.attributes[attributeIndex];
24225
24686
  if (isNodeOfType(attribute, "JSXAttribute")) {
@@ -24227,11 +24688,11 @@ const checkKeyBeforeSpread = (context, openingElement) => {
24227
24688
  keyIndex = attributeIndex;
24228
24689
  keyAttribute = attribute;
24229
24690
  }
24230
- } else if (isNodeOfType(attribute, "JSXSpreadAttribute") && firstKeyCarryingSpreadIndex === null && spreadCanOverwriteKey(attribute)) firstKeyCarryingSpreadIndex = attributeIndex;
24691
+ } else if (isNodeOfType(attribute, "JSXSpreadAttribute") && spreadCanOverwriteKey(attribute)) lastKeyCarryingSpreadIndex = attributeIndex;
24231
24692
  }
24232
- if (keyIndex !== null && firstKeyCarryingSpreadIndex !== null && firstKeyCarryingSpreadIndex < keyIndex && keyAttribute) context.report({
24693
+ if (keyIndex !== null && lastKeyCarryingSpreadIndex !== null && lastKeyCarryingSpreadIndex > keyIndex && keyAttribute) context.report({
24233
24694
  node: keyAttribute,
24234
- message: KEY_AFTER_SPREAD
24695
+ message: KEY_BEFORE_SPREAD
24235
24696
  });
24236
24697
  };
24237
24698
  const getKeyAttributeValueString = (openingElement) => {
@@ -24275,7 +24736,7 @@ const jsxKey = defineRule({
24275
24736
  severity: "error",
24276
24737
  recommendation: "Add a stable `key` prop so React can keep list items matched to the right data when the list changes.",
24277
24738
  create: (context) => {
24278
- const settings = resolveSettings$31(context.settings);
24739
+ const settings = resolveSettings$30(context.settings);
24279
24740
  return {
24280
24741
  JSXElement(node) {
24281
24742
  const openingElement = node.openingElement;
@@ -24293,7 +24754,7 @@ const jsxKey = defineRule({
24293
24754
  else seenKeys.add(keyValue.keyValue);
24294
24755
  }
24295
24756
  }
24296
- const enclosingContext = findEnclosingIteratorContext(node);
24757
+ const enclosingContext = findEnclosingIteratorContext(node, context.scopes);
24297
24758
  if (!enclosingContext) return;
24298
24759
  if (isWithinChildrenToArray(node)) return;
24299
24760
  if (hasJsxKeyAttribute(openingElement)) return;
@@ -24329,7 +24790,7 @@ const jsxKey = defineRule({
24329
24790
  //#region src/plugin/rules/react-builtins/jsx-max-depth.ts
24330
24791
  const buildMessage$19 = (depth, max) => `This JSX is hard to read at ${depth} levels deep, past the limit of ${max}.`;
24331
24792
  const DEFAULT_MAX_DEPTH = 14;
24332
- const resolveSettings$30 = (settings) => {
24793
+ const resolveSettings$29 = (settings) => {
24333
24794
  const reactDoctor = settings?.["react-doctor"];
24334
24795
  return { max: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxMaxDepth ?? {} : {}).max ?? DEFAULT_MAX_DEPTH };
24335
24796
  };
@@ -24398,7 +24859,7 @@ const jsxMaxDepth = defineRule({
24398
24859
  recommendation: "Pull deeply nested JSX into smaller components so it's easier to read.",
24399
24860
  category: "Architecture",
24400
24861
  create: (context) => {
24401
- const { max } = resolveSettings$30(context.settings);
24862
+ const { max } = resolveSettings$29(context.settings);
24402
24863
  const deepestLeafPerTree = /* @__PURE__ */ new Map();
24403
24864
  const checkNode = (node) => {
24404
24865
  if (!isLeafJsxNode(node)) return;
@@ -24587,9 +25048,14 @@ const collectContextBindings = (programRoot) => {
24587
25048
  }
24588
25049
  return bindings;
24589
25050
  };
24590
- const isCreateContextBindingJsxName = (node, contextBindings) => {
25051
+ const isCreateContextBindingJsxName = (node, contextBindings, scopes) => {
24591
25052
  if (!isNodeOfType(node, "JSXIdentifier")) return false;
24592
- if (!contextBindings.has(node.name)) return false;
25053
+ if (!contextBindings.has(node.name)) {
25054
+ const symbol = scopes.symbolFor(node);
25055
+ if (!symbol || symbol.kind !== "import") return false;
25056
+ const importedName = getImportedName(symbol.declarationNode);
25057
+ return getImportSourceForName(node, node.name)?.toLowerCase().includes("context") === true && (node.name.endsWith("Context") || importedName?.endsWith("Context") === true);
25058
+ }
24593
25059
  const binding = findVariableInitializer(node, node.name);
24594
25060
  if (!binding) return false;
24595
25061
  return binding.scopeOwner.type === "Program";
@@ -24613,7 +25079,7 @@ const jsxNoConstructedContextValues = defineRule({
24613
25079
  if (isTestlikeFile) return;
24614
25080
  const nameNode = node.name;
24615
25081
  const isLegacyProvider = isProviderMemberName(nameNode);
24616
- const isReact19Shorthand = isCreateContextBindingJsxName(nameNode, contextBindings);
25082
+ const isReact19Shorthand = isCreateContextBindingJsxName(nameNode, contextBindings, context.scopes);
24617
25083
  if (!isLegacyProvider && !isReact19Shorthand) return;
24618
25084
  if (!isInsideFunctionScope(node)) return;
24619
25085
  for (const attribute of node.attributes) {
@@ -24884,6 +25350,29 @@ const buildSameFileJsxSlotPropRegistry = (program, memoRegistry, scopes) => {
24884
25350
  return registry;
24885
25351
  };
24886
25352
  //#endregion
25353
+ //#region src/plugin/utils/has-custom-memo-comparator.ts
25354
+ const MEMO_CALLEE_NAMES$1 = new Set(["memo", "React.memo"]);
25355
+ const isIdentitySensitiveMemoComparator = (comparatorNode, scopes) => {
25356
+ if (isNodeOfType(comparatorNode, "Identifier")) {
25357
+ if (comparatorNode.name === "undefined") return scopes.isGlobalReference(comparatorNode);
25358
+ return getImportedNameFromModule(comparatorNode, comparatorNode.name, "react-redux") === "shallowEqual";
25359
+ }
25360
+ if (!isNodeOfType(comparatorNode, "MemberExpression") || comparatorNode.computed || !isNodeOfType(comparatorNode.object, "Identifier") || !isNodeOfType(comparatorNode.property, "Identifier") || comparatorNode.property.name !== "shallowEqual") return false;
25361
+ return isNamespaceImportFromModule$1(comparatorNode, comparatorNode.object.name, "react-redux");
25362
+ };
25363
+ const hasCustomMemoComparator = (openingName, scopes) => {
25364
+ if (!openingName || !isNodeOfType(openingName, "JSXIdentifier")) return false;
25365
+ const binding = findVariableInitializer(openingName, openingName.name);
25366
+ if (!binding || !binding.initializer) return false;
25367
+ const initializer = binding.initializer;
25368
+ if (!isNodeOfType(initializer, "CallExpression")) return false;
25369
+ const calleeName = flattenCalleeName(initializer.callee);
25370
+ if (calleeName === null || !MEMO_CALLEE_NAMES$1.has(calleeName)) return false;
25371
+ const comparatorNode = (initializer.arguments ?? [])[1];
25372
+ if (!comparatorNode) return false;
25373
+ return !isIdentitySensitiveMemoComparator(comparatorNode, scopes);
25374
+ };
25375
+ //#endregion
24887
25376
  //#region src/plugin/utils/is-on-intrinsic-html-element.ts
24888
25377
  const isJsxAttributeOnIntrinsicHtmlElement = (attribute) => {
24889
25378
  const openingElement = attribute.parent;
@@ -25169,6 +25658,7 @@ const jsxNoJsxAsProp = defineRule({
25169
25658
  const parentJsxOpening = node.parent;
25170
25659
  const openingName = parentJsxOpening && isNodeOfType(parentJsxOpening, "JSXOpeningElement") ? parentJsxOpening.name : null;
25171
25660
  if (memoStatusForJsxOpeningName(memoRegistry, openingName) !== "memoised") return;
25661
+ if (hasCustomMemoComparator(openingName, context.scopes)) return;
25172
25662
  const openingSymbol = openingName && isNodeOfType(openingName, "JSXIdentifier") ? context.scopes.symbolFor(openingName) : null;
25173
25663
  if (openingSymbol && isNodeOfType(node.name, "JSXIdentifier") && jsxSlotPropRegistry?.get(openingSymbol.id)?.has(node.name.name)) return;
25174
25664
  if (isNodeOfType(node.name, "JSXIdentifier") && isSlotPropName(node.name.name)) return;
@@ -25188,19 +25678,6 @@ const jsxNoJsxAsProp = defineRule({
25188
25678
  }
25189
25679
  });
25190
25680
  //#endregion
25191
- //#region src/plugin/utils/has-custom-memo-comparator.ts
25192
- const MEMO_CALLEE_NAMES = new Set(["memo", "React.memo"]);
25193
- const hasCustomMemoComparator = (openingName) => {
25194
- if (!openingName || !isNodeOfType(openingName, "JSXIdentifier")) return false;
25195
- const binding = findVariableInitializer(openingName, openingName.name);
25196
- if (!binding || !binding.initializer) return false;
25197
- const initializer = binding.initializer;
25198
- if (!isNodeOfType(initializer, "CallExpression")) return false;
25199
- const calleeName = flattenCalleeName(initializer.callee);
25200
- if (calleeName === null || !MEMO_CALLEE_NAMES.has(calleeName)) return false;
25201
- return (initializer.arguments ?? []).length >= 2;
25202
- };
25203
- //#endregion
25204
25681
  //#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop-tables.ts
25205
25682
  const DATA_ARRAY_PROP_NAMES = new Set([
25206
25683
  "data",
@@ -25559,7 +26036,7 @@ const jsxNoNewArrayAsProp = defineRule({
25559
26036
  const parentJsxOpening = node.parent;
25560
26037
  const openingName = parentJsxOpening && isNodeOfType(parentJsxOpening, "JSXOpeningElement") ? parentJsxOpening.name : null;
25561
26038
  if (memoStatusForJsxOpeningName(memoRegistry, openingName) !== "memoised") return;
25562
- if (hasCustomMemoComparator(openingName)) return;
26039
+ if (hasCustomMemoComparator(openingName, context.scopes)) return;
25563
26040
  if (isNodeOfType(node.name, "JSXIdentifier") && isDataArrayPropName(node.name.name)) return;
25564
26041
  if (!isInsideFunctionScope(node)) return;
25565
26042
  const value = node.value;
@@ -26023,6 +26500,7 @@ const jsxNoNewFunctionAsProp = defineRule({
26023
26500
  const parentJsxOpening = node.parent;
26024
26501
  const openingName = parentJsxOpening && isNodeOfType(parentJsxOpening, "JSXOpeningElement") ? parentJsxOpening.name : null;
26025
26502
  if (memoStatusForJsxOpeningName(memoRegistry, openingName) !== "memoised") return;
26503
+ if (hasCustomMemoComparator(openingName, context.scopes)) return;
26026
26504
  if (isNodeOfType(node.name, "JSXIdentifier") && isOneShotHandlerName(node.name.name)) return;
26027
26505
  if (!isInsideFunctionScope(node)) return;
26028
26506
  const value = node.value;
@@ -26330,7 +26808,7 @@ const jsxNoNewObjectAsProp = defineRule({
26330
26808
  const parentJsxOpening = node.parent;
26331
26809
  const openingName = parentJsxOpening && isNodeOfType(parentJsxOpening, "JSXOpeningElement") ? parentJsxOpening.name : null;
26332
26810
  if (memoStatusForJsxOpeningName(memoRegistry, openingName) !== "memoised") return;
26333
- if (hasCustomMemoComparator(openingName)) return;
26811
+ if (hasCustomMemoComparator(openingName, context.scopes)) return;
26334
26812
  if (!isInsideFunctionScope(node)) return;
26335
26813
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
26336
26814
  if (ALWAYS_FRESH_OBJECT_PROPS.has(node.name.name)) return;
@@ -26353,7 +26831,7 @@ const jsxNoNewObjectAsProp = defineRule({
26353
26831
  //#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
26354
26832
  const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
26355
26833
  const JAVASCRIPT_URL_PATTERN = /^[\u0000-\u001F ]*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;
26356
- const resolveSettings$29 = (settings) => {
26834
+ const resolveSettings$28 = (settings) => {
26357
26835
  const reactDoctor = settings?.["react-doctor"];
26358
26836
  if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
26359
26837
  return reactDoctor.jsxNoScriptUrl ?? {};
@@ -26375,7 +26853,7 @@ const jsxNoScriptUrl = defineRule({
26375
26853
  recommendation: "Use real event handlers instead of `javascript:` URLs so injected URL text cannot execute as code.",
26376
26854
  category: "Security",
26377
26855
  create: (context) => {
26378
- const options = resolveSettings$29(context.settings);
26856
+ const options = resolveSettings$28(context.settings);
26379
26857
  return { JSXOpeningElement(node) {
26380
26858
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
26381
26859
  const elementName = resolveJsxElementType(node);
@@ -26396,283 +26874,6 @@ const jsxNoScriptUrl = defineRule({
26396
26874
  }
26397
26875
  });
26398
26876
  //#endregion
26399
- //#region src/plugin/rules/react-builtins/jsx-no-target-blank.ts
26400
- const NOREFERRER_MESSAGE = "`target=\"_blank\"` without `rel=\"noreferrer\"` lets the linked page hijack your tab to a phishing site.";
26401
- const NOOPENER_MESSAGE = "`target=\"_blank\"` without `rel` lets the linked page hijack your tab to a phishing site.";
26402
- const SPREAD_MESSAGE = "A spread here can add `target=\"_blank\"`, letting the linked page hijack your tab to a phishing site.";
26403
- const resolveSettings$28 = (settings) => {
26404
- const reactDoctor = settings?.["react-doctor"];
26405
- const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxNoTargetBlank ?? {} : {};
26406
- const reactSettings = typeof settings?.react === "object" && settings.react !== null ? settings.react : {};
26407
- const linkComponents = /* @__PURE__ */ new Map();
26408
- for (const entry of reactSettings.linkComponents ?? []) if (typeof entry === "string") linkComponents.set(entry, ["href"]);
26409
- else if (typeof entry === "object" && entry !== null) {
26410
- const linkAttribute = entry.linkAttribute ?? "href";
26411
- linkComponents.set(entry.name, Array.isArray(linkAttribute) ? linkAttribute : [linkAttribute]);
26412
- }
26413
- const formComponents = /* @__PURE__ */ new Map();
26414
- for (const entry of reactSettings.formComponents ?? []) if (typeof entry === "string") formComponents.set(entry, ["action"]);
26415
- else if (typeof entry === "object" && entry !== null) {
26416
- const formAttribute = entry.formAttribute ?? "action";
26417
- formComponents.set(entry.name, Array.isArray(formAttribute) ? formAttribute : [formAttribute]);
26418
- }
26419
- return {
26420
- enforceDynamicLinks: ruleSettings.enforceDynamicLinks ?? "always",
26421
- warnOnSpreadAttributes: ruleSettings.warnOnSpreadAttributes ?? false,
26422
- allowReferrer: ruleSettings.allowReferrer ?? false,
26423
- links: ruleSettings.links ?? true,
26424
- forms: ruleSettings.forms ?? false,
26425
- linkComponents,
26426
- formComponents
26427
- };
26428
- };
26429
- const isExternalLink = (href) => href.includes("//");
26430
- const matchHrefExpression = (expression, state) => {
26431
- if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") {
26432
- if (isExternalLink(expression.value)) state.isExternal = true;
26433
- return;
26434
- }
26435
- if (isNodeOfType(expression, "Identifier")) {
26436
- state.isDynamic = true;
26437
- return;
26438
- }
26439
- if (isNodeOfType(expression, "ConditionalExpression")) {
26440
- matchHrefExpression(expression.consequent, state);
26441
- matchHrefExpression(expression.alternate, state);
26442
- }
26443
- };
26444
- const checkHref = (attributeValue, enforceDynamicLinks) => {
26445
- const state = {
26446
- isExternal: false,
26447
- isDynamic: false
26448
- };
26449
- if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") state.isExternal = isExternalLink(attributeValue.value);
26450
- else if (isNodeOfType(attributeValue, "JSXExpressionContainer")) matchHrefExpression(attributeValue.expression, state);
26451
- if (enforceDynamicLinks === "never") return !state.isExternal || state.isDynamic;
26452
- return !(state.isExternal || state.isDynamic);
26453
- };
26454
- const checkRelValue = (text, allowReferrer) => {
26455
- const tokens = text.split(/\s+/);
26456
- if (allowReferrer) return tokens.includes("noopener") || tokens.includes("noreferrer");
26457
- return tokens.some((token) => token.toLowerCase() === "noreferrer");
26458
- };
26459
- const matchRelExpression = (expression, allowReferrer) => {
26460
- const empty = {
26461
- combined: false,
26462
- testName: "",
26463
- consequent: false,
26464
- alternate: false
26465
- };
26466
- if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
26467
- combined: checkRelValue(expression.value, allowReferrer),
26468
- testName: "",
26469
- consequent: false,
26470
- alternate: false
26471
- };
26472
- if (isNodeOfType(expression, "ConditionalExpression")) {
26473
- const consequent = matchRelExpression(expression.consequent, allowReferrer);
26474
- const alternate = matchRelExpression(expression.alternate, allowReferrer);
26475
- const test = expression.test;
26476
- if (isNodeOfType(test, "Identifier")) return {
26477
- combined: consequent.combined && alternate.combined,
26478
- testName: test.name,
26479
- consequent: consequent.combined,
26480
- alternate: alternate.combined
26481
- };
26482
- return {
26483
- combined: consequent.combined && alternate.combined,
26484
- testName: "",
26485
- consequent: consequent.combined,
26486
- alternate: alternate.combined
26487
- };
26488
- }
26489
- return empty;
26490
- };
26491
- const checkRel = (attributeValue, allowReferrer) => {
26492
- const empty = {
26493
- combined: false,
26494
- testName: "",
26495
- consequent: false,
26496
- alternate: false
26497
- };
26498
- if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
26499
- combined: checkRelValue(attributeValue.value, allowReferrer),
26500
- testName: "",
26501
- consequent: false,
26502
- alternate: false
26503
- };
26504
- if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
26505
- const expression = attributeValue.expression;
26506
- if (expression.type === "JSXEmptyExpression") return empty;
26507
- return matchRelExpression(expression, allowReferrer);
26508
- }
26509
- return empty;
26510
- };
26511
- const matchTargetExpression = (expression) => {
26512
- const empty = {
26513
- combined: false,
26514
- testName: "",
26515
- consequent: false,
26516
- alternate: false
26517
- };
26518
- if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
26519
- combined: expression.value.toLowerCase() === "_blank",
26520
- testName: "",
26521
- consequent: false,
26522
- alternate: false
26523
- };
26524
- if (isNodeOfType(expression, "ConditionalExpression")) {
26525
- const consequent = matchTargetExpression(expression.consequent);
26526
- const alternate = matchTargetExpression(expression.alternate);
26527
- const test = expression.test;
26528
- const combined = consequent.combined || alternate.combined;
26529
- if (isNodeOfType(test, "Identifier")) return {
26530
- combined,
26531
- testName: test.name,
26532
- consequent: consequent.combined,
26533
- alternate: alternate.combined
26534
- };
26535
- return {
26536
- combined,
26537
- testName: "",
26538
- consequent: consequent.combined,
26539
- alternate: alternate.combined
26540
- };
26541
- }
26542
- return empty;
26543
- };
26544
- const checkTarget = (attributeValue) => {
26545
- if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
26546
- combined: attributeValue.value.toLowerCase() === "_blank",
26547
- testName: "",
26548
- consequent: false,
26549
- alternate: false
26550
- };
26551
- if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
26552
- const expression = attributeValue.expression;
26553
- if (expression.type === "JSXEmptyExpression") return {
26554
- combined: false,
26555
- testName: "",
26556
- consequent: false,
26557
- alternate: false
26558
- };
26559
- return matchTargetExpression(expression);
26560
- }
26561
- return {
26562
- combined: false,
26563
- testName: "",
26564
- consequent: false,
26565
- alternate: false
26566
- };
26567
- };
26568
- const jsxNoTargetBlank = defineRule({
26569
- id: "jsx-no-target-blank",
26570
- title: "Unsafe target=_blank link",
26571
- severity: "warn",
26572
- recommendation: "Add `rel=\"noreferrer\"` (or `\"noopener\"`) when using `target=\"_blank\"`.",
26573
- category: "Security",
26574
- create: (context) => {
26575
- const settings = resolveSettings$28(context.settings);
26576
- const isLink = (tagName) => {
26577
- if (!settings.links) return false;
26578
- if (tagName === "a") return true;
26579
- return settings.linkComponents.has(tagName);
26580
- };
26581
- const isForm = (tagName) => {
26582
- if (!settings.forms) return false;
26583
- if (tagName === "form") return true;
26584
- return settings.formComponents.has(tagName);
26585
- };
26586
- return { JSXOpeningElement(node) {
26587
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
26588
- const tagName = resolveJsxElementType(node);
26589
- if (!tagName) return;
26590
- if (!isLink(tagName) && !isForm(tagName)) return;
26591
- const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
26592
- const formAttributeNames = settings.formComponents.get(tagName) ?? ["action"];
26593
- let targetTuple = {
26594
- combined: false,
26595
- testName: "",
26596
- consequent: false,
26597
- alternate: false
26598
- };
26599
- let relTuple = {
26600
- combined: false,
26601
- testName: "",
26602
- consequent: false,
26603
- alternate: false
26604
- };
26605
- let isHrefValid = true;
26606
- let hasHrefValue = false;
26607
- let warnSpread = false;
26608
- let targetReportNode = node.name;
26609
- let spreadReportNode = null;
26610
- for (const attribute of node.attributes) {
26611
- if (isNodeOfType(attribute, "JSXSpreadAttribute")) {
26612
- if (settings.warnOnSpreadAttributes) {
26613
- warnSpread = true;
26614
- spreadReportNode = attribute;
26615
- targetTuple = {
26616
- combined: false,
26617
- testName: "",
26618
- consequent: false,
26619
- alternate: false
26620
- };
26621
- relTuple = {
26622
- combined: false,
26623
- testName: "",
26624
- consequent: false,
26625
- alternate: false
26626
- };
26627
- isHrefValid = false;
26628
- hasHrefValue = true;
26629
- }
26630
- continue;
26631
- }
26632
- if (!isNodeOfType(attribute, "JSXAttribute")) continue;
26633
- const attributeName = attribute.name;
26634
- if (!isNodeOfType(attributeName, "JSXIdentifier")) continue;
26635
- const propName = attributeName.name;
26636
- const value = attribute.value;
26637
- if (propName === "target") {
26638
- if (value) {
26639
- targetTuple = checkTarget(value);
26640
- targetReportNode = value;
26641
- }
26642
- } else if (propName === "href" || propName === "action" || linkAttributeNames.includes(propName) || formAttributeNames.includes(propName)) {
26643
- if (value) {
26644
- hasHrefValue = true;
26645
- isHrefValid = checkHref(value, settings.enforceDynamicLinks);
26646
- }
26647
- } else if (propName === "rel" && value) relTuple = checkRel(value, settings.allowReferrer);
26648
- }
26649
- if (warnSpread) {
26650
- if (hasHrefValue && isHrefValid || relTuple.combined) return;
26651
- context.report({
26652
- node: spreadReportNode ?? node,
26653
- message: SPREAD_MESSAGE
26654
- });
26655
- return;
26656
- }
26657
- if (!isHrefValid) {
26658
- if (targetTuple.testName !== "" && targetTuple.testName === relTuple.testName) {
26659
- const consequentBad = targetTuple.consequent && !relTuple.consequent;
26660
- const alternateBad = targetTuple.alternate && !relTuple.alternate;
26661
- if (consequentBad || alternateBad) context.report({
26662
- node: targetReportNode,
26663
- message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
26664
- });
26665
- return;
26666
- }
26667
- if (targetTuple.combined && !relTuple.combined) context.report({
26668
- node: targetReportNode,
26669
- message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
26670
- });
26671
- }
26672
- } };
26673
- }
26674
- });
26675
- //#endregion
26676
26877
  //#region src/plugin/rules/react-builtins/jsx-no-undef.ts
26677
26878
  const buildMessage$18 = (name) => `\`${name}\` crashes at runtime because it isn't defined here.`;
26678
26879
  const KNOWN_GLOBALS = new Set([
@@ -28330,6 +28531,30 @@ const hasAncestorLayoutMatching = (pageFilename, matchesLayout) => {
28330
28531
  const hasAncestorMetadataLayout = (pageFilename) => hasAncestorLayoutMatching(pageFilename, (layoutPath) => METADATA_EXPORT_NAMES.some((exportName) => doesModuleExportName(layoutPath, exportName)));
28331
28532
  //#endregion
28332
28533
  //#region src/plugin/rules/nextjs/nextjs-missing-metadata.ts
28534
+ const unwrapAwaitedExpression = (node) => {
28535
+ let expression = stripParenExpression(node);
28536
+ while (isNodeOfType(expression, "AwaitExpression")) expression = stripParenExpression(expression.argument);
28537
+ return expression;
28538
+ };
28539
+ const isNextNavigationRedirectCall = (node, context) => {
28540
+ const expression = unwrapAwaitedExpression(node);
28541
+ if (!isNodeOfType(expression, "CallExpression")) return false;
28542
+ const callee = stripParenExpression(expression.callee);
28543
+ if (!isNodeOfType(callee, "Identifier")) return false;
28544
+ if (context.scopes.symbolFor(callee)?.kind !== "import") return false;
28545
+ const importedName = getImportedNameFromModule(expression, callee.name, "next/navigation");
28546
+ return importedName === "redirect" || importedName === "permanentRedirect";
28547
+ };
28548
+ const isRedirectOnlyDefaultExport = (programNode, context) => {
28549
+ const defaultExport = findExportedValue(programNode, "default");
28550
+ if (!defaultExport || !isFunctionLike$1(defaultExport)) return false;
28551
+ if (!isNodeOfType(defaultExport.body, "BlockStatement")) return isNextNavigationRedirectCall(defaultExport.body, context);
28552
+ if (defaultExport.body.body.length !== 1) return false;
28553
+ const onlyStatement = defaultExport.body.body[0];
28554
+ if (isNodeOfType(onlyStatement, "ExpressionStatement")) return isNextNavigationRedirectCall(onlyStatement.expression, context);
28555
+ if (isNodeOfType(onlyStatement, "ReturnStatement") && onlyStatement.argument) return isNextNavigationRedirectCall(onlyStatement.argument, context);
28556
+ return false;
28557
+ };
28333
28558
  const nextjsMissingMetadata = defineRule({
28334
28559
  id: "nextjs-missing-metadata",
28335
28560
  title: "Page missing metadata for search previews",
@@ -28349,6 +28574,7 @@ const nextjsMissingMetadata = defineRule({
28349
28574
  if (isNodeOfType(declaration, "FunctionDeclaration")) return declaration.id?.name === "generateMetadata";
28350
28575
  return (statement.specifiers ?? []).some((specifier) => isNodeOfType(specifier, "ExportSpecifier") && isNodeOfType(specifier.exported, "Identifier") && METADATA_EXPORT_NAMES.includes(specifier.exported.name));
28351
28576
  })) return;
28577
+ if (isRedirectOnlyDefaultExport(programNode, context)) return;
28352
28578
  if (hasAncestorMetadataLayout(context.filename ?? "")) return;
28353
28579
  context.report({
28354
28580
  node: programNode,
@@ -29362,6 +29588,14 @@ const nextjsNoImgElement = defineRule({
29362
29588
  });
29363
29589
  //#endregion
29364
29590
  //#region src/plugin/rules/nextjs/nextjs-no-native-script.ts
29591
+ const hasEnabledBooleanAttribute = (attributes, attributeName) => {
29592
+ const attribute = findJsxAttribute(attributes, attributeName);
29593
+ if (!attribute) return false;
29594
+ if (attribute.value === null) return true;
29595
+ if (isNodeOfType(attribute.value, "Literal")) return true;
29596
+ if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return false;
29597
+ return isNodeOfType(attribute.value.expression, "Literal") && Boolean(attribute.value.expression.value);
29598
+ };
29365
29599
  const nextjsNoNativeScript = defineRule({
29366
29600
  id: "nextjs-no-native-script",
29367
29601
  title: "Plain script can block Next.js rendering",
@@ -29374,6 +29608,9 @@ const nextjsNoNativeScript = defineRule({
29374
29608
  const typeAttribute = findJsxAttribute(node.attributes ?? [], "type");
29375
29609
  const typeValue = isNodeOfType(typeAttribute?.value, "Literal") ? typeAttribute.value.value : null;
29376
29610
  if (typeof typeValue === "string" && !EXECUTABLE_SCRIPT_TYPES.has(typeValue)) return;
29611
+ if (hasEnabledBooleanAttribute(node.attributes ?? [], "async")) return;
29612
+ if (hasEnabledBooleanAttribute(node.attributes ?? [], "defer")) return;
29613
+ if (typeValue === "module") return;
29377
29614
  const hasSrcAttribute = Boolean(findJsxAttribute(node.attributes ?? [], "src"));
29378
29615
  if (Boolean(findJsxAttribute(node.attributes ?? [], "dangerouslySetInnerHTML")) && !hasSrcAttribute) return;
29379
29616
  context.report({
@@ -34466,6 +34703,19 @@ const noCascadingSetState = defineRetiredRule({
34466
34703
  recommendation: "Retired: React batches synchronous state updates from one effect into the same follow-up commit, so setter count does not prove repeated redraws."
34467
34704
  });
34468
34705
  //#endregion
34706
+ //#region src/plugin/utils/get-react-use-callback-call.ts
34707
+ const getReactUseCallbackCall = (expression, scopes) => {
34708
+ const identifier = stripParenExpression(expression);
34709
+ if (!isNodeOfType(identifier, "Identifier")) return null;
34710
+ const symbol = resolveConstIdentifierAlias(identifier, scopes);
34711
+ if (symbol?.kind !== "const" || !symbol.initializer) return null;
34712
+ const initializer = stripParenExpression(symbol.initializer);
34713
+ return isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, "useCallback", scopes, {
34714
+ allowGlobalReactNamespace: true,
34715
+ resolveNamedAliases: true
34716
+ }) ? initializer : null;
34717
+ };
34718
+ //#endregion
34469
34719
  //#region src/plugin/rules/state-and-effects/utils/create-state-trigger-reachability.ts
34470
34720
  const getRefCurrentSymbol = (node, context) => {
34471
34721
  const expression = stripParenExpression(node);
@@ -34994,19 +35244,8 @@ const isBuiltinNamespaceCallee = (callee) => {
34994
35244
  }
34995
35245
  return false;
34996
35246
  };
34997
- const getReactUseCallbackSource = (reference, context) => {
34998
- const identifier = reference.identifier;
34999
- const symbol = resolveConstIdentifierAlias(identifier, context.scopes);
35000
- if (!symbol || symbol.kind !== "const" || !symbol.initializer) return null;
35001
- const initializer = stripParenExpression(symbol.initializer);
35002
- if (isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, "useCallback", context.scopes, {
35003
- allowGlobalReactNamespace: true,
35004
- resolveNamedAliases: true
35005
- })) return initializer;
35006
- return null;
35007
- };
35008
35247
  const getDependencyStateRefs = (analysis, context, dependencyReference) => {
35009
- const useCallbackCall = getReactUseCallbackSource(dependencyReference, context);
35248
+ const useCallbackCall = getReactUseCallbackCall(dependencyReference.identifier, context.scopes);
35010
35249
  if (!useCallbackCall) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
35011
35250
  const dependencyList = useCallbackCall.arguments?.[1];
35012
35251
  if (!dependencyList || !isNodeOfType(dependencyList, "ArrayExpression")) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
@@ -36594,19 +36833,6 @@ const noDefaultProps = defineRule({
36594
36833
  } })
36595
36834
  });
36596
36835
  //#endregion
36597
- //#region src/plugin/utils/is-component-function.ts
36598
- const isFunctionAssignedToComponent = (functionNode) => {
36599
- let cursor = functionNode.parent ?? null;
36600
- while (isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
36601
- if (isNodeOfType(cursor, "VariableDeclarator") && isNodeOfType(cursor.id, "Identifier") && isUppercaseName(cursor.id.name)) return true;
36602
- return isNodeOfType(cursor, "ExportDefaultDeclaration");
36603
- };
36604
- const isComponentFunction$1 = (node) => {
36605
- if (!isFunctionLike$1(node)) return false;
36606
- if (isNodeOfType(node, "FunctionDeclaration")) return !node.id || node.id.name === "default" || isUppercaseName(node.id.name) || isNodeOfType(node.parent, "ExportDefaultDeclaration");
36607
- return isFunctionAssignedToComponent(node);
36608
- };
36609
- //#endregion
36610
36836
  //#region src/plugin/utils/create-component-prop-stack-tracker.ts
36611
36837
  const extractDestructuredPropNames = (params) => {
36612
36838
  const propNames = /* @__PURE__ */ new Set();
@@ -38520,6 +38746,17 @@ const noDynamicImportPath = defineRule({
38520
38746
  })
38521
38747
  });
38522
38748
  //#endregion
38749
+ //#region src/plugin/utils/get-require-call-source.ts
38750
+ const getRequireCallSource = (expression) => {
38751
+ const unwrappedExpression = stripParenExpression(expression);
38752
+ if (isNodeOfType(unwrappedExpression, "MemberExpression")) return getRequireCallSource(unwrappedExpression.object);
38753
+ if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
38754
+ if (!isNodeOfType(unwrappedExpression.callee, "Identifier") || unwrappedExpression.callee.name !== "require") return null;
38755
+ const [firstArgument] = unwrappedExpression.arguments ?? [];
38756
+ if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
38757
+ return typeof firstArgument.value === "string" ? firstArgument.value : null;
38758
+ };
38759
+ //#endregion
38523
38760
  //#region src/plugin/rules/state-and-effects/utils/is-cleanup-return.ts
38524
38761
  const ITERATOR_CALLBACK_METHOD_NAMES = new Set([
38525
38762
  "each",
@@ -38664,7 +38901,16 @@ const collectDependencyStateSymbolIds = (effectNode, stateSymbolIds, scopes) =>
38664
38901
  }
38665
38902
  return dependencyStateSymbolIds;
38666
38903
  };
38667
- const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
38904
+ const resolveSynchronouslyInvokedFunction = (expression, scopes) => {
38905
+ const localFunction = resolveExactLocalFunction(expression, scopes);
38906
+ if (localFunction) return localFunction;
38907
+ const useCallbackCall = getReactUseCallbackCall(expression, scopes);
38908
+ if (!useCallbackCall) return null;
38909
+ const callback = useCallbackCall.arguments[0];
38910
+ if (!callback || isNodeOfType(callback, "SpreadElement")) return null;
38911
+ return resolveExactLocalFunction(callback, scopes);
38912
+ };
38913
+ const collectSynchronouslyInvokedFunctions = (effectCallback, scopes, includeStableCallbacks = true) => {
38668
38914
  const analysisFunctions = new Set([effectCallback]);
38669
38915
  const pendingFunctions = [effectCallback];
38670
38916
  while (pendingFunctions.length > 0) {
@@ -38672,9 +38918,9 @@ const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
38672
38918
  if (!currentFunction || !isFunctionLike$1(currentFunction)) continue;
38673
38919
  walkInsideStatementBlocks(currentFunction.body, (child) => {
38674
38920
  if (!isNodeOfType(child, "CallExpression")) return;
38675
- const invokedFunction = resolveExactLocalFunction(child.callee, scopes);
38921
+ const invokedFunction = includeStableCallbacks ? resolveSynchronouslyInvokedFunction(child.callee, scopes) : resolveExactLocalFunction(child.callee, scopes);
38676
38922
  if (!invokedFunction || analysisFunctions.has(invokedFunction)) return;
38677
- if (isFunctionLike$1(invokedFunction) && invokedFunction.async) return;
38923
+ if (isFunctionLike$1(invokedFunction) && (invokedFunction.async || invokedFunction.generator)) return;
38678
38924
  analysisFunctions.add(invokedFunction);
38679
38925
  pendingFunctions.push(invokedFunction);
38680
38926
  });
@@ -38687,7 +38933,51 @@ const visitSynchronousFunctionBodies = (analysisFunctions, visitor) => {
38687
38933
  walkInsideStatementBlocks(analysisFunction.body, visitor);
38688
38934
  }
38689
38935
  };
38690
- const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
38936
+ const getStateNameForSetterCall = (node, setterSymbolIdToStateName, scopes) => {
38937
+ if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier")) return null;
38938
+ const setterSymbol = resolveConstIdentifierAlias(node.callee, scopes, true);
38939
+ return setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) ?? null : null;
38940
+ };
38941
+ const collectStateWriteAnalysisFunctions = (effectCallback, scopes, setterSymbolIdToStateName) => {
38942
+ const ordinaryAnalysisFunctions = collectSynchronouslyInvokedFunctions(effectCallback, scopes, false);
38943
+ const fullAnalysisFunctions = collectSynchronouslyInvokedFunctions(effectCallback, scopes);
38944
+ const stableAnalysisFunctions = [...fullAnalysisFunctions].filter((analysisFunction) => !ordinaryAnalysisFunctions.has(analysisFunction));
38945
+ if (stableAnalysisFunctions.length === 0) return ordinaryAnalysisFunctions;
38946
+ let hasUnprovenObservableWork = stableAnalysisFunctions.some((analysisFunction) => {
38947
+ if (!isFunctionLike$1(analysisFunction)) return false;
38948
+ let hasExecutableDefault = false;
38949
+ for (const parameter of analysisFunction.params) {
38950
+ walkInsideStatementBlocks(parameter, (child) => {
38951
+ if (isNodeOfType(child, "CallExpression") || isNodeOfType(child, "AssignmentExpression") || isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "ImportExpression") || isNodeOfType(child, "NewExpression") || isNodeOfType(child, "TaggedTemplateExpression") || isNodeOfType(child, "ThrowStatement") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") hasExecutableDefault = true;
38952
+ });
38953
+ if (hasExecutableDefault) return true;
38954
+ }
38955
+ return false;
38956
+ });
38957
+ const stableWrittenStateNames = /* @__PURE__ */ new Set();
38958
+ const allWrittenStateNames = /* @__PURE__ */ new Set();
38959
+ visitSynchronousFunctionBodies(fullAnalysisFunctions, (child) => {
38960
+ const writtenStateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
38961
+ if (writtenStateName) allWrittenStateNames.add(writtenStateName);
38962
+ });
38963
+ visitSynchronousFunctionBodies(new Set(stableAnalysisFunctions), (child) => {
38964
+ if (hasUnprovenObservableWork) return;
38965
+ if (isNodeOfType(child, "CallExpression")) {
38966
+ const writtenStateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
38967
+ if (writtenStateName) {
38968
+ stableWrittenStateNames.add(writtenStateName);
38969
+ return;
38970
+ }
38971
+ const invokedFunction = resolveSynchronouslyInvokedFunction(child.callee, scopes);
38972
+ if (invokedFunction && fullAnalysisFunctions.has(invokedFunction)) return;
38973
+ hasUnprovenObservableWork = true;
38974
+ return;
38975
+ }
38976
+ if (isNodeOfType(child, "AssignmentExpression") || isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "ImportExpression") || isNodeOfType(child, "NewExpression") || isNodeOfType(child, "TaggedTemplateExpression") || isNodeOfType(child, "ThrowStatement") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") hasUnprovenObservableWork = true;
38977
+ });
38978
+ return !hasUnprovenObservableWork && stableWrittenStateNames.size === 1 && allWrittenStateNames.size === 1 ? fullAnalysisFunctions : ordinaryAnalysisFunctions;
38979
+ };
38980
+ const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, visitedSymbolIds = /* @__PURE__ */ new Set(), additionalStateValues = /* @__PURE__ */ new Map()) => {
38691
38981
  const unwrappedExpression = stripParenExpression(expression);
38692
38982
  if (isNodeOfType(unwrappedExpression, "Literal")) {
38693
38983
  const literalValue = unwrappedExpression.value;
@@ -38695,46 +38985,49 @@ const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, vi
38695
38985
  return null;
38696
38986
  }
38697
38987
  if (isNodeOfType(unwrappedExpression, "Identifier")) {
38698
- if (scopes.symbolFor(unwrappedExpression)?.id === stateSymbolId) return stateValue;
38988
+ const symbol = scopes.symbolFor(unwrappedExpression);
38989
+ if (symbol?.id === stateSymbolId) return stateValue;
38990
+ if (symbol && additionalStateValues.has(symbol.id)) return additionalStateValues.get(symbol.id) ?? null;
38699
38991
  if (unwrappedExpression.name === "undefined" && scopes.isGlobalReference(unwrappedExpression)) return { value: void 0 };
38992
+ if (unwrappedExpression.name === "NaN" && scopes.isGlobalReference(unwrappedExpression)) return { value: NaN };
38700
38993
  const immutableSymbol = scopes.symbolFor(unwrappedExpression);
38701
38994
  if (immutableSymbol?.kind !== "const" || !immutableSymbol.initializer || !isNodeOfType(immutableSymbol.declarationNode, "VariableDeclarator") || immutableSymbol.declarationNode.id !== immutableSymbol.bindingIdentifier || immutableSymbol.declarationNode.init !== immutableSymbol.initializer || immutableSymbol.references.some((reference) => reference.flag !== "read") || visitedSymbolIds.has(immutableSymbol.id)) return null;
38702
- return readStaticEffectValue(immutableSymbol.initializer, scopes, stateSymbolId, stateValue, new Set(visitedSymbolIds).add(immutableSymbol.id));
38995
+ return readStaticEffectValue(immutableSymbol.initializer, scopes, stateSymbolId, stateValue, new Set(visitedSymbolIds).add(immutableSymbol.id), additionalStateValues);
38703
38996
  }
38704
38997
  if (isNodeOfType(unwrappedExpression, "UnaryExpression")) {
38705
38998
  if (unwrappedExpression.operator === "void") return { value: void 0 };
38706
38999
  if (unwrappedExpression.operator !== "!") return null;
38707
- const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39000
+ const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38708
39001
  return argumentValue ? { value: !argumentValue.value } : null;
38709
39002
  }
38710
39003
  if (isNodeOfType(unwrappedExpression, "CallExpression")) {
38711
39004
  if (isNodeOfType(unwrappedExpression.callee, "Identifier") && unwrappedExpression.callee.name === "Boolean" && scopes.isGlobalReference(unwrappedExpression.callee) && unwrappedExpression.arguments.length === 1 && unwrappedExpression.arguments[0] && !isNodeOfType(unwrappedExpression.arguments[0], "SpreadElement")) {
38712
- const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds);
39005
+ const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38713
39006
  return argumentValue ? { value: Boolean(argumentValue.value) } : null;
38714
39007
  }
38715
39008
  return null;
38716
39009
  }
38717
39010
  if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
38718
- const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39011
+ const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38719
39012
  if (!leftValue) return null;
38720
39013
  if (unwrappedExpression.operator === "&&" && !leftValue.value) return leftValue;
38721
39014
  if (unwrappedExpression.operator === "||" && leftValue.value) return leftValue;
38722
39015
  if (unwrappedExpression.operator === "??" && leftValue.value !== null && leftValue.value !== void 0) return leftValue;
38723
- return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39016
+ return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38724
39017
  }
38725
39018
  if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
38726
- const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39019
+ const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38727
39020
  if (!testValue) return null;
38728
- return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39021
+ return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38729
39022
  }
38730
39023
  if (isNodeOfType(unwrappedExpression, "MemberExpression") && unwrappedExpression.optional) {
38731
- const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39024
+ const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38732
39025
  if (objectValue?.value === null || objectValue?.value === void 0) return { value: void 0 };
38733
39026
  return null;
38734
39027
  }
38735
39028
  if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
38736
- const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
38737
- const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39029
+ const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
39030
+ const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38738
39031
  if (!leftValue || !rightValue) return null;
38739
39032
  if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
38740
39033
  const areEqual = leftValue.value === rightValue.value;
@@ -38773,8 +39066,7 @@ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName
38773
39066
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
38774
39067
  if (!isNodeOfType(child, "CallExpression")) return;
38775
39068
  if (!isNodeOfType(child.callee, "Identifier")) return;
38776
- const setterSymbol = resolveConstIdentifierAlias(child.callee, scopes, true);
38777
- const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
39069
+ const stateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
38778
39070
  if (!stateName) return;
38779
39071
  const writeInfo = stateWrites.get(stateName) ?? {
38780
39072
  values: /* @__PURE__ */ new Set(),
@@ -38790,27 +39082,27 @@ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName
38790
39082
  const isGlobalBooleanCall = (node, scopes) => {
38791
39083
  return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Boolean" && scopes.isGlobalReference(node.callee);
38792
39084
  };
38793
- const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes) => {
39085
+ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map()) => {
38794
39086
  let currentNode = workNode;
38795
39087
  while (currentNode.parent) {
38796
39088
  const parentNode = currentNode.parent;
38797
39089
  if (isFunctionLike$1(parentNode)) break;
38798
39090
  if (isNodeOfType(parentNode, "IfStatement")) {
38799
- const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
39091
+ const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
38800
39092
  if (testValue) {
38801
39093
  if (currentNode === parentNode.consequent && !testValue.value) return false;
38802
39094
  if (currentNode === parentNode.alternate && testValue.value) return false;
38803
39095
  }
38804
39096
  }
38805
39097
  if (isNodeOfType(parentNode, "ConditionalExpression")) {
38806
- const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
39098
+ const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
38807
39099
  if (testValue) {
38808
39100
  if (currentNode === parentNode.consequent && !testValue.value) return false;
38809
39101
  if (currentNode === parentNode.alternate && testValue.value) return false;
38810
39102
  }
38811
39103
  }
38812
39104
  if (isNodeOfType(parentNode, "LogicalExpression") && currentNode === parentNode.right) {
38813
- const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue);
39105
+ const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
38814
39106
  if (leftValue) {
38815
39107
  if (parentNode.operator === "&&" && !leftValue.value) return false;
38816
39108
  if (parentNode.operator === "||" && leftValue.value) return false;
@@ -38822,7 +39114,7 @@ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, s
38822
39114
  if (statementIndex >= 0) for (let index = 0; index < statementIndex; index += 1) {
38823
39115
  const earlierStatement = parentNode.body[index];
38824
39116
  if (!isNodeOfType(earlierStatement, "IfStatement") || earlierStatement.alternate || !statementAlwaysExits(earlierStatement.consequent)) continue;
38825
- if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue)?.value) return false;
39117
+ if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues)?.value) return false;
38826
39118
  }
38827
39119
  }
38828
39120
  currentNode = parentNode;
@@ -38832,24 +39124,93 @@ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, s
38832
39124
  const isReaderWorkNode = (node, analysisFunctions, scopes) => {
38833
39125
  if (isNodeOfType(node, "CallExpression")) {
38834
39126
  if (isGlobalBooleanCall(node, scopes)) return false;
38835
- const invokedFunction = resolveExactLocalFunction(node.callee, scopes);
39127
+ const invokedFunction = resolveSynchronouslyInvokedFunction(node.callee, scopes);
38836
39128
  return !invokedFunction || !analysisFunctions.has(invokedFunction);
38837
39129
  }
38838
39130
  return isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "ThrowStatement") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete";
38839
39131
  };
38840
- const canStateWriteReachReaderWork = (writeInfo, readerEffect, stateSymbolId, scopes) => {
38841
- if (writeInfo.hasUnknownValue || stateSymbolId === null) return true;
38842
- for (const writtenValue of writeInfo.values) {
38843
- const stateValue = { value: writtenValue };
39132
+ const mergeKnownStateValues = (additionalStateValues, parameterStateValues) => {
39133
+ if (parameterStateValues.size === 0) return additionalStateValues;
39134
+ const knownStateValues = new Map(additionalStateValues);
39135
+ for (const [symbolId, value] of parameterStateValues) knownStateValues.set(symbolId, value);
39136
+ return knownStateValues;
39137
+ };
39138
+ const buildInvokedFunctionParameterStateValues = (invokedFunction, invocation, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map(), callerParameterStateValues = /* @__PURE__ */ new Map()) => {
39139
+ if (!isFunctionLike$1(invokedFunction)) return /* @__PURE__ */ new Map();
39140
+ const parameterStateValues = /* @__PURE__ */ new Map();
39141
+ const callerKnownStateValues = mergeKnownStateValues(additionalStateValues, callerParameterStateValues);
39142
+ for (let parameterIndex = 0; parameterIndex < invokedFunction.params.length; parameterIndex += 1) {
39143
+ const rawParameter = invokedFunction.params[parameterIndex];
39144
+ const rawArgument = invocation.arguments[parameterIndex];
39145
+ if (rawArgument && isNodeOfType(rawArgument, "SpreadElement")) break;
39146
+ const parameter = isNodeOfType(rawParameter, "AssignmentPattern") ? rawParameter.left : rawParameter;
39147
+ if (!isNodeOfType(parameter, "Identifier")) continue;
39148
+ const parameterSymbol = scopes.symbolFor(parameter);
39149
+ if (!parameterSymbol) continue;
39150
+ let argumentValue = null;
39151
+ if (rawArgument) {
39152
+ argumentValue = readStaticEffectValue(rawArgument, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), callerKnownStateValues);
39153
+ if (argumentValue !== null && argumentValue.value === void 0 && isNodeOfType(rawParameter, "AssignmentPattern")) {
39154
+ const defaultKnownStateValues = mergeKnownStateValues(callerKnownStateValues, parameterStateValues);
39155
+ argumentValue = readStaticEffectValue(rawParameter.right, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), defaultKnownStateValues);
39156
+ }
39157
+ } else if (isNodeOfType(rawParameter, "AssignmentPattern")) {
39158
+ const defaultKnownStateValues = mergeKnownStateValues(callerKnownStateValues, parameterStateValues);
39159
+ argumentValue = readStaticEffectValue(rawParameter.right, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), defaultKnownStateValues);
39160
+ } else argumentValue = { value: void 0 };
39161
+ if (argumentValue) parameterStateValues.set(parameterSymbol.id, argumentValue);
39162
+ }
39163
+ return parameterStateValues;
39164
+ };
39165
+ const haveEqualParameterStateValues = (leftValues, rightValues) => {
39166
+ if (leftValues.size !== rightValues.size) return false;
39167
+ for (const [symbolId, leftValue] of leftValues) {
39168
+ const rightValue = rightValues.get(symbolId);
39169
+ if (!rightValue || !Object.is(leftValue.value, rightValue.value)) return false;
39170
+ }
39171
+ return true;
39172
+ };
39173
+ const canReaderWorkRunForStateValues = (readerEffect, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map()) => {
39174
+ const pendingFrames = [{
39175
+ functionNode: readerEffect.callback,
39176
+ parameterStateValues: /* @__PURE__ */ new Map()
39177
+ }];
39178
+ const visitedParameterStateValues = /* @__PURE__ */ new Map();
39179
+ while (pendingFrames.length > 0) {
39180
+ const frame = pendingFrames.pop();
39181
+ if (!frame || !isFunctionLike$1(frame.functionNode)) continue;
39182
+ const functionParameterStateValues = visitedParameterStateValues.get(frame.functionNode) ?? [];
39183
+ if (functionParameterStateValues.some((previousValues) => haveEqualParameterStateValues(previousValues, frame.parameterStateValues))) continue;
39184
+ functionParameterStateValues.push(frame.parameterStateValues);
39185
+ visitedParameterStateValues.set(frame.functionNode, functionParameterStateValues);
39186
+ const knownStateValues = mergeKnownStateValues(additionalStateValues, frame.parameterStateValues);
38844
39187
  let didFindReachableWork = false;
38845
- visitSynchronousFunctionBodies(readerEffect.analysisFunctions, (child) => {
38846
- if (didFindReachableWork || !isReaderWorkNode(child, readerEffect.analysisFunctions, scopes)) return;
38847
- if (isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes)) didFindReachableWork = true;
39188
+ walkInsideStatementBlocks(frame.functionNode.body, (child) => {
39189
+ if (didFindReachableWork) return;
39190
+ if (isNodeOfType(child, "CallExpression")) {
39191
+ const invokedFunction = resolveSynchronouslyInvokedFunction(child.callee, scopes);
39192
+ if (invokedFunction && readerEffect.analysisFunctions.has(invokedFunction)) {
39193
+ if (isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes, knownStateValues)) pendingFrames.push({
39194
+ functionNode: invokedFunction,
39195
+ parameterStateValues: buildInvokedFunctionParameterStateValues(invokedFunction, child, stateSymbolId, stateValue, scopes, additionalStateValues, frame.parameterStateValues)
39196
+ });
39197
+ return;
39198
+ }
39199
+ }
39200
+ if (isReaderWorkNode(child, readerEffect.analysisFunctions, scopes) && isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes, knownStateValues)) didFindReachableWork = true;
38848
39201
  });
38849
39202
  if (didFindReachableWork) return true;
38850
39203
  }
38851
39204
  return false;
38852
39205
  };
39206
+ const canStateWriteReachReaderWork = (writtenStateName, writerEffect, readerEffect, stateSymbolIds, scopes) => {
39207
+ const writeInfo = writerEffect.stateWrites.get(writtenStateName);
39208
+ const stateSymbolId = stateSymbolIds.get(writtenStateName);
39209
+ if (!writeInfo || stateSymbolId === void 0) return true;
39210
+ if (writeInfo.hasUnknownValue) return true;
39211
+ for (const writtenValue of writeInfo.values) if (canReaderWorkRunForStateValues(readerEffect, stateSymbolId, { value: writtenValue }, scopes)) return true;
39212
+ return false;
39213
+ };
38853
39214
  const EMPTY_CLEANUP_NAME_SET = /* @__PURE__ */ new Set();
38854
39215
  const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
38855
39216
  "clear",
@@ -38860,26 +39221,499 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
38860
39221
  "keys",
38861
39222
  "values"
38862
39223
  ]);
38863
- const isFunctionShapedReturn = (returnedValue, setterToStateName, setterSymbolIdToStateName, scopes, isExplicitReturnStatement) => {
38864
- if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
38865
- if (isNodeOfType(returnedValue, "CallExpression")) {
38866
- if (isNodeOfType(returnedValue.callee, "Identifier")) {
38867
- const setterSymbol = resolveConstIdentifierAlias(returnedValue.callee, scopes, true);
38868
- if (setterToStateName.has(returnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
38869
- if (isSetterIdentifier(returnedValue.callee.name)) return true;
39224
+ const EXTERNAL_SYNC_DIRECT_IMPORT_SOURCES = new Map([
39225
+ ["fetch", new Set([
39226
+ "cross-fetch",
39227
+ "node-fetch",
39228
+ "undici"
39229
+ ])],
39230
+ ["ky", new Set(["ky"])],
39231
+ ["got", new Set(["got"])],
39232
+ ["wretch", new Set(["wretch"])],
39233
+ ["ofetch", new Set(["ofetch"])],
39234
+ ["setTimeout", new Set([
39235
+ "node:timers",
39236
+ "node:timers/promises",
39237
+ "timers",
39238
+ "timers/promises"
39239
+ ])],
39240
+ ["setInterval", new Set([
39241
+ "node:timers",
39242
+ "node:timers/promises",
39243
+ "timers",
39244
+ "timers/promises"
39245
+ ])]
39246
+ ]);
39247
+ const EXTERNAL_SYNC_DEFAULT_IMPORT_NAMES = new Map([
39248
+ ["axios", "axios"],
39249
+ ["cross-fetch", "fetch"],
39250
+ ["got", "got"],
39251
+ ["ky", "ky"],
39252
+ ["node-fetch", "fetch"],
39253
+ ["wretch", "wretch"]
39254
+ ]);
39255
+ const EXTERNAL_SYNC_HTTP_CLIENT_MODULE_SOURCES = new Set([
39256
+ "axios",
39257
+ "cross-fetch",
39258
+ "got",
39259
+ "ky",
39260
+ "node-fetch",
39261
+ "ofetch",
39262
+ "undici",
39263
+ "wretch"
39264
+ ]);
39265
+ const EXTERNAL_SYNC_GLOBAL_HTTP_CLIENT_NAMES = new Set([
39266
+ "axios",
39267
+ "got",
39268
+ "ky",
39269
+ "ofetch",
39270
+ "wretch"
39271
+ ]);
39272
+ const EXTERNAL_SYNC_HTTP_METHOD_NAMES = new Set([
39273
+ ...EXTERNAL_SYNC_AMBIGUOUS_HTTP_METHOD_NAMES,
39274
+ "fetch",
39275
+ "patch",
39276
+ "post",
39277
+ "put",
39278
+ "request"
39279
+ ]);
39280
+ const TANSTACK_QUERY_CLIENT_MODULE_SOURCES = new Set(["@tanstack/query-core", "@tanstack/react-query"]);
39281
+ const TANSTACK_QUERY_EXTERNAL_SYNC_METHOD_NAMES = new Set(["fetchQuery", "prefetchQuery"]);
39282
+ const EXTERNAL_SYNC_RESOURCE_CONSTRUCTOR_NAMES = new Set([
39283
+ ...EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS,
39284
+ ...SOCKET_CONSTRUCTOR_NAMES_REQUIRING_CLEANUP,
39285
+ "EventTarget",
39286
+ "XMLHttpRequest"
39287
+ ]);
39288
+ const getDestructuredBindingDepth = (bindingIdentifier) => {
39289
+ let bindingNode = bindingIdentifier;
39290
+ let depth = 0;
39291
+ while (true) {
39292
+ if (isNodeOfType(bindingNode.parent, "AssignmentPattern") && bindingNode.parent.left === bindingNode) bindingNode = bindingNode.parent;
39293
+ const property = bindingNode.parent;
39294
+ if (!property || !isNodeOfType(property, "Property") || property.value !== bindingNode || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return depth;
39295
+ depth += 1;
39296
+ bindingNode = property.parent;
39297
+ }
39298
+ };
39299
+ const isMutatedMemberExpression = (rawMemberExpression) => {
39300
+ let expression = findTransparentExpressionRoot(rawMemberExpression);
39301
+ while (expression.parent) {
39302
+ const parent = expression.parent;
39303
+ if (isNodeOfType(parent, "MemberExpression") && parent.object === expression) {
39304
+ expression = findTransparentExpressionRoot(parent);
39305
+ continue;
39306
+ }
39307
+ if (isNodeOfType(parent, "Property") && parent.value === expression && isNodeOfType(parent.parent, "ObjectPattern")) {
39308
+ expression = findTransparentExpressionRoot(parent);
39309
+ continue;
38870
39310
  }
38871
- return isCleanupReturn(returnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
39311
+ if (isNodeOfType(parent, "ObjectPattern") && parent.properties.some((property) => property === expression)) {
39312
+ expression = findTransparentExpressionRoot(parent);
39313
+ continue;
39314
+ }
39315
+ if (isNodeOfType(parent, "ArrayPattern") && parent.elements.some((element) => element === expression)) {
39316
+ expression = findTransparentExpressionRoot(parent);
39317
+ continue;
39318
+ }
39319
+ if (isNodeOfType(parent, "RestElement") && parent.argument === expression || isNodeOfType(parent, "AssignmentPattern") && parent.left === expression) {
39320
+ expression = findTransparentExpressionRoot(parent);
39321
+ continue;
39322
+ }
39323
+ break;
38872
39324
  }
38873
- if (isNodeOfType(returnedValue, "Identifier")) return true;
39325
+ const parent = expression.parent;
39326
+ return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expression || isNodeOfType(parent, "UpdateExpression") && parent.argument === expression || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expression || (isNodeOfType(parent, "ForInStatement") || isNodeOfType(parent, "ForOfStatement")) && parent.left === expression);
39327
+ };
39328
+ const isReactHookDependencyArgument = (expression, scopes) => {
39329
+ const call = expression.parent;
39330
+ return Boolean(isNodeOfType(call, "CallExpression") && call.arguments[1] === expression && isReactApiCall(call, HOOKS_WITH_DEPS, scopes, {
39331
+ allowGlobalReactNamespace: true,
39332
+ allowUnboundBareCalls: true,
39333
+ resolveNamedAliases: true
39334
+ }));
39335
+ };
39336
+ const isPlainDependencyObjectExpression = (expression) => isNodeOfType(expression, "ObjectExpression") && expression.properties.every((property) => isNodeOfType(property, "Property") && property.kind === "init" && !property.computed && !property.method);
39337
+ const getContainingReactDependencyValue = (rawExpression, containerKind) => {
39338
+ const expression = findTransparentExpressionRoot(rawExpression);
39339
+ const parent = expression.parent;
39340
+ if (isNodeOfType(parent, "ArrayExpression")) return {
39341
+ expression: parent,
39342
+ containerKind: "array"
39343
+ };
39344
+ if (containerKind === "array" && isNodeOfType(parent, "SpreadElement") && parent.argument === expression && isNodeOfType(parent.parent, "ArrayExpression")) return {
39345
+ expression: parent.parent,
39346
+ containerKind: "array"
39347
+ };
39348
+ if (isNodeOfType(parent, "Property") && (parent.value === expression || parent.shorthand && parent.key === expression) && parent.parent && isPlainDependencyObjectExpression(parent.parent)) return {
39349
+ expression: parent.parent,
39350
+ containerKind: "object"
39351
+ };
39352
+ return null;
39353
+ };
39354
+ const getFullArrayRestCopySymbol = (initializer, scopes) => {
39355
+ const declaration = initializer.parent;
39356
+ if (!isNodeOfType(declaration, "VariableDeclarator") || declaration.init !== initializer || !isNodeOfType(declaration.id, "ArrayPattern") || declaration.id.elements.length !== 1) return null;
39357
+ const restElement = declaration.id.elements[0];
39358
+ if (!isNodeOfType(restElement, "RestElement") || !isNodeOfType(restElement.argument, "Identifier")) return null;
39359
+ const symbol = scopes.symbolFor(restElement.argument);
39360
+ return symbol?.kind === "const" && symbol.declarationNode === declaration && symbol.references.every((reference) => reference.flag === "read") ? symbol : null;
39361
+ };
39362
+ const isReactDependencyArrayReference = (initialExpression, scopes) => {
39363
+ const pendingPaths = [{
39364
+ expression: initialExpression,
39365
+ containerKind: null
39366
+ }];
39367
+ const visitedExpressions = /* @__PURE__ */ new Set();
39368
+ let didFindReactDependencyArgument = false;
39369
+ while (pendingPaths.length > 0) {
39370
+ const pendingPath = pendingPaths.pop();
39371
+ if (!pendingPath) continue;
39372
+ const expression = findTransparentExpressionRoot(pendingPath.expression);
39373
+ if (visitedExpressions.has(expression)) continue;
39374
+ visitedExpressions.add(expression);
39375
+ if (pendingPath.containerKind === "array" && isReactHookDependencyArgument(expression, scopes)) {
39376
+ didFindReactDependencyArgument = true;
39377
+ continue;
39378
+ }
39379
+ const containingValue = getContainingReactDependencyValue(expression, pendingPath.containerKind);
39380
+ if (containingValue) {
39381
+ pendingPaths.push(containingValue);
39382
+ continue;
39383
+ }
39384
+ const declaration = expression.parent;
39385
+ if (isNodeOfType(declaration, "VariableDeclarator") && declaration.init === expression && isNodeOfType(declaration.id, "Identifier")) {
39386
+ const symbol = scopes.symbolFor(declaration.id);
39387
+ if (!symbol || getDirectUnreassignedInitializer(symbol) !== expression) return false;
39388
+ if (symbol.references.length === 0) return false;
39389
+ for (const reference of symbol.references) pendingPaths.push({
39390
+ expression: reference.identifier,
39391
+ containerKind: pendingPath.containerKind
39392
+ });
39393
+ continue;
39394
+ }
39395
+ if (pendingPath.containerKind === "array") {
39396
+ const restCopySymbol = getFullArrayRestCopySymbol(expression, scopes);
39397
+ if (restCopySymbol) {
39398
+ if (restCopySymbol.references.length === 0) return false;
39399
+ for (const reference of restCopySymbol.references) pendingPaths.push({
39400
+ expression: reference.identifier,
39401
+ containerKind: "array"
39402
+ });
39403
+ continue;
39404
+ }
39405
+ }
39406
+ return false;
39407
+ }
39408
+ return didFindReactDependencyArgument;
39409
+ };
39410
+ const hasSafeReceiverAliases = (rootSymbols, scopes) => {
39411
+ const pendingSymbols = [...rootSymbols];
39412
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
39413
+ while (pendingSymbols.length > 0) {
39414
+ const symbol = pendingSymbols.pop();
39415
+ if (!symbol || visitedSymbolIds.has(symbol.id)) continue;
39416
+ visitedSymbolIds.add(symbol.id);
39417
+ for (const reference of symbol.references) {
39418
+ const expression = findTransparentExpressionRoot(reference.identifier);
39419
+ const container = expression.parent;
39420
+ if (isReactDependencyArrayReference(expression, scopes)) continue;
39421
+ if (isNodeOfType(container, "MemberExpression") && container.object === expression) {
39422
+ if (isMutatedMemberExpression(container)) return false;
39423
+ continue;
39424
+ }
39425
+ if (isNodeOfType(container, "CallExpression") && container.callee === expression) continue;
39426
+ if (isNodeOfType(container, "VariableDeclarator") && container.init === expression && isNodeOfType(container.id, "ObjectPattern")) continue;
39427
+ if (isNodeOfType(container, "AssignmentExpression") && container.right === expression && isNodeOfType(container.left, "ObjectPattern")) continue;
39428
+ if (isNodeOfType(container, "VariableDeclarator") && container.init === expression && isNodeOfType(container.id, "Identifier")) {
39429
+ const aliasSymbol = scopes.symbolFor(container.id);
39430
+ const aliasInitializer = aliasSymbol && getDirectUnreassignedInitializer(aliasSymbol);
39431
+ if (!aliasSymbol || aliasInitializer !== expression) return false;
39432
+ pendingSymbols.push(aliasSymbol);
39433
+ continue;
39434
+ }
39435
+ return false;
39436
+ }
39437
+ }
39438
+ return true;
39439
+ };
39440
+ const hasProvenGlobalNamespaceReference = (expression, namespaceNames, scopes) => {
39441
+ for (const namespaceName of namespaceNames) if (isProvenGlobalNamespaceReference(expression, namespaceName, scopes)) return true;
38874
39442
  return false;
38875
39443
  };
38876
- const STORAGE_GLOBAL_NAMES = new Set(["localStorage", "sessionStorage"]);
38877
- const isBrowserStorageReceiver = (receiver) => {
38878
- if (!receiver) return false;
38879
- if (isNodeOfType(receiver, "Identifier")) return STORAGE_GLOBAL_NAMES.has(receiver.name);
38880
- if (isNodeOfType(receiver, "MemberExpression")) return isNodeOfType(receiver.property, "Identifier") && STORAGE_GLOBAL_NAMES.has(receiver.property.name);
39444
+ const getTypeScriptImportEqualsSource = (symbol) => {
39445
+ if (symbol.kind !== "ts-import-equals" || !isNodeOfType(symbol.declarationNode, "TSImportEqualsDeclaration")) return null;
39446
+ const moduleReference = symbol.declarationNode.moduleReference;
39447
+ if (!isNodeOfType(moduleReference, "TSExternalModuleReference") || !isNodeOfType(moduleReference.expression, "Literal")) return null;
39448
+ return typeof moduleReference.expression.value === "string" ? moduleReference.expression.value : null;
39449
+ };
39450
+ const getUnshadowedRequireSource = (rawExpression, scopes) => {
39451
+ let expression = stripParenExpression(rawExpression);
39452
+ while (isNodeOfType(expression, "MemberExpression")) expression = stripParenExpression(expression.object);
39453
+ if (!isNodeOfType(expression, "CallExpression") || !isNodeOfType(expression.callee, "Identifier") || expression.callee.name !== "require" || !scopes.isGlobalReference(expression.callee)) return null;
39454
+ return getRequireCallSource(rawExpression);
39455
+ };
39456
+ const resolveExternalModuleBinding = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set(), ignoreCommonJsMutation = false) => {
39457
+ let expression = stripParenExpression(rawExpression);
39458
+ let outermostExportedName = null;
39459
+ let exportedPropertyDepth = 0;
39460
+ let resolvedBinding = null;
39461
+ let isCommonJsModuleObject = false;
39462
+ const traversedSymbolIds = new Set(visitedSymbolIds);
39463
+ const commonJsModuleObjectSymbols = /* @__PURE__ */ new Set();
39464
+ while (true) {
39465
+ if (isNodeOfType(expression, "MemberExpression")) {
39466
+ const propertyName = getStaticPropertyName(expression);
39467
+ if (propertyName === null) return null;
39468
+ outermostExportedName ??= propertyName;
39469
+ exportedPropertyDepth += 1;
39470
+ expression = stripParenExpression(expression.object);
39471
+ continue;
39472
+ }
39473
+ const requireSource = getUnshadowedRequireSource(expression, scopes);
39474
+ if (requireSource !== null) {
39475
+ resolvedBinding = {
39476
+ source: requireSource,
39477
+ exportedName: null,
39478
+ isModuleObject: true
39479
+ };
39480
+ isCommonJsModuleObject = true;
39481
+ break;
39482
+ }
39483
+ if (!isNodeOfType(expression, "Identifier")) return null;
39484
+ const symbol = scopes.symbolFor(expression);
39485
+ if (!symbol || traversedSymbolIds.has(symbol.id)) return null;
39486
+ if (symbol.kind === "import") {
39487
+ const importDeclaration = symbol.declarationNode.parent;
39488
+ if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration") || typeof importDeclaration.source.value !== "string") return null;
39489
+ if (isNodeOfType(symbol.declarationNode, "ImportSpecifier")) resolvedBinding = {
39490
+ source: importDeclaration.source.value,
39491
+ exportedName: getImportedName(symbol.declarationNode) ?? null,
39492
+ isModuleObject: false
39493
+ };
39494
+ else if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) resolvedBinding = {
39495
+ source: importDeclaration.source.value,
39496
+ exportedName: "default",
39497
+ isModuleObject: false
39498
+ };
39499
+ else if (isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")) resolvedBinding = {
39500
+ source: importDeclaration.source.value,
39501
+ exportedName: null,
39502
+ isModuleObject: true
39503
+ };
39504
+ break;
39505
+ }
39506
+ const importEqualsSource = getTypeScriptImportEqualsSource(symbol);
39507
+ if (importEqualsSource !== null) {
39508
+ resolvedBinding = {
39509
+ source: importEqualsSource,
39510
+ exportedName: null,
39511
+ isModuleObject: true
39512
+ };
39513
+ isCommonJsModuleObject = true;
39514
+ commonJsModuleObjectSymbols.add(symbol);
39515
+ break;
39516
+ }
39517
+ const directInitializer = getDirectUnreassignedInitializer(symbol);
39518
+ const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
39519
+ const initializer = directInitializer ?? (destructuredPropertyName !== null && symbol.kind === "const" && isNodeOfType(symbol.declarationNode, "VariableDeclarator") ? symbol.declarationNode.init : null);
39520
+ if (!initializer) return null;
39521
+ traversedSymbolIds.add(symbol.id);
39522
+ outermostExportedName ??= destructuredPropertyName;
39523
+ if (destructuredPropertyName !== null) exportedPropertyDepth += getDestructuredBindingDepth(symbol.bindingIdentifier);
39524
+ const unwrappedInitializer = stripParenExpression(initializer);
39525
+ if (destructuredPropertyName === null && !isNodeOfType(unwrappedInitializer, "MemberExpression")) commonJsModuleObjectSymbols.add(symbol);
39526
+ expression = unwrappedInitializer;
39527
+ }
39528
+ if (!resolvedBinding) return null;
39529
+ if (!ignoreCommonJsMutation && isCommonJsModuleObject && !hasSafeReceiverAliases(commonJsModuleObjectSymbols, scopes)) return null;
39530
+ if (outermostExportedName === null) return resolvedBinding;
39531
+ if (!resolvedBinding.isModuleObject || exportedPropertyDepth !== 1) return null;
39532
+ return {
39533
+ source: resolvedBinding.source,
39534
+ exportedName: outermostExportedName,
39535
+ isModuleObject: false
39536
+ };
39537
+ };
39538
+ const getCanonicalExternalSyncExportName = (binding) => {
39539
+ if (binding.exportedName && binding.exportedName !== "default") return binding.exportedName;
39540
+ return EXTERNAL_SYNC_DEFAULT_IMPORT_NAMES.get(binding.source) ?? null;
39541
+ };
39542
+ const isKnownExternalSyncDirectModuleBinding = (binding) => {
39543
+ const exportedName = getCanonicalExternalSyncExportName(binding);
39544
+ return Boolean(exportedName && EXTERNAL_SYNC_DIRECT_IMPORT_SOURCES.get(exportedName)?.has(binding.source));
39545
+ };
39546
+ const DEFINITELY_NON_FUNCTION_GLOBAL_CALL_NAMES = new Set([
39547
+ "Array",
39548
+ "BigInt",
39549
+ "Boolean",
39550
+ "Date",
39551
+ "Number",
39552
+ "String",
39553
+ "Symbol"
39554
+ ]);
39555
+ const DEFINITELY_NON_FUNCTION_GLOBAL_CONSTRUCTOR_NAMES = new Set([
39556
+ "Array",
39557
+ "Boolean",
39558
+ "Date",
39559
+ "Map",
39560
+ "Number",
39561
+ "Promise",
39562
+ "RegExp",
39563
+ "Set",
39564
+ "String",
39565
+ "URL",
39566
+ "URLSearchParams",
39567
+ "WeakMap",
39568
+ "WeakSet"
39569
+ ]);
39570
+ const PROMISE_STATIC_RESULT_METHOD_NAMES = new Set([
39571
+ "all",
39572
+ "allSettled",
39573
+ "any",
39574
+ "race",
39575
+ "reject",
39576
+ "resolve",
39577
+ "withResolvers"
39578
+ ]);
39579
+ const returnsOnlyNonCleanupValues = (functionNode, setterToStateName, scopes, visitedFunctions = /* @__PURE__ */ new Set()) => {
39580
+ if (!isFunctionLike$1(functionNode) || functionNode.async || functionNode.generator || visitedFunctions.has(functionNode)) return false;
39581
+ const nextVisitedFunctions = new Set(visitedFunctions).add(functionNode);
39582
+ const isNonCleanupValue = (returnValue) => {
39583
+ const expression = stripParenExpression(returnValue);
39584
+ if (isNodeOfType(expression, "Identifier") && expression.name === "undefined" && scopes.isGlobalReference(expression)) return true;
39585
+ if (isNodeOfType(expression, "Literal") || isNodeOfType(expression, "ArrayExpression") || isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "JSXElement") || isNodeOfType(expression, "JSXFragment") || isNodeOfType(expression, "TemplateLiteral") || isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return true;
39586
+ if (isNodeOfType(expression, "ConditionalExpression")) return isNonCleanupValue(expression.consequent) && isNonCleanupValue(expression.alternate);
39587
+ if (isNodeOfType(expression, "LogicalExpression")) return isNonCleanupValue(expression.left) && isNonCleanupValue(expression.right);
39588
+ if (isNodeOfType(expression, "SequenceExpression")) {
39589
+ const finalExpression = expression.expressions.at(-1);
39590
+ return Boolean(finalExpression && isNonCleanupValue(finalExpression));
39591
+ }
39592
+ if (isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && DEFINITELY_NON_FUNCTION_GLOBAL_CONSTRUCTOR_NAMES.has(expression.callee.name) && scopes.isGlobalReference(expression.callee)) return true;
39593
+ if (!isNodeOfType(expression, "CallExpression")) return false;
39594
+ if (isNodeOfType(expression.callee, "Identifier") && DEFINITELY_NON_FUNCTION_GLOBAL_CALL_NAMES.has(expression.callee.name) && scopes.isGlobalReference(expression.callee)) return true;
39595
+ if (isNodeOfType(expression.callee, "MemberExpression") && isNodeOfType(expression.callee.object, "Identifier") && expression.callee.object.name === "Promise" && scopes.isGlobalReference(expression.callee.object) && PROMISE_STATIC_RESULT_METHOD_NAMES.has(getStaticPropertyName(expression.callee) ?? "")) return true;
39596
+ if (isNodeOfType(expression.callee, "Identifier") && setterToStateName.has(expression.callee.name)) return true;
39597
+ const invokedFunction = resolveSynchronouslyInvokedFunction(expression.callee, scopes);
39598
+ return Boolean(invokedFunction && returnsOnlyNonCleanupValues(invokedFunction, setterToStateName, scopes, nextVisitedFunctions));
39599
+ };
39600
+ if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNonCleanupValue(functionNode.body);
39601
+ let returnsOnlyNonCleanup = true;
39602
+ walkAst(functionNode.body, (child) => {
39603
+ if (!returnsOnlyNonCleanup) return false;
39604
+ if (child !== functionNode.body && isFunctionLike$1(child)) return false;
39605
+ if (!isNodeOfType(child, "ReturnStatement")) return;
39606
+ if (child.argument && !isNonCleanupValue(child.argument)) {
39607
+ returnsOnlyNonCleanup = false;
39608
+ return false;
39609
+ }
39610
+ });
39611
+ return returnsOnlyNonCleanup;
39612
+ };
39613
+ const getResolvedFunctionCleanupReturnProof = (functionNode, scopes, visitedFunctions = /* @__PURE__ */ new Set()) => {
39614
+ if (!isFunctionLike$1(functionNode) || functionNode.async || functionNode.generator || visitedFunctions.has(functionNode)) return {
39615
+ hasCleanup: false,
39616
+ isValid: false
39617
+ };
39618
+ const nextVisitedFunctions = new Set(visitedFunctions).add(functionNode);
39619
+ const getCleanupReturnProof = (returnValue) => {
39620
+ const expression = stripParenExpression(returnValue);
39621
+ if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression")) return {
39622
+ hasCleanup: true,
39623
+ isValid: true
39624
+ };
39625
+ if (isNodeOfType(expression, "Identifier")) {
39626
+ if (expression.name === "undefined" && scopes.isGlobalReference(expression)) return {
39627
+ hasCleanup: false,
39628
+ isValid: true
39629
+ };
39630
+ const localFunction = resolveExactLocalFunction(expression, scopes);
39631
+ return {
39632
+ hasCleanup: Boolean(localFunction),
39633
+ isValid: Boolean(localFunction)
39634
+ };
39635
+ }
39636
+ if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return {
39637
+ hasCleanup: false,
39638
+ isValid: true
39639
+ };
39640
+ if (isNodeOfType(expression, "ConditionalExpression")) {
39641
+ const consequentProof = getCleanupReturnProof(expression.consequent);
39642
+ const alternateProof = getCleanupReturnProof(expression.alternate);
39643
+ return {
39644
+ hasCleanup: consequentProof.hasCleanup || alternateProof.hasCleanup,
39645
+ isValid: consequentProof.isValid && alternateProof.isValid
39646
+ };
39647
+ }
39648
+ if (isNodeOfType(expression, "LogicalExpression")) {
39649
+ const leftProof = getCleanupReturnProof(expression.left);
39650
+ const rightProof = getCleanupReturnProof(expression.right);
39651
+ return {
39652
+ hasCleanup: leftProof.hasCleanup || rightProof.hasCleanup,
39653
+ isValid: leftProof.isValid && rightProof.isValid
39654
+ };
39655
+ }
39656
+ if (isNodeOfType(expression, "SequenceExpression")) {
39657
+ const finalExpression = expression.expressions.at(-1);
39658
+ return finalExpression ? getCleanupReturnProof(finalExpression) : {
39659
+ hasCleanup: false,
39660
+ isValid: false
39661
+ };
39662
+ }
39663
+ if (!isNodeOfType(expression, "CallExpression")) return {
39664
+ hasCleanup: false,
39665
+ isValid: false
39666
+ };
39667
+ const invokedFunction = resolveSynchronouslyInvokedFunction(expression.callee, scopes);
39668
+ return invokedFunction ? getResolvedFunctionCleanupReturnProof(invokedFunction, scopes, nextVisitedFunctions) : {
39669
+ hasCleanup: false,
39670
+ isValid: false
39671
+ };
39672
+ };
39673
+ if (!isNodeOfType(functionNode.body, "BlockStatement")) return getCleanupReturnProof(functionNode.body);
39674
+ let cleanupReturnProof = {
39675
+ hasCleanup: false,
39676
+ isValid: true
39677
+ };
39678
+ walkAst(functionNode.body, (child) => {
39679
+ if (!cleanupReturnProof.isValid) return false;
39680
+ if (child !== functionNode.body && isFunctionLike$1(child)) return false;
39681
+ if (!isNodeOfType(child, "ReturnStatement")) return;
39682
+ if (!child.argument) return;
39683
+ const returnProof = getCleanupReturnProof(child.argument);
39684
+ if (!returnProof.isValid) {
39685
+ cleanupReturnProof = {
39686
+ hasCleanup: false,
39687
+ isValid: false
39688
+ };
39689
+ return false;
39690
+ }
39691
+ cleanupReturnProof.hasCleanup ||= returnProof.hasCleanup;
39692
+ });
39693
+ return cleanupReturnProof;
39694
+ };
39695
+ const isFunctionShapedReturn = (returnedValue, setterToStateName, setterSymbolIdToStateName, scopes, isExplicitReturnStatement) => {
39696
+ const unwrappedReturnedValue = stripParenExpression(returnedValue);
39697
+ if (isNodeOfType(unwrappedReturnedValue, "ArrowFunctionExpression") || isNodeOfType(unwrappedReturnedValue, "FunctionExpression")) return true;
39698
+ if (isNodeOfType(unwrappedReturnedValue, "CallExpression")) {
39699
+ if (isNodeOfType(unwrappedReturnedValue.callee, "Identifier")) {
39700
+ const setterSymbol = resolveConstIdentifierAlias(unwrappedReturnedValue.callee, scopes, true);
39701
+ if (setterToStateName.has(unwrappedReturnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
39702
+ }
39703
+ const invokedFunction = resolveSynchronouslyInvokedFunction(unwrappedReturnedValue.callee, scopes);
39704
+ if (invokedFunction && returnsOnlyNonCleanupValues(invokedFunction, setterToStateName, scopes)) return false;
39705
+ if (invokedFunction) {
39706
+ const cleanupReturnProof = getResolvedFunctionCleanupReturnProof(invokedFunction, scopes);
39707
+ if (cleanupReturnProof.isValid) return cleanupReturnProof.hasCleanup;
39708
+ }
39709
+ if (!invokedFunction && isNodeOfType(unwrappedReturnedValue.callee, "Identifier") && isSetterIdentifier(unwrappedReturnedValue.callee.name)) return true;
39710
+ return isCleanupReturn(unwrappedReturnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
39711
+ }
39712
+ if (isNodeOfType(unwrappedReturnedValue, "Identifier")) return true;
38881
39713
  return false;
38882
39714
  };
39715
+ const STORAGE_GLOBAL_NAMES = ["localStorage", "sessionStorage"];
39716
+ const isBrowserStorageReceiver = (receiver, scopes) => Boolean(receiver && STORAGE_GLOBAL_NAMES.some((storageName) => isProvenGlobalNamespaceReference(receiver, storageName, scopes)));
38883
39717
  const STORAGE_HOOK_PATTERN = /^use\w*Storage/i;
38884
39718
  const collectStorageHookSetterNames = (componentBody) => {
38885
39719
  const setterNames = /* @__PURE__ */ new Set();
@@ -38911,23 +39745,16 @@ const callsOpaqueExternalSetter = (analysisFunctions, setterToStateName) => {
38911
39745
  });
38912
39746
  return didFindOpaqueSetterCall;
38913
39747
  };
38914
- const isReactRefCall = (expression, scopes) => isNodeOfType(expression, "CallExpression") && (isReactApiCall(expression, "useRef", scopes, {
39748
+ const isReactUseRefCall = (expression, scopes) => isNodeOfType(expression, "CallExpression") && isReactApiCall(expression, "useRef", scopes, {
38915
39749
  allowGlobalReactNamespace: true,
38916
39750
  allowUnboundBareCalls: true,
38917
39751
  resolveNamedAliases: true
38918
- }) || isReactApiCall(expression, "createRef", scopes, {
39752
+ });
39753
+ const isReactRefCall = (expression, scopes) => isReactUseRefCall(expression, scopes) || isNodeOfType(expression, "CallExpression") && isReactApiCall(expression, "createRef", scopes, {
38919
39754
  allowGlobalReactNamespace: true,
38920
39755
  allowUnboundBareCalls: true,
38921
39756
  resolveNamedAliases: true
38922
- }));
38923
- const getDirectReactRefSymbol = (rawExpression, scopes) => {
38924
- const expression = stripParenExpression(rawExpression);
38925
- if (!isNodeOfType(expression, "Identifier")) return null;
38926
- const symbol = scopes.symbolFor(expression);
38927
- if (!symbol) return null;
38928
- const initializer = getDirectUnreassignedInitializer(symbol);
38929
- return initializer && isReactRefCall(stripParenExpression(initializer), scopes) ? symbol : null;
38930
- };
39757
+ });
38931
39758
  const isReactNativeJsxElement = (openingElement, scopes) => {
38932
39759
  if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
38933
39760
  const symbol = scopes.symbolFor(openingElement.name);
@@ -38935,19 +39762,33 @@ const isReactNativeJsxElement = (openingElement, scopes) => {
38935
39762
  return Boolean(symbol?.kind === "import" && importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react-native");
38936
39763
  };
38937
39764
  const isDirectHostJsxRef = (symbol, scopes) => {
38938
- let hostRefCount = 0;
38939
- for (const reference of symbol.references) {
38940
- const expression = findTransparentExpressionRoot(reference.identifier);
38941
- const container = expression.parent;
38942
- if (isNodeOfType(container, "MemberExpression") && container.object === expression && getStaticPropertyName(container) === "current") continue;
38943
- if (!container || !isNodeOfType(container, "JSXExpressionContainer") || container.expression !== expression) return false;
38944
- const attribute = container.parent;
38945
- if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
38946
- const openingElement = attribute.parent;
38947
- if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || !isProvenIntrinsicJsxElement(openingElement, scopes) && !isReactNativeJsxElement(openingElement, scopes)) return false;
38948
- hostRefCount += 1;
38949
- }
38950
- return hostRefCount > 0;
39765
+ let didFindHostRef = false;
39766
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
39767
+ const pendingSymbols = [symbol];
39768
+ while (pendingSymbols.length > 0) {
39769
+ const currentSymbol = pendingSymbols.pop();
39770
+ if (!currentSymbol || visitedSymbolIds.has(currentSymbol.id)) continue;
39771
+ visitedSymbolIds.add(currentSymbol.id);
39772
+ for (const reference of currentSymbol.references) {
39773
+ const expression = findTransparentExpressionRoot(reference.identifier);
39774
+ const container = expression.parent;
39775
+ if (isNodeOfType(container, "MemberExpression") && container.object === expression && getStaticPropertyName(container) === "current") continue;
39776
+ if (isNodeOfType(container, "VariableDeclarator") && container.init === expression && isNodeOfType(container.id, "Identifier")) {
39777
+ const aliasSymbol = scopes.symbolFor(container.id);
39778
+ const aliasInitializer = aliasSymbol && getDirectUnreassignedInitializer(aliasSymbol);
39779
+ if (!aliasSymbol || aliasInitializer !== expression) return false;
39780
+ pendingSymbols.push(aliasSymbol);
39781
+ continue;
39782
+ }
39783
+ if (!container || !isNodeOfType(container, "JSXExpressionContainer") || container.expression !== expression) return false;
39784
+ const attribute = container.parent;
39785
+ if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
39786
+ const openingElement = attribute.parent;
39787
+ if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || !isProvenIntrinsicJsxElement(openingElement, scopes) && !isReactNativeJsxElement(openingElement, scopes)) return false;
39788
+ didFindHostRef = true;
39789
+ }
39790
+ }
39791
+ return didFindHostRef;
38951
39792
  };
38952
39793
  const isIntrinsicRefCallbackParameter = (expression, scopes) => {
38953
39794
  const identifier = stripParenExpression(expression);
@@ -38957,7 +39798,18 @@ const isIntrinsicRefCallbackParameter = (expression, scopes) => {
38957
39798
  const rawFirstParameter = callback.params?.[0];
38958
39799
  const firstParameter = isNodeOfType(rawFirstParameter, "AssignmentPattern") ? rawFirstParameter.left : rawFirstParameter;
38959
39800
  const symbol = scopes.symbolFor(identifier);
38960
- return Boolean(firstParameter && symbol?.bindingIdentifier === firstParameter);
39801
+ return Boolean(firstParameter && symbol?.bindingIdentifier === firstParameter && symbol.references.every((reference) => {
39802
+ if (reference.flag !== "read") return false;
39803
+ let referenceRoot = reference.identifier;
39804
+ while (referenceRoot.parent) {
39805
+ const parent = referenceRoot.parent;
39806
+ if (isNodeOfType(parent, "AssignmentExpression")) return parent.left !== referenceRoot;
39807
+ if (isNodeOfType(parent, "UpdateExpression")) return parent.argument !== referenceRoot;
39808
+ if (isNodeOfType(parent, "ForInStatement") || isNodeOfType(parent, "ForOfStatement")) return parent.left !== referenceRoot;
39809
+ referenceRoot = parent;
39810
+ }
39811
+ return true;
39812
+ }));
38961
39813
  };
38962
39814
  const getDirectReactRefCall = (symbol, scopes) => {
38963
39815
  const initializer = getDirectUnreassignedInitializer(symbol);
@@ -38965,15 +39817,38 @@ const getDirectReactRefCall = (symbol, scopes) => {
38965
39817
  const expression = stripParenExpression(initializer);
38966
39818
  return isNodeOfType(expression, "CallExpression") && isReactRefCall(expression, scopes) ? expression : null;
38967
39819
  };
39820
+ const isEmptyGlobalMapConstruction = (rawExpression, scopes) => {
39821
+ const expression = stripParenExpression(rawExpression);
39822
+ return isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && expression.callee.name === "Map" && scopes.isGlobalReference(expression.callee) && expression.arguments.length === 0;
39823
+ };
39824
+ const hasEmptyRefSentinelInitializer = (refCall, scopes) => {
39825
+ if (!isReactUseRefCall(refCall, scopes) || refCall.arguments.length > 1) return false;
39826
+ const [initialValue] = refCall.arguments;
39827
+ return !initialValue || isNodeOfType(initialValue, "Literal") && initialValue.value === null || isNodeOfType(initialValue, "Identifier") && initialValue.name === "undefined" && scopes.isGlobalReference(initialValue);
39828
+ };
39829
+ const isDirectLazyEmptyMapInitialization = (currentExpression, symbol, scopes) => {
39830
+ const assignment = currentExpression.parent;
39831
+ if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.left !== currentExpression || assignment.operator !== "??=" || !isEmptyGlobalMapConstruction(assignment.right, scopes)) return false;
39832
+ const refOwner = findEnclosingFunction$1(symbol.bindingIdentifier);
39833
+ return refOwner !== null && findEnclosingFunction$1(assignment) === refOwner;
39834
+ };
38968
39835
  const storesOnlyIntrinsicRefCallbackValues = (symbol, scopes) => {
38969
- const initialValue = getDirectReactRefCall(symbol, scopes)?.arguments?.[0];
38970
- if (!initialValue || !isNodeOfType(initialValue, "NewExpression") || !isNodeOfType(initialValue.callee, "Identifier") || initialValue.callee.name !== "Map" || !scopes.isGlobalReference(initialValue.callee) || initialValue.arguments.length !== 0) return false;
39836
+ const refCall = getDirectReactRefCall(symbol, scopes);
39837
+ const initialValue = refCall?.arguments[0];
39838
+ const hasDirectEmptyMapInitializer = Boolean(initialValue && isEmptyGlobalMapConstruction(initialValue, scopes));
39839
+ const hasLazyEmptyMapInitializer = Boolean(refCall && hasEmptyRefSentinelInitializer(refCall, scopes));
39840
+ if (!hasDirectEmptyMapInitializer && !hasLazyEmptyMapInitializer) return false;
38971
39841
  let intrinsicValueWriteCount = 0;
39842
+ let lazyEmptyMapInitializationCount = 0;
38972
39843
  for (const reference of symbol.references) {
38973
39844
  const identifier = findTransparentExpressionRoot(reference.identifier);
38974
39845
  const currentMember = identifier.parent;
38975
39846
  if (!isNodeOfType(currentMember, "MemberExpression") || currentMember.object !== identifier || getStaticPropertyName(currentMember) !== "current") return false;
38976
39847
  const currentExpression = findTransparentExpressionRoot(currentMember);
39848
+ if (hasLazyEmptyMapInitializer && isDirectLazyEmptyMapInitialization(currentExpression, symbol, scopes)) {
39849
+ lazyEmptyMapInitializationCount += 1;
39850
+ continue;
39851
+ }
38977
39852
  const methodMember = currentExpression.parent;
38978
39853
  if (!isNodeOfType(methodMember, "MemberExpression") || methodMember.object !== currentExpression) return false;
38979
39854
  const methodName = getStaticPropertyName(methodMember);
@@ -38986,7 +39861,7 @@ const storesOnlyIntrinsicRefCallbackValues = (symbol, scopes) => {
38986
39861
  if (!storedValue || isNodeOfType(storedValue, "SpreadElement") || !isIntrinsicRefCallbackParameter(storedValue, scopes)) return false;
38987
39862
  intrinsicValueWriteCount += 1;
38988
39863
  }
38989
- return intrinsicValueWriteCount > 0;
39864
+ return intrinsicValueWriteCount > 0 && (hasDirectEmptyMapInitializer || lazyEmptyMapInitializationCount === 1);
38990
39865
  };
38991
39866
  const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollectionValue = false, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
38992
39867
  const expression = stripParenExpression(rawExpression);
@@ -39000,7 +39875,11 @@ const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollecti
39000
39875
  }
39001
39876
  if (isNodeOfType(expression, "MemberExpression")) {
39002
39877
  if (getStaticPropertyName(expression) === "current") {
39003
- const symbol = getDirectReactRefSymbol(expression.object, scopes);
39878
+ const symbol = resolveReactRefSymbol(expression, scopes, {
39879
+ allowUnboundBareCalls: true,
39880
+ includeCreateRef: true,
39881
+ resolveNamedAliases: true
39882
+ });
39004
39883
  return Boolean(symbol && (isDirectHostJsxRef(symbol, scopes) || didReadCollectionValue && storesOnlyIntrinsicRefCallbackValues(symbol, scopes)));
39005
39884
  }
39006
39885
  return isDerivedFromProvenDomRefCurrent(expression.object, scopes, didReadCollectionValue, visitedSymbolIds);
@@ -39018,19 +39897,80 @@ const isCommittedDomSyncNode = (node, scopes) => {
39018
39897
  if (propertyName === null || !EXTERNAL_SYNC_DOM_MEMBER_METHOD_NAMES.has(propertyName)) return false;
39019
39898
  return isDerivedFromProvenDomRefCurrent(callee.object, scopes) || isProvenBrowserApiReceiver(callee.object, "dom-event-target", scopes);
39020
39899
  };
39021
- const isExternalSyncNode = (node) => {
39022
- if (isNodeOfType(node, "NewExpression")) return isNodeOfType(node.callee, "Identifier") && EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS.has(node.callee.name);
39023
- if (isNodeOfType(node, "AssignmentExpression")) return isNodeOfType(node.left, "MemberExpression") && isNodeOfType(node.left.property, "Identifier") && node.left.property.name === "current";
39900
+ const isProvenHttpClientReceiver = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
39901
+ const expression = stripParenExpression(rawExpression);
39902
+ if (isNodeOfType(expression, "Identifier")) {
39903
+ const receiverSymbol = scopes.symbolFor(expression);
39904
+ if (receiverSymbol && !hasSafeReceiverAliases(new Set([receiverSymbol]), scopes)) return false;
39905
+ }
39906
+ if (hasProvenGlobalNamespaceReference(expression, EXTERNAL_SYNC_GLOBAL_HTTP_CLIENT_NAMES, scopes)) return true;
39907
+ const moduleBinding = resolveExternalModuleBinding(expression, scopes);
39908
+ if (moduleBinding && EXTERNAL_SYNC_HTTP_CLIENT_MODULE_SOURCES.has(moduleBinding.source) && (moduleBinding.isModuleObject || moduleBinding.exportedName === "default" && EXTERNAL_SYNC_DEFAULT_IMPORT_NAMES.has(moduleBinding.source) || isKnownExternalSyncDirectModuleBinding(moduleBinding))) return true;
39909
+ if (isNodeOfType(expression, "CallExpression")) {
39910
+ const callee = stripParenExpression(expression.callee);
39911
+ return Boolean(isNodeOfType(callee, "MemberExpression") && getStaticPropertyName(callee) === "create" && isProvenHttpClientReceiver(callee.object, scopes, visitedSymbolIds));
39912
+ }
39913
+ if (!isNodeOfType(expression, "Identifier")) return false;
39914
+ const symbol = scopes.symbolFor(expression);
39915
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
39916
+ if (resolveExternalModuleBinding(expression, scopes, /* @__PURE__ */ new Set(), true)) return false;
39917
+ const initializer = getDirectUnreassignedInitializer(symbol);
39918
+ return Boolean(initializer && isProvenHttpClientReceiver(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id)));
39919
+ };
39920
+ const isProvenTanStackQueryClientReceiver = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
39921
+ const expression = stripParenExpression(rawExpression);
39922
+ if (isNodeOfType(expression, "Identifier")) {
39923
+ const receiverSymbol = scopes.symbolFor(expression);
39924
+ if (receiverSymbol && !hasSafeReceiverAliases(new Set([receiverSymbol]), scopes)) return false;
39925
+ }
39926
+ if (isNodeOfType(expression, "CallExpression")) {
39927
+ const binding = resolveExternalModuleBinding(expression.callee, scopes);
39928
+ return Boolean(binding && binding.source === "@tanstack/react-query" && binding.exportedName === "useQueryClient");
39929
+ }
39930
+ if (isNodeOfType(expression, "NewExpression")) {
39931
+ const binding = resolveExternalModuleBinding(expression.callee, scopes);
39932
+ return Boolean(binding && TANSTACK_QUERY_CLIENT_MODULE_SOURCES.has(binding.source) && binding.exportedName === "QueryClient");
39933
+ }
39934
+ if (!isNodeOfType(expression, "Identifier")) return false;
39935
+ const symbol = scopes.symbolFor(expression);
39936
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
39937
+ const initializer = getDirectUnreassignedInitializer(symbol);
39938
+ return Boolean(initializer && isProvenTanStackQueryClientReceiver(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id)));
39939
+ };
39940
+ const isProvenExternalResourceReceiver = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
39941
+ const expression = stripParenExpression(rawExpression);
39942
+ if (isDerivedFromProvenDomRefCurrent(expression, scopes) || isProvenBrowserApiReceiver(expression, "dom-event-target", scopes) || isProvenBrowserApiReceiver(expression, "xml-http-request", scopes) || isProvenHttpClientReceiver(expression, scopes)) return true;
39943
+ if (isNodeOfType(expression, "NewExpression")) return hasProvenGlobalNamespaceReference(expression.callee, EXTERNAL_SYNC_RESOURCE_CONSTRUCTOR_NAMES, scopes);
39944
+ if (!isNodeOfType(expression, "Identifier")) return false;
39945
+ const symbol = scopes.symbolFor(expression);
39946
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
39947
+ const initializer = getDirectUnreassignedInitializer(symbol);
39948
+ return Boolean(initializer && isProvenExternalResourceReceiver(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id)));
39949
+ };
39950
+ const isProvenExternalSyncDirectCallee = (callee, scopes) => {
39951
+ if (hasProvenGlobalNamespaceReference(callee, EXTERNAL_SYNC_DIRECT_CALLEE_NAMES, scopes)) return true;
39952
+ const moduleBinding = resolveExternalModuleBinding(callee, scopes);
39953
+ return Boolean(moduleBinding && isKnownExternalSyncDirectModuleBinding(moduleBinding));
39954
+ };
39955
+ const isExternalSyncNode = (node, scopes) => {
39956
+ if (isNodeOfType(node, "NewExpression")) return hasProvenGlobalNamespaceReference(node.callee, EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS, scopes);
39957
+ if (isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression")) {
39958
+ const mutationTarget = isNodeOfType(node, "AssignmentExpression") ? node.left : node.argument;
39959
+ return isNodeOfType(mutationTarget, "MemberExpression") && getStaticPropertyName(mutationTarget) === "current" && Boolean(resolveReactRefSymbol(mutationTarget, scopes, {
39960
+ allowUnboundBareCalls: true,
39961
+ includeCreateRef: true,
39962
+ resolveNamedAliases: true
39963
+ }));
39964
+ }
39024
39965
  if (!isNodeOfType(node, "CallExpression")) return false;
39025
- if (isNodeOfType(node.callee, "Identifier")) return EXTERNAL_SYNC_DIRECT_CALLEE_NAMES.has(node.callee.name);
39966
+ if (isProvenExternalSyncDirectCallee(node.callee, scopes)) return true;
39026
39967
  if (!isNodeOfType(node.callee, "MemberExpression")) return false;
39027
39968
  const propertyName = getStaticPropertyName(node.callee);
39028
39969
  if (propertyName === null) return false;
39029
- if (EXTERNAL_SYNC_MEMBER_METHOD_NAMES.has(propertyName)) return true;
39030
- if (isBrowserStorageReceiver(node.callee.object)) return true;
39031
- if (!EXTERNAL_SYNC_AMBIGUOUS_HTTP_METHOD_NAMES.has(propertyName)) return false;
39032
- const receiverRootName = getRootIdentifierName(node.callee.object);
39033
- return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
39970
+ if (isBrowserStorageReceiver(node.callee.object, scopes)) return true;
39971
+ if (TANSTACK_QUERY_EXTERNAL_SYNC_METHOD_NAMES.has(propertyName)) return isProvenTanStackQueryClientReceiver(node.callee.object, scopes);
39972
+ if (EXTERNAL_SYNC_HTTP_METHOD_NAMES.has(propertyName)) return isProvenHttpClientReceiver(node.callee.object, scopes);
39973
+ return EXTERNAL_SYNC_MEMBER_METHOD_NAMES.has(propertyName) && isProvenExternalResourceReceiver(node.callee.object, scopes);
39034
39974
  };
39035
39975
  const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, scopes, allowCommittedDomSync) => {
39036
39976
  if (!isFunctionLike$1(effectCallback)) return false;
@@ -39039,7 +39979,7 @@ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateNa
39039
39979
  } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, setterSymbolIdToStateName, scopes, true)) return true;
39040
39980
  let didFindExternalCall = false;
39041
39981
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
39042
- if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
39982
+ if (isExternalSyncNode(child, scopes) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
39043
39983
  });
39044
39984
  return didFindExternalCall;
39045
39985
  };
@@ -39061,11 +40001,11 @@ const noEffectChain = defineRule({
39061
40001
  setterToStateName.set(binding.setterName, binding.valueName);
39062
40002
  if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
39063
40003
  const stateIdentifier = binding.declarator.id.elements[0];
40004
+ const setterIdentifier = binding.declarator.id.elements[1];
39064
40005
  if (isNodeOfType(stateIdentifier, "Identifier")) {
39065
40006
  const stateSymbol = context.scopes.symbolFor(stateIdentifier);
39066
40007
  if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
39067
40008
  }
39068
- const setterIdentifier = binding.declarator.id.elements[1];
39069
40009
  if (isNodeOfType(setterIdentifier, "Identifier")) {
39070
40010
  const setterSymbol = context.scopes.symbolFor(setterIdentifier);
39071
40011
  if (setterSymbol) setterSymbolIdToStateName.set(setterSymbol.id, binding.valueName);
@@ -39078,10 +40018,11 @@ const noEffectChain = defineRule({
39078
40018
  const callback = getEffectCallback(effectCall, context.scopes);
39079
40019
  if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
39080
40020
  const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
39081
- const stateWrites = collectStateWritesInEffect(analysisFunctions, setterSymbolIdToStateName, context.scopes);
40021
+ const stateWrites = collectStateWritesInEffect(collectStateWriteAnalysisFunctions(callback, context.scopes, setterSymbolIdToStateName), setterSymbolIdToStateName, context.scopes);
39082
40022
  const writtenStateNames = new Set(stateWrites.keys());
39083
40023
  effectInfos.push({
39084
40024
  node: effectCall,
40025
+ callback,
39085
40026
  dependencyStateSymbolIds: collectDependencyStateSymbolIds(effectCall, stateSymbolIdSet, context.scopes),
39086
40027
  stateWrites,
39087
40028
  analysisFunctions,
@@ -39098,10 +40039,10 @@ const noEffectChain = defineRule({
39098
40039
  if (readerEffect.isExternalSync) continue;
39099
40040
  if (readerEffect.dependencyStateSymbolIds.size === 0) continue;
39100
40041
  let chainedStateName = null;
39101
- for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
40042
+ for (const writtenName of writerEffect.stateWrites.keys()) {
39102
40043
  const writtenStateSymbolId = stateSymbolIds.get(writtenName);
39103
40044
  if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
39104
- if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
40045
+ if (!canStateWriteReachReaderWork(writtenName, writerEffect, readerEffect, stateSymbolIds, context.scopes)) continue;
39105
40046
  chainedStateName = writtenName;
39106
40047
  break;
39107
40048
  }
@@ -39490,7 +40431,7 @@ const noEffectEventInDeps = defineRule({
39490
40431
  title: "Effect Event listed in effect deps",
39491
40432
  tags: ["test-noise"],
39492
40433
  severity: "error",
39493
- recommendation: "Call the useEffectEvent function inside the effect body and don't list it in the deps. It changes on every render on purpose.",
40434
+ recommendation: "Call the useEffectEvent function inside the effect body and don't list it in the deps. It's non-reactive on purpose, so it must be omitted.",
39494
40435
  create: (context) => {
39495
40436
  const componentBindings = createComponentBindingStackTracker({ onVariableDeclarator: (declaratorNode) => {
39496
40437
  if (!isNodeOfType(declaratorNode, "VariableDeclarator")) return;
@@ -39512,7 +40453,7 @@ const noEffectEventInDeps = defineRule({
39512
40453
  if (!isNodeOfType(element, "Identifier")) continue;
39513
40454
  if (componentBindings.isBoundName(element.name)) context.report({
39514
40455
  node: element,
39515
- message: `Listing "${element.name}" in the deps re-runs your effect every render & defeats useEffectEvent.`
40456
+ message: `Listing "${element.name}" in the deps defeats useEffectEvent — Effect Events are non-reactive and must be omitted from deps.`
39516
40457
  });
39517
40458
  }
39518
40459
  }
@@ -39555,6 +40496,7 @@ const noEffectWithFreshDeps = defineRule({
39555
40496
  title: "Effect dependency recreated every render",
39556
40497
  severity: "error",
39557
40498
  category: "State & Effects",
40499
+ disabledWhen: ["react-compiler"],
39558
40500
  recommendation: "Move the value inside the hook body and depend on its simple inputs instead, or wrap it in useMemo / useCallback so it stays the same between renders.",
39559
40501
  create: (context) => ({ CallExpression(node) {
39560
40502
  for (const finding of findForwardedFreshHookDependencies(node, context, EFFECT_HOOK_NAMES$1)) context.report({
@@ -42495,14 +43437,13 @@ const noInlineExhaustiveStyle = defineRule({
42495
43437
  });
42496
43438
  //#endregion
42497
43439
  //#region src/plugin/rules/performance/no-inline-prop-on-memo-component.ts
42498
- const isMemoCall = (node) => {
43440
+ const MEMO_CALLEE_NAMES = new Set(["memo", "React.memo"]);
43441
+ const isMemoCall = (node) => isNodeOfType(node, "CallExpression") && MEMO_CALLEE_NAMES.has(flattenCalleeName(node.callee) ?? "");
43442
+ const hasCustomComparator = (node, scopes) => {
42499
43443
  if (!isNodeOfType(node, "CallExpression")) return false;
42500
- if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "memo") return true;
42501
- if (isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && node.callee.object.name === "React" && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "memo") return true;
42502
- return false;
43444
+ const comparator = node.arguments?.[1];
43445
+ return comparator ? !isIdentitySensitiveMemoComparator(comparator, scopes) : false;
42503
43446
  };
42504
- const isDefaultEquivalentComparator = (comparator) => isNodeOfType(comparator, "Identifier") && (comparator.name === "undefined" || comparator.name === "shallowEqual");
42505
- const hasCustomComparator = (node) => isNodeOfType(node, "CallExpression") && (node.arguments?.length ?? 0) >= 2 && !isDefaultEquivalentComparator(node.arguments?.[1]);
42506
43447
  const isInlineReference = (node, scopes) => {
42507
43448
  const referenceNode = unwrapObjectIntegrityExpression(node, scopes);
42508
43449
  if (isNodeOfType(referenceNode, "ArrowFunctionExpression") || isNodeOfType(referenceNode, "FunctionExpression") || isNodeOfType(referenceNode, "CallExpression") && isNodeOfType(referenceNode.callee, "MemberExpression") && isNodeOfType(referenceNode.callee.property, "Identifier") && referenceNode.callee.property.name === "bind") return "functions";
@@ -42516,16 +43457,17 @@ const noInlinePropOnMemoComponent = defineRule({
42516
43457
  title: "Inline prop defeats memo()",
42517
43458
  tags: ["test-noise"],
42518
43459
  severity: "warn",
43460
+ disabledWhen: ["react-compiler"],
42519
43461
  recommendation: "Move the inline `() => ...` / `[]` / `{}` to a stable value with useMemo, useCallback, or module scope, so the memoized child stops redrawing on every parent render",
42520
43462
  create: (context) => {
42521
43463
  const memoizedComponentNames = /* @__PURE__ */ new Set();
42522
43464
  return {
42523
43465
  VariableDeclarator(node) {
42524
43466
  if (!isNodeOfType(node.id, "Identifier") || !node.init) return;
42525
- if (isMemoCall(node.init) && !hasCustomComparator(node.init)) memoizedComponentNames.add(node.id.name);
43467
+ if (isMemoCall(node.init) && !hasCustomComparator(node.init, context.scopes)) memoizedComponentNames.add(node.id.name);
42526
43468
  },
42527
43469
  ExportDefaultDeclaration(node) {
42528
- if (node.declaration && isNodeOfType(node.declaration, "CallExpression") && isMemoCall(node.declaration) && !hasCustomComparator(node.declaration)) {
43470
+ if (node.declaration && isNodeOfType(node.declaration, "CallExpression") && isMemoCall(node.declaration) && !hasCustomComparator(node.declaration, context.scopes)) {
42529
43471
  const innerArgument = node.declaration.arguments?.[0];
42530
43472
  if (isNodeOfType(innerArgument, "Identifier")) memoizedComponentNames.add(innerArgument.name);
42531
43473
  }
@@ -43885,16 +44827,6 @@ const noManyBooleanProps = defineRule({
43885
44827
  }
43886
44828
  });
43887
44829
  //#endregion
43888
- //#region src/plugin/utils/is-global-match-media-call.ts
43889
- const isGlobalMatchMediaCall = (node, scopes) => {
43890
- if (!isNodeOfType(node, "CallExpression")) return false;
43891
- const callee = stripParenExpression(node.callee);
43892
- if (isNodeOfType(callee, "Identifier")) return callee.name === "matchMedia" && scopes.isGlobalReference(callee);
43893
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "matchMedia") return false;
43894
- const receiver = stripParenExpression(callee.object);
43895
- return isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && scopes.isGlobalReference(receiver);
43896
- };
43897
- //#endregion
43898
44830
  //#region src/plugin/rules/performance/no-match-media-in-state-initializer.ts
43899
44831
  const REACT_USE_STATE_OPTIONS = {
43900
44832
  allowGlobalReactNamespace: true,
@@ -46284,6 +47216,103 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
46284
47216
  "useVisibility",
46285
47217
  "useWindowSize"
46286
47218
  ]);
47219
+ const EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES = new Set([
47220
+ "useMatchMedia",
47221
+ "useMediaQuery",
47222
+ "useVisibility"
47223
+ ]);
47224
+ const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
47225
+ const getImportedExternalSubscriptionHookName = (analysis, rawCallee) => {
47226
+ const callee = stripParenExpression(rawCallee);
47227
+ if (isNodeOfType(callee, "Identifier")) {
47228
+ const calleeRef = getRef(analysis, callee);
47229
+ if (!calleeRef || !isImportBindingRef(calleeRef)) return null;
47230
+ const importBinding = getImportBindingForName(callee, callee.name);
47231
+ if (!importBinding || importBinding.isNamespace) return null;
47232
+ if (importBinding.exportedName && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(importBinding.exportedName)) return importBinding.exportedName;
47233
+ return importBinding.exportedName === "default" && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name) ? callee.name : null;
47234
+ }
47235
+ if (!isNodeOfType(callee, "MemberExpression")) return null;
47236
+ const hookName = getStaticMemberPropertyName(callee);
47237
+ const namespaceIdentifier = stripParenExpression(callee.object);
47238
+ if (!hookName || !EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(hookName) || !isNodeOfType(namespaceIdentifier, "Identifier")) return null;
47239
+ const namespaceRef = getRef(analysis, namespaceIdentifier);
47240
+ if (!namespaceRef || !isImportBindingRef(namespaceRef)) return null;
47241
+ return getImportBindingForName(namespaceIdentifier, namespaceIdentifier.name)?.isNamespace ? hookName : null;
47242
+ };
47243
+ const isSafeExternalSubscriptionResultBinding = (bindingIdentifier, bindingPattern) => {
47244
+ let current = bindingIdentifier;
47245
+ let didCrossDestructuringBoundary = false;
47246
+ while (current !== bindingPattern) {
47247
+ const parent = current.parent;
47248
+ if (!parent || isNodeOfType(parent, "AssignmentPattern") || isNodeOfType(parent, "RestElement")) return false;
47249
+ if (isNodeOfType(parent, "Property")) {
47250
+ if (parent.value !== current) return false;
47251
+ didCrossDestructuringBoundary = true;
47252
+ }
47253
+ if (isNodeOfType(parent, "ArrayPattern")) didCrossDestructuringBoundary = true;
47254
+ current = parent;
47255
+ }
47256
+ return didCrossDestructuringBoundary;
47257
+ };
47258
+ const getVariablesDefinedByDeclarator = (analysis, declarator) => analysis.scopeManager.scopes.flatMap((scope) => scope.variables).filter((variable) => variable.defs.some((definition) => definition.node === declarator));
47259
+ const hasUnsafeExternalSubscriptionBindingUse = (analysis, variable, visitedVariables = /* @__PURE__ */ new Set()) => {
47260
+ if (visitedVariables.has(variable)) return false;
47261
+ visitedVariables.add(variable);
47262
+ return variable.references.some((candidateReference) => {
47263
+ if (candidateReference.init) return false;
47264
+ if (candidateReference.isWrite()) return true;
47265
+ let usageRoot = findTransparentExpressionRoot(candidateReference.identifier);
47266
+ while (isNodeOfType(usageRoot.parent, "MemberExpression") && usageRoot.parent.object === usageRoot) usageRoot = findTransparentExpressionRoot(usageRoot.parent);
47267
+ const usageParent = usageRoot.parent;
47268
+ if (isNodeOfType(usageParent, "AssignmentExpression") && usageParent.left === usageRoot || isNodeOfType(usageParent, "UpdateExpression") && usageParent.argument === usageRoot || isNodeOfType(usageParent, "UnaryExpression") && usageParent.operator === "delete" && usageParent.argument === usageRoot) return true;
47269
+ let usageAncestor = usageRoot.parent;
47270
+ while (usageAncestor && !isNodeOfType(usageAncestor, "VariableDeclarator") && !isFunctionLike$1(usageAncestor) && !isNodeOfType(usageAncestor, "Program")) usageAncestor = usageAncestor.parent;
47271
+ if (isNodeOfType(usageAncestor, "VariableDeclarator") && usageAncestor.init) {
47272
+ const aliasVariables = getVariablesDefinedByDeclarator(analysis, usageAncestor);
47273
+ return aliasVariables.length === 0 || aliasVariables.some((aliasVariable) => hasUnsafeExternalSubscriptionBindingUse(analysis, aliasVariable, visitedVariables));
47274
+ }
47275
+ return false;
47276
+ });
47277
+ };
47278
+ const hasOnlySafeExternalSubscriptionResultBindings = (analysis, declarator, variables = getVariablesDefinedByDeclarator(analysis, declarator)) => {
47279
+ return variables.length > 0 && variables.every((variable) => {
47280
+ const bindingIdentifier = variable.defs.find((candidateDefinition) => candidateDefinition.node === declarator)?.name;
47281
+ return Boolean(bindingIdentifier && isNodeOfType(bindingIdentifier, "Identifier") && isSafeExternalSubscriptionResultBinding(bindingIdentifier, declarator.id) && !hasUnsafeExternalSubscriptionBindingUse(analysis, variable));
47282
+ });
47283
+ };
47284
+ const hasImmutableExternalSubscriptionCallResult = (analysis, rawCallee, relatedRefs, allowWholeResult) => {
47285
+ const callee = stripParenExpression(rawCallee);
47286
+ const callExpression = callee.parent;
47287
+ if (!callExpression || !isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== callee) return false;
47288
+ const initializer = findTransparentExpressionRoot(callExpression);
47289
+ const declarator = initializer.parent;
47290
+ if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== initializer || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return false;
47291
+ const relatedVariables = getVariablesDefinedByDeclarator(analysis, declarator).filter((variable) => relatedRefs.some((relatedRef) => relatedRef.resolved === variable));
47292
+ if (relatedVariables.length === 0) return false;
47293
+ return isNodeOfType(declarator.id, "Identifier") ? allowWholeResult && relatedVariables.every((variable) => !hasUnsafeExternalSubscriptionBindingUse(analysis, variable)) : hasOnlySafeExternalSubscriptionResultBindings(analysis, declarator, relatedVariables);
47294
+ };
47295
+ const isImmutableImportedExternalSubscriptionHookCallee = (analysis, rawCallee, relatedRefs) => {
47296
+ const hookName = getImportedExternalSubscriptionHookName(analysis, rawCallee);
47297
+ return Boolean(hookName && hasImmutableExternalSubscriptionCallResult(analysis, rawCallee, relatedRefs, EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES.has(hookName)));
47298
+ };
47299
+ const isExternalSubscriptionHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
47300
+ const declarator = def.node;
47301
+ if (!isNodeOfType(declarator, "VariableDeclarator") || !declarator.init || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return false;
47302
+ const initializer = stripParenExpression(declarator.init);
47303
+ if (!isNodeOfType(initializer, "CallExpression")) return false;
47304
+ const hookName = getImportedExternalSubscriptionHookName(analysis, initializer.callee);
47305
+ if (!hookName) return false;
47306
+ if (isNodeOfType(declarator.id, "Identifier")) return EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES.has(hookName) && ref.resolved && !hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved);
47307
+ const bindingIdentifier = def.name;
47308
+ return isNodeOfType(bindingIdentifier, "Identifier") && isSafeExternalSubscriptionResultBinding(bindingIdentifier, declarator.id) && ref.resolved && !hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved);
47309
+ }));
47310
+ const isExternalSubscriptionHookResultArgument = (analysis, argument) => {
47311
+ const unwrappedArgument = stripParenExpression(argument);
47312
+ if (!isNodeOfType(unwrappedArgument, "Identifier")) return false;
47313
+ const argumentRef = getRef(analysis, unwrappedArgument);
47314
+ return Boolean(argumentRef && isExternalSubscriptionHookResultRef(analysis, argumentRef));
47315
+ };
46287
47316
  const isCallbackPropReference = (analysis, ref) => {
46288
47317
  if (!isProp(analysis, ref)) return false;
46289
47318
  const identifier = ref.identifier;
@@ -46316,8 +47345,9 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
46316
47345
  if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
46317
47346
  return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
46318
47347
  };
46319
- const getLocalHookExternalStateProof = (analysis, ref) => {
47348
+ const getLocalHookExternalStateProof = (analysis, ref, scopes) => {
46320
47349
  let hookFunction = resolveToFunction(ref);
47350
+ let didResolveThroughResultBinding = false;
46321
47351
  if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
46322
47352
  const definitionNode = definition.node;
46323
47353
  if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
@@ -46328,29 +47358,27 @@ const getLocalHookExternalStateProof = (analysis, ref) => {
46328
47358
  const calleeReference = getRef(analysis, callee);
46329
47359
  if (!calleeReference) continue;
46330
47360
  hookFunction = resolveToFunction(calleeReference);
46331
- if (hookFunction) break;
47361
+ if (hookFunction) {
47362
+ didResolveThroughResultBinding = true;
47363
+ break;
47364
+ }
46332
47365
  }
46333
47366
  if (!hookFunction) return null;
46334
- const returnedReferences = collectFunctionReturnStatements(hookFunction).flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
47367
+ if (didResolveThroughResultBinding && (!ref.resolved || hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved))) return false;
47368
+ if (isNodeOfType(hookFunction, "ArrowFunctionExpression") && !isNodeOfType(hookFunction.body, "BlockStatement") && isReactHookCall(stripParenExpression(hookFunction.body), "useSyncExternalStore", scopes)) return true;
47369
+ const returnStatements = collectFunctionReturnStatements(hookFunction);
47370
+ if (returnStatements.length > 0 && returnStatements.every((returnStatement) => returnStatement.argument && isReactHookCall(stripParenExpression(returnStatement.argument), "useSyncExternalStore", scopes))) return true;
47371
+ const returnedReferences = returnStatements.flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
46335
47372
  if (returnedReferences.length === 0) return null;
46336
47373
  return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
46337
47374
  };
46338
- const isExternalSubscriptionHookRef = (analysis, ref) => {
46339
- const identifier = ref.identifier;
46340
- if (!isNodeOfType(identifier, "Identifier")) return false;
46341
- const localHookProof = getLocalHookExternalStateProof(analysis, ref);
46342
- if (localHookProof !== null) return localHookProof;
46343
- if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
46344
- return Boolean(ref.resolved?.defs.some((def) => {
46345
- const node = def.node;
46346
- if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
46347
- const initializer = stripParenExpression(node.init);
46348
- if (!isNodeOfType(initializer, "CallExpression")) return false;
46349
- const callee = stripParenExpression(initializer.callee);
46350
- return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name);
46351
- }));
47375
+ const isImmutableLocalExternalStoreHookCallee = (analysis, rawCallee, relatedRefs, scopes) => {
47376
+ const callee = stripParenExpression(rawCallee);
47377
+ if (!isNodeOfType(callee, "Identifier")) return false;
47378
+ const calleeReference = getRef(analysis, callee);
47379
+ if (!calleeReference || getLocalHookExternalStateProof(analysis, calleeReference, scopes) !== true) return false;
47380
+ return hasImmutableExternalSubscriptionCallResult(analysis, callee, relatedRefs, true);
46352
47381
  };
46353
- const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
46354
47382
  const isCalleePosition = (identifier) => {
46355
47383
  const parent = identifier.parent;
46356
47384
  return Boolean(parent && (isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression")) && parent.callee === identifier);
@@ -46411,25 +47439,35 @@ const noPassDataToParent = defineRule({
46411
47439
  return getFunctionalUpdaterDataRefs(analysis, argument);
46412
47440
  }
46413
47441
  if (isHandlerBagArgument(analysis, argument)) return [];
47442
+ if (isExternalSubscriptionHookResultArgument(analysis, argument)) return [];
46414
47443
  if (isParentWiredHookResultArgument(analysis, argument)) return [];
46415
47444
  if (isNodeOfType(argument, "Identifier")) {
46416
47445
  const argumentRef = getRef(analysis, argument);
46417
47446
  if (argumentRef && resolveToFunction(argumentRef)) return [];
46418
47447
  }
46419
47448
  return getDownstreamRefs(analysis, argument);
46420
- }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) || isExternalSubscriptionHookRef(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
46421
- if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
47449
+ }).flatMap((argumentRef) => {
47450
+ if (isExternallyDrivenState(analysis, argumentRef) || getLocalHookExternalStateProof(analysis, argumentRef, context.scopes) === true) return [];
47451
+ const upstreamRefs = getUpstreamRefs(analysis, argumentRef);
47452
+ return upstreamRefs.filter((upstreamRef) => {
47453
+ if (!isLeafRef(upstreamRef)) return false;
47454
+ return !isImmutableImportedExternalSubscriptionHookCallee(analysis, upstreamRef.identifier, upstreamRefs) && !isImmutableLocalExternalStoreHookCallee(analysis, upstreamRef.identifier, upstreamRefs, context.scopes);
47455
+ });
47456
+ });
47457
+ if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) {
47458
+ const wrapperUpstreamRefs = getArgsUpstreamRefs(analysis, ref);
47459
+ argsUpstreamRefs.push(...wrapperUpstreamRefs.filter((upstreamRef) => isLeafRef(upstreamRef) && !isImmutableImportedExternalSubscriptionHookCallee(analysis, upstreamRef.identifier, wrapperUpstreamRefs) && !isImmutableLocalExternalStoreHookCallee(analysis, upstreamRef.identifier, wrapperUpstreamRefs, context.scopes)));
47460
+ }
46422
47461
  if (!argsUpstreamRefs.some((argRef) => {
46423
- if (isUseStateIdentifier(argRef.identifier)) return false;
46424
- if (isExternalSubscriptionHookRef(analysis, argRef)) return false;
47462
+ const argIdentifier = argRef.identifier;
47463
+ if (isUseStateIdentifier(argIdentifier)) return false;
46425
47464
  if (isProp(analysis, argRef)) return false;
46426
- if (isUseRefIdentifier(argRef.identifier)) return false;
47465
+ if (isUseRefIdentifier(argIdentifier)) return false;
46427
47466
  if (isRefCurrent(argRef)) return false;
46428
47467
  if (isConstant(argRef)) return false;
46429
47468
  if (isParentWiredHookResultRef(analysis, argRef)) return false;
46430
47469
  if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
46431
47470
  if (resolveToFunction(argRef)) return false;
46432
- const argIdentifier = argRef.identifier;
46433
47471
  if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
46434
47472
  if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
46435
47473
  return true;
@@ -55380,6 +56418,7 @@ const preferModuleScopePureFunction = defineRule({
55380
56418
  tags: ["test-noise"],
55381
56419
  severity: "warn",
55382
56420
  category: "Architecture",
56421
+ disabledWhen: ["react-compiler"],
55383
56422
  recommendation: "Move the function above the component, at the top of the file. It doesn't use local state, so rebuilding it each update is wasted work.",
55384
56423
  create: (context) => {
55385
56424
  const report = (functionNode, name, componentName) => {
@@ -55419,17 +56458,6 @@ const preferModuleScopePureFunction = defineRule({
55419
56458
  }
55420
56459
  });
55421
56460
  //#endregion
55422
- //#region src/plugin/utils/get-require-call-source.ts
55423
- const getRequireCallSource = (expression) => {
55424
- const unwrappedExpression = stripParenExpression(expression);
55425
- if (isNodeOfType(unwrappedExpression, "MemberExpression")) return getRequireCallSource(unwrappedExpression.object);
55426
- if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
55427
- if (!isNodeOfType(unwrappedExpression.callee, "Identifier") || unwrappedExpression.callee.name !== "require") return null;
55428
- const [firstArgument] = unwrappedExpression.arguments ?? [];
55429
- if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
55430
- return typeof firstArgument.value === "string" ? firstArgument.value : null;
55431
- };
55432
- //#endregion
55433
56461
  //#region src/plugin/utils/is-proven-node-crypto-namespace-reference.ts
55434
56462
  const NODE_CRYPTO_MODULE_SOURCES = new Set(["crypto", "node:crypto"]);
55435
56463
  const isProvenNodeCryptoNamespaceReference = (expression, scopes) => {
@@ -56021,10 +57049,10 @@ const classifyCallableReadsInsideEffect = (callableIdentifier, effectCallback, c
56021
57049
  const preferUseEffectEvent = defineRule({
56022
57050
  id: "prefer-use-effect-event",
56023
57051
  title: "Effect re-subscribes on a changing callback",
56024
- requires: ["react:19"],
57052
+ requires: ["react:19.2"],
56025
57053
  tags: ["test-noise"],
56026
57054
  severity: "warn",
56027
- recommendation: "Wrap the callback with `useEffectEvent(callback)` (React 19+) and call it inside the sub-handler. An Effect Event always sees the latest props and state but isn't a dependency, so the effect won't re-subscribe every time the parent redraws. See https://react.dev/reference/react/useEffectEvent",
57055
+ recommendation: "Wrap the callback with `useEffectEvent(callback)` (React 19.2+) and call it inside the sub-handler. An Effect Event always sees the latest props and state but isn't a dependency, so the effect won't re-subscribe every time the parent redraws. See https://react.dev/reference/react/useEffectEvent",
56028
57056
  create: (context) => {
56029
57057
  const checkComponent = (componentBody) => {
56030
57058
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
@@ -57629,7 +58657,6 @@ const reduxUseselectorInlineDerivation = defineRule({
57629
58657
  title: "useSelector derives data inline",
57630
58658
  severity: "warn",
57631
58659
  category: "Performance",
57632
- disabledWhen: ["react-compiler"],
57633
58660
  recommendation: "Select the raw slice and memoize derivation so Redux actions do not rebuild a collection and redraw this component.",
57634
58661
  create: (context) => {
57635
58662
  let aliases = /* @__PURE__ */ new Set();
@@ -57689,7 +58716,6 @@ const reduxUseselectorReturnsNewCollection = defineRule({
57689
58716
  title: "useSelector returns a new collection",
57690
58717
  severity: "warn",
57691
58718
  category: "Performance",
57692
- disabledWhen: ["react-compiler"],
57693
58719
  recommendation: "Return a stable selected value, split selectors, or pass `shallowEqual` so every Redux action does not redraw this component.",
57694
58720
  create: (context) => {
57695
58721
  let aliases = /* @__PURE__ */ new Set();
@@ -57815,6 +58841,7 @@ const renderingHoistJsx = defineRule({
57815
58841
  title: "Constant JSX rebuilt each render",
57816
58842
  tags: ["test-noise"],
57817
58843
  severity: "warn",
58844
+ disabledWhen: ["react-compiler"],
57818
58845
  recommendation: "Move the static JSX out to the top of the file: `const ICON = <svg>...</svg>`, so it isn't rebuilt on every render",
57819
58846
  create: (context) => {
57820
58847
  let componentDepth = 0;
@@ -58261,8 +59288,9 @@ const renderingHydrationNoFlicker = defineRule({
58261
59288
  id: "rendering-hydration-no-flicker",
58262
59289
  title: "useEffect setState flashes on mount",
58263
59290
  tags: ["test-noise"],
59291
+ requires: ["ssr"],
58264
59292
  severity: "warn",
58265
- recommendation: "Use `useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)` or add `suppressHydrationWarning` to the element",
59293
+ recommendation: "Read the value with `useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)` inside a reusable hook, or add `suppressHydrationWarning` to the element",
58266
59294
  create: (context) => ({ CallExpression(node) {
58267
59295
  if (!isHookCall$2(node, USE_EFFECT_ONLY) || (node.arguments?.length ?? 0) < 2) return;
58268
59296
  const depsNode = node.arguments[1];
@@ -58715,7 +59743,7 @@ const renderingUsetransitionLoading = defineRule({
58715
59743
  title: "Loading useState forces extra render",
58716
59744
  tags: ["test-noise"],
58717
59745
  severity: "warn",
58718
- recommendation: "Replace with `const [isPending, startTransition] = useTransition()`, which skips the extra render for the loading flag",
59746
+ recommendation: "Replace with `const [isPending, startTransition] = useTransition()`, which marks the update as non-urgent and interruptible so the input stays responsive",
58719
59747
  create: (context) => ({ VariableDeclarator(node) {
58720
59748
  if (!isNodeOfType(node.id, "ArrayPattern") || !node.id.elements?.length) return;
58721
59749
  if (!node.init || !isHookCall$2(node.init, "useState")) return;
@@ -58747,7 +59775,7 @@ const renderingUsetransitionLoading = defineRule({
58747
59775
  }
58748
59776
  context.report({
58749
59777
  node: node.init,
58750
- message: `This adds an extra render because useState for "${stateVariableName}" re-renders just for the loading flag, so if it's a state change & not a data fetch, use useTransition instead`
59778
+ message: `This makes the "${stateVariableName}" update urgent and blocking because it's a plain useState flag, so if it's a state change & not a data fetch, use useTransition to keep the UI responsive while it runs`
58751
59779
  });
58752
59780
  } })
58753
59781
  });
@@ -58756,7 +59784,7 @@ const renderingUsetransitionLoading = defineRule({
58756
59784
  const isRepositorySecretFilePath = (relativePath) => DOTENV_FILE_PATTERN.test(relativePath) || /(?:^|\/)\.npmrc$/.test(relativePath) || /(?:^|\/)[^/]*(?:credential|credentials|service-account|serviceAccount|firebase-admin|google-service-account|gcp-service-account)[^/]*\.(?:json|env|pem|key)$/i.test(relativePath);
58757
59785
  //#endregion
58758
59786
  //#region src/plugin/rules/security-scan/repository-secret-file.ts
58759
- const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env\.(?:example|sample|template|dist|defaults?)$|(?:^|\/)[^/]*(?:example|sample|template)[^/]*\.(?:env|json|pem|key)$/i.test(relativePath);
59787
+ const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env(?:\.[^./]+)*\.(?:example|sample|template|dist|defaults?)$|(?:^|\/)[^/]*(?:example|sample|template)[^/]*\.(?:env|json|pem|key)$/i.test(relativePath);
58760
59788
  const repositorySecretFile = defineRule({
58761
59789
  id: "repository-secret-file",
58762
59790
  title: "Secret file checked into repository",
@@ -59062,6 +60090,7 @@ const rerenderDependencies = defineRule({
59062
60090
  title: "Unstable value recreated every render",
59063
60091
  tags: ["test-noise"],
59064
60092
  severity: "error",
60093
+ disabledWhen: ["react-compiler"],
59065
60094
  recommendation: "Move it into a useMemo, useRef, or a constant outside the component so it stays the same between renders.",
59066
60095
  create: (context) => ({ CallExpression(node) {
59067
60096
  if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes) || node.arguments.length < 2) return;
@@ -59474,7 +60503,7 @@ const rerenderMemoBeforeEarlyReturn = defineRule({
59474
60503
  title: "useMemo before an early return",
59475
60504
  tags: ["test-noise"],
59476
60505
  severity: "warn",
59477
- recommendation: "Move the JSX into a child component wrapped in memo, so the parent's early return skips it",
60506
+ recommendation: "Move the JSX into a child component rendered after the early return, so renders that take the early return never build it",
59478
60507
  create: (context) => {
59479
60508
  const inspectFunctionBody = (statements) => {
59480
60509
  let memoNode = null;
@@ -59500,7 +60529,7 @@ const rerenderMemoBeforeEarlyReturn = defineRule({
59500
60529
  if (callbackGuardTests.some((guardTest) => areConditionsStructurallyEqual(stmt.test, guardTest))) continue;
59501
60530
  context.report({
59502
60531
  node: memoNode,
59503
- message: "This runs even when the component bails out because the useMemo builds JSX before an early return, so move the JSX into a child wrapped in memo to skip it on the early return"
60532
+ message: "This rebuilds the JSX whenever its dependencies change even on renders that take the early return, so move the JSX into a child component rendered after the early return to skip it"
59504
60533
  });
59505
60534
  return;
59506
60535
  }
@@ -60003,6 +61032,7 @@ const rerenderMemoWithDefaultValue = defineRule({
60003
61032
  title: "Empty default prop breaks memo",
60004
61033
  tags: ["test-noise"],
60005
61034
  severity: "warn",
61035
+ disabledWhen: ["react-compiler"],
60006
61036
  recommendation: "Move it to the top of the file: `const EMPTY_ITEMS: Item[] = []`, then use that as the default value",
60007
61037
  create: (context) => {
60008
61038
  let memoRegistry = /* @__PURE__ */ new Map();
@@ -60135,6 +61165,717 @@ const isSetterCalledDuringRender = (root, setterName) => {
60135
61165
  return found;
60136
61166
  };
60137
61167
  //#endregion
61168
+ //#region src/plugin/rules/state-and-effects/utils/create-external-location-invalidation-checker.ts
61169
+ const HISTORY_LOCATION_MUTATION_METHOD_NAMES = new Set(["pushState", "replaceState"]);
61170
+ const AGGREGATE_MUTATION_METHOD_NAMES = new Set([...MUTATING_ARRAY_METHODS, ...MUTATING_COLLECTION_METHODS]);
61171
+ const OBJECT_AGGREGATE_MUTATION_METHOD_NAMES = new Set([
61172
+ "assign",
61173
+ "defineProperties",
61174
+ "defineProperty",
61175
+ "setPrototypeOf"
61176
+ ]);
61177
+ const REFLECT_AGGREGATE_MUTATION_METHOD_NAMES = new Set([
61178
+ "defineProperty",
61179
+ "deleteProperty",
61180
+ "set",
61181
+ "setPrototypeOf"
61182
+ ]);
61183
+ const LOCATION_CHANGE_EVENT_NAMES = new Set(["hashchange", "popstate"]);
61184
+ const containsGlobalLocationSnapshotRead = (node, scopes) => {
61185
+ let didFindLocationSnapshotRead = false;
61186
+ walkAst(node, (child) => {
61187
+ if (didFindLocationSnapshotRead) return false;
61188
+ if (child !== node && isFunctionLike$1(child) && !executesDuringRender(findTransparentExpressionRoot(child), scopes)) return false;
61189
+ if (!isProvenGlobalNamespaceReference(child, "location", scopes)) return;
61190
+ didFindLocationSnapshotRead = true;
61191
+ return false;
61192
+ });
61193
+ return didFindLocationSnapshotRead;
61194
+ };
61195
+ const resolveExactLocalOrReactCallbackFunction = (expression, scopes) => {
61196
+ const localFunction = resolveExactLocalFunction(expression, scopes);
61197
+ if (isFunctionLike$1(localFunction)) return localFunction;
61198
+ const unwrappedExpression = stripParenExpression(expression);
61199
+ let callbackSource = unwrappedExpression;
61200
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
61201
+ const callbackSymbol = resolveConstIdentifierAlias(unwrappedExpression, scopes);
61202
+ if (callbackSymbol?.kind !== "const" || !callbackSymbol.initializer) return null;
61203
+ callbackSource = callbackSymbol.initializer;
61204
+ }
61205
+ const unwrappedCallbackSource = stripParenExpression(callbackSource);
61206
+ if (!isNodeOfType(unwrappedCallbackSource, "CallExpression") || !isReactApiCall(unwrappedCallbackSource, "useCallback", scopes, { resolveNamedAliases: true })) return null;
61207
+ const callback = unwrappedCallbackSource.arguments?.[0];
61208
+ if (!callback || isNodeOfType(callback, "SpreadElement")) return null;
61209
+ return resolveExactLocalFunction(stripParenExpression(callback), scopes);
61210
+ };
61211
+ const bindingReadsExactGlobalLocationSnapshot = (bindingIdentifier, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
61212
+ const symbol = scopes.symbolFor(bindingIdentifier);
61213
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
61214
+ if ((symbol.kind === "function" ? scopes.scopeFor(symbol.declarationNode) : symbol.scope).symbols.some((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.references.some((reference) => reference.flag !== "read"))) return false;
61215
+ const resolvedFunction = resolveExactLocalOrReactCallbackFunction(bindingIdentifier, scopes);
61216
+ const initializer = isFunctionLike$1(resolvedFunction) ? resolvedFunction.body : getDirectUnreassignedInitializer(symbol);
61217
+ if (!initializer) return false;
61218
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
61219
+ nextVisitedSymbolIds.add(symbol.id);
61220
+ if (containsGlobalLocationSnapshotRead(initializer, scopes)) return true;
61221
+ let didFindAliasedLocationSnapshotRead = false;
61222
+ walkAst(initializer, (child) => {
61223
+ if (didFindAliasedLocationSnapshotRead) return false;
61224
+ if (child !== initializer && isFunctionLike$1(child) && !executesDuringRender(findTransparentExpressionRoot(child), scopes)) return false;
61225
+ if (!isNodeOfType(child, "Identifier")) return;
61226
+ if (bindingReadsExactGlobalLocationSnapshot(child, scopes, nextVisitedSymbolIds)) {
61227
+ didFindAliasedLocationSnapshotRead = true;
61228
+ return false;
61229
+ }
61230
+ });
61231
+ return didFindAliasedLocationSnapshotRead;
61232
+ };
61233
+ const hasRenderReachableLocationSnapshotRead = (componentBody, renderReachableExpressions, directRenderNames, scopes) => {
61234
+ if (renderReachableExpressions.some((expression) => containsGlobalLocationSnapshotRead(expression, scopes))) return true;
61235
+ for (const statement of componentBody.body ?? []) {
61236
+ if (isNodeOfType(statement, "FunctionDeclaration") && statement.id) {
61237
+ if (directRenderNames.has(statement.id.name) && bindingReadsExactGlobalLocationSnapshot(statement.id, scopes)) return true;
61238
+ continue;
61239
+ }
61240
+ if (!isNodeOfType(statement, "VariableDeclaration")) continue;
61241
+ for (const declarator of statement.declarations ?? []) {
61242
+ if (!isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
61243
+ if (!directRenderNames.has(declarator.id.name)) continue;
61244
+ if (bindingReadsExactGlobalLocationSnapshot(declarator.id, scopes)) return true;
61245
+ }
61246
+ }
61247
+ return false;
61248
+ };
61249
+ const isGlobalHistoryLocationMutation = (node, scopes) => {
61250
+ if (!isNodeOfType(node, "CallExpression")) return false;
61251
+ const callee = stripParenExpression(node.callee);
61252
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
61253
+ const methodName = getStaticPropertyName(callee);
61254
+ return Boolean(methodName && HISTORY_LOCATION_MUTATION_METHOD_NAMES.has(methodName) && isProvenGlobalNamespaceReference(callee.object, "history", scopes));
61255
+ };
61256
+ const getStaticLocationChangeEvent = (node) => {
61257
+ if (!node) return null;
61258
+ const eventNameNode = stripParenExpression(node);
61259
+ if (!isNodeOfType(eventNameNode, "Literal") || typeof eventNameNode.value !== "string") return null;
61260
+ return LOCATION_CHANGE_EVENT_NAMES.has(eventNameNode.value) ? eventNameNode.value : null;
61261
+ };
61262
+ const addToSetIndex = (index, key, value) => {
61263
+ const indexedValues = index.get(key) ?? /* @__PURE__ */ new Set();
61264
+ indexedValues.add(value);
61265
+ index.set(key, indexedValues);
61266
+ };
61267
+ const getSynchronousInvocationExpression = (functionNode, scopes) => {
61268
+ const functionExpressionRoot = findTransparentExpressionRoot(functionNode);
61269
+ if (!executesDuringRender(functionExpressionRoot, scopes)) return null;
61270
+ const parent = functionExpressionRoot.parent;
61271
+ return isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression") ? parent : null;
61272
+ };
61273
+ const getLocationListenerOperation = (callExpression, scopes) => {
61274
+ if (!isNodeOfType(callExpression, "CallExpression")) return null;
61275
+ const callee = stripParenExpression(callExpression.callee);
61276
+ const methodName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : isNodeOfType(callee, "Identifier") ? callee.name : null;
61277
+ if (methodName !== "addEventListener" && methodName !== "removeEventListener") return null;
61278
+ if (!(isNodeOfType(callee, "MemberExpression") ? isProvenGlobalObjectReference(callee.object, scopes) : isNodeOfType(callee, "Identifier") && scopes.isGlobalReference(callee))) return null;
61279
+ const eventName = getStaticLocationChangeEvent(callExpression.arguments?.[0]);
61280
+ if (!eventName) return null;
61281
+ const listenerExpression = callExpression.arguments?.[1];
61282
+ if (!listenerExpression || isNodeOfType(listenerExpression, "SpreadElement")) return null;
61283
+ const listenerFunction = resolveExactLocalOrReactCallbackFunction(listenerExpression, scopes);
61284
+ if (!isFunctionLike$1(listenerFunction)) return null;
61285
+ const captureArgument = callExpression.arguments?.[2];
61286
+ const capture = isNodeOfType(captureArgument, "SpreadElement") ? null : resolveEventListenerCapture(captureArgument, {
61287
+ allowComputedString: true,
61288
+ allowIndeterminateEntries: true
61289
+ });
61290
+ return {
61291
+ operation: methodName === "addEventListener" ? "add" : "remove",
61292
+ registration: {
61293
+ callExpression,
61294
+ listenerFunction,
61295
+ capture,
61296
+ eventName
61297
+ }
61298
+ };
61299
+ };
61300
+ const buildLocationInvalidationIndex = (componentBody, componentFunction, context) => {
61301
+ const index = {
61302
+ componentFunction,
61303
+ context,
61304
+ effectCallbacks: /* @__PURE__ */ new Set(),
61305
+ expressionsByOwner: /* @__PURE__ */ new Map(),
61306
+ historyMutationsByOwner: /* @__PURE__ */ new Map(),
61307
+ awaitExpressionsByOwner: /* @__PURE__ */ new Map(),
61308
+ callSitesByFunction: /* @__PURE__ */ new Map(),
61309
+ calledFunctionByExpression: /* @__PURE__ */ new Map(),
61310
+ synchronousInvocationsByFunction: /* @__PURE__ */ new Map(),
61311
+ synchronousCallbacksByExpression: /* @__PURE__ */ new Map(),
61312
+ callsByCalleeSymbolId: /* @__PURE__ */ new Map(),
61313
+ identifierCalls: /* @__PURE__ */ new Set(),
61314
+ listenerRegistrations: [],
61315
+ listenerRemovals: [],
61316
+ mountedListenerFunctions: /* @__PURE__ */ new Set(),
61317
+ mutationExecutionsByOwner: /* @__PURE__ */ new Map(),
61318
+ synchronousMutationResultByFunction: /* @__PURE__ */ new Map()
61319
+ };
61320
+ walkAst(componentBody, (child) => {
61321
+ if (isFunctionLike$1(child)) {
61322
+ const invocationExpression = getSynchronousInvocationExpression(child, context.scopes);
61323
+ if (invocationExpression) {
61324
+ addToSetIndex(index.synchronousInvocationsByFunction, child, invocationExpression);
61325
+ addToSetIndex(index.synchronousCallbacksByExpression, invocationExpression, child);
61326
+ }
61327
+ return;
61328
+ }
61329
+ const owner = context.cfg.enclosingFunction(child);
61330
+ if (!owner) return;
61331
+ if (isNodeOfType(child, "AwaitExpression")) {
61332
+ addToSetIndex(index.awaitExpressionsByOwner, owner, child);
61333
+ return;
61334
+ }
61335
+ if (!isNodeOfType(child, "CallExpression") && !isNodeOfType(child, "NewExpression")) return;
61336
+ addToSetIndex(index.expressionsByOwner, owner, child);
61337
+ if (isNodeOfType(child, "CallExpression")) {
61338
+ if (isGlobalHistoryLocationMutation(child, context.scopes)) addToSetIndex(index.historyMutationsByOwner, owner, child);
61339
+ if (isNodeOfType(stripParenExpression(child.callee), "Identifier")) index.identifierCalls.add(child);
61340
+ const calledFunction = resolveExactLocalOrReactCallbackFunction(child.callee, context.scopes);
61341
+ if (isFunctionLike$1(calledFunction)) {
61342
+ addToSetIndex(index.callSitesByFunction, calledFunction, child);
61343
+ index.calledFunctionByExpression.set(child, calledFunction);
61344
+ }
61345
+ if (context.cfg.enclosingFunction(child) === componentFunction && isReactApiCall(child, EFFECT_HOOK_NAMES$1, context.scopes, {
61346
+ allowGlobalReactNamespace: true,
61347
+ allowUnboundBareCalls: true,
61348
+ resolveNamedAliases: true
61349
+ })) {
61350
+ const effectCallback = getEffectCallback(child, context.scopes);
61351
+ if (isFunctionLike$1(effectCallback)) index.effectCallbacks.add(effectCallback);
61352
+ }
61353
+ const listenerOperation = getLocationListenerOperation(child, context.scopes);
61354
+ if (listenerOperation?.operation === "add") index.listenerRegistrations.push(listenerOperation.registration);
61355
+ else if (listenerOperation) index.listenerRemovals.push(listenerOperation.registration);
61356
+ }
61357
+ });
61358
+ return index;
61359
+ };
61360
+ const isDescendantWithoutFunctionBoundary = (descendant, ancestor) => {
61361
+ let current = descendant;
61362
+ while (current && current !== ancestor) {
61363
+ if (current !== descendant && isFunctionLike$1(current)) return false;
61364
+ current = current.parent;
61365
+ }
61366
+ return current === ancestor;
61367
+ };
61368
+ const areInMutuallyExclusiveConditionalBranches = (firstNode, secondNode) => {
61369
+ const firstBranches = /* @__PURE__ */ new Map();
61370
+ let current = firstNode;
61371
+ while (current?.parent) {
61372
+ const parent = current.parent;
61373
+ if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current)) firstBranches.set(parent, current);
61374
+ if (current !== firstNode && isFunctionLike$1(current)) break;
61375
+ current = parent;
61376
+ }
61377
+ current = secondNode;
61378
+ while (current?.parent) {
61379
+ const parent = current.parent;
61380
+ if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current)) {
61381
+ const firstBranch = firstBranches.get(parent);
61382
+ if (firstBranch && firstBranch !== current) return true;
61383
+ }
61384
+ if (current !== secondNode && isFunctionLike$1(current)) break;
61385
+ current = parent;
61386
+ }
61387
+ return false;
61388
+ };
61389
+ const collectStableBooleanGuardConstraints = (node, functionBoundary, scopes) => {
61390
+ const constraintsBySymbolId = /* @__PURE__ */ new Map();
61391
+ const recordConstraint = (test, requiredTestValue) => {
61392
+ let expression = stripParenExpression(test);
61393
+ let requiredValue = requiredTestValue;
61394
+ while (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") {
61395
+ requiredValue = !requiredValue;
61396
+ expression = stripParenExpression(expression.argument);
61397
+ }
61398
+ if (!isNodeOfType(expression, "Identifier")) return;
61399
+ const symbol = scopes.symbolFor(expression);
61400
+ if (!symbol || symbol.references.some((reference) => reference.flag !== "read")) return;
61401
+ const existingConstraint = constraintsBySymbolId.get(symbol.id);
61402
+ constraintsBySymbolId.set(symbol.id, existingConstraint === void 0 || existingConstraint === requiredValue ? requiredValue : null);
61403
+ };
61404
+ let current = node;
61405
+ while (current?.parent && current !== functionBoundary) {
61406
+ const parent = current.parent;
61407
+ if (isNodeOfType(parent, "IfStatement")) {
61408
+ if (parent.consequent === current) recordConstraint(parent.test, true);
61409
+ if (parent.alternate === current) recordConstraint(parent.test, false);
61410
+ } else if (isNodeOfType(parent, "ConditionalExpression")) {
61411
+ if (parent.consequent === current) recordConstraint(parent.test, true);
61412
+ if (parent.alternate === current) recordConstraint(parent.test, false);
61413
+ } else if (isNodeOfType(parent, "LogicalExpression") && parent.right === current) {
61414
+ if (parent.operator === "&&") recordConstraint(parent.left, true);
61415
+ if (parent.operator === "||") recordConstraint(parent.left, false);
61416
+ }
61417
+ if (current !== node && isFunctionLike$1(current)) break;
61418
+ current = parent;
61419
+ }
61420
+ return constraintsBySymbolId;
61421
+ };
61422
+ const haveContradictoryStableBooleanGuards = (firstNode, secondNode, functionBoundary, scopes) => {
61423
+ const firstConstraints = collectStableBooleanGuardConstraints(firstNode, functionBoundary, scopes);
61424
+ const secondConstraints = collectStableBooleanGuardConstraints(secondNode, functionBoundary, scopes);
61425
+ for (const requiredValue of firstConstraints.values()) if (requiredValue === null) return true;
61426
+ for (const requiredValue of secondConstraints.values()) if (requiredValue === null) return true;
61427
+ for (const [symbolId, firstRequiredValue] of firstConstraints) {
61428
+ const secondRequiredValue = secondConstraints.get(symbolId);
61429
+ if (firstRequiredValue !== null && secondRequiredValue !== void 0 && secondRequiredValue !== null && firstRequiredValue !== secondRequiredValue) return true;
61430
+ }
61431
+ return false;
61432
+ };
61433
+ const isInsideIntrinsicReactEventHandlerAttribute = (node, functionBoundary) => {
61434
+ let current = node;
61435
+ while (current && current !== functionBoundary) {
61436
+ if (isEventHandlerAttribute(current) && isJsxAttributeOnIntrinsicHtmlElement(current)) return true;
61437
+ current = current.parent;
61438
+ }
61439
+ return false;
61440
+ };
61441
+ const getTransparentExpressionBindingIdentifier = (expression) => {
61442
+ const expressionRoot = findTransparentExpressionRoot(expression);
61443
+ const parent = expressionRoot.parent;
61444
+ if (isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier")) return parent.id;
61445
+ if (isNodeOfType(parent, "AssignmentExpression") && parent.right === expressionRoot && isNodeOfType(parent.left, "Identifier")) return parent.left;
61446
+ return null;
61447
+ };
61448
+ const getIntrinsicReactEventHandlerBindingIdentifier = (functionNode, index) => {
61449
+ if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
61450
+ const functionExpressionRoot = findTransparentExpressionRoot(functionNode);
61451
+ const directTransparentBindingIdentifier = getTransparentExpressionBindingIdentifier(functionExpressionRoot);
61452
+ if (directTransparentBindingIdentifier) return directTransparentBindingIdentifier;
61453
+ const callbackCall = functionExpressionRoot.parent;
61454
+ if (!isNodeOfType(callbackCall, "CallExpression") || callbackCall.arguments?.[0] !== functionExpressionRoot || !isReactApiCall(callbackCall, "useCallback", index.context.scopes, { resolveNamedAliases: true })) return null;
61455
+ return getTransparentExpressionBindingIdentifier(callbackCall);
61456
+ };
61457
+ const isExclusiveIntrinsicReactEventHandler = (functionNode, index) => {
61458
+ const bindingIdentifier = getIntrinsicReactEventHandlerBindingIdentifier(functionNode, index);
61459
+ if (!bindingIdentifier) return isInsideIntrinsicReactEventHandlerAttribute(functionNode, index.componentFunction);
61460
+ const bindingSymbol = index.context.scopes.symbolFor(bindingIdentifier);
61461
+ return Boolean(bindingSymbol && bindingSymbol.references.length > 0 && bindingSymbol.references.every((reference) => isInsideIntrinsicReactEventHandlerAttribute(reference.identifier, index.componentFunction)));
61462
+ };
61463
+ const canNodeReachNode = (sourceNode, targetNode, index) => {
61464
+ const { context } = index;
61465
+ if (!isNodeReachableWithinFunction(sourceNode, context)) return false;
61466
+ if (!isNodeReachableWithinFunction(targetNode, context)) return false;
61467
+ const sourceOwner = context.cfg.enclosingFunction(sourceNode);
61468
+ const targetOwner = context.cfg.enclosingFunction(targetNode);
61469
+ if (!sourceOwner || sourceOwner !== targetOwner) return false;
61470
+ if (areInMutuallyExclusiveConditionalBranches(sourceNode, targetNode)) return false;
61471
+ if (haveContradictoryStableBooleanGuards(sourceNode, targetNode, sourceOwner, context.scopes)) return false;
61472
+ if (isDescendantWithoutFunctionBoundary(targetNode, sourceNode)) return false;
61473
+ if (isDescendantWithoutFunctionBoundary(sourceNode, targetNode)) return true;
61474
+ const functionCfg = context.cfg.cfgFor(sourceOwner);
61475
+ const sourceBlock = functionCfg?.blockOf(sourceNode);
61476
+ const targetBlock = functionCfg?.blockOf(targetNode);
61477
+ if (!functionCfg || !sourceBlock || !targetBlock) return false;
61478
+ if (sourceBlock === targetBlock) {
61479
+ const sourceStart = getRangeStart(sourceNode);
61480
+ const targetStart = getRangeStart(targetNode);
61481
+ return sourceStart !== null && targetStart !== null && sourceStart < targetStart;
61482
+ }
61483
+ const visitedBlocks = new Set([sourceBlock]);
61484
+ const pendingBlocks = sourceBlock.successors.filter((edge) => edge.kind !== "throw").map((edge) => edge.to);
61485
+ while (pendingBlocks.length > 0) {
61486
+ const block = pendingBlocks.pop();
61487
+ if (!block || visitedBlocks.has(block)) continue;
61488
+ if (block === targetBlock) return true;
61489
+ visitedBlocks.add(block);
61490
+ for (const edge of block.successors) if (edge.kind !== "throw") pendingBlocks.push(edge.to);
61491
+ }
61492
+ return false;
61493
+ };
61494
+ const getInlineIntrinsicHandlerJsxElement = (functionNode) => {
61495
+ let currentNode = functionNode;
61496
+ while (currentNode) {
61497
+ if (isNodeOfType(currentNode, "JSXAttribute") && isEventHandlerAttribute(currentNode) && isJsxAttributeOnIntrinsicHtmlElement(currentNode)) {
61498
+ const openingElement = currentNode.parent;
61499
+ return isNodeOfType(openingElement?.parent, "JSXElement") ? openingElement.parent : null;
61500
+ }
61501
+ if (currentNode !== functionNode && isFunctionLike$1(currentNode)) return null;
61502
+ currentNode = currentNode.parent;
61503
+ }
61504
+ return null;
61505
+ };
61506
+ const isProvenNonEscapingVoidRead = (identifier) => {
61507
+ const expressionRoot = findTransparentExpressionRoot(identifier);
61508
+ return isNodeOfType(expressionRoot.parent, "UnaryExpression") && expressionRoot.parent.operator === "void" && expressionRoot.parent.argument === expressionRoot;
61509
+ };
61510
+ const getDirectReadonlyValueAliasSymbol = (referenceIdentifier, sourceSymbol, index) => {
61511
+ const referenceRoot = findTransparentExpressionRoot(referenceIdentifier);
61512
+ const parent = referenceRoot.parent;
61513
+ if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier")) {
61514
+ const aliasSymbol = index.context.scopes.symbolFor(parent.id);
61515
+ const aliasInitializer = aliasSymbol ? getDirectConstInitializer(aliasSymbol) : null;
61516
+ const unwrappedInitializer = aliasInitializer ? stripParenExpression(aliasInitializer) : null;
61517
+ return aliasSymbol && isNodeOfType(unwrappedInitializer, "Identifier") && index.context.scopes.symbolFor(unwrappedInitializer) === sourceSymbol ? aliasSymbol : null;
61518
+ }
61519
+ if (!isNodeOfType(parent, "CallExpression") || parent.arguments?.[0] !== referenceRoot || !isReactApiCall(parent, "useCallback", index.context.scopes, { resolveNamedAliases: true })) return null;
61520
+ const callbackResultRoot = findTransparentExpressionRoot(parent);
61521
+ const resultParent = callbackResultRoot.parent;
61522
+ if (!isNodeOfType(resultParent, "VariableDeclarator") || resultParent.init !== callbackResultRoot || !isNodeOfType(resultParent.id, "Identifier")) return null;
61523
+ const callbackResultSymbol = index.context.scopes.symbolFor(resultParent.id);
61524
+ return callbackResultSymbol && getDirectConstInitializer(callbackResultSymbol) === callbackResultRoot ? callbackResultSymbol : null;
61525
+ };
61526
+ const isKnownAggregateMutationReference = (referenceIdentifier, index) => {
61527
+ const referenceRoot = findTransparentExpressionRoot(referenceIdentifier);
61528
+ const parent = referenceRoot.parent;
61529
+ if (isNodeOfType(parent, "MemberExpression") && parent.object === referenceRoot) {
61530
+ const memberRoot = findTransparentExpressionRoot(parent);
61531
+ const callExpression = memberRoot.parent;
61532
+ const methodName = getStaticPropertyName(parent);
61533
+ if (isNodeOfType(callExpression, "CallExpression") && callExpression.callee === memberRoot && methodName !== null && AGGREGATE_MUTATION_METHOD_NAMES.has(methodName)) return true;
61534
+ }
61535
+ if (!isNodeOfType(parent, "CallExpression") || parent.arguments?.[0] !== referenceRoot) return false;
61536
+ const callee = stripParenExpression(parent.callee);
61537
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
61538
+ const methodName = getStaticPropertyName(callee);
61539
+ if (methodName === null) return false;
61540
+ return OBJECT_AGGREGATE_MUTATION_METHOD_NAMES.has(methodName) && isProvenGlobalNamespaceReference(callee.object, "Object", index.context.scopes) || REFLECT_AGGREGATE_MUTATION_METHOD_NAMES.has(methodName) && isProvenGlobalNamespaceReference(callee.object, "Reflect", index.context.scopes);
61541
+ };
61542
+ const collectExactReadonlyValueEscapeAnchors = (symbol, index, valueFunctionNode, options = {}, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
61543
+ if (visitedSymbolIds.has(symbol.id)) return null;
61544
+ if (symbol.references.some((reference) => reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier) || options.rejectKnownMutations && isKnownAggregateMutationReference(reference.identifier, index))) return null;
61545
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
61546
+ nextVisitedSymbolIds.add(symbol.id);
61547
+ const escapeAnchors = [];
61548
+ for (const reference of symbol.references) {
61549
+ if (valueFunctionNode && index.context.cfg.enclosingFunction(reference.identifier) === valueFunctionNode || isProvenNonEscapingVoidRead(reference.identifier)) continue;
61550
+ const aliasSymbol = getDirectReadonlyValueAliasSymbol(reference.identifier, symbol, index);
61551
+ if (!aliasSymbol) {
61552
+ escapeAnchors.push(reference.identifier);
61553
+ continue;
61554
+ }
61555
+ const aliasEscapeAnchors = collectExactReadonlyValueEscapeAnchors(aliasSymbol, index, valueFunctionNode, options, nextVisitedSymbolIds);
61556
+ if (!aliasEscapeAnchors) return null;
61557
+ escapeAnchors.push(...aliasEscapeAnchors);
61558
+ }
61559
+ return escapeAnchors.length > 0 ? escapeAnchors : null;
61560
+ };
61561
+ const findReadonlyJsxAggregateRoot = (jsxElement) => {
61562
+ let aggregateRoot = findTransparentExpressionRoot(jsxElement);
61563
+ while (aggregateRoot.parent) {
61564
+ const parent = aggregateRoot.parent;
61565
+ if ((isNodeOfType(parent, "JSXElement") || isNodeOfType(parent, "JSXFragment")) && parent.children?.some((child) => child === aggregateRoot)) {
61566
+ aggregateRoot = parent;
61567
+ continue;
61568
+ }
61569
+ if (isNodeOfType(parent, "ArrayExpression") && parent.elements?.some((element) => element === aggregateRoot)) {
61570
+ aggregateRoot = findTransparentExpressionRoot(parent);
61571
+ continue;
61572
+ }
61573
+ if (isNodeOfType(parent, "Property") && parent.value === aggregateRoot && parent.kind === "init" && !parent.computed && isNodeOfType(parent.parent, "ObjectExpression")) {
61574
+ aggregateRoot = findTransparentExpressionRoot(parent.parent);
61575
+ continue;
61576
+ }
61577
+ if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === aggregateRoot || parent.alternate === aggregateRoot)) {
61578
+ aggregateRoot = findTransparentExpressionRoot(parent);
61579
+ continue;
61580
+ }
61581
+ if (isNodeOfType(parent, "LogicalExpression") && (parent.right === aggregateRoot || parent.left === aggregateRoot && parent.operator !== "&&")) {
61582
+ aggregateRoot = findTransparentExpressionRoot(parent);
61583
+ continue;
61584
+ }
61585
+ if (isNodeOfType(parent, "SequenceExpression") && getFinalSequenceExpressionValue(parent) === getFinalSequenceExpressionValue(aggregateRoot)) {
61586
+ aggregateRoot = findTransparentExpressionRoot(parent);
61587
+ continue;
61588
+ }
61589
+ break;
61590
+ }
61591
+ return aggregateRoot;
61592
+ };
61593
+ const getReadonlyJsxValueEscapeAnchors = (jsxElement, index) => {
61594
+ const jsxValueRoot = findReadonlyJsxAggregateRoot(jsxElement);
61595
+ const parent = jsxValueRoot.parent;
61596
+ if (!isNodeOfType(parent, "VariableDeclarator") || parent.init !== jsxValueRoot) return [jsxValueRoot];
61597
+ if (!isNodeOfType(parent.id, "Identifier")) return null;
61598
+ const bindingSymbol = index.context.scopes.symbolFor(parent.id);
61599
+ if (!bindingSymbol || getDirectConstInitializer(bindingSymbol) !== jsxValueRoot) return null;
61600
+ return collectExactReadonlyValueEscapeAnchors(bindingSymbol, index, null, { rejectKnownMutations: true });
61601
+ };
61602
+ const getExclusiveIntrinsicReactEventHandlerAnchors = (functionNode, index) => {
61603
+ if (!isExclusiveIntrinsicReactEventHandler(functionNode, index)) return null;
61604
+ const bindingIdentifier = getIntrinsicReactEventHandlerBindingIdentifier(functionNode, index);
61605
+ if (!bindingIdentifier) {
61606
+ const jsxElement = getInlineIntrinsicHandlerJsxElement(functionNode);
61607
+ return jsxElement ? getReadonlyJsxValueEscapeAnchors(jsxElement, index) : null;
61608
+ }
61609
+ return index.context.scopes.symbolFor(bindingIdentifier)?.references.map((reference) => reference.identifier) ?? null;
61610
+ };
61611
+ const getReadonlyFunctionEscapeAnchors = (functionNode, index) => {
61612
+ const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
61613
+ if (!bindingIdentifier) return null;
61614
+ const bindingSymbol = index.context.scopes.symbolFor(bindingIdentifier);
61615
+ if (!bindingSymbol || resolveExactLocalFunction(bindingIdentifier, index.context.scopes) !== functionNode) return null;
61616
+ return collectExactReadonlyValueEscapeAnchors(bindingSymbol, index, functionNode);
61617
+ };
61618
+ const isAliasInitializedBeforeExecution = (aliasDeclaration, executionNode, index, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
61619
+ if (!isNodeReachableWithinFunction(aliasDeclaration, index.context)) return false;
61620
+ const aliasOwner = index.context.cfg.enclosingFunction(aliasDeclaration);
61621
+ const executionOwner = index.context.cfg.enclosingFunction(executionNode);
61622
+ if (!aliasOwner || !executionOwner) return false;
61623
+ if (aliasOwner === executionOwner) return canNodeReachNode(aliasDeclaration, executionNode, index);
61624
+ if (visitedFunctionNodes.has(executionOwner)) return false;
61625
+ const nextVisitedFunctionNodes = new Set(visitedFunctionNodes);
61626
+ nextVisitedFunctionNodes.add(executionOwner);
61627
+ if (aliasOwner === index.componentFunction && (index.effectCallbacks.has(executionOwner) || index.mountedListenerFunctions.has(executionOwner))) return doNodesCoverEveryPathFromFunctionEntry(index.componentFunction, [aliasDeclaration], index.context, { ignoreThrowEdges: true });
61628
+ const invocations = new Set([...index.callSitesByFunction.get(executionOwner) ?? [], ...index.synchronousInvocationsByFunction.get(executionOwner) ?? []]);
61629
+ if (invocations.size > 0) return [...invocations].every((invocation) => isAliasInitializedBeforeExecution(aliasDeclaration, invocation, index, nextVisitedFunctionNodes));
61630
+ const deferredExecutionAnchors = getExclusiveIntrinsicReactEventHandlerAnchors(executionOwner, index);
61631
+ if (deferredExecutionAnchors) return deferredExecutionAnchors.every((anchor) => isAliasInitializedBeforeExecution(aliasDeclaration, anchor, index, nextVisitedFunctionNodes));
61632
+ const functionEscapeAnchors = getReadonlyFunctionEscapeAnchors(executionOwner, index);
61633
+ return Boolean(functionEscapeAnchors?.every((anchor) => isAliasInitializedBeforeExecution(aliasDeclaration, anchor, index, nextVisitedFunctionNodes)));
61634
+ };
61635
+ const resolveExactReadonlyCalleeSymbolId = (callExpression, index) => {
61636
+ if (!isNodeOfType(callExpression, "CallExpression")) return null;
61637
+ const callee = stripParenExpression(callExpression.callee);
61638
+ if (!isNodeOfType(callee, "Identifier")) return null;
61639
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
61640
+ let symbol = index.context.scopes.symbolFor(callee);
61641
+ while (symbol) {
61642
+ if (visitedSymbolIds.has(symbol.id)) return null;
61643
+ visitedSymbolIds.add(symbol.id);
61644
+ const initializer = getDirectConstInitializer(symbol);
61645
+ if (!initializer) return symbol.id;
61646
+ if (symbol.references.some((reference) => reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier)) || !isAliasInitializedBeforeExecution(symbol.declarationNode, callExpression, index)) return null;
61647
+ const unwrappedInitializer = stripParenExpression(initializer);
61648
+ if (!isNodeOfType(unwrappedInitializer, "Identifier")) return symbol.id;
61649
+ const initializerSymbol = index.context.scopes.symbolFor(unwrappedInitializer);
61650
+ if (!initializerSymbol || !isAliasInitializedBeforeExecution(initializerSymbol.declarationNode, symbol.declarationNode, index)) return null;
61651
+ symbol = initializerSymbol;
61652
+ }
61653
+ return null;
61654
+ };
61655
+ const collectExactReadonlyCalleeCalls = (index) => {
61656
+ for (const callExpression of index.identifierCalls) {
61657
+ const calleeSymbolId = resolveExactReadonlyCalleeSymbolId(callExpression, index);
61658
+ if (calleeSymbolId !== null) addToSetIndex(index.callsByCalleeSymbolId, calleeSymbolId, callExpression);
61659
+ }
61660
+ };
61661
+ const canNodeReachNormalFunctionExit = (node, functionNode, index) => {
61662
+ const functionCfg = index.context.cfg.cfgFor(functionNode);
61663
+ const sourceBlock = functionCfg?.blockOf(node);
61664
+ if (!functionCfg || !sourceBlock) return false;
61665
+ const visitedBlocks = /* @__PURE__ */ new Set();
61666
+ const pendingBlocks = [sourceBlock];
61667
+ while (pendingBlocks.length > 0) {
61668
+ const block = pendingBlocks.pop();
61669
+ if (!block || visitedBlocks.has(block)) continue;
61670
+ visitedBlocks.add(block);
61671
+ for (const edge of block.successors) {
61672
+ if (edge.kind === "throw") continue;
61673
+ if (edge.to === functionCfg.exit) return true;
61674
+ pendingBlocks.push(edge.to);
61675
+ }
61676
+ }
61677
+ return false;
61678
+ };
61679
+ const canExecuteBeforeAsyncSuspension = (node, functionNode, index) => {
61680
+ if (!isFunctionLike$1(functionNode) || !functionNode.async) return isNodeReachableWithinFunction(node, index.context);
61681
+ const functionCfg = index.context.cfg.cfgFor(functionNode);
61682
+ const targetBlock = functionCfg?.blockOf(node);
61683
+ if (!functionCfg || !targetBlock) return false;
61684
+ const awaitsByBlock = /* @__PURE__ */ new Map();
61685
+ for (const awaitExpression of index.awaitExpressionsByOwner.get(functionNode) ?? []) {
61686
+ const awaitBlock = functionCfg.blockOf(awaitExpression);
61687
+ if (!awaitBlock) continue;
61688
+ const blockAwaits = awaitsByBlock.get(awaitBlock) ?? [];
61689
+ blockAwaits.push(awaitExpression);
61690
+ awaitsByBlock.set(awaitBlock, blockAwaits);
61691
+ }
61692
+ const visitedBlocks = /* @__PURE__ */ new Set();
61693
+ const pendingBlocks = [functionCfg.entry];
61694
+ const targetStart = getRangeStart(node);
61695
+ while (pendingBlocks.length > 0) {
61696
+ const block = pendingBlocks.pop();
61697
+ if (!block || visitedBlocks.has(block)) continue;
61698
+ visitedBlocks.add(block);
61699
+ const blockAwaits = awaitsByBlock.get(block) ?? [];
61700
+ if (block === targetBlock) return !blockAwaits.some((awaitExpression) => {
61701
+ if (isNodeOfType(awaitExpression, "AwaitExpression") && isDescendantWithoutFunctionBoundary(node, awaitExpression.argument)) return false;
61702
+ if (isDescendantWithoutFunctionBoundary(awaitExpression, node)) return true;
61703
+ const awaitStart = getRangeStart(awaitExpression);
61704
+ return awaitStart !== null && targetStart !== null && awaitStart < targetStart;
61705
+ });
61706
+ if (blockAwaits.length > 0) continue;
61707
+ for (const edge of block.successors) if (edge.kind !== "throw") pendingBlocks.push(edge.to);
61708
+ }
61709
+ return false;
61710
+ };
61711
+ const canReactBatchMutationAfterExecution = (executionNode, mutationNode, owner, index) => (isExclusiveIntrinsicReactEventHandler(owner, index) || index.effectCallbacks.has(owner)) && canNodeReachNode(executionNode, mutationNode, index) && canExecuteBeforeAsyncSuspension(mutationNode, owner, index) && canNodeReachNormalFunctionExit(mutationNode, owner, index);
61712
+ const functionMaySynchronouslyMutateLocation = (functionNode, index, visitingFunctions, cycleAffectedFunctions) => {
61713
+ const cachedResult = index.synchronousMutationResultByFunction.get(functionNode);
61714
+ if (cachedResult !== void 0) return cachedResult;
61715
+ if (!isFunctionLike$1(functionNode) || functionNode.generator) return false;
61716
+ if (visitingFunctions.has(functionNode)) {
61717
+ let didReachCycleEntry = false;
61718
+ for (const visitingFunction of visitingFunctions) {
61719
+ if (visitingFunction === functionNode) didReachCycleEntry = true;
61720
+ if (didReachCycleEntry) cycleAffectedFunctions.add(visitingFunction);
61721
+ }
61722
+ return false;
61723
+ }
61724
+ visitingFunctions.add(functionNode);
61725
+ const doesMutateSynchronously = [...collectLocationMutationExecutions(functionNode, index, visitingFunctions, cycleAffectedFunctions)].some((mutationExecution) => canExecuteBeforeAsyncSuspension(mutationExecution, functionNode, index) && canNodeReachNormalFunctionExit(mutationExecution, functionNode, index));
61726
+ visitingFunctions.delete(functionNode);
61727
+ if (doesMutateSynchronously || !cycleAffectedFunctions.has(functionNode)) index.synchronousMutationResultByFunction.set(functionNode, doesMutateSynchronously);
61728
+ return doesMutateSynchronously;
61729
+ };
61730
+ const collectLocationMutationExecutions = (functionNode, index, visitingFunctions = /* @__PURE__ */ new Set(), cycleAffectedFunctions = /* @__PURE__ */ new Set()) => {
61731
+ const cachedExecutions = index.mutationExecutionsByOwner.get(functionNode);
61732
+ if (cachedExecutions) return cachedExecutions;
61733
+ const mutationExecutions = new Set(index.historyMutationsByOwner.get(functionNode) ?? []);
61734
+ for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
61735
+ const calledFunction = index.calledFunctionByExpression.get(expression);
61736
+ if (calledFunction && functionMaySynchronouslyMutateLocation(calledFunction, index, visitingFunctions, cycleAffectedFunctions)) mutationExecutions.add(expression);
61737
+ for (const callbackFunction of index.synchronousCallbacksByExpression.get(expression) ?? []) if (functionMaySynchronouslyMutateLocation(callbackFunction, index, visitingFunctions, cycleAffectedFunctions)) mutationExecutions.add(expression);
61738
+ }
61739
+ if (!cycleAffectedFunctions.has(functionNode)) index.mutationExecutionsByOwner.set(functionNode, mutationExecutions);
61740
+ return mutationExecutions;
61741
+ };
61742
+ const isDefinitelyMatchingLocationListenerRemoval = (registration, removal) => registration.eventName === removal.eventName && registration.listenerFunction === removal.listenerFunction && registration.capture !== null && removal.capture !== null && registration.capture === removal.capture;
61743
+ const functionMustSynchronouslyRemoveLocationListener = (functionNode, registration, index, visitingFunctions) => {
61744
+ if (!isFunctionLike$1(functionNode) || functionNode.generator) return false;
61745
+ if (visitingFunctions.has(functionNode)) return false;
61746
+ const nextVisitingFunctions = new Set(visitingFunctions);
61747
+ nextVisitingFunctions.add(functionNode);
61748
+ const removalExecutions = [];
61749
+ for (const removal of index.listenerRemovals) {
61750
+ if (!isDefinitelyMatchingLocationListenerRemoval(registration, removal)) continue;
61751
+ if (index.context.cfg.enclosingFunction(removal.callExpression) !== functionNode) continue;
61752
+ if (canExecuteBeforeAsyncSuspension(removal.callExpression, functionNode, index)) removalExecutions.push(removal.callExpression);
61753
+ }
61754
+ for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
61755
+ const calledFunction = index.calledFunctionByExpression.get(expression);
61756
+ if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index) && functionMustSynchronouslyRemoveLocationListener(calledFunction, registration, index, nextVisitingFunctions)) removalExecutions.push(expression);
61757
+ }
61758
+ return doNodesCoverEveryPathFromFunctionEntry(functionNode, removalExecutions, index.context);
61759
+ };
61760
+ const collectSynchronousLocationListenerRemovalExecutions = (functionNode, registration, index) => {
61761
+ const removalExecutions = /* @__PURE__ */ new Set();
61762
+ for (const removal of index.listenerRemovals) if (isDefinitelyMatchingLocationListenerRemoval(registration, removal) && index.context.cfg.enclosingFunction(removal.callExpression) === functionNode && canExecuteBeforeAsyncSuspension(removal.callExpression, functionNode, index)) removalExecutions.add(removal.callExpression);
61763
+ for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
61764
+ const calledFunction = index.calledFunctionByExpression.get(expression);
61765
+ if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index) && functionMustSynchronouslyRemoveLocationListener(calledFunction, registration, index, /* @__PURE__ */ new Set())) removalExecutions.add(expression);
61766
+ }
61767
+ return removalExecutions;
61768
+ };
61769
+ const collectImpliedExpressionExecutionBoundaries = (node, owner) => {
61770
+ const expressionBoundaries = [];
61771
+ let currentChild = node;
61772
+ let currentParent = currentChild.parent ?? null;
61773
+ while (currentParent && currentParent !== owner) {
61774
+ if (isNodeOfType(currentParent, "ConditionalExpression") && currentParent.test === currentChild) {
61775
+ const staticTestValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.test));
61776
+ if (staticTestValue !== null) expressionBoundaries.push(staticTestValue ? currentParent.consequent : currentParent.alternate);
61777
+ }
61778
+ if (isNodeOfType(currentParent, "LogicalExpression") && currentParent.left === currentChild) {
61779
+ const staticLeftValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.left));
61780
+ if (currentParent.operator === "&&" && staticLeftValue === true || currentParent.operator === "||" && staticLeftValue === false) expressionBoundaries.push(currentParent.right);
61781
+ }
61782
+ if (isNodeOfType(currentParent, "ConditionalExpression") && (currentParent.consequent === currentChild || currentParent.alternate === currentChild) || isNodeOfType(currentParent, "LogicalExpression") && currentParent.right === currentChild || isNodeOfType(currentParent, "AssignmentPattern") && currentParent.right === currentChild) expressionBoundaries.push(currentChild);
61783
+ currentChild = currentParent;
61784
+ currentParent = currentChild.parent ?? null;
61785
+ }
61786
+ return expressionBoundaries;
61787
+ };
61788
+ const canExecutionReachFunctionExitWithoutListenerRemoval = (executionNode, registration, index) => {
61789
+ if (!isNodeReachableWithinFunction(executionNode, index.context)) return false;
61790
+ const owner = index.context.cfg.enclosingFunction(executionNode);
61791
+ if (!owner) return false;
61792
+ const functionCfg = index.context.cfg.cfgFor(owner);
61793
+ const sourceBlock = functionCfg?.blockOf(executionNode);
61794
+ if (!functionCfg || !sourceBlock) return false;
61795
+ const expressionBoundaries = collectImpliedExpressionExecutionBoundaries(executionNode, owner);
61796
+ const matchingRemovalsByBlock = /* @__PURE__ */ new Map();
61797
+ for (const removalExecution of collectSynchronousLocationListenerRemovalExecutions(owner, registration, index)) {
61798
+ const removalBlock = functionCfg.blockOf(removalExecution);
61799
+ if (!removalBlock) continue;
61800
+ const blockRemovals = matchingRemovalsByBlock.get(removalBlock) ?? [];
61801
+ blockRemovals.push(removalExecution);
61802
+ matchingRemovalsByBlock.set(removalBlock, blockRemovals);
61803
+ }
61804
+ const sourceStart = getRangeStart(executionNode);
61805
+ const hasRemovalAfterExecution = (block) => {
61806
+ const removals = (matchingRemovalsByBlock.get(block) ?? []).filter((removal) => {
61807
+ if (block !== sourceBlock) return true;
61808
+ const removalStart = getRangeStart(removal);
61809
+ return sourceStart !== null && removalStart !== null && sourceStart < removalStart;
61810
+ });
61811
+ if (collectExpressionPathCoverageNodes(owner, removals, index.context).size > 0) return true;
61812
+ if (block !== sourceBlock) return false;
61813
+ return expressionBoundaries.some((expressionBoundary) => collectExpressionPathCoverageNodes(owner, removals, index.context, expressionBoundary).size > 0);
61814
+ };
61815
+ const visitedBlocks = /* @__PURE__ */ new Set();
61816
+ const pendingBlocks = [sourceBlock];
61817
+ while (pendingBlocks.length > 0) {
61818
+ const block = pendingBlocks.pop();
61819
+ if (!block || visitedBlocks.has(block)) continue;
61820
+ visitedBlocks.add(block);
61821
+ if (hasRemovalAfterExecution(block)) continue;
61822
+ for (const edge of block.successors) {
61823
+ if (edge.kind === "throw") continue;
61824
+ if (edge.to === functionCfg.exit) return true;
61825
+ pendingBlocks.push(edge.to);
61826
+ }
61827
+ }
61828
+ return false;
61829
+ };
61830
+ const isListenerActiveAtMountedExit = (executionNode, registration, index, visitedExecutions = /* @__PURE__ */ new Set()) => {
61831
+ if (visitedExecutions.has(executionNode)) return false;
61832
+ if (!canExecutionReachFunctionExitWithoutListenerRemoval(executionNode, registration, index)) return false;
61833
+ const owner = index.context.cfg.enclosingFunction(executionNode);
61834
+ if (!owner) return false;
61835
+ if (owner === index.componentFunction || index.effectCallbacks.has(owner)) return true;
61836
+ const nextVisitedExecutions = new Set(visitedExecutions);
61837
+ nextVisitedExecutions.add(executionNode);
61838
+ for (const callSite of index.callSitesByFunction.get(owner) ?? []) if (isListenerActiveAtMountedExit(callSite, registration, index, nextVisitedExecutions)) return true;
61839
+ for (const invocation of index.synchronousInvocationsByFunction.get(owner) ?? []) if (isListenerActiveAtMountedExit(invocation, registration, index, nextVisitedExecutions)) return true;
61840
+ return false;
61841
+ };
61842
+ const collectMountedListenerFunctions = (index) => {
61843
+ for (const registration of index.listenerRegistrations) if (isListenerActiveAtMountedExit(registration.callExpression, registration, index)) index.mountedListenerFunctions.add(registration.listenerFunction);
61844
+ };
61845
+ const setterArgumentMutatesLocation = (setterCall, index) => {
61846
+ if (!isNodeOfType(setterCall, "CallExpression")) return false;
61847
+ return (setterCall.arguments ?? []).some((argument) => {
61848
+ if (isNodeOfType(argument, "SpreadElement")) return false;
61849
+ const updaterFunction = resolveExactLocalFunction(argument, index.context.scopes);
61850
+ return Boolean(isFunctionLike$1(updaterFunction) && functionMaySynchronouslyMutateLocation(updaterFunction, index, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set()));
61851
+ });
61852
+ };
61853
+ const executionAnchorInvalidatesLocationSnapshot = (executionAnchor, index, visitedExecutions = /* @__PURE__ */ new Set()) => {
61854
+ if (visitedExecutions.has(executionAnchor)) return false;
61855
+ if (!isNodeReachableWithinFunction(executionAnchor, index.context)) return false;
61856
+ const owner = index.context.cfg.enclosingFunction(executionAnchor);
61857
+ if (!owner) return false;
61858
+ if (index.mountedListenerFunctions.has(owner)) return true;
61859
+ if (setterArgumentMutatesLocation(executionAnchor, index) || [...collectLocationMutationExecutions(owner, index)].some((mutationExecution) => canNodeReachNode(mutationExecution, executionAnchor, index) || canReactBatchMutationAfterExecution(executionAnchor, mutationExecution, owner, index))) return true;
61860
+ const nextVisitedExecutions = new Set(visitedExecutions);
61861
+ nextVisitedExecutions.add(executionAnchor);
61862
+ for (const callSite of index.callSitesByFunction.get(owner) ?? []) if (executionAnchorInvalidatesLocationSnapshot(callSite, index, nextVisitedExecutions)) return true;
61863
+ for (const invocation of index.synchronousInvocationsByFunction.get(owner) ?? []) if (executionAnchorInvalidatesLocationSnapshot(invocation, index, nextVisitedExecutions)) return true;
61864
+ return false;
61865
+ };
61866
+ const setterInvalidatesGlobalLocationSnapshot = (setterBindingIdentifier, index) => {
61867
+ const setterSymbol = index.context.scopes.symbolFor(setterBindingIdentifier);
61868
+ if (!setterSymbol) return false;
61869
+ return [...index.callsByCalleeSymbolId.get(setterSymbol.id) ?? []].some((setterCall) => executionAnchorInvalidatesLocationSnapshot(setterCall, index));
61870
+ };
61871
+ const createExternalLocationInvalidationChecker = ({ componentBody, componentFunction, context, directRenderNames, renderReachableExpressions }) => {
61872
+ if (!hasRenderReachableLocationSnapshotRead(componentBody, renderReachableExpressions, directRenderNames, context.scopes)) return () => false;
61873
+ const locationInvalidationIndex = buildLocationInvalidationIndex(componentBody, componentFunction, context);
61874
+ collectMountedListenerFunctions(locationInvalidationIndex);
61875
+ collectExactReadonlyCalleeCalls(locationInvalidationIndex);
61876
+ return (setterBindingIdentifier) => setterInvalidatesGlobalLocationSnapshot(setterBindingIdentifier, locationInvalidationIndex);
61877
+ };
61878
+ //#endregion
60138
61879
  //#region src/plugin/rules/state-and-effects/rerender-state-only-in-handlers.ts
60139
61880
  const isInsideConditionTest = (identifier, stopAt) => {
60140
61881
  let current = identifier;
@@ -60256,7 +61997,8 @@ const rerenderStateOnlyInHandlers = defineRule({
60256
61997
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
60257
61998
  const bindings = collectUseStateBindings(componentBody, context.scopes);
60258
61999
  if (bindings.length === 0) return;
60259
- if (collectRenderReachableExpressions(componentBody).length === 0) return;
62000
+ const renderReachableExpressions = collectRenderReachableExpressions(componentBody);
62001
+ if (renderReachableExpressions.length === 0) return;
60260
62002
  const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
60261
62003
  const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
60262
62004
  const directRenderNames = collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames);
@@ -60270,12 +62012,22 @@ const rerenderStateOnlyInHandlers = defineRule({
60270
62012
  for (const effectInfo of effectInfos) for (const dependencyName of effectInfo.dependencyNames) if (!selfEchoValueNames.has(dependencyName)) effectConsumedNames.add(dependencyName);
60271
62013
  for (const hookArgumentName of collectCustomHookArgumentNames(componentBody, context.scopes)) effectConsumedNames.add(hookArgumentName);
60272
62014
  for (const reachableName of expandTransitiveDependencies(effectConsumedNames, dependencyGraph)) renderReachableNames.add(reachableName);
62015
+ const componentFunction = context.cfg.enclosingFunction(componentBody);
62016
+ const doesSetterInvalidateExternalLocation = isFunctionLike$1(componentFunction) ? createExternalLocationInvalidationChecker({
62017
+ componentBody,
62018
+ componentFunction,
62019
+ context,
62020
+ directRenderNames,
62021
+ renderReachableExpressions
62022
+ }) : () => false;
60273
62023
  const calledSetterNames = /* @__PURE__ */ new Set();
60274
62024
  walkAst(componentBody, (child) => {
60275
62025
  if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && setterNames.has(child.callee.name)) calledSetterNames.add(child.callee.name);
60276
62026
  });
60277
62027
  for (const binding of bindings) {
60278
62028
  if (renderReachableNames.has(binding.valueName)) continue;
62029
+ const setterBindingIdentifier = isNodeOfType(binding.declarator.id, "ArrayPattern") ? binding.declarator.id.elements?.[1] : null;
62030
+ if (setterBindingIdentifier && doesSetterInvalidateExternalLocation(setterBindingIdentifier)) continue;
60279
62031
  if (binding.valueName === "_" || binding.valueName.startsWith("_")) continue;
60280
62032
  const setterSuffix = binding.setterName.slice(3);
60281
62033
  if (/^(TriggerRender|ForceUpdate|Rerender|ForceRender|Tick|Bump|BumpVersion|InvalidateRender|Refresh|Repaint)$/i.test(setterSuffix)) continue;
@@ -60828,6 +62580,7 @@ const rnListDataMapped = defineRule({
60828
62580
  title: "List data rebuilt every render",
60829
62581
  tags: ["test-noise"],
60830
62582
  requires: ["react-native"],
62583
+ disabledWhen: ["react-compiler"],
60831
62584
  severity: "warn",
60832
62585
  recommendation: "This builds a new array each time the parent redraws, so every row redraws too. Wrap it in `useMemo(() => items.map(...), [items])` to keep the same array.",
60833
62586
  create: (context) => ({ JSXOpeningElement(node) {
@@ -63181,6 +64934,66 @@ const roleHasRequiredAriaProps = defineRule({
63181
64934
  } })
63182
64935
  });
63183
64936
  //#endregion
64937
+ //#region src/plugin/constants/global-aria-properties.ts
64938
+ const GLOBAL_ARIA_PROPERTIES = new Set([
64939
+ "aria-atomic",
64940
+ "aria-braillelabel",
64941
+ "aria-brailleroledescription",
64942
+ "aria-busy",
64943
+ "aria-controls",
64944
+ "aria-current",
64945
+ "aria-describedby",
64946
+ "aria-description",
64947
+ "aria-details",
64948
+ "aria-disabled",
64949
+ "aria-dropeffect",
64950
+ "aria-errormessage",
64951
+ "aria-flowto",
64952
+ "aria-grabbed",
64953
+ "aria-haspopup",
64954
+ "aria-hidden",
64955
+ "aria-invalid",
64956
+ "aria-keyshortcuts",
64957
+ "aria-label",
64958
+ "aria-labelledby",
64959
+ "aria-live",
64960
+ "aria-owns",
64961
+ "aria-relevant",
64962
+ "aria-roledescription"
64963
+ ]);
64964
+ //#endregion
64965
+ //#region src/plugin/constants/prohibited-aria-properties-by-role.ts
64966
+ const ACCESSIBLE_NAME_PROPERTIES = new Set([
64967
+ "aria-braillelabel",
64968
+ "aria-label",
64969
+ "aria-labelledby"
64970
+ ]);
64971
+ const PROHIBITED_ARIA_PROPERTIES_BY_ROLE = {
64972
+ caption: ACCESSIBLE_NAME_PROPERTIES,
64973
+ code: ACCESSIBLE_NAME_PROPERTIES,
64974
+ definition: ACCESSIBLE_NAME_PROPERTIES,
64975
+ deletion: ACCESSIBLE_NAME_PROPERTIES,
64976
+ emphasis: ACCESSIBLE_NAME_PROPERTIES,
64977
+ generic: new Set([
64978
+ "aria-braillelabel",
64979
+ "aria-brailleroledescription",
64980
+ "aria-label",
64981
+ "aria-labelledby",
64982
+ "aria-roledescription"
64983
+ ]),
64984
+ insertion: ACCESSIBLE_NAME_PROPERTIES,
64985
+ mark: ACCESSIBLE_NAME_PROPERTIES,
64986
+ none: ACCESSIBLE_NAME_PROPERTIES,
64987
+ paragraph: ACCESSIBLE_NAME_PROPERTIES,
64988
+ presentation: ACCESSIBLE_NAME_PROPERTIES,
64989
+ strong: ACCESSIBLE_NAME_PROPERTIES,
64990
+ subscript: ACCESSIBLE_NAME_PROPERTIES,
64991
+ superscript: ACCESSIBLE_NAME_PROPERTIES,
64992
+ term: ACCESSIBLE_NAME_PROPERTIES,
64993
+ time: ACCESSIBLE_NAME_PROPERTIES,
64994
+ tooltip: ACCESSIBLE_NAME_PROPERTIES
64995
+ };
64996
+ //#endregion
63184
64997
  //#region src/plugin/constants/role-supports-aria-props.ts
63185
64998
  const ROLE_SUPPORTS_ARIA_PROPS = {
63186
64999
  alert: new Set([
@@ -66281,6 +68094,12 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
66281
68094
  ])
66282
68095
  };
66283
68096
  //#endregion
68097
+ //#region src/plugin/utils/is-aria-property-supported-by-role.ts
68098
+ const isAriaPropertySupportedByRole = (role, property) => {
68099
+ if (PROHIBITED_ARIA_PROPERTIES_BY_ROLE[role]?.has(property)) return false;
68100
+ return ROLE_SUPPORTS_ARIA_PROPS[role]?.has(property) === true || GLOBAL_ARIA_PROPERTIES.has(property);
68101
+ };
68102
+ //#endregion
66284
68103
  //#region src/plugin/rules/a11y/role-supports-aria-props.ts
66285
68104
  const buildMessageDefault = (roles, propName) => {
66286
68105
  return `Screen reader users get no help from \`${propName}\` because role ${roles.map((role) => `\`${role}\``).join(" / ")} ignores it, so remove it or change the role.`;
@@ -66317,16 +68136,10 @@ const roleSupportsAriaProps = defineRule({
66317
68136
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
66318
68137
  const roleCandidates = roleAttribute ? getJsxPropStaticStringValues(roleAttribute, context.scopes) : [getImplicitRole(node, elementType, context.scopes)].filter((role) => role !== null);
66319
68138
  if (roleCandidates === null || roleCandidates.length === 0) return;
66320
- const supportedSets = [];
66321
- for (const role of roleCandidates) {
66322
- if (!VALID_ARIA_ROLES.has(role)) return;
66323
- const supported = ROLE_SUPPORTS_ARIA_PROPS[role];
66324
- if (!supported) return;
66325
- supportedSets.push(supported);
66326
- }
68139
+ for (const role of roleCandidates) if (!VALID_ARIA_ROLES.has(role)) return;
66327
68140
  const isImplicit = !roleAttribute;
66328
68141
  for (const { attribute, propName } of ariaAttributes) {
66329
- if (supportedSets.some((supported) => supported.has(propName))) continue;
68142
+ if (roleCandidates.some((role) => isAriaPropertySupportedByRole(role, propName))) continue;
66330
68143
  context.report({
66331
68144
  node: attribute,
66332
68145
  message: isImplicit ? buildMessageImplicit(roleCandidates[0], propName, elementType) : buildMessageDefault(roleCandidates, propName)
@@ -71390,18 +73203,6 @@ const reactDoctorRules = [
71390
73203
  requires: [...new Set(["react", ...jsxNoScriptUrl.requires ?? []])]
71391
73204
  }
71392
73205
  },
71393
- {
71394
- key: "react-doctor/jsx-no-target-blank",
71395
- id: "jsx-no-target-blank",
71396
- source: "react-doctor",
71397
- originallyExternal: true,
71398
- rule: {
71399
- ...jsxNoTargetBlank,
71400
- framework: "global",
71401
- category: "Security",
71402
- requires: [...new Set(["react", ...jsxNoTargetBlank.requires ?? []])]
71403
- }
71404
- },
71405
73206
  {
71406
73207
  key: "react-doctor/jsx-no-undef",
71407
73208
  id: "jsx-no-undef",
@@ -75108,7 +76909,7 @@ const EXTERNAL_RULES = [
75108
76909
  {
75109
76910
  key: "react-hooks-js/set-state-in-effect",
75110
76911
  source: "react-compiler",
75111
- severity: "error"
76912
+ severity: "warn"
75112
76913
  },
75113
76914
  {
75114
76915
  key: "react-hooks-js/globals",