oxlint-plugin-react-doctor 0.8.1-dev.82c4a12 → 0.8.1-dev.88114a2

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 +0 -46
  2. package/dist/index.js +2015 -597
  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,
@@ -10380,7 +10380,7 @@ const WRAPPER_LABEL_PROP = "label";
10380
10380
  const SELECT_ELEMENT = "select";
10381
10381
  const DEFAULT_DEPTH = 5;
10382
10382
  const MAX_DEPTH = 25;
10383
- const resolveSettings$46 = (settings) => {
10383
+ const resolveSettings$45 = (settings) => {
10384
10384
  const reactDoctor = settings?.["react-doctor"];
10385
10385
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.controlHasAssociatedLabel ?? {} : {};
10386
10386
  return {
@@ -10713,7 +10713,7 @@ const controlHasAssociatedLabel = defineRule({
10713
10713
  recommendation: "Give every interactive control a label screen readers can read.",
10714
10714
  category: "Accessibility",
10715
10715
  create: (context) => {
10716
- const settings = resolveSettings$46(context.settings);
10716
+ const settings = resolveSettings$45(context.settings);
10717
10717
  const isTestlikeFile = isTestlikeFilename(context.filename);
10718
10718
  const iconComponentNames = /* @__PURE__ */ new Set();
10719
10719
  const iconNamespaceNames = /* @__PURE__ */ new Set();
@@ -12665,7 +12665,7 @@ const DEFAULT_ADDITIONAL_HOCS = [
12665
12665
  "lazy",
12666
12666
  "withTracking"
12667
12667
  ];
12668
- const resolveSettings$45 = (settings) => {
12668
+ const resolveSettings$44 = (settings) => {
12669
12669
  const reactDoctor = settings?.["react-doctor"];
12670
12670
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.displayName ?? {} : {};
12671
12671
  const additionalHoCs = new Set(ruleSettings.additionalHoCs ?? DEFAULT_ADDITIONAL_HOCS);
@@ -12847,7 +12847,7 @@ const displayName = defineRule({
12847
12847
  recommendation: "Give each component a `displayName` so DevTools shows a clear name.",
12848
12848
  category: "Architecture",
12849
12849
  create: (context) => {
12850
- const settings = resolveSettings$45(context.settings);
12850
+ const settings = resolveSettings$44(context.settings);
12851
12851
  const ignoreNamed = !settings.ignoreTranspilerName;
12852
12852
  const reportAt = (node) => {
12853
12853
  context.report({
@@ -13827,6 +13827,116 @@ const findRenderPhaseComponentOrHook = (node, scopes) => {
13827
13827
  return null;
13828
13828
  };
13829
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
13830
13940
  //#region src/plugin/utils/get-function-binding-name.ts
13831
13941
  const getFunctionBindingIdentifier$1 = (functionNode) => {
13832
13942
  if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
@@ -13864,14 +13974,19 @@ const isAstDescendant = (inner, outer) => {
13864
13974
  };
13865
13975
  //#endregion
13866
13976
  //#region src/plugin/utils/react-ref-origin.ts
13867
- const resolveReactRefSymbol = (memberExpression, scopes) => {
13977
+ const REACT_REF_API_NAMES = new Set(["createRef", "useRef"]);
13978
+ const resolveReactRefSymbol = (memberExpression, scopes, options = {}) => {
13868
13979
  const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
13869
13980
  if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticPropertyName(memberExpression) !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
13870
13981
  const symbol = resolveConstIdentifierAlias(receiver, scopes);
13871
13982
  if (!symbol?.initializer) return null;
13872
13983
  const initializer = stripParenExpression(symbol.initializer);
13873
13984
  if (!isNodeOfType(initializer, "CallExpression")) return null;
13874
- 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;
13875
13990
  };
13876
13991
  const resolveReactRefCurrentOriginSymbol = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13877
13992
  const expression = stripParenExpression(node);
@@ -13970,6 +14085,20 @@ const isNodeReachableWithinFunction = (node, context) => {
13970
14085
  return false;
13971
14086
  };
13972
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
13973
14102
  //#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
13974
14103
  const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
13975
14104
  const REPLAYABLE_ITERATOR_COLLECTION_CACHE = /* @__PURE__ */ new WeakMap();
@@ -14018,7 +14147,7 @@ const resolveExpressionKey = (expression, context, visitedSymbolIds = /* @__PURE
14018
14147
  return objectKey ? `${objectKey}.${unwrappedExpression.property.name}` : null;
14019
14148
  }
14020
14149
  if (isNodeOfType(unwrappedExpression, "ThisExpression")) return "this";
14021
- 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)}`;
14022
14151
  if (isFunctionLike$1(unwrappedExpression)) {
14023
14152
  const rangeStart = getRangeStart(unwrappedExpression);
14024
14153
  return rangeStart === null ? null : `function:${rangeStart}`;
@@ -14047,9 +14176,11 @@ const resolveForEachProjection = (expression, context) => {
14047
14176
  const collectionKey = resolveExpressionKey(forEachCallee.object, context);
14048
14177
  if (!collectionKey) return null;
14049
14178
  const firstParameter = callbackNode.params[0];
14050
- const bindingProperty = symbol.bindingIdentifier.parent;
14051
- const propertyName = isNodeOfType(firstParameter, "ObjectPattern") && isNodeOfType(bindingProperty, "Property") && bindingProperty.parent === firstParameter && bindingProperty.value === symbol.bindingIdentifier ? getStaticPropertyKeyName(bindingProperty) : null;
14052
- const parameterProjection = firstParameter === symbol.bindingIdentifier ? "value" : propertyName;
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;
14053
14184
  if (!parameterProjection) return null;
14054
14185
  return {
14055
14186
  collectionKey,
@@ -14061,13 +14192,23 @@ const resolveForEachProjectionKey = (expression, context) => {
14061
14192
  return projection ? `forEach:${projection.collectionKey}:${projection.projectionKey}` : null;
14062
14193
  };
14063
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
+ };
14064
14205
  const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqueOptionsIdentity) => {
14065
14206
  const capture = resolveEventListenerCapture(optionsNode, { allowIndeterminateEntries: true });
14066
14207
  if (capture !== null) return `capture:${String(capture)}`;
14067
14208
  if (!optionsNode) return null;
14068
14209
  const unwrappedOptions = stripParenExpression(optionsNode);
14069
14210
  if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) {
14070
- const optionsKey = allowOpaqueOptionsIdentity ? resolveResourceIdentityKey(unwrappedOptions, context) : null;
14211
+ const optionsKey = allowOpaqueOptionsIdentity ? resolveEventListenerCaptureValueIdentityKey(unwrappedOptions, context) : null;
14071
14212
  return optionsKey ? `options:${optionsKey}` : null;
14072
14213
  }
14073
14214
  let captureKey = "capture:false";
@@ -14082,7 +14223,7 @@ const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqu
14082
14223
  continue;
14083
14224
  }
14084
14225
  if (propertyName === "capture") {
14085
- const propertyValueKey = resolveResourceIdentityKey(property.value, context);
14226
+ const propertyValueKey = resolveEventListenerCaptureValueIdentityKey(property.value, context);
14086
14227
  captureKey = propertyValueKey ? `capture-value:${propertyValueKey}` : null;
14087
14228
  }
14088
14229
  }
@@ -14264,30 +14405,6 @@ const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, conte
14264
14405
  }
14265
14406
  return matchingBlocks.size > 0;
14266
14407
  };
14267
- const doMatchingNodesCoverEveryPathFromFunctionEntry = (owner, matchingNodes, context) => {
14268
- const functionCfg = context.cfg.cfgFor(owner);
14269
- if (!functionCfg) return false;
14270
- const matchingBlocks = new Set(matchingNodes.flatMap((matchingNode) => {
14271
- if (context.cfg.enclosingFunction(matchingNode) !== owner) return [];
14272
- const matchingBlock = functionCfg.blockOf(matchingNode);
14273
- return matchingBlock ? [matchingBlock] : [];
14274
- }));
14275
- if (matchingBlocks.size === 0) return false;
14276
- const visitedBlocks = new Set([functionCfg.entry]);
14277
- const pendingBlocks = [functionCfg.entry];
14278
- while (pendingBlocks.length > 0) {
14279
- const currentBlock = pendingBlocks.pop();
14280
- if (!currentBlock) break;
14281
- if (matchingBlocks.has(currentBlock)) continue;
14282
- for (const edge of currentBlock.successors) {
14283
- if (edge.to === functionCfg.exit) return false;
14284
- if (visitedBlocks.has(edge.to)) continue;
14285
- visitedBlocks.add(edge.to);
14286
- pendingBlocks.push(edge.to);
14287
- }
14288
- }
14289
- return true;
14290
- };
14291
14408
  const removeSynchronouslyReleasedUsages = (callback, usages, context) => {
14292
14409
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
14293
14410
  if (!isNodeOfType(callback.body, "BlockStatement")) return usages;
@@ -14530,18 +14647,6 @@ const findPushedResourceCollectionKey = (usage, context) => {
14530
14647
  return memberNode.property.name === "forEach" || memberNode.property.name === "push";
14531
14648
  }) ? resolveExpressionKey(pushCallee.object, context) : null;
14532
14649
  };
14533
- const isWithinAssignmentTarget = (identifier) => {
14534
- let currentNode = identifier;
14535
- let parentNode = currentNode.parent;
14536
- while (parentNode) {
14537
- if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
14538
- if (isNodeOfType(parentNode, "UpdateExpression") || isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "delete") return parentNode.argument === currentNode;
14539
- if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
14540
- currentNode = parentNode;
14541
- parentNode = currentNode.parent;
14542
- }
14543
- return false;
14544
- };
14545
14650
  const resolveStableValue = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
14546
14651
  if (!expression) return null;
14547
14652
  const unwrappedExpression = stripParenExpression(expression);
@@ -14665,7 +14770,7 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
14665
14770
  const helperFunction = isNodeOfType(stableHelperFunction, "Identifier") ? resolveSingleAssignedCleanupFunction(stableHelperFunction, usage, context) : stableHelperFunction;
14666
14771
  if (helperFunction && isFunctionLike$1(helperFunction) && !helperFunction.async && !helperFunction.generator && doesCleanupFunctionReleaseUsage(helperFunction, usage, context, new Set(visitedFunctions))) matchingLoopOrHelperAnchors.push(cleanupCall);
14667
14772
  });
14668
- return didCleanupFunctionMatch || doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, matchingLoopOrHelperAnchors, context);
14773
+ return didCleanupFunctionMatch || doNodesCoverEveryPathFromFunctionEntry(cleanupFunction, matchingLoopOrHelperAnchors, context);
14669
14774
  };
14670
14775
  const doesBoundCleanupReleaseUsage = (expression, usage, context) => {
14671
14776
  const callExpression = stripParenExpression(expression);
@@ -14694,7 +14799,7 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
14694
14799
  walkInsideStatementBlocks(callback.body, (child) => {
14695
14800
  if (isNodeOfType(child, "ReturnStatement") && child.argument && doesReturnedValueReleaseUsage(stripParenExpression(child.argument))) matchingCleanupReturns.push(child);
14696
14801
  });
14697
- return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
14802
+ return doNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
14698
14803
  };
14699
14804
  const doesTestRequireLiveExpressionKey = (test, expressionKey, context) => {
14700
14805
  if (resolveExpressionKey(test, context) === expressionKey) return true;
@@ -14718,7 +14823,24 @@ const findLiveExpressionGuardForRelease = (releaseCall, owner, expressionKey, co
14718
14823
  return null;
14719
14824
  };
14720
14825
  const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => usage.handleKey === null ? null : findLiveExpressionGuardForRelease(releaseCall, owner, usage.handleKey, context);
14721
- 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) => {
14722
14844
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
14723
14845
  const functionCfg = context.cfg.cfgFor(callback);
14724
14846
  const usageBlock = functionCfg?.blockOf(usage.node);
@@ -14729,7 +14851,8 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
14729
14851
  if (!isNodeOfType(child, "CallExpression")) return;
14730
14852
  const releaseStart = getRangeStart(child);
14731
14853
  const handleGuard = findDirectHandleGuardForRelease(child, callback, usage, context);
14732
- 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;
14733
14856
  if (doesReleaseCallMatchUsage(child, usage, context)) {
14734
14857
  matchingReleaseAnchors.push(handleGuard ?? child);
14735
14858
  return;
@@ -14737,7 +14860,7 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
14737
14860
  const helperFunction = resolveStableValue(child.callee, context);
14738
14861
  if (helperFunction && isFunctionLike$1(helperFunction) && doesCleanupFunctionReleaseUsage(helperFunction, usage, context)) matchingReleaseAnchors.push(handleGuard ?? child);
14739
14862
  });
14740
- return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
14863
+ return allowUnreleasedPathsWithoutUsage ? doMatchingNodesCoverEveryPathBeforeUsage(usage.node, matchingReleaseAnchors, callback, context) : doNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
14741
14864
  };
14742
14865
  const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
14743
14866
  const componentFunction = findEnclosingFunction$1(callback);
@@ -14757,7 +14880,7 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
14757
14880
  });
14758
14881
  return didFindUnmountCleanup;
14759
14882
  };
14760
- 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");
14761
14884
  const collectBlockingBooleanStates = (expression, blockedExpressionValue, guardNode, context) => {
14762
14885
  const unwrappedExpression = stripParenExpression(expression);
14763
14886
  if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, guardNode, context);
@@ -14988,7 +15111,7 @@ const hasGuardedRefOwnedNestedCleanup = (callback, usage, cleanupReturns, contex
14988
15111
  const usageFunction = findEnclosingFunction$1(usage.node);
14989
15112
  const usageExpression = findTransparentExpressionRoot(usage.node);
14990
15113
  const usageAssignment = usageExpression.parent;
14991
- 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;
14992
15115
  const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
14993
15116
  if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
14994
15117
  const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
@@ -15036,7 +15159,7 @@ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) =>
15036
15159
  });
15037
15160
  }
15038
15161
  });
15039
- if (!doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, globalReleaseProofs.map((releaseProof) => releaseProof.anchor), context)) return false;
15162
+ if (!doNodesCoverEveryPathFromFunctionEntry(cleanupFunction, globalReleaseProofs.map((releaseProof) => releaseProof.anchor), context)) return false;
15040
15163
  globalReleaseProofsByCleanup.set(cleanupFunction, globalReleaseProofs);
15041
15164
  }
15042
15165
  const handleAssignments = handleSymbol.references.filter((reference) => isWithinAssignmentTarget(reference.identifier));
@@ -15249,7 +15372,7 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
15249
15372
  });
15250
15373
  const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
15251
15374
  const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
15252
- return doMatchingNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
15375
+ return doNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
15253
15376
  };
15254
15377
  const findDirectExhaustiveForEachCleanupFunction = (releaseNode, requiredCollectionKeys, context) => {
15255
15378
  let currentNode = findTransparentExpressionRoot(releaseNode);
@@ -15262,7 +15385,7 @@ const findDirectExhaustiveForEachCleanupFunction = (releaseNode, requiredCollect
15262
15385
  const isDirectConciseBody = ownerFunction.body === currentNode;
15263
15386
  const statementNode = currentNode.parent;
15264
15387
  const isDirectBlockStatement = isNodeOfType(ownerFunction.body, "BlockStatement") && isNodeOfType(statementNode, "ExpressionStatement") && statementNode.parent === ownerFunction.body;
15265
- if (!isDirectConciseBody && !isDirectBlockStatement || !doMatchingNodesCoverEveryPathFromFunctionEntry(ownerFunction, [isDirectBlockStatement ? statementNode : currentNode], context)) return null;
15388
+ if (!isDirectConciseBody && !isDirectBlockStatement || !doNodesCoverEveryPathFromFunctionEntry(ownerFunction, [isDirectBlockStatement ? statementNode : currentNode], context)) return null;
15266
15389
  const forEachCall = findEnclosingForEachCall(ownerFunction);
15267
15390
  if (!forEachCall) return replayedCollectionKeys.size === requiredCollectionKeys.size && isReturnedEffectCleanupFunction(ownerFunction, context) ? ownerFunction : null;
15268
15391
  const forEachCallee = stripParenExpression(forEachCall.callee);
@@ -15350,7 +15473,7 @@ const hasSafeForEachProjectionCleanup = (registrationCall, releaseCall, context)
15350
15473
  const registrationEventKey = resolveResourceIdentityKey(registrationCall.arguments?.[0], context);
15351
15474
  const releaseEventKey = resolveResourceIdentityKey(releaseCall.arguments?.[0], context);
15352
15475
  const doesHandlerlessOffReleaseEveryRegistration = releaseVerbName === "off" && !releaseHandler && (releaseCall.arguments?.length === 0 || registrationEventKey !== null && registrationEventKey === releaseEventKey);
15353
- if (Boolean(releaseFunction && isFunctionLike$1(releaseFunction) && isReturnedEffectCleanupFunction(releaseFunction, context) && doMatchingNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseCall], context)) && (releaseVerbName !== null && UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName) || doesHandlerlessOffReleaseEveryRegistration)) return true;
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;
15354
15477
  const projections = [
15355
15478
  registrationCallee.object,
15356
15479
  registrationCall.arguments?.[0],
@@ -15549,7 +15672,7 @@ const hasEffectCleanupInvocation = (storage, usage, context) => {
15549
15672
  if (!cleanupFunction || !cleanupFunctionInvokesRef(cleanupFunction)) return;
15550
15673
  matchingReturns.push(child);
15551
15674
  });
15552
- return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15675
+ return doNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15553
15676
  };
15554
15677
  let didFindInvocation = false;
15555
15678
  walkAst(componentFunction.body, (child) => {
@@ -15601,7 +15724,7 @@ const isRetainedDisposerRefRelease = (releaseNode, usage, context) => {
15601
15724
  return findRetainedDisposerStorages(disposerFunction, usage, context).some((storage) => isRetainedDisposerStorageEstablished(storage, usage, context) && !hasUnsafeRetainedDisposerOverwrite(storage, usage, context) && (hasEffectCleanupInvocation(storage, usage, context) || hasCallbackRefReplacementInvocation(storage, usage, context)));
15602
15725
  };
15603
15726
  const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
15604
- 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;
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;
15605
15728
  const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
15606
15729
  if (!isNodeOfType(releaseCall, "CallExpression")) return false;
15607
15730
  if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], releaseCall.arguments?.[2], context)) return false;
@@ -15829,7 +15952,7 @@ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retaine
15829
15952
  walkInsideStatementBlocks(effectCallback.body, (child) => {
15830
15953
  if (isNodeOfType(child, "ReturnStatement") && child.argument && matchesReturnedCleanup(stripParenExpression(child.argument))) matchingReturns.push(child);
15831
15954
  });
15832
- return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15955
+ return doNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15833
15956
  };
15834
15957
  const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
15835
15958
  const componentFunction = findEnclosingFunction$1(retainedFunction);
@@ -15872,15 +15995,6 @@ const findUnconditionalReturnStatement = (expression, ownerFunction) => {
15872
15995
  const returnStatement = expressionRoot.parent;
15873
15996
  return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === expressionRoot && findEnclosingFunction$1(returnStatement) === ownerFunction ? returnStatement : null;
15874
15997
  };
15875
- const getFinalSequenceExpressionValue = (expression) => {
15876
- let finalExpression = stripParenExpression(expression);
15877
- while (isNodeOfType(finalExpression, "SequenceExpression")) {
15878
- const sequenceResult = finalExpression.expressions.at(-1);
15879
- if (!sequenceResult) break;
15880
- finalExpression = stripParenExpression(sequenceResult);
15881
- }
15882
- return finalExpression;
15883
- };
15884
15998
  const doesResourceResultEscape = (resourceNode, allowReturnedResourceEscape, allowConciseReturnEscape, context) => {
15885
15999
  if (!allowReturnedResourceEscape) return false;
15886
16000
  let currentNode = resourceNode;
@@ -16129,7 +16243,7 @@ const isReactRefCallbackCleanupOwnedByEffect = (retainedFunction, cleanupFunctio
16129
16243
  if (child !== returnedCleanupFunction.body && isFunctionLike$1(child)) return false;
16130
16244
  if (isNodeOfType(child, "CallExpression") && resolveRefOwnedCleanupFunction(child.callee, context) === cleanupFunction) matchingCalls.push(child);
16131
16245
  });
16132
- return doMatchingNodesCoverEveryPathFromFunctionEntry(returnedCleanupFunction, matchingCalls, context);
16246
+ return doNodesCoverEveryPathFromFunctionEntry(returnedCleanupFunction, matchingCalls, context);
16133
16247
  };
16134
16248
  const matchingReturns = [];
16135
16249
  walkInsideStatementBlocks(retainedFunction.body, (child) => {
@@ -18932,7 +19046,7 @@ const compileGlob = (pattern) => {
18932
19046
  };
18933
19047
  //#endregion
18934
19048
  //#region src/plugin/rules/react-builtins/forbid-component-props.ts
18935
- const resolveSettings$44 = (settings) => {
19049
+ const resolveSettings$43 = (settings) => {
18936
19050
  const reactDoctor = settings?.["react-doctor"];
18937
19051
  return { forbid: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidComponentProps ?? {} : {}).forbid ?? [] };
18938
19052
  };
@@ -18984,7 +19098,7 @@ const forbidComponentProps = defineRule({
18984
19098
  recommendation: "Configure blocked component props so callers cannot bypass the component API contract.",
18985
19099
  category: "Architecture",
18986
19100
  create: (context) => {
18987
- const entries = resolveSettings$44(context.settings).forbid.map(normalizeEntry);
19101
+ const entries = resolveSettings$43(context.settings).forbid.map(normalizeEntry);
18988
19102
  return { JSXOpeningElement(node) {
18989
19103
  if (entries.length === 0) return;
18990
19104
  if (!isSupportedJsxName(node.name)) return;
@@ -19013,7 +19127,7 @@ const forbidComponentProps = defineRule({
19013
19127
  //#endregion
19014
19128
  //#region src/plugin/rules/react-builtins/forbid-dom-props.ts
19015
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.`;
19016
- const resolveSettings$43 = (settings) => {
19130
+ const resolveSettings$42 = (settings) => {
19017
19131
  const reactDoctor = settings?.["react-doctor"];
19018
19132
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidDomProps ?? {} : {};
19019
19133
  const map = /* @__PURE__ */ new Map();
@@ -19035,7 +19149,7 @@ const forbidDomProps = defineRule({
19035
19149
  recommendation: "Configure blocked DOM props so plain HTML tags stay on the agreed DOM API surface.",
19036
19150
  category: "Architecture",
19037
19151
  create: (context) => {
19038
- const forbidMap = resolveSettings$43(context.settings);
19152
+ const forbidMap = resolveSettings$42(context.settings);
19039
19153
  return { JSXOpeningElement(node) {
19040
19154
  if (forbidMap.size === 0) return;
19041
19155
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
@@ -19098,7 +19212,7 @@ const isReactFunctionCall = (node, expectedCall) => {
19098
19212
  //#endregion
19099
19213
  //#region src/plugin/rules/react-builtins/forbid-elements.ts
19100
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.`;
19101
- const resolveSettings$42 = (settings) => {
19215
+ const resolveSettings$41 = (settings) => {
19102
19216
  const reactDoctor = settings?.["react-doctor"];
19103
19217
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidElements ?? {} : {};
19104
19218
  const map = /* @__PURE__ */ new Map();
@@ -19114,7 +19228,7 @@ const forbidElements = defineRule({
19114
19228
  recommendation: "Configure blocked elements so code stays on the approved UI primitives.",
19115
19229
  category: "Architecture",
19116
19230
  create: (context) => {
19117
- const forbidMap = resolveSettings$42(context.settings);
19231
+ const forbidMap = resolveSettings$41(context.settings);
19118
19232
  return {
19119
19233
  JSXOpeningElement(node) {
19120
19234
  if (forbidMap.size === 0) return;
@@ -19223,7 +19337,7 @@ const DEFAULT_HEADING_TAGS = [
19223
19337
  "h5",
19224
19338
  "h6"
19225
19339
  ];
19226
- const resolveSettings$41 = (settings) => {
19340
+ const resolveSettings$40 = (settings) => {
19227
19341
  const reactDoctor = settings?.["react-doctor"];
19228
19342
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.headingHasContent ?? {} : {};
19229
19343
  return { headingTags: new Set([...DEFAULT_HEADING_TAGS, ...ruleSettings.components ?? []]) };
@@ -19236,7 +19350,7 @@ const headingHasContent = defineRule({
19236
19350
  recommendation: "Put readable text in every heading.",
19237
19351
  category: "Accessibility",
19238
19352
  create: (context) => {
19239
- const settings = resolveSettings$41(context.settings);
19353
+ const settings = resolveSettings$40(context.settings);
19240
19354
  return { JSXOpeningElement(node) {
19241
19355
  const elementType = getElementType(node, context.settings);
19242
19356
  if (!settings.headingTags.has(elementType)) return;
@@ -19257,7 +19371,7 @@ const headingHasContent = defineRule({
19257
19371
  //#region src/plugin/rules/react-builtins/hook-use-state.ts
19258
19372
  const REQUIRE_DESTRUCTURE_MESSAGE = "`useState` should be destructured as `[value, setValue]` so readers can see the state value and setter together.";
19259
19373
  const NAMING_CONVENTION_MESSAGE = "This `useState` setter does not match its value name, so updates are harder to trace.";
19260
- const resolveSettings$40 = (settings) => {
19374
+ const resolveSettings$39 = (settings) => {
19261
19375
  const reactDoctor = settings?.["react-doctor"];
19262
19376
  return { allowDestructuredState: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.hookUseState ?? {} : {}).allowDestructuredState ?? false };
19263
19377
  };
@@ -19284,7 +19398,7 @@ const hookUseState = defineRule({
19284
19398
  recommendation: "Destructure `useState` as `const [thing, setThing] = useState(…)` so state reads and writes stay visible together.",
19285
19399
  category: "Architecture",
19286
19400
  create: (context) => {
19287
- const { allowDestructuredState } = resolveSettings$40(context.settings);
19401
+ const { allowDestructuredState } = resolveSettings$39(context.settings);
19288
19402
  return { CallExpression(node) {
19289
19403
  const callExpressionNode = node;
19290
19404
  if (!isReactFunctionCall(callExpressionNode, "useState")) return;
@@ -19417,7 +19531,7 @@ const hooksNoNanInDeps = defineRule({
19417
19531
  //#endregion
19418
19532
  //#region src/plugin/rules/a11y/html-has-lang.ts
19419
19533
  const MESSAGE$52 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
19420
- const resolveSettings$39 = (settings) => {
19534
+ const resolveSettings$38 = (settings) => {
19421
19535
  const reactDoctor = settings?.["react-doctor"];
19422
19536
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
19423
19537
  };
@@ -19454,7 +19568,7 @@ const htmlHasLang = defineRule({
19454
19568
  recommendation: "Set `<html lang=\"…\">` so screen readers know the page language.",
19455
19569
  category: "Accessibility",
19456
19570
  create: (context) => {
19457
- const settings = resolveSettings$39(context.settings);
19571
+ const settings = resolveSettings$38(context.settings);
19458
19572
  const tagSet = new Set(settings.htmlTags);
19459
19573
  return { JSXOpeningElement(node) {
19460
19574
  const tag = getElementType(node, context.settings);
@@ -19970,7 +20084,7 @@ const DEFAULT_REDUNDANT_WORDS = [
19970
20084
  "photo",
19971
20085
  "picture"
19972
20086
  ];
19973
- const resolveSettings$38 = (settings) => {
20087
+ const resolveSettings$37 = (settings) => {
19974
20088
  const reactDoctor = settings?.["react-doctor"];
19975
20089
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.imgRedundantAlt ?? {} : {};
19976
20090
  return {
@@ -20018,7 +20132,7 @@ const imgRedundantAlt = defineRule({
20018
20132
  category: "Accessibility",
20019
20133
  create: (context) => {
20020
20134
  if (isGeneratedImageRenderContext(context)) return {};
20021
- const settings = resolveSettings$38(context.settings);
20135
+ const settings = resolveSettings$37(context.settings);
20022
20136
  return { JSXOpeningElement(node) {
20023
20137
  const tag = getElementType(node, context.settings);
20024
20138
  if (!settings.components.includes(tag)) return;
@@ -20326,7 +20440,7 @@ const COMPOSITE_ITEM_ROLES = new Set([
20326
20440
  "row",
20327
20441
  "gridcell"
20328
20442
  ]);
20329
- const resolveSettings$37 = (settings) => {
20443
+ const resolveSettings$36 = (settings) => {
20330
20444
  const reactDoctor = settings?.["react-doctor"];
20331
20445
  return { tabbable: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.interactiveSupportsFocus ?? {} : {}).tabbable ?? DEFAULT_TABBABLE_ROLES };
20332
20446
  };
@@ -20338,7 +20452,7 @@ const interactiveSupportsFocus = defineRule({
20338
20452
  recommendation: "Add keyboard focus support so users can reach interactive elements without a pointer.",
20339
20453
  category: "Accessibility",
20340
20454
  create: (context) => {
20341
- const settings = resolveSettings$37(context.settings);
20455
+ const settings = resolveSettings$36(context.settings);
20342
20456
  const tabbableSet = new Set(settings.tabbable);
20343
20457
  return { JSXOpeningElement(node) {
20344
20458
  if (isLocalTestScaffoldJsx(node, context)) return;
@@ -22479,25 +22593,49 @@ const jsLengthCheckFirst = defineRule({
22479
22593
  });
22480
22594
  //#endregion
22481
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
+ ]);
22482
22602
  const isProvenGlobalObjectReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
22483
- const strippedExpression = stripParenExpression(expression);
22484
- if (!isNodeOfType(strippedExpression, "Identifier")) return false;
22485
- if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
22486
- const symbol = scopes.symbolFor(strippedExpression);
22487
- if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
22488
- visitedSymbolIds.add(symbol.id);
22489
- 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
+ }
22490
22621
  };
22491
22622
  const isProvenGlobalNamespaceReference = (expression, namespaceName, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
22492
- const strippedExpression = stripParenExpression(expression);
22493
- 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);
22494
22627
  if (strippedExpression.name === namespaceName && scopes.isGlobalReference(strippedExpression)) return true;
22495
22628
  const symbol = scopes.symbolFor(strippedExpression);
22496
- if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
22629
+ if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
22497
22630
  visitedSymbolIds.add(symbol.id);
22498
- 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;
22499
22638
  }
22500
- return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
22501
22639
  };
22502
22640
  //#endregion
22503
22641
  //#region src/plugin/rules/js-performance/js-min-max-loop.ts
@@ -23610,7 +23748,7 @@ const jsTosortedImmutable = defineRule({
23610
23748
  const NEVER_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
23611
23749
  const ALWAYS_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
23612
23750
  const FALSE_OMITTED_MESSAGE = (attributeName) => `\`${attributeName}={false}\` does nothing, so the explicit false value adds noise without changing output.`;
23613
- const resolveSettings$36 = (settings) => {
23751
+ const resolveSettings$35 = (settings) => {
23614
23752
  const reactDoctor = settings?.["react-doctor"];
23615
23753
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxBooleanValue ?? {} : {};
23616
23754
  return {
@@ -23628,7 +23766,7 @@ const jsxBooleanValue = defineRule({
23628
23766
  recommendation: "Use one boolean-attribute style so equivalent true props scan the same across the codebase.",
23629
23767
  category: "Architecture",
23630
23768
  create: (context) => {
23631
- const settings = resolveSettings$36(context.settings);
23769
+ const settings = resolveSettings$35(context.settings);
23632
23770
  const alwaysSet = new Set(settings.always);
23633
23771
  const neverSet = new Set(settings.never);
23634
23772
  return { JSXAttribute(node) {
@@ -23682,7 +23820,7 @@ const jsxBooleanValue = defineRule({
23682
23820
  const UNNECESSARY_BRACES_MESSAGE = "These curly braces wrap a literal value, so they add JSX noise without changing output.";
23683
23821
  const REQUIRED_BRACES_MESSAGE = "This JSX value needs `{ }` so React reads it as an expression instead of text.";
23684
23822
  const isAllowedMode = (value) => value === "always" || value === "never" || value === "ignore";
23685
- const resolveSettings$35 = (settings) => {
23823
+ const resolveSettings$34 = (settings) => {
23686
23824
  const reactDoctor = settings?.["react-doctor"];
23687
23825
  const ruleSettingsRaw = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxCurlyBracePresence : void 0;
23688
23826
  if (isAllowedMode(ruleSettingsRaw)) return {
@@ -23778,7 +23916,7 @@ const jsxCurlyBracePresence = defineRule({
23778
23916
  recommendation: "Use one JSX literal style so equivalent markup scans the same without extra JSX noise.",
23779
23917
  category: "Architecture",
23780
23918
  create: (context) => {
23781
- const settings = resolveSettings$35(context.settings);
23919
+ const settings = resolveSettings$34(context.settings);
23782
23920
  return {
23783
23921
  JSXAttribute(node) {
23784
23922
  const value = node.value;
@@ -23861,7 +23999,7 @@ const containsJsxElement = (root) => {
23861
23999
  //#region src/plugin/rules/react-builtins/jsx-filename-extension.ts
23862
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.`;
23863
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.`;
23864
- const resolveSettings$34 = (settings) => {
24002
+ const resolveSettings$33 = (settings) => {
23865
24003
  const reactDoctor = settings?.["react-doctor"];
23866
24004
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFilenameExtension ?? {} : {};
23867
24005
  return {
@@ -23883,7 +24021,7 @@ const jsxFilenameExtension = defineRule({
23883
24021
  recommendation: "Name JSX files with the configured extension so file names accurately signal which files contain JSX.",
23884
24022
  category: "Architecture",
23885
24023
  create: (context) => {
23886
- const settings = resolveSettings$34(context.settings);
24024
+ const settings = resolveSettings$33(context.settings);
23887
24025
  const allowedExtensions = normalizeExtensions(settings.extensions);
23888
24026
  const filename = normalizeFilename(context.filename ?? "fixture.tsx");
23889
24027
  const extensionOnly = path.extname(filename).slice(1);
@@ -23924,7 +24062,7 @@ const jsxFilenameExtension = defineRule({
23924
24062
  //#region src/plugin/rules/react-builtins/jsx-fragments.ts
23925
24063
  const SYNTAX_MESSAGE = "`<React.Fragment>` is used where shorthand fragments are configured, so similar wrappers look different across the codebase.";
23926
24064
  const ELEMENT_MESSAGE = "Fragment shorthand is used where explicit fragments are configured, so similar wrappers look different across the codebase.";
23927
- const resolveSettings$33 = (settings) => {
24065
+ const resolveSettings$32 = (settings) => {
23928
24066
  const reactDoctor = settings?.["react-doctor"];
23929
24067
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFragments ?? {} : {}).mode ?? "syntax" };
23930
24068
  };
@@ -23936,7 +24074,7 @@ const jsxFragments = defineRule({
23936
24074
  recommendation: "Use one fragment style so identical wrappers do not look different across files.",
23937
24075
  category: "Architecture",
23938
24076
  create: (context) => {
23939
- const { mode } = resolveSettings$33(context.settings);
24077
+ const { mode } = resolveSettings$32(context.settings);
23940
24078
  return {
23941
24079
  JSXElement(node) {
23942
24080
  if (mode !== "syntax") return;
@@ -23980,7 +24118,7 @@ const buildPropRegex = (handlerPropPrefix) => {
23980
24118
  const escaped = tokens.map((token) => token.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("|");
23981
24119
  return new RegExp(`^(${escaped})[A-Z].*$`);
23982
24120
  };
23983
- const resolveSettings$32 = (settings) => {
24121
+ const resolveSettings$31 = (settings) => {
23984
24122
  const reactDoctor = settings?.["react-doctor"];
23985
24123
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxHandlerNames ?? {} : {};
23986
24124
  let handlerPrefix = DEFAULT_HANDLER_PREFIX;
@@ -24056,7 +24194,7 @@ const jsxHandlerNames = defineRule({
24056
24194
  recommendation: "Use the `on…` prefix for event-handler props and `handle…` for handlers so readers can trace event flow.",
24057
24195
  category: "Architecture",
24058
24196
  create: (context) => {
24059
- const settings = resolveSettings$32(context.settings);
24197
+ const settings = resolveSettings$31(context.settings);
24060
24198
  return { JSXAttribute(node) {
24061
24199
  if (settings.ignoreComponentNames.length > 0) {
24062
24200
  const opening = node.parent;
@@ -24131,6 +24269,19 @@ const hasJsxKeyAttribute = (openingElement) => {
24131
24269
  return false;
24132
24270
  };
24133
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
24134
24285
  //#region src/plugin/utils/is-const-declared-binding.ts
24135
24286
  const BINDING_PATTERN_ANCESTOR_TYPES = new Set([
24136
24287
  "VariableDeclarator",
@@ -24175,11 +24326,17 @@ const ITERATOR_METHOD_NAMES$1 = new Set([
24175
24326
  "flatMap",
24176
24327
  "from"
24177
24328
  ]);
24329
+ const RENDERING_CALL_NAMES = new Set([
24330
+ "createPortal",
24331
+ "hydrate",
24332
+ "hydrateRoot",
24333
+ "render"
24334
+ ]);
24178
24335
  const MISSING_KEY_ARRAY = "Your users can see the wrong data when this array reorders.";
24179
24336
  const MISSING_KEY_ITERATOR = "Your users can see the wrong data when this list reorders.";
24180
- 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.";
24181
24338
  const DUPLICATE_KEY = (keyValue) => `Your users can see the wrong data because two elements share the key "${keyValue}".`;
24182
- const resolveSettings$31 = (settings) => {
24339
+ const resolveSettings$30 = (settings) => {
24183
24340
  const reactDoctor = settings?.["react-doctor"];
24184
24341
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxKey ?? {} : {};
24185
24342
  return {
@@ -24193,6 +24350,33 @@ const findArrayVariableDeclarator = (arrayExpression) => {
24193
24350
  if (ancestor && isNodeOfType(ancestor, "VariableDeclarator") && ancestor.init === wrapped && isNodeOfType(ancestor.id, "Identifier")) return ancestor;
24194
24351
  return null;
24195
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
+ };
24196
24380
  const isIdentityIteratorCallback = (callback) => {
24197
24381
  const callbackFunction = stripParenExpression(callback);
24198
24382
  if (!isNodeOfType(callbackFunction, "ArrowFunctionExpression") && !isNodeOfType(callbackFunction, "FunctionExpression")) return true;
@@ -24289,7 +24473,7 @@ const findNamedCallbackIteratorCall = (functionNode) => {
24289
24473
  namedCallbackIteratorCallCache.set(functionNode, iteratorCall);
24290
24474
  return iteratorCall;
24291
24475
  };
24292
- const findEnclosingIteratorContext = (jsxNode) => {
24476
+ const findEnclosingIteratorContext = (jsxNode, scopes) => {
24293
24477
  let current = jsxNode;
24294
24478
  let isOutsideContainingFunction = false;
24295
24479
  let didSeeReturnStatement = false;
@@ -24311,6 +24495,8 @@ const findEnclosingIteratorContext = (jsxNode) => {
24311
24495
  isOutsideContainingFunction = true;
24312
24496
  } else if (isNodeOfType(parent, "ArrayExpression")) {
24313
24497
  if (isOutsideContainingFunction) return null;
24498
+ if (isArrayNestedInObjectProperty(parent)) return null;
24499
+ if (isArrayPassedToNonRenderingCall(parent, scopes)) return null;
24314
24500
  const arrayParent = parent.parent;
24315
24501
  if (arrayParent && isNodeOfType(arrayParent, "Property")) return null;
24316
24502
  if (arrayParent && isNodeOfType(arrayParent, "ArrayExpression")) return null;
@@ -24392,9 +24578,15 @@ const isWithinChildrenToArray = (jsxNode) => {
24392
24578
  const isKeyPropertyName = (propertyKey) => {
24393
24579
  if (isNodeOfType(propertyKey, "Identifier")) return propertyKey.name === "key";
24394
24580
  if (isNodeOfType(propertyKey, "Literal")) return String(propertyKey.value) === "key";
24395
- return true;
24581
+ return false;
24396
24582
  };
24397
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
+ };
24398
24590
  const isFunctionParameterPropsRest = (bindingIdentifier) => {
24399
24591
  const restElement = bindingIdentifier.parent;
24400
24592
  if (!restElement || !isNodeOfType(restElement, "RestElement")) return false;
@@ -24404,7 +24596,7 @@ const isFunctionParameterPropsRest = (bindingIdentifier) => {
24404
24596
  const parameterNode = patternParent && isNodeOfType(patternParent, "AssignmentPattern") && patternParent.left === objectPattern ? patternParent : objectPattern;
24405
24597
  const functionNode = parameterNode.parent;
24406
24598
  if (!functionNode || !isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "FunctionDeclaration")) return false;
24407
- return functionNode.params.some((param) => param === parameterNode);
24599
+ return isComponentFunction$1(functionNode) && functionNode.params[0] === parameterNode;
24408
24600
  };
24409
24601
  const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
24410
24602
  const restElement = bindingIdentifier.parent;
@@ -24417,14 +24609,14 @@ const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
24417
24609
  }
24418
24610
  return false;
24419
24611
  };
24420
- const isBindingObjectMutated = (scopeOwner, bindingName) => {
24421
- let didFindMutation = false;
24612
+ const isBindingAssignedKey = (scopeOwner, bindingName) => {
24613
+ let didFindKeyAssignment = false;
24422
24614
  walkAst(scopeOwner, (node) => {
24423
- if (didFindMutation) return false;
24615
+ if (didFindKeyAssignment) return false;
24424
24616
  if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression")) {
24425
24617
  const assignedObject = stripParenExpression(node.left.object);
24426
- if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName) {
24427
- didFindMutation = true;
24618
+ if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName && isKeyPropertyName(node.left.property)) {
24619
+ didFindKeyAssignment = true;
24428
24620
  return false;
24429
24621
  }
24430
24622
  }
@@ -24432,59 +24624,63 @@ const isBindingObjectMutated = (scopeOwner, bindingName) => {
24432
24624
  const callee = node.callee;
24433
24625
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Object" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "assign") {
24434
24626
  const firstArgument = node.arguments[0];
24435
- if (firstArgument && isNodeOfType(firstArgument, "Identifier") && firstArgument.name === bindingName) {
24436
- didFindMutation = true;
24437
- 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
+ }
24438
24634
  }
24439
24635
  }
24440
24636
  }
24441
24637
  });
24442
- return didFindMutation;
24638
+ return didFindKeyAssignment;
24443
24639
  };
24444
- const spreadExpressionCanCarryKey = (expression, depth) => {
24640
+ const spreadExpressionHasKey = (expression, depth) => {
24445
24641
  const inner = stripParenExpression(expression);
24446
24642
  if (isNodeOfType(inner, "ObjectExpression")) {
24447
24643
  for (const property of inner.properties) {
24448
24644
  if (isNodeOfType(property, "SpreadElement")) {
24449
- if (depth >= 3) return true;
24450
- if (spreadExpressionCanCarryKey(property.argument, depth + 1)) return true;
24645
+ if (depth >= 3) continue;
24646
+ if (spreadExpressionHasKey(property.argument, depth + 1)) return true;
24451
24647
  continue;
24452
24648
  }
24453
- if (!isNodeOfType(property, "Property")) return true;
24454
- if (property.computed) return true;
24649
+ if (!isNodeOfType(property, "Property")) continue;
24455
24650
  if (isKeyPropertyName(property.key)) return true;
24456
24651
  }
24457
24652
  return false;
24458
24653
  }
24459
24654
  if (isNodeOfType(inner, "ConditionalExpression")) {
24460
- if (depth >= 3) return true;
24461
- 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);
24462
24657
  }
24463
24658
  if (isNodeOfType(inner, "LogicalExpression")) {
24464
- if (depth >= 3) return true;
24465
- if (inner.operator === "&&") return spreadExpressionCanCarryKey(inner.right, depth + 1);
24466
- 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);
24467
24662
  }
24468
24663
  if (isNodeOfType(inner, "Literal")) return false;
24469
24664
  if (isThisPropsMember(inner)) return false;
24470
24665
  if (isNodeOfType(inner, "Identifier")) {
24471
24666
  if (inner.name === "undefined") return false;
24472
- if (depth >= 3) return true;
24667
+ if (depth >= 3) return false;
24473
24668
  const binding = findVariableInitializer(inner, inner.name);
24474
- if (!binding) return true;
24669
+ if (!binding) return false;
24475
24670
  if (isRestBindingWithKeyExtracted(binding.bindingIdentifier)) return false;
24476
24671
  if (isFunctionParameterPropsRest(binding.bindingIdentifier)) return false;
24477
- if (!isConstDeclaredBinding(binding) || !binding.initializer) return true;
24478
- if (isBindingObjectMutated(binding.scopeOwner, inner.name)) return true;
24479
- 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);
24480
24676
  }
24481
- return true;
24677
+ return false;
24482
24678
  };
24483
- const spreadCanOverwriteKey = (spreadAttribute) => spreadExpressionCanCarryKey(spreadAttribute.argument, 0);
24679
+ const spreadCanOverwriteKey = (spreadAttribute) => spreadExpressionHasKey(spreadAttribute.argument, 0);
24484
24680
  const checkKeyBeforeSpread = (context, openingElement) => {
24485
24681
  let keyIndex = null;
24486
24682
  let keyAttribute = null;
24487
- let firstKeyCarryingSpreadIndex = null;
24683
+ let lastKeyCarryingSpreadIndex = null;
24488
24684
  for (let attributeIndex = 0; attributeIndex < openingElement.attributes.length; attributeIndex++) {
24489
24685
  const attribute = openingElement.attributes[attributeIndex];
24490
24686
  if (isNodeOfType(attribute, "JSXAttribute")) {
@@ -24492,11 +24688,11 @@ const checkKeyBeforeSpread = (context, openingElement) => {
24492
24688
  keyIndex = attributeIndex;
24493
24689
  keyAttribute = attribute;
24494
24690
  }
24495
- } else if (isNodeOfType(attribute, "JSXSpreadAttribute") && firstKeyCarryingSpreadIndex === null && spreadCanOverwriteKey(attribute)) firstKeyCarryingSpreadIndex = attributeIndex;
24691
+ } else if (isNodeOfType(attribute, "JSXSpreadAttribute") && spreadCanOverwriteKey(attribute)) lastKeyCarryingSpreadIndex = attributeIndex;
24496
24692
  }
24497
- if (keyIndex !== null && firstKeyCarryingSpreadIndex !== null && firstKeyCarryingSpreadIndex < keyIndex && keyAttribute) context.report({
24693
+ if (keyIndex !== null && lastKeyCarryingSpreadIndex !== null && lastKeyCarryingSpreadIndex > keyIndex && keyAttribute) context.report({
24498
24694
  node: keyAttribute,
24499
- message: KEY_AFTER_SPREAD
24695
+ message: KEY_BEFORE_SPREAD
24500
24696
  });
24501
24697
  };
24502
24698
  const getKeyAttributeValueString = (openingElement) => {
@@ -24540,7 +24736,7 @@ const jsxKey = defineRule({
24540
24736
  severity: "error",
24541
24737
  recommendation: "Add a stable `key` prop so React can keep list items matched to the right data when the list changes.",
24542
24738
  create: (context) => {
24543
- const settings = resolveSettings$31(context.settings);
24739
+ const settings = resolveSettings$30(context.settings);
24544
24740
  return {
24545
24741
  JSXElement(node) {
24546
24742
  const openingElement = node.openingElement;
@@ -24558,7 +24754,7 @@ const jsxKey = defineRule({
24558
24754
  else seenKeys.add(keyValue.keyValue);
24559
24755
  }
24560
24756
  }
24561
- const enclosingContext = findEnclosingIteratorContext(node);
24757
+ const enclosingContext = findEnclosingIteratorContext(node, context.scopes);
24562
24758
  if (!enclosingContext) return;
24563
24759
  if (isWithinChildrenToArray(node)) return;
24564
24760
  if (hasJsxKeyAttribute(openingElement)) return;
@@ -24594,7 +24790,7 @@ const jsxKey = defineRule({
24594
24790
  //#region src/plugin/rules/react-builtins/jsx-max-depth.ts
24595
24791
  const buildMessage$19 = (depth, max) => `This JSX is hard to read at ${depth} levels deep, past the limit of ${max}.`;
24596
24792
  const DEFAULT_MAX_DEPTH = 14;
24597
- const resolveSettings$30 = (settings) => {
24793
+ const resolveSettings$29 = (settings) => {
24598
24794
  const reactDoctor = settings?.["react-doctor"];
24599
24795
  return { max: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxMaxDepth ?? {} : {}).max ?? DEFAULT_MAX_DEPTH };
24600
24796
  };
@@ -24663,7 +24859,7 @@ const jsxMaxDepth = defineRule({
24663
24859
  recommendation: "Pull deeply nested JSX into smaller components so it's easier to read.",
24664
24860
  category: "Architecture",
24665
24861
  create: (context) => {
24666
- const { max } = resolveSettings$30(context.settings);
24862
+ const { max } = resolveSettings$29(context.settings);
24667
24863
  const deepestLeafPerTree = /* @__PURE__ */ new Map();
24668
24864
  const checkNode = (node) => {
24669
24865
  if (!isLeafJsxNode(node)) return;
@@ -26635,7 +26831,7 @@ const jsxNoNewObjectAsProp = defineRule({
26635
26831
  //#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
26636
26832
  const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
26637
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;
26638
- const resolveSettings$29 = (settings) => {
26834
+ const resolveSettings$28 = (settings) => {
26639
26835
  const reactDoctor = settings?.["react-doctor"];
26640
26836
  if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
26641
26837
  return reactDoctor.jsxNoScriptUrl ?? {};
@@ -26657,7 +26853,7 @@ const jsxNoScriptUrl = defineRule({
26657
26853
  recommendation: "Use real event handlers instead of `javascript:` URLs so injected URL text cannot execute as code.",
26658
26854
  category: "Security",
26659
26855
  create: (context) => {
26660
- const options = resolveSettings$29(context.settings);
26856
+ const options = resolveSettings$28(context.settings);
26661
26857
  return { JSXOpeningElement(node) {
26662
26858
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
26663
26859
  const elementName = resolveJsxElementType(node);
@@ -26678,283 +26874,6 @@ const jsxNoScriptUrl = defineRule({
26678
26874
  }
26679
26875
  });
26680
26876
  //#endregion
26681
- //#region src/plugin/rules/react-builtins/jsx-no-target-blank.ts
26682
- const NOREFERRER_MESSAGE = "`target=\"_blank\"` without `rel=\"noreferrer\"` lets the linked page hijack your tab to a phishing site.";
26683
- const NOOPENER_MESSAGE = "`target=\"_blank\"` without `rel` lets the linked page hijack your tab to a phishing site.";
26684
- const SPREAD_MESSAGE = "A spread here can add `target=\"_blank\"`, letting the linked page hijack your tab to a phishing site.";
26685
- const resolveSettings$28 = (settings) => {
26686
- const reactDoctor = settings?.["react-doctor"];
26687
- const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxNoTargetBlank ?? {} : {};
26688
- const reactSettings = typeof settings?.react === "object" && settings.react !== null ? settings.react : {};
26689
- const linkComponents = /* @__PURE__ */ new Map();
26690
- for (const entry of reactSettings.linkComponents ?? []) if (typeof entry === "string") linkComponents.set(entry, ["href"]);
26691
- else if (typeof entry === "object" && entry !== null) {
26692
- const linkAttribute = entry.linkAttribute ?? "href";
26693
- linkComponents.set(entry.name, Array.isArray(linkAttribute) ? linkAttribute : [linkAttribute]);
26694
- }
26695
- const formComponents = /* @__PURE__ */ new Map();
26696
- for (const entry of reactSettings.formComponents ?? []) if (typeof entry === "string") formComponents.set(entry, ["action"]);
26697
- else if (typeof entry === "object" && entry !== null) {
26698
- const formAttribute = entry.formAttribute ?? "action";
26699
- formComponents.set(entry.name, Array.isArray(formAttribute) ? formAttribute : [formAttribute]);
26700
- }
26701
- return {
26702
- enforceDynamicLinks: ruleSettings.enforceDynamicLinks ?? "always",
26703
- warnOnSpreadAttributes: ruleSettings.warnOnSpreadAttributes ?? false,
26704
- allowReferrer: ruleSettings.allowReferrer ?? false,
26705
- links: ruleSettings.links ?? true,
26706
- forms: ruleSettings.forms ?? false,
26707
- linkComponents,
26708
- formComponents
26709
- };
26710
- };
26711
- const isExternalLink = (href) => href.includes("//");
26712
- const matchHrefExpression = (expression, state) => {
26713
- if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") {
26714
- if (isExternalLink(expression.value)) state.isExternal = true;
26715
- return;
26716
- }
26717
- if (isNodeOfType(expression, "Identifier")) {
26718
- state.isDynamic = true;
26719
- return;
26720
- }
26721
- if (isNodeOfType(expression, "ConditionalExpression")) {
26722
- matchHrefExpression(expression.consequent, state);
26723
- matchHrefExpression(expression.alternate, state);
26724
- }
26725
- };
26726
- const checkHref = (attributeValue, enforceDynamicLinks) => {
26727
- const state = {
26728
- isExternal: false,
26729
- isDynamic: false
26730
- };
26731
- if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") state.isExternal = isExternalLink(attributeValue.value);
26732
- else if (isNodeOfType(attributeValue, "JSXExpressionContainer")) matchHrefExpression(attributeValue.expression, state);
26733
- if (enforceDynamicLinks === "never") return !state.isExternal || state.isDynamic;
26734
- return !(state.isExternal || state.isDynamic);
26735
- };
26736
- const checkRelValue = (text, allowReferrer) => {
26737
- const tokens = text.split(/\s+/);
26738
- if (allowReferrer) return tokens.includes("noopener") || tokens.includes("noreferrer");
26739
- return tokens.some((token) => token.toLowerCase() === "noreferrer");
26740
- };
26741
- const matchRelExpression = (expression, allowReferrer) => {
26742
- const empty = {
26743
- combined: false,
26744
- testName: "",
26745
- consequent: false,
26746
- alternate: false
26747
- };
26748
- if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
26749
- combined: checkRelValue(expression.value, allowReferrer),
26750
- testName: "",
26751
- consequent: false,
26752
- alternate: false
26753
- };
26754
- if (isNodeOfType(expression, "ConditionalExpression")) {
26755
- const consequent = matchRelExpression(expression.consequent, allowReferrer);
26756
- const alternate = matchRelExpression(expression.alternate, allowReferrer);
26757
- const test = expression.test;
26758
- if (isNodeOfType(test, "Identifier")) return {
26759
- combined: consequent.combined && alternate.combined,
26760
- testName: test.name,
26761
- consequent: consequent.combined,
26762
- alternate: alternate.combined
26763
- };
26764
- return {
26765
- combined: consequent.combined && alternate.combined,
26766
- testName: "",
26767
- consequent: consequent.combined,
26768
- alternate: alternate.combined
26769
- };
26770
- }
26771
- return empty;
26772
- };
26773
- const checkRel = (attributeValue, allowReferrer) => {
26774
- const empty = {
26775
- combined: false,
26776
- testName: "",
26777
- consequent: false,
26778
- alternate: false
26779
- };
26780
- if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
26781
- combined: checkRelValue(attributeValue.value, allowReferrer),
26782
- testName: "",
26783
- consequent: false,
26784
- alternate: false
26785
- };
26786
- if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
26787
- const expression = attributeValue.expression;
26788
- if (expression.type === "JSXEmptyExpression") return empty;
26789
- return matchRelExpression(expression, allowReferrer);
26790
- }
26791
- return empty;
26792
- };
26793
- const matchTargetExpression = (expression) => {
26794
- const empty = {
26795
- combined: false,
26796
- testName: "",
26797
- consequent: false,
26798
- alternate: false
26799
- };
26800
- if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
26801
- combined: expression.value.toLowerCase() === "_blank",
26802
- testName: "",
26803
- consequent: false,
26804
- alternate: false
26805
- };
26806
- if (isNodeOfType(expression, "ConditionalExpression")) {
26807
- const consequent = matchTargetExpression(expression.consequent);
26808
- const alternate = matchTargetExpression(expression.alternate);
26809
- const test = expression.test;
26810
- const combined = consequent.combined || alternate.combined;
26811
- if (isNodeOfType(test, "Identifier")) return {
26812
- combined,
26813
- testName: test.name,
26814
- consequent: consequent.combined,
26815
- alternate: alternate.combined
26816
- };
26817
- return {
26818
- combined,
26819
- testName: "",
26820
- consequent: consequent.combined,
26821
- alternate: alternate.combined
26822
- };
26823
- }
26824
- return empty;
26825
- };
26826
- const checkTarget = (attributeValue) => {
26827
- if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
26828
- combined: attributeValue.value.toLowerCase() === "_blank",
26829
- testName: "",
26830
- consequent: false,
26831
- alternate: false
26832
- };
26833
- if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
26834
- const expression = attributeValue.expression;
26835
- if (expression.type === "JSXEmptyExpression") return {
26836
- combined: false,
26837
- testName: "",
26838
- consequent: false,
26839
- alternate: false
26840
- };
26841
- return matchTargetExpression(expression);
26842
- }
26843
- return {
26844
- combined: false,
26845
- testName: "",
26846
- consequent: false,
26847
- alternate: false
26848
- };
26849
- };
26850
- const jsxNoTargetBlank = defineRule({
26851
- id: "jsx-no-target-blank",
26852
- title: "Unsafe target=_blank link",
26853
- severity: "warn",
26854
- recommendation: "Add `rel=\"noreferrer\"` (or `\"noopener\"`) when using `target=\"_blank\"`.",
26855
- category: "Security",
26856
- create: (context) => {
26857
- const settings = resolveSettings$28(context.settings);
26858
- const isLink = (tagName) => {
26859
- if (!settings.links) return false;
26860
- if (tagName === "a") return true;
26861
- return settings.linkComponents.has(tagName);
26862
- };
26863
- const isForm = (tagName) => {
26864
- if (!settings.forms) return false;
26865
- if (tagName === "form") return true;
26866
- return settings.formComponents.has(tagName);
26867
- };
26868
- return { JSXOpeningElement(node) {
26869
- if (!isNodeOfType(node.name, "JSXIdentifier")) return;
26870
- const tagName = resolveJsxElementType(node);
26871
- if (!tagName) return;
26872
- if (!isLink(tagName) && !isForm(tagName)) return;
26873
- const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
26874
- const formAttributeNames = settings.formComponents.get(tagName) ?? ["action"];
26875
- let targetTuple = {
26876
- combined: false,
26877
- testName: "",
26878
- consequent: false,
26879
- alternate: false
26880
- };
26881
- let relTuple = {
26882
- combined: false,
26883
- testName: "",
26884
- consequent: false,
26885
- alternate: false
26886
- };
26887
- let isHrefValid = true;
26888
- let hasHrefValue = false;
26889
- let warnSpread = false;
26890
- let targetReportNode = node.name;
26891
- let spreadReportNode = null;
26892
- for (const attribute of node.attributes) {
26893
- if (isNodeOfType(attribute, "JSXSpreadAttribute")) {
26894
- if (settings.warnOnSpreadAttributes) {
26895
- warnSpread = true;
26896
- spreadReportNode = attribute;
26897
- targetTuple = {
26898
- combined: false,
26899
- testName: "",
26900
- consequent: false,
26901
- alternate: false
26902
- };
26903
- relTuple = {
26904
- combined: false,
26905
- testName: "",
26906
- consequent: false,
26907
- alternate: false
26908
- };
26909
- isHrefValid = false;
26910
- hasHrefValue = true;
26911
- }
26912
- continue;
26913
- }
26914
- if (!isNodeOfType(attribute, "JSXAttribute")) continue;
26915
- const attributeName = attribute.name;
26916
- if (!isNodeOfType(attributeName, "JSXIdentifier")) continue;
26917
- const propName = attributeName.name;
26918
- const value = attribute.value;
26919
- if (propName === "target") {
26920
- if (value) {
26921
- targetTuple = checkTarget(value);
26922
- targetReportNode = value;
26923
- }
26924
- } else if (propName === "href" || propName === "action" || linkAttributeNames.includes(propName) || formAttributeNames.includes(propName)) {
26925
- if (value) {
26926
- hasHrefValue = true;
26927
- isHrefValid = checkHref(value, settings.enforceDynamicLinks);
26928
- }
26929
- } else if (propName === "rel" && value) relTuple = checkRel(value, settings.allowReferrer);
26930
- }
26931
- if (warnSpread) {
26932
- if (hasHrefValue && isHrefValid || relTuple.combined) return;
26933
- context.report({
26934
- node: spreadReportNode ?? node,
26935
- message: SPREAD_MESSAGE
26936
- });
26937
- return;
26938
- }
26939
- if (!isHrefValid) {
26940
- if (targetTuple.testName !== "" && targetTuple.testName === relTuple.testName) {
26941
- const consequentBad = targetTuple.consequent && !relTuple.consequent;
26942
- const alternateBad = targetTuple.alternate && !relTuple.alternate;
26943
- if (consequentBad || alternateBad) context.report({
26944
- node: targetReportNode,
26945
- message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
26946
- });
26947
- return;
26948
- }
26949
- if (targetTuple.combined && !relTuple.combined) context.report({
26950
- node: targetReportNode,
26951
- message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
26952
- });
26953
- }
26954
- } };
26955
- }
26956
- });
26957
- //#endregion
26958
26877
  //#region src/plugin/rules/react-builtins/jsx-no-undef.ts
26959
26878
  const buildMessage$18 = (name) => `\`${name}\` crashes at runtime because it isn't defined here.`;
26960
26879
  const KNOWN_GLOBALS = new Set([
@@ -28612,6 +28531,30 @@ const hasAncestorLayoutMatching = (pageFilename, matchesLayout) => {
28612
28531
  const hasAncestorMetadataLayout = (pageFilename) => hasAncestorLayoutMatching(pageFilename, (layoutPath) => METADATA_EXPORT_NAMES.some((exportName) => doesModuleExportName(layoutPath, exportName)));
28613
28532
  //#endregion
28614
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
+ };
28615
28558
  const nextjsMissingMetadata = defineRule({
28616
28559
  id: "nextjs-missing-metadata",
28617
28560
  title: "Page missing metadata for search previews",
@@ -28631,6 +28574,7 @@ const nextjsMissingMetadata = defineRule({
28631
28574
  if (isNodeOfType(declaration, "FunctionDeclaration")) return declaration.id?.name === "generateMetadata";
28632
28575
  return (statement.specifiers ?? []).some((specifier) => isNodeOfType(specifier, "ExportSpecifier") && isNodeOfType(specifier.exported, "Identifier") && METADATA_EXPORT_NAMES.includes(specifier.exported.name));
28633
28576
  })) return;
28577
+ if (isRedirectOnlyDefaultExport(programNode, context)) return;
28634
28578
  if (hasAncestorMetadataLayout(context.filename ?? "")) return;
28635
28579
  context.report({
28636
28580
  node: programNode,
@@ -34759,6 +34703,19 @@ const noCascadingSetState = defineRetiredRule({
34759
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."
34760
34704
  });
34761
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
34762
34719
  //#region src/plugin/rules/state-and-effects/utils/create-state-trigger-reachability.ts
34763
34720
  const getRefCurrentSymbol = (node, context) => {
34764
34721
  const expression = stripParenExpression(node);
@@ -35287,19 +35244,8 @@ const isBuiltinNamespaceCallee = (callee) => {
35287
35244
  }
35288
35245
  return false;
35289
35246
  };
35290
- const getReactUseCallbackSource = (reference, context) => {
35291
- const identifier = reference.identifier;
35292
- const symbol = resolveConstIdentifierAlias(identifier, context.scopes);
35293
- if (!symbol || symbol.kind !== "const" || !symbol.initializer) return null;
35294
- const initializer = stripParenExpression(symbol.initializer);
35295
- if (isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, "useCallback", context.scopes, {
35296
- allowGlobalReactNamespace: true,
35297
- resolveNamedAliases: true
35298
- })) return initializer;
35299
- return null;
35300
- };
35301
35247
  const getDependencyStateRefs = (analysis, context, dependencyReference) => {
35302
- const useCallbackCall = getReactUseCallbackSource(dependencyReference, context);
35248
+ const useCallbackCall = getReactUseCallbackCall(dependencyReference.identifier, context.scopes);
35303
35249
  if (!useCallbackCall) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
35304
35250
  const dependencyList = useCallbackCall.arguments?.[1];
35305
35251
  if (!dependencyList || !isNodeOfType(dependencyList, "ArrayExpression")) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
@@ -36887,19 +36833,6 @@ const noDefaultProps = defineRule({
36887
36833
  } })
36888
36834
  });
36889
36835
  //#endregion
36890
- //#region src/plugin/utils/is-component-function.ts
36891
- const isFunctionAssignedToComponent = (functionNode) => {
36892
- let cursor = functionNode.parent ?? null;
36893
- while (isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
36894
- if (isNodeOfType(cursor, "VariableDeclarator") && isNodeOfType(cursor.id, "Identifier") && isUppercaseName(cursor.id.name)) return true;
36895
- return isNodeOfType(cursor, "ExportDefaultDeclaration");
36896
- };
36897
- const isComponentFunction$1 = (node) => {
36898
- if (!isFunctionLike$1(node)) return false;
36899
- if (isNodeOfType(node, "FunctionDeclaration")) return !node.id || node.id.name === "default" || isUppercaseName(node.id.name) || isNodeOfType(node.parent, "ExportDefaultDeclaration");
36900
- return isFunctionAssignedToComponent(node);
36901
- };
36902
- //#endregion
36903
36836
  //#region src/plugin/utils/create-component-prop-stack-tracker.ts
36904
36837
  const extractDestructuredPropNames = (params) => {
36905
36838
  const propNames = /* @__PURE__ */ new Set();
@@ -38813,6 +38746,17 @@ const noDynamicImportPath = defineRule({
38813
38746
  })
38814
38747
  });
38815
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
38816
38760
  //#region src/plugin/rules/state-and-effects/utils/is-cleanup-return.ts
38817
38761
  const ITERATOR_CALLBACK_METHOD_NAMES = new Set([
38818
38762
  "each",
@@ -38957,7 +38901,16 @@ const collectDependencyStateSymbolIds = (effectNode, stateSymbolIds, scopes) =>
38957
38901
  }
38958
38902
  return dependencyStateSymbolIds;
38959
38903
  };
38960
- 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) => {
38961
38914
  const analysisFunctions = new Set([effectCallback]);
38962
38915
  const pendingFunctions = [effectCallback];
38963
38916
  while (pendingFunctions.length > 0) {
@@ -38965,9 +38918,9 @@ const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
38965
38918
  if (!currentFunction || !isFunctionLike$1(currentFunction)) continue;
38966
38919
  walkInsideStatementBlocks(currentFunction.body, (child) => {
38967
38920
  if (!isNodeOfType(child, "CallExpression")) return;
38968
- const invokedFunction = resolveExactLocalFunction(child.callee, scopes);
38921
+ const invokedFunction = includeStableCallbacks ? resolveSynchronouslyInvokedFunction(child.callee, scopes) : resolveExactLocalFunction(child.callee, scopes);
38969
38922
  if (!invokedFunction || analysisFunctions.has(invokedFunction)) return;
38970
- if (isFunctionLike$1(invokedFunction) && invokedFunction.async) return;
38923
+ if (isFunctionLike$1(invokedFunction) && (invokedFunction.async || invokedFunction.generator)) return;
38971
38924
  analysisFunctions.add(invokedFunction);
38972
38925
  pendingFunctions.push(invokedFunction);
38973
38926
  });
@@ -38980,7 +38933,51 @@ const visitSynchronousFunctionBodies = (analysisFunctions, visitor) => {
38980
38933
  walkInsideStatementBlocks(analysisFunction.body, visitor);
38981
38934
  }
38982
38935
  };
38983
- 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()) => {
38984
38981
  const unwrappedExpression = stripParenExpression(expression);
38985
38982
  if (isNodeOfType(unwrappedExpression, "Literal")) {
38986
38983
  const literalValue = unwrappedExpression.value;
@@ -38988,46 +38985,49 @@ const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, vi
38988
38985
  return null;
38989
38986
  }
38990
38987
  if (isNodeOfType(unwrappedExpression, "Identifier")) {
38991
- 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;
38992
38991
  if (unwrappedExpression.name === "undefined" && scopes.isGlobalReference(unwrappedExpression)) return { value: void 0 };
38992
+ if (unwrappedExpression.name === "NaN" && scopes.isGlobalReference(unwrappedExpression)) return { value: NaN };
38993
38993
  const immutableSymbol = scopes.symbolFor(unwrappedExpression);
38994
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;
38995
- 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);
38996
38996
  }
38997
38997
  if (isNodeOfType(unwrappedExpression, "UnaryExpression")) {
38998
38998
  if (unwrappedExpression.operator === "void") return { value: void 0 };
38999
38999
  if (unwrappedExpression.operator !== "!") return null;
39000
- const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39000
+ const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
39001
39001
  return argumentValue ? { value: !argumentValue.value } : null;
39002
39002
  }
39003
39003
  if (isNodeOfType(unwrappedExpression, "CallExpression")) {
39004
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")) {
39005
- const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds);
39005
+ const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
39006
39006
  return argumentValue ? { value: Boolean(argumentValue.value) } : null;
39007
39007
  }
39008
39008
  return null;
39009
39009
  }
39010
39010
  if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
39011
- const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39011
+ const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
39012
39012
  if (!leftValue) return null;
39013
39013
  if (unwrappedExpression.operator === "&&" && !leftValue.value) return leftValue;
39014
39014
  if (unwrappedExpression.operator === "||" && leftValue.value) return leftValue;
39015
39015
  if (unwrappedExpression.operator === "??" && leftValue.value !== null && leftValue.value !== void 0) return leftValue;
39016
- return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39016
+ return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
39017
39017
  }
39018
39018
  if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
39019
- const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39019
+ const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
39020
39020
  if (!testValue) return null;
39021
- 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);
39022
39022
  }
39023
39023
  if (isNodeOfType(unwrappedExpression, "MemberExpression") && unwrappedExpression.optional) {
39024
- const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39024
+ const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
39025
39025
  if (objectValue?.value === null || objectValue?.value === void 0) return { value: void 0 };
39026
39026
  return null;
39027
39027
  }
39028
39028
  if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
39029
- const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39030
- 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);
39031
39031
  if (!leftValue || !rightValue) return null;
39032
39032
  if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
39033
39033
  const areEqual = leftValue.value === rightValue.value;
@@ -39066,8 +39066,7 @@ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName
39066
39066
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
39067
39067
  if (!isNodeOfType(child, "CallExpression")) return;
39068
39068
  if (!isNodeOfType(child.callee, "Identifier")) return;
39069
- const setterSymbol = resolveConstIdentifierAlias(child.callee, scopes, true);
39070
- const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
39069
+ const stateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
39071
39070
  if (!stateName) return;
39072
39071
  const writeInfo = stateWrites.get(stateName) ?? {
39073
39072
  values: /* @__PURE__ */ new Set(),
@@ -39083,27 +39082,27 @@ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName
39083
39082
  const isGlobalBooleanCall = (node, scopes) => {
39084
39083
  return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Boolean" && scopes.isGlobalReference(node.callee);
39085
39084
  };
39086
- const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes) => {
39085
+ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map()) => {
39087
39086
  let currentNode = workNode;
39088
39087
  while (currentNode.parent) {
39089
39088
  const parentNode = currentNode.parent;
39090
39089
  if (isFunctionLike$1(parentNode)) break;
39091
39090
  if (isNodeOfType(parentNode, "IfStatement")) {
39092
- const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
39091
+ const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
39093
39092
  if (testValue) {
39094
39093
  if (currentNode === parentNode.consequent && !testValue.value) return false;
39095
39094
  if (currentNode === parentNode.alternate && testValue.value) return false;
39096
39095
  }
39097
39096
  }
39098
39097
  if (isNodeOfType(parentNode, "ConditionalExpression")) {
39099
- const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
39098
+ const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
39100
39099
  if (testValue) {
39101
39100
  if (currentNode === parentNode.consequent && !testValue.value) return false;
39102
39101
  if (currentNode === parentNode.alternate && testValue.value) return false;
39103
39102
  }
39104
39103
  }
39105
39104
  if (isNodeOfType(parentNode, "LogicalExpression") && currentNode === parentNode.right) {
39106
- const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue);
39105
+ const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
39107
39106
  if (leftValue) {
39108
39107
  if (parentNode.operator === "&&" && !leftValue.value) return false;
39109
39108
  if (parentNode.operator === "||" && leftValue.value) return false;
@@ -39115,7 +39114,7 @@ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, s
39115
39114
  if (statementIndex >= 0) for (let index = 0; index < statementIndex; index += 1) {
39116
39115
  const earlierStatement = parentNode.body[index];
39117
39116
  if (!isNodeOfType(earlierStatement, "IfStatement") || earlierStatement.alternate || !statementAlwaysExits(earlierStatement.consequent)) continue;
39118
- 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;
39119
39118
  }
39120
39119
  }
39121
39120
  currentNode = parentNode;
@@ -39125,24 +39124,93 @@ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, s
39125
39124
  const isReaderWorkNode = (node, analysisFunctions, scopes) => {
39126
39125
  if (isNodeOfType(node, "CallExpression")) {
39127
39126
  if (isGlobalBooleanCall(node, scopes)) return false;
39128
- const invokedFunction = resolveExactLocalFunction(node.callee, scopes);
39127
+ const invokedFunction = resolveSynchronouslyInvokedFunction(node.callee, scopes);
39129
39128
  return !invokedFunction || !analysisFunctions.has(invokedFunction);
39130
39129
  }
39131
39130
  return isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "ThrowStatement") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete";
39132
39131
  };
39133
- const canStateWriteReachReaderWork = (writeInfo, readerEffect, stateSymbolId, scopes) => {
39134
- if (writeInfo.hasUnknownValue || stateSymbolId === null) return true;
39135
- for (const writtenValue of writeInfo.values) {
39136
- 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);
39137
39187
  let didFindReachableWork = false;
39138
- visitSynchronousFunctionBodies(readerEffect.analysisFunctions, (child) => {
39139
- if (didFindReachableWork || !isReaderWorkNode(child, readerEffect.analysisFunctions, scopes)) return;
39140
- 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;
39141
39201
  });
39142
39202
  if (didFindReachableWork) return true;
39143
39203
  }
39144
39204
  return false;
39145
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
+ };
39146
39214
  const EMPTY_CLEANUP_NAME_SET = /* @__PURE__ */ new Set();
39147
39215
  const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
39148
39216
  "clear",
@@ -39153,26 +39221,499 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
39153
39221
  "keys",
39154
39222
  "values"
39155
39223
  ]);
39156
- const isFunctionShapedReturn = (returnedValue, setterToStateName, setterSymbolIdToStateName, scopes, isExplicitReturnStatement) => {
39157
- if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
39158
- if (isNodeOfType(returnedValue, "CallExpression")) {
39159
- if (isNodeOfType(returnedValue.callee, "Identifier")) {
39160
- const setterSymbol = resolveConstIdentifierAlias(returnedValue.callee, scopes, true);
39161
- if (setterToStateName.has(returnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
39162
- 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;
39310
+ }
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;
39324
+ }
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;
39163
39436
  }
39164
- return isCleanupReturn(returnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
39165
39437
  }
39166
- if (isNodeOfType(returnedValue, "Identifier")) return true;
39438
+ return true;
39439
+ };
39440
+ const hasProvenGlobalNamespaceReference = (expression, namespaceNames, scopes) => {
39441
+ for (const namespaceName of namespaceNames) if (isProvenGlobalNamespaceReference(expression, namespaceName, scopes)) return true;
39167
39442
  return false;
39168
39443
  };
39169
- const STORAGE_GLOBAL_NAMES = new Set(["localStorage", "sessionStorage"]);
39170
- const isBrowserStorageReceiver = (receiver) => {
39171
- if (!receiver) return false;
39172
- if (isNodeOfType(receiver, "Identifier")) return STORAGE_GLOBAL_NAMES.has(receiver.name);
39173
- 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;
39174
39713
  return false;
39175
39714
  };
39715
+ const STORAGE_GLOBAL_NAMES = ["localStorage", "sessionStorage"];
39716
+ const isBrowserStorageReceiver = (receiver, scopes) => Boolean(receiver && STORAGE_GLOBAL_NAMES.some((storageName) => isProvenGlobalNamespaceReference(receiver, storageName, scopes)));
39176
39717
  const STORAGE_HOOK_PATTERN = /^use\w*Storage/i;
39177
39718
  const collectStorageHookSetterNames = (componentBody) => {
39178
39719
  const setterNames = /* @__PURE__ */ new Set();
@@ -39214,14 +39755,6 @@ const isReactRefCall = (expression, scopes) => isReactUseRefCall(expression, sco
39214
39755
  allowUnboundBareCalls: true,
39215
39756
  resolveNamedAliases: true
39216
39757
  });
39217
- const getDirectReactRefSymbol = (rawExpression, scopes) => {
39218
- const expression = stripParenExpression(rawExpression);
39219
- if (!isNodeOfType(expression, "Identifier")) return null;
39220
- const symbol = scopes.symbolFor(expression);
39221
- if (!symbol) return null;
39222
- const initializer = getDirectUnreassignedInitializer(symbol);
39223
- return initializer && isReactRefCall(stripParenExpression(initializer), scopes) ? symbol : null;
39224
- };
39225
39758
  const isReactNativeJsxElement = (openingElement, scopes) => {
39226
39759
  if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
39227
39760
  const symbol = scopes.symbolFor(openingElement.name);
@@ -39229,19 +39762,33 @@ const isReactNativeJsxElement = (openingElement, scopes) => {
39229
39762
  return Boolean(symbol?.kind === "import" && importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react-native");
39230
39763
  };
39231
39764
  const isDirectHostJsxRef = (symbol, scopes) => {
39232
- let hostRefCount = 0;
39233
- for (const reference of symbol.references) {
39234
- const expression = findTransparentExpressionRoot(reference.identifier);
39235
- const container = expression.parent;
39236
- if (isNodeOfType(container, "MemberExpression") && container.object === expression && getStaticPropertyName(container) === "current") continue;
39237
- if (!container || !isNodeOfType(container, "JSXExpressionContainer") || container.expression !== expression) return false;
39238
- const attribute = container.parent;
39239
- if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
39240
- const openingElement = attribute.parent;
39241
- if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || !isProvenIntrinsicJsxElement(openingElement, scopes) && !isReactNativeJsxElement(openingElement, scopes)) return false;
39242
- hostRefCount += 1;
39243
- }
39244
- 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;
39245
39792
  };
39246
39793
  const isIntrinsicRefCallbackParameter = (expression, scopes) => {
39247
39794
  const identifier = stripParenExpression(expression);
@@ -39328,7 +39875,11 @@ const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollecti
39328
39875
  }
39329
39876
  if (isNodeOfType(expression, "MemberExpression")) {
39330
39877
  if (getStaticPropertyName(expression) === "current") {
39331
- const symbol = getDirectReactRefSymbol(expression.object, scopes);
39878
+ const symbol = resolveReactRefSymbol(expression, scopes, {
39879
+ allowUnboundBareCalls: true,
39880
+ includeCreateRef: true,
39881
+ resolveNamedAliases: true
39882
+ });
39332
39883
  return Boolean(symbol && (isDirectHostJsxRef(symbol, scopes) || didReadCollectionValue && storesOnlyIntrinsicRefCallbackValues(symbol, scopes)));
39333
39884
  }
39334
39885
  return isDerivedFromProvenDomRefCurrent(expression.object, scopes, didReadCollectionValue, visitedSymbolIds);
@@ -39346,19 +39897,80 @@ const isCommittedDomSyncNode = (node, scopes) => {
39346
39897
  if (propertyName === null || !EXTERNAL_SYNC_DOM_MEMBER_METHOD_NAMES.has(propertyName)) return false;
39347
39898
  return isDerivedFromProvenDomRefCurrent(callee.object, scopes) || isProvenBrowserApiReceiver(callee.object, "dom-event-target", scopes);
39348
39899
  };
39349
- const isExternalSyncNode = (node) => {
39350
- if (isNodeOfType(node, "NewExpression")) return isNodeOfType(node.callee, "Identifier") && EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS.has(node.callee.name);
39351
- 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
+ }
39352
39965
  if (!isNodeOfType(node, "CallExpression")) return false;
39353
- if (isNodeOfType(node.callee, "Identifier")) return EXTERNAL_SYNC_DIRECT_CALLEE_NAMES.has(node.callee.name);
39966
+ if (isProvenExternalSyncDirectCallee(node.callee, scopes)) return true;
39354
39967
  if (!isNodeOfType(node.callee, "MemberExpression")) return false;
39355
39968
  const propertyName = getStaticPropertyName(node.callee);
39356
39969
  if (propertyName === null) return false;
39357
- if (EXTERNAL_SYNC_MEMBER_METHOD_NAMES.has(propertyName)) return true;
39358
- if (isBrowserStorageReceiver(node.callee.object)) return true;
39359
- if (!EXTERNAL_SYNC_AMBIGUOUS_HTTP_METHOD_NAMES.has(propertyName)) return false;
39360
- const receiverRootName = getRootIdentifierName(node.callee.object);
39361
- 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);
39362
39974
  };
39363
39975
  const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, scopes, allowCommittedDomSync) => {
39364
39976
  if (!isFunctionLike$1(effectCallback)) return false;
@@ -39367,7 +39979,7 @@ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateNa
39367
39979
  } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, setterSymbolIdToStateName, scopes, true)) return true;
39368
39980
  let didFindExternalCall = false;
39369
39981
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
39370
- if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
39982
+ if (isExternalSyncNode(child, scopes) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
39371
39983
  });
39372
39984
  return didFindExternalCall;
39373
39985
  };
@@ -39389,11 +40001,11 @@ const noEffectChain = defineRule({
39389
40001
  setterToStateName.set(binding.setterName, binding.valueName);
39390
40002
  if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
39391
40003
  const stateIdentifier = binding.declarator.id.elements[0];
40004
+ const setterIdentifier = binding.declarator.id.elements[1];
39392
40005
  if (isNodeOfType(stateIdentifier, "Identifier")) {
39393
40006
  const stateSymbol = context.scopes.symbolFor(stateIdentifier);
39394
40007
  if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
39395
40008
  }
39396
- const setterIdentifier = binding.declarator.id.elements[1];
39397
40009
  if (isNodeOfType(setterIdentifier, "Identifier")) {
39398
40010
  const setterSymbol = context.scopes.symbolFor(setterIdentifier);
39399
40011
  if (setterSymbol) setterSymbolIdToStateName.set(setterSymbol.id, binding.valueName);
@@ -39406,10 +40018,11 @@ const noEffectChain = defineRule({
39406
40018
  const callback = getEffectCallback(effectCall, context.scopes);
39407
40019
  if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
39408
40020
  const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
39409
- const stateWrites = collectStateWritesInEffect(analysisFunctions, setterSymbolIdToStateName, context.scopes);
40021
+ const stateWrites = collectStateWritesInEffect(collectStateWriteAnalysisFunctions(callback, context.scopes, setterSymbolIdToStateName), setterSymbolIdToStateName, context.scopes);
39410
40022
  const writtenStateNames = new Set(stateWrites.keys());
39411
40023
  effectInfos.push({
39412
40024
  node: effectCall,
40025
+ callback,
39413
40026
  dependencyStateSymbolIds: collectDependencyStateSymbolIds(effectCall, stateSymbolIdSet, context.scopes),
39414
40027
  stateWrites,
39415
40028
  analysisFunctions,
@@ -39426,10 +40039,10 @@ const noEffectChain = defineRule({
39426
40039
  if (readerEffect.isExternalSync) continue;
39427
40040
  if (readerEffect.dependencyStateSymbolIds.size === 0) continue;
39428
40041
  let chainedStateName = null;
39429
- for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
40042
+ for (const writtenName of writerEffect.stateWrites.keys()) {
39430
40043
  const writtenStateSymbolId = stateSymbolIds.get(writtenName);
39431
40044
  if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
39432
- if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
40045
+ if (!canStateWriteReachReaderWork(writtenName, writerEffect, readerEffect, stateSymbolIds, context.scopes)) continue;
39433
40046
  chainedStateName = writtenName;
39434
40047
  break;
39435
40048
  }
@@ -46603,6 +47216,103 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
46603
47216
  "useVisibility",
46604
47217
  "useWindowSize"
46605
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
+ };
46606
47316
  const isCallbackPropReference = (analysis, ref) => {
46607
47317
  if (!isProp(analysis, ref)) return false;
46608
47318
  const identifier = ref.identifier;
@@ -46635,8 +47345,9 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
46635
47345
  if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
46636
47346
  return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
46637
47347
  };
46638
- const getLocalHookExternalStateProof = (analysis, ref) => {
47348
+ const getLocalHookExternalStateProof = (analysis, ref, scopes) => {
46639
47349
  let hookFunction = resolveToFunction(ref);
47350
+ let didResolveThroughResultBinding = false;
46640
47351
  if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
46641
47352
  const definitionNode = definition.node;
46642
47353
  if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
@@ -46647,29 +47358,27 @@ const getLocalHookExternalStateProof = (analysis, ref) => {
46647
47358
  const calleeReference = getRef(analysis, callee);
46648
47359
  if (!calleeReference) continue;
46649
47360
  hookFunction = resolveToFunction(calleeReference);
46650
- if (hookFunction) break;
47361
+ if (hookFunction) {
47362
+ didResolveThroughResultBinding = true;
47363
+ break;
47364
+ }
46651
47365
  }
46652
47366
  if (!hookFunction) return null;
46653
- 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) : []);
46654
47372
  if (returnedReferences.length === 0) return null;
46655
47373
  return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
46656
47374
  };
46657
- const isExternalSubscriptionHookRef = (analysis, ref) => {
46658
- const identifier = ref.identifier;
46659
- if (!isNodeOfType(identifier, "Identifier")) return false;
46660
- const localHookProof = getLocalHookExternalStateProof(analysis, ref);
46661
- if (localHookProof !== null) return localHookProof;
46662
- if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
46663
- return Boolean(ref.resolved?.defs.some((def) => {
46664
- const node = def.node;
46665
- if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
46666
- const initializer = stripParenExpression(node.init);
46667
- if (!isNodeOfType(initializer, "CallExpression")) return false;
46668
- const callee = stripParenExpression(initializer.callee);
46669
- return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name);
46670
- }));
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);
46671
47381
  };
46672
- const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
46673
47382
  const isCalleePosition = (identifier) => {
46674
47383
  const parent = identifier.parent;
46675
47384
  return Boolean(parent && (isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression")) && parent.callee === identifier);
@@ -46730,25 +47439,35 @@ const noPassDataToParent = defineRule({
46730
47439
  return getFunctionalUpdaterDataRefs(analysis, argument);
46731
47440
  }
46732
47441
  if (isHandlerBagArgument(analysis, argument)) return [];
47442
+ if (isExternalSubscriptionHookResultArgument(analysis, argument)) return [];
46733
47443
  if (isParentWiredHookResultArgument(analysis, argument)) return [];
46734
47444
  if (isNodeOfType(argument, "Identifier")) {
46735
47445
  const argumentRef = getRef(analysis, argument);
46736
47446
  if (argumentRef && resolveToFunction(argumentRef)) return [];
46737
47447
  }
46738
47448
  return getDownstreamRefs(analysis, argument);
46739
- }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) || isExternalSubscriptionHookRef(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
46740
- 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
+ }
46741
47461
  if (!argsUpstreamRefs.some((argRef) => {
46742
- if (isUseStateIdentifier(argRef.identifier)) return false;
46743
- if (isExternalSubscriptionHookRef(analysis, argRef)) return false;
47462
+ const argIdentifier = argRef.identifier;
47463
+ if (isUseStateIdentifier(argIdentifier)) return false;
46744
47464
  if (isProp(analysis, argRef)) return false;
46745
- if (isUseRefIdentifier(argRef.identifier)) return false;
47465
+ if (isUseRefIdentifier(argIdentifier)) return false;
46746
47466
  if (isRefCurrent(argRef)) return false;
46747
47467
  if (isConstant(argRef)) return false;
46748
47468
  if (isParentWiredHookResultRef(analysis, argRef)) return false;
46749
47469
  if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
46750
47470
  if (resolveToFunction(argRef)) return false;
46751
- const argIdentifier = argRef.identifier;
46752
47471
  if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
46753
47472
  if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
46754
47473
  return true;
@@ -55739,17 +56458,6 @@ const preferModuleScopePureFunction = defineRule({
55739
56458
  }
55740
56459
  });
55741
56460
  //#endregion
55742
- //#region src/plugin/utils/get-require-call-source.ts
55743
- const getRequireCallSource = (expression) => {
55744
- const unwrappedExpression = stripParenExpression(expression);
55745
- if (isNodeOfType(unwrappedExpression, "MemberExpression")) return getRequireCallSource(unwrappedExpression.object);
55746
- if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
55747
- if (!isNodeOfType(unwrappedExpression.callee, "Identifier") || unwrappedExpression.callee.name !== "require") return null;
55748
- const [firstArgument] = unwrappedExpression.arguments ?? [];
55749
- if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
55750
- return typeof firstArgument.value === "string" ? firstArgument.value : null;
55751
- };
55752
- //#endregion
55753
56461
  //#region src/plugin/utils/is-proven-node-crypto-namespace-reference.ts
55754
56462
  const NODE_CRYPTO_MODULE_SOURCES = new Set(["crypto", "node:crypto"]);
55755
56463
  const isProvenNodeCryptoNamespaceReference = (expression, scopes) => {
@@ -59076,7 +59784,7 @@ const renderingUsetransitionLoading = defineRule({
59076
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);
59077
59785
  //#endregion
59078
59786
  //#region src/plugin/rules/security-scan/repository-secret-file.ts
59079
- 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);
59080
59788
  const repositorySecretFile = defineRule({
59081
59789
  id: "repository-secret-file",
59082
59790
  title: "Secret file checked into repository",
@@ -60457,6 +61165,717 @@ const isSetterCalledDuringRender = (root, setterName) => {
60457
61165
  return found;
60458
61166
  };
60459
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
60460
61879
  //#region src/plugin/rules/state-and-effects/rerender-state-only-in-handlers.ts
60461
61880
  const isInsideConditionTest = (identifier, stopAt) => {
60462
61881
  let current = identifier;
@@ -60578,7 +61997,8 @@ const rerenderStateOnlyInHandlers = defineRule({
60578
61997
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
60579
61998
  const bindings = collectUseStateBindings(componentBody, context.scopes);
60580
61999
  if (bindings.length === 0) return;
60581
- if (collectRenderReachableExpressions(componentBody).length === 0) return;
62000
+ const renderReachableExpressions = collectRenderReachableExpressions(componentBody);
62001
+ if (renderReachableExpressions.length === 0) return;
60582
62002
  const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
60583
62003
  const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
60584
62004
  const directRenderNames = collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames);
@@ -60592,12 +62012,22 @@ const rerenderStateOnlyInHandlers = defineRule({
60592
62012
  for (const effectInfo of effectInfos) for (const dependencyName of effectInfo.dependencyNames) if (!selfEchoValueNames.has(dependencyName)) effectConsumedNames.add(dependencyName);
60593
62013
  for (const hookArgumentName of collectCustomHookArgumentNames(componentBody, context.scopes)) effectConsumedNames.add(hookArgumentName);
60594
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;
60595
62023
  const calledSetterNames = /* @__PURE__ */ new Set();
60596
62024
  walkAst(componentBody, (child) => {
60597
62025
  if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && setterNames.has(child.callee.name)) calledSetterNames.add(child.callee.name);
60598
62026
  });
60599
62027
  for (const binding of bindings) {
60600
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;
60601
62031
  if (binding.valueName === "_" || binding.valueName.startsWith("_")) continue;
60602
62032
  const setterSuffix = binding.setterName.slice(3);
60603
62033
  if (/^(TriggerRender|ForceUpdate|Rerender|ForceRender|Tick|Bump|BumpVersion|InvalidateRender|Refresh|Repaint)$/i.test(setterSuffix)) continue;
@@ -71773,18 +73203,6 @@ const reactDoctorRules = [
71773
73203
  requires: [...new Set(["react", ...jsxNoScriptUrl.requires ?? []])]
71774
73204
  }
71775
73205
  },
71776
- {
71777
- key: "react-doctor/jsx-no-target-blank",
71778
- id: "jsx-no-target-blank",
71779
- source: "react-doctor",
71780
- originallyExternal: true,
71781
- rule: {
71782
- ...jsxNoTargetBlank,
71783
- framework: "global",
71784
- category: "Security",
71785
- requires: [...new Set(["react", ...jsxNoTargetBlank.requires ?? []])]
71786
- }
71787
- },
71788
73206
  {
71789
73207
  key: "react-doctor/jsx-no-undef",
71790
73208
  id: "jsx-no-undef",