oxlint-plugin-react-doctor 0.8.1-dev.7c9dbed → 0.8.1-dev.82c4a12

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 +46 -0
  2. package/dist/index.js +597 -2015
  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
- new Set([
765
+ const EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS = 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$52 = (settings) => {
2625
+ const resolveSettings$53 = (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$52(context.settings);
2748
+ const settings = resolveSettings$53(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$51 = (settings) => {
2810
+ const resolveSettings$52 = (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$51(context.settings);
2860
+ const settings = resolveSettings$52(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$50 = (settings) => {
3228
+ const resolveSettings$51 = (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$50(context.settings);
3271
+ const settings = resolveSettings$51(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$49 = (settings) => {
4184
+ const resolveSettings$50 = (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$49(context.settings);
4200
+ const settings = resolveSettings$50(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$48 = (settings) => {
7118
+ const resolveSettings$49 = (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$48(context.settings);
7131
+ const settings = resolveSettings$49(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$47 = (settings) => {
7250
+ const resolveSettings$48 = (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$47(context.settings);
7488
+ const settings = resolveSettings$48(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$46 = (settings) => {
7569
+ const resolveSettings$47 = (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$46(context.settings);
7670
+ const settings = resolveSettings$47(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$45 = (settings) => {
10383
+ const resolveSettings$46 = (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$45(context.settings);
10716
+ const settings = resolveSettings$46(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$44 = (settings) => {
12668
+ const resolveSettings$45 = (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$44(context.settings);
12850
+ const settings = resolveSettings$45(context.settings);
12851
12851
  const ignoreNamed = !settings.ignoreTranspilerName;
12852
12852
  const reportAt = (node) => {
12853
12853
  context.report({
@@ -13827,116 +13827,6 @@ 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
13940
13830
  //#region src/plugin/utils/get-function-binding-name.ts
13941
13831
  const getFunctionBindingIdentifier$1 = (functionNode) => {
13942
13832
  if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
@@ -13974,19 +13864,14 @@ const isAstDescendant = (inner, outer) => {
13974
13864
  };
13975
13865
  //#endregion
13976
13866
  //#region src/plugin/utils/react-ref-origin.ts
13977
- const REACT_REF_API_NAMES = new Set(["createRef", "useRef"]);
13978
- const resolveReactRefSymbol = (memberExpression, scopes, options = {}) => {
13867
+ const resolveReactRefSymbol = (memberExpression, scopes) => {
13979
13868
  const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
13980
13869
  if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticPropertyName(memberExpression) !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
13981
13870
  const symbol = resolveConstIdentifierAlias(receiver, scopes);
13982
13871
  if (!symbol?.initializer) return null;
13983
13872
  const initializer = stripParenExpression(symbol.initializer);
13984
13873
  if (!isNodeOfType(initializer, "CallExpression")) return 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;
13874
+ return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
13990
13875
  };
13991
13876
  const resolveReactRefCurrentOriginSymbol = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13992
13877
  const expression = stripParenExpression(node);
@@ -14085,20 +13970,6 @@ const isNodeReachableWithinFunction = (node, context) => {
14085
13970
  return false;
14086
13971
  };
14087
13972
  //#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
14102
13973
  //#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
14103
13974
  const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
14104
13975
  const REPLAYABLE_ITERATOR_COLLECTION_CACHE = /* @__PURE__ */ new WeakMap();
@@ -14147,7 +14018,7 @@ const resolveExpressionKey = (expression, context, visitedSymbolIds = /* @__PURE
14147
14018
  return objectKey ? `${objectKey}.${unwrappedExpression.property.name}` : null;
14148
14019
  }
14149
14020
  if (isNodeOfType(unwrappedExpression, "ThisExpression")) return "this";
14150
- if (isNodeOfType(unwrappedExpression, "Literal") && (typeof unwrappedExpression.value === "string" || typeof unwrappedExpression.value === "number" || typeof unwrappedExpression.value === "boolean")) return `literal:${String(unwrappedExpression.value)}`;
14021
+ if (isNodeOfType(unwrappedExpression, "Literal") && (typeof unwrappedExpression.value === "string" || typeof unwrappedExpression.value === "number")) return `literal:${String(unwrappedExpression.value)}`;
14151
14022
  if (isFunctionLike$1(unwrappedExpression)) {
14152
14023
  const rangeStart = getRangeStart(unwrappedExpression);
14153
14024
  return rangeStart === null ? null : `function:${rangeStart}`;
@@ -14176,11 +14047,9 @@ const resolveForEachProjection = (expression, context) => {
14176
14047
  const collectionKey = resolveExpressionKey(forEachCallee.object, context);
14177
14048
  if (!collectionKey) return null;
14178
14049
  const firstParameter = callbackNode.params[0];
14179
- const assignmentPattern = isNodeOfType(symbol.bindingIdentifier.parent, "AssignmentPattern") && symbol.bindingIdentifier.parent.left === symbol.bindingIdentifier ? symbol.bindingIdentifier.parent : null;
14180
- const propertyName = isNodeOfType(firstParameter, "ObjectPattern") ? getDestructuredBindingPropertyName(symbol.bindingIdentifier) : null;
14181
- const defaultValueKey = assignmentPattern ? resolveExpressionKey(assignmentPattern.right, context) : null;
14182
- if (assignmentPattern && !defaultValueKey) return null;
14183
- const parameterProjection = firstParameter === symbol.bindingIdentifier ? "value" : propertyName && defaultValueKey ? `${propertyName}=default:${defaultValueKey}` : propertyName;
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;
14184
14053
  if (!parameterProjection) return null;
14185
14054
  return {
14186
14055
  collectionKey,
@@ -14192,23 +14061,13 @@ const resolveForEachProjectionKey = (expression, context) => {
14192
14061
  return projection ? `forEach:${projection.collectionKey}:${projection.projectionKey}` : null;
14193
14062
  };
14194
14063
  const resolveResourceIdentityKey = (expression, context) => resolveForEachProjectionKey(expression, context) ?? resolveExpressionKey(expression, context);
14195
- const resolveEventListenerCaptureValueIdentityKey = (expression, context) => {
14196
- if (!expression) return null;
14197
- const directIdentityKey = resolveResourceIdentityKey(expression, context);
14198
- if (directIdentityKey) return directIdentityKey;
14199
- const unwrappedExpression = stripParenExpression(expression);
14200
- if (!isNodeOfType(unwrappedExpression, "BinaryExpression") && !isNodeOfType(unwrappedExpression, "LogicalExpression")) return null;
14201
- const leftIdentityKey = resolveEventListenerCaptureValueIdentityKey(unwrappedExpression.left, context);
14202
- const rightIdentityKey = resolveEventListenerCaptureValueIdentityKey(unwrappedExpression.right, context);
14203
- return leftIdentityKey && rightIdentityKey ? `${unwrappedExpression.type}:${unwrappedExpression.operator}:${leftIdentityKey}:${rightIdentityKey}` : null;
14204
- };
14205
14064
  const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqueOptionsIdentity) => {
14206
14065
  const capture = resolveEventListenerCapture(optionsNode, { allowIndeterminateEntries: true });
14207
14066
  if (capture !== null) return `capture:${String(capture)}`;
14208
14067
  if (!optionsNode) return null;
14209
14068
  const unwrappedOptions = stripParenExpression(optionsNode);
14210
14069
  if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) {
14211
- const optionsKey = allowOpaqueOptionsIdentity ? resolveEventListenerCaptureValueIdentityKey(unwrappedOptions, context) : null;
14070
+ const optionsKey = allowOpaqueOptionsIdentity ? resolveResourceIdentityKey(unwrappedOptions, context) : null;
14212
14071
  return optionsKey ? `options:${optionsKey}` : null;
14213
14072
  }
14214
14073
  let captureKey = "capture:false";
@@ -14223,7 +14082,7 @@ const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqu
14223
14082
  continue;
14224
14083
  }
14225
14084
  if (propertyName === "capture") {
14226
- const propertyValueKey = resolveEventListenerCaptureValueIdentityKey(property.value, context);
14085
+ const propertyValueKey = resolveResourceIdentityKey(property.value, context);
14227
14086
  captureKey = propertyValueKey ? `capture-value:${propertyValueKey}` : null;
14228
14087
  }
14229
14088
  }
@@ -14405,6 +14264,30 @@ const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, conte
14405
14264
  }
14406
14265
  return matchingBlocks.size > 0;
14407
14266
  };
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
+ };
14408
14291
  const removeSynchronouslyReleasedUsages = (callback, usages, context) => {
14409
14292
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
14410
14293
  if (!isNodeOfType(callback.body, "BlockStatement")) return usages;
@@ -14647,6 +14530,18 @@ const findPushedResourceCollectionKey = (usage, context) => {
14647
14530
  return memberNode.property.name === "forEach" || memberNode.property.name === "push";
14648
14531
  }) ? resolveExpressionKey(pushCallee.object, context) : null;
14649
14532
  };
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
+ };
14650
14545
  const resolveStableValue = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
14651
14546
  if (!expression) return null;
14652
14547
  const unwrappedExpression = stripParenExpression(expression);
@@ -14770,7 +14665,7 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
14770
14665
  const helperFunction = isNodeOfType(stableHelperFunction, "Identifier") ? resolveSingleAssignedCleanupFunction(stableHelperFunction, usage, context) : stableHelperFunction;
14771
14666
  if (helperFunction && isFunctionLike$1(helperFunction) && !helperFunction.async && !helperFunction.generator && doesCleanupFunctionReleaseUsage(helperFunction, usage, context, new Set(visitedFunctions))) matchingLoopOrHelperAnchors.push(cleanupCall);
14772
14667
  });
14773
- return didCleanupFunctionMatch || doNodesCoverEveryPathFromFunctionEntry(cleanupFunction, matchingLoopOrHelperAnchors, context);
14668
+ return didCleanupFunctionMatch || doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, matchingLoopOrHelperAnchors, context);
14774
14669
  };
14775
14670
  const doesBoundCleanupReleaseUsage = (expression, usage, context) => {
14776
14671
  const callExpression = stripParenExpression(expression);
@@ -14799,7 +14694,7 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
14799
14694
  walkInsideStatementBlocks(callback.body, (child) => {
14800
14695
  if (isNodeOfType(child, "ReturnStatement") && child.argument && doesReturnedValueReleaseUsage(stripParenExpression(child.argument))) matchingCleanupReturns.push(child);
14801
14696
  });
14802
- return doNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
14697
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
14803
14698
  };
14804
14699
  const doesTestRequireLiveExpressionKey = (test, expressionKey, context) => {
14805
14700
  if (resolveExpressionKey(test, context) === expressionKey) return true;
@@ -14823,24 +14718,7 @@ const findLiveExpressionGuardForRelease = (releaseCall, owner, expressionKey, co
14823
14718
  return null;
14824
14719
  };
14825
14720
  const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => usage.handleKey === null ? null : findLiveExpressionGuardForRelease(releaseCall, owner, usage.handleKey, 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) => {
14721
+ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
14844
14722
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
14845
14723
  const functionCfg = context.cfg.cfgFor(callback);
14846
14724
  const usageBlock = functionCfg?.blockOf(usage.node);
@@ -14851,8 +14729,7 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context, allowUnreleasedPat
14851
14729
  if (!isNodeOfType(child, "CallExpression")) return;
14852
14730
  const releaseStart = getRangeStart(child);
14853
14731
  const handleGuard = findDirectHandleGuardForRelease(child, callback, usage, context);
14854
- const releaseBlock = functionCfg.blockOf(child);
14855
- if (releaseStart === null || releaseStart >= usageStart || releaseBlock !== usageBlock && !handleGuard || !handleGuard && hasExecutionBoundaryNotSharedWithUsage(child, usage.node, callback)) return;
14732
+ if (releaseStart === null || releaseStart >= usageStart || functionCfg.blockOf(child) !== usageBlock && !handleGuard) return;
14856
14733
  if (doesReleaseCallMatchUsage(child, usage, context)) {
14857
14734
  matchingReleaseAnchors.push(handleGuard ?? child);
14858
14735
  return;
@@ -14860,7 +14737,7 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context, allowUnreleasedPat
14860
14737
  const helperFunction = resolveStableValue(child.callee, context);
14861
14738
  if (helperFunction && isFunctionLike$1(helperFunction) && doesCleanupFunctionReleaseUsage(helperFunction, usage, context)) matchingReleaseAnchors.push(handleGuard ?? child);
14862
14739
  });
14863
- return allowUnreleasedPathsWithoutUsage ? doMatchingNodesCoverEveryPathBeforeUsage(usage.node, matchingReleaseAnchors, callback, context) : doNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
14740
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
14864
14741
  };
14865
14742
  const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
14866
14743
  const componentFunction = findEnclosingFunction$1(callback);
@@ -14880,7 +14757,7 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
14880
14757
  });
14881
14758
  return didFindUnmountCleanup;
14882
14759
  };
14883
- const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasStableUnmountCleanupForUsage(callback, usage, context) && hasRerunReleaseBeforeUsage(callback, usage, context, usage.registrationVerbName === "setTimeout");
14760
+ const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
14884
14761
  const collectBlockingBooleanStates = (expression, blockedExpressionValue, guardNode, context) => {
14885
14762
  const unwrappedExpression = stripParenExpression(expression);
14886
14763
  if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, guardNode, context);
@@ -15111,7 +14988,7 @@ const hasGuardedRefOwnedNestedCleanup = (callback, usage, cleanupReturns, contex
15111
14988
  const usageFunction = findEnclosingFunction$1(usage.node);
15112
14989
  const usageExpression = findTransparentExpressionRoot(usage.node);
15113
14990
  const usageAssignment = usageExpression.parent;
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;
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;
15115
14992
  const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
15116
14993
  if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
15117
14994
  const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
@@ -15159,7 +15036,7 @@ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) =>
15159
15036
  });
15160
15037
  }
15161
15038
  });
15162
- if (!doNodesCoverEveryPathFromFunctionEntry(cleanupFunction, globalReleaseProofs.map((releaseProof) => releaseProof.anchor), context)) return false;
15039
+ if (!doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, globalReleaseProofs.map((releaseProof) => releaseProof.anchor), context)) return false;
15163
15040
  globalReleaseProofsByCleanup.set(cleanupFunction, globalReleaseProofs);
15164
15041
  }
15165
15042
  const handleAssignments = handleSymbol.references.filter((reference) => isWithinAssignmentTarget(reference.identifier));
@@ -15372,7 +15249,7 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
15372
15249
  });
15373
15250
  const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
15374
15251
  const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
15375
- return doNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
15252
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
15376
15253
  };
15377
15254
  const findDirectExhaustiveForEachCleanupFunction = (releaseNode, requiredCollectionKeys, context) => {
15378
15255
  let currentNode = findTransparentExpressionRoot(releaseNode);
@@ -15385,7 +15262,7 @@ const findDirectExhaustiveForEachCleanupFunction = (releaseNode, requiredCollect
15385
15262
  const isDirectConciseBody = ownerFunction.body === currentNode;
15386
15263
  const statementNode = currentNode.parent;
15387
15264
  const isDirectBlockStatement = isNodeOfType(ownerFunction.body, "BlockStatement") && isNodeOfType(statementNode, "ExpressionStatement") && statementNode.parent === ownerFunction.body;
15388
- if (!isDirectConciseBody && !isDirectBlockStatement || !doNodesCoverEveryPathFromFunctionEntry(ownerFunction, [isDirectBlockStatement ? statementNode : currentNode], context)) return null;
15265
+ if (!isDirectConciseBody && !isDirectBlockStatement || !doMatchingNodesCoverEveryPathFromFunctionEntry(ownerFunction, [isDirectBlockStatement ? statementNode : currentNode], context)) return null;
15389
15266
  const forEachCall = findEnclosingForEachCall(ownerFunction);
15390
15267
  if (!forEachCall) return replayedCollectionKeys.size === requiredCollectionKeys.size && isReturnedEffectCleanupFunction(ownerFunction, context) ? ownerFunction : null;
15391
15268
  const forEachCallee = stripParenExpression(forEachCall.callee);
@@ -15473,7 +15350,7 @@ const hasSafeForEachProjectionCleanup = (registrationCall, releaseCall, context)
15473
15350
  const registrationEventKey = resolveResourceIdentityKey(registrationCall.arguments?.[0], context);
15474
15351
  const releaseEventKey = resolveResourceIdentityKey(releaseCall.arguments?.[0], context);
15475
15352
  const doesHandlerlessOffReleaseEveryRegistration = releaseVerbName === "off" && !releaseHandler && (releaseCall.arguments?.length === 0 || registrationEventKey !== null && registrationEventKey === releaseEventKey);
15476
- if (Boolean(releaseFunction && isFunctionLike$1(releaseFunction) && isReturnedEffectCleanupFunction(releaseFunction, context) && doNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseCall], context)) && (releaseVerbName !== null && UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName) || doesHandlerlessOffReleaseEveryRegistration)) return true;
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;
15477
15354
  const projections = [
15478
15355
  registrationCallee.object,
15479
15356
  registrationCall.arguments?.[0],
@@ -15672,7 +15549,7 @@ const hasEffectCleanupInvocation = (storage, usage, context) => {
15672
15549
  if (!cleanupFunction || !cleanupFunctionInvokesRef(cleanupFunction)) return;
15673
15550
  matchingReturns.push(child);
15674
15551
  });
15675
- return doNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15552
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15676
15553
  };
15677
15554
  let didFindInvocation = false;
15678
15555
  walkAst(componentFunction.body, (child) => {
@@ -15724,7 +15601,7 @@ const isRetainedDisposerRefRelease = (releaseNode, usage, context) => {
15724
15601
  return findRetainedDisposerStorages(disposerFunction, usage, context).some((storage) => isRetainedDisposerStorageEstablished(storage, usage, context) && !hasUnsafeRetainedDisposerOverwrite(storage, usage, context) && (hasEffectCleanupInvocation(storage, usage, context) || hasCallbackRefReplacementInvocation(storage, usage, context)));
15725
15602
  };
15726
15603
  const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
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;
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;
15728
15605
  const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
15729
15606
  if (!isNodeOfType(releaseCall, "CallExpression")) return false;
15730
15607
  if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], releaseCall.arguments?.[2], context)) return false;
@@ -15952,7 +15829,7 @@ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retaine
15952
15829
  walkInsideStatementBlocks(effectCallback.body, (child) => {
15953
15830
  if (isNodeOfType(child, "ReturnStatement") && child.argument && matchesReturnedCleanup(stripParenExpression(child.argument))) matchingReturns.push(child);
15954
15831
  });
15955
- return doNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15832
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15956
15833
  };
15957
15834
  const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
15958
15835
  const componentFunction = findEnclosingFunction$1(retainedFunction);
@@ -15995,6 +15872,15 @@ const findUnconditionalReturnStatement = (expression, ownerFunction) => {
15995
15872
  const returnStatement = expressionRoot.parent;
15996
15873
  return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === expressionRoot && findEnclosingFunction$1(returnStatement) === ownerFunction ? returnStatement : null;
15997
15874
  };
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
+ };
15998
15884
  const doesResourceResultEscape = (resourceNode, allowReturnedResourceEscape, allowConciseReturnEscape, context) => {
15999
15885
  if (!allowReturnedResourceEscape) return false;
16000
15886
  let currentNode = resourceNode;
@@ -16243,7 +16129,7 @@ const isReactRefCallbackCleanupOwnedByEffect = (retainedFunction, cleanupFunctio
16243
16129
  if (child !== returnedCleanupFunction.body && isFunctionLike$1(child)) return false;
16244
16130
  if (isNodeOfType(child, "CallExpression") && resolveRefOwnedCleanupFunction(child.callee, context) === cleanupFunction) matchingCalls.push(child);
16245
16131
  });
16246
- return doNodesCoverEveryPathFromFunctionEntry(returnedCleanupFunction, matchingCalls, context);
16132
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(returnedCleanupFunction, matchingCalls, context);
16247
16133
  };
16248
16134
  const matchingReturns = [];
16249
16135
  walkInsideStatementBlocks(retainedFunction.body, (child) => {
@@ -19046,7 +18932,7 @@ const compileGlob = (pattern) => {
19046
18932
  };
19047
18933
  //#endregion
19048
18934
  //#region src/plugin/rules/react-builtins/forbid-component-props.ts
19049
- const resolveSettings$43 = (settings) => {
18935
+ const resolveSettings$44 = (settings) => {
19050
18936
  const reactDoctor = settings?.["react-doctor"];
19051
18937
  return { forbid: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidComponentProps ?? {} : {}).forbid ?? [] };
19052
18938
  };
@@ -19098,7 +18984,7 @@ const forbidComponentProps = defineRule({
19098
18984
  recommendation: "Configure blocked component props so callers cannot bypass the component API contract.",
19099
18985
  category: "Architecture",
19100
18986
  create: (context) => {
19101
- const entries = resolveSettings$43(context.settings).forbid.map(normalizeEntry);
18987
+ const entries = resolveSettings$44(context.settings).forbid.map(normalizeEntry);
19102
18988
  return { JSXOpeningElement(node) {
19103
18989
  if (entries.length === 0) return;
19104
18990
  if (!isSupportedJsxName(node.name)) return;
@@ -19127,7 +19013,7 @@ const forbidComponentProps = defineRule({
19127
19013
  //#endregion
19128
19014
  //#region src/plugin/rules/react-builtins/forbid-dom-props.ts
19129
19015
  const buildMessage$24 = (propName, customMessage) => customMessage ?? `Your project blocks the \`${propName}\` prop on plain HTML tags, so this bypasses the agreed DOM API contract.`;
19130
- const resolveSettings$42 = (settings) => {
19016
+ const resolveSettings$43 = (settings) => {
19131
19017
  const reactDoctor = settings?.["react-doctor"];
19132
19018
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidDomProps ?? {} : {};
19133
19019
  const map = /* @__PURE__ */ new Map();
@@ -19149,7 +19035,7 @@ const forbidDomProps = defineRule({
19149
19035
  recommendation: "Configure blocked DOM props so plain HTML tags stay on the agreed DOM API surface.",
19150
19036
  category: "Architecture",
19151
19037
  create: (context) => {
19152
- const forbidMap = resolveSettings$42(context.settings);
19038
+ const forbidMap = resolveSettings$43(context.settings);
19153
19039
  return { JSXOpeningElement(node) {
19154
19040
  if (forbidMap.size === 0) return;
19155
19041
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
@@ -19212,7 +19098,7 @@ const isReactFunctionCall = (node, expectedCall) => {
19212
19098
  //#endregion
19213
19099
  //#region src/plugin/rules/react-builtins/forbid-elements.ts
19214
19100
  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.`;
19215
- const resolveSettings$41 = (settings) => {
19101
+ const resolveSettings$42 = (settings) => {
19216
19102
  const reactDoctor = settings?.["react-doctor"];
19217
19103
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidElements ?? {} : {};
19218
19104
  const map = /* @__PURE__ */ new Map();
@@ -19228,7 +19114,7 @@ const forbidElements = defineRule({
19228
19114
  recommendation: "Configure blocked elements so code stays on the approved UI primitives.",
19229
19115
  category: "Architecture",
19230
19116
  create: (context) => {
19231
- const forbidMap = resolveSettings$41(context.settings);
19117
+ const forbidMap = resolveSettings$42(context.settings);
19232
19118
  return {
19233
19119
  JSXOpeningElement(node) {
19234
19120
  if (forbidMap.size === 0) return;
@@ -19337,7 +19223,7 @@ const DEFAULT_HEADING_TAGS = [
19337
19223
  "h5",
19338
19224
  "h6"
19339
19225
  ];
19340
- const resolveSettings$40 = (settings) => {
19226
+ const resolveSettings$41 = (settings) => {
19341
19227
  const reactDoctor = settings?.["react-doctor"];
19342
19228
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.headingHasContent ?? {} : {};
19343
19229
  return { headingTags: new Set([...DEFAULT_HEADING_TAGS, ...ruleSettings.components ?? []]) };
@@ -19350,7 +19236,7 @@ const headingHasContent = defineRule({
19350
19236
  recommendation: "Put readable text in every heading.",
19351
19237
  category: "Accessibility",
19352
19238
  create: (context) => {
19353
- const settings = resolveSettings$40(context.settings);
19239
+ const settings = resolveSettings$41(context.settings);
19354
19240
  return { JSXOpeningElement(node) {
19355
19241
  const elementType = getElementType(node, context.settings);
19356
19242
  if (!settings.headingTags.has(elementType)) return;
@@ -19371,7 +19257,7 @@ const headingHasContent = defineRule({
19371
19257
  //#region src/plugin/rules/react-builtins/hook-use-state.ts
19372
19258
  const REQUIRE_DESTRUCTURE_MESSAGE = "`useState` should be destructured as `[value, setValue]` so readers can see the state value and setter together.";
19373
19259
  const NAMING_CONVENTION_MESSAGE = "This `useState` setter does not match its value name, so updates are harder to trace.";
19374
- const resolveSettings$39 = (settings) => {
19260
+ const resolveSettings$40 = (settings) => {
19375
19261
  const reactDoctor = settings?.["react-doctor"];
19376
19262
  return { allowDestructuredState: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.hookUseState ?? {} : {}).allowDestructuredState ?? false };
19377
19263
  };
@@ -19398,7 +19284,7 @@ const hookUseState = defineRule({
19398
19284
  recommendation: "Destructure `useState` as `const [thing, setThing] = useState(…)` so state reads and writes stay visible together.",
19399
19285
  category: "Architecture",
19400
19286
  create: (context) => {
19401
- const { allowDestructuredState } = resolveSettings$39(context.settings);
19287
+ const { allowDestructuredState } = resolveSettings$40(context.settings);
19402
19288
  return { CallExpression(node) {
19403
19289
  const callExpressionNode = node;
19404
19290
  if (!isReactFunctionCall(callExpressionNode, "useState")) return;
@@ -19531,7 +19417,7 @@ const hooksNoNanInDeps = defineRule({
19531
19417
  //#endregion
19532
19418
  //#region src/plugin/rules/a11y/html-has-lang.ts
19533
19419
  const MESSAGE$52 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
19534
- const resolveSettings$38 = (settings) => {
19420
+ const resolveSettings$39 = (settings) => {
19535
19421
  const reactDoctor = settings?.["react-doctor"];
19536
19422
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
19537
19423
  };
@@ -19568,7 +19454,7 @@ const htmlHasLang = defineRule({
19568
19454
  recommendation: "Set `<html lang=\"…\">` so screen readers know the page language.",
19569
19455
  category: "Accessibility",
19570
19456
  create: (context) => {
19571
- const settings = resolveSettings$38(context.settings);
19457
+ const settings = resolveSettings$39(context.settings);
19572
19458
  const tagSet = new Set(settings.htmlTags);
19573
19459
  return { JSXOpeningElement(node) {
19574
19460
  const tag = getElementType(node, context.settings);
@@ -20084,7 +19970,7 @@ const DEFAULT_REDUNDANT_WORDS = [
20084
19970
  "photo",
20085
19971
  "picture"
20086
19972
  ];
20087
- const resolveSettings$37 = (settings) => {
19973
+ const resolveSettings$38 = (settings) => {
20088
19974
  const reactDoctor = settings?.["react-doctor"];
20089
19975
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.imgRedundantAlt ?? {} : {};
20090
19976
  return {
@@ -20132,7 +20018,7 @@ const imgRedundantAlt = defineRule({
20132
20018
  category: "Accessibility",
20133
20019
  create: (context) => {
20134
20020
  if (isGeneratedImageRenderContext(context)) return {};
20135
- const settings = resolveSettings$37(context.settings);
20021
+ const settings = resolveSettings$38(context.settings);
20136
20022
  return { JSXOpeningElement(node) {
20137
20023
  const tag = getElementType(node, context.settings);
20138
20024
  if (!settings.components.includes(tag)) return;
@@ -20440,7 +20326,7 @@ const COMPOSITE_ITEM_ROLES = new Set([
20440
20326
  "row",
20441
20327
  "gridcell"
20442
20328
  ]);
20443
- const resolveSettings$36 = (settings) => {
20329
+ const resolveSettings$37 = (settings) => {
20444
20330
  const reactDoctor = settings?.["react-doctor"];
20445
20331
  return { tabbable: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.interactiveSupportsFocus ?? {} : {}).tabbable ?? DEFAULT_TABBABLE_ROLES };
20446
20332
  };
@@ -20452,7 +20338,7 @@ const interactiveSupportsFocus = defineRule({
20452
20338
  recommendation: "Add keyboard focus support so users can reach interactive elements without a pointer.",
20453
20339
  category: "Accessibility",
20454
20340
  create: (context) => {
20455
- const settings = resolveSettings$36(context.settings);
20341
+ const settings = resolveSettings$37(context.settings);
20456
20342
  const tabbableSet = new Set(settings.tabbable);
20457
20343
  return { JSXOpeningElement(node) {
20458
20344
  if (isLocalTestScaffoldJsx(node, context)) return;
@@ -22593,49 +22479,25 @@ const jsLengthCheckFirst = defineRule({
22593
22479
  });
22594
22480
  //#endregion
22595
22481
  //#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
- ]);
22602
22482
  const isProvenGlobalObjectReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
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
- }
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);
22621
22490
  };
22622
22491
  const isProvenGlobalNamespaceReference = (expression, namespaceName, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
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);
22492
+ const strippedExpression = stripParenExpression(expression);
22493
+ if (isNodeOfType(strippedExpression, "Identifier")) {
22627
22494
  if (strippedExpression.name === namespaceName && scopes.isGlobalReference(strippedExpression)) return true;
22628
22495
  const symbol = scopes.symbolFor(strippedExpression);
22629
- if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
22496
+ if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
22630
22497
  visitedSymbolIds.add(symbol.id);
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;
22498
+ return isProvenGlobalNamespaceReference(symbol.initializer, namespaceName, scopes, visitedSymbolIds);
22638
22499
  }
22500
+ return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
22639
22501
  };
22640
22502
  //#endregion
22641
22503
  //#region src/plugin/rules/js-performance/js-min-max-loop.ts
@@ -23748,7 +23610,7 @@ const jsTosortedImmutable = defineRule({
23748
23610
  const NEVER_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
23749
23611
  const ALWAYS_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
23750
23612
  const FALSE_OMITTED_MESSAGE = (attributeName) => `\`${attributeName}={false}\` does nothing, so the explicit false value adds noise without changing output.`;
23751
- const resolveSettings$35 = (settings) => {
23613
+ const resolveSettings$36 = (settings) => {
23752
23614
  const reactDoctor = settings?.["react-doctor"];
23753
23615
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxBooleanValue ?? {} : {};
23754
23616
  return {
@@ -23766,7 +23628,7 @@ const jsxBooleanValue = defineRule({
23766
23628
  recommendation: "Use one boolean-attribute style so equivalent true props scan the same across the codebase.",
23767
23629
  category: "Architecture",
23768
23630
  create: (context) => {
23769
- const settings = resolveSettings$35(context.settings);
23631
+ const settings = resolveSettings$36(context.settings);
23770
23632
  const alwaysSet = new Set(settings.always);
23771
23633
  const neverSet = new Set(settings.never);
23772
23634
  return { JSXAttribute(node) {
@@ -23820,7 +23682,7 @@ const jsxBooleanValue = defineRule({
23820
23682
  const UNNECESSARY_BRACES_MESSAGE = "These curly braces wrap a literal value, so they add JSX noise without changing output.";
23821
23683
  const REQUIRED_BRACES_MESSAGE = "This JSX value needs `{ }` so React reads it as an expression instead of text.";
23822
23684
  const isAllowedMode = (value) => value === "always" || value === "never" || value === "ignore";
23823
- const resolveSettings$34 = (settings) => {
23685
+ const resolveSettings$35 = (settings) => {
23824
23686
  const reactDoctor = settings?.["react-doctor"];
23825
23687
  const ruleSettingsRaw = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxCurlyBracePresence : void 0;
23826
23688
  if (isAllowedMode(ruleSettingsRaw)) return {
@@ -23916,7 +23778,7 @@ const jsxCurlyBracePresence = defineRule({
23916
23778
  recommendation: "Use one JSX literal style so equivalent markup scans the same without extra JSX noise.",
23917
23779
  category: "Architecture",
23918
23780
  create: (context) => {
23919
- const settings = resolveSettings$34(context.settings);
23781
+ const settings = resolveSettings$35(context.settings);
23920
23782
  return {
23921
23783
  JSXAttribute(node) {
23922
23784
  const value = node.value;
@@ -23999,7 +23861,7 @@ const containsJsxElement = (root) => {
23999
23861
  //#region src/plugin/rules/react-builtins/jsx-filename-extension.ts
24000
23862
  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.`;
24001
23863
  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.`;
24002
- const resolveSettings$33 = (settings) => {
23864
+ const resolveSettings$34 = (settings) => {
24003
23865
  const reactDoctor = settings?.["react-doctor"];
24004
23866
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFilenameExtension ?? {} : {};
24005
23867
  return {
@@ -24021,7 +23883,7 @@ const jsxFilenameExtension = defineRule({
24021
23883
  recommendation: "Name JSX files with the configured extension so file names accurately signal which files contain JSX.",
24022
23884
  category: "Architecture",
24023
23885
  create: (context) => {
24024
- const settings = resolveSettings$33(context.settings);
23886
+ const settings = resolveSettings$34(context.settings);
24025
23887
  const allowedExtensions = normalizeExtensions(settings.extensions);
24026
23888
  const filename = normalizeFilename(context.filename ?? "fixture.tsx");
24027
23889
  const extensionOnly = path.extname(filename).slice(1);
@@ -24062,7 +23924,7 @@ const jsxFilenameExtension = defineRule({
24062
23924
  //#region src/plugin/rules/react-builtins/jsx-fragments.ts
24063
23925
  const SYNTAX_MESSAGE = "`<React.Fragment>` is used where shorthand fragments are configured, so similar wrappers look different across the codebase.";
24064
23926
  const ELEMENT_MESSAGE = "Fragment shorthand is used where explicit fragments are configured, so similar wrappers look different across the codebase.";
24065
- const resolveSettings$32 = (settings) => {
23927
+ const resolveSettings$33 = (settings) => {
24066
23928
  const reactDoctor = settings?.["react-doctor"];
24067
23929
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFragments ?? {} : {}).mode ?? "syntax" };
24068
23930
  };
@@ -24074,7 +23936,7 @@ const jsxFragments = defineRule({
24074
23936
  recommendation: "Use one fragment style so identical wrappers do not look different across files.",
24075
23937
  category: "Architecture",
24076
23938
  create: (context) => {
24077
- const { mode } = resolveSettings$32(context.settings);
23939
+ const { mode } = resolveSettings$33(context.settings);
24078
23940
  return {
24079
23941
  JSXElement(node) {
24080
23942
  if (mode !== "syntax") return;
@@ -24118,7 +23980,7 @@ const buildPropRegex = (handlerPropPrefix) => {
24118
23980
  const escaped = tokens.map((token) => token.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("|");
24119
23981
  return new RegExp(`^(${escaped})[A-Z].*$`);
24120
23982
  };
24121
- const resolveSettings$31 = (settings) => {
23983
+ const resolveSettings$32 = (settings) => {
24122
23984
  const reactDoctor = settings?.["react-doctor"];
24123
23985
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxHandlerNames ?? {} : {};
24124
23986
  let handlerPrefix = DEFAULT_HANDLER_PREFIX;
@@ -24194,7 +24056,7 @@ const jsxHandlerNames = defineRule({
24194
24056
  recommendation: "Use the `on…` prefix for event-handler props and `handle…` for handlers so readers can trace event flow.",
24195
24057
  category: "Architecture",
24196
24058
  create: (context) => {
24197
- const settings = resolveSettings$31(context.settings);
24059
+ const settings = resolveSettings$32(context.settings);
24198
24060
  return { JSXAttribute(node) {
24199
24061
  if (settings.ignoreComponentNames.length > 0) {
24200
24062
  const opening = node.parent;
@@ -24269,19 +24131,6 @@ const hasJsxKeyAttribute = (openingElement) => {
24269
24131
  return false;
24270
24132
  };
24271
24133
  //#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
24285
24134
  //#region src/plugin/utils/is-const-declared-binding.ts
24286
24135
  const BINDING_PATTERN_ANCESTOR_TYPES = new Set([
24287
24136
  "VariableDeclarator",
@@ -24326,17 +24175,11 @@ const ITERATOR_METHOD_NAMES$1 = new Set([
24326
24175
  "flatMap",
24327
24176
  "from"
24328
24177
  ]);
24329
- const RENDERING_CALL_NAMES = new Set([
24330
- "createPortal",
24331
- "hydrate",
24332
- "hydrateRoot",
24333
- "render"
24334
- ]);
24335
24178
  const MISSING_KEY_ARRAY = "Your users can see the wrong data when this array reorders.";
24336
24179
  const MISSING_KEY_ITERATOR = "Your users can see the wrong data when this list reorders.";
24337
- const KEY_BEFORE_SPREAD = "Place this `key` after the `{...spread}` so the spread cannot override it.";
24180
+ const KEY_AFTER_SPREAD = "Place this `key` before the `{...spread}` so the JSX transform can always extract it.";
24338
24181
  const DUPLICATE_KEY = (keyValue) => `Your users can see the wrong data because two elements share the key "${keyValue}".`;
24339
- const resolveSettings$30 = (settings) => {
24182
+ const resolveSettings$31 = (settings) => {
24340
24183
  const reactDoctor = settings?.["react-doctor"];
24341
24184
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxKey ?? {} : {};
24342
24185
  return {
@@ -24350,33 +24193,6 @@ const findArrayVariableDeclarator = (arrayExpression) => {
24350
24193
  if (ancestor && isNodeOfType(ancestor, "VariableDeclarator") && ancestor.init === wrapped && isNodeOfType(ancestor.id, "Identifier")) return ancestor;
24351
24194
  return null;
24352
24195
  };
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
- };
24380
24196
  const isIdentityIteratorCallback = (callback) => {
24381
24197
  const callbackFunction = stripParenExpression(callback);
24382
24198
  if (!isNodeOfType(callbackFunction, "ArrowFunctionExpression") && !isNodeOfType(callbackFunction, "FunctionExpression")) return true;
@@ -24473,7 +24289,7 @@ const findNamedCallbackIteratorCall = (functionNode) => {
24473
24289
  namedCallbackIteratorCallCache.set(functionNode, iteratorCall);
24474
24290
  return iteratorCall;
24475
24291
  };
24476
- const findEnclosingIteratorContext = (jsxNode, scopes) => {
24292
+ const findEnclosingIteratorContext = (jsxNode) => {
24477
24293
  let current = jsxNode;
24478
24294
  let isOutsideContainingFunction = false;
24479
24295
  let didSeeReturnStatement = false;
@@ -24495,8 +24311,6 @@ const findEnclosingIteratorContext = (jsxNode, scopes) => {
24495
24311
  isOutsideContainingFunction = true;
24496
24312
  } else if (isNodeOfType(parent, "ArrayExpression")) {
24497
24313
  if (isOutsideContainingFunction) return null;
24498
- if (isArrayNestedInObjectProperty(parent)) return null;
24499
- if (isArrayPassedToNonRenderingCall(parent, scopes)) return null;
24500
24314
  const arrayParent = parent.parent;
24501
24315
  if (arrayParent && isNodeOfType(arrayParent, "Property")) return null;
24502
24316
  if (arrayParent && isNodeOfType(arrayParent, "ArrayExpression")) return null;
@@ -24578,15 +24392,9 @@ const isWithinChildrenToArray = (jsxNode) => {
24578
24392
  const isKeyPropertyName = (propertyKey) => {
24579
24393
  if (isNodeOfType(propertyKey, "Identifier")) return propertyKey.name === "key";
24580
24394
  if (isNodeOfType(propertyKey, "Literal")) return String(propertyKey.value) === "key";
24581
- return false;
24395
+ return true;
24582
24396
  };
24583
24397
  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
- };
24590
24398
  const isFunctionParameterPropsRest = (bindingIdentifier) => {
24591
24399
  const restElement = bindingIdentifier.parent;
24592
24400
  if (!restElement || !isNodeOfType(restElement, "RestElement")) return false;
@@ -24596,7 +24404,7 @@ const isFunctionParameterPropsRest = (bindingIdentifier) => {
24596
24404
  const parameterNode = patternParent && isNodeOfType(patternParent, "AssignmentPattern") && patternParent.left === objectPattern ? patternParent : objectPattern;
24597
24405
  const functionNode = parameterNode.parent;
24598
24406
  if (!functionNode || !isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "FunctionDeclaration")) return false;
24599
- return isComponentFunction$1(functionNode) && functionNode.params[0] === parameterNode;
24407
+ return functionNode.params.some((param) => param === parameterNode);
24600
24408
  };
24601
24409
  const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
24602
24410
  const restElement = bindingIdentifier.parent;
@@ -24609,14 +24417,14 @@ const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
24609
24417
  }
24610
24418
  return false;
24611
24419
  };
24612
- const isBindingAssignedKey = (scopeOwner, bindingName) => {
24613
- let didFindKeyAssignment = false;
24420
+ const isBindingObjectMutated = (scopeOwner, bindingName) => {
24421
+ let didFindMutation = false;
24614
24422
  walkAst(scopeOwner, (node) => {
24615
- if (didFindKeyAssignment) return false;
24423
+ if (didFindMutation) return false;
24616
24424
  if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression")) {
24617
24425
  const assignedObject = stripParenExpression(node.left.object);
24618
- if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName && isKeyPropertyName(node.left.property)) {
24619
- didFindKeyAssignment = true;
24426
+ if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName) {
24427
+ didFindMutation = true;
24620
24428
  return false;
24621
24429
  }
24622
24430
  }
@@ -24624,63 +24432,59 @@ const isBindingAssignedKey = (scopeOwner, bindingName) => {
24624
24432
  const callee = node.callee;
24625
24433
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Object" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "assign") {
24626
24434
  const firstArgument = node.arguments[0];
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
- }
24435
+ if (firstArgument && isNodeOfType(firstArgument, "Identifier") && firstArgument.name === bindingName) {
24436
+ didFindMutation = true;
24437
+ return false;
24634
24438
  }
24635
24439
  }
24636
24440
  }
24637
24441
  });
24638
- return didFindKeyAssignment;
24442
+ return didFindMutation;
24639
24443
  };
24640
- const spreadExpressionHasKey = (expression, depth) => {
24444
+ const spreadExpressionCanCarryKey = (expression, depth) => {
24641
24445
  const inner = stripParenExpression(expression);
24642
24446
  if (isNodeOfType(inner, "ObjectExpression")) {
24643
24447
  for (const property of inner.properties) {
24644
24448
  if (isNodeOfType(property, "SpreadElement")) {
24645
- if (depth >= 3) continue;
24646
- if (spreadExpressionHasKey(property.argument, depth + 1)) return true;
24449
+ if (depth >= 3) return true;
24450
+ if (spreadExpressionCanCarryKey(property.argument, depth + 1)) return true;
24647
24451
  continue;
24648
24452
  }
24649
- if (!isNodeOfType(property, "Property")) continue;
24453
+ if (!isNodeOfType(property, "Property")) return true;
24454
+ if (property.computed) return true;
24650
24455
  if (isKeyPropertyName(property.key)) return true;
24651
24456
  }
24652
24457
  return false;
24653
24458
  }
24654
24459
  if (isNodeOfType(inner, "ConditionalExpression")) {
24655
- if (depth >= 3) return false;
24656
- return spreadExpressionHasKey(inner.consequent, depth + 1) || spreadExpressionHasKey(inner.alternate, depth + 1);
24460
+ if (depth >= 3) return true;
24461
+ return spreadExpressionCanCarryKey(inner.consequent, depth + 1) || spreadExpressionCanCarryKey(inner.alternate, depth + 1);
24657
24462
  }
24658
24463
  if (isNodeOfType(inner, "LogicalExpression")) {
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);
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);
24662
24467
  }
24663
24468
  if (isNodeOfType(inner, "Literal")) return false;
24664
24469
  if (isThisPropsMember(inner)) return false;
24665
24470
  if (isNodeOfType(inner, "Identifier")) {
24666
24471
  if (inner.name === "undefined") return false;
24667
- if (depth >= 3) return false;
24472
+ if (depth >= 3) return true;
24668
24473
  const binding = findVariableInitializer(inner, inner.name);
24669
- if (!binding) return false;
24474
+ if (!binding) return true;
24670
24475
  if (isRestBindingWithKeyExtracted(binding.bindingIdentifier)) return false;
24671
24476
  if (isFunctionParameterPropsRest(binding.bindingIdentifier)) return false;
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);
24477
+ if (!isConstDeclaredBinding(binding) || !binding.initializer) return true;
24478
+ if (isBindingObjectMutated(binding.scopeOwner, inner.name)) return true;
24479
+ return spreadExpressionCanCarryKey(binding.initializer, depth + 1);
24676
24480
  }
24677
- return false;
24481
+ return true;
24678
24482
  };
24679
- const spreadCanOverwriteKey = (spreadAttribute) => spreadExpressionHasKey(spreadAttribute.argument, 0);
24483
+ const spreadCanOverwriteKey = (spreadAttribute) => spreadExpressionCanCarryKey(spreadAttribute.argument, 0);
24680
24484
  const checkKeyBeforeSpread = (context, openingElement) => {
24681
24485
  let keyIndex = null;
24682
24486
  let keyAttribute = null;
24683
- let lastKeyCarryingSpreadIndex = null;
24487
+ let firstKeyCarryingSpreadIndex = null;
24684
24488
  for (let attributeIndex = 0; attributeIndex < openingElement.attributes.length; attributeIndex++) {
24685
24489
  const attribute = openingElement.attributes[attributeIndex];
24686
24490
  if (isNodeOfType(attribute, "JSXAttribute")) {
@@ -24688,11 +24492,11 @@ const checkKeyBeforeSpread = (context, openingElement) => {
24688
24492
  keyIndex = attributeIndex;
24689
24493
  keyAttribute = attribute;
24690
24494
  }
24691
- } else if (isNodeOfType(attribute, "JSXSpreadAttribute") && spreadCanOverwriteKey(attribute)) lastKeyCarryingSpreadIndex = attributeIndex;
24495
+ } else if (isNodeOfType(attribute, "JSXSpreadAttribute") && firstKeyCarryingSpreadIndex === null && spreadCanOverwriteKey(attribute)) firstKeyCarryingSpreadIndex = attributeIndex;
24692
24496
  }
24693
- if (keyIndex !== null && lastKeyCarryingSpreadIndex !== null && lastKeyCarryingSpreadIndex > keyIndex && keyAttribute) context.report({
24497
+ if (keyIndex !== null && firstKeyCarryingSpreadIndex !== null && firstKeyCarryingSpreadIndex < keyIndex && keyAttribute) context.report({
24694
24498
  node: keyAttribute,
24695
- message: KEY_BEFORE_SPREAD
24499
+ message: KEY_AFTER_SPREAD
24696
24500
  });
24697
24501
  };
24698
24502
  const getKeyAttributeValueString = (openingElement) => {
@@ -24736,7 +24540,7 @@ const jsxKey = defineRule({
24736
24540
  severity: "error",
24737
24541
  recommendation: "Add a stable `key` prop so React can keep list items matched to the right data when the list changes.",
24738
24542
  create: (context) => {
24739
- const settings = resolveSettings$30(context.settings);
24543
+ const settings = resolveSettings$31(context.settings);
24740
24544
  return {
24741
24545
  JSXElement(node) {
24742
24546
  const openingElement = node.openingElement;
@@ -24754,7 +24558,7 @@ const jsxKey = defineRule({
24754
24558
  else seenKeys.add(keyValue.keyValue);
24755
24559
  }
24756
24560
  }
24757
- const enclosingContext = findEnclosingIteratorContext(node, context.scopes);
24561
+ const enclosingContext = findEnclosingIteratorContext(node);
24758
24562
  if (!enclosingContext) return;
24759
24563
  if (isWithinChildrenToArray(node)) return;
24760
24564
  if (hasJsxKeyAttribute(openingElement)) return;
@@ -24790,7 +24594,7 @@ const jsxKey = defineRule({
24790
24594
  //#region src/plugin/rules/react-builtins/jsx-max-depth.ts
24791
24595
  const buildMessage$19 = (depth, max) => `This JSX is hard to read at ${depth} levels deep, past the limit of ${max}.`;
24792
24596
  const DEFAULT_MAX_DEPTH = 14;
24793
- const resolveSettings$29 = (settings) => {
24597
+ const resolveSettings$30 = (settings) => {
24794
24598
  const reactDoctor = settings?.["react-doctor"];
24795
24599
  return { max: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxMaxDepth ?? {} : {}).max ?? DEFAULT_MAX_DEPTH };
24796
24600
  };
@@ -24859,7 +24663,7 @@ const jsxMaxDepth = defineRule({
24859
24663
  recommendation: "Pull deeply nested JSX into smaller components so it's easier to read.",
24860
24664
  category: "Architecture",
24861
24665
  create: (context) => {
24862
- const { max } = resolveSettings$29(context.settings);
24666
+ const { max } = resolveSettings$30(context.settings);
24863
24667
  const deepestLeafPerTree = /* @__PURE__ */ new Map();
24864
24668
  const checkNode = (node) => {
24865
24669
  if (!isLeafJsxNode(node)) return;
@@ -26831,7 +26635,7 @@ const jsxNoNewObjectAsProp = defineRule({
26831
26635
  //#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
26832
26636
  const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
26833
26637
  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;
26834
- const resolveSettings$28 = (settings) => {
26638
+ const resolveSettings$29 = (settings) => {
26835
26639
  const reactDoctor = settings?.["react-doctor"];
26836
26640
  if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
26837
26641
  return reactDoctor.jsxNoScriptUrl ?? {};
@@ -26853,7 +26657,7 @@ const jsxNoScriptUrl = defineRule({
26853
26657
  recommendation: "Use real event handlers instead of `javascript:` URLs so injected URL text cannot execute as code.",
26854
26658
  category: "Security",
26855
26659
  create: (context) => {
26856
- const options = resolveSettings$28(context.settings);
26660
+ const options = resolveSettings$29(context.settings);
26857
26661
  return { JSXOpeningElement(node) {
26858
26662
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
26859
26663
  const elementName = resolveJsxElementType(node);
@@ -26874,6 +26678,283 @@ const jsxNoScriptUrl = defineRule({
26874
26678
  }
26875
26679
  });
26876
26680
  //#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
26877
26958
  //#region src/plugin/rules/react-builtins/jsx-no-undef.ts
26878
26959
  const buildMessage$18 = (name) => `\`${name}\` crashes at runtime because it isn't defined here.`;
26879
26960
  const KNOWN_GLOBALS = new Set([
@@ -28531,30 +28612,6 @@ const hasAncestorLayoutMatching = (pageFilename, matchesLayout) => {
28531
28612
  const hasAncestorMetadataLayout = (pageFilename) => hasAncestorLayoutMatching(pageFilename, (layoutPath) => METADATA_EXPORT_NAMES.some((exportName) => doesModuleExportName(layoutPath, exportName)));
28532
28613
  //#endregion
28533
28614
  //#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
- };
28558
28615
  const nextjsMissingMetadata = defineRule({
28559
28616
  id: "nextjs-missing-metadata",
28560
28617
  title: "Page missing metadata for search previews",
@@ -28574,7 +28631,6 @@ const nextjsMissingMetadata = defineRule({
28574
28631
  if (isNodeOfType(declaration, "FunctionDeclaration")) return declaration.id?.name === "generateMetadata";
28575
28632
  return (statement.specifiers ?? []).some((specifier) => isNodeOfType(specifier, "ExportSpecifier") && isNodeOfType(specifier.exported, "Identifier") && METADATA_EXPORT_NAMES.includes(specifier.exported.name));
28576
28633
  })) return;
28577
- if (isRedirectOnlyDefaultExport(programNode, context)) return;
28578
28634
  if (hasAncestorMetadataLayout(context.filename ?? "")) return;
28579
28635
  context.report({
28580
28636
  node: programNode,
@@ -34703,19 +34759,6 @@ const noCascadingSetState = defineRetiredRule({
34703
34759
  recommendation: "Retired: React batches synchronous state updates from one effect into the same follow-up commit, so setter count does not prove repeated redraws."
34704
34760
  });
34705
34761
  //#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
34719
34762
  //#region src/plugin/rules/state-and-effects/utils/create-state-trigger-reachability.ts
34720
34763
  const getRefCurrentSymbol = (node, context) => {
34721
34764
  const expression = stripParenExpression(node);
@@ -35244,8 +35287,19 @@ const isBuiltinNamespaceCallee = (callee) => {
35244
35287
  }
35245
35288
  return false;
35246
35289
  };
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
+ };
35247
35301
  const getDependencyStateRefs = (analysis, context, dependencyReference) => {
35248
- const useCallbackCall = getReactUseCallbackCall(dependencyReference.identifier, context.scopes);
35302
+ const useCallbackCall = getReactUseCallbackSource(dependencyReference, context);
35249
35303
  if (!useCallbackCall) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
35250
35304
  const dependencyList = useCallbackCall.arguments?.[1];
35251
35305
  if (!dependencyList || !isNodeOfType(dependencyList, "ArrayExpression")) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
@@ -36833,6 +36887,19 @@ const noDefaultProps = defineRule({
36833
36887
  } })
36834
36888
  });
36835
36889
  //#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
36836
36903
  //#region src/plugin/utils/create-component-prop-stack-tracker.ts
36837
36904
  const extractDestructuredPropNames = (params) => {
36838
36905
  const propNames = /* @__PURE__ */ new Set();
@@ -38746,17 +38813,6 @@ const noDynamicImportPath = defineRule({
38746
38813
  })
38747
38814
  });
38748
38815
  //#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
38760
38816
  //#region src/plugin/rules/state-and-effects/utils/is-cleanup-return.ts
38761
38817
  const ITERATOR_CALLBACK_METHOD_NAMES = new Set([
38762
38818
  "each",
@@ -38901,16 +38957,7 @@ const collectDependencyStateSymbolIds = (effectNode, stateSymbolIds, scopes) =>
38901
38957
  }
38902
38958
  return dependencyStateSymbolIds;
38903
38959
  };
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) => {
38960
+ const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
38914
38961
  const analysisFunctions = new Set([effectCallback]);
38915
38962
  const pendingFunctions = [effectCallback];
38916
38963
  while (pendingFunctions.length > 0) {
@@ -38918,9 +38965,9 @@ const collectSynchronouslyInvokedFunctions = (effectCallback, scopes, includeSta
38918
38965
  if (!currentFunction || !isFunctionLike$1(currentFunction)) continue;
38919
38966
  walkInsideStatementBlocks(currentFunction.body, (child) => {
38920
38967
  if (!isNodeOfType(child, "CallExpression")) return;
38921
- const invokedFunction = includeStableCallbacks ? resolveSynchronouslyInvokedFunction(child.callee, scopes) : resolveExactLocalFunction(child.callee, scopes);
38968
+ const invokedFunction = resolveExactLocalFunction(child.callee, scopes);
38922
38969
  if (!invokedFunction || analysisFunctions.has(invokedFunction)) return;
38923
- if (isFunctionLike$1(invokedFunction) && (invokedFunction.async || invokedFunction.generator)) return;
38970
+ if (isFunctionLike$1(invokedFunction) && invokedFunction.async) return;
38924
38971
  analysisFunctions.add(invokedFunction);
38925
38972
  pendingFunctions.push(invokedFunction);
38926
38973
  });
@@ -38933,51 +38980,7 @@ const visitSynchronousFunctionBodies = (analysisFunctions, visitor) => {
38933
38980
  walkInsideStatementBlocks(analysisFunction.body, visitor);
38934
38981
  }
38935
38982
  };
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()) => {
38983
+ const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
38981
38984
  const unwrappedExpression = stripParenExpression(expression);
38982
38985
  if (isNodeOfType(unwrappedExpression, "Literal")) {
38983
38986
  const literalValue = unwrappedExpression.value;
@@ -38985,49 +38988,46 @@ const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, vi
38985
38988
  return null;
38986
38989
  }
38987
38990
  if (isNodeOfType(unwrappedExpression, "Identifier")) {
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;
38991
+ if (scopes.symbolFor(unwrappedExpression)?.id === stateSymbolId) return stateValue;
38991
38992
  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), additionalStateValues);
38995
+ return readStaticEffectValue(immutableSymbol.initializer, scopes, stateSymbolId, stateValue, new Set(visitedSymbolIds).add(immutableSymbol.id));
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, additionalStateValues);
39000
+ const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds);
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, additionalStateValues);
39005
+ const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds);
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, additionalStateValues);
39011
+ const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
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, additionalStateValues);
39016
+ return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39017
39017
  }
39018
39018
  if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
39019
- const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
39019
+ const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39020
39020
  if (!testValue) return null;
39021
- return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
39021
+ return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39022
39022
  }
39023
39023
  if (isNodeOfType(unwrappedExpression, "MemberExpression") && unwrappedExpression.optional) {
39024
- const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
39024
+ const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds);
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, additionalStateValues);
39030
- const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
39029
+ const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39030
+ const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39031
39031
  if (!leftValue || !rightValue) return null;
39032
39032
  if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
39033
39033
  const areEqual = leftValue.value === rightValue.value;
@@ -39066,7 +39066,8 @@ 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 stateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
39069
+ const setterSymbol = resolveConstIdentifierAlias(child.callee, scopes, true);
39070
+ const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
39070
39071
  if (!stateName) return;
39071
39072
  const writeInfo = stateWrites.get(stateName) ?? {
39072
39073
  values: /* @__PURE__ */ new Set(),
@@ -39082,27 +39083,27 @@ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName
39082
39083
  const isGlobalBooleanCall = (node, scopes) => {
39083
39084
  return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Boolean" && scopes.isGlobalReference(node.callee);
39084
39085
  };
39085
- const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map()) => {
39086
+ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes) => {
39086
39087
  let currentNode = workNode;
39087
39088
  while (currentNode.parent) {
39088
39089
  const parentNode = currentNode.parent;
39089
39090
  if (isFunctionLike$1(parentNode)) break;
39090
39091
  if (isNodeOfType(parentNode, "IfStatement")) {
39091
- const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
39092
+ const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
39092
39093
  if (testValue) {
39093
39094
  if (currentNode === parentNode.consequent && !testValue.value) return false;
39094
39095
  if (currentNode === parentNode.alternate && testValue.value) return false;
39095
39096
  }
39096
39097
  }
39097
39098
  if (isNodeOfType(parentNode, "ConditionalExpression")) {
39098
- const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
39099
+ const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
39099
39100
  if (testValue) {
39100
39101
  if (currentNode === parentNode.consequent && !testValue.value) return false;
39101
39102
  if (currentNode === parentNode.alternate && testValue.value) return false;
39102
39103
  }
39103
39104
  }
39104
39105
  if (isNodeOfType(parentNode, "LogicalExpression") && currentNode === parentNode.right) {
39105
- const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
39106
+ const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue);
39106
39107
  if (leftValue) {
39107
39108
  if (parentNode.operator === "&&" && !leftValue.value) return false;
39108
39109
  if (parentNode.operator === "||" && leftValue.value) return false;
@@ -39114,7 +39115,7 @@ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, s
39114
39115
  if (statementIndex >= 0) for (let index = 0; index < statementIndex; index += 1) {
39115
39116
  const earlierStatement = parentNode.body[index];
39116
39117
  if (!isNodeOfType(earlierStatement, "IfStatement") || earlierStatement.alternate || !statementAlwaysExits(earlierStatement.consequent)) continue;
39117
- if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues)?.value) return false;
39118
+ if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue)?.value) return false;
39118
39119
  }
39119
39120
  }
39120
39121
  currentNode = parentNode;
@@ -39124,93 +39125,24 @@ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, s
39124
39125
  const isReaderWorkNode = (node, analysisFunctions, scopes) => {
39125
39126
  if (isNodeOfType(node, "CallExpression")) {
39126
39127
  if (isGlobalBooleanCall(node, scopes)) return false;
39127
- const invokedFunction = resolveSynchronouslyInvokedFunction(node.callee, scopes);
39128
+ const invokedFunction = resolveExactLocalFunction(node.callee, scopes);
39128
39129
  return !invokedFunction || !analysisFunctions.has(invokedFunction);
39129
39130
  }
39130
39131
  return isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "ThrowStatement") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete";
39131
39132
  };
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);
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 };
39187
39137
  let didFindReachableWork = false;
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;
39138
+ visitSynchronousFunctionBodies(readerEffect.analysisFunctions, (child) => {
39139
+ if (didFindReachableWork || !isReaderWorkNode(child, readerEffect.analysisFunctions, scopes)) return;
39140
+ if (isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes)) didFindReachableWork = true;
39201
39141
  });
39202
39142
  if (didFindReachableWork) return true;
39203
39143
  }
39204
39144
  return false;
39205
39145
  };
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
- };
39214
39146
  const EMPTY_CLEANUP_NAME_SET = /* @__PURE__ */ new Set();
39215
39147
  const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
39216
39148
  "clear",
@@ -39221,499 +39153,26 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
39221
39153
  "keys",
39222
39154
  "values"
39223
39155
  ]);
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;
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;
39436
39163
  }
39164
+ return isCleanupReturn(returnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
39437
39165
  }
39438
- return true;
39439
- };
39440
- const hasProvenGlobalNamespaceReference = (expression, namespaceNames, scopes) => {
39441
- for (const namespaceName of namespaceNames) if (isProvenGlobalNamespaceReference(expression, namespaceName, scopes)) return true;
39166
+ if (isNodeOfType(returnedValue, "Identifier")) return true;
39442
39167
  return false;
39443
39168
  };
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;
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);
39713
39174
  return false;
39714
39175
  };
39715
- const STORAGE_GLOBAL_NAMES = ["localStorage", "sessionStorage"];
39716
- const isBrowserStorageReceiver = (receiver, scopes) => Boolean(receiver && STORAGE_GLOBAL_NAMES.some((storageName) => isProvenGlobalNamespaceReference(receiver, storageName, scopes)));
39717
39176
  const STORAGE_HOOK_PATTERN = /^use\w*Storage/i;
39718
39177
  const collectStorageHookSetterNames = (componentBody) => {
39719
39178
  const setterNames = /* @__PURE__ */ new Set();
@@ -39755,6 +39214,14 @@ const isReactRefCall = (expression, scopes) => isReactUseRefCall(expression, sco
39755
39214
  allowUnboundBareCalls: true,
39756
39215
  resolveNamedAliases: true
39757
39216
  });
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
+ };
39758
39225
  const isReactNativeJsxElement = (openingElement, scopes) => {
39759
39226
  if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
39760
39227
  const symbol = scopes.symbolFor(openingElement.name);
@@ -39762,33 +39229,19 @@ const isReactNativeJsxElement = (openingElement, scopes) => {
39762
39229
  return Boolean(symbol?.kind === "import" && importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react-native");
39763
39230
  };
39764
39231
  const isDirectHostJsxRef = (symbol, scopes) => {
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;
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;
39792
39245
  };
39793
39246
  const isIntrinsicRefCallbackParameter = (expression, scopes) => {
39794
39247
  const identifier = stripParenExpression(expression);
@@ -39875,11 +39328,7 @@ const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollecti
39875
39328
  }
39876
39329
  if (isNodeOfType(expression, "MemberExpression")) {
39877
39330
  if (getStaticPropertyName(expression) === "current") {
39878
- const symbol = resolveReactRefSymbol(expression, scopes, {
39879
- allowUnboundBareCalls: true,
39880
- includeCreateRef: true,
39881
- resolveNamedAliases: true
39882
- });
39331
+ const symbol = getDirectReactRefSymbol(expression.object, scopes);
39883
39332
  return Boolean(symbol && (isDirectHostJsxRef(symbol, scopes) || didReadCollectionValue && storesOnlyIntrinsicRefCallbackValues(symbol, scopes)));
39884
39333
  }
39885
39334
  return isDerivedFromProvenDomRefCurrent(expression.object, scopes, didReadCollectionValue, visitedSymbolIds);
@@ -39897,80 +39346,19 @@ const isCommittedDomSyncNode = (node, scopes) => {
39897
39346
  if (propertyName === null || !EXTERNAL_SYNC_DOM_MEMBER_METHOD_NAMES.has(propertyName)) return false;
39898
39347
  return isDerivedFromProvenDomRefCurrent(callee.object, scopes) || isProvenBrowserApiReceiver(callee.object, "dom-event-target", scopes);
39899
39348
  };
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
- }
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";
39965
39352
  if (!isNodeOfType(node, "CallExpression")) return false;
39966
- if (isProvenExternalSyncDirectCallee(node.callee, scopes)) return true;
39353
+ if (isNodeOfType(node.callee, "Identifier")) return EXTERNAL_SYNC_DIRECT_CALLEE_NAMES.has(node.callee.name);
39967
39354
  if (!isNodeOfType(node.callee, "MemberExpression")) return false;
39968
39355
  const propertyName = getStaticPropertyName(node.callee);
39969
39356
  if (propertyName === null) return false;
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);
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);
39974
39362
  };
39975
39363
  const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, scopes, allowCommittedDomSync) => {
39976
39364
  if (!isFunctionLike$1(effectCallback)) return false;
@@ -39979,7 +39367,7 @@ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateNa
39979
39367
  } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, setterSymbolIdToStateName, scopes, true)) return true;
39980
39368
  let didFindExternalCall = false;
39981
39369
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
39982
- if (isExternalSyncNode(child, scopes) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
39370
+ if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
39983
39371
  });
39984
39372
  return didFindExternalCall;
39985
39373
  };
@@ -40001,11 +39389,11 @@ const noEffectChain = defineRule({
40001
39389
  setterToStateName.set(binding.setterName, binding.valueName);
40002
39390
  if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
40003
39391
  const stateIdentifier = binding.declarator.id.elements[0];
40004
- const setterIdentifier = binding.declarator.id.elements[1];
40005
39392
  if (isNodeOfType(stateIdentifier, "Identifier")) {
40006
39393
  const stateSymbol = context.scopes.symbolFor(stateIdentifier);
40007
39394
  if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
40008
39395
  }
39396
+ const setterIdentifier = binding.declarator.id.elements[1];
40009
39397
  if (isNodeOfType(setterIdentifier, "Identifier")) {
40010
39398
  const setterSymbol = context.scopes.symbolFor(setterIdentifier);
40011
39399
  if (setterSymbol) setterSymbolIdToStateName.set(setterSymbol.id, binding.valueName);
@@ -40018,11 +39406,10 @@ const noEffectChain = defineRule({
40018
39406
  const callback = getEffectCallback(effectCall, context.scopes);
40019
39407
  if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
40020
39408
  const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
40021
- const stateWrites = collectStateWritesInEffect(collectStateWriteAnalysisFunctions(callback, context.scopes, setterSymbolIdToStateName), setterSymbolIdToStateName, context.scopes);
39409
+ const stateWrites = collectStateWritesInEffect(analysisFunctions, setterSymbolIdToStateName, context.scopes);
40022
39410
  const writtenStateNames = new Set(stateWrites.keys());
40023
39411
  effectInfos.push({
40024
39412
  node: effectCall,
40025
- callback,
40026
39413
  dependencyStateSymbolIds: collectDependencyStateSymbolIds(effectCall, stateSymbolIdSet, context.scopes),
40027
39414
  stateWrites,
40028
39415
  analysisFunctions,
@@ -40039,10 +39426,10 @@ const noEffectChain = defineRule({
40039
39426
  if (readerEffect.isExternalSync) continue;
40040
39427
  if (readerEffect.dependencyStateSymbolIds.size === 0) continue;
40041
39428
  let chainedStateName = null;
40042
- for (const writtenName of writerEffect.stateWrites.keys()) {
39429
+ for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
40043
39430
  const writtenStateSymbolId = stateSymbolIds.get(writtenName);
40044
39431
  if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
40045
- if (!canStateWriteReachReaderWork(writtenName, writerEffect, readerEffect, stateSymbolIds, context.scopes)) continue;
39432
+ if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
40046
39433
  chainedStateName = writtenName;
40047
39434
  break;
40048
39435
  }
@@ -47216,103 +46603,6 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
47216
46603
  "useVisibility",
47217
46604
  "useWindowSize"
47218
46605
  ]);
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
- };
47316
46606
  const isCallbackPropReference = (analysis, ref) => {
47317
46607
  if (!isProp(analysis, ref)) return false;
47318
46608
  const identifier = ref.identifier;
@@ -47345,9 +46635,8 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
47345
46635
  if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
47346
46636
  return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
47347
46637
  };
47348
- const getLocalHookExternalStateProof = (analysis, ref, scopes) => {
46638
+ const getLocalHookExternalStateProof = (analysis, ref) => {
47349
46639
  let hookFunction = resolveToFunction(ref);
47350
- let didResolveThroughResultBinding = false;
47351
46640
  if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
47352
46641
  const definitionNode = definition.node;
47353
46642
  if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
@@ -47358,27 +46647,29 @@ const getLocalHookExternalStateProof = (analysis, ref, scopes) => {
47358
46647
  const calleeReference = getRef(analysis, callee);
47359
46648
  if (!calleeReference) continue;
47360
46649
  hookFunction = resolveToFunction(calleeReference);
47361
- if (hookFunction) {
47362
- didResolveThroughResultBinding = true;
47363
- break;
47364
- }
46650
+ if (hookFunction) break;
47365
46651
  }
47366
46652
  if (!hookFunction) return null;
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) : []);
46653
+ const returnedReferences = collectFunctionReturnStatements(hookFunction).flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
47372
46654
  if (returnedReferences.length === 0) return null;
47373
46655
  return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
47374
46656
  };
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);
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
+ }));
47381
46671
  };
46672
+ const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
47382
46673
  const isCalleePosition = (identifier) => {
47383
46674
  const parent = identifier.parent;
47384
46675
  return Boolean(parent && (isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression")) && parent.callee === identifier);
@@ -47439,35 +46730,25 @@ const noPassDataToParent = defineRule({
47439
46730
  return getFunctionalUpdaterDataRefs(analysis, argument);
47440
46731
  }
47441
46732
  if (isHandlerBagArgument(analysis, argument)) return [];
47442
- if (isExternalSubscriptionHookResultArgument(analysis, argument)) return [];
47443
46733
  if (isParentWiredHookResultArgument(analysis, argument)) return [];
47444
46734
  if (isNodeOfType(argument, "Identifier")) {
47445
46735
  const argumentRef = getRef(analysis, argument);
47446
46736
  if (argumentRef && resolveToFunction(argumentRef)) return [];
47447
46737
  }
47448
46738
  return getDownstreamRefs(analysis, argument);
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
- }
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));
47461
46741
  if (!argsUpstreamRefs.some((argRef) => {
47462
- const argIdentifier = argRef.identifier;
47463
- if (isUseStateIdentifier(argIdentifier)) return false;
46742
+ if (isUseStateIdentifier(argRef.identifier)) return false;
46743
+ if (isExternalSubscriptionHookRef(analysis, argRef)) return false;
47464
46744
  if (isProp(analysis, argRef)) return false;
47465
- if (isUseRefIdentifier(argIdentifier)) return false;
46745
+ if (isUseRefIdentifier(argRef.identifier)) return false;
47466
46746
  if (isRefCurrent(argRef)) return false;
47467
46747
  if (isConstant(argRef)) return false;
47468
46748
  if (isParentWiredHookResultRef(analysis, argRef)) return false;
47469
46749
  if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
47470
46750
  if (resolveToFunction(argRef)) return false;
46751
+ const argIdentifier = argRef.identifier;
47471
46752
  if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
47472
46753
  if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
47473
46754
  return true;
@@ -56458,6 +55739,17 @@ const preferModuleScopePureFunction = defineRule({
56458
55739
  }
56459
55740
  });
56460
55741
  //#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
56461
55753
  //#region src/plugin/utils/is-proven-node-crypto-namespace-reference.ts
56462
55754
  const NODE_CRYPTO_MODULE_SOURCES = new Set(["crypto", "node:crypto"]);
56463
55755
  const isProvenNodeCryptoNamespaceReference = (expression, scopes) => {
@@ -59784,7 +59076,7 @@ const renderingUsetransitionLoading = defineRule({
59784
59076
  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);
59785
59077
  //#endregion
59786
59078
  //#region src/plugin/rules/security-scan/repository-secret-file.ts
59787
- const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env(?:\.[^./]+)*\.(?:example|sample|template|dist|defaults?)$|(?:^|\/)[^/]*(?:example|sample|template)[^/]*\.(?:env|json|pem|key)$/i.test(relativePath);
59079
+ const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env\.(?:example|sample|template|dist|defaults?)$|(?:^|\/)[^/]*(?:example|sample|template)[^/]*\.(?:env|json|pem|key)$/i.test(relativePath);
59788
59080
  const repositorySecretFile = defineRule({
59789
59081
  id: "repository-secret-file",
59790
59082
  title: "Secret file checked into repository",
@@ -61165,717 +60457,6 @@ const isSetterCalledDuringRender = (root, setterName) => {
61165
60457
  return found;
61166
60458
  };
61167
60459
  //#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
61879
60460
  //#region src/plugin/rules/state-and-effects/rerender-state-only-in-handlers.ts
61880
60461
  const isInsideConditionTest = (identifier, stopAt) => {
61881
60462
  let current = identifier;
@@ -61997,8 +60578,7 @@ const rerenderStateOnlyInHandlers = defineRule({
61997
60578
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
61998
60579
  const bindings = collectUseStateBindings(componentBody, context.scopes);
61999
60580
  if (bindings.length === 0) return;
62000
- const renderReachableExpressions = collectRenderReachableExpressions(componentBody);
62001
- if (renderReachableExpressions.length === 0) return;
60581
+ if (collectRenderReachableExpressions(componentBody).length === 0) return;
62002
60582
  const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
62003
60583
  const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
62004
60584
  const directRenderNames = collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames);
@@ -62012,22 +60592,12 @@ const rerenderStateOnlyInHandlers = defineRule({
62012
60592
  for (const effectInfo of effectInfos) for (const dependencyName of effectInfo.dependencyNames) if (!selfEchoValueNames.has(dependencyName)) effectConsumedNames.add(dependencyName);
62013
60593
  for (const hookArgumentName of collectCustomHookArgumentNames(componentBody, context.scopes)) effectConsumedNames.add(hookArgumentName);
62014
60594
  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;
62023
60595
  const calledSetterNames = /* @__PURE__ */ new Set();
62024
60596
  walkAst(componentBody, (child) => {
62025
60597
  if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && setterNames.has(child.callee.name)) calledSetterNames.add(child.callee.name);
62026
60598
  });
62027
60599
  for (const binding of bindings) {
62028
60600
  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;
62031
60601
  if (binding.valueName === "_" || binding.valueName.startsWith("_")) continue;
62032
60602
  const setterSuffix = binding.setterName.slice(3);
62033
60603
  if (/^(TriggerRender|ForceUpdate|Rerender|ForceRender|Tick|Bump|BumpVersion|InvalidateRender|Refresh|Repaint)$/i.test(setterSuffix)) continue;
@@ -73203,6 +71773,18 @@ const reactDoctorRules = [
73203
71773
  requires: [...new Set(["react", ...jsxNoScriptUrl.requires ?? []])]
73204
71774
  }
73205
71775
  },
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
+ },
73206
71788
  {
73207
71789
  key: "react-doctor/jsx-no-undef",
73208
71790
  id: "jsx-no-undef",