oxlint-plugin-react-doctor 0.8.1-dev.b4556e3 → 0.8.1-dev.c9f2c4b

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 +48 -2
  2. package/dist/index.js +642 -2379
  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,
@@ -9008,16 +9008,6 @@ const hasVisibleBindingNamed = (node, bindingName, scopes) => {
9008
9008
  }
9009
9009
  };
9010
9010
  //#endregion
9011
- //#region src/plugin/utils/is-global-match-media-call.ts
9012
- const isGlobalMatchMediaCall = (node, scopes) => {
9013
- if (!isNodeOfType(node, "CallExpression")) return false;
9014
- const callee = stripParenExpression(node.callee);
9015
- if (isNodeOfType(callee, "Identifier")) return callee.name === "matchMedia" && scopes.isGlobalReference(callee);
9016
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "matchMedia") return false;
9017
- const receiver = stripParenExpression(callee.object);
9018
- return isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && scopes.isGlobalReference(receiver);
9019
- };
9020
- //#endregion
9021
9011
  //#region src/plugin/utils/is-proven-browser-api-receiver.ts
9022
9012
  const DOM_EVENT_TARGET_TYPE_NAMES = new Set([
9023
9013
  "AbortSignal",
@@ -9164,10 +9154,8 @@ const getProvenDomEventTargetPrototypeOwnerNames = (rawExpression, scopes, visit
9164
9154
  if (memberName === "body" || memberName === "documentElement") return getDomPrototypeOwnerNamesForType("HTMLElement");
9165
9155
  }
9166
9156
  if (isNodeOfType(expression, "CallExpression")) {
9167
- if (isGlobalMatchMediaCall(expression, scopes)) return getDomPrototypeOwnerNamesForType("MediaQueryList");
9168
9157
  const callee = stripParenExpression(expression.callee);
9169
9158
  const methodName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
9170
- if (methodName === "matchMedia" && isNodeOfType(callee, "MemberExpression") && getProvenDomEventTargetPrototypeOwnerNames(callee.object, scopes, visitedSymbolIds).includes("Window")) return getDomPrototypeOwnerNamesForType("MediaQueryList");
9171
9159
  if (methodName === "createElement") return getDomPrototypeOwnerNamesForType("HTMLElement");
9172
9160
  if (methodName === "createElementNS") return getDomPrototypeOwnerNamesForType("SVGElement");
9173
9161
  if (methodName && DOM_EVENT_TARGET_FACTORY_METHOD_NAMES.has(methodName)) return getDomPrototypeOwnerNamesForType("Element");
@@ -10380,7 +10368,7 @@ const WRAPPER_LABEL_PROP = "label";
10380
10368
  const SELECT_ELEMENT = "select";
10381
10369
  const DEFAULT_DEPTH = 5;
10382
10370
  const MAX_DEPTH = 25;
10383
- const resolveSettings$45 = (settings) => {
10371
+ const resolveSettings$46 = (settings) => {
10384
10372
  const reactDoctor = settings?.["react-doctor"];
10385
10373
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.controlHasAssociatedLabel ?? {} : {};
10386
10374
  return {
@@ -10713,7 +10701,7 @@ const controlHasAssociatedLabel = defineRule({
10713
10701
  recommendation: "Give every interactive control a label screen readers can read.",
10714
10702
  category: "Accessibility",
10715
10703
  create: (context) => {
10716
- const settings = resolveSettings$45(context.settings);
10704
+ const settings = resolveSettings$46(context.settings);
10717
10705
  const isTestlikeFile = isTestlikeFilename(context.filename);
10718
10706
  const iconComponentNames = /* @__PURE__ */ new Set();
10719
10707
  const iconNamespaceNames = /* @__PURE__ */ new Set();
@@ -12665,7 +12653,7 @@ const DEFAULT_ADDITIONAL_HOCS = [
12665
12653
  "lazy",
12666
12654
  "withTracking"
12667
12655
  ];
12668
- const resolveSettings$44 = (settings) => {
12656
+ const resolveSettings$45 = (settings) => {
12669
12657
  const reactDoctor = settings?.["react-doctor"];
12670
12658
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.displayName ?? {} : {};
12671
12659
  const additionalHoCs = new Set(ruleSettings.additionalHoCs ?? DEFAULT_ADDITIONAL_HOCS);
@@ -12847,7 +12835,7 @@ const displayName = defineRule({
12847
12835
  recommendation: "Give each component a `displayName` so DevTools shows a clear name.",
12848
12836
  category: "Architecture",
12849
12837
  create: (context) => {
12850
- const settings = resolveSettings$44(context.settings);
12838
+ const settings = resolveSettings$45(context.settings);
12851
12839
  const ignoreNamed = !settings.ignoreTranspilerName;
12852
12840
  const reportAt = (node) => {
12853
12841
  context.report({
@@ -13827,116 +13815,6 @@ const findRenderPhaseComponentOrHook = (node, scopes) => {
13827
13815
  return null;
13828
13816
  };
13829
13817
  //#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
13818
  //#region src/plugin/utils/get-function-binding-name.ts
13941
13819
  const getFunctionBindingIdentifier$1 = (functionNode) => {
13942
13820
  if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
@@ -13974,19 +13852,14 @@ const isAstDescendant = (inner, outer) => {
13974
13852
  };
13975
13853
  //#endregion
13976
13854
  //#region src/plugin/utils/react-ref-origin.ts
13977
- const REACT_REF_API_NAMES = new Set(["createRef", "useRef"]);
13978
- const resolveReactRefSymbol = (memberExpression, scopes, options = {}) => {
13855
+ const resolveReactRefSymbol = (memberExpression, scopes) => {
13979
13856
  const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
13980
13857
  if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticPropertyName(memberExpression) !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
13981
13858
  const symbol = resolveConstIdentifierAlias(receiver, scopes);
13982
13859
  if (!symbol?.initializer) return null;
13983
13860
  const initializer = stripParenExpression(symbol.initializer);
13984
13861
  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;
13862
+ return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
13990
13863
  };
13991
13864
  const resolveReactRefCurrentOriginSymbol = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13992
13865
  const expression = stripParenExpression(node);
@@ -14085,35 +13958,9 @@ const isNodeReachableWithinFunction = (node, context) => {
14085
13958
  return false;
14086
13959
  };
14087
13960
  //#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
13961
  //#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
14103
13962
  const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
14104
13963
  const REPLAYABLE_ITERATOR_COLLECTION_CACHE = /* @__PURE__ */ new WeakMap();
14105
- const REPLAY_ENTRY_DROPPING_ARRAY_METHOD_NAMES = new Set([
14106
- "pop",
14107
- "shift",
14108
- "splice",
14109
- "fill",
14110
- "copyWithin"
14111
- ]);
14112
- const REPLAY_ENTRY_DROPPING_COLLECTION_METHOD_NAMES = new Set([
14113
- "clear",
14114
- "delete",
14115
- "set"
14116
- ]);
14117
13964
  const REACT_REF_EFFECT_ANALYSIS_CACHE = /* @__PURE__ */ new WeakMap();
14118
13965
  const RESOURCE_NOUN_BY_KIND = {
14119
13966
  subscribe: "subscription",
@@ -14154,92 +14001,6 @@ const resolveExpressionKey = (expression, context, visitedSymbolIds = /* @__PURE
14154
14001
  }
14155
14002
  return null;
14156
14003
  };
14157
- const resolveForEachProjection = (expression, context) => {
14158
- if (!expression) return null;
14159
- let currentExpression = stripParenExpression(expression);
14160
- const memberNames = [];
14161
- while (isNodeOfType(currentExpression, "MemberExpression") && !currentExpression.computed) {
14162
- if (!isNodeOfType(currentExpression.property, "Identifier")) return null;
14163
- memberNames.unshift(currentExpression.property.name);
14164
- currentExpression = stripParenExpression(currentExpression.object);
14165
- }
14166
- if (!isNodeOfType(currentExpression, "Identifier")) return null;
14167
- const symbol = context.scopes.symbolFor(currentExpression);
14168
- if (!symbol || symbol.kind !== "parameter") return null;
14169
- let callbackNode = symbol.bindingIdentifier.parent;
14170
- while (callbackNode && !isFunctionLike$1(callbackNode)) callbackNode = callbackNode.parent;
14171
- if (!callbackNode || !isFunctionLike$1(callbackNode)) return null;
14172
- const forEachCall = findEnclosingForEachCall(callbackNode);
14173
- if (!forEachCall) return null;
14174
- const forEachCallee = stripParenExpression(forEachCall.callee);
14175
- if (!isNodeOfType(forEachCallee, "MemberExpression")) return null;
14176
- const collectionKey = resolveExpressionKey(forEachCallee.object, context);
14177
- if (!collectionKey) return null;
14178
- const firstParameter = callbackNode.params[0];
14179
- const bindingProperty = symbol.bindingIdentifier.parent;
14180
- const propertyName = isNodeOfType(firstParameter, "ObjectPattern") && isNodeOfType(bindingProperty, "Property") && bindingProperty.parent === firstParameter && bindingProperty.value === symbol.bindingIdentifier ? getStaticPropertyKeyName(bindingProperty) : null;
14181
- const parameterProjection = firstParameter === symbol.bindingIdentifier ? "value" : propertyName;
14182
- if (!parameterProjection) return null;
14183
- return {
14184
- collectionKey,
14185
- projectionKey: [parameterProjection, ...memberNames].join(".")
14186
- };
14187
- };
14188
- const resolveForEachProjectionKey = (expression, context) => {
14189
- const projection = resolveForEachProjection(expression, context);
14190
- return projection ? `forEach:${projection.collectionKey}:${projection.projectionKey}` : null;
14191
- };
14192
- const resolveResourceIdentityKey = (expression, context) => resolveForEachProjectionKey(expression, context) ?? resolveExpressionKey(expression, context);
14193
- const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqueOptionsIdentity) => {
14194
- const capture = resolveEventListenerCapture(optionsNode, { allowIndeterminateEntries: true });
14195
- if (capture !== null) return `capture:${String(capture)}`;
14196
- if (!optionsNode) return null;
14197
- const unwrappedOptions = stripParenExpression(optionsNode);
14198
- if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) {
14199
- const optionsKey = allowOpaqueOptionsIdentity ? resolveResourceIdentityKey(unwrappedOptions, context) : null;
14200
- return optionsKey ? `options:${optionsKey}` : null;
14201
- }
14202
- let captureKey = "capture:false";
14203
- for (const property of unwrappedOptions.properties ?? []) {
14204
- if (!isNodeOfType(property, "Property")) {
14205
- captureKey = null;
14206
- continue;
14207
- }
14208
- const propertyName = getStaticPropertyKeyName(property);
14209
- if (propertyName === null || !property.computed && propertyName === "__proto__") {
14210
- captureKey = null;
14211
- continue;
14212
- }
14213
- if (propertyName === "capture") {
14214
- const propertyValueKey = resolveResourceIdentityKey(property.value, context);
14215
- captureKey = propertyValueKey ? `capture-value:${propertyValueKey}` : null;
14216
- }
14217
- }
14218
- return captureKey;
14219
- };
14220
- const resolveEventListenerCaptureProjection = (optionsNode, context) => {
14221
- if (!optionsNode) return null;
14222
- const unwrappedOptions = stripParenExpression(optionsNode);
14223
- if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) return resolveForEachProjection(unwrappedOptions, context);
14224
- let captureProjection = null;
14225
- for (const property of unwrappedOptions.properties ?? []) {
14226
- if (!isNodeOfType(property, "Property")) {
14227
- captureProjection = null;
14228
- continue;
14229
- }
14230
- const propertyName = getStaticPropertyKeyName(property);
14231
- if (propertyName === null || !property.computed && propertyName === "__proto__") {
14232
- captureProjection = null;
14233
- continue;
14234
- }
14235
- if (propertyName === "capture") captureProjection = resolveForEachProjection(property.value, context);
14236
- }
14237
- return captureProjection;
14238
- };
14239
- const doEventListenerCapturesMatch = (registrationOptions, releaseOptions, context, allowOpaqueOptionsIdentity = false) => {
14240
- const registrationCaptureKey = resolveEventListenerCaptureIdentityKey(registrationOptions, context, allowOpaqueOptionsIdentity);
14241
- return registrationCaptureKey !== null && registrationCaptureKey === resolveEventListenerCaptureIdentityKey(releaseOptions, context, allowOpaqueOptionsIdentity);
14242
- };
14243
14004
  const findAssignedResourceKey = (resourceNode, context) => {
14244
14005
  let currentNode = resourceNode;
14245
14006
  let parentNode = currentNode.parent;
@@ -14251,30 +14012,6 @@ const findAssignedResourceKey = (resourceNode, context) => {
14251
14012
  if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.right === currentNode) return resolveExpressionKey(parentNode.left, context);
14252
14013
  return null;
14253
14014
  };
14254
- const resolveStableMediaQueryListenerIdentityKey = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
14255
- if (!expression) return null;
14256
- const unwrappedExpression = stripParenExpression(expression);
14257
- if (isNodeOfType(unwrappedExpression, "Identifier")) {
14258
- const symbol = context.scopes.symbolFor(unwrappedExpression);
14259
- if (!symbol || visitedSymbolIds.has(symbol.id) || !symbol.references.every((reference) => reference.flag === "read" && !isWithinAssignmentTarget(reference.identifier))) return null;
14260
- const initializer = getDirectUnreassignedInitializer(symbol);
14261
- if (!initializer) return `symbol:${symbol.id}`;
14262
- const unwrappedInitializer = stripParenExpression(initializer);
14263
- if (!isNodeOfType(unwrappedInitializer, "Identifier")) return `symbol:${symbol.id}`;
14264
- const nextVisitedSymbolIds = new Set(visitedSymbolIds);
14265
- nextVisitedSymbolIds.add(symbol.id);
14266
- return resolveStableMediaQueryListenerIdentityKey(unwrappedInitializer, context, nextVisitedSymbolIds) ?? `symbol:${symbol.id}`;
14267
- }
14268
- if (isFunctionLike$1(unwrappedExpression)) {
14269
- const rangeStart = getRangeStart(unwrappedExpression);
14270
- return rangeStart === null ? null : `function:${rangeStart}`;
14271
- }
14272
- return null;
14273
- };
14274
- const isProvenLegacyMediaQueryListMethodCall = (callNode, methodName, context) => {
14275
- const callee = stripParenExpression(callNode.callee);
14276
- return callNode.arguments?.length === 1 && isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName && getProvenDomEventTargetPrototypeOwnerNames(callee.object, context.scopes).includes("MediaQueryList");
14277
- };
14278
14015
  const getCallRegistrationDetails = (callNode, context) => {
14279
14016
  const callee = stripParenExpression(callNode.callee);
14280
14017
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return {
@@ -14283,17 +14020,11 @@ const getCallRegistrationDetails = (callNode, context) => {
14283
14020
  eventKey: null,
14284
14021
  handlerKey: null
14285
14022
  };
14286
- if (isProvenLegacyMediaQueryListMethodCall(callNode, "addListener", context)) return {
14287
- receiverKey: resolveStableMediaQueryListenerIdentityKey(callee.object, context),
14288
- registrationVerbName: callee.property.name,
14289
- eventKey: null,
14290
- handlerKey: resolveStableMediaQueryListenerIdentityKey(callNode.arguments?.[0], context)
14291
- };
14292
14023
  return {
14293
- receiverKey: resolveResourceIdentityKey(callee.object, context),
14024
+ receiverKey: resolveExpressionKey(callee.object, context),
14294
14025
  registrationVerbName: callee.property.name,
14295
- eventKey: resolveResourceIdentityKey(callNode.arguments?.[0], context),
14296
- handlerKey: resolveResourceIdentityKey(callNode.arguments?.[1], context)
14026
+ eventKey: resolveExpressionKey(callNode.arguments?.[0], context),
14027
+ handlerKey: resolveExpressionKey(callNode.arguments?.[1], context)
14297
14028
  };
14298
14029
  };
14299
14030
  const findSubscribeLikeUsages = (callback, context) => {
@@ -14393,6 +14124,30 @@ const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, conte
14393
14124
  }
14394
14125
  return matchingBlocks.size > 0;
14395
14126
  };
14127
+ const doMatchingNodesCoverEveryPathFromFunctionEntry = (owner, matchingNodes, context) => {
14128
+ const functionCfg = context.cfg.cfgFor(owner);
14129
+ if (!functionCfg) return false;
14130
+ const matchingBlocks = new Set(matchingNodes.flatMap((matchingNode) => {
14131
+ if (context.cfg.enclosingFunction(matchingNode) !== owner) return [];
14132
+ const matchingBlock = functionCfg.blockOf(matchingNode);
14133
+ return matchingBlock ? [matchingBlock] : [];
14134
+ }));
14135
+ if (matchingBlocks.size === 0) return false;
14136
+ const visitedBlocks = new Set([functionCfg.entry]);
14137
+ const pendingBlocks = [functionCfg.entry];
14138
+ while (pendingBlocks.length > 0) {
14139
+ const currentBlock = pendingBlocks.pop();
14140
+ if (!currentBlock) break;
14141
+ if (matchingBlocks.has(currentBlock)) continue;
14142
+ for (const edge of currentBlock.successors) {
14143
+ if (edge.to === functionCfg.exit) return false;
14144
+ if (visitedBlocks.has(edge.to)) continue;
14145
+ visitedBlocks.add(edge.to);
14146
+ pendingBlocks.push(edge.to);
14147
+ }
14148
+ }
14149
+ return true;
14150
+ };
14396
14151
  const removeSynchronouslyReleasedUsages = (callback, usages, context) => {
14397
14152
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
14398
14153
  if (!isNodeOfType(callback.body, "BlockStatement")) return usages;
@@ -14635,6 +14390,18 @@ const findPushedResourceCollectionKey = (usage, context) => {
14635
14390
  return memberNode.property.name === "forEach" || memberNode.property.name === "push";
14636
14391
  }) ? resolveExpressionKey(pushCallee.object, context) : null;
14637
14392
  };
14393
+ const isWithinAssignmentTarget = (identifier) => {
14394
+ let currentNode = identifier;
14395
+ let parentNode = currentNode.parent;
14396
+ while (parentNode) {
14397
+ if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
14398
+ if (isNodeOfType(parentNode, "UpdateExpression") || isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "delete") return parentNode.argument === currentNode;
14399
+ if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
14400
+ currentNode = parentNode;
14401
+ parentNode = currentNode.parent;
14402
+ }
14403
+ return false;
14404
+ };
14638
14405
  const resolveStableValue = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
14639
14406
  if (!expression) return null;
14640
14407
  const unwrappedExpression = stripParenExpression(expression);
@@ -14682,8 +14449,8 @@ const isSynchronousIteratorCallback = (functionNode) => {
14682
14449
  return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments?.[0] === functionNode;
14683
14450
  };
14684
14451
  const findEnclosingForEachCall = (node) => {
14685
- const callbackNode = isFunctionLike$1(node) ? node : findEnclosingFunction$1(node);
14686
- if (!callbackNode || !isFunctionLike$1(callbackNode) || callbackNode.async || callbackNode.generator) return null;
14452
+ const callbackNode = findEnclosingFunction$1(node);
14453
+ if (!callbackNode) return null;
14687
14454
  const callNode = callbackNode.parent;
14688
14455
  if (!isNodeOfType(callNode, "CallExpression") || callNode.arguments?.[0] !== callbackNode) return null;
14689
14456
  const callee = stripParenExpression(callNode.callee);
@@ -14758,7 +14525,7 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
14758
14525
  const helperFunction = isNodeOfType(stableHelperFunction, "Identifier") ? resolveSingleAssignedCleanupFunction(stableHelperFunction, usage, context) : stableHelperFunction;
14759
14526
  if (helperFunction && isFunctionLike$1(helperFunction) && !helperFunction.async && !helperFunction.generator && doesCleanupFunctionReleaseUsage(helperFunction, usage, context, new Set(visitedFunctions))) matchingLoopOrHelperAnchors.push(cleanupCall);
14760
14527
  });
14761
- return didCleanupFunctionMatch || doNodesCoverEveryPathFromFunctionEntry(cleanupFunction, matchingLoopOrHelperAnchors, context);
14528
+ return didCleanupFunctionMatch || doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, matchingLoopOrHelperAnchors, context);
14762
14529
  };
14763
14530
  const doesBoundCleanupReleaseUsage = (expression, usage, context) => {
14764
14531
  const callExpression = stripParenExpression(expression);
@@ -14787,7 +14554,7 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
14787
14554
  walkInsideStatementBlocks(callback.body, (child) => {
14788
14555
  if (isNodeOfType(child, "ReturnStatement") && child.argument && doesReturnedValueReleaseUsage(stripParenExpression(child.argument))) matchingCleanupReturns.push(child);
14789
14556
  });
14790
- return doNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
14557
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
14791
14558
  };
14792
14559
  const doesTestRequireLiveExpressionKey = (test, expressionKey, context) => {
14793
14560
  if (resolveExpressionKey(test, context) === expressionKey) return true;
@@ -14830,7 +14597,7 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
14830
14597
  const helperFunction = resolveStableValue(child.callee, context);
14831
14598
  if (helperFunction && isFunctionLike$1(helperFunction) && doesCleanupFunctionReleaseUsage(helperFunction, usage, context)) matchingReleaseAnchors.push(handleGuard ?? child);
14832
14599
  });
14833
- return doNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
14600
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
14834
14601
  };
14835
14602
  const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
14836
14603
  const componentFunction = findEnclosingFunction$1(callback);
@@ -15081,7 +14848,7 @@ const hasGuardedRefOwnedNestedCleanup = (callback, usage, cleanupReturns, contex
15081
14848
  const usageFunction = findEnclosingFunction$1(usage.node);
15082
14849
  const usageExpression = findTransparentExpressionRoot(usage.node);
15083
14850
  const usageAssignment = usageExpression.parent;
15084
- 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;
14851
+ if (usage.kind !== "subscribe" && usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usageAssignment, "AssignmentExpression") || usageAssignment.operator !== "=" || usageAssignment.right !== usageExpression || !resolveReactRefSymbol(stripParenExpression(usageAssignment.left), context.scopes) || !collectSynchronouslyEffectInvokedFunctions(callback).has(usageFunction) || !cleanupReturnsReleaseUsage(cleanupReturns, usage, context) || !doMatchingNodesCoverEveryPathFromFunctionEntry(callback, cleanupReturns, context)) return false;
15085
14852
  const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
15086
14853
  if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
15087
14854
  const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
@@ -15129,7 +14896,7 @@ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) =>
15129
14896
  });
15130
14897
  }
15131
14898
  });
15132
- if (!doNodesCoverEveryPathFromFunctionEntry(cleanupFunction, globalReleaseProofs.map((releaseProof) => releaseProof.anchor), context)) return false;
14899
+ if (!doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, globalReleaseProofs.map((releaseProof) => releaseProof.anchor), context)) return false;
15133
14900
  globalReleaseProofsByCleanup.set(cleanupFunction, globalReleaseProofs);
15134
14901
  }
15135
14902
  const handleAssignments = handleSymbol.references.filter((reference) => isWithinAssignmentTarget(reference.identifier));
@@ -15332,7 +15099,9 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
15332
15099
  const nodeParameterKey = resolveExpressionKey(usageFunction.params?.[0], context);
15333
15100
  const releaseReceiverKey = resolveExpressionKey(releaseCallee.object, context);
15334
15101
  if (registrationReceiverKey === null || registrationReceiverKey !== nodeParameterKey || releaseReceiverKey === null || usage.eventKey === null || usage.eventKey !== resolveExpressionKey(releaseCall.arguments?.[0], context) || usage.handlerKey === null || usage.handlerKey !== resolveExpressionKey(releaseCall.arguments?.[1], context)) return false;
15335
- if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], releaseCall.arguments?.[2], context)) return false;
15102
+ const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
15103
+ const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
15104
+ if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
15336
15105
  const releaseStart = getRangeStart(releaseCall);
15337
15106
  const matchingOwnershipAssignments = [];
15338
15107
  const usageFunctionBody = usageFunction.body;
@@ -15342,127 +15111,7 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
15342
15111
  });
15343
15112
  const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
15344
15113
  const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
15345
- return doNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
15346
- };
15347
- const findDirectExhaustiveForEachCleanupFunction = (releaseNode, requiredCollectionKeys, context) => {
15348
- let currentNode = findTransparentExpressionRoot(releaseNode);
15349
- const visitedFunctions = /* @__PURE__ */ new Set();
15350
- const replayedCollectionKeys = /* @__PURE__ */ new Set();
15351
- while (true) {
15352
- const ownerFunction = findEnclosingFunction$1(currentNode);
15353
- if (!ownerFunction || !isFunctionLike$1(ownerFunction) || visitedFunctions.has(ownerFunction)) return null;
15354
- visitedFunctions.add(ownerFunction);
15355
- const isDirectConciseBody = ownerFunction.body === currentNode;
15356
- const statementNode = currentNode.parent;
15357
- const isDirectBlockStatement = isNodeOfType(ownerFunction.body, "BlockStatement") && isNodeOfType(statementNode, "ExpressionStatement") && statementNode.parent === ownerFunction.body;
15358
- if (!isDirectConciseBody && !isDirectBlockStatement || !doNodesCoverEveryPathFromFunctionEntry(ownerFunction, [isDirectBlockStatement ? statementNode : currentNode], context)) return null;
15359
- const forEachCall = findEnclosingForEachCall(ownerFunction);
15360
- if (!forEachCall) return replayedCollectionKeys.size === requiredCollectionKeys.size && isReturnedEffectCleanupFunction(ownerFunction, context) ? ownerFunction : null;
15361
- const forEachCallee = stripParenExpression(forEachCall.callee);
15362
- if (!isNodeOfType(forEachCallee, "MemberExpression")) return null;
15363
- const collectionKey = resolveExpressionKey(forEachCallee.object, context);
15364
- if (!collectionKey || !requiredCollectionKeys.has(collectionKey)) return null;
15365
- replayedCollectionKeys.add(collectionKey);
15366
- currentNode = findTransparentExpressionRoot(forEachCall);
15367
- }
15368
- };
15369
- const collectReplayOwnerFunctions = (usageNode) => {
15370
- const ownerFunctions = /* @__PURE__ */ new Set();
15371
- let currentNode = usageNode;
15372
- while (true) {
15373
- const ownerFunction = findEnclosingFunction$1(currentNode);
15374
- if (!ownerFunction || !isFunctionLike$1(ownerFunction) || ownerFunctions.has(ownerFunction)) break;
15375
- ownerFunctions.add(ownerFunction);
15376
- const forEachCall = findEnclosingForEachCall(ownerFunction);
15377
- if (!forEachCall) break;
15378
- currentNode = forEachCall;
15379
- }
15380
- return ownerFunctions;
15381
- };
15382
- const hasCollectionMutationBeforeRelease = (usageNode, releaseNode, collectionKeys, context) => {
15383
- const usageStart = getRangeStart(usageNode);
15384
- const releaseStart = getRangeStart(releaseNode);
15385
- if (usageStart === null || releaseStart === null) return true;
15386
- const setupOwnerFunctions = collectReplayOwnerFunctions(usageNode);
15387
- const cleanupOwnerFunctions = collectReplayOwnerFunctions(releaseNode);
15388
- let programNode = usageNode;
15389
- while (programNode.parent) programNode = programNode.parent;
15390
- let didFindMutation = false;
15391
- walkAst(programNode, (child) => {
15392
- if (didFindMutation) return false;
15393
- const childStart = getRangeStart(child);
15394
- if (childStart === null) return;
15395
- const ownerFunction = context.cfg.enclosingFunction(child);
15396
- if (!ownerFunction) return;
15397
- const isAfterRegistration = setupOwnerFunctions.has(ownerFunction) && childStart > usageStart;
15398
- const isBeforeRelease = cleanupOwnerFunctions.has(ownerFunction) && childStart < releaseStart;
15399
- if (!isAfterRegistration && !isBeforeRelease) return;
15400
- if (isNodeOfType(child, "AssignmentExpression")) {
15401
- const assignmentKey = resolveExpressionKey(child.left, context);
15402
- const assignmentTarget = stripParenExpression(child.left);
15403
- if (assignmentKey && [...collectionKeys].some((collectionKey) => assignmentKey === collectionKey || assignmentKey === `${collectionKey}.length`) || isNodeOfType(assignmentTarget, "MemberExpression") && assignmentTarget.computed && collectionKeys.has(resolveExpressionKey(assignmentTarget.object, context) ?? "")) {
15404
- didFindMutation = true;
15405
- return false;
15406
- }
15407
- return;
15408
- }
15409
- if (isNodeOfType(child, "UnaryExpression") && child.operator === "delete") {
15410
- const deletedMember = stripParenExpression(child.argument);
15411
- if (!isNodeOfType(deletedMember, "MemberExpression")) return;
15412
- if (collectionKeys.has(resolveExpressionKey(deletedMember.object, context) ?? "")) {
15413
- didFindMutation = true;
15414
- return false;
15415
- }
15416
- return;
15417
- }
15418
- if (isNodeOfType(child, "UpdateExpression")) {
15419
- const updatedKey = resolveExpressionKey(child.argument, context);
15420
- if (updatedKey && [...collectionKeys].some((collectionKey) => updatedKey === `${collectionKey}.length`)) {
15421
- didFindMutation = true;
15422
- return false;
15423
- }
15424
- return;
15425
- }
15426
- if (!isNodeOfType(child, "CallExpression")) return;
15427
- const callee = stripParenExpression(child.callee);
15428
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || !REPLAY_ENTRY_DROPPING_ARRAY_METHOD_NAMES.has(callee.property.name) && !REPLAY_ENTRY_DROPPING_COLLECTION_METHOD_NAMES.has(callee.property.name) || !collectionKeys.has(resolveExpressionKey(callee.object, context) ?? "")) return;
15429
- didFindMutation = true;
15430
- return false;
15431
- });
15432
- return didFindMutation;
15433
- };
15434
- const usesUnaryListenerSignature = (registrationCall, releaseCall) => getCalleeName$1(registrationCall) === "addListener" && registrationCall.arguments?.length === 1 && releaseCall.arguments?.length === 1;
15435
- const hasSafeForEachProjectionCleanup = (registrationCall, releaseCall, context) => {
15436
- const registrationCallee = stripParenExpression(registrationCall.callee);
15437
- const releaseCallee = stripParenExpression(releaseCall.callee);
15438
- if (!isNodeOfType(registrationCallee, "MemberExpression") || !isNodeOfType(releaseCallee, "MemberExpression")) return true;
15439
- const registrationVerbName = getCalleeName$1(registrationCall);
15440
- const releaseVerbName = getCalleeName$1(releaseCall);
15441
- const releaseHandler = releaseCall.arguments?.[usesUnaryListenerSignature(registrationCall, releaseCall) ? 0 : 1];
15442
- const releaseFunction = findEnclosingFunction$1(releaseCall);
15443
- const registrationEventKey = resolveResourceIdentityKey(registrationCall.arguments?.[0], context);
15444
- const releaseEventKey = resolveResourceIdentityKey(releaseCall.arguments?.[0], context);
15445
- const doesHandlerlessOffReleaseEveryRegistration = releaseVerbName === "off" && !releaseHandler && (releaseCall.arguments?.length === 0 || registrationEventKey !== null && registrationEventKey === releaseEventKey);
15446
- if (Boolean(releaseFunction && isFunctionLike$1(releaseFunction) && isReturnedEffectCleanupFunction(releaseFunction, context) && doNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseCall], context)) && (releaseVerbName !== null && UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName) || doesHandlerlessOffReleaseEveryRegistration)) return true;
15447
- const projections = [
15448
- registrationCallee.object,
15449
- registrationCall.arguments?.[0],
15450
- registrationCall.arguments?.[1],
15451
- releaseCallee.object,
15452
- releaseCall.arguments?.[0],
15453
- releaseCall.arguments?.[1]
15454
- ].flatMap((expression) => {
15455
- const projection = resolveForEachProjection(expression, context);
15456
- return projection ? [projection] : [];
15457
- });
15458
- if (registrationVerbName === "addEventListener" && releaseVerbName === "removeEventListener") for (const optionsNode of [registrationCall.arguments?.[2], releaseCall.arguments?.[2]]) {
15459
- const captureProjection = resolveEventListenerCaptureProjection(optionsNode, context);
15460
- if (captureProjection) projections.push(captureProjection);
15461
- }
15462
- if (projections.length === 0) return true;
15463
- const collectionKeys = new Set(projections.map((projection) => projection.collectionKey));
15464
- if (!findDirectExhaustiveForEachCleanupFunction(releaseCall, collectionKeys, context)) return false;
15465
- return !hasCollectionMutationBeforeRelease(registrationCall, releaseCall, collectionKeys, context);
15114
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
15466
15115
  };
15467
15116
  const doesReleaseCallMatchUsage = (node, usage, context) => {
15468
15117
  const callNode = isNodeOfType(node, "ChainExpression") ? node.expression : node;
@@ -15479,8 +15128,8 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15479
15128
  const releaseVerbName = getReleaseVerbName(callNode);
15480
15129
  if (!releaseVerbName) return false;
15481
15130
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
15482
- const releaseReceiverKey = resolveResourceIdentityKey(callee.object, context);
15483
- const releaseEventKey = resolveResourceIdentityKey(callNode.arguments?.[0], context);
15131
+ const releaseReceiverKey = resolveExpressionKey(callee.object, context);
15132
+ const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
15484
15133
  const pairedReleaseVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
15485
15134
  const pushedResourceCollectionKey = findPushedResourceCollectionKey(usage, context);
15486
15135
  const releaseReceiverForOfStatement = findForOfStatementForIteratorExpression(callee.object, context);
@@ -15490,13 +15139,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15490
15139
  if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
15491
15140
  if (usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub" || releaseVerbName === "close" || releaseVerbName === "unwatch" || releaseVerbName === "unlisten" || BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(releaseVerbName))) return true;
15492
15141
  if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
15493
- if (usage.registrationVerbName === "addListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1) return isProvenLegacyMediaQueryListMethodCall(usage.node, "addListener", context) && releaseVerbName === "removeListener" && isProvenLegacyMediaQueryListMethodCall(callNode, "removeListener", context) && usage.receiverKey !== null && resolveStableMediaQueryListenerIdentityKey(callee.object, context) === usage.receiverKey && usage.handlerKey !== null && resolveStableMediaQueryListenerIdentityKey(callNode.arguments?.[0], context) === usage.handlerKey;
15494
15142
  if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
15495
- if (usage.registrationVerbName === "addEventListener" && releaseVerbName === "removeEventListener" && isNodeOfType(usage.node, "CallExpression")) {
15496
- if (!isNodeOfType(stripParenExpression(usage.node.callee), "MemberExpression")) return false;
15497
- if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], callNode.arguments?.[2], context, true)) return false;
15498
- }
15499
- if (isNodeOfType(usage.node, "CallExpression") && !hasSafeForEachProjectionCleanup(usage.node, callNode, context)) return false;
15500
15143
  if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) return false;
15501
15144
  if (usage.registrationVerbName === "subscribe" && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub") && usage.handleKey !== null && resolveExpressionKey(callNode.arguments?.[0], context) === usage.handleKey) return true;
15502
15145
  const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
@@ -15506,9 +15149,6 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15506
15149
  if (isAssignmentFormForOfIteratorReference(usageEventArgument, context) || isAssignmentFormForOfIteratorReference(releaseEventArgument, context)) return false;
15507
15150
  if (usage.eventKey !== null && releaseEventKey !== null && usage.eventKey !== releaseEventKey) {
15508
15151
  if (!isNodeOfType(usage.node, "CallExpression")) return false;
15509
- const registrationEventProjectionKey = resolveForEachProjectionKey(usage.node.arguments?.[0], context);
15510
- const releaseEventProjectionKey = resolveForEachProjectionKey(callNode.arguments?.[0], context);
15511
- if ((registrationEventProjectionKey !== null || releaseEventProjectionKey !== null) && registrationEventProjectionKey !== releaseEventProjectionKey) return false;
15512
15152
  const usageForOfStatement = findForOfStatementForIteratorExpression(usageEventArgument, context);
15513
15153
  const releaseForOfStatement = findForOfStatementForIteratorExpression(releaseEventArgument, context);
15514
15154
  if (usageForOfStatement === null !== (releaseForOfStatement === null)) return false;
@@ -15522,7 +15162,9 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15522
15162
  const registrationCallee = stripParenExpression(usage.node.callee);
15523
15163
  if (!isNodeOfType(registrationCallee, "MemberExpression")) return false;
15524
15164
  if (!isStableLoopReceiver(registrationCallee.object, context) || !isStableLoopReceiver(callee.object, context) || registrationHandlerSymbolId === null || registrationHandlerSymbolId !== releaseHandlerSymbolId) return false;
15525
- if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], callNode.arguments?.[2], context)) return false;
15165
+ const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
15166
+ const releaseCapture = resolveEventListenerCapture(callNode.arguments?.[2], { allowIndeterminateEntries: true });
15167
+ if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
15526
15168
  if (!isDirectExhaustiveForOfRelease(callNode, releaseForOfStatement)) return false;
15527
15169
  }
15528
15170
  }
@@ -15531,28 +15173,27 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15531
15173
  return isNodeOfType(handlerArgument, "Literal") && handlerArgument.value === null;
15532
15174
  }
15533
15175
  if (releaseVerbName === "removeEventListener" || releaseVerbName === "removeListener" || releaseVerbName === "off") {
15534
- const usesUnaryListenerSignatureForCalls = isNodeOfType(usage.node, "CallExpression") && usesUnaryListenerSignature(usage.node, callNode);
15535
- const releaseHandler = usesUnaryListenerSignatureForCalls ? callNode.arguments?.[0] : callNode.arguments?.[1];
15176
+ const usesUnaryListenerSignature = usage.registrationVerbName === "addListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1 && callNode.arguments?.length === 1;
15177
+ const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
15536
15178
  if (!releaseHandler) return releaseVerbName === "off";
15537
- const expectedHandlerKey = usesUnaryListenerSignatureForCalls ? usage.eventKey : usage.handlerKey;
15538
- const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignatureForCalls ? 0 : 1] : null;
15539
- return expectedHandlerKey !== null && resolveResourceIdentityKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
15179
+ const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
15180
+ const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignature ? 0 : 1] : null;
15181
+ return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
15540
15182
  }
15541
15183
  if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
15542
15184
  return true;
15543
15185
  };
15544
15186
  const matchesPairedReleaseVerb = (releaseVerbName, pairedVerbNames) => pairedVerbNames.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName);
15545
15187
  const isReturnedEffectCleanupFunction = (functionNode, context) => {
15546
- const effectCallback = findEnclosingFunction$1(functionNode);
15547
- if (!effectCallback || !isFunctionLike$1(effectCallback)) return false;
15548
- const effectCall = effectCallback.parent;
15549
- if (!isNodeOfType(effectCall, "CallExpression") || !isReactHookCall(effectCall, CLEANUP_EFFECT_HOOK_NAMES, context.scopes)) return false;
15550
- if (!isNodeOfType(effectCallback.body, "BlockStatement")) return resolveStableValue(effectCallback.body, context) === functionNode;
15551
- let isReturned = false;
15552
- walkInsideStatementBlocks(effectCallback.body, (child) => {
15553
- if (isNodeOfType(child, "ReturnStatement") && child.argument && resolveStableValue(child.argument, context) === functionNode) isReturned = true;
15554
- });
15555
- return isReturned;
15188
+ let currentNode = functionNode;
15189
+ let parentNode = currentNode.parent;
15190
+ while (isNodeOfType(parentNode, "ChainExpression") || isNodeOfType(parentNode, "TSAsExpression") || isNodeOfType(parentNode, "TSNonNullExpression")) {
15191
+ currentNode = parentNode;
15192
+ parentNode = currentNode.parent;
15193
+ }
15194
+ const effectCallback = isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode ? findEnclosingFunction$1(parentNode) : isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode ? parentNode : null;
15195
+ const effectCall = effectCallback?.parent;
15196
+ return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isReactHookCall(effectCall, CLEANUP_EFFECT_HOOK_NAMES, context.scopes));
15556
15197
  };
15557
15198
  const isPotentiallyReachableFunction = (functionNode, context) => {
15558
15199
  if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode, context)) return true;
@@ -15642,7 +15283,7 @@ const hasEffectCleanupInvocation = (storage, usage, context) => {
15642
15283
  if (!cleanupFunction || !cleanupFunctionInvokesRef(cleanupFunction)) return;
15643
15284
  matchingReturns.push(child);
15644
15285
  });
15645
- return doNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15286
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15646
15287
  };
15647
15288
  let didFindInvocation = false;
15648
15289
  walkAst(componentFunction.body, (child) => {
@@ -15694,10 +15335,12 @@ const isRetainedDisposerRefRelease = (releaseNode, usage, context) => {
15694
15335
  return findRetainedDisposerStorages(disposerFunction, usage, context).some((storage) => isRetainedDisposerStorageEstablished(storage, usage, context) && !hasUnsafeRetainedDisposerOverwrite(storage, usage, context) && (hasEffectCleanupInvocation(storage, usage, context) || hasCallbackRefReplacementInvocation(storage, usage, context)));
15695
15336
  };
15696
15337
  const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
15697
- 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;
15338
+ if (usage.kind !== "subscribe" || usage.registrationVerbName !== "addEventListener" || usage.receiverKey === null || usage.eventKey === null || !isNodeOfType(usage.node, "CallExpression") || !isFunctionLike$1(releaseFunction) || releaseFunction.async || releaseFunction.generator || !isNodeOfType(releaseFunction.body, "BlockStatement") || !doMatchingNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseNode], context)) return false;
15339
+ const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
15698
15340
  const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
15699
15341
  if (!isNodeOfType(releaseCall, "CallExpression")) return false;
15700
- if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], releaseCall.arguments?.[2], context)) return false;
15342
+ const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
15343
+ if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
15701
15344
  const ownerFunction = findEnclosingFunction$1(releaseFunction);
15702
15345
  if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
15703
15346
  const triggerRegistrations = [];
@@ -15922,7 +15565,7 @@ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retaine
15922
15565
  walkInsideStatementBlocks(effectCallback.body, (child) => {
15923
15566
  if (isNodeOfType(child, "ReturnStatement") && child.argument && matchesReturnedCleanup(stripParenExpression(child.argument))) matchingReturns.push(child);
15924
15567
  });
15925
- return doNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15568
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15926
15569
  };
15927
15570
  const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
15928
15571
  const componentFunction = findEnclosingFunction$1(retainedFunction);
@@ -15965,6 +15608,15 @@ const findUnconditionalReturnStatement = (expression, ownerFunction) => {
15965
15608
  const returnStatement = expressionRoot.parent;
15966
15609
  return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === expressionRoot && findEnclosingFunction$1(returnStatement) === ownerFunction ? returnStatement : null;
15967
15610
  };
15611
+ const getFinalSequenceExpressionValue = (expression) => {
15612
+ let finalExpression = stripParenExpression(expression);
15613
+ while (isNodeOfType(finalExpression, "SequenceExpression")) {
15614
+ const sequenceResult = finalExpression.expressions.at(-1);
15615
+ if (!sequenceResult) break;
15616
+ finalExpression = stripParenExpression(sequenceResult);
15617
+ }
15618
+ return finalExpression;
15619
+ };
15968
15620
  const doesResourceResultEscape = (resourceNode, allowReturnedResourceEscape, allowConciseReturnEscape, context) => {
15969
15621
  if (!allowReturnedResourceEscape) return false;
15970
15622
  let currentNode = resourceNode;
@@ -16213,7 +15865,7 @@ const isReactRefCallbackCleanupOwnedByEffect = (retainedFunction, cleanupFunctio
16213
15865
  if (child !== returnedCleanupFunction.body && isFunctionLike$1(child)) return false;
16214
15866
  if (isNodeOfType(child, "CallExpression") && resolveRefOwnedCleanupFunction(child.callee, context) === cleanupFunction) matchingCalls.push(child);
16215
15867
  });
16216
- return doNodesCoverEveryPathFromFunctionEntry(returnedCleanupFunction, matchingCalls, context);
15868
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(returnedCleanupFunction, matchingCalls, context);
16217
15869
  };
16218
15870
  const matchingReturns = [];
16219
15871
  walkInsideStatementBlocks(retainedFunction.body, (child) => {
@@ -19016,7 +18668,7 @@ const compileGlob = (pattern) => {
19016
18668
  };
19017
18669
  //#endregion
19018
18670
  //#region src/plugin/rules/react-builtins/forbid-component-props.ts
19019
- const resolveSettings$43 = (settings) => {
18671
+ const resolveSettings$44 = (settings) => {
19020
18672
  const reactDoctor = settings?.["react-doctor"];
19021
18673
  return { forbid: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidComponentProps ?? {} : {}).forbid ?? [] };
19022
18674
  };
@@ -19068,7 +18720,7 @@ const forbidComponentProps = defineRule({
19068
18720
  recommendation: "Configure blocked component props so callers cannot bypass the component API contract.",
19069
18721
  category: "Architecture",
19070
18722
  create: (context) => {
19071
- const entries = resolveSettings$43(context.settings).forbid.map(normalizeEntry);
18723
+ const entries = resolveSettings$44(context.settings).forbid.map(normalizeEntry);
19072
18724
  return { JSXOpeningElement(node) {
19073
18725
  if (entries.length === 0) return;
19074
18726
  if (!isSupportedJsxName(node.name)) return;
@@ -19097,7 +18749,7 @@ const forbidComponentProps = defineRule({
19097
18749
  //#endregion
19098
18750
  //#region src/plugin/rules/react-builtins/forbid-dom-props.ts
19099
18751
  const buildMessage$24 = (propName, customMessage) => customMessage ?? `Your project blocks the \`${propName}\` prop on plain HTML tags, so this bypasses the agreed DOM API contract.`;
19100
- const resolveSettings$42 = (settings) => {
18752
+ const resolveSettings$43 = (settings) => {
19101
18753
  const reactDoctor = settings?.["react-doctor"];
19102
18754
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidDomProps ?? {} : {};
19103
18755
  const map = /* @__PURE__ */ new Map();
@@ -19119,7 +18771,7 @@ const forbidDomProps = defineRule({
19119
18771
  recommendation: "Configure blocked DOM props so plain HTML tags stay on the agreed DOM API surface.",
19120
18772
  category: "Architecture",
19121
18773
  create: (context) => {
19122
- const forbidMap = resolveSettings$42(context.settings);
18774
+ const forbidMap = resolveSettings$43(context.settings);
19123
18775
  return { JSXOpeningElement(node) {
19124
18776
  if (forbidMap.size === 0) return;
19125
18777
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
@@ -19182,7 +18834,7 @@ const isReactFunctionCall = (node, expectedCall) => {
19182
18834
  //#endregion
19183
18835
  //#region src/plugin/rules/react-builtins/forbid-elements.ts
19184
18836
  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.`;
19185
- const resolveSettings$41 = (settings) => {
18837
+ const resolveSettings$42 = (settings) => {
19186
18838
  const reactDoctor = settings?.["react-doctor"];
19187
18839
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidElements ?? {} : {};
19188
18840
  const map = /* @__PURE__ */ new Map();
@@ -19198,7 +18850,7 @@ const forbidElements = defineRule({
19198
18850
  recommendation: "Configure blocked elements so code stays on the approved UI primitives.",
19199
18851
  category: "Architecture",
19200
18852
  create: (context) => {
19201
- const forbidMap = resolveSettings$41(context.settings);
18853
+ const forbidMap = resolveSettings$42(context.settings);
19202
18854
  return {
19203
18855
  JSXOpeningElement(node) {
19204
18856
  if (forbidMap.size === 0) return;
@@ -19307,7 +18959,7 @@ const DEFAULT_HEADING_TAGS = [
19307
18959
  "h5",
19308
18960
  "h6"
19309
18961
  ];
19310
- const resolveSettings$40 = (settings) => {
18962
+ const resolveSettings$41 = (settings) => {
19311
18963
  const reactDoctor = settings?.["react-doctor"];
19312
18964
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.headingHasContent ?? {} : {};
19313
18965
  return { headingTags: new Set([...DEFAULT_HEADING_TAGS, ...ruleSettings.components ?? []]) };
@@ -19320,7 +18972,7 @@ const headingHasContent = defineRule({
19320
18972
  recommendation: "Put readable text in every heading.",
19321
18973
  category: "Accessibility",
19322
18974
  create: (context) => {
19323
- const settings = resolveSettings$40(context.settings);
18975
+ const settings = resolveSettings$41(context.settings);
19324
18976
  return { JSXOpeningElement(node) {
19325
18977
  const elementType = getElementType(node, context.settings);
19326
18978
  if (!settings.headingTags.has(elementType)) return;
@@ -19341,7 +18993,7 @@ const headingHasContent = defineRule({
19341
18993
  //#region src/plugin/rules/react-builtins/hook-use-state.ts
19342
18994
  const REQUIRE_DESTRUCTURE_MESSAGE = "`useState` should be destructured as `[value, setValue]` so readers can see the state value and setter together.";
19343
18995
  const NAMING_CONVENTION_MESSAGE = "This `useState` setter does not match its value name, so updates are harder to trace.";
19344
- const resolveSettings$39 = (settings) => {
18996
+ const resolveSettings$40 = (settings) => {
19345
18997
  const reactDoctor = settings?.["react-doctor"];
19346
18998
  return { allowDestructuredState: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.hookUseState ?? {} : {}).allowDestructuredState ?? false };
19347
18999
  };
@@ -19368,7 +19020,7 @@ const hookUseState = defineRule({
19368
19020
  recommendation: "Destructure `useState` as `const [thing, setThing] = useState(…)` so state reads and writes stay visible together.",
19369
19021
  category: "Architecture",
19370
19022
  create: (context) => {
19371
- const { allowDestructuredState } = resolveSettings$39(context.settings);
19023
+ const { allowDestructuredState } = resolveSettings$40(context.settings);
19372
19024
  return { CallExpression(node) {
19373
19025
  const callExpressionNode = node;
19374
19026
  if (!isReactFunctionCall(callExpressionNode, "useState")) return;
@@ -19501,7 +19153,7 @@ const hooksNoNanInDeps = defineRule({
19501
19153
  //#endregion
19502
19154
  //#region src/plugin/rules/a11y/html-has-lang.ts
19503
19155
  const MESSAGE$52 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
19504
- const resolveSettings$38 = (settings) => {
19156
+ const resolveSettings$39 = (settings) => {
19505
19157
  const reactDoctor = settings?.["react-doctor"];
19506
19158
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
19507
19159
  };
@@ -19538,7 +19190,7 @@ const htmlHasLang = defineRule({
19538
19190
  recommendation: "Set `<html lang=\"…\">` so screen readers know the page language.",
19539
19191
  category: "Accessibility",
19540
19192
  create: (context) => {
19541
- const settings = resolveSettings$38(context.settings);
19193
+ const settings = resolveSettings$39(context.settings);
19542
19194
  const tagSet = new Set(settings.htmlTags);
19543
19195
  return { JSXOpeningElement(node) {
19544
19196
  const tag = getElementType(node, context.settings);
@@ -20054,7 +19706,7 @@ const DEFAULT_REDUNDANT_WORDS = [
20054
19706
  "photo",
20055
19707
  "picture"
20056
19708
  ];
20057
- const resolveSettings$37 = (settings) => {
19709
+ const resolveSettings$38 = (settings) => {
20058
19710
  const reactDoctor = settings?.["react-doctor"];
20059
19711
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.imgRedundantAlt ?? {} : {};
20060
19712
  return {
@@ -20102,7 +19754,7 @@ const imgRedundantAlt = defineRule({
20102
19754
  category: "Accessibility",
20103
19755
  create: (context) => {
20104
19756
  if (isGeneratedImageRenderContext(context)) return {};
20105
- const settings = resolveSettings$37(context.settings);
19757
+ const settings = resolveSettings$38(context.settings);
20106
19758
  return { JSXOpeningElement(node) {
20107
19759
  const tag = getElementType(node, context.settings);
20108
19760
  if (!settings.components.includes(tag)) return;
@@ -20410,7 +20062,7 @@ const COMPOSITE_ITEM_ROLES = new Set([
20410
20062
  "row",
20411
20063
  "gridcell"
20412
20064
  ]);
20413
- const resolveSettings$36 = (settings) => {
20065
+ const resolveSettings$37 = (settings) => {
20414
20066
  const reactDoctor = settings?.["react-doctor"];
20415
20067
  return { tabbable: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.interactiveSupportsFocus ?? {} : {}).tabbable ?? DEFAULT_TABBABLE_ROLES };
20416
20068
  };
@@ -20422,7 +20074,7 @@ const interactiveSupportsFocus = defineRule({
20422
20074
  recommendation: "Add keyboard focus support so users can reach interactive elements without a pointer.",
20423
20075
  category: "Accessibility",
20424
20076
  create: (context) => {
20425
- const settings = resolveSettings$36(context.settings);
20077
+ const settings = resolveSettings$37(context.settings);
20426
20078
  const tabbableSet = new Set(settings.tabbable);
20427
20079
  return { JSXOpeningElement(node) {
20428
20080
  if (isLocalTestScaffoldJsx(node, context)) return;
@@ -22563,49 +22215,25 @@ const jsLengthCheckFirst = defineRule({
22563
22215
  });
22564
22216
  //#endregion
22565
22217
  //#region src/plugin/utils/is-proven-global-namespace-reference.ts
22566
- const GLOBAL_SELF_PROPERTY_NAMES = new Set([
22567
- "global",
22568
- "globalThis",
22569
- "self",
22570
- "window"
22571
- ]);
22572
22218
  const isProvenGlobalObjectReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
22573
- let currentExpression = expression;
22574
- while (true) {
22575
- const strippedExpression = stripParenExpression(currentExpression);
22576
- if (!isNodeOfType(strippedExpression, "Identifier")) return false;
22577
- if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
22578
- const symbol = scopes.symbolFor(strippedExpression);
22579
- if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
22580
- const declaration = symbol.declarationNode;
22581
- const initializer = declaration.init;
22582
- if (!initializer) return false;
22583
- const isDirectIdentifierAlias = isNodeOfType(declaration.id, "Identifier") && declaration.id === symbol.bindingIdentifier && initializer === symbol.initializer;
22584
- const bindingProperty = symbol.bindingIdentifier.parent;
22585
- const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
22586
- 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);
22587
- if (!isDirectIdentifierAlias && !isDirectGlobalSelfAlias) return false;
22588
- visitedSymbolIds.add(symbol.id);
22589
- currentExpression = initializer;
22590
- }
22219
+ const strippedExpression = stripParenExpression(expression);
22220
+ if (!isNodeOfType(strippedExpression, "Identifier")) return false;
22221
+ if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
22222
+ const symbol = scopes.symbolFor(strippedExpression);
22223
+ if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
22224
+ visitedSymbolIds.add(symbol.id);
22225
+ return isProvenGlobalObjectReference(symbol.initializer, scopes, visitedSymbolIds);
22591
22226
  };
22592
22227
  const isProvenGlobalNamespaceReference = (expression, namespaceName, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
22593
- let currentExpression = expression;
22594
- while (true) {
22595
- const strippedExpression = stripParenExpression(currentExpression);
22596
- if (!isNodeOfType(strippedExpression, "Identifier")) return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
22228
+ const strippedExpression = stripParenExpression(expression);
22229
+ if (isNodeOfType(strippedExpression, "Identifier")) {
22597
22230
  if (strippedExpression.name === namespaceName && scopes.isGlobalReference(strippedExpression)) return true;
22598
22231
  const symbol = scopes.symbolFor(strippedExpression);
22599
- if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
22232
+ if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
22600
22233
  visitedSymbolIds.add(symbol.id);
22601
- const declaration = symbol.declarationNode;
22602
- const bindingIdentifier = symbol.bindingIdentifier;
22603
- const bindingProperty = bindingIdentifier.parent;
22604
- 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);
22605
- const directInitializer = getDirectConstInitializer(symbol);
22606
- if (!directInitializer) return false;
22607
- currentExpression = directInitializer;
22234
+ return isProvenGlobalNamespaceReference(symbol.initializer, namespaceName, scopes, visitedSymbolIds);
22608
22235
  }
22236
+ return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
22609
22237
  };
22610
22238
  //#endregion
22611
22239
  //#region src/plugin/rules/js-performance/js-min-max-loop.ts
@@ -23718,7 +23346,7 @@ const jsTosortedImmutable = defineRule({
23718
23346
  const NEVER_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
23719
23347
  const ALWAYS_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
23720
23348
  const FALSE_OMITTED_MESSAGE = (attributeName) => `\`${attributeName}={false}\` does nothing, so the explicit false value adds noise without changing output.`;
23721
- const resolveSettings$35 = (settings) => {
23349
+ const resolveSettings$36 = (settings) => {
23722
23350
  const reactDoctor = settings?.["react-doctor"];
23723
23351
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxBooleanValue ?? {} : {};
23724
23352
  return {
@@ -23736,7 +23364,7 @@ const jsxBooleanValue = defineRule({
23736
23364
  recommendation: "Use one boolean-attribute style so equivalent true props scan the same across the codebase.",
23737
23365
  category: "Architecture",
23738
23366
  create: (context) => {
23739
- const settings = resolveSettings$35(context.settings);
23367
+ const settings = resolveSettings$36(context.settings);
23740
23368
  const alwaysSet = new Set(settings.always);
23741
23369
  const neverSet = new Set(settings.never);
23742
23370
  return { JSXAttribute(node) {
@@ -23790,7 +23418,7 @@ const jsxBooleanValue = defineRule({
23790
23418
  const UNNECESSARY_BRACES_MESSAGE = "These curly braces wrap a literal value, so they add JSX noise without changing output.";
23791
23419
  const REQUIRED_BRACES_MESSAGE = "This JSX value needs `{ }` so React reads it as an expression instead of text.";
23792
23420
  const isAllowedMode = (value) => value === "always" || value === "never" || value === "ignore";
23793
- const resolveSettings$34 = (settings) => {
23421
+ const resolveSettings$35 = (settings) => {
23794
23422
  const reactDoctor = settings?.["react-doctor"];
23795
23423
  const ruleSettingsRaw = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxCurlyBracePresence : void 0;
23796
23424
  if (isAllowedMode(ruleSettingsRaw)) return {
@@ -23886,7 +23514,7 @@ const jsxCurlyBracePresence = defineRule({
23886
23514
  recommendation: "Use one JSX literal style so equivalent markup scans the same without extra JSX noise.",
23887
23515
  category: "Architecture",
23888
23516
  create: (context) => {
23889
- const settings = resolveSettings$34(context.settings);
23517
+ const settings = resolveSettings$35(context.settings);
23890
23518
  return {
23891
23519
  JSXAttribute(node) {
23892
23520
  const value = node.value;
@@ -23969,7 +23597,7 @@ const containsJsxElement = (root) => {
23969
23597
  //#region src/plugin/rules/react-builtins/jsx-filename-extension.ts
23970
23598
  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.`;
23971
23599
  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.`;
23972
- const resolveSettings$33 = (settings) => {
23600
+ const resolveSettings$34 = (settings) => {
23973
23601
  const reactDoctor = settings?.["react-doctor"];
23974
23602
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFilenameExtension ?? {} : {};
23975
23603
  return {
@@ -23991,7 +23619,7 @@ const jsxFilenameExtension = defineRule({
23991
23619
  recommendation: "Name JSX files with the configured extension so file names accurately signal which files contain JSX.",
23992
23620
  category: "Architecture",
23993
23621
  create: (context) => {
23994
- const settings = resolveSettings$33(context.settings);
23622
+ const settings = resolveSettings$34(context.settings);
23995
23623
  const allowedExtensions = normalizeExtensions(settings.extensions);
23996
23624
  const filename = normalizeFilename(context.filename ?? "fixture.tsx");
23997
23625
  const extensionOnly = path.extname(filename).slice(1);
@@ -24032,7 +23660,7 @@ const jsxFilenameExtension = defineRule({
24032
23660
  //#region src/plugin/rules/react-builtins/jsx-fragments.ts
24033
23661
  const SYNTAX_MESSAGE = "`<React.Fragment>` is used where shorthand fragments are configured, so similar wrappers look different across the codebase.";
24034
23662
  const ELEMENT_MESSAGE = "Fragment shorthand is used where explicit fragments are configured, so similar wrappers look different across the codebase.";
24035
- const resolveSettings$32 = (settings) => {
23663
+ const resolveSettings$33 = (settings) => {
24036
23664
  const reactDoctor = settings?.["react-doctor"];
24037
23665
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFragments ?? {} : {}).mode ?? "syntax" };
24038
23666
  };
@@ -24044,7 +23672,7 @@ const jsxFragments = defineRule({
24044
23672
  recommendation: "Use one fragment style so identical wrappers do not look different across files.",
24045
23673
  category: "Architecture",
24046
23674
  create: (context) => {
24047
- const { mode } = resolveSettings$32(context.settings);
23675
+ const { mode } = resolveSettings$33(context.settings);
24048
23676
  return {
24049
23677
  JSXElement(node) {
24050
23678
  if (mode !== "syntax") return;
@@ -24088,7 +23716,7 @@ const buildPropRegex = (handlerPropPrefix) => {
24088
23716
  const escaped = tokens.map((token) => token.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("|");
24089
23717
  return new RegExp(`^(${escaped})[A-Z].*$`);
24090
23718
  };
24091
- const resolveSettings$31 = (settings) => {
23719
+ const resolveSettings$32 = (settings) => {
24092
23720
  const reactDoctor = settings?.["react-doctor"];
24093
23721
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxHandlerNames ?? {} : {};
24094
23722
  let handlerPrefix = DEFAULT_HANDLER_PREFIX;
@@ -24164,7 +23792,7 @@ const jsxHandlerNames = defineRule({
24164
23792
  recommendation: "Use the `on…` prefix for event-handler props and `handle…` for handlers so readers can trace event flow.",
24165
23793
  category: "Architecture",
24166
23794
  create: (context) => {
24167
- const settings = resolveSettings$31(context.settings);
23795
+ const settings = resolveSettings$32(context.settings);
24168
23796
  return { JSXAttribute(node) {
24169
23797
  if (settings.ignoreComponentNames.length > 0) {
24170
23798
  const opening = node.parent;
@@ -24239,19 +23867,6 @@ const hasJsxKeyAttribute = (openingElement) => {
24239
23867
  return false;
24240
23868
  };
24241
23869
  //#endregion
24242
- //#region src/plugin/utils/is-component-function.ts
24243
- const isFunctionAssignedToComponent = (functionNode) => {
24244
- let cursor = functionNode.parent ?? null;
24245
- while (isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
24246
- if (isNodeOfType(cursor, "VariableDeclarator") && isNodeOfType(cursor.id, "Identifier") && isUppercaseName(cursor.id.name)) return true;
24247
- return isNodeOfType(cursor, "ExportDefaultDeclaration");
24248
- };
24249
- const isComponentFunction$1 = (node) => {
24250
- if (!isFunctionLike$1(node)) return false;
24251
- if (isNodeOfType(node, "FunctionDeclaration")) return !node.id || node.id.name === "default" || isUppercaseName(node.id.name) || isNodeOfType(node.parent, "ExportDefaultDeclaration");
24252
- return isFunctionAssignedToComponent(node);
24253
- };
24254
- //#endregion
24255
23870
  //#region src/plugin/utils/is-const-declared-binding.ts
24256
23871
  const BINDING_PATTERN_ANCESTOR_TYPES = new Set([
24257
23872
  "VariableDeclarator",
@@ -24296,17 +23911,11 @@ const ITERATOR_METHOD_NAMES$1 = new Set([
24296
23911
  "flatMap",
24297
23912
  "from"
24298
23913
  ]);
24299
- const RENDERING_CALL_NAMES = new Set([
24300
- "createPortal",
24301
- "hydrate",
24302
- "hydrateRoot",
24303
- "render"
24304
- ]);
24305
23914
  const MISSING_KEY_ARRAY = "Your users can see the wrong data when this array reorders.";
24306
23915
  const MISSING_KEY_ITERATOR = "Your users can see the wrong data when this list reorders.";
24307
- const KEY_BEFORE_SPREAD = "Place this `key` after the `{...spread}` so the spread cannot override it.";
23916
+ const KEY_AFTER_SPREAD = "Place this `key` before the `{...spread}` so the JSX transform can always extract it.";
24308
23917
  const DUPLICATE_KEY = (keyValue) => `Your users can see the wrong data because two elements share the key "${keyValue}".`;
24309
- const resolveSettings$30 = (settings) => {
23918
+ const resolveSettings$31 = (settings) => {
24310
23919
  const reactDoctor = settings?.["react-doctor"];
24311
23920
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxKey ?? {} : {};
24312
23921
  return {
@@ -24320,33 +23929,6 @@ const findArrayVariableDeclarator = (arrayExpression) => {
24320
23929
  if (ancestor && isNodeOfType(ancestor, "VariableDeclarator") && ancestor.init === wrapped && isNodeOfType(ancestor.id, "Identifier")) return ancestor;
24321
23930
  return null;
24322
23931
  };
24323
- const isArrayNestedInObjectProperty = (arrayExpression) => {
24324
- let current = findTransparentExpressionRoot(arrayExpression);
24325
- while (current.parent) {
24326
- const parent = current.parent;
24327
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current) || isNodeOfType(parent, "LogicalExpression") && (parent.left === current || parent.right === current)) {
24328
- current = findTransparentExpressionRoot(parent);
24329
- continue;
24330
- }
24331
- return isNodeOfType(parent, "Property") && parent.value === current;
24332
- }
24333
- return false;
24334
- };
24335
- const isArrayPassedToNonRenderingCall = (arrayExpression, scopes) => {
24336
- const transparentArray = findTransparentExpressionRoot(arrayExpression);
24337
- const callExpression = transparentArray.parent;
24338
- if (!callExpression || !isNodeOfType(callExpression, "CallExpression") || !callExpression.arguments.some((argument) => argument === transparentArray)) return false;
24339
- const argumentIndex = callExpression.arguments.findIndex((argument) => argument === transparentArray);
24340
- if (isReactApiCall(callExpression, "createElement", scopes, {
24341
- allowGlobalReactNamespace: true,
24342
- allowUnboundBareCalls: true,
24343
- resolveNamedAliases: true
24344
- })) return argumentIndex < 2;
24345
- const callee = stripParenExpression(callExpression.callee);
24346
- if (isNodeOfType(callee, "Identifier")) return !RENDERING_CALL_NAMES.has(callee.name);
24347
- if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return !RENDERING_CALL_NAMES.has(callee.property.name);
24348
- return true;
24349
- };
24350
23932
  const isIdentityIteratorCallback = (callback) => {
24351
23933
  const callbackFunction = stripParenExpression(callback);
24352
23934
  if (!isNodeOfType(callbackFunction, "ArrowFunctionExpression") && !isNodeOfType(callbackFunction, "FunctionExpression")) return true;
@@ -24443,7 +24025,7 @@ const findNamedCallbackIteratorCall = (functionNode) => {
24443
24025
  namedCallbackIteratorCallCache.set(functionNode, iteratorCall);
24444
24026
  return iteratorCall;
24445
24027
  };
24446
- const findEnclosingIteratorContext = (jsxNode, scopes) => {
24028
+ const findEnclosingIteratorContext = (jsxNode) => {
24447
24029
  let current = jsxNode;
24448
24030
  let isOutsideContainingFunction = false;
24449
24031
  let didSeeReturnStatement = false;
@@ -24465,8 +24047,6 @@ const findEnclosingIteratorContext = (jsxNode, scopes) => {
24465
24047
  isOutsideContainingFunction = true;
24466
24048
  } else if (isNodeOfType(parent, "ArrayExpression")) {
24467
24049
  if (isOutsideContainingFunction) return null;
24468
- if (isArrayNestedInObjectProperty(parent)) return null;
24469
- if (isArrayPassedToNonRenderingCall(parent, scopes)) return null;
24470
24050
  const arrayParent = parent.parent;
24471
24051
  if (arrayParent && isNodeOfType(arrayParent, "Property")) return null;
24472
24052
  if (arrayParent && isNodeOfType(arrayParent, "ArrayExpression")) return null;
@@ -24548,15 +24128,9 @@ const isWithinChildrenToArray = (jsxNode) => {
24548
24128
  const isKeyPropertyName = (propertyKey) => {
24549
24129
  if (isNodeOfType(propertyKey, "Identifier")) return propertyKey.name === "key";
24550
24130
  if (isNodeOfType(propertyKey, "Literal")) return String(propertyKey.value) === "key";
24551
- return false;
24131
+ return true;
24552
24132
  };
24553
24133
  const isThisPropsMember = (expression) => isNodeOfType(expression, "MemberExpression") && !expression.computed && isNodeOfType(expression.object, "ThisExpression") && isNodeOfType(expression.property, "Identifier") && expression.property.name === "props";
24554
- const isDirectComponentPropsParameter = (bindingIdentifier, functionNode) => {
24555
- if (!isFunctionLike$1(functionNode) || !isComponentFunction$1(functionNode)) return false;
24556
- const firstParameter = functionNode.params?.[0];
24557
- if (!firstParameter) return false;
24558
- return (isNodeOfType(firstParameter, "AssignmentPattern") ? firstParameter.left : firstParameter) === bindingIdentifier;
24559
- };
24560
24134
  const isFunctionParameterPropsRest = (bindingIdentifier) => {
24561
24135
  const restElement = bindingIdentifier.parent;
24562
24136
  if (!restElement || !isNodeOfType(restElement, "RestElement")) return false;
@@ -24566,7 +24140,7 @@ const isFunctionParameterPropsRest = (bindingIdentifier) => {
24566
24140
  const parameterNode = patternParent && isNodeOfType(patternParent, "AssignmentPattern") && patternParent.left === objectPattern ? patternParent : objectPattern;
24567
24141
  const functionNode = parameterNode.parent;
24568
24142
  if (!functionNode || !isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "FunctionDeclaration")) return false;
24569
- return isComponentFunction$1(functionNode) && functionNode.params[0] === parameterNode;
24143
+ return functionNode.params.some((param) => param === parameterNode);
24570
24144
  };
24571
24145
  const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
24572
24146
  const restElement = bindingIdentifier.parent;
@@ -24579,14 +24153,14 @@ const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
24579
24153
  }
24580
24154
  return false;
24581
24155
  };
24582
- const isBindingAssignedKey = (scopeOwner, bindingName) => {
24583
- let didFindKeyAssignment = false;
24156
+ const isBindingObjectMutated = (scopeOwner, bindingName) => {
24157
+ let didFindMutation = false;
24584
24158
  walkAst(scopeOwner, (node) => {
24585
- if (didFindKeyAssignment) return false;
24159
+ if (didFindMutation) return false;
24586
24160
  if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression")) {
24587
24161
  const assignedObject = stripParenExpression(node.left.object);
24588
- if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName && isKeyPropertyName(node.left.property)) {
24589
- didFindKeyAssignment = true;
24162
+ if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName) {
24163
+ didFindMutation = true;
24590
24164
  return false;
24591
24165
  }
24592
24166
  }
@@ -24594,63 +24168,59 @@ const isBindingAssignedKey = (scopeOwner, bindingName) => {
24594
24168
  const callee = node.callee;
24595
24169
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Object" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "assign") {
24596
24170
  const firstArgument = node.arguments[0];
24597
- if (firstArgument && isNodeOfType(firstArgument, "Identifier") && firstArgument.name === bindingName) for (const sourceArgument of node.arguments.slice(1)) {
24598
- const source = stripParenExpression(sourceArgument);
24599
- if (!isNodeOfType(source, "ObjectExpression")) continue;
24600
- if (source.properties.some((property) => isNodeOfType(property, "Property") && isKeyPropertyName(property.key))) {
24601
- didFindKeyAssignment = true;
24602
- return false;
24603
- }
24171
+ if (firstArgument && isNodeOfType(firstArgument, "Identifier") && firstArgument.name === bindingName) {
24172
+ didFindMutation = true;
24173
+ return false;
24604
24174
  }
24605
24175
  }
24606
24176
  }
24607
24177
  });
24608
- return didFindKeyAssignment;
24178
+ return didFindMutation;
24609
24179
  };
24610
- const spreadExpressionHasKey = (expression, depth) => {
24180
+ const spreadExpressionCanCarryKey = (expression, depth) => {
24611
24181
  const inner = stripParenExpression(expression);
24612
24182
  if (isNodeOfType(inner, "ObjectExpression")) {
24613
24183
  for (const property of inner.properties) {
24614
24184
  if (isNodeOfType(property, "SpreadElement")) {
24615
- if (depth >= 3) continue;
24616
- if (spreadExpressionHasKey(property.argument, depth + 1)) return true;
24185
+ if (depth >= 3) return true;
24186
+ if (spreadExpressionCanCarryKey(property.argument, depth + 1)) return true;
24617
24187
  continue;
24618
24188
  }
24619
- if (!isNodeOfType(property, "Property")) continue;
24189
+ if (!isNodeOfType(property, "Property")) return true;
24190
+ if (property.computed) return true;
24620
24191
  if (isKeyPropertyName(property.key)) return true;
24621
24192
  }
24622
24193
  return false;
24623
24194
  }
24624
24195
  if (isNodeOfType(inner, "ConditionalExpression")) {
24625
- if (depth >= 3) return false;
24626
- return spreadExpressionHasKey(inner.consequent, depth + 1) || spreadExpressionHasKey(inner.alternate, depth + 1);
24196
+ if (depth >= 3) return true;
24197
+ return spreadExpressionCanCarryKey(inner.consequent, depth + 1) || spreadExpressionCanCarryKey(inner.alternate, depth + 1);
24627
24198
  }
24628
24199
  if (isNodeOfType(inner, "LogicalExpression")) {
24629
- if (depth >= 3) return false;
24630
- if (inner.operator === "&&") return spreadExpressionHasKey(inner.right, depth + 1);
24631
- return spreadExpressionHasKey(inner.left, depth + 1) || spreadExpressionHasKey(inner.right, depth + 1);
24200
+ if (depth >= 3) return true;
24201
+ if (inner.operator === "&&") return spreadExpressionCanCarryKey(inner.right, depth + 1);
24202
+ return spreadExpressionCanCarryKey(inner.left, depth + 1) || spreadExpressionCanCarryKey(inner.right, depth + 1);
24632
24203
  }
24633
24204
  if (isNodeOfType(inner, "Literal")) return false;
24634
24205
  if (isThisPropsMember(inner)) return false;
24635
24206
  if (isNodeOfType(inner, "Identifier")) {
24636
24207
  if (inner.name === "undefined") return false;
24637
- if (depth >= 3) return false;
24208
+ if (depth >= 3) return true;
24638
24209
  const binding = findVariableInitializer(inner, inner.name);
24639
- if (!binding) return false;
24210
+ if (!binding) return true;
24640
24211
  if (isRestBindingWithKeyExtracted(binding.bindingIdentifier)) return false;
24641
24212
  if (isFunctionParameterPropsRest(binding.bindingIdentifier)) return false;
24642
- if (isDirectComponentPropsParameter(binding.bindingIdentifier, binding.scopeOwner)) return false;
24643
- if (!isConstDeclaredBinding(binding) || !binding.initializer) return false;
24644
- if (isBindingAssignedKey(binding.scopeOwner, inner.name)) return true;
24645
- return spreadExpressionHasKey(binding.initializer, depth + 1);
24213
+ if (!isConstDeclaredBinding(binding) || !binding.initializer) return true;
24214
+ if (isBindingObjectMutated(binding.scopeOwner, inner.name)) return true;
24215
+ return spreadExpressionCanCarryKey(binding.initializer, depth + 1);
24646
24216
  }
24647
- return false;
24217
+ return true;
24648
24218
  };
24649
- const spreadCanOverwriteKey = (spreadAttribute) => spreadExpressionHasKey(spreadAttribute.argument, 0);
24219
+ const spreadCanOverwriteKey = (spreadAttribute) => spreadExpressionCanCarryKey(spreadAttribute.argument, 0);
24650
24220
  const checkKeyBeforeSpread = (context, openingElement) => {
24651
24221
  let keyIndex = null;
24652
24222
  let keyAttribute = null;
24653
- let lastKeyCarryingSpreadIndex = null;
24223
+ let firstKeyCarryingSpreadIndex = null;
24654
24224
  for (let attributeIndex = 0; attributeIndex < openingElement.attributes.length; attributeIndex++) {
24655
24225
  const attribute = openingElement.attributes[attributeIndex];
24656
24226
  if (isNodeOfType(attribute, "JSXAttribute")) {
@@ -24658,11 +24228,11 @@ const checkKeyBeforeSpread = (context, openingElement) => {
24658
24228
  keyIndex = attributeIndex;
24659
24229
  keyAttribute = attribute;
24660
24230
  }
24661
- } else if (isNodeOfType(attribute, "JSXSpreadAttribute") && spreadCanOverwriteKey(attribute)) lastKeyCarryingSpreadIndex = attributeIndex;
24231
+ } else if (isNodeOfType(attribute, "JSXSpreadAttribute") && firstKeyCarryingSpreadIndex === null && spreadCanOverwriteKey(attribute)) firstKeyCarryingSpreadIndex = attributeIndex;
24662
24232
  }
24663
- if (keyIndex !== null && lastKeyCarryingSpreadIndex !== null && lastKeyCarryingSpreadIndex > keyIndex && keyAttribute) context.report({
24233
+ if (keyIndex !== null && firstKeyCarryingSpreadIndex !== null && firstKeyCarryingSpreadIndex < keyIndex && keyAttribute) context.report({
24664
24234
  node: keyAttribute,
24665
- message: KEY_BEFORE_SPREAD
24235
+ message: KEY_AFTER_SPREAD
24666
24236
  });
24667
24237
  };
24668
24238
  const getKeyAttributeValueString = (openingElement) => {
@@ -24706,7 +24276,7 @@ const jsxKey = defineRule({
24706
24276
  severity: "error",
24707
24277
  recommendation: "Add a stable `key` prop so React can keep list items matched to the right data when the list changes.",
24708
24278
  create: (context) => {
24709
- const settings = resolveSettings$30(context.settings);
24279
+ const settings = resolveSettings$31(context.settings);
24710
24280
  return {
24711
24281
  JSXElement(node) {
24712
24282
  const openingElement = node.openingElement;
@@ -24724,7 +24294,7 @@ const jsxKey = defineRule({
24724
24294
  else seenKeys.add(keyValue.keyValue);
24725
24295
  }
24726
24296
  }
24727
- const enclosingContext = findEnclosingIteratorContext(node, context.scopes);
24297
+ const enclosingContext = findEnclosingIteratorContext(node);
24728
24298
  if (!enclosingContext) return;
24729
24299
  if (isWithinChildrenToArray(node)) return;
24730
24300
  if (hasJsxKeyAttribute(openingElement)) return;
@@ -24760,7 +24330,7 @@ const jsxKey = defineRule({
24760
24330
  //#region src/plugin/rules/react-builtins/jsx-max-depth.ts
24761
24331
  const buildMessage$19 = (depth, max) => `This JSX is hard to read at ${depth} levels deep, past the limit of ${max}.`;
24762
24332
  const DEFAULT_MAX_DEPTH = 14;
24763
- const resolveSettings$29 = (settings) => {
24333
+ const resolveSettings$30 = (settings) => {
24764
24334
  const reactDoctor = settings?.["react-doctor"];
24765
24335
  return { max: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxMaxDepth ?? {} : {}).max ?? DEFAULT_MAX_DEPTH };
24766
24336
  };
@@ -24829,7 +24399,7 @@ const jsxMaxDepth = defineRule({
24829
24399
  recommendation: "Pull deeply nested JSX into smaller components so it's easier to read.",
24830
24400
  category: "Architecture",
24831
24401
  create: (context) => {
24832
- const { max } = resolveSettings$29(context.settings);
24402
+ const { max } = resolveSettings$30(context.settings);
24833
24403
  const deepestLeafPerTree = /* @__PURE__ */ new Map();
24834
24404
  const checkNode = (node) => {
24835
24405
  if (!isLeafJsxNode(node)) return;
@@ -26801,7 +26371,7 @@ const jsxNoNewObjectAsProp = defineRule({
26801
26371
  //#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
26802
26372
  const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
26803
26373
  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;
26804
- const resolveSettings$28 = (settings) => {
26374
+ const resolveSettings$29 = (settings) => {
26805
26375
  const reactDoctor = settings?.["react-doctor"];
26806
26376
  if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
26807
26377
  return reactDoctor.jsxNoScriptUrl ?? {};
@@ -26823,7 +26393,7 @@ const jsxNoScriptUrl = defineRule({
26823
26393
  recommendation: "Use real event handlers instead of `javascript:` URLs so injected URL text cannot execute as code.",
26824
26394
  category: "Security",
26825
26395
  create: (context) => {
26826
- const options = resolveSettings$28(context.settings);
26396
+ const options = resolveSettings$29(context.settings);
26827
26397
  return { JSXOpeningElement(node) {
26828
26398
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
26829
26399
  const elementName = resolveJsxElementType(node);
@@ -26844,6 +26414,283 @@ const jsxNoScriptUrl = defineRule({
26844
26414
  }
26845
26415
  });
26846
26416
  //#endregion
26417
+ //#region src/plugin/rules/react-builtins/jsx-no-target-blank.ts
26418
+ const NOREFERRER_MESSAGE = "`target=\"_blank\"` without `rel=\"noreferrer\"` lets the linked page hijack your tab to a phishing site.";
26419
+ const NOOPENER_MESSAGE = "`target=\"_blank\"` without `rel` lets the linked page hijack your tab to a phishing site.";
26420
+ const SPREAD_MESSAGE = "A spread here can add `target=\"_blank\"`, letting the linked page hijack your tab to a phishing site.";
26421
+ const resolveSettings$28 = (settings) => {
26422
+ const reactDoctor = settings?.["react-doctor"];
26423
+ const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxNoTargetBlank ?? {} : {};
26424
+ const reactSettings = typeof settings?.react === "object" && settings.react !== null ? settings.react : {};
26425
+ const linkComponents = /* @__PURE__ */ new Map();
26426
+ for (const entry of reactSettings.linkComponents ?? []) if (typeof entry === "string") linkComponents.set(entry, ["href"]);
26427
+ else if (typeof entry === "object" && entry !== null) {
26428
+ const linkAttribute = entry.linkAttribute ?? "href";
26429
+ linkComponents.set(entry.name, Array.isArray(linkAttribute) ? linkAttribute : [linkAttribute]);
26430
+ }
26431
+ const formComponents = /* @__PURE__ */ new Map();
26432
+ for (const entry of reactSettings.formComponents ?? []) if (typeof entry === "string") formComponents.set(entry, ["action"]);
26433
+ else if (typeof entry === "object" && entry !== null) {
26434
+ const formAttribute = entry.formAttribute ?? "action";
26435
+ formComponents.set(entry.name, Array.isArray(formAttribute) ? formAttribute : [formAttribute]);
26436
+ }
26437
+ return {
26438
+ enforceDynamicLinks: ruleSettings.enforceDynamicLinks ?? "always",
26439
+ warnOnSpreadAttributes: ruleSettings.warnOnSpreadAttributes ?? false,
26440
+ allowReferrer: ruleSettings.allowReferrer ?? false,
26441
+ links: ruleSettings.links ?? true,
26442
+ forms: ruleSettings.forms ?? false,
26443
+ linkComponents,
26444
+ formComponents
26445
+ };
26446
+ };
26447
+ const isExternalLink = (href) => href.includes("//");
26448
+ const matchHrefExpression = (expression, state) => {
26449
+ if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") {
26450
+ if (isExternalLink(expression.value)) state.isExternal = true;
26451
+ return;
26452
+ }
26453
+ if (isNodeOfType(expression, "Identifier")) {
26454
+ state.isDynamic = true;
26455
+ return;
26456
+ }
26457
+ if (isNodeOfType(expression, "ConditionalExpression")) {
26458
+ matchHrefExpression(expression.consequent, state);
26459
+ matchHrefExpression(expression.alternate, state);
26460
+ }
26461
+ };
26462
+ const checkHref = (attributeValue, enforceDynamicLinks) => {
26463
+ const state = {
26464
+ isExternal: false,
26465
+ isDynamic: false
26466
+ };
26467
+ if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") state.isExternal = isExternalLink(attributeValue.value);
26468
+ else if (isNodeOfType(attributeValue, "JSXExpressionContainer")) matchHrefExpression(attributeValue.expression, state);
26469
+ if (enforceDynamicLinks === "never") return !state.isExternal || state.isDynamic;
26470
+ return !(state.isExternal || state.isDynamic);
26471
+ };
26472
+ const checkRelValue = (text, allowReferrer) => {
26473
+ const tokens = text.split(/\s+/);
26474
+ if (allowReferrer) return tokens.includes("noopener") || tokens.includes("noreferrer");
26475
+ return tokens.some((token) => token.toLowerCase() === "noreferrer");
26476
+ };
26477
+ const matchRelExpression = (expression, allowReferrer) => {
26478
+ const empty = {
26479
+ combined: false,
26480
+ testName: "",
26481
+ consequent: false,
26482
+ alternate: false
26483
+ };
26484
+ if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
26485
+ combined: checkRelValue(expression.value, allowReferrer),
26486
+ testName: "",
26487
+ consequent: false,
26488
+ alternate: false
26489
+ };
26490
+ if (isNodeOfType(expression, "ConditionalExpression")) {
26491
+ const consequent = matchRelExpression(expression.consequent, allowReferrer);
26492
+ const alternate = matchRelExpression(expression.alternate, allowReferrer);
26493
+ const test = expression.test;
26494
+ if (isNodeOfType(test, "Identifier")) return {
26495
+ combined: consequent.combined && alternate.combined,
26496
+ testName: test.name,
26497
+ consequent: consequent.combined,
26498
+ alternate: alternate.combined
26499
+ };
26500
+ return {
26501
+ combined: consequent.combined && alternate.combined,
26502
+ testName: "",
26503
+ consequent: consequent.combined,
26504
+ alternate: alternate.combined
26505
+ };
26506
+ }
26507
+ return empty;
26508
+ };
26509
+ const checkRel = (attributeValue, allowReferrer) => {
26510
+ const empty = {
26511
+ combined: false,
26512
+ testName: "",
26513
+ consequent: false,
26514
+ alternate: false
26515
+ };
26516
+ if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
26517
+ combined: checkRelValue(attributeValue.value, allowReferrer),
26518
+ testName: "",
26519
+ consequent: false,
26520
+ alternate: false
26521
+ };
26522
+ if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
26523
+ const expression = attributeValue.expression;
26524
+ if (expression.type === "JSXEmptyExpression") return empty;
26525
+ return matchRelExpression(expression, allowReferrer);
26526
+ }
26527
+ return empty;
26528
+ };
26529
+ const matchTargetExpression = (expression) => {
26530
+ const empty = {
26531
+ combined: false,
26532
+ testName: "",
26533
+ consequent: false,
26534
+ alternate: false
26535
+ };
26536
+ if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
26537
+ combined: expression.value.toLowerCase() === "_blank",
26538
+ testName: "",
26539
+ consequent: false,
26540
+ alternate: false
26541
+ };
26542
+ if (isNodeOfType(expression, "ConditionalExpression")) {
26543
+ const consequent = matchTargetExpression(expression.consequent);
26544
+ const alternate = matchTargetExpression(expression.alternate);
26545
+ const test = expression.test;
26546
+ const combined = consequent.combined || alternate.combined;
26547
+ if (isNodeOfType(test, "Identifier")) return {
26548
+ combined,
26549
+ testName: test.name,
26550
+ consequent: consequent.combined,
26551
+ alternate: alternate.combined
26552
+ };
26553
+ return {
26554
+ combined,
26555
+ testName: "",
26556
+ consequent: consequent.combined,
26557
+ alternate: alternate.combined
26558
+ };
26559
+ }
26560
+ return empty;
26561
+ };
26562
+ const checkTarget = (attributeValue) => {
26563
+ if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
26564
+ combined: attributeValue.value.toLowerCase() === "_blank",
26565
+ testName: "",
26566
+ consequent: false,
26567
+ alternate: false
26568
+ };
26569
+ if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
26570
+ const expression = attributeValue.expression;
26571
+ if (expression.type === "JSXEmptyExpression") return {
26572
+ combined: false,
26573
+ testName: "",
26574
+ consequent: false,
26575
+ alternate: false
26576
+ };
26577
+ return matchTargetExpression(expression);
26578
+ }
26579
+ return {
26580
+ combined: false,
26581
+ testName: "",
26582
+ consequent: false,
26583
+ alternate: false
26584
+ };
26585
+ };
26586
+ const jsxNoTargetBlank = defineRule({
26587
+ id: "jsx-no-target-blank",
26588
+ title: "Unsafe target=_blank link",
26589
+ severity: "warn",
26590
+ recommendation: "Add `rel=\"noreferrer\"` (or `\"noopener\"`) when using `target=\"_blank\"`.",
26591
+ category: "Security",
26592
+ create: (context) => {
26593
+ const settings = resolveSettings$28(context.settings);
26594
+ const isLink = (tagName) => {
26595
+ if (!settings.links) return false;
26596
+ if (tagName === "a") return true;
26597
+ return settings.linkComponents.has(tagName);
26598
+ };
26599
+ const isForm = (tagName) => {
26600
+ if (!settings.forms) return false;
26601
+ if (tagName === "form") return true;
26602
+ return settings.formComponents.has(tagName);
26603
+ };
26604
+ return { JSXOpeningElement(node) {
26605
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
26606
+ const tagName = resolveJsxElementType(node);
26607
+ if (!tagName) return;
26608
+ if (!isLink(tagName) && !isForm(tagName)) return;
26609
+ const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
26610
+ const formAttributeNames = settings.formComponents.get(tagName) ?? ["action"];
26611
+ let targetTuple = {
26612
+ combined: false,
26613
+ testName: "",
26614
+ consequent: false,
26615
+ alternate: false
26616
+ };
26617
+ let relTuple = {
26618
+ combined: false,
26619
+ testName: "",
26620
+ consequent: false,
26621
+ alternate: false
26622
+ };
26623
+ let isHrefValid = true;
26624
+ let hasHrefValue = false;
26625
+ let warnSpread = false;
26626
+ let targetReportNode = node.name;
26627
+ let spreadReportNode = null;
26628
+ for (const attribute of node.attributes) {
26629
+ if (isNodeOfType(attribute, "JSXSpreadAttribute")) {
26630
+ if (settings.warnOnSpreadAttributes) {
26631
+ warnSpread = true;
26632
+ spreadReportNode = attribute;
26633
+ targetTuple = {
26634
+ combined: false,
26635
+ testName: "",
26636
+ consequent: false,
26637
+ alternate: false
26638
+ };
26639
+ relTuple = {
26640
+ combined: false,
26641
+ testName: "",
26642
+ consequent: false,
26643
+ alternate: false
26644
+ };
26645
+ isHrefValid = false;
26646
+ hasHrefValue = true;
26647
+ }
26648
+ continue;
26649
+ }
26650
+ if (!isNodeOfType(attribute, "JSXAttribute")) continue;
26651
+ const attributeName = attribute.name;
26652
+ if (!isNodeOfType(attributeName, "JSXIdentifier")) continue;
26653
+ const propName = attributeName.name;
26654
+ const value = attribute.value;
26655
+ if (propName === "target") {
26656
+ if (value) {
26657
+ targetTuple = checkTarget(value);
26658
+ targetReportNode = value;
26659
+ }
26660
+ } else if (propName === "href" || propName === "action" || linkAttributeNames.includes(propName) || formAttributeNames.includes(propName)) {
26661
+ if (value) {
26662
+ hasHrefValue = true;
26663
+ isHrefValid = checkHref(value, settings.enforceDynamicLinks);
26664
+ }
26665
+ } else if (propName === "rel" && value) relTuple = checkRel(value, settings.allowReferrer);
26666
+ }
26667
+ if (warnSpread) {
26668
+ if (hasHrefValue && isHrefValid || relTuple.combined) return;
26669
+ context.report({
26670
+ node: spreadReportNode ?? node,
26671
+ message: SPREAD_MESSAGE
26672
+ });
26673
+ return;
26674
+ }
26675
+ if (!isHrefValid) {
26676
+ if (targetTuple.testName !== "" && targetTuple.testName === relTuple.testName) {
26677
+ const consequentBad = targetTuple.consequent && !relTuple.consequent;
26678
+ const alternateBad = targetTuple.alternate && !relTuple.alternate;
26679
+ if (consequentBad || alternateBad) context.report({
26680
+ node: targetReportNode,
26681
+ message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
26682
+ });
26683
+ return;
26684
+ }
26685
+ if (targetTuple.combined && !relTuple.combined) context.report({
26686
+ node: targetReportNode,
26687
+ message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
26688
+ });
26689
+ }
26690
+ } };
26691
+ }
26692
+ });
26693
+ //#endregion
26847
26694
  //#region src/plugin/rules/react-builtins/jsx-no-undef.ts
26848
26695
  const buildMessage$18 = (name) => `\`${name}\` crashes at runtime because it isn't defined here.`;
26849
26696
  const KNOWN_GLOBALS = new Set([
@@ -28501,30 +28348,6 @@ const hasAncestorLayoutMatching = (pageFilename, matchesLayout) => {
28501
28348
  const hasAncestorMetadataLayout = (pageFilename) => hasAncestorLayoutMatching(pageFilename, (layoutPath) => METADATA_EXPORT_NAMES.some((exportName) => doesModuleExportName(layoutPath, exportName)));
28502
28349
  //#endregion
28503
28350
  //#region src/plugin/rules/nextjs/nextjs-missing-metadata.ts
28504
- const unwrapAwaitedExpression = (node) => {
28505
- let expression = stripParenExpression(node);
28506
- while (isNodeOfType(expression, "AwaitExpression")) expression = stripParenExpression(expression.argument);
28507
- return expression;
28508
- };
28509
- const isNextNavigationRedirectCall = (node, context) => {
28510
- const expression = unwrapAwaitedExpression(node);
28511
- if (!isNodeOfType(expression, "CallExpression")) return false;
28512
- const callee = stripParenExpression(expression.callee);
28513
- if (!isNodeOfType(callee, "Identifier")) return false;
28514
- if (context.scopes.symbolFor(callee)?.kind !== "import") return false;
28515
- const importedName = getImportedNameFromModule(expression, callee.name, "next/navigation");
28516
- return importedName === "redirect" || importedName === "permanentRedirect";
28517
- };
28518
- const isRedirectOnlyDefaultExport = (programNode, context) => {
28519
- const defaultExport = findExportedValue(programNode, "default");
28520
- if (!defaultExport || !isFunctionLike$1(defaultExport)) return false;
28521
- if (!isNodeOfType(defaultExport.body, "BlockStatement")) return isNextNavigationRedirectCall(defaultExport.body, context);
28522
- if (defaultExport.body.body.length !== 1) return false;
28523
- const onlyStatement = defaultExport.body.body[0];
28524
- if (isNodeOfType(onlyStatement, "ExpressionStatement")) return isNextNavigationRedirectCall(onlyStatement.expression, context);
28525
- if (isNodeOfType(onlyStatement, "ReturnStatement") && onlyStatement.argument) return isNextNavigationRedirectCall(onlyStatement.argument, context);
28526
- return false;
28527
- };
28528
28351
  const nextjsMissingMetadata = defineRule({
28529
28352
  id: "nextjs-missing-metadata",
28530
28353
  title: "Page missing metadata for search previews",
@@ -28544,7 +28367,6 @@ const nextjsMissingMetadata = defineRule({
28544
28367
  if (isNodeOfType(declaration, "FunctionDeclaration")) return declaration.id?.name === "generateMetadata";
28545
28368
  return (statement.specifiers ?? []).some((specifier) => isNodeOfType(specifier, "ExportSpecifier") && isNodeOfType(specifier.exported, "Identifier") && METADATA_EXPORT_NAMES.includes(specifier.exported.name));
28546
28369
  })) return;
28547
- if (isRedirectOnlyDefaultExport(programNode, context)) return;
28548
28370
  if (hasAncestorMetadataLayout(context.filename ?? "")) return;
28549
28371
  context.report({
28550
28372
  node: programNode,
@@ -34673,19 +34495,6 @@ const noCascadingSetState = defineRetiredRule({
34673
34495
  recommendation: "Retired: React batches synchronous state updates from one effect into the same follow-up commit, so setter count does not prove repeated redraws."
34674
34496
  });
34675
34497
  //#endregion
34676
- //#region src/plugin/utils/get-react-use-callback-call.ts
34677
- const getReactUseCallbackCall = (expression, scopes) => {
34678
- const identifier = stripParenExpression(expression);
34679
- if (!isNodeOfType(identifier, "Identifier")) return null;
34680
- const symbol = resolveConstIdentifierAlias(identifier, scopes);
34681
- if (symbol?.kind !== "const" || !symbol.initializer) return null;
34682
- const initializer = stripParenExpression(symbol.initializer);
34683
- return isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, "useCallback", scopes, {
34684
- allowGlobalReactNamespace: true,
34685
- resolveNamedAliases: true
34686
- }) ? initializer : null;
34687
- };
34688
- //#endregion
34689
34498
  //#region src/plugin/rules/state-and-effects/utils/create-state-trigger-reachability.ts
34690
34499
  const getRefCurrentSymbol = (node, context) => {
34691
34500
  const expression = stripParenExpression(node);
@@ -35214,8 +35023,19 @@ const isBuiltinNamespaceCallee = (callee) => {
35214
35023
  }
35215
35024
  return false;
35216
35025
  };
35026
+ const getReactUseCallbackSource = (reference, context) => {
35027
+ const identifier = reference.identifier;
35028
+ const symbol = resolveConstIdentifierAlias(identifier, context.scopes);
35029
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer) return null;
35030
+ const initializer = stripParenExpression(symbol.initializer);
35031
+ if (isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, "useCallback", context.scopes, {
35032
+ allowGlobalReactNamespace: true,
35033
+ resolveNamedAliases: true
35034
+ })) return initializer;
35035
+ return null;
35036
+ };
35217
35037
  const getDependencyStateRefs = (analysis, context, dependencyReference) => {
35218
- const useCallbackCall = getReactUseCallbackCall(dependencyReference.identifier, context.scopes);
35038
+ const useCallbackCall = getReactUseCallbackSource(dependencyReference, context);
35219
35039
  if (!useCallbackCall) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
35220
35040
  const dependencyList = useCallbackCall.arguments?.[1];
35221
35041
  if (!dependencyList || !isNodeOfType(dependencyList, "ArrayExpression")) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
@@ -36803,6 +36623,19 @@ const noDefaultProps = defineRule({
36803
36623
  } })
36804
36624
  });
36805
36625
  //#endregion
36626
+ //#region src/plugin/utils/is-component-function.ts
36627
+ const isFunctionAssignedToComponent = (functionNode) => {
36628
+ let cursor = functionNode.parent ?? null;
36629
+ while (isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
36630
+ if (isNodeOfType(cursor, "VariableDeclarator") && isNodeOfType(cursor.id, "Identifier") && isUppercaseName(cursor.id.name)) return true;
36631
+ return isNodeOfType(cursor, "ExportDefaultDeclaration");
36632
+ };
36633
+ const isComponentFunction$1 = (node) => {
36634
+ if (!isFunctionLike$1(node)) return false;
36635
+ if (isNodeOfType(node, "FunctionDeclaration")) return !node.id || node.id.name === "default" || isUppercaseName(node.id.name) || isNodeOfType(node.parent, "ExportDefaultDeclaration");
36636
+ return isFunctionAssignedToComponent(node);
36637
+ };
36638
+ //#endregion
36806
36639
  //#region src/plugin/utils/create-component-prop-stack-tracker.ts
36807
36640
  const extractDestructuredPropNames = (params) => {
36808
36641
  const propNames = /* @__PURE__ */ new Set();
@@ -38716,17 +38549,6 @@ const noDynamicImportPath = defineRule({
38716
38549
  })
38717
38550
  });
38718
38551
  //#endregion
38719
- //#region src/plugin/utils/get-require-call-source.ts
38720
- const getRequireCallSource = (expression) => {
38721
- const unwrappedExpression = stripParenExpression(expression);
38722
- if (isNodeOfType(unwrappedExpression, "MemberExpression")) return getRequireCallSource(unwrappedExpression.object);
38723
- if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
38724
- if (!isNodeOfType(unwrappedExpression.callee, "Identifier") || unwrappedExpression.callee.name !== "require") return null;
38725
- const [firstArgument] = unwrappedExpression.arguments ?? [];
38726
- if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
38727
- return typeof firstArgument.value === "string" ? firstArgument.value : null;
38728
- };
38729
- //#endregion
38730
38552
  //#region src/plugin/rules/state-and-effects/utils/is-cleanup-return.ts
38731
38553
  const ITERATOR_CALLBACK_METHOD_NAMES = new Set([
38732
38554
  "each",
@@ -38871,16 +38693,7 @@ const collectDependencyStateSymbolIds = (effectNode, stateSymbolIds, scopes) =>
38871
38693
  }
38872
38694
  return dependencyStateSymbolIds;
38873
38695
  };
38874
- const resolveSynchronouslyInvokedFunction = (expression, scopes) => {
38875
- const localFunction = resolveExactLocalFunction(expression, scopes);
38876
- if (localFunction) return localFunction;
38877
- const useCallbackCall = getReactUseCallbackCall(expression, scopes);
38878
- if (!useCallbackCall) return null;
38879
- const callback = useCallbackCall.arguments[0];
38880
- if (!callback || isNodeOfType(callback, "SpreadElement")) return null;
38881
- return resolveExactLocalFunction(callback, scopes);
38882
- };
38883
- const collectSynchronouslyInvokedFunctions = (effectCallback, scopes, includeStableCallbacks = true) => {
38696
+ const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
38884
38697
  const analysisFunctions = new Set([effectCallback]);
38885
38698
  const pendingFunctions = [effectCallback];
38886
38699
  while (pendingFunctions.length > 0) {
@@ -38888,9 +38701,9 @@ const collectSynchronouslyInvokedFunctions = (effectCallback, scopes, includeSta
38888
38701
  if (!currentFunction || !isFunctionLike$1(currentFunction)) continue;
38889
38702
  walkInsideStatementBlocks(currentFunction.body, (child) => {
38890
38703
  if (!isNodeOfType(child, "CallExpression")) return;
38891
- const invokedFunction = includeStableCallbacks ? resolveSynchronouslyInvokedFunction(child.callee, scopes) : resolveExactLocalFunction(child.callee, scopes);
38704
+ const invokedFunction = resolveExactLocalFunction(child.callee, scopes);
38892
38705
  if (!invokedFunction || analysisFunctions.has(invokedFunction)) return;
38893
- if (isFunctionLike$1(invokedFunction) && (invokedFunction.async || invokedFunction.generator)) return;
38706
+ if (isFunctionLike$1(invokedFunction) && invokedFunction.async) return;
38894
38707
  analysisFunctions.add(invokedFunction);
38895
38708
  pendingFunctions.push(invokedFunction);
38896
38709
  });
@@ -38903,51 +38716,7 @@ const visitSynchronousFunctionBodies = (analysisFunctions, visitor) => {
38903
38716
  walkInsideStatementBlocks(analysisFunction.body, visitor);
38904
38717
  }
38905
38718
  };
38906
- const getStateNameForSetterCall = (node, setterSymbolIdToStateName, scopes) => {
38907
- if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier")) return null;
38908
- const setterSymbol = resolveConstIdentifierAlias(node.callee, scopes, true);
38909
- return setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) ?? null : null;
38910
- };
38911
- const collectStateWriteAnalysisFunctions = (effectCallback, scopes, setterSymbolIdToStateName) => {
38912
- const ordinaryAnalysisFunctions = collectSynchronouslyInvokedFunctions(effectCallback, scopes, false);
38913
- const fullAnalysisFunctions = collectSynchronouslyInvokedFunctions(effectCallback, scopes);
38914
- const stableAnalysisFunctions = [...fullAnalysisFunctions].filter((analysisFunction) => !ordinaryAnalysisFunctions.has(analysisFunction));
38915
- if (stableAnalysisFunctions.length === 0) return ordinaryAnalysisFunctions;
38916
- let hasUnprovenObservableWork = stableAnalysisFunctions.some((analysisFunction) => {
38917
- if (!isFunctionLike$1(analysisFunction)) return false;
38918
- let hasExecutableDefault = false;
38919
- for (const parameter of analysisFunction.params) {
38920
- walkInsideStatementBlocks(parameter, (child) => {
38921
- 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;
38922
- });
38923
- if (hasExecutableDefault) return true;
38924
- }
38925
- return false;
38926
- });
38927
- const stableWrittenStateNames = /* @__PURE__ */ new Set();
38928
- const allWrittenStateNames = /* @__PURE__ */ new Set();
38929
- visitSynchronousFunctionBodies(fullAnalysisFunctions, (child) => {
38930
- const writtenStateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
38931
- if (writtenStateName) allWrittenStateNames.add(writtenStateName);
38932
- });
38933
- visitSynchronousFunctionBodies(new Set(stableAnalysisFunctions), (child) => {
38934
- if (hasUnprovenObservableWork) return;
38935
- if (isNodeOfType(child, "CallExpression")) {
38936
- const writtenStateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
38937
- if (writtenStateName) {
38938
- stableWrittenStateNames.add(writtenStateName);
38939
- return;
38940
- }
38941
- const invokedFunction = resolveSynchronouslyInvokedFunction(child.callee, scopes);
38942
- if (invokedFunction && fullAnalysisFunctions.has(invokedFunction)) return;
38943
- hasUnprovenObservableWork = true;
38944
- return;
38945
- }
38946
- 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;
38947
- });
38948
- return !hasUnprovenObservableWork && stableWrittenStateNames.size === 1 && allWrittenStateNames.size === 1 ? fullAnalysisFunctions : ordinaryAnalysisFunctions;
38949
- };
38950
- const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, visitedSymbolIds = /* @__PURE__ */ new Set(), additionalStateValues = /* @__PURE__ */ new Map()) => {
38719
+ const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
38951
38720
  const unwrappedExpression = stripParenExpression(expression);
38952
38721
  if (isNodeOfType(unwrappedExpression, "Literal")) {
38953
38722
  const literalValue = unwrappedExpression.value;
@@ -38955,49 +38724,46 @@ const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, vi
38955
38724
  return null;
38956
38725
  }
38957
38726
  if (isNodeOfType(unwrappedExpression, "Identifier")) {
38958
- const symbol = scopes.symbolFor(unwrappedExpression);
38959
- if (symbol?.id === stateSymbolId) return stateValue;
38960
- if (symbol && additionalStateValues.has(symbol.id)) return additionalStateValues.get(symbol.id) ?? null;
38727
+ if (scopes.symbolFor(unwrappedExpression)?.id === stateSymbolId) return stateValue;
38961
38728
  if (unwrappedExpression.name === "undefined" && scopes.isGlobalReference(unwrappedExpression)) return { value: void 0 };
38962
- if (unwrappedExpression.name === "NaN" && scopes.isGlobalReference(unwrappedExpression)) return { value: NaN };
38963
38729
  const immutableSymbol = scopes.symbolFor(unwrappedExpression);
38964
38730
  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;
38965
- return readStaticEffectValue(immutableSymbol.initializer, scopes, stateSymbolId, stateValue, new Set(visitedSymbolIds).add(immutableSymbol.id), additionalStateValues);
38731
+ return readStaticEffectValue(immutableSymbol.initializer, scopes, stateSymbolId, stateValue, new Set(visitedSymbolIds).add(immutableSymbol.id));
38966
38732
  }
38967
38733
  if (isNodeOfType(unwrappedExpression, "UnaryExpression")) {
38968
38734
  if (unwrappedExpression.operator === "void") return { value: void 0 };
38969
38735
  if (unwrappedExpression.operator !== "!") return null;
38970
- const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38736
+ const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds);
38971
38737
  return argumentValue ? { value: !argumentValue.value } : null;
38972
38738
  }
38973
38739
  if (isNodeOfType(unwrappedExpression, "CallExpression")) {
38974
38740
  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")) {
38975
- const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38741
+ const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds);
38976
38742
  return argumentValue ? { value: Boolean(argumentValue.value) } : null;
38977
38743
  }
38978
38744
  return null;
38979
38745
  }
38980
38746
  if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
38981
- const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38747
+ const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
38982
38748
  if (!leftValue) return null;
38983
38749
  if (unwrappedExpression.operator === "&&" && !leftValue.value) return leftValue;
38984
38750
  if (unwrappedExpression.operator === "||" && leftValue.value) return leftValue;
38985
38751
  if (unwrappedExpression.operator === "??" && leftValue.value !== null && leftValue.value !== void 0) return leftValue;
38986
- return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38752
+ return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
38987
38753
  }
38988
38754
  if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
38989
- const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38755
+ const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds);
38990
38756
  if (!testValue) return null;
38991
- return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38757
+ return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds);
38992
38758
  }
38993
38759
  if (isNodeOfType(unwrappedExpression, "MemberExpression") && unwrappedExpression.optional) {
38994
- const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38760
+ const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds);
38995
38761
  if (objectValue?.value === null || objectValue?.value === void 0) return { value: void 0 };
38996
38762
  return null;
38997
38763
  }
38998
38764
  if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
38999
- const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
39000
- const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38765
+ const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
38766
+ const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39001
38767
  if (!leftValue || !rightValue) return null;
39002
38768
  if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
39003
38769
  const areEqual = leftValue.value === rightValue.value;
@@ -39036,7 +38802,8 @@ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName
39036
38802
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
39037
38803
  if (!isNodeOfType(child, "CallExpression")) return;
39038
38804
  if (!isNodeOfType(child.callee, "Identifier")) return;
39039
- const stateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
38805
+ const setterSymbol = resolveConstIdentifierAlias(child.callee, scopes, true);
38806
+ const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
39040
38807
  if (!stateName) return;
39041
38808
  const writeInfo = stateWrites.get(stateName) ?? {
39042
38809
  values: /* @__PURE__ */ new Set(),
@@ -39052,27 +38819,27 @@ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName
39052
38819
  const isGlobalBooleanCall = (node, scopes) => {
39053
38820
  return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Boolean" && scopes.isGlobalReference(node.callee);
39054
38821
  };
39055
- const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map()) => {
38822
+ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes) => {
39056
38823
  let currentNode = workNode;
39057
38824
  while (currentNode.parent) {
39058
38825
  const parentNode = currentNode.parent;
39059
38826
  if (isFunctionLike$1(parentNode)) break;
39060
38827
  if (isNodeOfType(parentNode, "IfStatement")) {
39061
- const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
38828
+ const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
39062
38829
  if (testValue) {
39063
38830
  if (currentNode === parentNode.consequent && !testValue.value) return false;
39064
38831
  if (currentNode === parentNode.alternate && testValue.value) return false;
39065
38832
  }
39066
38833
  }
39067
38834
  if (isNodeOfType(parentNode, "ConditionalExpression")) {
39068
- const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
38835
+ const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
39069
38836
  if (testValue) {
39070
38837
  if (currentNode === parentNode.consequent && !testValue.value) return false;
39071
38838
  if (currentNode === parentNode.alternate && testValue.value) return false;
39072
38839
  }
39073
38840
  }
39074
38841
  if (isNodeOfType(parentNode, "LogicalExpression") && currentNode === parentNode.right) {
39075
- const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
38842
+ const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue);
39076
38843
  if (leftValue) {
39077
38844
  if (parentNode.operator === "&&" && !leftValue.value) return false;
39078
38845
  if (parentNode.operator === "||" && leftValue.value) return false;
@@ -39084,7 +38851,7 @@ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, s
39084
38851
  if (statementIndex >= 0) for (let index = 0; index < statementIndex; index += 1) {
39085
38852
  const earlierStatement = parentNode.body[index];
39086
38853
  if (!isNodeOfType(earlierStatement, "IfStatement") || earlierStatement.alternate || !statementAlwaysExits(earlierStatement.consequent)) continue;
39087
- if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues)?.value) return false;
38854
+ if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue)?.value) return false;
39088
38855
  }
39089
38856
  }
39090
38857
  currentNode = parentNode;
@@ -39094,93 +38861,24 @@ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, s
39094
38861
  const isReaderWorkNode = (node, analysisFunctions, scopes) => {
39095
38862
  if (isNodeOfType(node, "CallExpression")) {
39096
38863
  if (isGlobalBooleanCall(node, scopes)) return false;
39097
- const invokedFunction = resolveSynchronouslyInvokedFunction(node.callee, scopes);
38864
+ const invokedFunction = resolveExactLocalFunction(node.callee, scopes);
39098
38865
  return !invokedFunction || !analysisFunctions.has(invokedFunction);
39099
38866
  }
39100
38867
  return isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "ThrowStatement") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete";
39101
38868
  };
39102
- const mergeKnownStateValues = (additionalStateValues, parameterStateValues) => {
39103
- if (parameterStateValues.size === 0) return additionalStateValues;
39104
- const knownStateValues = new Map(additionalStateValues);
39105
- for (const [symbolId, value] of parameterStateValues) knownStateValues.set(symbolId, value);
39106
- return knownStateValues;
39107
- };
39108
- const buildInvokedFunctionParameterStateValues = (invokedFunction, invocation, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map(), callerParameterStateValues = /* @__PURE__ */ new Map()) => {
39109
- if (!isFunctionLike$1(invokedFunction)) return /* @__PURE__ */ new Map();
39110
- const parameterStateValues = /* @__PURE__ */ new Map();
39111
- const callerKnownStateValues = mergeKnownStateValues(additionalStateValues, callerParameterStateValues);
39112
- for (let parameterIndex = 0; parameterIndex < invokedFunction.params.length; parameterIndex += 1) {
39113
- const rawParameter = invokedFunction.params[parameterIndex];
39114
- const rawArgument = invocation.arguments[parameterIndex];
39115
- if (rawArgument && isNodeOfType(rawArgument, "SpreadElement")) break;
39116
- const parameter = isNodeOfType(rawParameter, "AssignmentPattern") ? rawParameter.left : rawParameter;
39117
- if (!isNodeOfType(parameter, "Identifier")) continue;
39118
- const parameterSymbol = scopes.symbolFor(parameter);
39119
- if (!parameterSymbol) continue;
39120
- let argumentValue = null;
39121
- if (rawArgument) {
39122
- argumentValue = readStaticEffectValue(rawArgument, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), callerKnownStateValues);
39123
- if (argumentValue !== null && argumentValue.value === void 0 && isNodeOfType(rawParameter, "AssignmentPattern")) {
39124
- const defaultKnownStateValues = mergeKnownStateValues(callerKnownStateValues, parameterStateValues);
39125
- argumentValue = readStaticEffectValue(rawParameter.right, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), defaultKnownStateValues);
39126
- }
39127
- } else if (isNodeOfType(rawParameter, "AssignmentPattern")) {
39128
- const defaultKnownStateValues = mergeKnownStateValues(callerKnownStateValues, parameterStateValues);
39129
- argumentValue = readStaticEffectValue(rawParameter.right, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), defaultKnownStateValues);
39130
- } else argumentValue = { value: void 0 };
39131
- if (argumentValue) parameterStateValues.set(parameterSymbol.id, argumentValue);
39132
- }
39133
- return parameterStateValues;
39134
- };
39135
- const haveEqualParameterStateValues = (leftValues, rightValues) => {
39136
- if (leftValues.size !== rightValues.size) return false;
39137
- for (const [symbolId, leftValue] of leftValues) {
39138
- const rightValue = rightValues.get(symbolId);
39139
- if (!rightValue || !Object.is(leftValue.value, rightValue.value)) return false;
39140
- }
39141
- return true;
39142
- };
39143
- const canReaderWorkRunForStateValues = (readerEffect, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map()) => {
39144
- const pendingFrames = [{
39145
- functionNode: readerEffect.callback,
39146
- parameterStateValues: /* @__PURE__ */ new Map()
39147
- }];
39148
- const visitedParameterStateValues = /* @__PURE__ */ new Map();
39149
- while (pendingFrames.length > 0) {
39150
- const frame = pendingFrames.pop();
39151
- if (!frame || !isFunctionLike$1(frame.functionNode)) continue;
39152
- const functionParameterStateValues = visitedParameterStateValues.get(frame.functionNode) ?? [];
39153
- if (functionParameterStateValues.some((previousValues) => haveEqualParameterStateValues(previousValues, frame.parameterStateValues))) continue;
39154
- functionParameterStateValues.push(frame.parameterStateValues);
39155
- visitedParameterStateValues.set(frame.functionNode, functionParameterStateValues);
39156
- const knownStateValues = mergeKnownStateValues(additionalStateValues, frame.parameterStateValues);
38869
+ const canStateWriteReachReaderWork = (writeInfo, readerEffect, stateSymbolId, scopes) => {
38870
+ if (writeInfo.hasUnknownValue || stateSymbolId === null) return true;
38871
+ for (const writtenValue of writeInfo.values) {
38872
+ const stateValue = { value: writtenValue };
39157
38873
  let didFindReachableWork = false;
39158
- walkInsideStatementBlocks(frame.functionNode.body, (child) => {
39159
- if (didFindReachableWork) return;
39160
- if (isNodeOfType(child, "CallExpression")) {
39161
- const invokedFunction = resolveSynchronouslyInvokedFunction(child.callee, scopes);
39162
- if (invokedFunction && readerEffect.analysisFunctions.has(invokedFunction)) {
39163
- if (isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes, knownStateValues)) pendingFrames.push({
39164
- functionNode: invokedFunction,
39165
- parameterStateValues: buildInvokedFunctionParameterStateValues(invokedFunction, child, stateSymbolId, stateValue, scopes, additionalStateValues, frame.parameterStateValues)
39166
- });
39167
- return;
39168
- }
39169
- }
39170
- if (isReaderWorkNode(child, readerEffect.analysisFunctions, scopes) && isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes, knownStateValues)) didFindReachableWork = true;
38874
+ visitSynchronousFunctionBodies(readerEffect.analysisFunctions, (child) => {
38875
+ if (didFindReachableWork || !isReaderWorkNode(child, readerEffect.analysisFunctions, scopes)) return;
38876
+ if (isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes)) didFindReachableWork = true;
39171
38877
  });
39172
38878
  if (didFindReachableWork) return true;
39173
38879
  }
39174
38880
  return false;
39175
38881
  };
39176
- const canStateWriteReachReaderWork = (writtenStateName, writerEffect, readerEffect, stateSymbolIds, scopes) => {
39177
- const writeInfo = writerEffect.stateWrites.get(writtenStateName);
39178
- const stateSymbolId = stateSymbolIds.get(writtenStateName);
39179
- if (!writeInfo || stateSymbolId === void 0) return true;
39180
- if (writeInfo.hasUnknownValue) return true;
39181
- for (const writtenValue of writeInfo.values) if (canReaderWorkRunForStateValues(readerEffect, stateSymbolId, { value: writtenValue }, scopes)) return true;
39182
- return false;
39183
- };
39184
38882
  const EMPTY_CLEANUP_NAME_SET = /* @__PURE__ */ new Set();
39185
38883
  const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
39186
38884
  "clear",
@@ -39191,499 +38889,26 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
39191
38889
  "keys",
39192
38890
  "values"
39193
38891
  ]);
39194
- const EXTERNAL_SYNC_DIRECT_IMPORT_SOURCES = new Map([
39195
- ["fetch", new Set([
39196
- "cross-fetch",
39197
- "node-fetch",
39198
- "undici"
39199
- ])],
39200
- ["ky", new Set(["ky"])],
39201
- ["got", new Set(["got"])],
39202
- ["wretch", new Set(["wretch"])],
39203
- ["ofetch", new Set(["ofetch"])],
39204
- ["setTimeout", new Set([
39205
- "node:timers",
39206
- "node:timers/promises",
39207
- "timers",
39208
- "timers/promises"
39209
- ])],
39210
- ["setInterval", new Set([
39211
- "node:timers",
39212
- "node:timers/promises",
39213
- "timers",
39214
- "timers/promises"
39215
- ])]
39216
- ]);
39217
- const EXTERNAL_SYNC_DEFAULT_IMPORT_NAMES = new Map([
39218
- ["axios", "axios"],
39219
- ["cross-fetch", "fetch"],
39220
- ["got", "got"],
39221
- ["ky", "ky"],
39222
- ["node-fetch", "fetch"],
39223
- ["wretch", "wretch"]
39224
- ]);
39225
- const EXTERNAL_SYNC_HTTP_CLIENT_MODULE_SOURCES = new Set([
39226
- "axios",
39227
- "cross-fetch",
39228
- "got",
39229
- "ky",
39230
- "node-fetch",
39231
- "ofetch",
39232
- "undici",
39233
- "wretch"
39234
- ]);
39235
- const EXTERNAL_SYNC_GLOBAL_HTTP_CLIENT_NAMES = new Set([
39236
- "axios",
39237
- "got",
39238
- "ky",
39239
- "ofetch",
39240
- "wretch"
39241
- ]);
39242
- const EXTERNAL_SYNC_HTTP_METHOD_NAMES = new Set([
39243
- ...EXTERNAL_SYNC_AMBIGUOUS_HTTP_METHOD_NAMES,
39244
- "fetch",
39245
- "patch",
39246
- "post",
39247
- "put",
39248
- "request"
39249
- ]);
39250
- const TANSTACK_QUERY_CLIENT_MODULE_SOURCES = new Set(["@tanstack/query-core", "@tanstack/react-query"]);
39251
- const TANSTACK_QUERY_EXTERNAL_SYNC_METHOD_NAMES = new Set(["fetchQuery", "prefetchQuery"]);
39252
- const EXTERNAL_SYNC_RESOURCE_CONSTRUCTOR_NAMES = new Set([
39253
- ...EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS,
39254
- ...SOCKET_CONSTRUCTOR_NAMES_REQUIRING_CLEANUP,
39255
- "EventTarget",
39256
- "XMLHttpRequest"
39257
- ]);
39258
- const getDestructuredBindingDepth = (bindingIdentifier) => {
39259
- let bindingNode = bindingIdentifier;
39260
- let depth = 0;
39261
- while (true) {
39262
- if (isNodeOfType(bindingNode.parent, "AssignmentPattern") && bindingNode.parent.left === bindingNode) bindingNode = bindingNode.parent;
39263
- const property = bindingNode.parent;
39264
- if (!property || !isNodeOfType(property, "Property") || property.value !== bindingNode || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return depth;
39265
- depth += 1;
39266
- bindingNode = property.parent;
39267
- }
39268
- };
39269
- const isMutatedMemberExpression = (rawMemberExpression) => {
39270
- let expression = findTransparentExpressionRoot(rawMemberExpression);
39271
- while (expression.parent) {
39272
- const parent = expression.parent;
39273
- if (isNodeOfType(parent, "MemberExpression") && parent.object === expression) {
39274
- expression = findTransparentExpressionRoot(parent);
39275
- continue;
39276
- }
39277
- if (isNodeOfType(parent, "Property") && parent.value === expression && isNodeOfType(parent.parent, "ObjectPattern")) {
39278
- expression = findTransparentExpressionRoot(parent);
39279
- continue;
39280
- }
39281
- if (isNodeOfType(parent, "ObjectPattern") && parent.properties.some((property) => property === expression)) {
39282
- expression = findTransparentExpressionRoot(parent);
39283
- continue;
39284
- }
39285
- if (isNodeOfType(parent, "ArrayPattern") && parent.elements.some((element) => element === expression)) {
39286
- expression = findTransparentExpressionRoot(parent);
39287
- continue;
39288
- }
39289
- if (isNodeOfType(parent, "RestElement") && parent.argument === expression || isNodeOfType(parent, "AssignmentPattern") && parent.left === expression) {
39290
- expression = findTransparentExpressionRoot(parent);
39291
- continue;
39292
- }
39293
- break;
39294
- }
39295
- const parent = expression.parent;
39296
- 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);
39297
- };
39298
- const isReactHookDependencyArgument = (expression, scopes) => {
39299
- const call = expression.parent;
39300
- return Boolean(isNodeOfType(call, "CallExpression") && call.arguments[1] === expression && isReactApiCall(call, HOOKS_WITH_DEPS, scopes, {
39301
- allowGlobalReactNamespace: true,
39302
- allowUnboundBareCalls: true,
39303
- resolveNamedAliases: true
39304
- }));
39305
- };
39306
- const isPlainDependencyObjectExpression = (expression) => isNodeOfType(expression, "ObjectExpression") && expression.properties.every((property) => isNodeOfType(property, "Property") && property.kind === "init" && !property.computed && !property.method);
39307
- const getContainingReactDependencyValue = (rawExpression, containerKind) => {
39308
- const expression = findTransparentExpressionRoot(rawExpression);
39309
- const parent = expression.parent;
39310
- if (isNodeOfType(parent, "ArrayExpression")) return {
39311
- expression: parent,
39312
- containerKind: "array"
39313
- };
39314
- if (containerKind === "array" && isNodeOfType(parent, "SpreadElement") && parent.argument === expression && isNodeOfType(parent.parent, "ArrayExpression")) return {
39315
- expression: parent.parent,
39316
- containerKind: "array"
39317
- };
39318
- if (isNodeOfType(parent, "Property") && (parent.value === expression || parent.shorthand && parent.key === expression) && parent.parent && isPlainDependencyObjectExpression(parent.parent)) return {
39319
- expression: parent.parent,
39320
- containerKind: "object"
39321
- };
39322
- return null;
39323
- };
39324
- const getFullArrayRestCopySymbol = (initializer, scopes) => {
39325
- const declaration = initializer.parent;
39326
- if (!isNodeOfType(declaration, "VariableDeclarator") || declaration.init !== initializer || !isNodeOfType(declaration.id, "ArrayPattern") || declaration.id.elements.length !== 1) return null;
39327
- const restElement = declaration.id.elements[0];
39328
- if (!isNodeOfType(restElement, "RestElement") || !isNodeOfType(restElement.argument, "Identifier")) return null;
39329
- const symbol = scopes.symbolFor(restElement.argument);
39330
- return symbol?.kind === "const" && symbol.declarationNode === declaration && symbol.references.every((reference) => reference.flag === "read") ? symbol : null;
39331
- };
39332
- const isReactDependencyArrayReference = (initialExpression, scopes) => {
39333
- const pendingPaths = [{
39334
- expression: initialExpression,
39335
- containerKind: null
39336
- }];
39337
- const visitedExpressions = /* @__PURE__ */ new Set();
39338
- let didFindReactDependencyArgument = false;
39339
- while (pendingPaths.length > 0) {
39340
- const pendingPath = pendingPaths.pop();
39341
- if (!pendingPath) continue;
39342
- const expression = findTransparentExpressionRoot(pendingPath.expression);
39343
- if (visitedExpressions.has(expression)) continue;
39344
- visitedExpressions.add(expression);
39345
- if (pendingPath.containerKind === "array" && isReactHookDependencyArgument(expression, scopes)) {
39346
- didFindReactDependencyArgument = true;
39347
- continue;
39348
- }
39349
- const containingValue = getContainingReactDependencyValue(expression, pendingPath.containerKind);
39350
- if (containingValue) {
39351
- pendingPaths.push(containingValue);
39352
- continue;
39353
- }
39354
- const declaration = expression.parent;
39355
- if (isNodeOfType(declaration, "VariableDeclarator") && declaration.init === expression && isNodeOfType(declaration.id, "Identifier")) {
39356
- const symbol = scopes.symbolFor(declaration.id);
39357
- if (!symbol || getDirectUnreassignedInitializer(symbol) !== expression) return false;
39358
- if (symbol.references.length === 0) return false;
39359
- for (const reference of symbol.references) pendingPaths.push({
39360
- expression: reference.identifier,
39361
- containerKind: pendingPath.containerKind
39362
- });
39363
- continue;
39364
- }
39365
- if (pendingPath.containerKind === "array") {
39366
- const restCopySymbol = getFullArrayRestCopySymbol(expression, scopes);
39367
- if (restCopySymbol) {
39368
- if (restCopySymbol.references.length === 0) return false;
39369
- for (const reference of restCopySymbol.references) pendingPaths.push({
39370
- expression: reference.identifier,
39371
- containerKind: "array"
39372
- });
39373
- continue;
39374
- }
39375
- }
39376
- return false;
39377
- }
39378
- return didFindReactDependencyArgument;
39379
- };
39380
- const hasSafeReceiverAliases = (rootSymbols, scopes) => {
39381
- const pendingSymbols = [...rootSymbols];
39382
- const visitedSymbolIds = /* @__PURE__ */ new Set();
39383
- while (pendingSymbols.length > 0) {
39384
- const symbol = pendingSymbols.pop();
39385
- if (!symbol || visitedSymbolIds.has(symbol.id)) continue;
39386
- visitedSymbolIds.add(symbol.id);
39387
- for (const reference of symbol.references) {
39388
- const expression = findTransparentExpressionRoot(reference.identifier);
39389
- const container = expression.parent;
39390
- if (isReactDependencyArrayReference(expression, scopes)) continue;
39391
- if (isNodeOfType(container, "MemberExpression") && container.object === expression) {
39392
- if (isMutatedMemberExpression(container)) return false;
39393
- continue;
39394
- }
39395
- if (isNodeOfType(container, "CallExpression") && container.callee === expression) continue;
39396
- if (isNodeOfType(container, "VariableDeclarator") && container.init === expression && isNodeOfType(container.id, "ObjectPattern")) continue;
39397
- if (isNodeOfType(container, "AssignmentExpression") && container.right === expression && isNodeOfType(container.left, "ObjectPattern")) continue;
39398
- if (isNodeOfType(container, "VariableDeclarator") && container.init === expression && isNodeOfType(container.id, "Identifier")) {
39399
- const aliasSymbol = scopes.symbolFor(container.id);
39400
- const aliasInitializer = aliasSymbol && getDirectUnreassignedInitializer(aliasSymbol);
39401
- if (!aliasSymbol || aliasInitializer !== expression) return false;
39402
- pendingSymbols.push(aliasSymbol);
39403
- continue;
39404
- }
39405
- return false;
38892
+ const isFunctionShapedReturn = (returnedValue, setterToStateName, setterSymbolIdToStateName, scopes, isExplicitReturnStatement) => {
38893
+ if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
38894
+ if (isNodeOfType(returnedValue, "CallExpression")) {
38895
+ if (isNodeOfType(returnedValue.callee, "Identifier")) {
38896
+ const setterSymbol = resolveConstIdentifierAlias(returnedValue.callee, scopes, true);
38897
+ if (setterToStateName.has(returnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
38898
+ if (isSetterIdentifier(returnedValue.callee.name)) return true;
39406
38899
  }
38900
+ return isCleanupReturn(returnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
39407
38901
  }
39408
- return true;
39409
- };
39410
- const hasProvenGlobalNamespaceReference = (expression, namespaceNames, scopes) => {
39411
- for (const namespaceName of namespaceNames) if (isProvenGlobalNamespaceReference(expression, namespaceName, scopes)) return true;
38902
+ if (isNodeOfType(returnedValue, "Identifier")) return true;
39412
38903
  return false;
39413
38904
  };
39414
- const getTypeScriptImportEqualsSource = (symbol) => {
39415
- if (symbol.kind !== "ts-import-equals" || !isNodeOfType(symbol.declarationNode, "TSImportEqualsDeclaration")) return null;
39416
- const moduleReference = symbol.declarationNode.moduleReference;
39417
- if (!isNodeOfType(moduleReference, "TSExternalModuleReference") || !isNodeOfType(moduleReference.expression, "Literal")) return null;
39418
- return typeof moduleReference.expression.value === "string" ? moduleReference.expression.value : null;
39419
- };
39420
- const getUnshadowedRequireSource = (rawExpression, scopes) => {
39421
- let expression = stripParenExpression(rawExpression);
39422
- while (isNodeOfType(expression, "MemberExpression")) expression = stripParenExpression(expression.object);
39423
- if (!isNodeOfType(expression, "CallExpression") || !isNodeOfType(expression.callee, "Identifier") || expression.callee.name !== "require" || !scopes.isGlobalReference(expression.callee)) return null;
39424
- return getRequireCallSource(rawExpression);
39425
- };
39426
- const resolveExternalModuleBinding = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set(), ignoreCommonJsMutation = false) => {
39427
- let expression = stripParenExpression(rawExpression);
39428
- let outermostExportedName = null;
39429
- let exportedPropertyDepth = 0;
39430
- let resolvedBinding = null;
39431
- let isCommonJsModuleObject = false;
39432
- const traversedSymbolIds = new Set(visitedSymbolIds);
39433
- const commonJsModuleObjectSymbols = /* @__PURE__ */ new Set();
39434
- while (true) {
39435
- if (isNodeOfType(expression, "MemberExpression")) {
39436
- const propertyName = getStaticPropertyName(expression);
39437
- if (propertyName === null) return null;
39438
- outermostExportedName ??= propertyName;
39439
- exportedPropertyDepth += 1;
39440
- expression = stripParenExpression(expression.object);
39441
- continue;
39442
- }
39443
- const requireSource = getUnshadowedRequireSource(expression, scopes);
39444
- if (requireSource !== null) {
39445
- resolvedBinding = {
39446
- source: requireSource,
39447
- exportedName: null,
39448
- isModuleObject: true
39449
- };
39450
- isCommonJsModuleObject = true;
39451
- break;
39452
- }
39453
- if (!isNodeOfType(expression, "Identifier")) return null;
39454
- const symbol = scopes.symbolFor(expression);
39455
- if (!symbol || traversedSymbolIds.has(symbol.id)) return null;
39456
- if (symbol.kind === "import") {
39457
- const importDeclaration = symbol.declarationNode.parent;
39458
- if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration") || typeof importDeclaration.source.value !== "string") return null;
39459
- if (isNodeOfType(symbol.declarationNode, "ImportSpecifier")) resolvedBinding = {
39460
- source: importDeclaration.source.value,
39461
- exportedName: getImportedName(symbol.declarationNode) ?? null,
39462
- isModuleObject: false
39463
- };
39464
- else if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) resolvedBinding = {
39465
- source: importDeclaration.source.value,
39466
- exportedName: "default",
39467
- isModuleObject: false
39468
- };
39469
- else if (isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")) resolvedBinding = {
39470
- source: importDeclaration.source.value,
39471
- exportedName: null,
39472
- isModuleObject: true
39473
- };
39474
- break;
39475
- }
39476
- const importEqualsSource = getTypeScriptImportEqualsSource(symbol);
39477
- if (importEqualsSource !== null) {
39478
- resolvedBinding = {
39479
- source: importEqualsSource,
39480
- exportedName: null,
39481
- isModuleObject: true
39482
- };
39483
- isCommonJsModuleObject = true;
39484
- commonJsModuleObjectSymbols.add(symbol);
39485
- break;
39486
- }
39487
- const directInitializer = getDirectUnreassignedInitializer(symbol);
39488
- const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
39489
- const initializer = directInitializer ?? (destructuredPropertyName !== null && symbol.kind === "const" && isNodeOfType(symbol.declarationNode, "VariableDeclarator") ? symbol.declarationNode.init : null);
39490
- if (!initializer) return null;
39491
- traversedSymbolIds.add(symbol.id);
39492
- outermostExportedName ??= destructuredPropertyName;
39493
- if (destructuredPropertyName !== null) exportedPropertyDepth += getDestructuredBindingDepth(symbol.bindingIdentifier);
39494
- const unwrappedInitializer = stripParenExpression(initializer);
39495
- if (destructuredPropertyName === null && !isNodeOfType(unwrappedInitializer, "MemberExpression")) commonJsModuleObjectSymbols.add(symbol);
39496
- expression = unwrappedInitializer;
39497
- }
39498
- if (!resolvedBinding) return null;
39499
- if (!ignoreCommonJsMutation && isCommonJsModuleObject && !hasSafeReceiverAliases(commonJsModuleObjectSymbols, scopes)) return null;
39500
- if (outermostExportedName === null) return resolvedBinding;
39501
- if (!resolvedBinding.isModuleObject || exportedPropertyDepth !== 1) return null;
39502
- return {
39503
- source: resolvedBinding.source,
39504
- exportedName: outermostExportedName,
39505
- isModuleObject: false
39506
- };
39507
- };
39508
- const getCanonicalExternalSyncExportName = (binding) => {
39509
- if (binding.exportedName && binding.exportedName !== "default") return binding.exportedName;
39510
- return EXTERNAL_SYNC_DEFAULT_IMPORT_NAMES.get(binding.source) ?? null;
39511
- };
39512
- const isKnownExternalSyncDirectModuleBinding = (binding) => {
39513
- const exportedName = getCanonicalExternalSyncExportName(binding);
39514
- return Boolean(exportedName && EXTERNAL_SYNC_DIRECT_IMPORT_SOURCES.get(exportedName)?.has(binding.source));
39515
- };
39516
- const DEFINITELY_NON_FUNCTION_GLOBAL_CALL_NAMES = new Set([
39517
- "Array",
39518
- "BigInt",
39519
- "Boolean",
39520
- "Date",
39521
- "Number",
39522
- "String",
39523
- "Symbol"
39524
- ]);
39525
- const DEFINITELY_NON_FUNCTION_GLOBAL_CONSTRUCTOR_NAMES = new Set([
39526
- "Array",
39527
- "Boolean",
39528
- "Date",
39529
- "Map",
39530
- "Number",
39531
- "Promise",
39532
- "RegExp",
39533
- "Set",
39534
- "String",
39535
- "URL",
39536
- "URLSearchParams",
39537
- "WeakMap",
39538
- "WeakSet"
39539
- ]);
39540
- const PROMISE_STATIC_RESULT_METHOD_NAMES = new Set([
39541
- "all",
39542
- "allSettled",
39543
- "any",
39544
- "race",
39545
- "reject",
39546
- "resolve",
39547
- "withResolvers"
39548
- ]);
39549
- const returnsOnlyNonCleanupValues = (functionNode, setterToStateName, scopes, visitedFunctions = /* @__PURE__ */ new Set()) => {
39550
- if (!isFunctionLike$1(functionNode) || functionNode.async || functionNode.generator || visitedFunctions.has(functionNode)) return false;
39551
- const nextVisitedFunctions = new Set(visitedFunctions).add(functionNode);
39552
- const isNonCleanupValue = (returnValue) => {
39553
- const expression = stripParenExpression(returnValue);
39554
- if (isNodeOfType(expression, "Identifier") && expression.name === "undefined" && scopes.isGlobalReference(expression)) return true;
39555
- 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;
39556
- if (isNodeOfType(expression, "ConditionalExpression")) return isNonCleanupValue(expression.consequent) && isNonCleanupValue(expression.alternate);
39557
- if (isNodeOfType(expression, "LogicalExpression")) return isNonCleanupValue(expression.left) && isNonCleanupValue(expression.right);
39558
- if (isNodeOfType(expression, "SequenceExpression")) {
39559
- const finalExpression = expression.expressions.at(-1);
39560
- return Boolean(finalExpression && isNonCleanupValue(finalExpression));
39561
- }
39562
- if (isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && DEFINITELY_NON_FUNCTION_GLOBAL_CONSTRUCTOR_NAMES.has(expression.callee.name) && scopes.isGlobalReference(expression.callee)) return true;
39563
- if (!isNodeOfType(expression, "CallExpression")) return false;
39564
- if (isNodeOfType(expression.callee, "Identifier") && DEFINITELY_NON_FUNCTION_GLOBAL_CALL_NAMES.has(expression.callee.name) && scopes.isGlobalReference(expression.callee)) return true;
39565
- 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;
39566
- if (isNodeOfType(expression.callee, "Identifier") && setterToStateName.has(expression.callee.name)) return true;
39567
- const invokedFunction = resolveSynchronouslyInvokedFunction(expression.callee, scopes);
39568
- return Boolean(invokedFunction && returnsOnlyNonCleanupValues(invokedFunction, setterToStateName, scopes, nextVisitedFunctions));
39569
- };
39570
- if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNonCleanupValue(functionNode.body);
39571
- let returnsOnlyNonCleanup = true;
39572
- walkAst(functionNode.body, (child) => {
39573
- if (!returnsOnlyNonCleanup) return false;
39574
- if (child !== functionNode.body && isFunctionLike$1(child)) return false;
39575
- if (!isNodeOfType(child, "ReturnStatement")) return;
39576
- if (child.argument && !isNonCleanupValue(child.argument)) {
39577
- returnsOnlyNonCleanup = false;
39578
- return false;
39579
- }
39580
- });
39581
- return returnsOnlyNonCleanup;
39582
- };
39583
- const getResolvedFunctionCleanupReturnProof = (functionNode, scopes, visitedFunctions = /* @__PURE__ */ new Set()) => {
39584
- if (!isFunctionLike$1(functionNode) || functionNode.async || functionNode.generator || visitedFunctions.has(functionNode)) return {
39585
- hasCleanup: false,
39586
- isValid: false
39587
- };
39588
- const nextVisitedFunctions = new Set(visitedFunctions).add(functionNode);
39589
- const getCleanupReturnProof = (returnValue) => {
39590
- const expression = stripParenExpression(returnValue);
39591
- if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression")) return {
39592
- hasCleanup: true,
39593
- isValid: true
39594
- };
39595
- if (isNodeOfType(expression, "Identifier")) {
39596
- if (expression.name === "undefined" && scopes.isGlobalReference(expression)) return {
39597
- hasCleanup: false,
39598
- isValid: true
39599
- };
39600
- const localFunction = resolveExactLocalFunction(expression, scopes);
39601
- return {
39602
- hasCleanup: Boolean(localFunction),
39603
- isValid: Boolean(localFunction)
39604
- };
39605
- }
39606
- if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return {
39607
- hasCleanup: false,
39608
- isValid: true
39609
- };
39610
- if (isNodeOfType(expression, "ConditionalExpression")) {
39611
- const consequentProof = getCleanupReturnProof(expression.consequent);
39612
- const alternateProof = getCleanupReturnProof(expression.alternate);
39613
- return {
39614
- hasCleanup: consequentProof.hasCleanup || alternateProof.hasCleanup,
39615
- isValid: consequentProof.isValid && alternateProof.isValid
39616
- };
39617
- }
39618
- if (isNodeOfType(expression, "LogicalExpression")) {
39619
- const leftProof = getCleanupReturnProof(expression.left);
39620
- const rightProof = getCleanupReturnProof(expression.right);
39621
- return {
39622
- hasCleanup: leftProof.hasCleanup || rightProof.hasCleanup,
39623
- isValid: leftProof.isValid && rightProof.isValid
39624
- };
39625
- }
39626
- if (isNodeOfType(expression, "SequenceExpression")) {
39627
- const finalExpression = expression.expressions.at(-1);
39628
- return finalExpression ? getCleanupReturnProof(finalExpression) : {
39629
- hasCleanup: false,
39630
- isValid: false
39631
- };
39632
- }
39633
- if (!isNodeOfType(expression, "CallExpression")) return {
39634
- hasCleanup: false,
39635
- isValid: false
39636
- };
39637
- const invokedFunction = resolveSynchronouslyInvokedFunction(expression.callee, scopes);
39638
- return invokedFunction ? getResolvedFunctionCleanupReturnProof(invokedFunction, scopes, nextVisitedFunctions) : {
39639
- hasCleanup: false,
39640
- isValid: false
39641
- };
39642
- };
39643
- if (!isNodeOfType(functionNode.body, "BlockStatement")) return getCleanupReturnProof(functionNode.body);
39644
- let cleanupReturnProof = {
39645
- hasCleanup: false,
39646
- isValid: true
39647
- };
39648
- walkAst(functionNode.body, (child) => {
39649
- if (!cleanupReturnProof.isValid) return false;
39650
- if (child !== functionNode.body && isFunctionLike$1(child)) return false;
39651
- if (!isNodeOfType(child, "ReturnStatement")) return;
39652
- if (!child.argument) return;
39653
- const returnProof = getCleanupReturnProof(child.argument);
39654
- if (!returnProof.isValid) {
39655
- cleanupReturnProof = {
39656
- hasCleanup: false,
39657
- isValid: false
39658
- };
39659
- return false;
39660
- }
39661
- cleanupReturnProof.hasCleanup ||= returnProof.hasCleanup;
39662
- });
39663
- return cleanupReturnProof;
39664
- };
39665
- const isFunctionShapedReturn = (returnedValue, setterToStateName, setterSymbolIdToStateName, scopes, isExplicitReturnStatement) => {
39666
- const unwrappedReturnedValue = stripParenExpression(returnedValue);
39667
- if (isNodeOfType(unwrappedReturnedValue, "ArrowFunctionExpression") || isNodeOfType(unwrappedReturnedValue, "FunctionExpression")) return true;
39668
- if (isNodeOfType(unwrappedReturnedValue, "CallExpression")) {
39669
- if (isNodeOfType(unwrappedReturnedValue.callee, "Identifier")) {
39670
- const setterSymbol = resolveConstIdentifierAlias(unwrappedReturnedValue.callee, scopes, true);
39671
- if (setterToStateName.has(unwrappedReturnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
39672
- }
39673
- const invokedFunction = resolveSynchronouslyInvokedFunction(unwrappedReturnedValue.callee, scopes);
39674
- if (invokedFunction && returnsOnlyNonCleanupValues(invokedFunction, setterToStateName, scopes)) return false;
39675
- if (invokedFunction) {
39676
- const cleanupReturnProof = getResolvedFunctionCleanupReturnProof(invokedFunction, scopes);
39677
- if (cleanupReturnProof.isValid) return cleanupReturnProof.hasCleanup;
39678
- }
39679
- if (!invokedFunction && isNodeOfType(unwrappedReturnedValue.callee, "Identifier") && isSetterIdentifier(unwrappedReturnedValue.callee.name)) return true;
39680
- return isCleanupReturn(unwrappedReturnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
39681
- }
39682
- if (isNodeOfType(unwrappedReturnedValue, "Identifier")) return true;
38905
+ const STORAGE_GLOBAL_NAMES = new Set(["localStorage", "sessionStorage"]);
38906
+ const isBrowserStorageReceiver = (receiver) => {
38907
+ if (!receiver) return false;
38908
+ if (isNodeOfType(receiver, "Identifier")) return STORAGE_GLOBAL_NAMES.has(receiver.name);
38909
+ if (isNodeOfType(receiver, "MemberExpression")) return isNodeOfType(receiver.property, "Identifier") && STORAGE_GLOBAL_NAMES.has(receiver.property.name);
39683
38910
  return false;
39684
38911
  };
39685
- const STORAGE_GLOBAL_NAMES = ["localStorage", "sessionStorage"];
39686
- const isBrowserStorageReceiver = (receiver, scopes) => Boolean(receiver && STORAGE_GLOBAL_NAMES.some((storageName) => isProvenGlobalNamespaceReference(receiver, storageName, scopes)));
39687
38912
  const STORAGE_HOOK_PATTERN = /^use\w*Storage/i;
39688
38913
  const collectStorageHookSetterNames = (componentBody) => {
39689
38914
  const setterNames = /* @__PURE__ */ new Set();
@@ -39715,16 +38940,23 @@ const callsOpaqueExternalSetter = (analysisFunctions, setterToStateName) => {
39715
38940
  });
39716
38941
  return didFindOpaqueSetterCall;
39717
38942
  };
39718
- const isReactUseRefCall = (expression, scopes) => isNodeOfType(expression, "CallExpression") && isReactApiCall(expression, "useRef", scopes, {
38943
+ const isReactRefCall = (expression, scopes) => isNodeOfType(expression, "CallExpression") && (isReactApiCall(expression, "useRef", scopes, {
39719
38944
  allowGlobalReactNamespace: true,
39720
38945
  allowUnboundBareCalls: true,
39721
38946
  resolveNamedAliases: true
39722
- });
39723
- const isReactRefCall = (expression, scopes) => isReactUseRefCall(expression, scopes) || isNodeOfType(expression, "CallExpression") && isReactApiCall(expression, "createRef", scopes, {
38947
+ }) || isReactApiCall(expression, "createRef", scopes, {
39724
38948
  allowGlobalReactNamespace: true,
39725
38949
  allowUnboundBareCalls: true,
39726
38950
  resolveNamedAliases: true
39727
- });
38951
+ }));
38952
+ const getDirectReactRefSymbol = (rawExpression, scopes) => {
38953
+ const expression = stripParenExpression(rawExpression);
38954
+ if (!isNodeOfType(expression, "Identifier")) return null;
38955
+ const symbol = scopes.symbolFor(expression);
38956
+ if (!symbol) return null;
38957
+ const initializer = getDirectUnreassignedInitializer(symbol);
38958
+ return initializer && isReactRefCall(stripParenExpression(initializer), scopes) ? symbol : null;
38959
+ };
39728
38960
  const isReactNativeJsxElement = (openingElement, scopes) => {
39729
38961
  if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
39730
38962
  const symbol = scopes.symbolFor(openingElement.name);
@@ -39732,33 +38964,19 @@ const isReactNativeJsxElement = (openingElement, scopes) => {
39732
38964
  return Boolean(symbol?.kind === "import" && importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react-native");
39733
38965
  };
39734
38966
  const isDirectHostJsxRef = (symbol, scopes) => {
39735
- let didFindHostRef = false;
39736
- const visitedSymbolIds = /* @__PURE__ */ new Set();
39737
- const pendingSymbols = [symbol];
39738
- while (pendingSymbols.length > 0) {
39739
- const currentSymbol = pendingSymbols.pop();
39740
- if (!currentSymbol || visitedSymbolIds.has(currentSymbol.id)) continue;
39741
- visitedSymbolIds.add(currentSymbol.id);
39742
- for (const reference of currentSymbol.references) {
39743
- const expression = findTransparentExpressionRoot(reference.identifier);
39744
- const container = expression.parent;
39745
- if (isNodeOfType(container, "MemberExpression") && container.object === expression && getStaticPropertyName(container) === "current") continue;
39746
- if (isNodeOfType(container, "VariableDeclarator") && container.init === expression && isNodeOfType(container.id, "Identifier")) {
39747
- const aliasSymbol = scopes.symbolFor(container.id);
39748
- const aliasInitializer = aliasSymbol && getDirectUnreassignedInitializer(aliasSymbol);
39749
- if (!aliasSymbol || aliasInitializer !== expression) return false;
39750
- pendingSymbols.push(aliasSymbol);
39751
- continue;
39752
- }
39753
- if (!container || !isNodeOfType(container, "JSXExpressionContainer") || container.expression !== expression) return false;
39754
- const attribute = container.parent;
39755
- if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
39756
- const openingElement = attribute.parent;
39757
- if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || !isProvenIntrinsicJsxElement(openingElement, scopes) && !isReactNativeJsxElement(openingElement, scopes)) return false;
39758
- didFindHostRef = true;
39759
- }
39760
- }
39761
- return didFindHostRef;
38967
+ let hostRefCount = 0;
38968
+ for (const reference of symbol.references) {
38969
+ const expression = findTransparentExpressionRoot(reference.identifier);
38970
+ const container = expression.parent;
38971
+ if (isNodeOfType(container, "MemberExpression") && container.object === expression && getStaticPropertyName(container) === "current") continue;
38972
+ if (!container || !isNodeOfType(container, "JSXExpressionContainer") || container.expression !== expression) return false;
38973
+ const attribute = container.parent;
38974
+ if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
38975
+ const openingElement = attribute.parent;
38976
+ if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || !isProvenIntrinsicJsxElement(openingElement, scopes) && !isReactNativeJsxElement(openingElement, scopes)) return false;
38977
+ hostRefCount += 1;
38978
+ }
38979
+ return hostRefCount > 0;
39762
38980
  };
39763
38981
  const isIntrinsicRefCallbackParameter = (expression, scopes) => {
39764
38982
  const identifier = stripParenExpression(expression);
@@ -39768,18 +38986,7 @@ const isIntrinsicRefCallbackParameter = (expression, scopes) => {
39768
38986
  const rawFirstParameter = callback.params?.[0];
39769
38987
  const firstParameter = isNodeOfType(rawFirstParameter, "AssignmentPattern") ? rawFirstParameter.left : rawFirstParameter;
39770
38988
  const symbol = scopes.symbolFor(identifier);
39771
- return Boolean(firstParameter && symbol?.bindingIdentifier === firstParameter && symbol.references.every((reference) => {
39772
- if (reference.flag !== "read") return false;
39773
- let referenceRoot = reference.identifier;
39774
- while (referenceRoot.parent) {
39775
- const parent = referenceRoot.parent;
39776
- if (isNodeOfType(parent, "AssignmentExpression")) return parent.left !== referenceRoot;
39777
- if (isNodeOfType(parent, "UpdateExpression")) return parent.argument !== referenceRoot;
39778
- if (isNodeOfType(parent, "ForInStatement") || isNodeOfType(parent, "ForOfStatement")) return parent.left !== referenceRoot;
39779
- referenceRoot = parent;
39780
- }
39781
- return true;
39782
- }));
38989
+ return Boolean(firstParameter && symbol?.bindingIdentifier === firstParameter);
39783
38990
  };
39784
38991
  const getDirectReactRefCall = (symbol, scopes) => {
39785
38992
  const initializer = getDirectUnreassignedInitializer(symbol);
@@ -39787,38 +38994,15 @@ const getDirectReactRefCall = (symbol, scopes) => {
39787
38994
  const expression = stripParenExpression(initializer);
39788
38995
  return isNodeOfType(expression, "CallExpression") && isReactRefCall(expression, scopes) ? expression : null;
39789
38996
  };
39790
- const isEmptyGlobalMapConstruction = (rawExpression, scopes) => {
39791
- const expression = stripParenExpression(rawExpression);
39792
- return isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && expression.callee.name === "Map" && scopes.isGlobalReference(expression.callee) && expression.arguments.length === 0;
39793
- };
39794
- const hasEmptyRefSentinelInitializer = (refCall, scopes) => {
39795
- if (!isReactUseRefCall(refCall, scopes) || refCall.arguments.length > 1) return false;
39796
- const [initialValue] = refCall.arguments;
39797
- return !initialValue || isNodeOfType(initialValue, "Literal") && initialValue.value === null || isNodeOfType(initialValue, "Identifier") && initialValue.name === "undefined" && scopes.isGlobalReference(initialValue);
39798
- };
39799
- const isDirectLazyEmptyMapInitialization = (currentExpression, symbol, scopes) => {
39800
- const assignment = currentExpression.parent;
39801
- if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.left !== currentExpression || assignment.operator !== "??=" || !isEmptyGlobalMapConstruction(assignment.right, scopes)) return false;
39802
- const refOwner = findEnclosingFunction$1(symbol.bindingIdentifier);
39803
- return refOwner !== null && findEnclosingFunction$1(assignment) === refOwner;
39804
- };
39805
38997
  const storesOnlyIntrinsicRefCallbackValues = (symbol, scopes) => {
39806
- const refCall = getDirectReactRefCall(symbol, scopes);
39807
- const initialValue = refCall?.arguments[0];
39808
- const hasDirectEmptyMapInitializer = Boolean(initialValue && isEmptyGlobalMapConstruction(initialValue, scopes));
39809
- const hasLazyEmptyMapInitializer = Boolean(refCall && hasEmptyRefSentinelInitializer(refCall, scopes));
39810
- if (!hasDirectEmptyMapInitializer && !hasLazyEmptyMapInitializer) return false;
38998
+ const initialValue = getDirectReactRefCall(symbol, scopes)?.arguments?.[0];
38999
+ if (!initialValue || !isNodeOfType(initialValue, "NewExpression") || !isNodeOfType(initialValue.callee, "Identifier") || initialValue.callee.name !== "Map" || !scopes.isGlobalReference(initialValue.callee) || initialValue.arguments.length !== 0) return false;
39811
39000
  let intrinsicValueWriteCount = 0;
39812
- let lazyEmptyMapInitializationCount = 0;
39813
39001
  for (const reference of symbol.references) {
39814
39002
  const identifier = findTransparentExpressionRoot(reference.identifier);
39815
39003
  const currentMember = identifier.parent;
39816
39004
  if (!isNodeOfType(currentMember, "MemberExpression") || currentMember.object !== identifier || getStaticPropertyName(currentMember) !== "current") return false;
39817
39005
  const currentExpression = findTransparentExpressionRoot(currentMember);
39818
- if (hasLazyEmptyMapInitializer && isDirectLazyEmptyMapInitialization(currentExpression, symbol, scopes)) {
39819
- lazyEmptyMapInitializationCount += 1;
39820
- continue;
39821
- }
39822
39006
  const methodMember = currentExpression.parent;
39823
39007
  if (!isNodeOfType(methodMember, "MemberExpression") || methodMember.object !== currentExpression) return false;
39824
39008
  const methodName = getStaticPropertyName(methodMember);
@@ -39831,7 +39015,7 @@ const storesOnlyIntrinsicRefCallbackValues = (symbol, scopes) => {
39831
39015
  if (!storedValue || isNodeOfType(storedValue, "SpreadElement") || !isIntrinsicRefCallbackParameter(storedValue, scopes)) return false;
39832
39016
  intrinsicValueWriteCount += 1;
39833
39017
  }
39834
- return intrinsicValueWriteCount > 0 && (hasDirectEmptyMapInitializer || lazyEmptyMapInitializationCount === 1);
39018
+ return intrinsicValueWriteCount > 0;
39835
39019
  };
39836
39020
  const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollectionValue = false, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
39837
39021
  const expression = stripParenExpression(rawExpression);
@@ -39845,11 +39029,7 @@ const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollecti
39845
39029
  }
39846
39030
  if (isNodeOfType(expression, "MemberExpression")) {
39847
39031
  if (getStaticPropertyName(expression) === "current") {
39848
- const symbol = resolveReactRefSymbol(expression, scopes, {
39849
- allowUnboundBareCalls: true,
39850
- includeCreateRef: true,
39851
- resolveNamedAliases: true
39852
- });
39032
+ const symbol = getDirectReactRefSymbol(expression.object, scopes);
39853
39033
  return Boolean(symbol && (isDirectHostJsxRef(symbol, scopes) || didReadCollectionValue && storesOnlyIntrinsicRefCallbackValues(symbol, scopes)));
39854
39034
  }
39855
39035
  return isDerivedFromProvenDomRefCurrent(expression.object, scopes, didReadCollectionValue, visitedSymbolIds);
@@ -39867,80 +39047,19 @@ const isCommittedDomSyncNode = (node, scopes) => {
39867
39047
  if (propertyName === null || !EXTERNAL_SYNC_DOM_MEMBER_METHOD_NAMES.has(propertyName)) return false;
39868
39048
  return isDerivedFromProvenDomRefCurrent(callee.object, scopes) || isProvenBrowserApiReceiver(callee.object, "dom-event-target", scopes);
39869
39049
  };
39870
- const isProvenHttpClientReceiver = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
39871
- const expression = stripParenExpression(rawExpression);
39872
- if (isNodeOfType(expression, "Identifier")) {
39873
- const receiverSymbol = scopes.symbolFor(expression);
39874
- if (receiverSymbol && !hasSafeReceiverAliases(new Set([receiverSymbol]), scopes)) return false;
39875
- }
39876
- if (hasProvenGlobalNamespaceReference(expression, EXTERNAL_SYNC_GLOBAL_HTTP_CLIENT_NAMES, scopes)) return true;
39877
- const moduleBinding = resolveExternalModuleBinding(expression, scopes);
39878
- 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;
39879
- if (isNodeOfType(expression, "CallExpression")) {
39880
- const callee = stripParenExpression(expression.callee);
39881
- return Boolean(isNodeOfType(callee, "MemberExpression") && getStaticPropertyName(callee) === "create" && isProvenHttpClientReceiver(callee.object, scopes, visitedSymbolIds));
39882
- }
39883
- if (!isNodeOfType(expression, "Identifier")) return false;
39884
- const symbol = scopes.symbolFor(expression);
39885
- if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
39886
- if (resolveExternalModuleBinding(expression, scopes, /* @__PURE__ */ new Set(), true)) return false;
39887
- const initializer = getDirectUnreassignedInitializer(symbol);
39888
- return Boolean(initializer && isProvenHttpClientReceiver(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id)));
39889
- };
39890
- const isProvenTanStackQueryClientReceiver = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
39891
- const expression = stripParenExpression(rawExpression);
39892
- if (isNodeOfType(expression, "Identifier")) {
39893
- const receiverSymbol = scopes.symbolFor(expression);
39894
- if (receiverSymbol && !hasSafeReceiverAliases(new Set([receiverSymbol]), scopes)) return false;
39895
- }
39896
- if (isNodeOfType(expression, "CallExpression")) {
39897
- const binding = resolveExternalModuleBinding(expression.callee, scopes);
39898
- return Boolean(binding && binding.source === "@tanstack/react-query" && binding.exportedName === "useQueryClient");
39899
- }
39900
- if (isNodeOfType(expression, "NewExpression")) {
39901
- const binding = resolveExternalModuleBinding(expression.callee, scopes);
39902
- return Boolean(binding && TANSTACK_QUERY_CLIENT_MODULE_SOURCES.has(binding.source) && binding.exportedName === "QueryClient");
39903
- }
39904
- if (!isNodeOfType(expression, "Identifier")) return false;
39905
- const symbol = scopes.symbolFor(expression);
39906
- if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
39907
- const initializer = getDirectUnreassignedInitializer(symbol);
39908
- return Boolean(initializer && isProvenTanStackQueryClientReceiver(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id)));
39909
- };
39910
- const isProvenExternalResourceReceiver = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
39911
- const expression = stripParenExpression(rawExpression);
39912
- if (isDerivedFromProvenDomRefCurrent(expression, scopes) || isProvenBrowserApiReceiver(expression, "dom-event-target", scopes) || isProvenBrowserApiReceiver(expression, "xml-http-request", scopes) || isProvenHttpClientReceiver(expression, scopes)) return true;
39913
- if (isNodeOfType(expression, "NewExpression")) return hasProvenGlobalNamespaceReference(expression.callee, EXTERNAL_SYNC_RESOURCE_CONSTRUCTOR_NAMES, scopes);
39914
- if (!isNodeOfType(expression, "Identifier")) return false;
39915
- const symbol = scopes.symbolFor(expression);
39916
- if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
39917
- const initializer = getDirectUnreassignedInitializer(symbol);
39918
- return Boolean(initializer && isProvenExternalResourceReceiver(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id)));
39919
- };
39920
- const isProvenExternalSyncDirectCallee = (callee, scopes) => {
39921
- if (hasProvenGlobalNamespaceReference(callee, EXTERNAL_SYNC_DIRECT_CALLEE_NAMES, scopes)) return true;
39922
- const moduleBinding = resolveExternalModuleBinding(callee, scopes);
39923
- return Boolean(moduleBinding && isKnownExternalSyncDirectModuleBinding(moduleBinding));
39924
- };
39925
- const isExternalSyncNode = (node, scopes) => {
39926
- if (isNodeOfType(node, "NewExpression")) return hasProvenGlobalNamespaceReference(node.callee, EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS, scopes);
39927
- if (isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression")) {
39928
- const mutationTarget = isNodeOfType(node, "AssignmentExpression") ? node.left : node.argument;
39929
- return isNodeOfType(mutationTarget, "MemberExpression") && getStaticPropertyName(mutationTarget) === "current" && Boolean(resolveReactRefSymbol(mutationTarget, scopes, {
39930
- allowUnboundBareCalls: true,
39931
- includeCreateRef: true,
39932
- resolveNamedAliases: true
39933
- }));
39934
- }
39050
+ const isExternalSyncNode = (node) => {
39051
+ if (isNodeOfType(node, "NewExpression")) return isNodeOfType(node.callee, "Identifier") && EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS.has(node.callee.name);
39052
+ if (isNodeOfType(node, "AssignmentExpression")) return isNodeOfType(node.left, "MemberExpression") && isNodeOfType(node.left.property, "Identifier") && node.left.property.name === "current";
39935
39053
  if (!isNodeOfType(node, "CallExpression")) return false;
39936
- if (isProvenExternalSyncDirectCallee(node.callee, scopes)) return true;
39054
+ if (isNodeOfType(node.callee, "Identifier")) return EXTERNAL_SYNC_DIRECT_CALLEE_NAMES.has(node.callee.name);
39937
39055
  if (!isNodeOfType(node.callee, "MemberExpression")) return false;
39938
39056
  const propertyName = getStaticPropertyName(node.callee);
39939
39057
  if (propertyName === null) return false;
39940
- if (isBrowserStorageReceiver(node.callee.object, scopes)) return true;
39941
- if (TANSTACK_QUERY_EXTERNAL_SYNC_METHOD_NAMES.has(propertyName)) return isProvenTanStackQueryClientReceiver(node.callee.object, scopes);
39942
- if (EXTERNAL_SYNC_HTTP_METHOD_NAMES.has(propertyName)) return isProvenHttpClientReceiver(node.callee.object, scopes);
39943
- return EXTERNAL_SYNC_MEMBER_METHOD_NAMES.has(propertyName) && isProvenExternalResourceReceiver(node.callee.object, scopes);
39058
+ if (EXTERNAL_SYNC_MEMBER_METHOD_NAMES.has(propertyName)) return true;
39059
+ if (isBrowserStorageReceiver(node.callee.object)) return true;
39060
+ if (!EXTERNAL_SYNC_AMBIGUOUS_HTTP_METHOD_NAMES.has(propertyName)) return false;
39061
+ const receiverRootName = getRootIdentifierName(node.callee.object);
39062
+ return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
39944
39063
  };
39945
39064
  const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, scopes, allowCommittedDomSync) => {
39946
39065
  if (!isFunctionLike$1(effectCallback)) return false;
@@ -39949,7 +39068,7 @@ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateNa
39949
39068
  } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, setterSymbolIdToStateName, scopes, true)) return true;
39950
39069
  let didFindExternalCall = false;
39951
39070
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
39952
- if (isExternalSyncNode(child, scopes) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
39071
+ if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
39953
39072
  });
39954
39073
  return didFindExternalCall;
39955
39074
  };
@@ -39971,11 +39090,11 @@ const noEffectChain = defineRule({
39971
39090
  setterToStateName.set(binding.setterName, binding.valueName);
39972
39091
  if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
39973
39092
  const stateIdentifier = binding.declarator.id.elements[0];
39974
- const setterIdentifier = binding.declarator.id.elements[1];
39975
39093
  if (isNodeOfType(stateIdentifier, "Identifier")) {
39976
39094
  const stateSymbol = context.scopes.symbolFor(stateIdentifier);
39977
39095
  if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
39978
39096
  }
39097
+ const setterIdentifier = binding.declarator.id.elements[1];
39979
39098
  if (isNodeOfType(setterIdentifier, "Identifier")) {
39980
39099
  const setterSymbol = context.scopes.symbolFor(setterIdentifier);
39981
39100
  if (setterSymbol) setterSymbolIdToStateName.set(setterSymbol.id, binding.valueName);
@@ -39988,11 +39107,10 @@ const noEffectChain = defineRule({
39988
39107
  const callback = getEffectCallback(effectCall, context.scopes);
39989
39108
  if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
39990
39109
  const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
39991
- const stateWrites = collectStateWritesInEffect(collectStateWriteAnalysisFunctions(callback, context.scopes, setterSymbolIdToStateName), setterSymbolIdToStateName, context.scopes);
39110
+ const stateWrites = collectStateWritesInEffect(analysisFunctions, setterSymbolIdToStateName, context.scopes);
39992
39111
  const writtenStateNames = new Set(stateWrites.keys());
39993
39112
  effectInfos.push({
39994
39113
  node: effectCall,
39995
- callback,
39996
39114
  dependencyStateSymbolIds: collectDependencyStateSymbolIds(effectCall, stateSymbolIdSet, context.scopes),
39997
39115
  stateWrites,
39998
39116
  analysisFunctions,
@@ -40009,10 +39127,10 @@ const noEffectChain = defineRule({
40009
39127
  if (readerEffect.isExternalSync) continue;
40010
39128
  if (readerEffect.dependencyStateSymbolIds.size === 0) continue;
40011
39129
  let chainedStateName = null;
40012
- for (const writtenName of writerEffect.stateWrites.keys()) {
39130
+ for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
40013
39131
  const writtenStateSymbolId = stateSymbolIds.get(writtenName);
40014
39132
  if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
40015
- if (!canStateWriteReachReaderWork(writtenName, writerEffect, readerEffect, stateSymbolIds, context.scopes)) continue;
39133
+ if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
40016
39134
  chainedStateName = writtenName;
40017
39135
  break;
40018
39136
  }
@@ -44797,6 +43915,16 @@ const noManyBooleanProps = defineRule({
44797
43915
  }
44798
43916
  });
44799
43917
  //#endregion
43918
+ //#region src/plugin/utils/is-global-match-media-call.ts
43919
+ const isGlobalMatchMediaCall = (node, scopes) => {
43920
+ if (!isNodeOfType(node, "CallExpression")) return false;
43921
+ const callee = stripParenExpression(node.callee);
43922
+ if (isNodeOfType(callee, "Identifier")) return callee.name === "matchMedia" && scopes.isGlobalReference(callee);
43923
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "matchMedia") return false;
43924
+ const receiver = stripParenExpression(callee.object);
43925
+ return isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && scopes.isGlobalReference(receiver);
43926
+ };
43927
+ //#endregion
44800
43928
  //#region src/plugin/rules/performance/no-match-media-in-state-initializer.ts
44801
43929
  const REACT_USE_STATE_OPTIONS = {
44802
43930
  allowGlobalReactNamespace: true,
@@ -47186,103 +46314,6 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
47186
46314
  "useVisibility",
47187
46315
  "useWindowSize"
47188
46316
  ]);
47189
- const EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES = new Set([
47190
- "useMatchMedia",
47191
- "useMediaQuery",
47192
- "useVisibility"
47193
- ]);
47194
- const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
47195
- const getImportedExternalSubscriptionHookName = (analysis, rawCallee) => {
47196
- const callee = stripParenExpression(rawCallee);
47197
- if (isNodeOfType(callee, "Identifier")) {
47198
- const calleeRef = getRef(analysis, callee);
47199
- if (!calleeRef || !isImportBindingRef(calleeRef)) return null;
47200
- const importBinding = getImportBindingForName(callee, callee.name);
47201
- if (!importBinding || importBinding.isNamespace) return null;
47202
- if (importBinding.exportedName && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(importBinding.exportedName)) return importBinding.exportedName;
47203
- return importBinding.exportedName === "default" && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name) ? callee.name : null;
47204
- }
47205
- if (!isNodeOfType(callee, "MemberExpression")) return null;
47206
- const hookName = getStaticMemberPropertyName(callee);
47207
- const namespaceIdentifier = stripParenExpression(callee.object);
47208
- if (!hookName || !EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(hookName) || !isNodeOfType(namespaceIdentifier, "Identifier")) return null;
47209
- const namespaceRef = getRef(analysis, namespaceIdentifier);
47210
- if (!namespaceRef || !isImportBindingRef(namespaceRef)) return null;
47211
- return getImportBindingForName(namespaceIdentifier, namespaceIdentifier.name)?.isNamespace ? hookName : null;
47212
- };
47213
- const isSafeExternalSubscriptionResultBinding = (bindingIdentifier, bindingPattern) => {
47214
- let current = bindingIdentifier;
47215
- let didCrossDestructuringBoundary = false;
47216
- while (current !== bindingPattern) {
47217
- const parent = current.parent;
47218
- if (!parent || isNodeOfType(parent, "AssignmentPattern") || isNodeOfType(parent, "RestElement")) return false;
47219
- if (isNodeOfType(parent, "Property")) {
47220
- if (parent.value !== current) return false;
47221
- didCrossDestructuringBoundary = true;
47222
- }
47223
- if (isNodeOfType(parent, "ArrayPattern")) didCrossDestructuringBoundary = true;
47224
- current = parent;
47225
- }
47226
- return didCrossDestructuringBoundary;
47227
- };
47228
- const getVariablesDefinedByDeclarator = (analysis, declarator) => analysis.scopeManager.scopes.flatMap((scope) => scope.variables).filter((variable) => variable.defs.some((definition) => definition.node === declarator));
47229
- const hasUnsafeExternalSubscriptionBindingUse = (analysis, variable, visitedVariables = /* @__PURE__ */ new Set()) => {
47230
- if (visitedVariables.has(variable)) return false;
47231
- visitedVariables.add(variable);
47232
- return variable.references.some((candidateReference) => {
47233
- if (candidateReference.init) return false;
47234
- if (candidateReference.isWrite()) return true;
47235
- let usageRoot = findTransparentExpressionRoot(candidateReference.identifier);
47236
- while (isNodeOfType(usageRoot.parent, "MemberExpression") && usageRoot.parent.object === usageRoot) usageRoot = findTransparentExpressionRoot(usageRoot.parent);
47237
- const usageParent = usageRoot.parent;
47238
- if (isNodeOfType(usageParent, "AssignmentExpression") && usageParent.left === usageRoot || isNodeOfType(usageParent, "UpdateExpression") && usageParent.argument === usageRoot || isNodeOfType(usageParent, "UnaryExpression") && usageParent.operator === "delete" && usageParent.argument === usageRoot) return true;
47239
- let usageAncestor = usageRoot.parent;
47240
- while (usageAncestor && !isNodeOfType(usageAncestor, "VariableDeclarator") && !isFunctionLike$1(usageAncestor) && !isNodeOfType(usageAncestor, "Program")) usageAncestor = usageAncestor.parent;
47241
- if (isNodeOfType(usageAncestor, "VariableDeclarator") && usageAncestor.init) {
47242
- const aliasVariables = getVariablesDefinedByDeclarator(analysis, usageAncestor);
47243
- return aliasVariables.length === 0 || aliasVariables.some((aliasVariable) => hasUnsafeExternalSubscriptionBindingUse(analysis, aliasVariable, visitedVariables));
47244
- }
47245
- return false;
47246
- });
47247
- };
47248
- const hasOnlySafeExternalSubscriptionResultBindings = (analysis, declarator, variables = getVariablesDefinedByDeclarator(analysis, declarator)) => {
47249
- return variables.length > 0 && variables.every((variable) => {
47250
- const bindingIdentifier = variable.defs.find((candidateDefinition) => candidateDefinition.node === declarator)?.name;
47251
- return Boolean(bindingIdentifier && isNodeOfType(bindingIdentifier, "Identifier") && isSafeExternalSubscriptionResultBinding(bindingIdentifier, declarator.id) && !hasUnsafeExternalSubscriptionBindingUse(analysis, variable));
47252
- });
47253
- };
47254
- const hasImmutableExternalSubscriptionCallResult = (analysis, rawCallee, relatedRefs, allowWholeResult) => {
47255
- const callee = stripParenExpression(rawCallee);
47256
- const callExpression = callee.parent;
47257
- if (!callExpression || !isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== callee) return false;
47258
- const initializer = findTransparentExpressionRoot(callExpression);
47259
- const declarator = initializer.parent;
47260
- if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== initializer || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return false;
47261
- const relatedVariables = getVariablesDefinedByDeclarator(analysis, declarator).filter((variable) => relatedRefs.some((relatedRef) => relatedRef.resolved === variable));
47262
- if (relatedVariables.length === 0) return false;
47263
- return isNodeOfType(declarator.id, "Identifier") ? allowWholeResult && relatedVariables.every((variable) => !hasUnsafeExternalSubscriptionBindingUse(analysis, variable)) : hasOnlySafeExternalSubscriptionResultBindings(analysis, declarator, relatedVariables);
47264
- };
47265
- const isImmutableImportedExternalSubscriptionHookCallee = (analysis, rawCallee, relatedRefs) => {
47266
- const hookName = getImportedExternalSubscriptionHookName(analysis, rawCallee);
47267
- return Boolean(hookName && hasImmutableExternalSubscriptionCallResult(analysis, rawCallee, relatedRefs, EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES.has(hookName)));
47268
- };
47269
- const isExternalSubscriptionHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
47270
- const declarator = def.node;
47271
- if (!isNodeOfType(declarator, "VariableDeclarator") || !declarator.init || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return false;
47272
- const initializer = stripParenExpression(declarator.init);
47273
- if (!isNodeOfType(initializer, "CallExpression")) return false;
47274
- const hookName = getImportedExternalSubscriptionHookName(analysis, initializer.callee);
47275
- if (!hookName) return false;
47276
- if (isNodeOfType(declarator.id, "Identifier")) return EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES.has(hookName) && ref.resolved && !hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved);
47277
- const bindingIdentifier = def.name;
47278
- return isNodeOfType(bindingIdentifier, "Identifier") && isSafeExternalSubscriptionResultBinding(bindingIdentifier, declarator.id) && ref.resolved && !hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved);
47279
- }));
47280
- const isExternalSubscriptionHookResultArgument = (analysis, argument) => {
47281
- const unwrappedArgument = stripParenExpression(argument);
47282
- if (!isNodeOfType(unwrappedArgument, "Identifier")) return false;
47283
- const argumentRef = getRef(analysis, unwrappedArgument);
47284
- return Boolean(argumentRef && isExternalSubscriptionHookResultRef(analysis, argumentRef));
47285
- };
47286
46317
  const isCallbackPropReference = (analysis, ref) => {
47287
46318
  if (!isProp(analysis, ref)) return false;
47288
46319
  const identifier = ref.identifier;
@@ -47315,9 +46346,8 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
47315
46346
  if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
47316
46347
  return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
47317
46348
  };
47318
- const getLocalHookExternalStateProof = (analysis, ref, scopes) => {
46349
+ const getLocalHookExternalStateProof = (analysis, ref) => {
47319
46350
  let hookFunction = resolveToFunction(ref);
47320
- let didResolveThroughResultBinding = false;
47321
46351
  if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
47322
46352
  const definitionNode = definition.node;
47323
46353
  if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
@@ -47328,27 +46358,29 @@ const getLocalHookExternalStateProof = (analysis, ref, scopes) => {
47328
46358
  const calleeReference = getRef(analysis, callee);
47329
46359
  if (!calleeReference) continue;
47330
46360
  hookFunction = resolveToFunction(calleeReference);
47331
- if (hookFunction) {
47332
- didResolveThroughResultBinding = true;
47333
- break;
47334
- }
46361
+ if (hookFunction) break;
47335
46362
  }
47336
46363
  if (!hookFunction) return null;
47337
- if (didResolveThroughResultBinding && (!ref.resolved || hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved))) return false;
47338
- if (isNodeOfType(hookFunction, "ArrowFunctionExpression") && !isNodeOfType(hookFunction.body, "BlockStatement") && isReactHookCall(stripParenExpression(hookFunction.body), "useSyncExternalStore", scopes)) return true;
47339
- const returnStatements = collectFunctionReturnStatements(hookFunction);
47340
- if (returnStatements.length > 0 && returnStatements.every((returnStatement) => returnStatement.argument && isReactHookCall(stripParenExpression(returnStatement.argument), "useSyncExternalStore", scopes))) return true;
47341
- const returnedReferences = returnStatements.flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
46364
+ const returnedReferences = collectFunctionReturnStatements(hookFunction).flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
47342
46365
  if (returnedReferences.length === 0) return null;
47343
46366
  return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
47344
46367
  };
47345
- const isImmutableLocalExternalStoreHookCallee = (analysis, rawCallee, relatedRefs, scopes) => {
47346
- const callee = stripParenExpression(rawCallee);
47347
- if (!isNodeOfType(callee, "Identifier")) return false;
47348
- const calleeReference = getRef(analysis, callee);
47349
- if (!calleeReference || getLocalHookExternalStateProof(analysis, calleeReference, scopes) !== true) return false;
47350
- return hasImmutableExternalSubscriptionCallResult(analysis, callee, relatedRefs, true);
46368
+ const isExternalSubscriptionHookRef = (analysis, ref) => {
46369
+ const identifier = ref.identifier;
46370
+ if (!isNodeOfType(identifier, "Identifier")) return false;
46371
+ const localHookProof = getLocalHookExternalStateProof(analysis, ref);
46372
+ if (localHookProof !== null) return localHookProof;
46373
+ if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
46374
+ return Boolean(ref.resolved?.defs.some((def) => {
46375
+ const node = def.node;
46376
+ if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
46377
+ const initializer = stripParenExpression(node.init);
46378
+ if (!isNodeOfType(initializer, "CallExpression")) return false;
46379
+ const callee = stripParenExpression(initializer.callee);
46380
+ return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name);
46381
+ }));
47351
46382
  };
46383
+ const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
47352
46384
  const isCalleePosition = (identifier) => {
47353
46385
  const parent = identifier.parent;
47354
46386
  return Boolean(parent && (isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression")) && parent.callee === identifier);
@@ -47409,35 +46441,25 @@ const noPassDataToParent = defineRule({
47409
46441
  return getFunctionalUpdaterDataRefs(analysis, argument);
47410
46442
  }
47411
46443
  if (isHandlerBagArgument(analysis, argument)) return [];
47412
- if (isExternalSubscriptionHookResultArgument(analysis, argument)) return [];
47413
46444
  if (isParentWiredHookResultArgument(analysis, argument)) return [];
47414
46445
  if (isNodeOfType(argument, "Identifier")) {
47415
46446
  const argumentRef = getRef(analysis, argument);
47416
46447
  if (argumentRef && resolveToFunction(argumentRef)) return [];
47417
46448
  }
47418
46449
  return getDownstreamRefs(analysis, argument);
47419
- }).flatMap((argumentRef) => {
47420
- if (isExternallyDrivenState(analysis, argumentRef) || getLocalHookExternalStateProof(analysis, argumentRef, context.scopes) === true) return [];
47421
- const upstreamRefs = getUpstreamRefs(analysis, argumentRef);
47422
- return upstreamRefs.filter((upstreamRef) => {
47423
- if (!isLeafRef(upstreamRef)) return false;
47424
- return !isImmutableImportedExternalSubscriptionHookCallee(analysis, upstreamRef.identifier, upstreamRefs) && !isImmutableLocalExternalStoreHookCallee(analysis, upstreamRef.identifier, upstreamRefs, context.scopes);
47425
- });
47426
- });
47427
- if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) {
47428
- const wrapperUpstreamRefs = getArgsUpstreamRefs(analysis, ref);
47429
- argsUpstreamRefs.push(...wrapperUpstreamRefs.filter((upstreamRef) => isLeafRef(upstreamRef) && !isImmutableImportedExternalSubscriptionHookCallee(analysis, upstreamRef.identifier, wrapperUpstreamRefs) && !isImmutableLocalExternalStoreHookCallee(analysis, upstreamRef.identifier, wrapperUpstreamRefs, context.scopes)));
47430
- }
46450
+ }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) || isExternalSubscriptionHookRef(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
46451
+ if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
47431
46452
  if (!argsUpstreamRefs.some((argRef) => {
47432
- const argIdentifier = argRef.identifier;
47433
- if (isUseStateIdentifier(argIdentifier)) return false;
46453
+ if (isUseStateIdentifier(argRef.identifier)) return false;
46454
+ if (isExternalSubscriptionHookRef(analysis, argRef)) return false;
47434
46455
  if (isProp(analysis, argRef)) return false;
47435
- if (isUseRefIdentifier(argIdentifier)) return false;
46456
+ if (isUseRefIdentifier(argRef.identifier)) return false;
47436
46457
  if (isRefCurrent(argRef)) return false;
47437
46458
  if (isConstant(argRef)) return false;
47438
46459
  if (isParentWiredHookResultRef(analysis, argRef)) return false;
47439
46460
  if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
47440
46461
  if (resolveToFunction(argRef)) return false;
46462
+ const argIdentifier = argRef.identifier;
47441
46463
  if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
47442
46464
  if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
47443
46465
  return true;
@@ -56428,6 +55450,17 @@ const preferModuleScopePureFunction = defineRule({
56428
55450
  }
56429
55451
  });
56430
55452
  //#endregion
55453
+ //#region src/plugin/utils/get-require-call-source.ts
55454
+ const getRequireCallSource = (expression) => {
55455
+ const unwrappedExpression = stripParenExpression(expression);
55456
+ if (isNodeOfType(unwrappedExpression, "MemberExpression")) return getRequireCallSource(unwrappedExpression.object);
55457
+ if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
55458
+ if (!isNodeOfType(unwrappedExpression.callee, "Identifier") || unwrappedExpression.callee.name !== "require") return null;
55459
+ const [firstArgument] = unwrappedExpression.arguments ?? [];
55460
+ if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
55461
+ return typeof firstArgument.value === "string" ? firstArgument.value : null;
55462
+ };
55463
+ //#endregion
56431
55464
  //#region src/plugin/utils/is-proven-node-crypto-namespace-reference.ts
56432
55465
  const NODE_CRYPTO_MODULE_SOURCES = new Set(["crypto", "node:crypto"]);
56433
55466
  const isProvenNodeCryptoNamespaceReference = (expression, scopes) => {
@@ -59754,7 +58787,7 @@ const renderingUsetransitionLoading = defineRule({
59754
58787
  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);
59755
58788
  //#endregion
59756
58789
  //#region src/plugin/rules/security-scan/repository-secret-file.ts
59757
- const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env(?:\.[^./]+)*\.(?:example|sample|template|dist|defaults?)$|(?:^|\/)[^/]*(?:example|sample|template)[^/]*\.(?:env|json|pem|key)$/i.test(relativePath);
58790
+ const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env\.(?:example|sample|template|dist|defaults?)$|(?:^|\/)[^/]*(?:example|sample|template)[^/]*\.(?:env|json|pem|key)$/i.test(relativePath);
59758
58791
  const repositorySecretFile = defineRule({
59759
58792
  id: "repository-secret-file",
59760
58793
  title: "Secret file checked into repository",
@@ -61135,717 +60168,6 @@ const isSetterCalledDuringRender = (root, setterName) => {
61135
60168
  return found;
61136
60169
  };
61137
60170
  //#endregion
61138
- //#region src/plugin/rules/state-and-effects/utils/create-external-location-invalidation-checker.ts
61139
- const HISTORY_LOCATION_MUTATION_METHOD_NAMES = new Set(["pushState", "replaceState"]);
61140
- const AGGREGATE_MUTATION_METHOD_NAMES = new Set([...MUTATING_ARRAY_METHODS, ...MUTATING_COLLECTION_METHODS]);
61141
- const OBJECT_AGGREGATE_MUTATION_METHOD_NAMES = new Set([
61142
- "assign",
61143
- "defineProperties",
61144
- "defineProperty",
61145
- "setPrototypeOf"
61146
- ]);
61147
- const REFLECT_AGGREGATE_MUTATION_METHOD_NAMES = new Set([
61148
- "defineProperty",
61149
- "deleteProperty",
61150
- "set",
61151
- "setPrototypeOf"
61152
- ]);
61153
- const LOCATION_CHANGE_EVENT_NAMES = new Set(["hashchange", "popstate"]);
61154
- const containsGlobalLocationSnapshotRead = (node, scopes) => {
61155
- let didFindLocationSnapshotRead = false;
61156
- walkAst(node, (child) => {
61157
- if (didFindLocationSnapshotRead) return false;
61158
- if (child !== node && isFunctionLike$1(child) && !executesDuringRender(findTransparentExpressionRoot(child), scopes)) return false;
61159
- if (!isProvenGlobalNamespaceReference(child, "location", scopes)) return;
61160
- didFindLocationSnapshotRead = true;
61161
- return false;
61162
- });
61163
- return didFindLocationSnapshotRead;
61164
- };
61165
- const resolveExactLocalOrReactCallbackFunction = (expression, scopes) => {
61166
- const localFunction = resolveExactLocalFunction(expression, scopes);
61167
- if (isFunctionLike$1(localFunction)) return localFunction;
61168
- const unwrappedExpression = stripParenExpression(expression);
61169
- let callbackSource = unwrappedExpression;
61170
- if (isNodeOfType(unwrappedExpression, "Identifier")) {
61171
- const callbackSymbol = resolveConstIdentifierAlias(unwrappedExpression, scopes);
61172
- if (callbackSymbol?.kind !== "const" || !callbackSymbol.initializer) return null;
61173
- callbackSource = callbackSymbol.initializer;
61174
- }
61175
- const unwrappedCallbackSource = stripParenExpression(callbackSource);
61176
- if (!isNodeOfType(unwrappedCallbackSource, "CallExpression") || !isReactApiCall(unwrappedCallbackSource, "useCallback", scopes, { resolveNamedAliases: true })) return null;
61177
- const callback = unwrappedCallbackSource.arguments?.[0];
61178
- if (!callback || isNodeOfType(callback, "SpreadElement")) return null;
61179
- return resolveExactLocalFunction(stripParenExpression(callback), scopes);
61180
- };
61181
- const bindingReadsExactGlobalLocationSnapshot = (bindingIdentifier, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
61182
- const symbol = scopes.symbolFor(bindingIdentifier);
61183
- if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
61184
- 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;
61185
- const resolvedFunction = resolveExactLocalOrReactCallbackFunction(bindingIdentifier, scopes);
61186
- const initializer = isFunctionLike$1(resolvedFunction) ? resolvedFunction.body : getDirectUnreassignedInitializer(symbol);
61187
- if (!initializer) return false;
61188
- const nextVisitedSymbolIds = new Set(visitedSymbolIds);
61189
- nextVisitedSymbolIds.add(symbol.id);
61190
- if (containsGlobalLocationSnapshotRead(initializer, scopes)) return true;
61191
- let didFindAliasedLocationSnapshotRead = false;
61192
- walkAst(initializer, (child) => {
61193
- if (didFindAliasedLocationSnapshotRead) return false;
61194
- if (child !== initializer && isFunctionLike$1(child) && !executesDuringRender(findTransparentExpressionRoot(child), scopes)) return false;
61195
- if (!isNodeOfType(child, "Identifier")) return;
61196
- if (bindingReadsExactGlobalLocationSnapshot(child, scopes, nextVisitedSymbolIds)) {
61197
- didFindAliasedLocationSnapshotRead = true;
61198
- return false;
61199
- }
61200
- });
61201
- return didFindAliasedLocationSnapshotRead;
61202
- };
61203
- const hasRenderReachableLocationSnapshotRead = (componentBody, renderReachableExpressions, directRenderNames, scopes) => {
61204
- if (renderReachableExpressions.some((expression) => containsGlobalLocationSnapshotRead(expression, scopes))) return true;
61205
- for (const statement of componentBody.body ?? []) {
61206
- if (isNodeOfType(statement, "FunctionDeclaration") && statement.id) {
61207
- if (directRenderNames.has(statement.id.name) && bindingReadsExactGlobalLocationSnapshot(statement.id, scopes)) return true;
61208
- continue;
61209
- }
61210
- if (!isNodeOfType(statement, "VariableDeclaration")) continue;
61211
- for (const declarator of statement.declarations ?? []) {
61212
- if (!isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
61213
- if (!directRenderNames.has(declarator.id.name)) continue;
61214
- if (bindingReadsExactGlobalLocationSnapshot(declarator.id, scopes)) return true;
61215
- }
61216
- }
61217
- return false;
61218
- };
61219
- const isGlobalHistoryLocationMutation = (node, scopes) => {
61220
- if (!isNodeOfType(node, "CallExpression")) return false;
61221
- const callee = stripParenExpression(node.callee);
61222
- if (!isNodeOfType(callee, "MemberExpression")) return false;
61223
- const methodName = getStaticPropertyName(callee);
61224
- return Boolean(methodName && HISTORY_LOCATION_MUTATION_METHOD_NAMES.has(methodName) && isProvenGlobalNamespaceReference(callee.object, "history", scopes));
61225
- };
61226
- const getStaticLocationChangeEvent = (node) => {
61227
- if (!node) return null;
61228
- const eventNameNode = stripParenExpression(node);
61229
- if (!isNodeOfType(eventNameNode, "Literal") || typeof eventNameNode.value !== "string") return null;
61230
- return LOCATION_CHANGE_EVENT_NAMES.has(eventNameNode.value) ? eventNameNode.value : null;
61231
- };
61232
- const addToSetIndex = (index, key, value) => {
61233
- const indexedValues = index.get(key) ?? /* @__PURE__ */ new Set();
61234
- indexedValues.add(value);
61235
- index.set(key, indexedValues);
61236
- };
61237
- const getSynchronousInvocationExpression = (functionNode, scopes) => {
61238
- const functionExpressionRoot = findTransparentExpressionRoot(functionNode);
61239
- if (!executesDuringRender(functionExpressionRoot, scopes)) return null;
61240
- const parent = functionExpressionRoot.parent;
61241
- return isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression") ? parent : null;
61242
- };
61243
- const getLocationListenerOperation = (callExpression, scopes) => {
61244
- if (!isNodeOfType(callExpression, "CallExpression")) return null;
61245
- const callee = stripParenExpression(callExpression.callee);
61246
- const methodName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : isNodeOfType(callee, "Identifier") ? callee.name : null;
61247
- if (methodName !== "addEventListener" && methodName !== "removeEventListener") return null;
61248
- if (!(isNodeOfType(callee, "MemberExpression") ? isProvenGlobalObjectReference(callee.object, scopes) : isNodeOfType(callee, "Identifier") && scopes.isGlobalReference(callee))) return null;
61249
- const eventName = getStaticLocationChangeEvent(callExpression.arguments?.[0]);
61250
- if (!eventName) return null;
61251
- const listenerExpression = callExpression.arguments?.[1];
61252
- if (!listenerExpression || isNodeOfType(listenerExpression, "SpreadElement")) return null;
61253
- const listenerFunction = resolveExactLocalOrReactCallbackFunction(listenerExpression, scopes);
61254
- if (!isFunctionLike$1(listenerFunction)) return null;
61255
- const captureArgument = callExpression.arguments?.[2];
61256
- const capture = isNodeOfType(captureArgument, "SpreadElement") ? null : resolveEventListenerCapture(captureArgument, {
61257
- allowComputedString: true,
61258
- allowIndeterminateEntries: true
61259
- });
61260
- return {
61261
- operation: methodName === "addEventListener" ? "add" : "remove",
61262
- registration: {
61263
- callExpression,
61264
- listenerFunction,
61265
- capture,
61266
- eventName
61267
- }
61268
- };
61269
- };
61270
- const buildLocationInvalidationIndex = (componentBody, componentFunction, context) => {
61271
- const index = {
61272
- componentFunction,
61273
- context,
61274
- effectCallbacks: /* @__PURE__ */ new Set(),
61275
- expressionsByOwner: /* @__PURE__ */ new Map(),
61276
- historyMutationsByOwner: /* @__PURE__ */ new Map(),
61277
- awaitExpressionsByOwner: /* @__PURE__ */ new Map(),
61278
- callSitesByFunction: /* @__PURE__ */ new Map(),
61279
- calledFunctionByExpression: /* @__PURE__ */ new Map(),
61280
- synchronousInvocationsByFunction: /* @__PURE__ */ new Map(),
61281
- synchronousCallbacksByExpression: /* @__PURE__ */ new Map(),
61282
- callsByCalleeSymbolId: /* @__PURE__ */ new Map(),
61283
- identifierCalls: /* @__PURE__ */ new Set(),
61284
- listenerRegistrations: [],
61285
- listenerRemovals: [],
61286
- mountedListenerFunctions: /* @__PURE__ */ new Set(),
61287
- mutationExecutionsByOwner: /* @__PURE__ */ new Map(),
61288
- synchronousMutationResultByFunction: /* @__PURE__ */ new Map()
61289
- };
61290
- walkAst(componentBody, (child) => {
61291
- if (isFunctionLike$1(child)) {
61292
- const invocationExpression = getSynchronousInvocationExpression(child, context.scopes);
61293
- if (invocationExpression) {
61294
- addToSetIndex(index.synchronousInvocationsByFunction, child, invocationExpression);
61295
- addToSetIndex(index.synchronousCallbacksByExpression, invocationExpression, child);
61296
- }
61297
- return;
61298
- }
61299
- const owner = context.cfg.enclosingFunction(child);
61300
- if (!owner) return;
61301
- if (isNodeOfType(child, "AwaitExpression")) {
61302
- addToSetIndex(index.awaitExpressionsByOwner, owner, child);
61303
- return;
61304
- }
61305
- if (!isNodeOfType(child, "CallExpression") && !isNodeOfType(child, "NewExpression")) return;
61306
- addToSetIndex(index.expressionsByOwner, owner, child);
61307
- if (isNodeOfType(child, "CallExpression")) {
61308
- if (isGlobalHistoryLocationMutation(child, context.scopes)) addToSetIndex(index.historyMutationsByOwner, owner, child);
61309
- if (isNodeOfType(stripParenExpression(child.callee), "Identifier")) index.identifierCalls.add(child);
61310
- const calledFunction = resolveExactLocalOrReactCallbackFunction(child.callee, context.scopes);
61311
- if (isFunctionLike$1(calledFunction)) {
61312
- addToSetIndex(index.callSitesByFunction, calledFunction, child);
61313
- index.calledFunctionByExpression.set(child, calledFunction);
61314
- }
61315
- if (context.cfg.enclosingFunction(child) === componentFunction && isReactApiCall(child, EFFECT_HOOK_NAMES$1, context.scopes, {
61316
- allowGlobalReactNamespace: true,
61317
- allowUnboundBareCalls: true,
61318
- resolveNamedAliases: true
61319
- })) {
61320
- const effectCallback = getEffectCallback(child, context.scopes);
61321
- if (isFunctionLike$1(effectCallback)) index.effectCallbacks.add(effectCallback);
61322
- }
61323
- const listenerOperation = getLocationListenerOperation(child, context.scopes);
61324
- if (listenerOperation?.operation === "add") index.listenerRegistrations.push(listenerOperation.registration);
61325
- else if (listenerOperation) index.listenerRemovals.push(listenerOperation.registration);
61326
- }
61327
- });
61328
- return index;
61329
- };
61330
- const isDescendantWithoutFunctionBoundary = (descendant, ancestor) => {
61331
- let current = descendant;
61332
- while (current && current !== ancestor) {
61333
- if (current !== descendant && isFunctionLike$1(current)) return false;
61334
- current = current.parent;
61335
- }
61336
- return current === ancestor;
61337
- };
61338
- const areInMutuallyExclusiveConditionalBranches = (firstNode, secondNode) => {
61339
- const firstBranches = /* @__PURE__ */ new Map();
61340
- let current = firstNode;
61341
- while (current?.parent) {
61342
- const parent = current.parent;
61343
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current)) firstBranches.set(parent, current);
61344
- if (current !== firstNode && isFunctionLike$1(current)) break;
61345
- current = parent;
61346
- }
61347
- current = secondNode;
61348
- while (current?.parent) {
61349
- const parent = current.parent;
61350
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current)) {
61351
- const firstBranch = firstBranches.get(parent);
61352
- if (firstBranch && firstBranch !== current) return true;
61353
- }
61354
- if (current !== secondNode && isFunctionLike$1(current)) break;
61355
- current = parent;
61356
- }
61357
- return false;
61358
- };
61359
- const collectStableBooleanGuardConstraints = (node, functionBoundary, scopes) => {
61360
- const constraintsBySymbolId = /* @__PURE__ */ new Map();
61361
- const recordConstraint = (test, requiredTestValue) => {
61362
- let expression = stripParenExpression(test);
61363
- let requiredValue = requiredTestValue;
61364
- while (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") {
61365
- requiredValue = !requiredValue;
61366
- expression = stripParenExpression(expression.argument);
61367
- }
61368
- if (!isNodeOfType(expression, "Identifier")) return;
61369
- const symbol = scopes.symbolFor(expression);
61370
- if (!symbol || symbol.references.some((reference) => reference.flag !== "read")) return;
61371
- const existingConstraint = constraintsBySymbolId.get(symbol.id);
61372
- constraintsBySymbolId.set(symbol.id, existingConstraint === void 0 || existingConstraint === requiredValue ? requiredValue : null);
61373
- };
61374
- let current = node;
61375
- while (current?.parent && current !== functionBoundary) {
61376
- const parent = current.parent;
61377
- if (isNodeOfType(parent, "IfStatement")) {
61378
- if (parent.consequent === current) recordConstraint(parent.test, true);
61379
- if (parent.alternate === current) recordConstraint(parent.test, false);
61380
- } else if (isNodeOfType(parent, "ConditionalExpression")) {
61381
- if (parent.consequent === current) recordConstraint(parent.test, true);
61382
- if (parent.alternate === current) recordConstraint(parent.test, false);
61383
- } else if (isNodeOfType(parent, "LogicalExpression") && parent.right === current) {
61384
- if (parent.operator === "&&") recordConstraint(parent.left, true);
61385
- if (parent.operator === "||") recordConstraint(parent.left, false);
61386
- }
61387
- if (current !== node && isFunctionLike$1(current)) break;
61388
- current = parent;
61389
- }
61390
- return constraintsBySymbolId;
61391
- };
61392
- const haveContradictoryStableBooleanGuards = (firstNode, secondNode, functionBoundary, scopes) => {
61393
- const firstConstraints = collectStableBooleanGuardConstraints(firstNode, functionBoundary, scopes);
61394
- const secondConstraints = collectStableBooleanGuardConstraints(secondNode, functionBoundary, scopes);
61395
- for (const requiredValue of firstConstraints.values()) if (requiredValue === null) return true;
61396
- for (const requiredValue of secondConstraints.values()) if (requiredValue === null) return true;
61397
- for (const [symbolId, firstRequiredValue] of firstConstraints) {
61398
- const secondRequiredValue = secondConstraints.get(symbolId);
61399
- if (firstRequiredValue !== null && secondRequiredValue !== void 0 && secondRequiredValue !== null && firstRequiredValue !== secondRequiredValue) return true;
61400
- }
61401
- return false;
61402
- };
61403
- const isInsideIntrinsicReactEventHandlerAttribute = (node, functionBoundary) => {
61404
- let current = node;
61405
- while (current && current !== functionBoundary) {
61406
- if (isEventHandlerAttribute(current) && isJsxAttributeOnIntrinsicHtmlElement(current)) return true;
61407
- current = current.parent;
61408
- }
61409
- return false;
61410
- };
61411
- const getTransparentExpressionBindingIdentifier = (expression) => {
61412
- const expressionRoot = findTransparentExpressionRoot(expression);
61413
- const parent = expressionRoot.parent;
61414
- if (isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier")) return parent.id;
61415
- if (isNodeOfType(parent, "AssignmentExpression") && parent.right === expressionRoot && isNodeOfType(parent.left, "Identifier")) return parent.left;
61416
- return null;
61417
- };
61418
- const getIntrinsicReactEventHandlerBindingIdentifier = (functionNode, index) => {
61419
- if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
61420
- const functionExpressionRoot = findTransparentExpressionRoot(functionNode);
61421
- const directTransparentBindingIdentifier = getTransparentExpressionBindingIdentifier(functionExpressionRoot);
61422
- if (directTransparentBindingIdentifier) return directTransparentBindingIdentifier;
61423
- const callbackCall = functionExpressionRoot.parent;
61424
- if (!isNodeOfType(callbackCall, "CallExpression") || callbackCall.arguments?.[0] !== functionExpressionRoot || !isReactApiCall(callbackCall, "useCallback", index.context.scopes, { resolveNamedAliases: true })) return null;
61425
- return getTransparentExpressionBindingIdentifier(callbackCall);
61426
- };
61427
- const isExclusiveIntrinsicReactEventHandler = (functionNode, index) => {
61428
- const bindingIdentifier = getIntrinsicReactEventHandlerBindingIdentifier(functionNode, index);
61429
- if (!bindingIdentifier) return isInsideIntrinsicReactEventHandlerAttribute(functionNode, index.componentFunction);
61430
- const bindingSymbol = index.context.scopes.symbolFor(bindingIdentifier);
61431
- return Boolean(bindingSymbol && bindingSymbol.references.length > 0 && bindingSymbol.references.every((reference) => isInsideIntrinsicReactEventHandlerAttribute(reference.identifier, index.componentFunction)));
61432
- };
61433
- const canNodeReachNode = (sourceNode, targetNode, index) => {
61434
- const { context } = index;
61435
- if (!isNodeReachableWithinFunction(sourceNode, context)) return false;
61436
- if (!isNodeReachableWithinFunction(targetNode, context)) return false;
61437
- const sourceOwner = context.cfg.enclosingFunction(sourceNode);
61438
- const targetOwner = context.cfg.enclosingFunction(targetNode);
61439
- if (!sourceOwner || sourceOwner !== targetOwner) return false;
61440
- if (areInMutuallyExclusiveConditionalBranches(sourceNode, targetNode)) return false;
61441
- if (haveContradictoryStableBooleanGuards(sourceNode, targetNode, sourceOwner, context.scopes)) return false;
61442
- if (isDescendantWithoutFunctionBoundary(targetNode, sourceNode)) return false;
61443
- if (isDescendantWithoutFunctionBoundary(sourceNode, targetNode)) return true;
61444
- const functionCfg = context.cfg.cfgFor(sourceOwner);
61445
- const sourceBlock = functionCfg?.blockOf(sourceNode);
61446
- const targetBlock = functionCfg?.blockOf(targetNode);
61447
- if (!functionCfg || !sourceBlock || !targetBlock) return false;
61448
- if (sourceBlock === targetBlock) {
61449
- const sourceStart = getRangeStart(sourceNode);
61450
- const targetStart = getRangeStart(targetNode);
61451
- return sourceStart !== null && targetStart !== null && sourceStart < targetStart;
61452
- }
61453
- const visitedBlocks = new Set([sourceBlock]);
61454
- const pendingBlocks = sourceBlock.successors.filter((edge) => edge.kind !== "throw").map((edge) => edge.to);
61455
- while (pendingBlocks.length > 0) {
61456
- const block = pendingBlocks.pop();
61457
- if (!block || visitedBlocks.has(block)) continue;
61458
- if (block === targetBlock) return true;
61459
- visitedBlocks.add(block);
61460
- for (const edge of block.successors) if (edge.kind !== "throw") pendingBlocks.push(edge.to);
61461
- }
61462
- return false;
61463
- };
61464
- const getInlineIntrinsicHandlerJsxElement = (functionNode) => {
61465
- let currentNode = functionNode;
61466
- while (currentNode) {
61467
- if (isNodeOfType(currentNode, "JSXAttribute") && isEventHandlerAttribute(currentNode) && isJsxAttributeOnIntrinsicHtmlElement(currentNode)) {
61468
- const openingElement = currentNode.parent;
61469
- return isNodeOfType(openingElement?.parent, "JSXElement") ? openingElement.parent : null;
61470
- }
61471
- if (currentNode !== functionNode && isFunctionLike$1(currentNode)) return null;
61472
- currentNode = currentNode.parent;
61473
- }
61474
- return null;
61475
- };
61476
- const isProvenNonEscapingVoidRead = (identifier) => {
61477
- const expressionRoot = findTransparentExpressionRoot(identifier);
61478
- return isNodeOfType(expressionRoot.parent, "UnaryExpression") && expressionRoot.parent.operator === "void" && expressionRoot.parent.argument === expressionRoot;
61479
- };
61480
- const getDirectReadonlyValueAliasSymbol = (referenceIdentifier, sourceSymbol, index) => {
61481
- const referenceRoot = findTransparentExpressionRoot(referenceIdentifier);
61482
- const parent = referenceRoot.parent;
61483
- if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier")) {
61484
- const aliasSymbol = index.context.scopes.symbolFor(parent.id);
61485
- const aliasInitializer = aliasSymbol ? getDirectConstInitializer(aliasSymbol) : null;
61486
- const unwrappedInitializer = aliasInitializer ? stripParenExpression(aliasInitializer) : null;
61487
- return aliasSymbol && isNodeOfType(unwrappedInitializer, "Identifier") && index.context.scopes.symbolFor(unwrappedInitializer) === sourceSymbol ? aliasSymbol : null;
61488
- }
61489
- if (!isNodeOfType(parent, "CallExpression") || parent.arguments?.[0] !== referenceRoot || !isReactApiCall(parent, "useCallback", index.context.scopes, { resolveNamedAliases: true })) return null;
61490
- const callbackResultRoot = findTransparentExpressionRoot(parent);
61491
- const resultParent = callbackResultRoot.parent;
61492
- if (!isNodeOfType(resultParent, "VariableDeclarator") || resultParent.init !== callbackResultRoot || !isNodeOfType(resultParent.id, "Identifier")) return null;
61493
- const callbackResultSymbol = index.context.scopes.symbolFor(resultParent.id);
61494
- return callbackResultSymbol && getDirectConstInitializer(callbackResultSymbol) === callbackResultRoot ? callbackResultSymbol : null;
61495
- };
61496
- const isKnownAggregateMutationReference = (referenceIdentifier, index) => {
61497
- const referenceRoot = findTransparentExpressionRoot(referenceIdentifier);
61498
- const parent = referenceRoot.parent;
61499
- if (isNodeOfType(parent, "MemberExpression") && parent.object === referenceRoot) {
61500
- const memberRoot = findTransparentExpressionRoot(parent);
61501
- const callExpression = memberRoot.parent;
61502
- const methodName = getStaticPropertyName(parent);
61503
- if (isNodeOfType(callExpression, "CallExpression") && callExpression.callee === memberRoot && methodName !== null && AGGREGATE_MUTATION_METHOD_NAMES.has(methodName)) return true;
61504
- }
61505
- if (!isNodeOfType(parent, "CallExpression") || parent.arguments?.[0] !== referenceRoot) return false;
61506
- const callee = stripParenExpression(parent.callee);
61507
- if (!isNodeOfType(callee, "MemberExpression")) return false;
61508
- const methodName = getStaticPropertyName(callee);
61509
- if (methodName === null) return false;
61510
- 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);
61511
- };
61512
- const collectExactReadonlyValueEscapeAnchors = (symbol, index, valueFunctionNode, options = {}, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
61513
- if (visitedSymbolIds.has(symbol.id)) return null;
61514
- if (symbol.references.some((reference) => reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier) || options.rejectKnownMutations && isKnownAggregateMutationReference(reference.identifier, index))) return null;
61515
- const nextVisitedSymbolIds = new Set(visitedSymbolIds);
61516
- nextVisitedSymbolIds.add(symbol.id);
61517
- const escapeAnchors = [];
61518
- for (const reference of symbol.references) {
61519
- if (valueFunctionNode && index.context.cfg.enclosingFunction(reference.identifier) === valueFunctionNode || isProvenNonEscapingVoidRead(reference.identifier)) continue;
61520
- const aliasSymbol = getDirectReadonlyValueAliasSymbol(reference.identifier, symbol, index);
61521
- if (!aliasSymbol) {
61522
- escapeAnchors.push(reference.identifier);
61523
- continue;
61524
- }
61525
- const aliasEscapeAnchors = collectExactReadonlyValueEscapeAnchors(aliasSymbol, index, valueFunctionNode, options, nextVisitedSymbolIds);
61526
- if (!aliasEscapeAnchors) return null;
61527
- escapeAnchors.push(...aliasEscapeAnchors);
61528
- }
61529
- return escapeAnchors.length > 0 ? escapeAnchors : null;
61530
- };
61531
- const findReadonlyJsxAggregateRoot = (jsxElement) => {
61532
- let aggregateRoot = findTransparentExpressionRoot(jsxElement);
61533
- while (aggregateRoot.parent) {
61534
- const parent = aggregateRoot.parent;
61535
- if ((isNodeOfType(parent, "JSXElement") || isNodeOfType(parent, "JSXFragment")) && parent.children?.some((child) => child === aggregateRoot)) {
61536
- aggregateRoot = parent;
61537
- continue;
61538
- }
61539
- if (isNodeOfType(parent, "ArrayExpression") && parent.elements?.some((element) => element === aggregateRoot)) {
61540
- aggregateRoot = findTransparentExpressionRoot(parent);
61541
- continue;
61542
- }
61543
- if (isNodeOfType(parent, "Property") && parent.value === aggregateRoot && parent.kind === "init" && !parent.computed && isNodeOfType(parent.parent, "ObjectExpression")) {
61544
- aggregateRoot = findTransparentExpressionRoot(parent.parent);
61545
- continue;
61546
- }
61547
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === aggregateRoot || parent.alternate === aggregateRoot)) {
61548
- aggregateRoot = findTransparentExpressionRoot(parent);
61549
- continue;
61550
- }
61551
- if (isNodeOfType(parent, "LogicalExpression") && (parent.right === aggregateRoot || parent.left === aggregateRoot && parent.operator !== "&&")) {
61552
- aggregateRoot = findTransparentExpressionRoot(parent);
61553
- continue;
61554
- }
61555
- if (isNodeOfType(parent, "SequenceExpression") && getFinalSequenceExpressionValue(parent) === getFinalSequenceExpressionValue(aggregateRoot)) {
61556
- aggregateRoot = findTransparentExpressionRoot(parent);
61557
- continue;
61558
- }
61559
- break;
61560
- }
61561
- return aggregateRoot;
61562
- };
61563
- const getReadonlyJsxValueEscapeAnchors = (jsxElement, index) => {
61564
- const jsxValueRoot = findReadonlyJsxAggregateRoot(jsxElement);
61565
- const parent = jsxValueRoot.parent;
61566
- if (!isNodeOfType(parent, "VariableDeclarator") || parent.init !== jsxValueRoot) return [jsxValueRoot];
61567
- if (!isNodeOfType(parent.id, "Identifier")) return null;
61568
- const bindingSymbol = index.context.scopes.symbolFor(parent.id);
61569
- if (!bindingSymbol || getDirectConstInitializer(bindingSymbol) !== jsxValueRoot) return null;
61570
- return collectExactReadonlyValueEscapeAnchors(bindingSymbol, index, null, { rejectKnownMutations: true });
61571
- };
61572
- const getExclusiveIntrinsicReactEventHandlerAnchors = (functionNode, index) => {
61573
- if (!isExclusiveIntrinsicReactEventHandler(functionNode, index)) return null;
61574
- const bindingIdentifier = getIntrinsicReactEventHandlerBindingIdentifier(functionNode, index);
61575
- if (!bindingIdentifier) {
61576
- const jsxElement = getInlineIntrinsicHandlerJsxElement(functionNode);
61577
- return jsxElement ? getReadonlyJsxValueEscapeAnchors(jsxElement, index) : null;
61578
- }
61579
- return index.context.scopes.symbolFor(bindingIdentifier)?.references.map((reference) => reference.identifier) ?? null;
61580
- };
61581
- const getReadonlyFunctionEscapeAnchors = (functionNode, index) => {
61582
- const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
61583
- if (!bindingIdentifier) return null;
61584
- const bindingSymbol = index.context.scopes.symbolFor(bindingIdentifier);
61585
- if (!bindingSymbol || resolveExactLocalFunction(bindingIdentifier, index.context.scopes) !== functionNode) return null;
61586
- return collectExactReadonlyValueEscapeAnchors(bindingSymbol, index, functionNode);
61587
- };
61588
- const isAliasInitializedBeforeExecution = (aliasDeclaration, executionNode, index, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
61589
- if (!isNodeReachableWithinFunction(aliasDeclaration, index.context)) return false;
61590
- const aliasOwner = index.context.cfg.enclosingFunction(aliasDeclaration);
61591
- const executionOwner = index.context.cfg.enclosingFunction(executionNode);
61592
- if (!aliasOwner || !executionOwner) return false;
61593
- if (aliasOwner === executionOwner) return canNodeReachNode(aliasDeclaration, executionNode, index);
61594
- if (visitedFunctionNodes.has(executionOwner)) return false;
61595
- const nextVisitedFunctionNodes = new Set(visitedFunctionNodes);
61596
- nextVisitedFunctionNodes.add(executionOwner);
61597
- if (aliasOwner === index.componentFunction && (index.effectCallbacks.has(executionOwner) || index.mountedListenerFunctions.has(executionOwner))) return doNodesCoverEveryPathFromFunctionEntry(index.componentFunction, [aliasDeclaration], index.context, { ignoreThrowEdges: true });
61598
- const invocations = new Set([...index.callSitesByFunction.get(executionOwner) ?? [], ...index.synchronousInvocationsByFunction.get(executionOwner) ?? []]);
61599
- if (invocations.size > 0) return [...invocations].every((invocation) => isAliasInitializedBeforeExecution(aliasDeclaration, invocation, index, nextVisitedFunctionNodes));
61600
- const deferredExecutionAnchors = getExclusiveIntrinsicReactEventHandlerAnchors(executionOwner, index);
61601
- if (deferredExecutionAnchors) return deferredExecutionAnchors.every((anchor) => isAliasInitializedBeforeExecution(aliasDeclaration, anchor, index, nextVisitedFunctionNodes));
61602
- const functionEscapeAnchors = getReadonlyFunctionEscapeAnchors(executionOwner, index);
61603
- return Boolean(functionEscapeAnchors?.every((anchor) => isAliasInitializedBeforeExecution(aliasDeclaration, anchor, index, nextVisitedFunctionNodes)));
61604
- };
61605
- const resolveExactReadonlyCalleeSymbolId = (callExpression, index) => {
61606
- if (!isNodeOfType(callExpression, "CallExpression")) return null;
61607
- const callee = stripParenExpression(callExpression.callee);
61608
- if (!isNodeOfType(callee, "Identifier")) return null;
61609
- const visitedSymbolIds = /* @__PURE__ */ new Set();
61610
- let symbol = index.context.scopes.symbolFor(callee);
61611
- while (symbol) {
61612
- if (visitedSymbolIds.has(symbol.id)) return null;
61613
- visitedSymbolIds.add(symbol.id);
61614
- const initializer = getDirectConstInitializer(symbol);
61615
- if (!initializer) return symbol.id;
61616
- if (symbol.references.some((reference) => reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier)) || !isAliasInitializedBeforeExecution(symbol.declarationNode, callExpression, index)) return null;
61617
- const unwrappedInitializer = stripParenExpression(initializer);
61618
- if (!isNodeOfType(unwrappedInitializer, "Identifier")) return symbol.id;
61619
- const initializerSymbol = index.context.scopes.symbolFor(unwrappedInitializer);
61620
- if (!initializerSymbol || !isAliasInitializedBeforeExecution(initializerSymbol.declarationNode, symbol.declarationNode, index)) return null;
61621
- symbol = initializerSymbol;
61622
- }
61623
- return null;
61624
- };
61625
- const collectExactReadonlyCalleeCalls = (index) => {
61626
- for (const callExpression of index.identifierCalls) {
61627
- const calleeSymbolId = resolveExactReadonlyCalleeSymbolId(callExpression, index);
61628
- if (calleeSymbolId !== null) addToSetIndex(index.callsByCalleeSymbolId, calleeSymbolId, callExpression);
61629
- }
61630
- };
61631
- const canNodeReachNormalFunctionExit = (node, functionNode, index) => {
61632
- const functionCfg = index.context.cfg.cfgFor(functionNode);
61633
- const sourceBlock = functionCfg?.blockOf(node);
61634
- if (!functionCfg || !sourceBlock) return false;
61635
- const visitedBlocks = /* @__PURE__ */ new Set();
61636
- const pendingBlocks = [sourceBlock];
61637
- while (pendingBlocks.length > 0) {
61638
- const block = pendingBlocks.pop();
61639
- if (!block || visitedBlocks.has(block)) continue;
61640
- visitedBlocks.add(block);
61641
- for (const edge of block.successors) {
61642
- if (edge.kind === "throw") continue;
61643
- if (edge.to === functionCfg.exit) return true;
61644
- pendingBlocks.push(edge.to);
61645
- }
61646
- }
61647
- return false;
61648
- };
61649
- const canExecuteBeforeAsyncSuspension = (node, functionNode, index) => {
61650
- if (!isFunctionLike$1(functionNode) || !functionNode.async) return isNodeReachableWithinFunction(node, index.context);
61651
- const functionCfg = index.context.cfg.cfgFor(functionNode);
61652
- const targetBlock = functionCfg?.blockOf(node);
61653
- if (!functionCfg || !targetBlock) return false;
61654
- const awaitsByBlock = /* @__PURE__ */ new Map();
61655
- for (const awaitExpression of index.awaitExpressionsByOwner.get(functionNode) ?? []) {
61656
- const awaitBlock = functionCfg.blockOf(awaitExpression);
61657
- if (!awaitBlock) continue;
61658
- const blockAwaits = awaitsByBlock.get(awaitBlock) ?? [];
61659
- blockAwaits.push(awaitExpression);
61660
- awaitsByBlock.set(awaitBlock, blockAwaits);
61661
- }
61662
- const visitedBlocks = /* @__PURE__ */ new Set();
61663
- const pendingBlocks = [functionCfg.entry];
61664
- const targetStart = getRangeStart(node);
61665
- while (pendingBlocks.length > 0) {
61666
- const block = pendingBlocks.pop();
61667
- if (!block || visitedBlocks.has(block)) continue;
61668
- visitedBlocks.add(block);
61669
- const blockAwaits = awaitsByBlock.get(block) ?? [];
61670
- if (block === targetBlock) return !blockAwaits.some((awaitExpression) => {
61671
- if (isNodeOfType(awaitExpression, "AwaitExpression") && isDescendantWithoutFunctionBoundary(node, awaitExpression.argument)) return false;
61672
- if (isDescendantWithoutFunctionBoundary(awaitExpression, node)) return true;
61673
- const awaitStart = getRangeStart(awaitExpression);
61674
- return awaitStart !== null && targetStart !== null && awaitStart < targetStart;
61675
- });
61676
- if (blockAwaits.length > 0) continue;
61677
- for (const edge of block.successors) if (edge.kind !== "throw") pendingBlocks.push(edge.to);
61678
- }
61679
- return false;
61680
- };
61681
- 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);
61682
- const functionMaySynchronouslyMutateLocation = (functionNode, index, visitingFunctions, cycleAffectedFunctions) => {
61683
- const cachedResult = index.synchronousMutationResultByFunction.get(functionNode);
61684
- if (cachedResult !== void 0) return cachedResult;
61685
- if (!isFunctionLike$1(functionNode) || functionNode.generator) return false;
61686
- if (visitingFunctions.has(functionNode)) {
61687
- let didReachCycleEntry = false;
61688
- for (const visitingFunction of visitingFunctions) {
61689
- if (visitingFunction === functionNode) didReachCycleEntry = true;
61690
- if (didReachCycleEntry) cycleAffectedFunctions.add(visitingFunction);
61691
- }
61692
- return false;
61693
- }
61694
- visitingFunctions.add(functionNode);
61695
- const doesMutateSynchronously = [...collectLocationMutationExecutions(functionNode, index, visitingFunctions, cycleAffectedFunctions)].some((mutationExecution) => canExecuteBeforeAsyncSuspension(mutationExecution, functionNode, index) && canNodeReachNormalFunctionExit(mutationExecution, functionNode, index));
61696
- visitingFunctions.delete(functionNode);
61697
- if (doesMutateSynchronously || !cycleAffectedFunctions.has(functionNode)) index.synchronousMutationResultByFunction.set(functionNode, doesMutateSynchronously);
61698
- return doesMutateSynchronously;
61699
- };
61700
- const collectLocationMutationExecutions = (functionNode, index, visitingFunctions = /* @__PURE__ */ new Set(), cycleAffectedFunctions = /* @__PURE__ */ new Set()) => {
61701
- const cachedExecutions = index.mutationExecutionsByOwner.get(functionNode);
61702
- if (cachedExecutions) return cachedExecutions;
61703
- const mutationExecutions = new Set(index.historyMutationsByOwner.get(functionNode) ?? []);
61704
- for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
61705
- const calledFunction = index.calledFunctionByExpression.get(expression);
61706
- if (calledFunction && functionMaySynchronouslyMutateLocation(calledFunction, index, visitingFunctions, cycleAffectedFunctions)) mutationExecutions.add(expression);
61707
- for (const callbackFunction of index.synchronousCallbacksByExpression.get(expression) ?? []) if (functionMaySynchronouslyMutateLocation(callbackFunction, index, visitingFunctions, cycleAffectedFunctions)) mutationExecutions.add(expression);
61708
- }
61709
- if (!cycleAffectedFunctions.has(functionNode)) index.mutationExecutionsByOwner.set(functionNode, mutationExecutions);
61710
- return mutationExecutions;
61711
- };
61712
- const isDefinitelyMatchingLocationListenerRemoval = (registration, removal) => registration.eventName === removal.eventName && registration.listenerFunction === removal.listenerFunction && registration.capture !== null && removal.capture !== null && registration.capture === removal.capture;
61713
- const functionMustSynchronouslyRemoveLocationListener = (functionNode, registration, index, visitingFunctions) => {
61714
- if (!isFunctionLike$1(functionNode) || functionNode.generator) return false;
61715
- if (visitingFunctions.has(functionNode)) return false;
61716
- const nextVisitingFunctions = new Set(visitingFunctions);
61717
- nextVisitingFunctions.add(functionNode);
61718
- const removalExecutions = [];
61719
- for (const removal of index.listenerRemovals) {
61720
- if (!isDefinitelyMatchingLocationListenerRemoval(registration, removal)) continue;
61721
- if (index.context.cfg.enclosingFunction(removal.callExpression) !== functionNode) continue;
61722
- if (canExecuteBeforeAsyncSuspension(removal.callExpression, functionNode, index)) removalExecutions.push(removal.callExpression);
61723
- }
61724
- for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
61725
- const calledFunction = index.calledFunctionByExpression.get(expression);
61726
- if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index) && functionMustSynchronouslyRemoveLocationListener(calledFunction, registration, index, nextVisitingFunctions)) removalExecutions.push(expression);
61727
- }
61728
- return doNodesCoverEveryPathFromFunctionEntry(functionNode, removalExecutions, index.context);
61729
- };
61730
- const collectSynchronousLocationListenerRemovalExecutions = (functionNode, registration, index) => {
61731
- const removalExecutions = /* @__PURE__ */ new Set();
61732
- 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);
61733
- for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
61734
- const calledFunction = index.calledFunctionByExpression.get(expression);
61735
- if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index) && functionMustSynchronouslyRemoveLocationListener(calledFunction, registration, index, /* @__PURE__ */ new Set())) removalExecutions.add(expression);
61736
- }
61737
- return removalExecutions;
61738
- };
61739
- const collectImpliedExpressionExecutionBoundaries = (node, owner) => {
61740
- const expressionBoundaries = [];
61741
- let currentChild = node;
61742
- let currentParent = currentChild.parent ?? null;
61743
- while (currentParent && currentParent !== owner) {
61744
- if (isNodeOfType(currentParent, "ConditionalExpression") && currentParent.test === currentChild) {
61745
- const staticTestValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.test));
61746
- if (staticTestValue !== null) expressionBoundaries.push(staticTestValue ? currentParent.consequent : currentParent.alternate);
61747
- }
61748
- if (isNodeOfType(currentParent, "LogicalExpression") && currentParent.left === currentChild) {
61749
- const staticLeftValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.left));
61750
- if (currentParent.operator === "&&" && staticLeftValue === true || currentParent.operator === "||" && staticLeftValue === false) expressionBoundaries.push(currentParent.right);
61751
- }
61752
- 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);
61753
- currentChild = currentParent;
61754
- currentParent = currentChild.parent ?? null;
61755
- }
61756
- return expressionBoundaries;
61757
- };
61758
- const canExecutionReachFunctionExitWithoutListenerRemoval = (executionNode, registration, index) => {
61759
- if (!isNodeReachableWithinFunction(executionNode, index.context)) return false;
61760
- const owner = index.context.cfg.enclosingFunction(executionNode);
61761
- if (!owner) return false;
61762
- const functionCfg = index.context.cfg.cfgFor(owner);
61763
- const sourceBlock = functionCfg?.blockOf(executionNode);
61764
- if (!functionCfg || !sourceBlock) return false;
61765
- const expressionBoundaries = collectImpliedExpressionExecutionBoundaries(executionNode, owner);
61766
- const matchingRemovalsByBlock = /* @__PURE__ */ new Map();
61767
- for (const removalExecution of collectSynchronousLocationListenerRemovalExecutions(owner, registration, index)) {
61768
- const removalBlock = functionCfg.blockOf(removalExecution);
61769
- if (!removalBlock) continue;
61770
- const blockRemovals = matchingRemovalsByBlock.get(removalBlock) ?? [];
61771
- blockRemovals.push(removalExecution);
61772
- matchingRemovalsByBlock.set(removalBlock, blockRemovals);
61773
- }
61774
- const sourceStart = getRangeStart(executionNode);
61775
- const hasRemovalAfterExecution = (block) => {
61776
- const removals = (matchingRemovalsByBlock.get(block) ?? []).filter((removal) => {
61777
- if (block !== sourceBlock) return true;
61778
- const removalStart = getRangeStart(removal);
61779
- return sourceStart !== null && removalStart !== null && sourceStart < removalStart;
61780
- });
61781
- if (collectExpressionPathCoverageNodes(owner, removals, index.context).size > 0) return true;
61782
- if (block !== sourceBlock) return false;
61783
- return expressionBoundaries.some((expressionBoundary) => collectExpressionPathCoverageNodes(owner, removals, index.context, expressionBoundary).size > 0);
61784
- };
61785
- const visitedBlocks = /* @__PURE__ */ new Set();
61786
- const pendingBlocks = [sourceBlock];
61787
- while (pendingBlocks.length > 0) {
61788
- const block = pendingBlocks.pop();
61789
- if (!block || visitedBlocks.has(block)) continue;
61790
- visitedBlocks.add(block);
61791
- if (hasRemovalAfterExecution(block)) continue;
61792
- for (const edge of block.successors) {
61793
- if (edge.kind === "throw") continue;
61794
- if (edge.to === functionCfg.exit) return true;
61795
- pendingBlocks.push(edge.to);
61796
- }
61797
- }
61798
- return false;
61799
- };
61800
- const isListenerActiveAtMountedExit = (executionNode, registration, index, visitedExecutions = /* @__PURE__ */ new Set()) => {
61801
- if (visitedExecutions.has(executionNode)) return false;
61802
- if (!canExecutionReachFunctionExitWithoutListenerRemoval(executionNode, registration, index)) return false;
61803
- const owner = index.context.cfg.enclosingFunction(executionNode);
61804
- if (!owner) return false;
61805
- if (owner === index.componentFunction || index.effectCallbacks.has(owner)) return true;
61806
- const nextVisitedExecutions = new Set(visitedExecutions);
61807
- nextVisitedExecutions.add(executionNode);
61808
- for (const callSite of index.callSitesByFunction.get(owner) ?? []) if (isListenerActiveAtMountedExit(callSite, registration, index, nextVisitedExecutions)) return true;
61809
- for (const invocation of index.synchronousInvocationsByFunction.get(owner) ?? []) if (isListenerActiveAtMountedExit(invocation, registration, index, nextVisitedExecutions)) return true;
61810
- return false;
61811
- };
61812
- const collectMountedListenerFunctions = (index) => {
61813
- for (const registration of index.listenerRegistrations) if (isListenerActiveAtMountedExit(registration.callExpression, registration, index)) index.mountedListenerFunctions.add(registration.listenerFunction);
61814
- };
61815
- const setterArgumentMutatesLocation = (setterCall, index) => {
61816
- if (!isNodeOfType(setterCall, "CallExpression")) return false;
61817
- return (setterCall.arguments ?? []).some((argument) => {
61818
- if (isNodeOfType(argument, "SpreadElement")) return false;
61819
- const updaterFunction = resolveExactLocalFunction(argument, index.context.scopes);
61820
- return Boolean(isFunctionLike$1(updaterFunction) && functionMaySynchronouslyMutateLocation(updaterFunction, index, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set()));
61821
- });
61822
- };
61823
- const executionAnchorInvalidatesLocationSnapshot = (executionAnchor, index, visitedExecutions = /* @__PURE__ */ new Set()) => {
61824
- if (visitedExecutions.has(executionAnchor)) return false;
61825
- if (!isNodeReachableWithinFunction(executionAnchor, index.context)) return false;
61826
- const owner = index.context.cfg.enclosingFunction(executionAnchor);
61827
- if (!owner) return false;
61828
- if (index.mountedListenerFunctions.has(owner)) return true;
61829
- if (setterArgumentMutatesLocation(executionAnchor, index) || [...collectLocationMutationExecutions(owner, index)].some((mutationExecution) => canNodeReachNode(mutationExecution, executionAnchor, index) || canReactBatchMutationAfterExecution(executionAnchor, mutationExecution, owner, index))) return true;
61830
- const nextVisitedExecutions = new Set(visitedExecutions);
61831
- nextVisitedExecutions.add(executionAnchor);
61832
- for (const callSite of index.callSitesByFunction.get(owner) ?? []) if (executionAnchorInvalidatesLocationSnapshot(callSite, index, nextVisitedExecutions)) return true;
61833
- for (const invocation of index.synchronousInvocationsByFunction.get(owner) ?? []) if (executionAnchorInvalidatesLocationSnapshot(invocation, index, nextVisitedExecutions)) return true;
61834
- return false;
61835
- };
61836
- const setterInvalidatesGlobalLocationSnapshot = (setterBindingIdentifier, index) => {
61837
- const setterSymbol = index.context.scopes.symbolFor(setterBindingIdentifier);
61838
- if (!setterSymbol) return false;
61839
- return [...index.callsByCalleeSymbolId.get(setterSymbol.id) ?? []].some((setterCall) => executionAnchorInvalidatesLocationSnapshot(setterCall, index));
61840
- };
61841
- const createExternalLocationInvalidationChecker = ({ componentBody, componentFunction, context, directRenderNames, renderReachableExpressions }) => {
61842
- if (!hasRenderReachableLocationSnapshotRead(componentBody, renderReachableExpressions, directRenderNames, context.scopes)) return () => false;
61843
- const locationInvalidationIndex = buildLocationInvalidationIndex(componentBody, componentFunction, context);
61844
- collectMountedListenerFunctions(locationInvalidationIndex);
61845
- collectExactReadonlyCalleeCalls(locationInvalidationIndex);
61846
- return (setterBindingIdentifier) => setterInvalidatesGlobalLocationSnapshot(setterBindingIdentifier, locationInvalidationIndex);
61847
- };
61848
- //#endregion
61849
60171
  //#region src/plugin/rules/state-and-effects/rerender-state-only-in-handlers.ts
61850
60172
  const isInsideConditionTest = (identifier, stopAt) => {
61851
60173
  let current = identifier;
@@ -61967,8 +60289,7 @@ const rerenderStateOnlyInHandlers = defineRule({
61967
60289
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
61968
60290
  const bindings = collectUseStateBindings(componentBody, context.scopes);
61969
60291
  if (bindings.length === 0) return;
61970
- const renderReachableExpressions = collectRenderReachableExpressions(componentBody);
61971
- if (renderReachableExpressions.length === 0) return;
60292
+ if (collectRenderReachableExpressions(componentBody).length === 0) return;
61972
60293
  const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
61973
60294
  const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
61974
60295
  const directRenderNames = collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames);
@@ -61982,22 +60303,12 @@ const rerenderStateOnlyInHandlers = defineRule({
61982
60303
  for (const effectInfo of effectInfos) for (const dependencyName of effectInfo.dependencyNames) if (!selfEchoValueNames.has(dependencyName)) effectConsumedNames.add(dependencyName);
61983
60304
  for (const hookArgumentName of collectCustomHookArgumentNames(componentBody, context.scopes)) effectConsumedNames.add(hookArgumentName);
61984
60305
  for (const reachableName of expandTransitiveDependencies(effectConsumedNames, dependencyGraph)) renderReachableNames.add(reachableName);
61985
- const componentFunction = context.cfg.enclosingFunction(componentBody);
61986
- const doesSetterInvalidateExternalLocation = isFunctionLike$1(componentFunction) ? createExternalLocationInvalidationChecker({
61987
- componentBody,
61988
- componentFunction,
61989
- context,
61990
- directRenderNames,
61991
- renderReachableExpressions
61992
- }) : () => false;
61993
60306
  const calledSetterNames = /* @__PURE__ */ new Set();
61994
60307
  walkAst(componentBody, (child) => {
61995
60308
  if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && setterNames.has(child.callee.name)) calledSetterNames.add(child.callee.name);
61996
60309
  });
61997
60310
  for (const binding of bindings) {
61998
60311
  if (renderReachableNames.has(binding.valueName)) continue;
61999
- const setterBindingIdentifier = isNodeOfType(binding.declarator.id, "ArrayPattern") ? binding.declarator.id.elements?.[1] : null;
62000
- if (setterBindingIdentifier && doesSetterInvalidateExternalLocation(setterBindingIdentifier)) continue;
62001
60312
  if (binding.valueName === "_" || binding.valueName.startsWith("_")) continue;
62002
60313
  const setterSuffix = binding.setterName.slice(3);
62003
60314
  if (/^(TriggerRender|ForceUpdate|Rerender|ForceRender|Tick|Bump|BumpVersion|InvalidateRender|Refresh|Repaint)$/i.test(setterSuffix)) continue;
@@ -64904,66 +63215,6 @@ const roleHasRequiredAriaProps = defineRule({
64904
63215
  } })
64905
63216
  });
64906
63217
  //#endregion
64907
- //#region src/plugin/constants/global-aria-properties.ts
64908
- const GLOBAL_ARIA_PROPERTIES = new Set([
64909
- "aria-atomic",
64910
- "aria-braillelabel",
64911
- "aria-brailleroledescription",
64912
- "aria-busy",
64913
- "aria-controls",
64914
- "aria-current",
64915
- "aria-describedby",
64916
- "aria-description",
64917
- "aria-details",
64918
- "aria-disabled",
64919
- "aria-dropeffect",
64920
- "aria-errormessage",
64921
- "aria-flowto",
64922
- "aria-grabbed",
64923
- "aria-haspopup",
64924
- "aria-hidden",
64925
- "aria-invalid",
64926
- "aria-keyshortcuts",
64927
- "aria-label",
64928
- "aria-labelledby",
64929
- "aria-live",
64930
- "aria-owns",
64931
- "aria-relevant",
64932
- "aria-roledescription"
64933
- ]);
64934
- //#endregion
64935
- //#region src/plugin/constants/prohibited-aria-properties-by-role.ts
64936
- const ACCESSIBLE_NAME_PROPERTIES = new Set([
64937
- "aria-braillelabel",
64938
- "aria-label",
64939
- "aria-labelledby"
64940
- ]);
64941
- const PROHIBITED_ARIA_PROPERTIES_BY_ROLE = {
64942
- caption: ACCESSIBLE_NAME_PROPERTIES,
64943
- code: ACCESSIBLE_NAME_PROPERTIES,
64944
- definition: ACCESSIBLE_NAME_PROPERTIES,
64945
- deletion: ACCESSIBLE_NAME_PROPERTIES,
64946
- emphasis: ACCESSIBLE_NAME_PROPERTIES,
64947
- generic: new Set([
64948
- "aria-braillelabel",
64949
- "aria-brailleroledescription",
64950
- "aria-label",
64951
- "aria-labelledby",
64952
- "aria-roledescription"
64953
- ]),
64954
- insertion: ACCESSIBLE_NAME_PROPERTIES,
64955
- mark: ACCESSIBLE_NAME_PROPERTIES,
64956
- none: ACCESSIBLE_NAME_PROPERTIES,
64957
- paragraph: ACCESSIBLE_NAME_PROPERTIES,
64958
- presentation: ACCESSIBLE_NAME_PROPERTIES,
64959
- strong: ACCESSIBLE_NAME_PROPERTIES,
64960
- subscript: ACCESSIBLE_NAME_PROPERTIES,
64961
- superscript: ACCESSIBLE_NAME_PROPERTIES,
64962
- term: ACCESSIBLE_NAME_PROPERTIES,
64963
- time: ACCESSIBLE_NAME_PROPERTIES,
64964
- tooltip: ACCESSIBLE_NAME_PROPERTIES
64965
- };
64966
- //#endregion
64967
63218
  //#region src/plugin/constants/role-supports-aria-props.ts
64968
63219
  const ROLE_SUPPORTS_ARIA_PROPS = {
64969
63220
  alert: new Set([
@@ -68064,12 +66315,6 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
68064
66315
  ])
68065
66316
  };
68066
66317
  //#endregion
68067
- //#region src/plugin/utils/is-aria-property-supported-by-role.ts
68068
- const isAriaPropertySupportedByRole = (role, property) => {
68069
- if (PROHIBITED_ARIA_PROPERTIES_BY_ROLE[role]?.has(property)) return false;
68070
- return ROLE_SUPPORTS_ARIA_PROPS[role]?.has(property) === true || GLOBAL_ARIA_PROPERTIES.has(property);
68071
- };
68072
- //#endregion
68073
66318
  //#region src/plugin/rules/a11y/role-supports-aria-props.ts
68074
66319
  const buildMessageDefault = (roles, propName) => {
68075
66320
  return `Screen reader users get no help from \`${propName}\` because role ${roles.map((role) => `\`${role}\``).join(" / ")} ignores it, so remove it or change the role.`;
@@ -68106,10 +66351,16 @@ const roleSupportsAriaProps = defineRule({
68106
66351
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
68107
66352
  const roleCandidates = roleAttribute ? getJsxPropStaticStringValues(roleAttribute, context.scopes) : [getImplicitRole(node, elementType, context.scopes)].filter((role) => role !== null);
68108
66353
  if (roleCandidates === null || roleCandidates.length === 0) return;
68109
- for (const role of roleCandidates) if (!VALID_ARIA_ROLES.has(role)) return;
66354
+ const supportedSets = [];
66355
+ for (const role of roleCandidates) {
66356
+ if (!VALID_ARIA_ROLES.has(role)) return;
66357
+ const supported = ROLE_SUPPORTS_ARIA_PROPS[role];
66358
+ if (!supported) return;
66359
+ supportedSets.push(supported);
66360
+ }
68110
66361
  const isImplicit = !roleAttribute;
68111
66362
  for (const { attribute, propName } of ariaAttributes) {
68112
- if (roleCandidates.some((role) => isAriaPropertySupportedByRole(role, propName))) continue;
66363
+ if (supportedSets.some((supported) => supported.has(propName))) continue;
68113
66364
  context.report({
68114
66365
  node: attribute,
68115
66366
  message: isImplicit ? buildMessageImplicit(roleCandidates[0], propName, elementType) : buildMessageDefault(roleCandidates, propName)
@@ -73173,6 +71424,18 @@ const reactDoctorRules = [
73173
71424
  requires: [...new Set(["react", ...jsxNoScriptUrl.requires ?? []])]
73174
71425
  }
73175
71426
  },
71427
+ {
71428
+ key: "react-doctor/jsx-no-target-blank",
71429
+ id: "jsx-no-target-blank",
71430
+ source: "react-doctor",
71431
+ originallyExternal: true,
71432
+ rule: {
71433
+ ...jsxNoTargetBlank,
71434
+ framework: "global",
71435
+ category: "Security",
71436
+ requires: [...new Set(["react", ...jsxNoTargetBlank.requires ?? []])]
71437
+ }
71438
+ },
73176
71439
  {
73177
71440
  key: "react-doctor/jsx-no-undef",
73178
71441
  id: "jsx-no-undef",
@@ -76879,7 +75142,7 @@ const EXTERNAL_RULES = [
76879
75142
  {
76880
75143
  key: "react-hooks-js/set-state-in-effect",
76881
75144
  source: "react-compiler",
76882
- severity: "warn"
75145
+ severity: "error"
76883
75146
  },
76884
75147
  {
76885
75148
  key: "react-hooks-js/globals",