oxlint-plugin-react-doctor 0.8.1-dev.90e794f → 0.8.1-dev.a2f9bf4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +46 -0
  2. package/dist/index.js +635 -2223
  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,20 +13958,6 @@ 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();
@@ -14147,7 +14006,7 @@ const resolveExpressionKey = (expression, context, visitedSymbolIds = /* @__PURE
14147
14006
  return objectKey ? `${objectKey}.${unwrappedExpression.property.name}` : null;
14148
14007
  }
14149
14008
  if (isNodeOfType(unwrappedExpression, "ThisExpression")) return "this";
14150
- if (isNodeOfType(unwrappedExpression, "Literal") && (typeof unwrappedExpression.value === "string" || typeof unwrappedExpression.value === "number" || typeof unwrappedExpression.value === "boolean")) return `literal:${String(unwrappedExpression.value)}`;
14009
+ if (isNodeOfType(unwrappedExpression, "Literal") && (typeof unwrappedExpression.value === "string" || typeof unwrappedExpression.value === "number")) return `literal:${String(unwrappedExpression.value)}`;
14151
14010
  if (isFunctionLike$1(unwrappedExpression)) {
14152
14011
  const rangeStart = getRangeStart(unwrappedExpression);
14153
14012
  return rangeStart === null ? null : `function:${rangeStart}`;
@@ -14176,11 +14035,9 @@ const resolveForEachProjection = (expression, context) => {
14176
14035
  const collectionKey = resolveExpressionKey(forEachCallee.object, context);
14177
14036
  if (!collectionKey) return null;
14178
14037
  const firstParameter = callbackNode.params[0];
14179
- const assignmentPattern = isNodeOfType(symbol.bindingIdentifier.parent, "AssignmentPattern") && symbol.bindingIdentifier.parent.left === symbol.bindingIdentifier ? symbol.bindingIdentifier.parent : null;
14180
- const propertyName = isNodeOfType(firstParameter, "ObjectPattern") ? getDestructuredBindingPropertyName(symbol.bindingIdentifier) : null;
14181
- const defaultValueKey = assignmentPattern ? resolveExpressionKey(assignmentPattern.right, context) : null;
14182
- if (assignmentPattern && !defaultValueKey) return null;
14183
- const parameterProjection = firstParameter === symbol.bindingIdentifier ? "value" : propertyName && defaultValueKey ? `${propertyName}=default:${defaultValueKey}` : propertyName;
14038
+ const bindingProperty = symbol.bindingIdentifier.parent;
14039
+ const propertyName = isNodeOfType(firstParameter, "ObjectPattern") && isNodeOfType(bindingProperty, "Property") && bindingProperty.parent === firstParameter && bindingProperty.value === symbol.bindingIdentifier ? getStaticPropertyKeyName(bindingProperty) : null;
14040
+ const parameterProjection = firstParameter === symbol.bindingIdentifier ? "value" : propertyName;
14184
14041
  if (!parameterProjection) return null;
14185
14042
  return {
14186
14043
  collectionKey,
@@ -14192,23 +14049,13 @@ const resolveForEachProjectionKey = (expression, context) => {
14192
14049
  return projection ? `forEach:${projection.collectionKey}:${projection.projectionKey}` : null;
14193
14050
  };
14194
14051
  const resolveResourceIdentityKey = (expression, context) => resolveForEachProjectionKey(expression, context) ?? resolveExpressionKey(expression, context);
14195
- const resolveEventListenerCaptureValueIdentityKey = (expression, context) => {
14196
- if (!expression) return null;
14197
- const directIdentityKey = resolveResourceIdentityKey(expression, context);
14198
- if (directIdentityKey) return directIdentityKey;
14199
- const unwrappedExpression = stripParenExpression(expression);
14200
- if (!isNodeOfType(unwrappedExpression, "BinaryExpression") && !isNodeOfType(unwrappedExpression, "LogicalExpression")) return null;
14201
- const leftIdentityKey = resolveEventListenerCaptureValueIdentityKey(unwrappedExpression.left, context);
14202
- const rightIdentityKey = resolveEventListenerCaptureValueIdentityKey(unwrappedExpression.right, context);
14203
- return leftIdentityKey && rightIdentityKey ? `${unwrappedExpression.type}:${unwrappedExpression.operator}:${leftIdentityKey}:${rightIdentityKey}` : null;
14204
- };
14205
14052
  const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqueOptionsIdentity) => {
14206
14053
  const capture = resolveEventListenerCapture(optionsNode, { allowIndeterminateEntries: true });
14207
14054
  if (capture !== null) return `capture:${String(capture)}`;
14208
14055
  if (!optionsNode) return null;
14209
14056
  const unwrappedOptions = stripParenExpression(optionsNode);
14210
14057
  if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) {
14211
- const optionsKey = allowOpaqueOptionsIdentity ? resolveEventListenerCaptureValueIdentityKey(unwrappedOptions, context) : null;
14058
+ const optionsKey = allowOpaqueOptionsIdentity ? resolveResourceIdentityKey(unwrappedOptions, context) : null;
14212
14059
  return optionsKey ? `options:${optionsKey}` : null;
14213
14060
  }
14214
14061
  let captureKey = "capture:false";
@@ -14223,7 +14070,7 @@ const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqu
14223
14070
  continue;
14224
14071
  }
14225
14072
  if (propertyName === "capture") {
14226
- const propertyValueKey = resolveEventListenerCaptureValueIdentityKey(property.value, context);
14073
+ const propertyValueKey = resolveResourceIdentityKey(property.value, context);
14227
14074
  captureKey = propertyValueKey ? `capture-value:${propertyValueKey}` : null;
14228
14075
  }
14229
14076
  }
@@ -14263,30 +14110,6 @@ const findAssignedResourceKey = (resourceNode, context) => {
14263
14110
  if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.right === currentNode) return resolveExpressionKey(parentNode.left, context);
14264
14111
  return null;
14265
14112
  };
14266
- const resolveStableMediaQueryListenerIdentityKey = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
14267
- if (!expression) return null;
14268
- const unwrappedExpression = stripParenExpression(expression);
14269
- if (isNodeOfType(unwrappedExpression, "Identifier")) {
14270
- const symbol = context.scopes.symbolFor(unwrappedExpression);
14271
- if (!symbol || visitedSymbolIds.has(symbol.id) || !symbol.references.every((reference) => reference.flag === "read" && !isWithinAssignmentTarget(reference.identifier))) return null;
14272
- const initializer = getDirectUnreassignedInitializer(symbol);
14273
- if (!initializer) return `symbol:${symbol.id}`;
14274
- const unwrappedInitializer = stripParenExpression(initializer);
14275
- if (!isNodeOfType(unwrappedInitializer, "Identifier")) return `symbol:${symbol.id}`;
14276
- const nextVisitedSymbolIds = new Set(visitedSymbolIds);
14277
- nextVisitedSymbolIds.add(symbol.id);
14278
- return resolveStableMediaQueryListenerIdentityKey(unwrappedInitializer, context, nextVisitedSymbolIds) ?? `symbol:${symbol.id}`;
14279
- }
14280
- if (isFunctionLike$1(unwrappedExpression)) {
14281
- const rangeStart = getRangeStart(unwrappedExpression);
14282
- return rangeStart === null ? null : `function:${rangeStart}`;
14283
- }
14284
- return null;
14285
- };
14286
- const isProvenLegacyMediaQueryListMethodCall = (callNode, methodName, context) => {
14287
- const callee = stripParenExpression(callNode.callee);
14288
- return callNode.arguments?.length === 1 && isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName && getProvenDomEventTargetPrototypeOwnerNames(callee.object, context.scopes).includes("MediaQueryList");
14289
- };
14290
14113
  const getCallRegistrationDetails = (callNode, context) => {
14291
14114
  const callee = stripParenExpression(callNode.callee);
14292
14115
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return {
@@ -14295,12 +14118,6 @@ const getCallRegistrationDetails = (callNode, context) => {
14295
14118
  eventKey: null,
14296
14119
  handlerKey: null
14297
14120
  };
14298
- if (isProvenLegacyMediaQueryListMethodCall(callNode, "addListener", context)) return {
14299
- receiverKey: resolveStableMediaQueryListenerIdentityKey(callee.object, context),
14300
- registrationVerbName: callee.property.name,
14301
- eventKey: null,
14302
- handlerKey: resolveStableMediaQueryListenerIdentityKey(callNode.arguments?.[0], context)
14303
- };
14304
14121
  return {
14305
14122
  receiverKey: resolveResourceIdentityKey(callee.object, context),
14306
14123
  registrationVerbName: callee.property.name,
@@ -14405,6 +14222,30 @@ const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, conte
14405
14222
  }
14406
14223
  return matchingBlocks.size > 0;
14407
14224
  };
14225
+ const doMatchingNodesCoverEveryPathFromFunctionEntry = (owner, matchingNodes, context) => {
14226
+ const functionCfg = context.cfg.cfgFor(owner);
14227
+ if (!functionCfg) return false;
14228
+ const matchingBlocks = new Set(matchingNodes.flatMap((matchingNode) => {
14229
+ if (context.cfg.enclosingFunction(matchingNode) !== owner) return [];
14230
+ const matchingBlock = functionCfg.blockOf(matchingNode);
14231
+ return matchingBlock ? [matchingBlock] : [];
14232
+ }));
14233
+ if (matchingBlocks.size === 0) return false;
14234
+ const visitedBlocks = new Set([functionCfg.entry]);
14235
+ const pendingBlocks = [functionCfg.entry];
14236
+ while (pendingBlocks.length > 0) {
14237
+ const currentBlock = pendingBlocks.pop();
14238
+ if (!currentBlock) break;
14239
+ if (matchingBlocks.has(currentBlock)) continue;
14240
+ for (const edge of currentBlock.successors) {
14241
+ if (edge.to === functionCfg.exit) return false;
14242
+ if (visitedBlocks.has(edge.to)) continue;
14243
+ visitedBlocks.add(edge.to);
14244
+ pendingBlocks.push(edge.to);
14245
+ }
14246
+ }
14247
+ return true;
14248
+ };
14408
14249
  const removeSynchronouslyReleasedUsages = (callback, usages, context) => {
14409
14250
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
14410
14251
  if (!isNodeOfType(callback.body, "BlockStatement")) return usages;
@@ -14647,6 +14488,18 @@ const findPushedResourceCollectionKey = (usage, context) => {
14647
14488
  return memberNode.property.name === "forEach" || memberNode.property.name === "push";
14648
14489
  }) ? resolveExpressionKey(pushCallee.object, context) : null;
14649
14490
  };
14491
+ const isWithinAssignmentTarget = (identifier) => {
14492
+ let currentNode = identifier;
14493
+ let parentNode = currentNode.parent;
14494
+ while (parentNode) {
14495
+ if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
14496
+ if (isNodeOfType(parentNode, "UpdateExpression") || isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "delete") return parentNode.argument === currentNode;
14497
+ if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
14498
+ currentNode = parentNode;
14499
+ parentNode = currentNode.parent;
14500
+ }
14501
+ return false;
14502
+ };
14650
14503
  const resolveStableValue = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
14651
14504
  if (!expression) return null;
14652
14505
  const unwrappedExpression = stripParenExpression(expression);
@@ -14770,7 +14623,7 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
14770
14623
  const helperFunction = isNodeOfType(stableHelperFunction, "Identifier") ? resolveSingleAssignedCleanupFunction(stableHelperFunction, usage, context) : stableHelperFunction;
14771
14624
  if (helperFunction && isFunctionLike$1(helperFunction) && !helperFunction.async && !helperFunction.generator && doesCleanupFunctionReleaseUsage(helperFunction, usage, context, new Set(visitedFunctions))) matchingLoopOrHelperAnchors.push(cleanupCall);
14772
14625
  });
14773
- return didCleanupFunctionMatch || doNodesCoverEveryPathFromFunctionEntry(cleanupFunction, matchingLoopOrHelperAnchors, context);
14626
+ return didCleanupFunctionMatch || doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, matchingLoopOrHelperAnchors, context);
14774
14627
  };
14775
14628
  const doesBoundCleanupReleaseUsage = (expression, usage, context) => {
14776
14629
  const callExpression = stripParenExpression(expression);
@@ -14799,7 +14652,7 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
14799
14652
  walkInsideStatementBlocks(callback.body, (child) => {
14800
14653
  if (isNodeOfType(child, "ReturnStatement") && child.argument && doesReturnedValueReleaseUsage(stripParenExpression(child.argument))) matchingCleanupReturns.push(child);
14801
14654
  });
14802
- return doNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
14655
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
14803
14656
  };
14804
14657
  const doesTestRequireLiveExpressionKey = (test, expressionKey, context) => {
14805
14658
  if (resolveExpressionKey(test, context) === expressionKey) return true;
@@ -14823,24 +14676,7 @@ const findLiveExpressionGuardForRelease = (releaseCall, owner, expressionKey, co
14823
14676
  return null;
14824
14677
  };
14825
14678
  const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => usage.handleKey === null ? null : findLiveExpressionGuardForRelease(releaseCall, owner, usage.handleKey, context);
14826
- const hasExecutionBoundaryNotSharedWithUsage = (node, usageNode, owner) => {
14827
- const usageAncestors = /* @__PURE__ */ new Set();
14828
- let usageAncestor = usageNode;
14829
- while (usageAncestor && usageAncestor !== owner) {
14830
- usageAncestors.add(usageAncestor);
14831
- usageAncestor = usageAncestor.parent ?? null;
14832
- }
14833
- let descendant = node;
14834
- let ancestor = descendant.parent ?? null;
14835
- while (ancestor && ancestor !== owner) {
14836
- const guardedSubtree = isNodeOfType(ancestor, "IfStatement") && (ancestor.consequent === descendant || ancestor.alternate === descendant) || isNodeOfType(ancestor, "ConditionalExpression") && (ancestor.consequent === descendant || ancestor.alternate === descendant) || isNodeOfType(ancestor, "LogicalExpression") && ancestor.right === descendant || isNodeOfType(ancestor, "AssignmentPattern") && ancestor.right === descendant || (isNodeOfType(ancestor, "ForStatement") || isNodeOfType(ancestor, "ForInStatement") || isNodeOfType(ancestor, "ForOfStatement") || isNodeOfType(ancestor, "WhileStatement") || isNodeOfType(ancestor, "DoWhileStatement")) && ancestor.body === descendant ? descendant : isNodeOfType(ancestor, "SwitchCase") ? ancestor : null;
14837
- if (guardedSubtree && !usageAncestors.has(guardedSubtree)) return true;
14838
- descendant = ancestor;
14839
- ancestor = descendant.parent ?? null;
14840
- }
14841
- return false;
14842
- };
14843
- const hasRerunReleaseBeforeUsage = (callback, usage, context, allowUnreleasedPathsWithoutUsage = false) => {
14679
+ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
14844
14680
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
14845
14681
  const functionCfg = context.cfg.cfgFor(callback);
14846
14682
  const usageBlock = functionCfg?.blockOf(usage.node);
@@ -14851,8 +14687,7 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context, allowUnreleasedPat
14851
14687
  if (!isNodeOfType(child, "CallExpression")) return;
14852
14688
  const releaseStart = getRangeStart(child);
14853
14689
  const handleGuard = findDirectHandleGuardForRelease(child, callback, usage, context);
14854
- const releaseBlock = functionCfg.blockOf(child);
14855
- if (releaseStart === null || releaseStart >= usageStart || releaseBlock !== usageBlock && !handleGuard || !handleGuard && hasExecutionBoundaryNotSharedWithUsage(child, usage.node, callback)) return;
14690
+ if (releaseStart === null || releaseStart >= usageStart || functionCfg.blockOf(child) !== usageBlock && !handleGuard) return;
14856
14691
  if (doesReleaseCallMatchUsage(child, usage, context)) {
14857
14692
  matchingReleaseAnchors.push(handleGuard ?? child);
14858
14693
  return;
@@ -14860,7 +14695,7 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context, allowUnreleasedPat
14860
14695
  const helperFunction = resolveStableValue(child.callee, context);
14861
14696
  if (helperFunction && isFunctionLike$1(helperFunction) && doesCleanupFunctionReleaseUsage(helperFunction, usage, context)) matchingReleaseAnchors.push(handleGuard ?? child);
14862
14697
  });
14863
- return allowUnreleasedPathsWithoutUsage ? doMatchingNodesCoverEveryPathBeforeUsage(usage.node, matchingReleaseAnchors, callback, context) : doNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
14698
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
14864
14699
  };
14865
14700
  const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
14866
14701
  const componentFunction = findEnclosingFunction$1(callback);
@@ -14880,7 +14715,7 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
14880
14715
  });
14881
14716
  return didFindUnmountCleanup;
14882
14717
  };
14883
- const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasStableUnmountCleanupForUsage(callback, usage, context) && hasRerunReleaseBeforeUsage(callback, usage, context, usage.registrationVerbName === "setTimeout");
14718
+ const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
14884
14719
  const collectBlockingBooleanStates = (expression, blockedExpressionValue, guardNode, context) => {
14885
14720
  const unwrappedExpression = stripParenExpression(expression);
14886
14721
  if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, guardNode, context);
@@ -15111,7 +14946,7 @@ const hasGuardedRefOwnedNestedCleanup = (callback, usage, cleanupReturns, contex
15111
14946
  const usageFunction = findEnclosingFunction$1(usage.node);
15112
14947
  const usageExpression = findTransparentExpressionRoot(usage.node);
15113
14948
  const usageAssignment = usageExpression.parent;
15114
- if (usage.kind !== "subscribe" && usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usageAssignment, "AssignmentExpression") || usageAssignment.operator !== "=" || usageAssignment.right !== usageExpression || !resolveReactRefSymbol(stripParenExpression(usageAssignment.left), context.scopes) || !collectSynchronouslyEffectInvokedFunctions(callback).has(usageFunction) || !cleanupReturnsReleaseUsage(cleanupReturns, usage, context) || !doNodesCoverEveryPathFromFunctionEntry(callback, cleanupReturns, context)) return false;
14949
+ if (usage.kind !== "subscribe" && usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usageAssignment, "AssignmentExpression") || usageAssignment.operator !== "=" || usageAssignment.right !== usageExpression || !resolveReactRefSymbol(stripParenExpression(usageAssignment.left), context.scopes) || !collectSynchronouslyEffectInvokedFunctions(callback).has(usageFunction) || !cleanupReturnsReleaseUsage(cleanupReturns, usage, context) || !doMatchingNodesCoverEveryPathFromFunctionEntry(callback, cleanupReturns, context)) return false;
15115
14950
  const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
15116
14951
  if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
15117
14952
  const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
@@ -15159,7 +14994,7 @@ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) =>
15159
14994
  });
15160
14995
  }
15161
14996
  });
15162
- if (!doNodesCoverEveryPathFromFunctionEntry(cleanupFunction, globalReleaseProofs.map((releaseProof) => releaseProof.anchor), context)) return false;
14997
+ if (!doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, globalReleaseProofs.map((releaseProof) => releaseProof.anchor), context)) return false;
15163
14998
  globalReleaseProofsByCleanup.set(cleanupFunction, globalReleaseProofs);
15164
14999
  }
15165
15000
  const handleAssignments = handleSymbol.references.filter((reference) => isWithinAssignmentTarget(reference.identifier));
@@ -15372,7 +15207,7 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
15372
15207
  });
15373
15208
  const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
15374
15209
  const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
15375
- return doNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
15210
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
15376
15211
  };
15377
15212
  const findDirectExhaustiveForEachCleanupFunction = (releaseNode, requiredCollectionKeys, context) => {
15378
15213
  let currentNode = findTransparentExpressionRoot(releaseNode);
@@ -15385,7 +15220,7 @@ const findDirectExhaustiveForEachCleanupFunction = (releaseNode, requiredCollect
15385
15220
  const isDirectConciseBody = ownerFunction.body === currentNode;
15386
15221
  const statementNode = currentNode.parent;
15387
15222
  const isDirectBlockStatement = isNodeOfType(ownerFunction.body, "BlockStatement") && isNodeOfType(statementNode, "ExpressionStatement") && statementNode.parent === ownerFunction.body;
15388
- if (!isDirectConciseBody && !isDirectBlockStatement || !doNodesCoverEveryPathFromFunctionEntry(ownerFunction, [isDirectBlockStatement ? statementNode : currentNode], context)) return null;
15223
+ if (!isDirectConciseBody && !isDirectBlockStatement || !doMatchingNodesCoverEveryPathFromFunctionEntry(ownerFunction, [isDirectBlockStatement ? statementNode : currentNode], context)) return null;
15389
15224
  const forEachCall = findEnclosingForEachCall(ownerFunction);
15390
15225
  if (!forEachCall) return replayedCollectionKeys.size === requiredCollectionKeys.size && isReturnedEffectCleanupFunction(ownerFunction, context) ? ownerFunction : null;
15391
15226
  const forEachCallee = stripParenExpression(forEachCall.callee);
@@ -15473,7 +15308,7 @@ const hasSafeForEachProjectionCleanup = (registrationCall, releaseCall, context)
15473
15308
  const registrationEventKey = resolveResourceIdentityKey(registrationCall.arguments?.[0], context);
15474
15309
  const releaseEventKey = resolveResourceIdentityKey(releaseCall.arguments?.[0], context);
15475
15310
  const doesHandlerlessOffReleaseEveryRegistration = releaseVerbName === "off" && !releaseHandler && (releaseCall.arguments?.length === 0 || registrationEventKey !== null && registrationEventKey === releaseEventKey);
15476
- if (Boolean(releaseFunction && isFunctionLike$1(releaseFunction) && isReturnedEffectCleanupFunction(releaseFunction, context) && doNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseCall], context)) && (releaseVerbName !== null && UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName) || doesHandlerlessOffReleaseEveryRegistration)) return true;
15311
+ if (Boolean(releaseFunction && isFunctionLike$1(releaseFunction) && isReturnedEffectCleanupFunction(releaseFunction, context) && doMatchingNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseCall], context)) && (releaseVerbName !== null && UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName) || doesHandlerlessOffReleaseEveryRegistration)) return true;
15477
15312
  const projections = [
15478
15313
  registrationCallee.object,
15479
15314
  registrationCall.arguments?.[0],
@@ -15520,7 +15355,6 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15520
15355
  if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
15521
15356
  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;
15522
15357
  if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
15523
- if (usage.registrationVerbName === "addListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1) return isProvenLegacyMediaQueryListMethodCall(usage.node, "addListener", context) && releaseVerbName === "removeListener" && isProvenLegacyMediaQueryListMethodCall(callNode, "removeListener", context) && usage.receiverKey !== null && resolveStableMediaQueryListenerIdentityKey(callee.object, context) === usage.receiverKey && usage.handlerKey !== null && resolveStableMediaQueryListenerIdentityKey(callNode.arguments?.[0], context) === usage.handlerKey;
15524
15358
  if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
15525
15359
  if (usage.registrationVerbName === "addEventListener" && releaseVerbName === "removeEventListener" && isNodeOfType(usage.node, "CallExpression")) {
15526
15360
  if (!isNodeOfType(stripParenExpression(usage.node.callee), "MemberExpression")) return false;
@@ -15672,7 +15506,7 @@ const hasEffectCleanupInvocation = (storage, usage, context) => {
15672
15506
  if (!cleanupFunction || !cleanupFunctionInvokesRef(cleanupFunction)) return;
15673
15507
  matchingReturns.push(child);
15674
15508
  });
15675
- return doNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15509
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15676
15510
  };
15677
15511
  let didFindInvocation = false;
15678
15512
  walkAst(componentFunction.body, (child) => {
@@ -15724,7 +15558,7 @@ const isRetainedDisposerRefRelease = (releaseNode, usage, context) => {
15724
15558
  return findRetainedDisposerStorages(disposerFunction, usage, context).some((storage) => isRetainedDisposerStorageEstablished(storage, usage, context) && !hasUnsafeRetainedDisposerOverwrite(storage, usage, context) && (hasEffectCleanupInvocation(storage, usage, context) || hasCallbackRefReplacementInvocation(storage, usage, context)));
15725
15559
  };
15726
15560
  const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
15727
- if (usage.kind !== "subscribe" || usage.registrationVerbName !== "addEventListener" || usage.receiverKey === null || usage.eventKey === null || !isNodeOfType(usage.node, "CallExpression") || !isFunctionLike$1(releaseFunction) || releaseFunction.async || releaseFunction.generator || !isNodeOfType(releaseFunction.body, "BlockStatement") || !doNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseNode], context)) return false;
15561
+ if (usage.kind !== "subscribe" || usage.registrationVerbName !== "addEventListener" || usage.receiverKey === null || usage.eventKey === null || !isNodeOfType(usage.node, "CallExpression") || !isFunctionLike$1(releaseFunction) || releaseFunction.async || releaseFunction.generator || !isNodeOfType(releaseFunction.body, "BlockStatement") || !doMatchingNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseNode], context)) return false;
15728
15562
  const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
15729
15563
  if (!isNodeOfType(releaseCall, "CallExpression")) return false;
15730
15564
  if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], releaseCall.arguments?.[2], context)) return false;
@@ -15952,7 +15786,7 @@ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retaine
15952
15786
  walkInsideStatementBlocks(effectCallback.body, (child) => {
15953
15787
  if (isNodeOfType(child, "ReturnStatement") && child.argument && matchesReturnedCleanup(stripParenExpression(child.argument))) matchingReturns.push(child);
15954
15788
  });
15955
- return doNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15789
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15956
15790
  };
15957
15791
  const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
15958
15792
  const componentFunction = findEnclosingFunction$1(retainedFunction);
@@ -15995,6 +15829,15 @@ const findUnconditionalReturnStatement = (expression, ownerFunction) => {
15995
15829
  const returnStatement = expressionRoot.parent;
15996
15830
  return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === expressionRoot && findEnclosingFunction$1(returnStatement) === ownerFunction ? returnStatement : null;
15997
15831
  };
15832
+ const getFinalSequenceExpressionValue = (expression) => {
15833
+ let finalExpression = stripParenExpression(expression);
15834
+ while (isNodeOfType(finalExpression, "SequenceExpression")) {
15835
+ const sequenceResult = finalExpression.expressions.at(-1);
15836
+ if (!sequenceResult) break;
15837
+ finalExpression = stripParenExpression(sequenceResult);
15838
+ }
15839
+ return finalExpression;
15840
+ };
15998
15841
  const doesResourceResultEscape = (resourceNode, allowReturnedResourceEscape, allowConciseReturnEscape, context) => {
15999
15842
  if (!allowReturnedResourceEscape) return false;
16000
15843
  let currentNode = resourceNode;
@@ -16243,7 +16086,7 @@ const isReactRefCallbackCleanupOwnedByEffect = (retainedFunction, cleanupFunctio
16243
16086
  if (child !== returnedCleanupFunction.body && isFunctionLike$1(child)) return false;
16244
16087
  if (isNodeOfType(child, "CallExpression") && resolveRefOwnedCleanupFunction(child.callee, context) === cleanupFunction) matchingCalls.push(child);
16245
16088
  });
16246
- return doNodesCoverEveryPathFromFunctionEntry(returnedCleanupFunction, matchingCalls, context);
16089
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(returnedCleanupFunction, matchingCalls, context);
16247
16090
  };
16248
16091
  const matchingReturns = [];
16249
16092
  walkInsideStatementBlocks(retainedFunction.body, (child) => {
@@ -19046,7 +18889,7 @@ const compileGlob = (pattern) => {
19046
18889
  };
19047
18890
  //#endregion
19048
18891
  //#region src/plugin/rules/react-builtins/forbid-component-props.ts
19049
- const resolveSettings$43 = (settings) => {
18892
+ const resolveSettings$44 = (settings) => {
19050
18893
  const reactDoctor = settings?.["react-doctor"];
19051
18894
  return { forbid: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidComponentProps ?? {} : {}).forbid ?? [] };
19052
18895
  };
@@ -19098,7 +18941,7 @@ const forbidComponentProps = defineRule({
19098
18941
  recommendation: "Configure blocked component props so callers cannot bypass the component API contract.",
19099
18942
  category: "Architecture",
19100
18943
  create: (context) => {
19101
- const entries = resolveSettings$43(context.settings).forbid.map(normalizeEntry);
18944
+ const entries = resolveSettings$44(context.settings).forbid.map(normalizeEntry);
19102
18945
  return { JSXOpeningElement(node) {
19103
18946
  if (entries.length === 0) return;
19104
18947
  if (!isSupportedJsxName(node.name)) return;
@@ -19127,7 +18970,7 @@ const forbidComponentProps = defineRule({
19127
18970
  //#endregion
19128
18971
  //#region src/plugin/rules/react-builtins/forbid-dom-props.ts
19129
18972
  const buildMessage$24 = (propName, customMessage) => customMessage ?? `Your project blocks the \`${propName}\` prop on plain HTML tags, so this bypasses the agreed DOM API contract.`;
19130
- const resolveSettings$42 = (settings) => {
18973
+ const resolveSettings$43 = (settings) => {
19131
18974
  const reactDoctor = settings?.["react-doctor"];
19132
18975
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidDomProps ?? {} : {};
19133
18976
  const map = /* @__PURE__ */ new Map();
@@ -19149,7 +18992,7 @@ const forbidDomProps = defineRule({
19149
18992
  recommendation: "Configure blocked DOM props so plain HTML tags stay on the agreed DOM API surface.",
19150
18993
  category: "Architecture",
19151
18994
  create: (context) => {
19152
- const forbidMap = resolveSettings$42(context.settings);
18995
+ const forbidMap = resolveSettings$43(context.settings);
19153
18996
  return { JSXOpeningElement(node) {
19154
18997
  if (forbidMap.size === 0) return;
19155
18998
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
@@ -19212,7 +19055,7 @@ const isReactFunctionCall = (node, expectedCall) => {
19212
19055
  //#endregion
19213
19056
  //#region src/plugin/rules/react-builtins/forbid-elements.ts
19214
19057
  const buildMessage$23 = (element, customHelp) => customHelp ? `Your project blocks \`<${element}>\` here. ${customHelp}` : `Your project blocks \`<${element}>\` here, so code stays on the approved UI surface.`;
19215
- const resolveSettings$41 = (settings) => {
19058
+ const resolveSettings$42 = (settings) => {
19216
19059
  const reactDoctor = settings?.["react-doctor"];
19217
19060
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidElements ?? {} : {};
19218
19061
  const map = /* @__PURE__ */ new Map();
@@ -19228,7 +19071,7 @@ const forbidElements = defineRule({
19228
19071
  recommendation: "Configure blocked elements so code stays on the approved UI primitives.",
19229
19072
  category: "Architecture",
19230
19073
  create: (context) => {
19231
- const forbidMap = resolveSettings$41(context.settings);
19074
+ const forbidMap = resolveSettings$42(context.settings);
19232
19075
  return {
19233
19076
  JSXOpeningElement(node) {
19234
19077
  if (forbidMap.size === 0) return;
@@ -19337,7 +19180,7 @@ const DEFAULT_HEADING_TAGS = [
19337
19180
  "h5",
19338
19181
  "h6"
19339
19182
  ];
19340
- const resolveSettings$40 = (settings) => {
19183
+ const resolveSettings$41 = (settings) => {
19341
19184
  const reactDoctor = settings?.["react-doctor"];
19342
19185
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.headingHasContent ?? {} : {};
19343
19186
  return { headingTags: new Set([...DEFAULT_HEADING_TAGS, ...ruleSettings.components ?? []]) };
@@ -19350,7 +19193,7 @@ const headingHasContent = defineRule({
19350
19193
  recommendation: "Put readable text in every heading.",
19351
19194
  category: "Accessibility",
19352
19195
  create: (context) => {
19353
- const settings = resolveSettings$40(context.settings);
19196
+ const settings = resolveSettings$41(context.settings);
19354
19197
  return { JSXOpeningElement(node) {
19355
19198
  const elementType = getElementType(node, context.settings);
19356
19199
  if (!settings.headingTags.has(elementType)) return;
@@ -19371,7 +19214,7 @@ const headingHasContent = defineRule({
19371
19214
  //#region src/plugin/rules/react-builtins/hook-use-state.ts
19372
19215
  const REQUIRE_DESTRUCTURE_MESSAGE = "`useState` should be destructured as `[value, setValue]` so readers can see the state value and setter together.";
19373
19216
  const NAMING_CONVENTION_MESSAGE = "This `useState` setter does not match its value name, so updates are harder to trace.";
19374
- const resolveSettings$39 = (settings) => {
19217
+ const resolveSettings$40 = (settings) => {
19375
19218
  const reactDoctor = settings?.["react-doctor"];
19376
19219
  return { allowDestructuredState: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.hookUseState ?? {} : {}).allowDestructuredState ?? false };
19377
19220
  };
@@ -19398,7 +19241,7 @@ const hookUseState = defineRule({
19398
19241
  recommendation: "Destructure `useState` as `const [thing, setThing] = useState(…)` so state reads and writes stay visible together.",
19399
19242
  category: "Architecture",
19400
19243
  create: (context) => {
19401
- const { allowDestructuredState } = resolveSettings$39(context.settings);
19244
+ const { allowDestructuredState } = resolveSettings$40(context.settings);
19402
19245
  return { CallExpression(node) {
19403
19246
  const callExpressionNode = node;
19404
19247
  if (!isReactFunctionCall(callExpressionNode, "useState")) return;
@@ -19531,7 +19374,7 @@ const hooksNoNanInDeps = defineRule({
19531
19374
  //#endregion
19532
19375
  //#region src/plugin/rules/a11y/html-has-lang.ts
19533
19376
  const MESSAGE$52 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
19534
- const resolveSettings$38 = (settings) => {
19377
+ const resolveSettings$39 = (settings) => {
19535
19378
  const reactDoctor = settings?.["react-doctor"];
19536
19379
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
19537
19380
  };
@@ -19568,7 +19411,7 @@ const htmlHasLang = defineRule({
19568
19411
  recommendation: "Set `<html lang=\"…\">` so screen readers know the page language.",
19569
19412
  category: "Accessibility",
19570
19413
  create: (context) => {
19571
- const settings = resolveSettings$38(context.settings);
19414
+ const settings = resolveSettings$39(context.settings);
19572
19415
  const tagSet = new Set(settings.htmlTags);
19573
19416
  return { JSXOpeningElement(node) {
19574
19417
  const tag = getElementType(node, context.settings);
@@ -20084,7 +19927,7 @@ const DEFAULT_REDUNDANT_WORDS = [
20084
19927
  "photo",
20085
19928
  "picture"
20086
19929
  ];
20087
- const resolveSettings$37 = (settings) => {
19930
+ const resolveSettings$38 = (settings) => {
20088
19931
  const reactDoctor = settings?.["react-doctor"];
20089
19932
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.imgRedundantAlt ?? {} : {};
20090
19933
  return {
@@ -20132,7 +19975,7 @@ const imgRedundantAlt = defineRule({
20132
19975
  category: "Accessibility",
20133
19976
  create: (context) => {
20134
19977
  if (isGeneratedImageRenderContext(context)) return {};
20135
- const settings = resolveSettings$37(context.settings);
19978
+ const settings = resolveSettings$38(context.settings);
20136
19979
  return { JSXOpeningElement(node) {
20137
19980
  const tag = getElementType(node, context.settings);
20138
19981
  if (!settings.components.includes(tag)) return;
@@ -20440,7 +20283,7 @@ const COMPOSITE_ITEM_ROLES = new Set([
20440
20283
  "row",
20441
20284
  "gridcell"
20442
20285
  ]);
20443
- const resolveSettings$36 = (settings) => {
20286
+ const resolveSettings$37 = (settings) => {
20444
20287
  const reactDoctor = settings?.["react-doctor"];
20445
20288
  return { tabbable: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.interactiveSupportsFocus ?? {} : {}).tabbable ?? DEFAULT_TABBABLE_ROLES };
20446
20289
  };
@@ -20452,7 +20295,7 @@ const interactiveSupportsFocus = defineRule({
20452
20295
  recommendation: "Add keyboard focus support so users can reach interactive elements without a pointer.",
20453
20296
  category: "Accessibility",
20454
20297
  create: (context) => {
20455
- const settings = resolveSettings$36(context.settings);
20298
+ const settings = resolveSettings$37(context.settings);
20456
20299
  const tabbableSet = new Set(settings.tabbable);
20457
20300
  return { JSXOpeningElement(node) {
20458
20301
  if (isLocalTestScaffoldJsx(node, context)) return;
@@ -20674,7 +20517,7 @@ const collectHandlerReferencedNames = (root) => {
20674
20517
  //#endregion
20675
20518
  //#region src/plugin/rules/jotai/jotai-select-atom-in-render-body.ts
20676
20519
  const JOTAI_SELECT_ATOM_SOURCES = ["jotai/utils", "jotai"];
20677
- const COMPONENT_NAME_PATTERN$1 = /^[A-Z]/;
20520
+ const COMPONENT_NAME_PATTERN = /^[A-Z]/;
20678
20521
  const isImportedSelectAtom = (callExpression) => {
20679
20522
  if (!isNodeOfType(callExpression.callee, "Identifier")) return false;
20680
20523
  const localName = callExpression.callee.name;
@@ -20710,14 +20553,14 @@ const getHandlerNamedBindingName = (functionNode) => {
20710
20553
  const containingFunctionIsComponentOrHook = (functionNode) => {
20711
20554
  if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.id) {
20712
20555
  const declaredName = functionNode.id.name;
20713
- return COMPONENT_NAME_PATTERN$1.test(declaredName) || HOOK_NAME_PATTERN$3.test(declaredName);
20556
+ return COMPONENT_NAME_PATTERN.test(declaredName) || HOOK_NAME_PATTERN$3.test(declaredName);
20714
20557
  }
20715
20558
  let cursor = functionNode.parent ?? null;
20716
20559
  while (cursor && isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
20717
20560
  if (!cursor) return false;
20718
20561
  if (!isNodeOfType(cursor, "VariableDeclarator")) return false;
20719
20562
  if (!isNodeOfType(cursor.id, "Identifier")) return false;
20720
- return COMPONENT_NAME_PATTERN$1.test(cursor.id.name) || HOOK_NAME_PATTERN$3.test(cursor.id.name);
20563
+ return COMPONENT_NAME_PATTERN.test(cursor.id.name) || HOOK_NAME_PATTERN$3.test(cursor.id.name);
20721
20564
  };
20722
20565
  const isBindingInvokedOnRenderPath = (root, bindingName) => {
20723
20566
  let didFindRenderPathInvocation = false;
@@ -22593,49 +22436,25 @@ const jsLengthCheckFirst = defineRule({
22593
22436
  });
22594
22437
  //#endregion
22595
22438
  //#region src/plugin/utils/is-proven-global-namespace-reference.ts
22596
- const GLOBAL_SELF_PROPERTY_NAMES = new Set([
22597
- "global",
22598
- "globalThis",
22599
- "self",
22600
- "window"
22601
- ]);
22602
22439
  const isProvenGlobalObjectReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
22603
- let currentExpression = expression;
22604
- while (true) {
22605
- const strippedExpression = stripParenExpression(currentExpression);
22606
- if (!isNodeOfType(strippedExpression, "Identifier")) return false;
22607
- if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
22608
- const symbol = scopes.symbolFor(strippedExpression);
22609
- if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
22610
- const declaration = symbol.declarationNode;
22611
- const initializer = declaration.init;
22612
- if (!initializer) return false;
22613
- const isDirectIdentifierAlias = isNodeOfType(declaration.id, "Identifier") && declaration.id === symbol.bindingIdentifier && initializer === symbol.initializer;
22614
- const bindingProperty = symbol.bindingIdentifier.parent;
22615
- const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
22616
- const isDirectGlobalSelfAlias = isNodeOfType(declaration.id, "ObjectPattern") && isNodeOfType(bindingProperty, "Property") && bindingProperty.value === symbol.bindingIdentifier && bindingProperty.parent === declaration.id && destructuredPropertyName !== null && GLOBAL_SELF_PROPERTY_NAMES.has(destructuredPropertyName);
22617
- if (!isDirectIdentifierAlias && !isDirectGlobalSelfAlias) return false;
22618
- visitedSymbolIds.add(symbol.id);
22619
- currentExpression = initializer;
22620
- }
22440
+ const strippedExpression = stripParenExpression(expression);
22441
+ if (!isNodeOfType(strippedExpression, "Identifier")) return false;
22442
+ if ((strippedExpression.name === "globalThis" || strippedExpression.name === "window" || strippedExpression.name === "self" || strippedExpression.name === "global") && scopes.isGlobalReference(strippedExpression)) return true;
22443
+ const symbol = scopes.symbolFor(strippedExpression);
22444
+ if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
22445
+ visitedSymbolIds.add(symbol.id);
22446
+ return isProvenGlobalObjectReference(symbol.initializer, scopes, visitedSymbolIds);
22621
22447
  };
22622
22448
  const isProvenGlobalNamespaceReference = (expression, namespaceName, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
22623
- let currentExpression = expression;
22624
- while (true) {
22625
- const strippedExpression = stripParenExpression(currentExpression);
22626
- if (!isNodeOfType(strippedExpression, "Identifier")) return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
22449
+ const strippedExpression = stripParenExpression(expression);
22450
+ if (isNodeOfType(strippedExpression, "Identifier")) {
22627
22451
  if (strippedExpression.name === namespaceName && scopes.isGlobalReference(strippedExpression)) return true;
22628
22452
  const symbol = scopes.symbolFor(strippedExpression);
22629
- if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
22453
+ if (!symbol?.initializer || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id)) return false;
22630
22454
  visitedSymbolIds.add(symbol.id);
22631
- const declaration = symbol.declarationNode;
22632
- const bindingIdentifier = symbol.bindingIdentifier;
22633
- const bindingProperty = bindingIdentifier.parent;
22634
- if (getDestructuredBindingPropertyName(bindingIdentifier) === namespaceName && isNodeOfType(declaration, "VariableDeclarator") && isNodeOfType(declaration.id, "ObjectPattern") && isNodeOfType(bindingProperty, "Property") && bindingProperty.value === bindingIdentifier && bindingProperty.parent === declaration.id && declaration.init) return isProvenGlobalObjectReference(declaration.init, scopes);
22635
- const directInitializer = getDirectConstInitializer(symbol);
22636
- if (!directInitializer) return false;
22637
- currentExpression = directInitializer;
22455
+ return isProvenGlobalNamespaceReference(symbol.initializer, namespaceName, scopes, visitedSymbolIds);
22638
22456
  }
22457
+ return isNodeOfType(strippedExpression, "MemberExpression") && getStaticPropertyName(strippedExpression) === namespaceName && isProvenGlobalObjectReference(strippedExpression.object, scopes);
22639
22458
  };
22640
22459
  //#endregion
22641
22460
  //#region src/plugin/rules/js-performance/js-min-max-loop.ts
@@ -23748,7 +23567,7 @@ const jsTosortedImmutable = defineRule({
23748
23567
  const NEVER_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
23749
23568
  const ALWAYS_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
23750
23569
  const FALSE_OMITTED_MESSAGE = (attributeName) => `\`${attributeName}={false}\` does nothing, so the explicit false value adds noise without changing output.`;
23751
- const resolveSettings$35 = (settings) => {
23570
+ const resolveSettings$36 = (settings) => {
23752
23571
  const reactDoctor = settings?.["react-doctor"];
23753
23572
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxBooleanValue ?? {} : {};
23754
23573
  return {
@@ -23766,7 +23585,7 @@ const jsxBooleanValue = defineRule({
23766
23585
  recommendation: "Use one boolean-attribute style so equivalent true props scan the same across the codebase.",
23767
23586
  category: "Architecture",
23768
23587
  create: (context) => {
23769
- const settings = resolveSettings$35(context.settings);
23588
+ const settings = resolveSettings$36(context.settings);
23770
23589
  const alwaysSet = new Set(settings.always);
23771
23590
  const neverSet = new Set(settings.never);
23772
23591
  return { JSXAttribute(node) {
@@ -23820,7 +23639,7 @@ const jsxBooleanValue = defineRule({
23820
23639
  const UNNECESSARY_BRACES_MESSAGE = "These curly braces wrap a literal value, so they add JSX noise without changing output.";
23821
23640
  const REQUIRED_BRACES_MESSAGE = "This JSX value needs `{ }` so React reads it as an expression instead of text.";
23822
23641
  const isAllowedMode = (value) => value === "always" || value === "never" || value === "ignore";
23823
- const resolveSettings$34 = (settings) => {
23642
+ const resolveSettings$35 = (settings) => {
23824
23643
  const reactDoctor = settings?.["react-doctor"];
23825
23644
  const ruleSettingsRaw = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxCurlyBracePresence : void 0;
23826
23645
  if (isAllowedMode(ruleSettingsRaw)) return {
@@ -23916,7 +23735,7 @@ const jsxCurlyBracePresence = defineRule({
23916
23735
  recommendation: "Use one JSX literal style so equivalent markup scans the same without extra JSX noise.",
23917
23736
  category: "Architecture",
23918
23737
  create: (context) => {
23919
- const settings = resolveSettings$34(context.settings);
23738
+ const settings = resolveSettings$35(context.settings);
23920
23739
  return {
23921
23740
  JSXAttribute(node) {
23922
23741
  const value = node.value;
@@ -23999,7 +23818,7 @@ const containsJsxElement = (root) => {
23999
23818
  //#region src/plugin/rules/react-builtins/jsx-filename-extension.ts
24000
23819
  const JSX_NOT_ALLOWED = (extension) => `This file contains JSX but uses a \`${extension}\` name, so the filename no longer signals JSX to readers or tooling conventions.`;
24001
23820
  const EXTENSION_ONLY_FOR_JSX = (extension) => `\`${extension}\` files are reserved for JSX here, so using that extension without JSX makes file-type conventions less useful.`;
24002
- const resolveSettings$33 = (settings) => {
23821
+ const resolveSettings$34 = (settings) => {
24003
23822
  const reactDoctor = settings?.["react-doctor"];
24004
23823
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFilenameExtension ?? {} : {};
24005
23824
  return {
@@ -24021,7 +23840,7 @@ const jsxFilenameExtension = defineRule({
24021
23840
  recommendation: "Name JSX files with the configured extension so file names accurately signal which files contain JSX.",
24022
23841
  category: "Architecture",
24023
23842
  create: (context) => {
24024
- const settings = resolveSettings$33(context.settings);
23843
+ const settings = resolveSettings$34(context.settings);
24025
23844
  const allowedExtensions = normalizeExtensions(settings.extensions);
24026
23845
  const filename = normalizeFilename(context.filename ?? "fixture.tsx");
24027
23846
  const extensionOnly = path.extname(filename).slice(1);
@@ -24062,7 +23881,7 @@ const jsxFilenameExtension = defineRule({
24062
23881
  //#region src/plugin/rules/react-builtins/jsx-fragments.ts
24063
23882
  const SYNTAX_MESSAGE = "`<React.Fragment>` is used where shorthand fragments are configured, so similar wrappers look different across the codebase.";
24064
23883
  const ELEMENT_MESSAGE = "Fragment shorthand is used where explicit fragments are configured, so similar wrappers look different across the codebase.";
24065
- const resolveSettings$32 = (settings) => {
23884
+ const resolveSettings$33 = (settings) => {
24066
23885
  const reactDoctor = settings?.["react-doctor"];
24067
23886
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFragments ?? {} : {}).mode ?? "syntax" };
24068
23887
  };
@@ -24074,7 +23893,7 @@ const jsxFragments = defineRule({
24074
23893
  recommendation: "Use one fragment style so identical wrappers do not look different across files.",
24075
23894
  category: "Architecture",
24076
23895
  create: (context) => {
24077
- const { mode } = resolveSettings$32(context.settings);
23896
+ const { mode } = resolveSettings$33(context.settings);
24078
23897
  return {
24079
23898
  JSXElement(node) {
24080
23899
  if (mode !== "syntax") return;
@@ -24118,7 +23937,7 @@ const buildPropRegex = (handlerPropPrefix) => {
24118
23937
  const escaped = tokens.map((token) => token.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("|");
24119
23938
  return new RegExp(`^(${escaped})[A-Z].*$`);
24120
23939
  };
24121
- const resolveSettings$31 = (settings) => {
23940
+ const resolveSettings$32 = (settings) => {
24122
23941
  const reactDoctor = settings?.["react-doctor"];
24123
23942
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxHandlerNames ?? {} : {};
24124
23943
  let handlerPrefix = DEFAULT_HANDLER_PREFIX;
@@ -24194,7 +24013,7 @@ const jsxHandlerNames = defineRule({
24194
24013
  recommendation: "Use the `on…` prefix for event-handler props and `handle…` for handlers so readers can trace event flow.",
24195
24014
  category: "Architecture",
24196
24015
  create: (context) => {
24197
- const settings = resolveSettings$31(context.settings);
24016
+ const settings = resolveSettings$32(context.settings);
24198
24017
  return { JSXAttribute(node) {
24199
24018
  if (settings.ignoreComponentNames.length > 0) {
24200
24019
  const opening = node.parent;
@@ -24269,19 +24088,6 @@ const hasJsxKeyAttribute = (openingElement) => {
24269
24088
  return false;
24270
24089
  };
24271
24090
  //#endregion
24272
- //#region src/plugin/utils/is-component-function.ts
24273
- const isFunctionAssignedToComponent = (functionNode) => {
24274
- let cursor = functionNode.parent ?? null;
24275
- while (isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
24276
- if (isNodeOfType(cursor, "VariableDeclarator") && isNodeOfType(cursor.id, "Identifier") && isUppercaseName(cursor.id.name)) return true;
24277
- return isNodeOfType(cursor, "ExportDefaultDeclaration");
24278
- };
24279
- const isComponentFunction$1 = (node) => {
24280
- if (!isFunctionLike$1(node)) return false;
24281
- if (isNodeOfType(node, "FunctionDeclaration")) return !node.id || node.id.name === "default" || isUppercaseName(node.id.name) || isNodeOfType(node.parent, "ExportDefaultDeclaration");
24282
- return isFunctionAssignedToComponent(node);
24283
- };
24284
- //#endregion
24285
24091
  //#region src/plugin/utils/is-const-declared-binding.ts
24286
24092
  const BINDING_PATTERN_ANCESTOR_TYPES = new Set([
24287
24093
  "VariableDeclarator",
@@ -24326,17 +24132,11 @@ const ITERATOR_METHOD_NAMES$1 = new Set([
24326
24132
  "flatMap",
24327
24133
  "from"
24328
24134
  ]);
24329
- const RENDERING_CALL_NAMES = new Set([
24330
- "createPortal",
24331
- "hydrate",
24332
- "hydrateRoot",
24333
- "render"
24334
- ]);
24335
24135
  const MISSING_KEY_ARRAY = "Your users can see the wrong data when this array reorders.";
24336
24136
  const MISSING_KEY_ITERATOR = "Your users can see the wrong data when this list reorders.";
24337
- const KEY_BEFORE_SPREAD = "Place this `key` after the `{...spread}` so the spread cannot override it.";
24137
+ const KEY_AFTER_SPREAD = "Place this `key` before the `{...spread}` so the JSX transform can always extract it.";
24338
24138
  const DUPLICATE_KEY = (keyValue) => `Your users can see the wrong data because two elements share the key "${keyValue}".`;
24339
- const resolveSettings$30 = (settings) => {
24139
+ const resolveSettings$31 = (settings) => {
24340
24140
  const reactDoctor = settings?.["react-doctor"];
24341
24141
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxKey ?? {} : {};
24342
24142
  return {
@@ -24350,33 +24150,6 @@ const findArrayVariableDeclarator = (arrayExpression) => {
24350
24150
  if (ancestor && isNodeOfType(ancestor, "VariableDeclarator") && ancestor.init === wrapped && isNodeOfType(ancestor.id, "Identifier")) return ancestor;
24351
24151
  return null;
24352
24152
  };
24353
- const isArrayNestedInObjectProperty = (arrayExpression) => {
24354
- let current = findTransparentExpressionRoot(arrayExpression);
24355
- while (current.parent) {
24356
- const parent = current.parent;
24357
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current) || isNodeOfType(parent, "LogicalExpression") && (parent.left === current || parent.right === current)) {
24358
- current = findTransparentExpressionRoot(parent);
24359
- continue;
24360
- }
24361
- return isNodeOfType(parent, "Property") && parent.value === current;
24362
- }
24363
- return false;
24364
- };
24365
- const isArrayPassedToNonRenderingCall = (arrayExpression, scopes) => {
24366
- const transparentArray = findTransparentExpressionRoot(arrayExpression);
24367
- const callExpression = transparentArray.parent;
24368
- if (!callExpression || !isNodeOfType(callExpression, "CallExpression") || !callExpression.arguments.some((argument) => argument === transparentArray)) return false;
24369
- const argumentIndex = callExpression.arguments.findIndex((argument) => argument === transparentArray);
24370
- if (isReactApiCall(callExpression, "createElement", scopes, {
24371
- allowGlobalReactNamespace: true,
24372
- allowUnboundBareCalls: true,
24373
- resolveNamedAliases: true
24374
- })) return argumentIndex < 2;
24375
- const callee = stripParenExpression(callExpression.callee);
24376
- if (isNodeOfType(callee, "Identifier")) return !RENDERING_CALL_NAMES.has(callee.name);
24377
- if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return !RENDERING_CALL_NAMES.has(callee.property.name);
24378
- return true;
24379
- };
24380
24153
  const isIdentityIteratorCallback = (callback) => {
24381
24154
  const callbackFunction = stripParenExpression(callback);
24382
24155
  if (!isNodeOfType(callbackFunction, "ArrowFunctionExpression") && !isNodeOfType(callbackFunction, "FunctionExpression")) return true;
@@ -24473,7 +24246,7 @@ const findNamedCallbackIteratorCall = (functionNode) => {
24473
24246
  namedCallbackIteratorCallCache.set(functionNode, iteratorCall);
24474
24247
  return iteratorCall;
24475
24248
  };
24476
- const findEnclosingIteratorContext = (jsxNode, scopes) => {
24249
+ const findEnclosingIteratorContext = (jsxNode) => {
24477
24250
  let current = jsxNode;
24478
24251
  let isOutsideContainingFunction = false;
24479
24252
  let didSeeReturnStatement = false;
@@ -24495,8 +24268,6 @@ const findEnclosingIteratorContext = (jsxNode, scopes) => {
24495
24268
  isOutsideContainingFunction = true;
24496
24269
  } else if (isNodeOfType(parent, "ArrayExpression")) {
24497
24270
  if (isOutsideContainingFunction) return null;
24498
- if (isArrayNestedInObjectProperty(parent)) return null;
24499
- if (isArrayPassedToNonRenderingCall(parent, scopes)) return null;
24500
24271
  const arrayParent = parent.parent;
24501
24272
  if (arrayParent && isNodeOfType(arrayParent, "Property")) return null;
24502
24273
  if (arrayParent && isNodeOfType(arrayParent, "ArrayExpression")) return null;
@@ -24578,15 +24349,9 @@ const isWithinChildrenToArray = (jsxNode) => {
24578
24349
  const isKeyPropertyName = (propertyKey) => {
24579
24350
  if (isNodeOfType(propertyKey, "Identifier")) return propertyKey.name === "key";
24580
24351
  if (isNodeOfType(propertyKey, "Literal")) return String(propertyKey.value) === "key";
24581
- return false;
24352
+ return true;
24582
24353
  };
24583
24354
  const isThisPropsMember = (expression) => isNodeOfType(expression, "MemberExpression") && !expression.computed && isNodeOfType(expression.object, "ThisExpression") && isNodeOfType(expression.property, "Identifier") && expression.property.name === "props";
24584
- const isDirectComponentPropsParameter = (bindingIdentifier, functionNode) => {
24585
- if (!isFunctionLike$1(functionNode) || !isComponentFunction$1(functionNode)) return false;
24586
- const firstParameter = functionNode.params?.[0];
24587
- if (!firstParameter) return false;
24588
- return (isNodeOfType(firstParameter, "AssignmentPattern") ? firstParameter.left : firstParameter) === bindingIdentifier;
24589
- };
24590
24355
  const isFunctionParameterPropsRest = (bindingIdentifier) => {
24591
24356
  const restElement = bindingIdentifier.parent;
24592
24357
  if (!restElement || !isNodeOfType(restElement, "RestElement")) return false;
@@ -24596,7 +24361,7 @@ const isFunctionParameterPropsRest = (bindingIdentifier) => {
24596
24361
  const parameterNode = patternParent && isNodeOfType(patternParent, "AssignmentPattern") && patternParent.left === objectPattern ? patternParent : objectPattern;
24597
24362
  const functionNode = parameterNode.parent;
24598
24363
  if (!functionNode || !isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "FunctionDeclaration")) return false;
24599
- return isComponentFunction$1(functionNode) && functionNode.params[0] === parameterNode;
24364
+ return functionNode.params.some((param) => param === parameterNode);
24600
24365
  };
24601
24366
  const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
24602
24367
  const restElement = bindingIdentifier.parent;
@@ -24609,14 +24374,14 @@ const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
24609
24374
  }
24610
24375
  return false;
24611
24376
  };
24612
- const isBindingAssignedKey = (scopeOwner, bindingName) => {
24613
- let didFindKeyAssignment = false;
24377
+ const isBindingObjectMutated = (scopeOwner, bindingName) => {
24378
+ let didFindMutation = false;
24614
24379
  walkAst(scopeOwner, (node) => {
24615
- if (didFindKeyAssignment) return false;
24380
+ if (didFindMutation) return false;
24616
24381
  if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression")) {
24617
24382
  const assignedObject = stripParenExpression(node.left.object);
24618
- if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName && isKeyPropertyName(node.left.property)) {
24619
- didFindKeyAssignment = true;
24383
+ if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName) {
24384
+ didFindMutation = true;
24620
24385
  return false;
24621
24386
  }
24622
24387
  }
@@ -24624,63 +24389,59 @@ const isBindingAssignedKey = (scopeOwner, bindingName) => {
24624
24389
  const callee = node.callee;
24625
24390
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Object" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "assign") {
24626
24391
  const firstArgument = node.arguments[0];
24627
- if (firstArgument && isNodeOfType(firstArgument, "Identifier") && firstArgument.name === bindingName) for (const sourceArgument of node.arguments.slice(1)) {
24628
- const source = stripParenExpression(sourceArgument);
24629
- if (!isNodeOfType(source, "ObjectExpression")) continue;
24630
- if (source.properties.some((property) => isNodeOfType(property, "Property") && isKeyPropertyName(property.key))) {
24631
- didFindKeyAssignment = true;
24632
- return false;
24633
- }
24392
+ if (firstArgument && isNodeOfType(firstArgument, "Identifier") && firstArgument.name === bindingName) {
24393
+ didFindMutation = true;
24394
+ return false;
24634
24395
  }
24635
24396
  }
24636
24397
  }
24637
24398
  });
24638
- return didFindKeyAssignment;
24399
+ return didFindMutation;
24639
24400
  };
24640
- const spreadExpressionHasKey = (expression, depth) => {
24401
+ const spreadExpressionCanCarryKey = (expression, depth) => {
24641
24402
  const inner = stripParenExpression(expression);
24642
24403
  if (isNodeOfType(inner, "ObjectExpression")) {
24643
24404
  for (const property of inner.properties) {
24644
24405
  if (isNodeOfType(property, "SpreadElement")) {
24645
- if (depth >= 3) continue;
24646
- if (spreadExpressionHasKey(property.argument, depth + 1)) return true;
24406
+ if (depth >= 3) return true;
24407
+ if (spreadExpressionCanCarryKey(property.argument, depth + 1)) return true;
24647
24408
  continue;
24648
24409
  }
24649
- if (!isNodeOfType(property, "Property")) continue;
24410
+ if (!isNodeOfType(property, "Property")) return true;
24411
+ if (property.computed) return true;
24650
24412
  if (isKeyPropertyName(property.key)) return true;
24651
24413
  }
24652
24414
  return false;
24653
24415
  }
24654
24416
  if (isNodeOfType(inner, "ConditionalExpression")) {
24655
- if (depth >= 3) return false;
24656
- return spreadExpressionHasKey(inner.consequent, depth + 1) || spreadExpressionHasKey(inner.alternate, depth + 1);
24417
+ if (depth >= 3) return true;
24418
+ return spreadExpressionCanCarryKey(inner.consequent, depth + 1) || spreadExpressionCanCarryKey(inner.alternate, depth + 1);
24657
24419
  }
24658
24420
  if (isNodeOfType(inner, "LogicalExpression")) {
24659
- if (depth >= 3) return false;
24660
- if (inner.operator === "&&") return spreadExpressionHasKey(inner.right, depth + 1);
24661
- return spreadExpressionHasKey(inner.left, depth + 1) || spreadExpressionHasKey(inner.right, depth + 1);
24421
+ if (depth >= 3) return true;
24422
+ if (inner.operator === "&&") return spreadExpressionCanCarryKey(inner.right, depth + 1);
24423
+ return spreadExpressionCanCarryKey(inner.left, depth + 1) || spreadExpressionCanCarryKey(inner.right, depth + 1);
24662
24424
  }
24663
24425
  if (isNodeOfType(inner, "Literal")) return false;
24664
24426
  if (isThisPropsMember(inner)) return false;
24665
24427
  if (isNodeOfType(inner, "Identifier")) {
24666
24428
  if (inner.name === "undefined") return false;
24667
- if (depth >= 3) return false;
24429
+ if (depth >= 3) return true;
24668
24430
  const binding = findVariableInitializer(inner, inner.name);
24669
- if (!binding) return false;
24431
+ if (!binding) return true;
24670
24432
  if (isRestBindingWithKeyExtracted(binding.bindingIdentifier)) return false;
24671
24433
  if (isFunctionParameterPropsRest(binding.bindingIdentifier)) return false;
24672
- if (isDirectComponentPropsParameter(binding.bindingIdentifier, binding.scopeOwner)) return false;
24673
- if (!isConstDeclaredBinding(binding) || !binding.initializer) return false;
24674
- if (isBindingAssignedKey(binding.scopeOwner, inner.name)) return true;
24675
- return spreadExpressionHasKey(binding.initializer, depth + 1);
24434
+ if (!isConstDeclaredBinding(binding) || !binding.initializer) return true;
24435
+ if (isBindingObjectMutated(binding.scopeOwner, inner.name)) return true;
24436
+ return spreadExpressionCanCarryKey(binding.initializer, depth + 1);
24676
24437
  }
24677
- return false;
24438
+ return true;
24678
24439
  };
24679
- const spreadCanOverwriteKey = (spreadAttribute) => spreadExpressionHasKey(spreadAttribute.argument, 0);
24440
+ const spreadCanOverwriteKey = (spreadAttribute) => spreadExpressionCanCarryKey(spreadAttribute.argument, 0);
24680
24441
  const checkKeyBeforeSpread = (context, openingElement) => {
24681
24442
  let keyIndex = null;
24682
24443
  let keyAttribute = null;
24683
- let lastKeyCarryingSpreadIndex = null;
24444
+ let firstKeyCarryingSpreadIndex = null;
24684
24445
  for (let attributeIndex = 0; attributeIndex < openingElement.attributes.length; attributeIndex++) {
24685
24446
  const attribute = openingElement.attributes[attributeIndex];
24686
24447
  if (isNodeOfType(attribute, "JSXAttribute")) {
@@ -24688,11 +24449,11 @@ const checkKeyBeforeSpread = (context, openingElement) => {
24688
24449
  keyIndex = attributeIndex;
24689
24450
  keyAttribute = attribute;
24690
24451
  }
24691
- } else if (isNodeOfType(attribute, "JSXSpreadAttribute") && spreadCanOverwriteKey(attribute)) lastKeyCarryingSpreadIndex = attributeIndex;
24452
+ } else if (isNodeOfType(attribute, "JSXSpreadAttribute") && firstKeyCarryingSpreadIndex === null && spreadCanOverwriteKey(attribute)) firstKeyCarryingSpreadIndex = attributeIndex;
24692
24453
  }
24693
- if (keyIndex !== null && lastKeyCarryingSpreadIndex !== null && lastKeyCarryingSpreadIndex > keyIndex && keyAttribute) context.report({
24454
+ if (keyIndex !== null && firstKeyCarryingSpreadIndex !== null && firstKeyCarryingSpreadIndex < keyIndex && keyAttribute) context.report({
24694
24455
  node: keyAttribute,
24695
- message: KEY_BEFORE_SPREAD
24456
+ message: KEY_AFTER_SPREAD
24696
24457
  });
24697
24458
  };
24698
24459
  const getKeyAttributeValueString = (openingElement) => {
@@ -24736,7 +24497,7 @@ const jsxKey = defineRule({
24736
24497
  severity: "error",
24737
24498
  recommendation: "Add a stable `key` prop so React can keep list items matched to the right data when the list changes.",
24738
24499
  create: (context) => {
24739
- const settings = resolveSettings$30(context.settings);
24500
+ const settings = resolveSettings$31(context.settings);
24740
24501
  return {
24741
24502
  JSXElement(node) {
24742
24503
  const openingElement = node.openingElement;
@@ -24754,7 +24515,7 @@ const jsxKey = defineRule({
24754
24515
  else seenKeys.add(keyValue.keyValue);
24755
24516
  }
24756
24517
  }
24757
- const enclosingContext = findEnclosingIteratorContext(node, context.scopes);
24518
+ const enclosingContext = findEnclosingIteratorContext(node);
24758
24519
  if (!enclosingContext) return;
24759
24520
  if (isWithinChildrenToArray(node)) return;
24760
24521
  if (hasJsxKeyAttribute(openingElement)) return;
@@ -24790,7 +24551,7 @@ const jsxKey = defineRule({
24790
24551
  //#region src/plugin/rules/react-builtins/jsx-max-depth.ts
24791
24552
  const buildMessage$19 = (depth, max) => `This JSX is hard to read at ${depth} levels deep, past the limit of ${max}.`;
24792
24553
  const DEFAULT_MAX_DEPTH = 14;
24793
- const resolveSettings$29 = (settings) => {
24554
+ const resolveSettings$30 = (settings) => {
24794
24555
  const reactDoctor = settings?.["react-doctor"];
24795
24556
  return { max: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxMaxDepth ?? {} : {}).max ?? DEFAULT_MAX_DEPTH };
24796
24557
  };
@@ -24859,7 +24620,7 @@ const jsxMaxDepth = defineRule({
24859
24620
  recommendation: "Pull deeply nested JSX into smaller components so it's easier to read.",
24860
24621
  category: "Architecture",
24861
24622
  create: (context) => {
24862
- const { max } = resolveSettings$29(context.settings);
24623
+ const { max } = resolveSettings$30(context.settings);
24863
24624
  const deepestLeafPerTree = /* @__PURE__ */ new Map();
24864
24625
  const checkNode = (node) => {
24865
24626
  if (!isLeafJsxNode(node)) return;
@@ -26831,7 +26592,7 @@ const jsxNoNewObjectAsProp = defineRule({
26831
26592
  //#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
26832
26593
  const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
26833
26594
  const JAVASCRIPT_URL_PATTERN = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;
26834
- const resolveSettings$28 = (settings) => {
26595
+ const resolveSettings$29 = (settings) => {
26835
26596
  const reactDoctor = settings?.["react-doctor"];
26836
26597
  if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
26837
26598
  return reactDoctor.jsxNoScriptUrl ?? {};
@@ -26853,7 +26614,7 @@ const jsxNoScriptUrl = defineRule({
26853
26614
  recommendation: "Use real event handlers instead of `javascript:` URLs so injected URL text cannot execute as code.",
26854
26615
  category: "Security",
26855
26616
  create: (context) => {
26856
- const options = resolveSettings$28(context.settings);
26617
+ const options = resolveSettings$29(context.settings);
26857
26618
  return { JSXOpeningElement(node) {
26858
26619
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
26859
26620
  const elementName = resolveJsxElementType(node);
@@ -26874,6 +26635,283 @@ const jsxNoScriptUrl = defineRule({
26874
26635
  }
26875
26636
  });
26876
26637
  //#endregion
26638
+ //#region src/plugin/rules/react-builtins/jsx-no-target-blank.ts
26639
+ const NOREFERRER_MESSAGE = "`target=\"_blank\"` without `rel=\"noreferrer\"` lets the linked page hijack your tab to a phishing site.";
26640
+ const NOOPENER_MESSAGE = "`target=\"_blank\"` without `rel` lets the linked page hijack your tab to a phishing site.";
26641
+ const SPREAD_MESSAGE = "A spread here can add `target=\"_blank\"`, letting the linked page hijack your tab to a phishing site.";
26642
+ const resolveSettings$28 = (settings) => {
26643
+ const reactDoctor = settings?.["react-doctor"];
26644
+ const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxNoTargetBlank ?? {} : {};
26645
+ const reactSettings = typeof settings?.react === "object" && settings.react !== null ? settings.react : {};
26646
+ const linkComponents = /* @__PURE__ */ new Map();
26647
+ for (const entry of reactSettings.linkComponents ?? []) if (typeof entry === "string") linkComponents.set(entry, ["href"]);
26648
+ else if (typeof entry === "object" && entry !== null) {
26649
+ const linkAttribute = entry.linkAttribute ?? "href";
26650
+ linkComponents.set(entry.name, Array.isArray(linkAttribute) ? linkAttribute : [linkAttribute]);
26651
+ }
26652
+ const formComponents = /* @__PURE__ */ new Map();
26653
+ for (const entry of reactSettings.formComponents ?? []) if (typeof entry === "string") formComponents.set(entry, ["action"]);
26654
+ else if (typeof entry === "object" && entry !== null) {
26655
+ const formAttribute = entry.formAttribute ?? "action";
26656
+ formComponents.set(entry.name, Array.isArray(formAttribute) ? formAttribute : [formAttribute]);
26657
+ }
26658
+ return {
26659
+ enforceDynamicLinks: ruleSettings.enforceDynamicLinks ?? "always",
26660
+ warnOnSpreadAttributes: ruleSettings.warnOnSpreadAttributes ?? false,
26661
+ allowReferrer: ruleSettings.allowReferrer ?? false,
26662
+ links: ruleSettings.links ?? true,
26663
+ forms: ruleSettings.forms ?? false,
26664
+ linkComponents,
26665
+ formComponents
26666
+ };
26667
+ };
26668
+ const isExternalLink = (href) => href.includes("//");
26669
+ const matchHrefExpression = (expression, state) => {
26670
+ if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") {
26671
+ if (isExternalLink(expression.value)) state.isExternal = true;
26672
+ return;
26673
+ }
26674
+ if (isNodeOfType(expression, "Identifier")) {
26675
+ state.isDynamic = true;
26676
+ return;
26677
+ }
26678
+ if (isNodeOfType(expression, "ConditionalExpression")) {
26679
+ matchHrefExpression(expression.consequent, state);
26680
+ matchHrefExpression(expression.alternate, state);
26681
+ }
26682
+ };
26683
+ const checkHref = (attributeValue, enforceDynamicLinks) => {
26684
+ const state = {
26685
+ isExternal: false,
26686
+ isDynamic: false
26687
+ };
26688
+ if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") state.isExternal = isExternalLink(attributeValue.value);
26689
+ else if (isNodeOfType(attributeValue, "JSXExpressionContainer")) matchHrefExpression(attributeValue.expression, state);
26690
+ if (enforceDynamicLinks === "never") return !state.isExternal || state.isDynamic;
26691
+ return !(state.isExternal || state.isDynamic);
26692
+ };
26693
+ const checkRelValue = (text, allowReferrer) => {
26694
+ const tokens = text.split(/\s+/);
26695
+ if (allowReferrer) return tokens.includes("noopener") || tokens.includes("noreferrer");
26696
+ return tokens.some((token) => token.toLowerCase() === "noreferrer");
26697
+ };
26698
+ const matchRelExpression = (expression, allowReferrer) => {
26699
+ const empty = {
26700
+ combined: false,
26701
+ testName: "",
26702
+ consequent: false,
26703
+ alternate: false
26704
+ };
26705
+ if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
26706
+ combined: checkRelValue(expression.value, allowReferrer),
26707
+ testName: "",
26708
+ consequent: false,
26709
+ alternate: false
26710
+ };
26711
+ if (isNodeOfType(expression, "ConditionalExpression")) {
26712
+ const consequent = matchRelExpression(expression.consequent, allowReferrer);
26713
+ const alternate = matchRelExpression(expression.alternate, allowReferrer);
26714
+ const test = expression.test;
26715
+ if (isNodeOfType(test, "Identifier")) return {
26716
+ combined: consequent.combined && alternate.combined,
26717
+ testName: test.name,
26718
+ consequent: consequent.combined,
26719
+ alternate: alternate.combined
26720
+ };
26721
+ return {
26722
+ combined: consequent.combined && alternate.combined,
26723
+ testName: "",
26724
+ consequent: consequent.combined,
26725
+ alternate: alternate.combined
26726
+ };
26727
+ }
26728
+ return empty;
26729
+ };
26730
+ const checkRel = (attributeValue, allowReferrer) => {
26731
+ const empty = {
26732
+ combined: false,
26733
+ testName: "",
26734
+ consequent: false,
26735
+ alternate: false
26736
+ };
26737
+ if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
26738
+ combined: checkRelValue(attributeValue.value, allowReferrer),
26739
+ testName: "",
26740
+ consequent: false,
26741
+ alternate: false
26742
+ };
26743
+ if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
26744
+ const expression = attributeValue.expression;
26745
+ if (expression.type === "JSXEmptyExpression") return empty;
26746
+ return matchRelExpression(expression, allowReferrer);
26747
+ }
26748
+ return empty;
26749
+ };
26750
+ const matchTargetExpression = (expression) => {
26751
+ const empty = {
26752
+ combined: false,
26753
+ testName: "",
26754
+ consequent: false,
26755
+ alternate: false
26756
+ };
26757
+ if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
26758
+ combined: expression.value.toLowerCase() === "_blank",
26759
+ testName: "",
26760
+ consequent: false,
26761
+ alternate: false
26762
+ };
26763
+ if (isNodeOfType(expression, "ConditionalExpression")) {
26764
+ const consequent = matchTargetExpression(expression.consequent);
26765
+ const alternate = matchTargetExpression(expression.alternate);
26766
+ const test = expression.test;
26767
+ const combined = consequent.combined || alternate.combined;
26768
+ if (isNodeOfType(test, "Identifier")) return {
26769
+ combined,
26770
+ testName: test.name,
26771
+ consequent: consequent.combined,
26772
+ alternate: alternate.combined
26773
+ };
26774
+ return {
26775
+ combined,
26776
+ testName: "",
26777
+ consequent: consequent.combined,
26778
+ alternate: alternate.combined
26779
+ };
26780
+ }
26781
+ return empty;
26782
+ };
26783
+ const checkTarget = (attributeValue) => {
26784
+ if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
26785
+ combined: attributeValue.value.toLowerCase() === "_blank",
26786
+ testName: "",
26787
+ consequent: false,
26788
+ alternate: false
26789
+ };
26790
+ if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
26791
+ const expression = attributeValue.expression;
26792
+ if (expression.type === "JSXEmptyExpression") return {
26793
+ combined: false,
26794
+ testName: "",
26795
+ consequent: false,
26796
+ alternate: false
26797
+ };
26798
+ return matchTargetExpression(expression);
26799
+ }
26800
+ return {
26801
+ combined: false,
26802
+ testName: "",
26803
+ consequent: false,
26804
+ alternate: false
26805
+ };
26806
+ };
26807
+ const jsxNoTargetBlank = defineRule({
26808
+ id: "jsx-no-target-blank",
26809
+ title: "Unsafe target=_blank link",
26810
+ severity: "warn",
26811
+ recommendation: "Add `rel=\"noreferrer\"` (or `\"noopener\"`) when using `target=\"_blank\"`.",
26812
+ category: "Security",
26813
+ create: (context) => {
26814
+ const settings = resolveSettings$28(context.settings);
26815
+ const isLink = (tagName) => {
26816
+ if (!settings.links) return false;
26817
+ if (tagName === "a") return true;
26818
+ return settings.linkComponents.has(tagName);
26819
+ };
26820
+ const isForm = (tagName) => {
26821
+ if (!settings.forms) return false;
26822
+ if (tagName === "form") return true;
26823
+ return settings.formComponents.has(tagName);
26824
+ };
26825
+ return { JSXOpeningElement(node) {
26826
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
26827
+ const tagName = resolveJsxElementType(node);
26828
+ if (!tagName) return;
26829
+ if (!isLink(tagName) && !isForm(tagName)) return;
26830
+ const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
26831
+ const formAttributeNames = settings.formComponents.get(tagName) ?? ["action"];
26832
+ let targetTuple = {
26833
+ combined: false,
26834
+ testName: "",
26835
+ consequent: false,
26836
+ alternate: false
26837
+ };
26838
+ let relTuple = {
26839
+ combined: false,
26840
+ testName: "",
26841
+ consequent: false,
26842
+ alternate: false
26843
+ };
26844
+ let isHrefValid = true;
26845
+ let hasHrefValue = false;
26846
+ let warnSpread = false;
26847
+ let targetReportNode = node.name;
26848
+ let spreadReportNode = null;
26849
+ for (const attribute of node.attributes) {
26850
+ if (isNodeOfType(attribute, "JSXSpreadAttribute")) {
26851
+ if (settings.warnOnSpreadAttributes) {
26852
+ warnSpread = true;
26853
+ spreadReportNode = attribute;
26854
+ targetTuple = {
26855
+ combined: false,
26856
+ testName: "",
26857
+ consequent: false,
26858
+ alternate: false
26859
+ };
26860
+ relTuple = {
26861
+ combined: false,
26862
+ testName: "",
26863
+ consequent: false,
26864
+ alternate: false
26865
+ };
26866
+ isHrefValid = false;
26867
+ hasHrefValue = true;
26868
+ }
26869
+ continue;
26870
+ }
26871
+ if (!isNodeOfType(attribute, "JSXAttribute")) continue;
26872
+ const attributeName = attribute.name;
26873
+ if (!isNodeOfType(attributeName, "JSXIdentifier")) continue;
26874
+ const propName = attributeName.name;
26875
+ const value = attribute.value;
26876
+ if (propName === "target") {
26877
+ if (value) {
26878
+ targetTuple = checkTarget(value);
26879
+ targetReportNode = value;
26880
+ }
26881
+ } else if (propName === "href" || propName === "action" || linkAttributeNames.includes(propName) || formAttributeNames.includes(propName)) {
26882
+ if (value) {
26883
+ hasHrefValue = true;
26884
+ isHrefValid = checkHref(value, settings.enforceDynamicLinks);
26885
+ }
26886
+ } else if (propName === "rel" && value) relTuple = checkRel(value, settings.allowReferrer);
26887
+ }
26888
+ if (warnSpread) {
26889
+ if (hasHrefValue && isHrefValid || relTuple.combined) return;
26890
+ context.report({
26891
+ node: spreadReportNode ?? node,
26892
+ message: SPREAD_MESSAGE
26893
+ });
26894
+ return;
26895
+ }
26896
+ if (!isHrefValid) {
26897
+ if (targetTuple.testName !== "" && targetTuple.testName === relTuple.testName) {
26898
+ const consequentBad = targetTuple.consequent && !relTuple.consequent;
26899
+ const alternateBad = targetTuple.alternate && !relTuple.alternate;
26900
+ if (consequentBad || alternateBad) context.report({
26901
+ node: targetReportNode,
26902
+ message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
26903
+ });
26904
+ return;
26905
+ }
26906
+ if (targetTuple.combined && !relTuple.combined) context.report({
26907
+ node: targetReportNode,
26908
+ message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
26909
+ });
26910
+ }
26911
+ } };
26912
+ }
26913
+ });
26914
+ //#endregion
26877
26915
  //#region src/plugin/rules/react-builtins/jsx-no-undef.ts
26878
26916
  const buildMessage$18 = (name) => `\`${name}\` crashes at runtime because it isn't defined here.`;
26879
26917
  const KNOWN_GLOBALS = new Set([
@@ -28063,7 +28101,7 @@ const mdxSsrExecutionRisk = defineRule({
28063
28101
  recommendation: "Use a constrained compiler for untrusted content, disable expressions/raw HTML, sandbox renderers, and avoid caching attacker-controlled output across tenants.",
28064
28102
  scan: scanByPattern({
28065
28103
  shouldScan: (file) => isProductionSourcePath(file.relativePath),
28066
- pattern: /(?:@mdx-js\/mdx|next-mdx-remote|\b(?:MDXRemote|compileMDX|evaluateMdx)\b)[\s\S]{0,700}\b(?:repo\b|customer|tenant|user[-_]?(?:content|markdown|mdx|input|provided|generated|submitted)|untrusted|searchParams|req\.|request\.|fetch\s*\(|prisma\.|db\.|database|rehypeRaw|allowDangerousHtml)/i,
28104
+ pattern: /(?:@mdx-js\/mdx|next-mdx-remote|\b(?:MDXRemote|compileMDX|evaluateMdx)\b)[\s\S]{0,700}\b(?:repo|customer|tenant|user[-_]?(?:content|markdown|mdx|input|provided|generated|submitted)|untrusted|searchParams|req\.|request\.|fetch\s*\(|prisma\.|db\.|database|rehypeRaw|allowDangerousHtml)/i,
28067
28105
  message: "MDX/markdown rendering code may evaluate user or repository content during SSR or static generation."
28068
28106
  })
28069
28107
  });
@@ -28531,30 +28569,6 @@ const hasAncestorLayoutMatching = (pageFilename, matchesLayout) => {
28531
28569
  const hasAncestorMetadataLayout = (pageFilename) => hasAncestorLayoutMatching(pageFilename, (layoutPath) => METADATA_EXPORT_NAMES.some((exportName) => doesModuleExportName(layoutPath, exportName)));
28532
28570
  //#endregion
28533
28571
  //#region src/plugin/rules/nextjs/nextjs-missing-metadata.ts
28534
- const unwrapAwaitedExpression = (node) => {
28535
- let expression = stripParenExpression(node);
28536
- while (isNodeOfType(expression, "AwaitExpression")) expression = stripParenExpression(expression.argument);
28537
- return expression;
28538
- };
28539
- const isNextNavigationRedirectCall = (node, context) => {
28540
- const expression = unwrapAwaitedExpression(node);
28541
- if (!isNodeOfType(expression, "CallExpression")) return false;
28542
- const callee = stripParenExpression(expression.callee);
28543
- if (!isNodeOfType(callee, "Identifier")) return false;
28544
- if (context.scopes.symbolFor(callee)?.kind !== "import") return false;
28545
- const importedName = getImportedNameFromModule(expression, callee.name, "next/navigation");
28546
- return importedName === "redirect" || importedName === "permanentRedirect";
28547
- };
28548
- const isRedirectOnlyDefaultExport = (programNode, context) => {
28549
- const defaultExport = findExportedValue(programNode, "default");
28550
- if (!defaultExport || !isFunctionLike$1(defaultExport)) return false;
28551
- if (!isNodeOfType(defaultExport.body, "BlockStatement")) return isNextNavigationRedirectCall(defaultExport.body, context);
28552
- if (defaultExport.body.body.length !== 1) return false;
28553
- const onlyStatement = defaultExport.body.body[0];
28554
- if (isNodeOfType(onlyStatement, "ExpressionStatement")) return isNextNavigationRedirectCall(onlyStatement.expression, context);
28555
- if (isNodeOfType(onlyStatement, "ReturnStatement") && onlyStatement.argument) return isNextNavigationRedirectCall(onlyStatement.argument, context);
28556
- return false;
28557
- };
28558
28572
  const nextjsMissingMetadata = defineRule({
28559
28573
  id: "nextjs-missing-metadata",
28560
28574
  title: "Page missing metadata for search previews",
@@ -28574,7 +28588,6 @@ const nextjsMissingMetadata = defineRule({
28574
28588
  if (isNodeOfType(declaration, "FunctionDeclaration")) return declaration.id?.name === "generateMetadata";
28575
28589
  return (statement.specifiers ?? []).some((specifier) => isNodeOfType(specifier, "ExportSpecifier") && isNodeOfType(specifier.exported, "Identifier") && METADATA_EXPORT_NAMES.includes(specifier.exported.name));
28576
28590
  })) return;
28577
- if (isRedirectOnlyDefaultExport(programNode, context)) return;
28578
28591
  if (hasAncestorMetadataLayout(context.filename ?? "")) return;
28579
28592
  context.report({
28580
28593
  node: programNode,
@@ -34703,19 +34716,6 @@ const noCascadingSetState = defineRetiredRule({
34703
34716
  recommendation: "Retired: React batches synchronous state updates from one effect into the same follow-up commit, so setter count does not prove repeated redraws."
34704
34717
  });
34705
34718
  //#endregion
34706
- //#region src/plugin/utils/get-react-use-callback-call.ts
34707
- const getReactUseCallbackCall = (expression, scopes) => {
34708
- const identifier = stripParenExpression(expression);
34709
- if (!isNodeOfType(identifier, "Identifier")) return null;
34710
- const symbol = resolveConstIdentifierAlias(identifier, scopes);
34711
- if (symbol?.kind !== "const" || !symbol.initializer) return null;
34712
- const initializer = stripParenExpression(symbol.initializer);
34713
- return isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, "useCallback", scopes, {
34714
- allowGlobalReactNamespace: true,
34715
- resolveNamedAliases: true
34716
- }) ? initializer : null;
34717
- };
34718
- //#endregion
34719
34719
  //#region src/plugin/rules/state-and-effects/utils/create-state-trigger-reachability.ts
34720
34720
  const getRefCurrentSymbol = (node, context) => {
34721
34721
  const expression = stripParenExpression(node);
@@ -35244,8 +35244,19 @@ const isBuiltinNamespaceCallee = (callee) => {
35244
35244
  }
35245
35245
  return false;
35246
35246
  };
35247
+ const getReactUseCallbackSource = (reference, context) => {
35248
+ const identifier = reference.identifier;
35249
+ const symbol = resolveConstIdentifierAlias(identifier, context.scopes);
35250
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer) return null;
35251
+ const initializer = stripParenExpression(symbol.initializer);
35252
+ if (isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, "useCallback", context.scopes, {
35253
+ allowGlobalReactNamespace: true,
35254
+ resolveNamedAliases: true
35255
+ })) return initializer;
35256
+ return null;
35257
+ };
35247
35258
  const getDependencyStateRefs = (analysis, context, dependencyReference) => {
35248
- const useCallbackCall = getReactUseCallbackCall(dependencyReference.identifier, context.scopes);
35259
+ const useCallbackCall = getReactUseCallbackSource(dependencyReference, context);
35249
35260
  if (!useCallbackCall) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
35250
35261
  const dependencyList = useCallbackCall.arguments?.[1];
35251
35262
  if (!dependencyList || !isNodeOfType(dependencyList, "ArrayExpression")) return getUpstreamRefs(analysis, dependencyReference).filter((reference) => isState(analysis, reference));
@@ -36833,6 +36844,19 @@ const noDefaultProps = defineRule({
36833
36844
  } })
36834
36845
  });
36835
36846
  //#endregion
36847
+ //#region src/plugin/utils/is-component-function.ts
36848
+ const isFunctionAssignedToComponent = (functionNode) => {
36849
+ let cursor = functionNode.parent ?? null;
36850
+ while (isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
36851
+ if (isNodeOfType(cursor, "VariableDeclarator") && isNodeOfType(cursor.id, "Identifier") && isUppercaseName(cursor.id.name)) return true;
36852
+ return isNodeOfType(cursor, "ExportDefaultDeclaration");
36853
+ };
36854
+ const isComponentFunction$1 = (node) => {
36855
+ if (!isFunctionLike$1(node)) return false;
36856
+ if (isNodeOfType(node, "FunctionDeclaration")) return !node.id || node.id.name === "default" || isUppercaseName(node.id.name) || isNodeOfType(node.parent, "ExportDefaultDeclaration");
36857
+ return isFunctionAssignedToComponent(node);
36858
+ };
36859
+ //#endregion
36836
36860
  //#region src/plugin/utils/create-component-prop-stack-tracker.ts
36837
36861
  const extractDestructuredPropNames = (params) => {
36838
36862
  const propNames = /* @__PURE__ */ new Set();
@@ -38746,17 +38770,6 @@ const noDynamicImportPath = defineRule({
38746
38770
  })
38747
38771
  });
38748
38772
  //#endregion
38749
- //#region src/plugin/utils/get-require-call-source.ts
38750
- const getRequireCallSource = (expression) => {
38751
- const unwrappedExpression = stripParenExpression(expression);
38752
- if (isNodeOfType(unwrappedExpression, "MemberExpression")) return getRequireCallSource(unwrappedExpression.object);
38753
- if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
38754
- if (!isNodeOfType(unwrappedExpression.callee, "Identifier") || unwrappedExpression.callee.name !== "require") return null;
38755
- const [firstArgument] = unwrappedExpression.arguments ?? [];
38756
- if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
38757
- return typeof firstArgument.value === "string" ? firstArgument.value : null;
38758
- };
38759
- //#endregion
38760
38773
  //#region src/plugin/rules/state-and-effects/utils/is-cleanup-return.ts
38761
38774
  const ITERATOR_CALLBACK_METHOD_NAMES = new Set([
38762
38775
  "each",
@@ -38901,16 +38914,7 @@ const collectDependencyStateSymbolIds = (effectNode, stateSymbolIds, scopes) =>
38901
38914
  }
38902
38915
  return dependencyStateSymbolIds;
38903
38916
  };
38904
- const resolveSynchronouslyInvokedFunction = (expression, scopes) => {
38905
- const localFunction = resolveExactLocalFunction(expression, scopes);
38906
- if (localFunction) return localFunction;
38907
- const useCallbackCall = getReactUseCallbackCall(expression, scopes);
38908
- if (!useCallbackCall) return null;
38909
- const callback = useCallbackCall.arguments[0];
38910
- if (!callback || isNodeOfType(callback, "SpreadElement")) return null;
38911
- return resolveExactLocalFunction(callback, scopes);
38912
- };
38913
- const collectSynchronouslyInvokedFunctions = (effectCallback, scopes, includeStableCallbacks = true) => {
38917
+ const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
38914
38918
  const analysisFunctions = new Set([effectCallback]);
38915
38919
  const pendingFunctions = [effectCallback];
38916
38920
  while (pendingFunctions.length > 0) {
@@ -38918,9 +38922,9 @@ const collectSynchronouslyInvokedFunctions = (effectCallback, scopes, includeSta
38918
38922
  if (!currentFunction || !isFunctionLike$1(currentFunction)) continue;
38919
38923
  walkInsideStatementBlocks(currentFunction.body, (child) => {
38920
38924
  if (!isNodeOfType(child, "CallExpression")) return;
38921
- const invokedFunction = includeStableCallbacks ? resolveSynchronouslyInvokedFunction(child.callee, scopes) : resolveExactLocalFunction(child.callee, scopes);
38925
+ const invokedFunction = resolveExactLocalFunction(child.callee, scopes);
38922
38926
  if (!invokedFunction || analysisFunctions.has(invokedFunction)) return;
38923
- if (isFunctionLike$1(invokedFunction) && (invokedFunction.async || invokedFunction.generator)) return;
38927
+ if (isFunctionLike$1(invokedFunction) && invokedFunction.async) return;
38924
38928
  analysisFunctions.add(invokedFunction);
38925
38929
  pendingFunctions.push(invokedFunction);
38926
38930
  });
@@ -38933,51 +38937,7 @@ const visitSynchronousFunctionBodies = (analysisFunctions, visitor) => {
38933
38937
  walkInsideStatementBlocks(analysisFunction.body, visitor);
38934
38938
  }
38935
38939
  };
38936
- const getStateNameForSetterCall = (node, setterSymbolIdToStateName, scopes) => {
38937
- if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier")) return null;
38938
- const setterSymbol = resolveConstIdentifierAlias(node.callee, scopes, true);
38939
- return setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) ?? null : null;
38940
- };
38941
- const collectStateWriteAnalysisFunctions = (effectCallback, scopes, setterSymbolIdToStateName) => {
38942
- const ordinaryAnalysisFunctions = collectSynchronouslyInvokedFunctions(effectCallback, scopes, false);
38943
- const fullAnalysisFunctions = collectSynchronouslyInvokedFunctions(effectCallback, scopes);
38944
- const stableAnalysisFunctions = [...fullAnalysisFunctions].filter((analysisFunction) => !ordinaryAnalysisFunctions.has(analysisFunction));
38945
- if (stableAnalysisFunctions.length === 0) return ordinaryAnalysisFunctions;
38946
- let hasUnprovenObservableWork = stableAnalysisFunctions.some((analysisFunction) => {
38947
- if (!isFunctionLike$1(analysisFunction)) return false;
38948
- let hasExecutableDefault = false;
38949
- for (const parameter of analysisFunction.params) {
38950
- walkInsideStatementBlocks(parameter, (child) => {
38951
- if (isNodeOfType(child, "CallExpression") || isNodeOfType(child, "AssignmentExpression") || isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "ImportExpression") || isNodeOfType(child, "NewExpression") || isNodeOfType(child, "TaggedTemplateExpression") || isNodeOfType(child, "ThrowStatement") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") hasExecutableDefault = true;
38952
- });
38953
- if (hasExecutableDefault) return true;
38954
- }
38955
- return false;
38956
- });
38957
- const stableWrittenStateNames = /* @__PURE__ */ new Set();
38958
- const allWrittenStateNames = /* @__PURE__ */ new Set();
38959
- visitSynchronousFunctionBodies(fullAnalysisFunctions, (child) => {
38960
- const writtenStateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
38961
- if (writtenStateName) allWrittenStateNames.add(writtenStateName);
38962
- });
38963
- visitSynchronousFunctionBodies(new Set(stableAnalysisFunctions), (child) => {
38964
- if (hasUnprovenObservableWork) return;
38965
- if (isNodeOfType(child, "CallExpression")) {
38966
- const writtenStateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
38967
- if (writtenStateName) {
38968
- stableWrittenStateNames.add(writtenStateName);
38969
- return;
38970
- }
38971
- const invokedFunction = resolveSynchronouslyInvokedFunction(child.callee, scopes);
38972
- if (invokedFunction && fullAnalysisFunctions.has(invokedFunction)) return;
38973
- hasUnprovenObservableWork = true;
38974
- return;
38975
- }
38976
- if (isNodeOfType(child, "AssignmentExpression") || isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "ImportExpression") || isNodeOfType(child, "NewExpression") || isNodeOfType(child, "TaggedTemplateExpression") || isNodeOfType(child, "ThrowStatement") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") hasUnprovenObservableWork = true;
38977
- });
38978
- return !hasUnprovenObservableWork && stableWrittenStateNames.size === 1 && allWrittenStateNames.size === 1 ? fullAnalysisFunctions : ordinaryAnalysisFunctions;
38979
- };
38980
- const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, visitedSymbolIds = /* @__PURE__ */ new Set(), additionalStateValues = /* @__PURE__ */ new Map()) => {
38940
+ const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
38981
38941
  const unwrappedExpression = stripParenExpression(expression);
38982
38942
  if (isNodeOfType(unwrappedExpression, "Literal")) {
38983
38943
  const literalValue = unwrappedExpression.value;
@@ -38985,49 +38945,46 @@ const readStaticEffectValue = (expression, scopes, stateSymbolId, stateValue, vi
38985
38945
  return null;
38986
38946
  }
38987
38947
  if (isNodeOfType(unwrappedExpression, "Identifier")) {
38988
- const symbol = scopes.symbolFor(unwrappedExpression);
38989
- if (symbol?.id === stateSymbolId) return stateValue;
38990
- if (symbol && additionalStateValues.has(symbol.id)) return additionalStateValues.get(symbol.id) ?? null;
38948
+ if (scopes.symbolFor(unwrappedExpression)?.id === stateSymbolId) return stateValue;
38991
38949
  if (unwrappedExpression.name === "undefined" && scopes.isGlobalReference(unwrappedExpression)) return { value: void 0 };
38992
- if (unwrappedExpression.name === "NaN" && scopes.isGlobalReference(unwrappedExpression)) return { value: NaN };
38993
38950
  const immutableSymbol = scopes.symbolFor(unwrappedExpression);
38994
38951
  if (immutableSymbol?.kind !== "const" || !immutableSymbol.initializer || !isNodeOfType(immutableSymbol.declarationNode, "VariableDeclarator") || immutableSymbol.declarationNode.id !== immutableSymbol.bindingIdentifier || immutableSymbol.declarationNode.init !== immutableSymbol.initializer || immutableSymbol.references.some((reference) => reference.flag !== "read") || visitedSymbolIds.has(immutableSymbol.id)) return null;
38995
- return readStaticEffectValue(immutableSymbol.initializer, scopes, stateSymbolId, stateValue, new Set(visitedSymbolIds).add(immutableSymbol.id), additionalStateValues);
38952
+ return readStaticEffectValue(immutableSymbol.initializer, scopes, stateSymbolId, stateValue, new Set(visitedSymbolIds).add(immutableSymbol.id));
38996
38953
  }
38997
38954
  if (isNodeOfType(unwrappedExpression, "UnaryExpression")) {
38998
38955
  if (unwrappedExpression.operator === "void") return { value: void 0 };
38999
38956
  if (unwrappedExpression.operator !== "!") return null;
39000
- const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38957
+ const argumentValue = readStaticEffectValue(unwrappedExpression.argument, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39001
38958
  return argumentValue ? { value: !argumentValue.value } : null;
39002
38959
  }
39003
38960
  if (isNodeOfType(unwrappedExpression, "CallExpression")) {
39004
38961
  if (isNodeOfType(unwrappedExpression.callee, "Identifier") && unwrappedExpression.callee.name === "Boolean" && scopes.isGlobalReference(unwrappedExpression.callee) && unwrappedExpression.arguments.length === 1 && unwrappedExpression.arguments[0] && !isNodeOfType(unwrappedExpression.arguments[0], "SpreadElement")) {
39005
- const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38962
+ const argumentValue = readStaticEffectValue(unwrappedExpression.arguments[0], scopes, stateSymbolId, stateValue, visitedSymbolIds);
39006
38963
  return argumentValue ? { value: Boolean(argumentValue.value) } : null;
39007
38964
  }
39008
38965
  return null;
39009
38966
  }
39010
38967
  if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
39011
- const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38968
+ const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39012
38969
  if (!leftValue) return null;
39013
38970
  if (unwrappedExpression.operator === "&&" && !leftValue.value) return leftValue;
39014
38971
  if (unwrappedExpression.operator === "||" && leftValue.value) return leftValue;
39015
38972
  if (unwrappedExpression.operator === "??" && leftValue.value !== null && leftValue.value !== void 0) return leftValue;
39016
- return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38973
+ return readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39017
38974
  }
39018
38975
  if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
39019
- const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38976
+ const testValue = readStaticEffectValue(unwrappedExpression.test, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39020
38977
  if (!testValue) return null;
39021
- return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38978
+ return readStaticEffectValue(testValue.value ? unwrappedExpression.consequent : unwrappedExpression.alternate, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39022
38979
  }
39023
38980
  if (isNodeOfType(unwrappedExpression, "MemberExpression") && unwrappedExpression.optional) {
39024
- const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38981
+ const objectValue = readStaticEffectValue(unwrappedExpression.object, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39025
38982
  if (objectValue?.value === null || objectValue?.value === void 0) return { value: void 0 };
39026
38983
  return null;
39027
38984
  }
39028
38985
  if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
39029
- const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
39030
- const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds, additionalStateValues);
38986
+ const leftValue = readStaticEffectValue(unwrappedExpression.left, scopes, stateSymbolId, stateValue, visitedSymbolIds);
38987
+ const rightValue = readStaticEffectValue(unwrappedExpression.right, scopes, stateSymbolId, stateValue, visitedSymbolIds);
39031
38988
  if (!leftValue || !rightValue) return null;
39032
38989
  if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
39033
38990
  const areEqual = leftValue.value === rightValue.value;
@@ -39066,7 +39023,8 @@ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName
39066
39023
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
39067
39024
  if (!isNodeOfType(child, "CallExpression")) return;
39068
39025
  if (!isNodeOfType(child.callee, "Identifier")) return;
39069
- const stateName = getStateNameForSetterCall(child, setterSymbolIdToStateName, scopes);
39026
+ const setterSymbol = resolveConstIdentifierAlias(child.callee, scopes, true);
39027
+ const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
39070
39028
  if (!stateName) return;
39071
39029
  const writeInfo = stateWrites.get(stateName) ?? {
39072
39030
  values: /* @__PURE__ */ new Set(),
@@ -39082,27 +39040,27 @@ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName
39082
39040
  const isGlobalBooleanCall = (node, scopes) => {
39083
39041
  return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Boolean" && scopes.isGlobalReference(node.callee);
39084
39042
  };
39085
- const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map()) => {
39043
+ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, scopes) => {
39086
39044
  let currentNode = workNode;
39087
39045
  while (currentNode.parent) {
39088
39046
  const parentNode = currentNode.parent;
39089
39047
  if (isFunctionLike$1(parentNode)) break;
39090
39048
  if (isNodeOfType(parentNode, "IfStatement")) {
39091
- const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
39049
+ const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
39092
39050
  if (testValue) {
39093
39051
  if (currentNode === parentNode.consequent && !testValue.value) return false;
39094
39052
  if (currentNode === parentNode.alternate && testValue.value) return false;
39095
39053
  }
39096
39054
  }
39097
39055
  if (isNodeOfType(parentNode, "ConditionalExpression")) {
39098
- const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
39056
+ const testValue = readStaticEffectValue(parentNode.test, scopes, stateSymbolId, stateValue);
39099
39057
  if (testValue) {
39100
39058
  if (currentNode === parentNode.consequent && !testValue.value) return false;
39101
39059
  if (currentNode === parentNode.alternate && testValue.value) return false;
39102
39060
  }
39103
39061
  }
39104
39062
  if (isNodeOfType(parentNode, "LogicalExpression") && currentNode === parentNode.right) {
39105
- const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues);
39063
+ const leftValue = readStaticEffectValue(parentNode.left, scopes, stateSymbolId, stateValue);
39106
39064
  if (leftValue) {
39107
39065
  if (parentNode.operator === "&&" && !leftValue.value) return false;
39108
39066
  if (parentNode.operator === "||" && leftValue.value) return false;
@@ -39114,7 +39072,7 @@ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, s
39114
39072
  if (statementIndex >= 0) for (let index = 0; index < statementIndex; index += 1) {
39115
39073
  const earlierStatement = parentNode.body[index];
39116
39074
  if (!isNodeOfType(earlierStatement, "IfStatement") || earlierStatement.alternate || !statementAlwaysExits(earlierStatement.consequent)) continue;
39117
- if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), additionalStateValues)?.value) return false;
39075
+ if (readStaticEffectValue(earlierStatement.test, scopes, stateSymbolId, stateValue)?.value) return false;
39118
39076
  }
39119
39077
  }
39120
39078
  currentNode = parentNode;
@@ -39124,93 +39082,24 @@ const isWorkNodeReachableForStateValue = (workNode, stateSymbolId, stateValue, s
39124
39082
  const isReaderWorkNode = (node, analysisFunctions, scopes) => {
39125
39083
  if (isNodeOfType(node, "CallExpression")) {
39126
39084
  if (isGlobalBooleanCall(node, scopes)) return false;
39127
- const invokedFunction = resolveSynchronouslyInvokedFunction(node.callee, scopes);
39085
+ const invokedFunction = resolveExactLocalFunction(node.callee, scopes);
39128
39086
  return !invokedFunction || !analysisFunctions.has(invokedFunction);
39129
39087
  }
39130
39088
  return isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "ThrowStatement") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete";
39131
39089
  };
39132
- const mergeKnownStateValues = (additionalStateValues, parameterStateValues) => {
39133
- if (parameterStateValues.size === 0) return additionalStateValues;
39134
- const knownStateValues = new Map(additionalStateValues);
39135
- for (const [symbolId, value] of parameterStateValues) knownStateValues.set(symbolId, value);
39136
- return knownStateValues;
39137
- };
39138
- const buildInvokedFunctionParameterStateValues = (invokedFunction, invocation, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map(), callerParameterStateValues = /* @__PURE__ */ new Map()) => {
39139
- if (!isFunctionLike$1(invokedFunction)) return /* @__PURE__ */ new Map();
39140
- const parameterStateValues = /* @__PURE__ */ new Map();
39141
- const callerKnownStateValues = mergeKnownStateValues(additionalStateValues, callerParameterStateValues);
39142
- for (let parameterIndex = 0; parameterIndex < invokedFunction.params.length; parameterIndex += 1) {
39143
- const rawParameter = invokedFunction.params[parameterIndex];
39144
- const rawArgument = invocation.arguments[parameterIndex];
39145
- if (rawArgument && isNodeOfType(rawArgument, "SpreadElement")) break;
39146
- const parameter = isNodeOfType(rawParameter, "AssignmentPattern") ? rawParameter.left : rawParameter;
39147
- if (!isNodeOfType(parameter, "Identifier")) continue;
39148
- const parameterSymbol = scopes.symbolFor(parameter);
39149
- if (!parameterSymbol) continue;
39150
- let argumentValue = null;
39151
- if (rawArgument) {
39152
- argumentValue = readStaticEffectValue(rawArgument, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), callerKnownStateValues);
39153
- if (argumentValue !== null && argumentValue.value === void 0 && isNodeOfType(rawParameter, "AssignmentPattern")) {
39154
- const defaultKnownStateValues = mergeKnownStateValues(callerKnownStateValues, parameterStateValues);
39155
- argumentValue = readStaticEffectValue(rawParameter.right, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), defaultKnownStateValues);
39156
- }
39157
- } else if (isNodeOfType(rawParameter, "AssignmentPattern")) {
39158
- const defaultKnownStateValues = mergeKnownStateValues(callerKnownStateValues, parameterStateValues);
39159
- argumentValue = readStaticEffectValue(rawParameter.right, scopes, stateSymbolId, stateValue, /* @__PURE__ */ new Set(), defaultKnownStateValues);
39160
- } else argumentValue = { value: void 0 };
39161
- if (argumentValue) parameterStateValues.set(parameterSymbol.id, argumentValue);
39162
- }
39163
- return parameterStateValues;
39164
- };
39165
- const haveEqualParameterStateValues = (leftValues, rightValues) => {
39166
- if (leftValues.size !== rightValues.size) return false;
39167
- for (const [symbolId, leftValue] of leftValues) {
39168
- const rightValue = rightValues.get(symbolId);
39169
- if (!rightValue || !Object.is(leftValue.value, rightValue.value)) return false;
39170
- }
39171
- return true;
39172
- };
39173
- const canReaderWorkRunForStateValues = (readerEffect, stateSymbolId, stateValue, scopes, additionalStateValues = /* @__PURE__ */ new Map()) => {
39174
- const pendingFrames = [{
39175
- functionNode: readerEffect.callback,
39176
- parameterStateValues: /* @__PURE__ */ new Map()
39177
- }];
39178
- const visitedParameterStateValues = /* @__PURE__ */ new Map();
39179
- while (pendingFrames.length > 0) {
39180
- const frame = pendingFrames.pop();
39181
- if (!frame || !isFunctionLike$1(frame.functionNode)) continue;
39182
- const functionParameterStateValues = visitedParameterStateValues.get(frame.functionNode) ?? [];
39183
- if (functionParameterStateValues.some((previousValues) => haveEqualParameterStateValues(previousValues, frame.parameterStateValues))) continue;
39184
- functionParameterStateValues.push(frame.parameterStateValues);
39185
- visitedParameterStateValues.set(frame.functionNode, functionParameterStateValues);
39186
- const knownStateValues = mergeKnownStateValues(additionalStateValues, frame.parameterStateValues);
39090
+ const canStateWriteReachReaderWork = (writeInfo, readerEffect, stateSymbolId, scopes) => {
39091
+ if (writeInfo.hasUnknownValue || stateSymbolId === null) return true;
39092
+ for (const writtenValue of writeInfo.values) {
39093
+ const stateValue = { value: writtenValue };
39187
39094
  let didFindReachableWork = false;
39188
- walkInsideStatementBlocks(frame.functionNode.body, (child) => {
39189
- if (didFindReachableWork) return;
39190
- if (isNodeOfType(child, "CallExpression")) {
39191
- const invokedFunction = resolveSynchronouslyInvokedFunction(child.callee, scopes);
39192
- if (invokedFunction && readerEffect.analysisFunctions.has(invokedFunction)) {
39193
- if (isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes, knownStateValues)) pendingFrames.push({
39194
- functionNode: invokedFunction,
39195
- parameterStateValues: buildInvokedFunctionParameterStateValues(invokedFunction, child, stateSymbolId, stateValue, scopes, additionalStateValues, frame.parameterStateValues)
39196
- });
39197
- return;
39198
- }
39199
- }
39200
- if (isReaderWorkNode(child, readerEffect.analysisFunctions, scopes) && isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes, knownStateValues)) didFindReachableWork = true;
39095
+ visitSynchronousFunctionBodies(readerEffect.analysisFunctions, (child) => {
39096
+ if (didFindReachableWork || !isReaderWorkNode(child, readerEffect.analysisFunctions, scopes)) return;
39097
+ if (isWorkNodeReachableForStateValue(child, stateSymbolId, stateValue, scopes)) didFindReachableWork = true;
39201
39098
  });
39202
39099
  if (didFindReachableWork) return true;
39203
39100
  }
39204
39101
  return false;
39205
39102
  };
39206
- const canStateWriteReachReaderWork = (writtenStateName, writerEffect, readerEffect, stateSymbolIds, scopes) => {
39207
- const writeInfo = writerEffect.stateWrites.get(writtenStateName);
39208
- const stateSymbolId = stateSymbolIds.get(writtenStateName);
39209
- if (!writeInfo || stateSymbolId === void 0) return true;
39210
- if (writeInfo.hasUnknownValue) return true;
39211
- for (const writtenValue of writeInfo.values) if (canReaderWorkRunForStateValues(readerEffect, stateSymbolId, { value: writtenValue }, scopes)) return true;
39212
- return false;
39213
- };
39214
39103
  const EMPTY_CLEANUP_NAME_SET = /* @__PURE__ */ new Set();
39215
39104
  const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
39216
39105
  "clear",
@@ -39221,499 +39110,26 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
39221
39110
  "keys",
39222
39111
  "values"
39223
39112
  ]);
39224
- const EXTERNAL_SYNC_DIRECT_IMPORT_SOURCES = new Map([
39225
- ["fetch", new Set([
39226
- "cross-fetch",
39227
- "node-fetch",
39228
- "undici"
39229
- ])],
39230
- ["ky", new Set(["ky"])],
39231
- ["got", new Set(["got"])],
39232
- ["wretch", new Set(["wretch"])],
39233
- ["ofetch", new Set(["ofetch"])],
39234
- ["setTimeout", new Set([
39235
- "node:timers",
39236
- "node:timers/promises",
39237
- "timers",
39238
- "timers/promises"
39239
- ])],
39240
- ["setInterval", new Set([
39241
- "node:timers",
39242
- "node:timers/promises",
39243
- "timers",
39244
- "timers/promises"
39245
- ])]
39246
- ]);
39247
- const EXTERNAL_SYNC_DEFAULT_IMPORT_NAMES = new Map([
39248
- ["axios", "axios"],
39249
- ["cross-fetch", "fetch"],
39250
- ["got", "got"],
39251
- ["ky", "ky"],
39252
- ["node-fetch", "fetch"],
39253
- ["wretch", "wretch"]
39254
- ]);
39255
- const EXTERNAL_SYNC_HTTP_CLIENT_MODULE_SOURCES = new Set([
39256
- "axios",
39257
- "cross-fetch",
39258
- "got",
39259
- "ky",
39260
- "node-fetch",
39261
- "ofetch",
39262
- "undici",
39263
- "wretch"
39264
- ]);
39265
- const EXTERNAL_SYNC_GLOBAL_HTTP_CLIENT_NAMES = new Set([
39266
- "axios",
39267
- "got",
39268
- "ky",
39269
- "ofetch",
39270
- "wretch"
39271
- ]);
39272
- const EXTERNAL_SYNC_HTTP_METHOD_NAMES = new Set([
39273
- ...EXTERNAL_SYNC_AMBIGUOUS_HTTP_METHOD_NAMES,
39274
- "fetch",
39275
- "patch",
39276
- "post",
39277
- "put",
39278
- "request"
39279
- ]);
39280
- const TANSTACK_QUERY_CLIENT_MODULE_SOURCES = new Set(["@tanstack/query-core", "@tanstack/react-query"]);
39281
- const TANSTACK_QUERY_EXTERNAL_SYNC_METHOD_NAMES = new Set(["fetchQuery", "prefetchQuery"]);
39282
- const EXTERNAL_SYNC_RESOURCE_CONSTRUCTOR_NAMES = new Set([
39283
- ...EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS,
39284
- ...SOCKET_CONSTRUCTOR_NAMES_REQUIRING_CLEANUP,
39285
- "EventTarget",
39286
- "XMLHttpRequest"
39287
- ]);
39288
- const getDestructuredBindingDepth = (bindingIdentifier) => {
39289
- let bindingNode = bindingIdentifier;
39290
- let depth = 0;
39291
- while (true) {
39292
- if (isNodeOfType(bindingNode.parent, "AssignmentPattern") && bindingNode.parent.left === bindingNode) bindingNode = bindingNode.parent;
39293
- const property = bindingNode.parent;
39294
- if (!property || !isNodeOfType(property, "Property") || property.value !== bindingNode || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return depth;
39295
- depth += 1;
39296
- bindingNode = property.parent;
39297
- }
39298
- };
39299
- const isMutatedMemberExpression = (rawMemberExpression) => {
39300
- let expression = findTransparentExpressionRoot(rawMemberExpression);
39301
- while (expression.parent) {
39302
- const parent = expression.parent;
39303
- if (isNodeOfType(parent, "MemberExpression") && parent.object === expression) {
39304
- expression = findTransparentExpressionRoot(parent);
39305
- continue;
39306
- }
39307
- if (isNodeOfType(parent, "Property") && parent.value === expression && isNodeOfType(parent.parent, "ObjectPattern")) {
39308
- expression = findTransparentExpressionRoot(parent);
39309
- continue;
39310
- }
39311
- if (isNodeOfType(parent, "ObjectPattern") && parent.properties.some((property) => property === expression)) {
39312
- expression = findTransparentExpressionRoot(parent);
39313
- continue;
39314
- }
39315
- if (isNodeOfType(parent, "ArrayPattern") && parent.elements.some((element) => element === expression)) {
39316
- expression = findTransparentExpressionRoot(parent);
39317
- continue;
39318
- }
39319
- if (isNodeOfType(parent, "RestElement") && parent.argument === expression || isNodeOfType(parent, "AssignmentPattern") && parent.left === expression) {
39320
- expression = findTransparentExpressionRoot(parent);
39321
- continue;
39322
- }
39323
- break;
39324
- }
39325
- const parent = expression.parent;
39326
- return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expression || isNodeOfType(parent, "UpdateExpression") && parent.argument === expression || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expression || (isNodeOfType(parent, "ForInStatement") || isNodeOfType(parent, "ForOfStatement")) && parent.left === expression);
39327
- };
39328
- const isReactHookDependencyArgument = (expression, scopes) => {
39329
- const call = expression.parent;
39330
- return Boolean(isNodeOfType(call, "CallExpression") && call.arguments[1] === expression && isReactApiCall(call, HOOKS_WITH_DEPS, scopes, {
39331
- allowGlobalReactNamespace: true,
39332
- allowUnboundBareCalls: true,
39333
- resolveNamedAliases: true
39334
- }));
39335
- };
39336
- const isPlainDependencyObjectExpression = (expression) => isNodeOfType(expression, "ObjectExpression") && expression.properties.every((property) => isNodeOfType(property, "Property") && property.kind === "init" && !property.computed && !property.method);
39337
- const getContainingReactDependencyValue = (rawExpression, containerKind) => {
39338
- const expression = findTransparentExpressionRoot(rawExpression);
39339
- const parent = expression.parent;
39340
- if (isNodeOfType(parent, "ArrayExpression")) return {
39341
- expression: parent,
39342
- containerKind: "array"
39343
- };
39344
- if (containerKind === "array" && isNodeOfType(parent, "SpreadElement") && parent.argument === expression && isNodeOfType(parent.parent, "ArrayExpression")) return {
39345
- expression: parent.parent,
39346
- containerKind: "array"
39347
- };
39348
- if (isNodeOfType(parent, "Property") && (parent.value === expression || parent.shorthand && parent.key === expression) && parent.parent && isPlainDependencyObjectExpression(parent.parent)) return {
39349
- expression: parent.parent,
39350
- containerKind: "object"
39351
- };
39352
- return null;
39353
- };
39354
- const getFullArrayRestCopySymbol = (initializer, scopes) => {
39355
- const declaration = initializer.parent;
39356
- if (!isNodeOfType(declaration, "VariableDeclarator") || declaration.init !== initializer || !isNodeOfType(declaration.id, "ArrayPattern") || declaration.id.elements.length !== 1) return null;
39357
- const restElement = declaration.id.elements[0];
39358
- if (!isNodeOfType(restElement, "RestElement") || !isNodeOfType(restElement.argument, "Identifier")) return null;
39359
- const symbol = scopes.symbolFor(restElement.argument);
39360
- return symbol?.kind === "const" && symbol.declarationNode === declaration && symbol.references.every((reference) => reference.flag === "read") ? symbol : null;
39361
- };
39362
- const isReactDependencyArrayReference = (initialExpression, scopes) => {
39363
- const pendingPaths = [{
39364
- expression: initialExpression,
39365
- containerKind: null
39366
- }];
39367
- const visitedExpressions = /* @__PURE__ */ new Set();
39368
- let didFindReactDependencyArgument = false;
39369
- while (pendingPaths.length > 0) {
39370
- const pendingPath = pendingPaths.pop();
39371
- if (!pendingPath) continue;
39372
- const expression = findTransparentExpressionRoot(pendingPath.expression);
39373
- if (visitedExpressions.has(expression)) continue;
39374
- visitedExpressions.add(expression);
39375
- if (pendingPath.containerKind === "array" && isReactHookDependencyArgument(expression, scopes)) {
39376
- didFindReactDependencyArgument = true;
39377
- continue;
39378
- }
39379
- const containingValue = getContainingReactDependencyValue(expression, pendingPath.containerKind);
39380
- if (containingValue) {
39381
- pendingPaths.push(containingValue);
39382
- continue;
39383
- }
39384
- const declaration = expression.parent;
39385
- if (isNodeOfType(declaration, "VariableDeclarator") && declaration.init === expression && isNodeOfType(declaration.id, "Identifier")) {
39386
- const symbol = scopes.symbolFor(declaration.id);
39387
- if (!symbol || getDirectUnreassignedInitializer(symbol) !== expression) return false;
39388
- if (symbol.references.length === 0) return false;
39389
- for (const reference of symbol.references) pendingPaths.push({
39390
- expression: reference.identifier,
39391
- containerKind: pendingPath.containerKind
39392
- });
39393
- continue;
39394
- }
39395
- if (pendingPath.containerKind === "array") {
39396
- const restCopySymbol = getFullArrayRestCopySymbol(expression, scopes);
39397
- if (restCopySymbol) {
39398
- if (restCopySymbol.references.length === 0) return false;
39399
- for (const reference of restCopySymbol.references) pendingPaths.push({
39400
- expression: reference.identifier,
39401
- containerKind: "array"
39402
- });
39403
- continue;
39404
- }
39405
- }
39406
- return false;
39407
- }
39408
- return didFindReactDependencyArgument;
39409
- };
39410
- const hasSafeReceiverAliases = (rootSymbols, scopes) => {
39411
- const pendingSymbols = [...rootSymbols];
39412
- const visitedSymbolIds = /* @__PURE__ */ new Set();
39413
- while (pendingSymbols.length > 0) {
39414
- const symbol = pendingSymbols.pop();
39415
- if (!symbol || visitedSymbolIds.has(symbol.id)) continue;
39416
- visitedSymbolIds.add(symbol.id);
39417
- for (const reference of symbol.references) {
39418
- const expression = findTransparentExpressionRoot(reference.identifier);
39419
- const container = expression.parent;
39420
- if (isReactDependencyArrayReference(expression, scopes)) continue;
39421
- if (isNodeOfType(container, "MemberExpression") && container.object === expression) {
39422
- if (isMutatedMemberExpression(container)) return false;
39423
- continue;
39424
- }
39425
- if (isNodeOfType(container, "CallExpression") && container.callee === expression) continue;
39426
- if (isNodeOfType(container, "VariableDeclarator") && container.init === expression && isNodeOfType(container.id, "ObjectPattern")) continue;
39427
- if (isNodeOfType(container, "AssignmentExpression") && container.right === expression && isNodeOfType(container.left, "ObjectPattern")) continue;
39428
- if (isNodeOfType(container, "VariableDeclarator") && container.init === expression && isNodeOfType(container.id, "Identifier")) {
39429
- const aliasSymbol = scopes.symbolFor(container.id);
39430
- const aliasInitializer = aliasSymbol && getDirectUnreassignedInitializer(aliasSymbol);
39431
- if (!aliasSymbol || aliasInitializer !== expression) return false;
39432
- pendingSymbols.push(aliasSymbol);
39433
- continue;
39434
- }
39435
- return false;
39113
+ const isFunctionShapedReturn = (returnedValue, setterToStateName, setterSymbolIdToStateName, scopes, isExplicitReturnStatement) => {
39114
+ if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
39115
+ if (isNodeOfType(returnedValue, "CallExpression")) {
39116
+ if (isNodeOfType(returnedValue.callee, "Identifier")) {
39117
+ const setterSymbol = resolveConstIdentifierAlias(returnedValue.callee, scopes, true);
39118
+ if (setterToStateName.has(returnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
39119
+ if (isSetterIdentifier(returnedValue.callee.name)) return true;
39436
39120
  }
39121
+ return isCleanupReturn(returnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
39437
39122
  }
39438
- return true;
39439
- };
39440
- const hasProvenGlobalNamespaceReference = (expression, namespaceNames, scopes) => {
39441
- for (const namespaceName of namespaceNames) if (isProvenGlobalNamespaceReference(expression, namespaceName, scopes)) return true;
39123
+ if (isNodeOfType(returnedValue, "Identifier")) return true;
39442
39124
  return false;
39443
39125
  };
39444
- const getTypeScriptImportEqualsSource = (symbol) => {
39445
- if (symbol.kind !== "ts-import-equals" || !isNodeOfType(symbol.declarationNode, "TSImportEqualsDeclaration")) return null;
39446
- const moduleReference = symbol.declarationNode.moduleReference;
39447
- if (!isNodeOfType(moduleReference, "TSExternalModuleReference") || !isNodeOfType(moduleReference.expression, "Literal")) return null;
39448
- return typeof moduleReference.expression.value === "string" ? moduleReference.expression.value : null;
39449
- };
39450
- const getUnshadowedRequireSource = (rawExpression, scopes) => {
39451
- let expression = stripParenExpression(rawExpression);
39452
- while (isNodeOfType(expression, "MemberExpression")) expression = stripParenExpression(expression.object);
39453
- if (!isNodeOfType(expression, "CallExpression") || !isNodeOfType(expression.callee, "Identifier") || expression.callee.name !== "require" || !scopes.isGlobalReference(expression.callee)) return null;
39454
- return getRequireCallSource(rawExpression);
39455
- };
39456
- const resolveExternalModuleBinding = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set(), ignoreCommonJsMutation = false) => {
39457
- let expression = stripParenExpression(rawExpression);
39458
- let outermostExportedName = null;
39459
- let exportedPropertyDepth = 0;
39460
- let resolvedBinding = null;
39461
- let isCommonJsModuleObject = false;
39462
- const traversedSymbolIds = new Set(visitedSymbolIds);
39463
- const commonJsModuleObjectSymbols = /* @__PURE__ */ new Set();
39464
- while (true) {
39465
- if (isNodeOfType(expression, "MemberExpression")) {
39466
- const propertyName = getStaticPropertyName(expression);
39467
- if (propertyName === null) return null;
39468
- outermostExportedName ??= propertyName;
39469
- exportedPropertyDepth += 1;
39470
- expression = stripParenExpression(expression.object);
39471
- continue;
39472
- }
39473
- const requireSource = getUnshadowedRequireSource(expression, scopes);
39474
- if (requireSource !== null) {
39475
- resolvedBinding = {
39476
- source: requireSource,
39477
- exportedName: null,
39478
- isModuleObject: true
39479
- };
39480
- isCommonJsModuleObject = true;
39481
- break;
39482
- }
39483
- if (!isNodeOfType(expression, "Identifier")) return null;
39484
- const symbol = scopes.symbolFor(expression);
39485
- if (!symbol || traversedSymbolIds.has(symbol.id)) return null;
39486
- if (symbol.kind === "import") {
39487
- const importDeclaration = symbol.declarationNode.parent;
39488
- if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration") || typeof importDeclaration.source.value !== "string") return null;
39489
- if (isNodeOfType(symbol.declarationNode, "ImportSpecifier")) resolvedBinding = {
39490
- source: importDeclaration.source.value,
39491
- exportedName: getImportedName(symbol.declarationNode) ?? null,
39492
- isModuleObject: false
39493
- };
39494
- else if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) resolvedBinding = {
39495
- source: importDeclaration.source.value,
39496
- exportedName: "default",
39497
- isModuleObject: false
39498
- };
39499
- else if (isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")) resolvedBinding = {
39500
- source: importDeclaration.source.value,
39501
- exportedName: null,
39502
- isModuleObject: true
39503
- };
39504
- break;
39505
- }
39506
- const importEqualsSource = getTypeScriptImportEqualsSource(symbol);
39507
- if (importEqualsSource !== null) {
39508
- resolvedBinding = {
39509
- source: importEqualsSource,
39510
- exportedName: null,
39511
- isModuleObject: true
39512
- };
39513
- isCommonJsModuleObject = true;
39514
- commonJsModuleObjectSymbols.add(symbol);
39515
- break;
39516
- }
39517
- const directInitializer = getDirectUnreassignedInitializer(symbol);
39518
- const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
39519
- const initializer = directInitializer ?? (destructuredPropertyName !== null && symbol.kind === "const" && isNodeOfType(symbol.declarationNode, "VariableDeclarator") ? symbol.declarationNode.init : null);
39520
- if (!initializer) return null;
39521
- traversedSymbolIds.add(symbol.id);
39522
- outermostExportedName ??= destructuredPropertyName;
39523
- if (destructuredPropertyName !== null) exportedPropertyDepth += getDestructuredBindingDepth(symbol.bindingIdentifier);
39524
- const unwrappedInitializer = stripParenExpression(initializer);
39525
- if (destructuredPropertyName === null && !isNodeOfType(unwrappedInitializer, "MemberExpression")) commonJsModuleObjectSymbols.add(symbol);
39526
- expression = unwrappedInitializer;
39527
- }
39528
- if (!resolvedBinding) return null;
39529
- if (!ignoreCommonJsMutation && isCommonJsModuleObject && !hasSafeReceiverAliases(commonJsModuleObjectSymbols, scopes)) return null;
39530
- if (outermostExportedName === null) return resolvedBinding;
39531
- if (!resolvedBinding.isModuleObject || exportedPropertyDepth !== 1) return null;
39532
- return {
39533
- source: resolvedBinding.source,
39534
- exportedName: outermostExportedName,
39535
- isModuleObject: false
39536
- };
39537
- };
39538
- const getCanonicalExternalSyncExportName = (binding) => {
39539
- if (binding.exportedName && binding.exportedName !== "default") return binding.exportedName;
39540
- return EXTERNAL_SYNC_DEFAULT_IMPORT_NAMES.get(binding.source) ?? null;
39541
- };
39542
- const isKnownExternalSyncDirectModuleBinding = (binding) => {
39543
- const exportedName = getCanonicalExternalSyncExportName(binding);
39544
- return Boolean(exportedName && EXTERNAL_SYNC_DIRECT_IMPORT_SOURCES.get(exportedName)?.has(binding.source));
39545
- };
39546
- const DEFINITELY_NON_FUNCTION_GLOBAL_CALL_NAMES = new Set([
39547
- "Array",
39548
- "BigInt",
39549
- "Boolean",
39550
- "Date",
39551
- "Number",
39552
- "String",
39553
- "Symbol"
39554
- ]);
39555
- const DEFINITELY_NON_FUNCTION_GLOBAL_CONSTRUCTOR_NAMES = new Set([
39556
- "Array",
39557
- "Boolean",
39558
- "Date",
39559
- "Map",
39560
- "Number",
39561
- "Promise",
39562
- "RegExp",
39563
- "Set",
39564
- "String",
39565
- "URL",
39566
- "URLSearchParams",
39567
- "WeakMap",
39568
- "WeakSet"
39569
- ]);
39570
- const PROMISE_STATIC_RESULT_METHOD_NAMES = new Set([
39571
- "all",
39572
- "allSettled",
39573
- "any",
39574
- "race",
39575
- "reject",
39576
- "resolve",
39577
- "withResolvers"
39578
- ]);
39579
- const returnsOnlyNonCleanupValues = (functionNode, setterToStateName, scopes, visitedFunctions = /* @__PURE__ */ new Set()) => {
39580
- if (!isFunctionLike$1(functionNode) || functionNode.async || functionNode.generator || visitedFunctions.has(functionNode)) return false;
39581
- const nextVisitedFunctions = new Set(visitedFunctions).add(functionNode);
39582
- const isNonCleanupValue = (returnValue) => {
39583
- const expression = stripParenExpression(returnValue);
39584
- if (isNodeOfType(expression, "Identifier") && expression.name === "undefined" && scopes.isGlobalReference(expression)) return true;
39585
- if (isNodeOfType(expression, "Literal") || isNodeOfType(expression, "ArrayExpression") || isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "JSXElement") || isNodeOfType(expression, "JSXFragment") || isNodeOfType(expression, "TemplateLiteral") || isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return true;
39586
- if (isNodeOfType(expression, "ConditionalExpression")) return isNonCleanupValue(expression.consequent) && isNonCleanupValue(expression.alternate);
39587
- if (isNodeOfType(expression, "LogicalExpression")) return isNonCleanupValue(expression.left) && isNonCleanupValue(expression.right);
39588
- if (isNodeOfType(expression, "SequenceExpression")) {
39589
- const finalExpression = expression.expressions.at(-1);
39590
- return Boolean(finalExpression && isNonCleanupValue(finalExpression));
39591
- }
39592
- if (isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && DEFINITELY_NON_FUNCTION_GLOBAL_CONSTRUCTOR_NAMES.has(expression.callee.name) && scopes.isGlobalReference(expression.callee)) return true;
39593
- if (!isNodeOfType(expression, "CallExpression")) return false;
39594
- if (isNodeOfType(expression.callee, "Identifier") && DEFINITELY_NON_FUNCTION_GLOBAL_CALL_NAMES.has(expression.callee.name) && scopes.isGlobalReference(expression.callee)) return true;
39595
- if (isNodeOfType(expression.callee, "MemberExpression") && isNodeOfType(expression.callee.object, "Identifier") && expression.callee.object.name === "Promise" && scopes.isGlobalReference(expression.callee.object) && PROMISE_STATIC_RESULT_METHOD_NAMES.has(getStaticPropertyName(expression.callee) ?? "")) return true;
39596
- if (isNodeOfType(expression.callee, "Identifier") && setterToStateName.has(expression.callee.name)) return true;
39597
- const invokedFunction = resolveSynchronouslyInvokedFunction(expression.callee, scopes);
39598
- return Boolean(invokedFunction && returnsOnlyNonCleanupValues(invokedFunction, setterToStateName, scopes, nextVisitedFunctions));
39599
- };
39600
- if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNonCleanupValue(functionNode.body);
39601
- let returnsOnlyNonCleanup = true;
39602
- walkAst(functionNode.body, (child) => {
39603
- if (!returnsOnlyNonCleanup) return false;
39604
- if (child !== functionNode.body && isFunctionLike$1(child)) return false;
39605
- if (!isNodeOfType(child, "ReturnStatement")) return;
39606
- if (child.argument && !isNonCleanupValue(child.argument)) {
39607
- returnsOnlyNonCleanup = false;
39608
- return false;
39609
- }
39610
- });
39611
- return returnsOnlyNonCleanup;
39612
- };
39613
- const getResolvedFunctionCleanupReturnProof = (functionNode, scopes, visitedFunctions = /* @__PURE__ */ new Set()) => {
39614
- if (!isFunctionLike$1(functionNode) || functionNode.async || functionNode.generator || visitedFunctions.has(functionNode)) return {
39615
- hasCleanup: false,
39616
- isValid: false
39617
- };
39618
- const nextVisitedFunctions = new Set(visitedFunctions).add(functionNode);
39619
- const getCleanupReturnProof = (returnValue) => {
39620
- const expression = stripParenExpression(returnValue);
39621
- if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression")) return {
39622
- hasCleanup: true,
39623
- isValid: true
39624
- };
39625
- if (isNodeOfType(expression, "Identifier")) {
39626
- if (expression.name === "undefined" && scopes.isGlobalReference(expression)) return {
39627
- hasCleanup: false,
39628
- isValid: true
39629
- };
39630
- const localFunction = resolveExactLocalFunction(expression, scopes);
39631
- return {
39632
- hasCleanup: Boolean(localFunction),
39633
- isValid: Boolean(localFunction)
39634
- };
39635
- }
39636
- if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return {
39637
- hasCleanup: false,
39638
- isValid: true
39639
- };
39640
- if (isNodeOfType(expression, "ConditionalExpression")) {
39641
- const consequentProof = getCleanupReturnProof(expression.consequent);
39642
- const alternateProof = getCleanupReturnProof(expression.alternate);
39643
- return {
39644
- hasCleanup: consequentProof.hasCleanup || alternateProof.hasCleanup,
39645
- isValid: consequentProof.isValid && alternateProof.isValid
39646
- };
39647
- }
39648
- if (isNodeOfType(expression, "LogicalExpression")) {
39649
- const leftProof = getCleanupReturnProof(expression.left);
39650
- const rightProof = getCleanupReturnProof(expression.right);
39651
- return {
39652
- hasCleanup: leftProof.hasCleanup || rightProof.hasCleanup,
39653
- isValid: leftProof.isValid && rightProof.isValid
39654
- };
39655
- }
39656
- if (isNodeOfType(expression, "SequenceExpression")) {
39657
- const finalExpression = expression.expressions.at(-1);
39658
- return finalExpression ? getCleanupReturnProof(finalExpression) : {
39659
- hasCleanup: false,
39660
- isValid: false
39661
- };
39662
- }
39663
- if (!isNodeOfType(expression, "CallExpression")) return {
39664
- hasCleanup: false,
39665
- isValid: false
39666
- };
39667
- const invokedFunction = resolveSynchronouslyInvokedFunction(expression.callee, scopes);
39668
- return invokedFunction ? getResolvedFunctionCleanupReturnProof(invokedFunction, scopes, nextVisitedFunctions) : {
39669
- hasCleanup: false,
39670
- isValid: false
39671
- };
39672
- };
39673
- if (!isNodeOfType(functionNode.body, "BlockStatement")) return getCleanupReturnProof(functionNode.body);
39674
- let cleanupReturnProof = {
39675
- hasCleanup: false,
39676
- isValid: true
39677
- };
39678
- walkAst(functionNode.body, (child) => {
39679
- if (!cleanupReturnProof.isValid) return false;
39680
- if (child !== functionNode.body && isFunctionLike$1(child)) return false;
39681
- if (!isNodeOfType(child, "ReturnStatement")) return;
39682
- if (!child.argument) return;
39683
- const returnProof = getCleanupReturnProof(child.argument);
39684
- if (!returnProof.isValid) {
39685
- cleanupReturnProof = {
39686
- hasCleanup: false,
39687
- isValid: false
39688
- };
39689
- return false;
39690
- }
39691
- cleanupReturnProof.hasCleanup ||= returnProof.hasCleanup;
39692
- });
39693
- return cleanupReturnProof;
39694
- };
39695
- const isFunctionShapedReturn = (returnedValue, setterToStateName, setterSymbolIdToStateName, scopes, isExplicitReturnStatement) => {
39696
- const unwrappedReturnedValue = stripParenExpression(returnedValue);
39697
- if (isNodeOfType(unwrappedReturnedValue, "ArrowFunctionExpression") || isNodeOfType(unwrappedReturnedValue, "FunctionExpression")) return true;
39698
- if (isNodeOfType(unwrappedReturnedValue, "CallExpression")) {
39699
- if (isNodeOfType(unwrappedReturnedValue.callee, "Identifier")) {
39700
- const setterSymbol = resolveConstIdentifierAlias(unwrappedReturnedValue.callee, scopes, true);
39701
- if (setterToStateName.has(unwrappedReturnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
39702
- }
39703
- const invokedFunction = resolveSynchronouslyInvokedFunction(unwrappedReturnedValue.callee, scopes);
39704
- if (invokedFunction && returnsOnlyNonCleanupValues(invokedFunction, setterToStateName, scopes)) return false;
39705
- if (invokedFunction) {
39706
- const cleanupReturnProof = getResolvedFunctionCleanupReturnProof(invokedFunction, scopes);
39707
- if (cleanupReturnProof.isValid) return cleanupReturnProof.hasCleanup;
39708
- }
39709
- if (!invokedFunction && isNodeOfType(unwrappedReturnedValue.callee, "Identifier") && isSetterIdentifier(unwrappedReturnedValue.callee.name)) return true;
39710
- return isCleanupReturn(unwrappedReturnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
39711
- }
39712
- if (isNodeOfType(unwrappedReturnedValue, "Identifier")) return true;
39126
+ const STORAGE_GLOBAL_NAMES = new Set(["localStorage", "sessionStorage"]);
39127
+ const isBrowserStorageReceiver = (receiver) => {
39128
+ if (!receiver) return false;
39129
+ if (isNodeOfType(receiver, "Identifier")) return STORAGE_GLOBAL_NAMES.has(receiver.name);
39130
+ if (isNodeOfType(receiver, "MemberExpression")) return isNodeOfType(receiver.property, "Identifier") && STORAGE_GLOBAL_NAMES.has(receiver.property.name);
39713
39131
  return false;
39714
39132
  };
39715
- const STORAGE_GLOBAL_NAMES = ["localStorage", "sessionStorage"];
39716
- const isBrowserStorageReceiver = (receiver, scopes) => Boolean(receiver && STORAGE_GLOBAL_NAMES.some((storageName) => isProvenGlobalNamespaceReference(receiver, storageName, scopes)));
39717
39133
  const STORAGE_HOOK_PATTERN = /^use\w*Storage/i;
39718
39134
  const collectStorageHookSetterNames = (componentBody) => {
39719
39135
  const setterNames = /* @__PURE__ */ new Set();
@@ -39745,16 +39161,23 @@ const callsOpaqueExternalSetter = (analysisFunctions, setterToStateName) => {
39745
39161
  });
39746
39162
  return didFindOpaqueSetterCall;
39747
39163
  };
39748
- const isReactUseRefCall = (expression, scopes) => isNodeOfType(expression, "CallExpression") && isReactApiCall(expression, "useRef", scopes, {
39164
+ const isReactRefCall = (expression, scopes) => isNodeOfType(expression, "CallExpression") && (isReactApiCall(expression, "useRef", scopes, {
39749
39165
  allowGlobalReactNamespace: true,
39750
39166
  allowUnboundBareCalls: true,
39751
39167
  resolveNamedAliases: true
39752
- });
39753
- const isReactRefCall = (expression, scopes) => isReactUseRefCall(expression, scopes) || isNodeOfType(expression, "CallExpression") && isReactApiCall(expression, "createRef", scopes, {
39168
+ }) || isReactApiCall(expression, "createRef", scopes, {
39754
39169
  allowGlobalReactNamespace: true,
39755
39170
  allowUnboundBareCalls: true,
39756
39171
  resolveNamedAliases: true
39757
- });
39172
+ }));
39173
+ const getDirectReactRefSymbol = (rawExpression, scopes) => {
39174
+ const expression = stripParenExpression(rawExpression);
39175
+ if (!isNodeOfType(expression, "Identifier")) return null;
39176
+ const symbol = scopes.symbolFor(expression);
39177
+ if (!symbol) return null;
39178
+ const initializer = getDirectUnreassignedInitializer(symbol);
39179
+ return initializer && isReactRefCall(stripParenExpression(initializer), scopes) ? symbol : null;
39180
+ };
39758
39181
  const isReactNativeJsxElement = (openingElement, scopes) => {
39759
39182
  if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
39760
39183
  const symbol = scopes.symbolFor(openingElement.name);
@@ -39762,33 +39185,19 @@ const isReactNativeJsxElement = (openingElement, scopes) => {
39762
39185
  return Boolean(symbol?.kind === "import" && importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react-native");
39763
39186
  };
39764
39187
  const isDirectHostJsxRef = (symbol, scopes) => {
39765
- let didFindHostRef = false;
39766
- const visitedSymbolIds = /* @__PURE__ */ new Set();
39767
- const pendingSymbols = [symbol];
39768
- while (pendingSymbols.length > 0) {
39769
- const currentSymbol = pendingSymbols.pop();
39770
- if (!currentSymbol || visitedSymbolIds.has(currentSymbol.id)) continue;
39771
- visitedSymbolIds.add(currentSymbol.id);
39772
- for (const reference of currentSymbol.references) {
39773
- const expression = findTransparentExpressionRoot(reference.identifier);
39774
- const container = expression.parent;
39775
- if (isNodeOfType(container, "MemberExpression") && container.object === expression && getStaticPropertyName(container) === "current") continue;
39776
- if (isNodeOfType(container, "VariableDeclarator") && container.init === expression && isNodeOfType(container.id, "Identifier")) {
39777
- const aliasSymbol = scopes.symbolFor(container.id);
39778
- const aliasInitializer = aliasSymbol && getDirectUnreassignedInitializer(aliasSymbol);
39779
- if (!aliasSymbol || aliasInitializer !== expression) return false;
39780
- pendingSymbols.push(aliasSymbol);
39781
- continue;
39782
- }
39783
- if (!container || !isNodeOfType(container, "JSXExpressionContainer") || container.expression !== expression) return false;
39784
- const attribute = container.parent;
39785
- if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
39786
- const openingElement = attribute.parent;
39787
- if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || !isProvenIntrinsicJsxElement(openingElement, scopes) && !isReactNativeJsxElement(openingElement, scopes)) return false;
39788
- didFindHostRef = true;
39789
- }
39790
- }
39791
- return didFindHostRef;
39188
+ let hostRefCount = 0;
39189
+ for (const reference of symbol.references) {
39190
+ const expression = findTransparentExpressionRoot(reference.identifier);
39191
+ const container = expression.parent;
39192
+ if (isNodeOfType(container, "MemberExpression") && container.object === expression && getStaticPropertyName(container) === "current") continue;
39193
+ if (!container || !isNodeOfType(container, "JSXExpressionContainer") || container.expression !== expression) return false;
39194
+ const attribute = container.parent;
39195
+ if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
39196
+ const openingElement = attribute.parent;
39197
+ if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || !isProvenIntrinsicJsxElement(openingElement, scopes) && !isReactNativeJsxElement(openingElement, scopes)) return false;
39198
+ hostRefCount += 1;
39199
+ }
39200
+ return hostRefCount > 0;
39792
39201
  };
39793
39202
  const isIntrinsicRefCallbackParameter = (expression, scopes) => {
39794
39203
  const identifier = stripParenExpression(expression);
@@ -39798,18 +39207,7 @@ const isIntrinsicRefCallbackParameter = (expression, scopes) => {
39798
39207
  const rawFirstParameter = callback.params?.[0];
39799
39208
  const firstParameter = isNodeOfType(rawFirstParameter, "AssignmentPattern") ? rawFirstParameter.left : rawFirstParameter;
39800
39209
  const symbol = scopes.symbolFor(identifier);
39801
- return Boolean(firstParameter && symbol?.bindingIdentifier === firstParameter && symbol.references.every((reference) => {
39802
- if (reference.flag !== "read") return false;
39803
- let referenceRoot = reference.identifier;
39804
- while (referenceRoot.parent) {
39805
- const parent = referenceRoot.parent;
39806
- if (isNodeOfType(parent, "AssignmentExpression")) return parent.left !== referenceRoot;
39807
- if (isNodeOfType(parent, "UpdateExpression")) return parent.argument !== referenceRoot;
39808
- if (isNodeOfType(parent, "ForInStatement") || isNodeOfType(parent, "ForOfStatement")) return parent.left !== referenceRoot;
39809
- referenceRoot = parent;
39810
- }
39811
- return true;
39812
- }));
39210
+ return Boolean(firstParameter && symbol?.bindingIdentifier === firstParameter);
39813
39211
  };
39814
39212
  const getDirectReactRefCall = (symbol, scopes) => {
39815
39213
  const initializer = getDirectUnreassignedInitializer(symbol);
@@ -39817,38 +39215,15 @@ const getDirectReactRefCall = (symbol, scopes) => {
39817
39215
  const expression = stripParenExpression(initializer);
39818
39216
  return isNodeOfType(expression, "CallExpression") && isReactRefCall(expression, scopes) ? expression : null;
39819
39217
  };
39820
- const isEmptyGlobalMapConstruction = (rawExpression, scopes) => {
39821
- const expression = stripParenExpression(rawExpression);
39822
- return isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && expression.callee.name === "Map" && scopes.isGlobalReference(expression.callee) && expression.arguments.length === 0;
39823
- };
39824
- const hasEmptyRefSentinelInitializer = (refCall, scopes) => {
39825
- if (!isReactUseRefCall(refCall, scopes) || refCall.arguments.length > 1) return false;
39826
- const [initialValue] = refCall.arguments;
39827
- return !initialValue || isNodeOfType(initialValue, "Literal") && initialValue.value === null || isNodeOfType(initialValue, "Identifier") && initialValue.name === "undefined" && scopes.isGlobalReference(initialValue);
39828
- };
39829
- const isDirectLazyEmptyMapInitialization = (currentExpression, symbol, scopes) => {
39830
- const assignment = currentExpression.parent;
39831
- if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.left !== currentExpression || assignment.operator !== "??=" || !isEmptyGlobalMapConstruction(assignment.right, scopes)) return false;
39832
- const refOwner = findEnclosingFunction$1(symbol.bindingIdentifier);
39833
- return refOwner !== null && findEnclosingFunction$1(assignment) === refOwner;
39834
- };
39835
39218
  const storesOnlyIntrinsicRefCallbackValues = (symbol, scopes) => {
39836
- const refCall = getDirectReactRefCall(symbol, scopes);
39837
- const initialValue = refCall?.arguments[0];
39838
- const hasDirectEmptyMapInitializer = Boolean(initialValue && isEmptyGlobalMapConstruction(initialValue, scopes));
39839
- const hasLazyEmptyMapInitializer = Boolean(refCall && hasEmptyRefSentinelInitializer(refCall, scopes));
39840
- if (!hasDirectEmptyMapInitializer && !hasLazyEmptyMapInitializer) return false;
39219
+ const initialValue = getDirectReactRefCall(symbol, scopes)?.arguments?.[0];
39220
+ if (!initialValue || !isNodeOfType(initialValue, "NewExpression") || !isNodeOfType(initialValue.callee, "Identifier") || initialValue.callee.name !== "Map" || !scopes.isGlobalReference(initialValue.callee) || initialValue.arguments.length !== 0) return false;
39841
39221
  let intrinsicValueWriteCount = 0;
39842
- let lazyEmptyMapInitializationCount = 0;
39843
39222
  for (const reference of symbol.references) {
39844
39223
  const identifier = findTransparentExpressionRoot(reference.identifier);
39845
39224
  const currentMember = identifier.parent;
39846
39225
  if (!isNodeOfType(currentMember, "MemberExpression") || currentMember.object !== identifier || getStaticPropertyName(currentMember) !== "current") return false;
39847
39226
  const currentExpression = findTransparentExpressionRoot(currentMember);
39848
- if (hasLazyEmptyMapInitializer && isDirectLazyEmptyMapInitialization(currentExpression, symbol, scopes)) {
39849
- lazyEmptyMapInitializationCount += 1;
39850
- continue;
39851
- }
39852
39227
  const methodMember = currentExpression.parent;
39853
39228
  if (!isNodeOfType(methodMember, "MemberExpression") || methodMember.object !== currentExpression) return false;
39854
39229
  const methodName = getStaticPropertyName(methodMember);
@@ -39861,7 +39236,7 @@ const storesOnlyIntrinsicRefCallbackValues = (symbol, scopes) => {
39861
39236
  if (!storedValue || isNodeOfType(storedValue, "SpreadElement") || !isIntrinsicRefCallbackParameter(storedValue, scopes)) return false;
39862
39237
  intrinsicValueWriteCount += 1;
39863
39238
  }
39864
- return intrinsicValueWriteCount > 0 && (hasDirectEmptyMapInitializer || lazyEmptyMapInitializationCount === 1);
39239
+ return intrinsicValueWriteCount > 0;
39865
39240
  };
39866
39241
  const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollectionValue = false, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
39867
39242
  const expression = stripParenExpression(rawExpression);
@@ -39875,11 +39250,7 @@ const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollecti
39875
39250
  }
39876
39251
  if (isNodeOfType(expression, "MemberExpression")) {
39877
39252
  if (getStaticPropertyName(expression) === "current") {
39878
- const symbol = resolveReactRefSymbol(expression, scopes, {
39879
- allowUnboundBareCalls: true,
39880
- includeCreateRef: true,
39881
- resolveNamedAliases: true
39882
- });
39253
+ const symbol = getDirectReactRefSymbol(expression.object, scopes);
39883
39254
  return Boolean(symbol && (isDirectHostJsxRef(symbol, scopes) || didReadCollectionValue && storesOnlyIntrinsicRefCallbackValues(symbol, scopes)));
39884
39255
  }
39885
39256
  return isDerivedFromProvenDomRefCurrent(expression.object, scopes, didReadCollectionValue, visitedSymbolIds);
@@ -39897,80 +39268,19 @@ const isCommittedDomSyncNode = (node, scopes) => {
39897
39268
  if (propertyName === null || !EXTERNAL_SYNC_DOM_MEMBER_METHOD_NAMES.has(propertyName)) return false;
39898
39269
  return isDerivedFromProvenDomRefCurrent(callee.object, scopes) || isProvenBrowserApiReceiver(callee.object, "dom-event-target", scopes);
39899
39270
  };
39900
- const isProvenHttpClientReceiver = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
39901
- const expression = stripParenExpression(rawExpression);
39902
- if (isNodeOfType(expression, "Identifier")) {
39903
- const receiverSymbol = scopes.symbolFor(expression);
39904
- if (receiverSymbol && !hasSafeReceiverAliases(new Set([receiverSymbol]), scopes)) return false;
39905
- }
39906
- if (hasProvenGlobalNamespaceReference(expression, EXTERNAL_SYNC_GLOBAL_HTTP_CLIENT_NAMES, scopes)) return true;
39907
- const moduleBinding = resolveExternalModuleBinding(expression, scopes);
39908
- if (moduleBinding && EXTERNAL_SYNC_HTTP_CLIENT_MODULE_SOURCES.has(moduleBinding.source) && (moduleBinding.isModuleObject || moduleBinding.exportedName === "default" && EXTERNAL_SYNC_DEFAULT_IMPORT_NAMES.has(moduleBinding.source) || isKnownExternalSyncDirectModuleBinding(moduleBinding))) return true;
39909
- if (isNodeOfType(expression, "CallExpression")) {
39910
- const callee = stripParenExpression(expression.callee);
39911
- return Boolean(isNodeOfType(callee, "MemberExpression") && getStaticPropertyName(callee) === "create" && isProvenHttpClientReceiver(callee.object, scopes, visitedSymbolIds));
39912
- }
39913
- if (!isNodeOfType(expression, "Identifier")) return false;
39914
- const symbol = scopes.symbolFor(expression);
39915
- if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
39916
- if (resolveExternalModuleBinding(expression, scopes, /* @__PURE__ */ new Set(), true)) return false;
39917
- const initializer = getDirectUnreassignedInitializer(symbol);
39918
- return Boolean(initializer && isProvenHttpClientReceiver(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id)));
39919
- };
39920
- const isProvenTanStackQueryClientReceiver = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
39921
- const expression = stripParenExpression(rawExpression);
39922
- if (isNodeOfType(expression, "Identifier")) {
39923
- const receiverSymbol = scopes.symbolFor(expression);
39924
- if (receiverSymbol && !hasSafeReceiverAliases(new Set([receiverSymbol]), scopes)) return false;
39925
- }
39926
- if (isNodeOfType(expression, "CallExpression")) {
39927
- const binding = resolveExternalModuleBinding(expression.callee, scopes);
39928
- return Boolean(binding && binding.source === "@tanstack/react-query" && binding.exportedName === "useQueryClient");
39929
- }
39930
- if (isNodeOfType(expression, "NewExpression")) {
39931
- const binding = resolveExternalModuleBinding(expression.callee, scopes);
39932
- return Boolean(binding && TANSTACK_QUERY_CLIENT_MODULE_SOURCES.has(binding.source) && binding.exportedName === "QueryClient");
39933
- }
39934
- if (!isNodeOfType(expression, "Identifier")) return false;
39935
- const symbol = scopes.symbolFor(expression);
39936
- if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
39937
- const initializer = getDirectUnreassignedInitializer(symbol);
39938
- return Boolean(initializer && isProvenTanStackQueryClientReceiver(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id)));
39939
- };
39940
- const isProvenExternalResourceReceiver = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
39941
- const expression = stripParenExpression(rawExpression);
39942
- if (isDerivedFromProvenDomRefCurrent(expression, scopes) || isProvenBrowserApiReceiver(expression, "dom-event-target", scopes) || isProvenBrowserApiReceiver(expression, "xml-http-request", scopes) || isProvenHttpClientReceiver(expression, scopes)) return true;
39943
- if (isNodeOfType(expression, "NewExpression")) return hasProvenGlobalNamespaceReference(expression.callee, EXTERNAL_SYNC_RESOURCE_CONSTRUCTOR_NAMES, scopes);
39944
- if (!isNodeOfType(expression, "Identifier")) return false;
39945
- const symbol = scopes.symbolFor(expression);
39946
- if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
39947
- const initializer = getDirectUnreassignedInitializer(symbol);
39948
- return Boolean(initializer && isProvenExternalResourceReceiver(initializer, scopes, new Set(visitedSymbolIds).add(symbol.id)));
39949
- };
39950
- const isProvenExternalSyncDirectCallee = (callee, scopes) => {
39951
- if (hasProvenGlobalNamespaceReference(callee, EXTERNAL_SYNC_DIRECT_CALLEE_NAMES, scopes)) return true;
39952
- const moduleBinding = resolveExternalModuleBinding(callee, scopes);
39953
- return Boolean(moduleBinding && isKnownExternalSyncDirectModuleBinding(moduleBinding));
39954
- };
39955
- const isExternalSyncNode = (node, scopes) => {
39956
- if (isNodeOfType(node, "NewExpression")) return hasProvenGlobalNamespaceReference(node.callee, EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS, scopes);
39957
- if (isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression")) {
39958
- const mutationTarget = isNodeOfType(node, "AssignmentExpression") ? node.left : node.argument;
39959
- return isNodeOfType(mutationTarget, "MemberExpression") && getStaticPropertyName(mutationTarget) === "current" && Boolean(resolveReactRefSymbol(mutationTarget, scopes, {
39960
- allowUnboundBareCalls: true,
39961
- includeCreateRef: true,
39962
- resolveNamedAliases: true
39963
- }));
39964
- }
39271
+ const isExternalSyncNode = (node) => {
39272
+ if (isNodeOfType(node, "NewExpression")) return isNodeOfType(node.callee, "Identifier") && EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS.has(node.callee.name);
39273
+ if (isNodeOfType(node, "AssignmentExpression")) return isNodeOfType(node.left, "MemberExpression") && isNodeOfType(node.left.property, "Identifier") && node.left.property.name === "current";
39965
39274
  if (!isNodeOfType(node, "CallExpression")) return false;
39966
- if (isProvenExternalSyncDirectCallee(node.callee, scopes)) return true;
39275
+ if (isNodeOfType(node.callee, "Identifier")) return EXTERNAL_SYNC_DIRECT_CALLEE_NAMES.has(node.callee.name);
39967
39276
  if (!isNodeOfType(node.callee, "MemberExpression")) return false;
39968
39277
  const propertyName = getStaticPropertyName(node.callee);
39969
39278
  if (propertyName === null) return false;
39970
- if (isBrowserStorageReceiver(node.callee.object, scopes)) return true;
39971
- if (TANSTACK_QUERY_EXTERNAL_SYNC_METHOD_NAMES.has(propertyName)) return isProvenTanStackQueryClientReceiver(node.callee.object, scopes);
39972
- if (EXTERNAL_SYNC_HTTP_METHOD_NAMES.has(propertyName)) return isProvenHttpClientReceiver(node.callee.object, scopes);
39973
- return EXTERNAL_SYNC_MEMBER_METHOD_NAMES.has(propertyName) && isProvenExternalResourceReceiver(node.callee.object, scopes);
39279
+ if (EXTERNAL_SYNC_MEMBER_METHOD_NAMES.has(propertyName)) return true;
39280
+ if (isBrowserStorageReceiver(node.callee.object)) return true;
39281
+ if (!EXTERNAL_SYNC_AMBIGUOUS_HTTP_METHOD_NAMES.has(propertyName)) return false;
39282
+ const receiverRootName = getRootIdentifierName(node.callee.object);
39283
+ return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
39974
39284
  };
39975
39285
  const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, scopes, allowCommittedDomSync) => {
39976
39286
  if (!isFunctionLike$1(effectCallback)) return false;
@@ -39979,7 +39289,7 @@ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateNa
39979
39289
  } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, setterSymbolIdToStateName, scopes, true)) return true;
39980
39290
  let didFindExternalCall = false;
39981
39291
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
39982
- if (isExternalSyncNode(child, scopes) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
39292
+ if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
39983
39293
  });
39984
39294
  return didFindExternalCall;
39985
39295
  };
@@ -40001,11 +39311,11 @@ const noEffectChain = defineRule({
40001
39311
  setterToStateName.set(binding.setterName, binding.valueName);
40002
39312
  if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
40003
39313
  const stateIdentifier = binding.declarator.id.elements[0];
40004
- const setterIdentifier = binding.declarator.id.elements[1];
40005
39314
  if (isNodeOfType(stateIdentifier, "Identifier")) {
40006
39315
  const stateSymbol = context.scopes.symbolFor(stateIdentifier);
40007
39316
  if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
40008
39317
  }
39318
+ const setterIdentifier = binding.declarator.id.elements[1];
40009
39319
  if (isNodeOfType(setterIdentifier, "Identifier")) {
40010
39320
  const setterSymbol = context.scopes.symbolFor(setterIdentifier);
40011
39321
  if (setterSymbol) setterSymbolIdToStateName.set(setterSymbol.id, binding.valueName);
@@ -40018,11 +39328,10 @@ const noEffectChain = defineRule({
40018
39328
  const callback = getEffectCallback(effectCall, context.scopes);
40019
39329
  if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
40020
39330
  const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
40021
- const stateWrites = collectStateWritesInEffect(collectStateWriteAnalysisFunctions(callback, context.scopes, setterSymbolIdToStateName), setterSymbolIdToStateName, context.scopes);
39331
+ const stateWrites = collectStateWritesInEffect(analysisFunctions, setterSymbolIdToStateName, context.scopes);
40022
39332
  const writtenStateNames = new Set(stateWrites.keys());
40023
39333
  effectInfos.push({
40024
39334
  node: effectCall,
40025
- callback,
40026
39335
  dependencyStateSymbolIds: collectDependencyStateSymbolIds(effectCall, stateSymbolIdSet, context.scopes),
40027
39336
  stateWrites,
40028
39337
  analysisFunctions,
@@ -40039,10 +39348,10 @@ const noEffectChain = defineRule({
40039
39348
  if (readerEffect.isExternalSync) continue;
40040
39349
  if (readerEffect.dependencyStateSymbolIds.size === 0) continue;
40041
39350
  let chainedStateName = null;
40042
- for (const writtenName of writerEffect.stateWrites.keys()) {
39351
+ for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
40043
39352
  const writtenStateSymbolId = stateSymbolIds.get(writtenName);
40044
39353
  if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
40045
- if (!canStateWriteReachReaderWork(writtenName, writerEffect, readerEffect, stateSymbolIds, context.scopes)) continue;
39354
+ if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
40046
39355
  chainedStateName = writtenName;
40047
39356
  break;
40048
39357
  }
@@ -44827,6 +44136,16 @@ const noManyBooleanProps = defineRule({
44827
44136
  }
44828
44137
  });
44829
44138
  //#endregion
44139
+ //#region src/plugin/utils/is-global-match-media-call.ts
44140
+ const isGlobalMatchMediaCall = (node, scopes) => {
44141
+ if (!isNodeOfType(node, "CallExpression")) return false;
44142
+ const callee = stripParenExpression(node.callee);
44143
+ if (isNodeOfType(callee, "Identifier")) return callee.name === "matchMedia" && scopes.isGlobalReference(callee);
44144
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "matchMedia") return false;
44145
+ const receiver = stripParenExpression(callee.object);
44146
+ return isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && scopes.isGlobalReference(receiver);
44147
+ };
44148
+ //#endregion
44830
44149
  //#region src/plugin/rules/performance/no-match-media-in-state-initializer.ts
44831
44150
  const REACT_USE_STATE_OPTIONS = {
44832
44151
  allowGlobalReactNamespace: true,
@@ -47216,103 +46535,6 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
47216
46535
  "useVisibility",
47217
46536
  "useWindowSize"
47218
46537
  ]);
47219
- const EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES = new Set([
47220
- "useMatchMedia",
47221
- "useMediaQuery",
47222
- "useVisibility"
47223
- ]);
47224
- const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
47225
- const getImportedExternalSubscriptionHookName = (analysis, rawCallee) => {
47226
- const callee = stripParenExpression(rawCallee);
47227
- if (isNodeOfType(callee, "Identifier")) {
47228
- const calleeRef = getRef(analysis, callee);
47229
- if (!calleeRef || !isImportBindingRef(calleeRef)) return null;
47230
- const importBinding = getImportBindingForName(callee, callee.name);
47231
- if (!importBinding || importBinding.isNamespace) return null;
47232
- if (importBinding.exportedName && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(importBinding.exportedName)) return importBinding.exportedName;
47233
- return importBinding.exportedName === "default" && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name) ? callee.name : null;
47234
- }
47235
- if (!isNodeOfType(callee, "MemberExpression")) return null;
47236
- const hookName = getStaticMemberPropertyName(callee);
47237
- const namespaceIdentifier = stripParenExpression(callee.object);
47238
- if (!hookName || !EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(hookName) || !isNodeOfType(namespaceIdentifier, "Identifier")) return null;
47239
- const namespaceRef = getRef(analysis, namespaceIdentifier);
47240
- if (!namespaceRef || !isImportBindingRef(namespaceRef)) return null;
47241
- return getImportBindingForName(namespaceIdentifier, namespaceIdentifier.name)?.isNamespace ? hookName : null;
47242
- };
47243
- const isSafeExternalSubscriptionResultBinding = (bindingIdentifier, bindingPattern) => {
47244
- let current = bindingIdentifier;
47245
- let didCrossDestructuringBoundary = false;
47246
- while (current !== bindingPattern) {
47247
- const parent = current.parent;
47248
- if (!parent || isNodeOfType(parent, "AssignmentPattern") || isNodeOfType(parent, "RestElement")) return false;
47249
- if (isNodeOfType(parent, "Property")) {
47250
- if (parent.value !== current) return false;
47251
- didCrossDestructuringBoundary = true;
47252
- }
47253
- if (isNodeOfType(parent, "ArrayPattern")) didCrossDestructuringBoundary = true;
47254
- current = parent;
47255
- }
47256
- return didCrossDestructuringBoundary;
47257
- };
47258
- const getVariablesDefinedByDeclarator = (analysis, declarator) => analysis.scopeManager.scopes.flatMap((scope) => scope.variables).filter((variable) => variable.defs.some((definition) => definition.node === declarator));
47259
- const hasUnsafeExternalSubscriptionBindingUse = (analysis, variable, visitedVariables = /* @__PURE__ */ new Set()) => {
47260
- if (visitedVariables.has(variable)) return false;
47261
- visitedVariables.add(variable);
47262
- return variable.references.some((candidateReference) => {
47263
- if (candidateReference.init) return false;
47264
- if (candidateReference.isWrite()) return true;
47265
- let usageRoot = findTransparentExpressionRoot(candidateReference.identifier);
47266
- while (isNodeOfType(usageRoot.parent, "MemberExpression") && usageRoot.parent.object === usageRoot) usageRoot = findTransparentExpressionRoot(usageRoot.parent);
47267
- const usageParent = usageRoot.parent;
47268
- if (isNodeOfType(usageParent, "AssignmentExpression") && usageParent.left === usageRoot || isNodeOfType(usageParent, "UpdateExpression") && usageParent.argument === usageRoot || isNodeOfType(usageParent, "UnaryExpression") && usageParent.operator === "delete" && usageParent.argument === usageRoot) return true;
47269
- let usageAncestor = usageRoot.parent;
47270
- while (usageAncestor && !isNodeOfType(usageAncestor, "VariableDeclarator") && !isFunctionLike$1(usageAncestor) && !isNodeOfType(usageAncestor, "Program")) usageAncestor = usageAncestor.parent;
47271
- if (isNodeOfType(usageAncestor, "VariableDeclarator") && usageAncestor.init) {
47272
- const aliasVariables = getVariablesDefinedByDeclarator(analysis, usageAncestor);
47273
- return aliasVariables.length === 0 || aliasVariables.some((aliasVariable) => hasUnsafeExternalSubscriptionBindingUse(analysis, aliasVariable, visitedVariables));
47274
- }
47275
- return false;
47276
- });
47277
- };
47278
- const hasOnlySafeExternalSubscriptionResultBindings = (analysis, declarator, variables = getVariablesDefinedByDeclarator(analysis, declarator)) => {
47279
- return variables.length > 0 && variables.every((variable) => {
47280
- const bindingIdentifier = variable.defs.find((candidateDefinition) => candidateDefinition.node === declarator)?.name;
47281
- return Boolean(bindingIdentifier && isNodeOfType(bindingIdentifier, "Identifier") && isSafeExternalSubscriptionResultBinding(bindingIdentifier, declarator.id) && !hasUnsafeExternalSubscriptionBindingUse(analysis, variable));
47282
- });
47283
- };
47284
- const hasImmutableExternalSubscriptionCallResult = (analysis, rawCallee, relatedRefs, allowWholeResult) => {
47285
- const callee = stripParenExpression(rawCallee);
47286
- const callExpression = callee.parent;
47287
- if (!callExpression || !isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== callee) return false;
47288
- const initializer = findTransparentExpressionRoot(callExpression);
47289
- const declarator = initializer.parent;
47290
- if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== initializer || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return false;
47291
- const relatedVariables = getVariablesDefinedByDeclarator(analysis, declarator).filter((variable) => relatedRefs.some((relatedRef) => relatedRef.resolved === variable));
47292
- if (relatedVariables.length === 0) return false;
47293
- return isNodeOfType(declarator.id, "Identifier") ? allowWholeResult && relatedVariables.every((variable) => !hasUnsafeExternalSubscriptionBindingUse(analysis, variable)) : hasOnlySafeExternalSubscriptionResultBindings(analysis, declarator, relatedVariables);
47294
- };
47295
- const isImmutableImportedExternalSubscriptionHookCallee = (analysis, rawCallee, relatedRefs) => {
47296
- const hookName = getImportedExternalSubscriptionHookName(analysis, rawCallee);
47297
- return Boolean(hookName && hasImmutableExternalSubscriptionCallResult(analysis, rawCallee, relatedRefs, EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES.has(hookName)));
47298
- };
47299
- const isExternalSubscriptionHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
47300
- const declarator = def.node;
47301
- if (!isNodeOfType(declarator, "VariableDeclarator") || !declarator.init || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return false;
47302
- const initializer = stripParenExpression(declarator.init);
47303
- if (!isNodeOfType(initializer, "CallExpression")) return false;
47304
- const hookName = getImportedExternalSubscriptionHookName(analysis, initializer.callee);
47305
- if (!hookName) return false;
47306
- if (isNodeOfType(declarator.id, "Identifier")) return EXTERNAL_SUBSCRIPTION_PRIMITIVE_RESULT_HOOK_NAMES.has(hookName) && ref.resolved && !hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved);
47307
- const bindingIdentifier = def.name;
47308
- return isNodeOfType(bindingIdentifier, "Identifier") && isSafeExternalSubscriptionResultBinding(bindingIdentifier, declarator.id) && ref.resolved && !hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved);
47309
- }));
47310
- const isExternalSubscriptionHookResultArgument = (analysis, argument) => {
47311
- const unwrappedArgument = stripParenExpression(argument);
47312
- if (!isNodeOfType(unwrappedArgument, "Identifier")) return false;
47313
- const argumentRef = getRef(analysis, unwrappedArgument);
47314
- return Boolean(argumentRef && isExternalSubscriptionHookResultRef(analysis, argumentRef));
47315
- };
47316
46538
  const isCallbackPropReference = (analysis, ref) => {
47317
46539
  if (!isProp(analysis, ref)) return false;
47318
46540
  const identifier = ref.identifier;
@@ -47345,9 +46567,8 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
47345
46567
  if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
47346
46568
  return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
47347
46569
  };
47348
- const getLocalHookExternalStateProof = (analysis, ref, scopes) => {
46570
+ const getLocalHookExternalStateProof = (analysis, ref) => {
47349
46571
  let hookFunction = resolveToFunction(ref);
47350
- let didResolveThroughResultBinding = false;
47351
46572
  if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
47352
46573
  const definitionNode = definition.node;
47353
46574
  if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
@@ -47358,27 +46579,29 @@ const getLocalHookExternalStateProof = (analysis, ref, scopes) => {
47358
46579
  const calleeReference = getRef(analysis, callee);
47359
46580
  if (!calleeReference) continue;
47360
46581
  hookFunction = resolveToFunction(calleeReference);
47361
- if (hookFunction) {
47362
- didResolveThroughResultBinding = true;
47363
- break;
47364
- }
46582
+ if (hookFunction) break;
47365
46583
  }
47366
46584
  if (!hookFunction) return null;
47367
- if (didResolveThroughResultBinding && (!ref.resolved || hasUnsafeExternalSubscriptionBindingUse(analysis, ref.resolved))) return false;
47368
- if (isNodeOfType(hookFunction, "ArrowFunctionExpression") && !isNodeOfType(hookFunction.body, "BlockStatement") && isReactHookCall(stripParenExpression(hookFunction.body), "useSyncExternalStore", scopes)) return true;
47369
- const returnStatements = collectFunctionReturnStatements(hookFunction);
47370
- if (returnStatements.length > 0 && returnStatements.every((returnStatement) => returnStatement.argument && isReactHookCall(stripParenExpression(returnStatement.argument), "useSyncExternalStore", scopes))) return true;
47371
- const returnedReferences = returnStatements.flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
46585
+ const returnedReferences = collectFunctionReturnStatements(hookFunction).flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
47372
46586
  if (returnedReferences.length === 0) return null;
47373
46587
  return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
47374
46588
  };
47375
- const isImmutableLocalExternalStoreHookCallee = (analysis, rawCallee, relatedRefs, scopes) => {
47376
- const callee = stripParenExpression(rawCallee);
47377
- if (!isNodeOfType(callee, "Identifier")) return false;
47378
- const calleeReference = getRef(analysis, callee);
47379
- if (!calleeReference || getLocalHookExternalStateProof(analysis, calleeReference, scopes) !== true) return false;
47380
- return hasImmutableExternalSubscriptionCallResult(analysis, callee, relatedRefs, true);
46589
+ const isExternalSubscriptionHookRef = (analysis, ref) => {
46590
+ const identifier = ref.identifier;
46591
+ if (!isNodeOfType(identifier, "Identifier")) return false;
46592
+ const localHookProof = getLocalHookExternalStateProof(analysis, ref);
46593
+ if (localHookProof !== null) return localHookProof;
46594
+ if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
46595
+ return Boolean(ref.resolved?.defs.some((def) => {
46596
+ const node = def.node;
46597
+ if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
46598
+ const initializer = stripParenExpression(node.init);
46599
+ if (!isNodeOfType(initializer, "CallExpression")) return false;
46600
+ const callee = stripParenExpression(initializer.callee);
46601
+ return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name);
46602
+ }));
47381
46603
  };
46604
+ const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
47382
46605
  const isCalleePosition = (identifier) => {
47383
46606
  const parent = identifier.parent;
47384
46607
  return Boolean(parent && (isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression")) && parent.callee === identifier);
@@ -47439,35 +46662,25 @@ const noPassDataToParent = defineRule({
47439
46662
  return getFunctionalUpdaterDataRefs(analysis, argument);
47440
46663
  }
47441
46664
  if (isHandlerBagArgument(analysis, argument)) return [];
47442
- if (isExternalSubscriptionHookResultArgument(analysis, argument)) return [];
47443
46665
  if (isParentWiredHookResultArgument(analysis, argument)) return [];
47444
46666
  if (isNodeOfType(argument, "Identifier")) {
47445
46667
  const argumentRef = getRef(analysis, argument);
47446
46668
  if (argumentRef && resolveToFunction(argumentRef)) return [];
47447
46669
  }
47448
46670
  return getDownstreamRefs(analysis, argument);
47449
- }).flatMap((argumentRef) => {
47450
- if (isExternallyDrivenState(analysis, argumentRef) || getLocalHookExternalStateProof(analysis, argumentRef, context.scopes) === true) return [];
47451
- const upstreamRefs = getUpstreamRefs(analysis, argumentRef);
47452
- return upstreamRefs.filter((upstreamRef) => {
47453
- if (!isLeafRef(upstreamRef)) return false;
47454
- return !isImmutableImportedExternalSubscriptionHookCallee(analysis, upstreamRef.identifier, upstreamRefs) && !isImmutableLocalExternalStoreHookCallee(analysis, upstreamRef.identifier, upstreamRefs, context.scopes);
47455
- });
47456
- });
47457
- if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) {
47458
- const wrapperUpstreamRefs = getArgsUpstreamRefs(analysis, ref);
47459
- argsUpstreamRefs.push(...wrapperUpstreamRefs.filter((upstreamRef) => isLeafRef(upstreamRef) && !isImmutableImportedExternalSubscriptionHookCallee(analysis, upstreamRef.identifier, wrapperUpstreamRefs) && !isImmutableLocalExternalStoreHookCallee(analysis, upstreamRef.identifier, wrapperUpstreamRefs, context.scopes)));
47460
- }
46671
+ }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) || isExternalSubscriptionHookRef(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
46672
+ if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
47461
46673
  if (!argsUpstreamRefs.some((argRef) => {
47462
- const argIdentifier = argRef.identifier;
47463
- if (isUseStateIdentifier(argIdentifier)) return false;
46674
+ if (isUseStateIdentifier(argRef.identifier)) return false;
46675
+ if (isExternalSubscriptionHookRef(analysis, argRef)) return false;
47464
46676
  if (isProp(analysis, argRef)) return false;
47465
- if (isUseRefIdentifier(argIdentifier)) return false;
46677
+ if (isUseRefIdentifier(argRef.identifier)) return false;
47466
46678
  if (isRefCurrent(argRef)) return false;
47467
46679
  if (isConstant(argRef)) return false;
47468
46680
  if (isParentWiredHookResultRef(analysis, argRef)) return false;
47469
46681
  if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
47470
46682
  if (resolveToFunction(argRef)) return false;
46683
+ const argIdentifier = argRef.identifier;
47471
46684
  if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
47472
46685
  if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
47473
46686
  return true;
@@ -56458,6 +55671,17 @@ const preferModuleScopePureFunction = defineRule({
56458
55671
  }
56459
55672
  });
56460
55673
  //#endregion
55674
+ //#region src/plugin/utils/get-require-call-source.ts
55675
+ const getRequireCallSource = (expression) => {
55676
+ const unwrappedExpression = stripParenExpression(expression);
55677
+ if (isNodeOfType(unwrappedExpression, "MemberExpression")) return getRequireCallSource(unwrappedExpression.object);
55678
+ if (!isNodeOfType(unwrappedExpression, "CallExpression")) return null;
55679
+ if (!isNodeOfType(unwrappedExpression.callee, "Identifier") || unwrappedExpression.callee.name !== "require") return null;
55680
+ const [firstArgument] = unwrappedExpression.arguments ?? [];
55681
+ if (!firstArgument || !isNodeOfType(firstArgument, "Literal")) return null;
55682
+ return typeof firstArgument.value === "string" ? firstArgument.value : null;
55683
+ };
55684
+ //#endregion
56461
55685
  //#region src/plugin/utils/is-proven-node-crypto-namespace-reference.ts
56462
55686
  const NODE_CRYPTO_MODULE_SOURCES = new Set(["crypto", "node:crypto"]);
56463
55687
  const isProvenNodeCryptoNamespaceReference = (expression, scopes) => {
@@ -57482,8 +56706,7 @@ const preferUseReducer = defineRule({
57482
56706
  //#endregion
57483
56707
  //#region src/plugin/rules/security-scan/utils/is-public-debug-artifact-path.ts
57484
56708
  const LOCALE_DIRECTORY_PATTERN = /(?:^|\/)(?:locales?|i18n|lang|langs|translations?)\//i;
57485
- const PUBLIC_DEBUG_ARTIFACT_PATH_PATTERN = /(?:^|\/)(?:\.env(?:\.[^/]*)?|(?:debug|crash|trace|stack[-_]?trace|report|dump|phpinfo)(?:[-_.][^/]*)?\.(?:txt|log|json|html?)|[^/]+\.log)$/i;
57486
- const isPublicDebugArtifactPath = (relativePath) => isBrowserArtifactPath(relativePath, GENERATED_BUNDLE_FILE_PATTERN.test(relativePath)) && !LOCALE_DIRECTORY_PATTERN.test(relativePath) && PUBLIC_DEBUG_ARTIFACT_PATH_PATTERN.test(relativePath);
56709
+ const isPublicDebugArtifactPath = (relativePath) => isBrowserArtifactPath(relativePath, GENERATED_BUNDLE_FILE_PATTERN.test(relativePath)) && !LOCALE_DIRECTORY_PATTERN.test(relativePath) && /(?:^|\/)(?:\.env(?:\.[^/]*)?|[^/]*(?:debug|crash|trace|stack|report|dump|phpinfo)[^/]*\.(?:txt|log|json|html?)|[^/]+\.log)$/i.test(relativePath);
57487
56710
  //#endregion
57488
56711
  //#region src/plugin/rules/security-scan/public-debug-artifact.ts
57489
56712
  const publicDebugArtifact = defineRule({
@@ -59785,7 +59008,7 @@ const renderingUsetransitionLoading = defineRule({
59785
59008
  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);
59786
59009
  //#endregion
59787
59010
  //#region src/plugin/rules/security-scan/repository-secret-file.ts
59788
- const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env(?:\.[^./]+)*\.(?:example|sample|template|dist|defaults?)$|(?:^|\/)[^/]*(?:example|sample|template)[^/]*\.(?:env|json|pem|key)$/i.test(relativePath);
59011
+ const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env\.(?:example|sample|template|dist|defaults?)$|(?:^|\/)[^/]*(?:example|sample|template)[^/]*\.(?:env|json|pem|key)$/i.test(relativePath);
59789
59012
  const repositorySecretFile = defineRule({
59790
59013
  id: "repository-secret-file",
59791
59014
  title: "Secret file checked into repository",
@@ -61166,717 +60389,6 @@ const isSetterCalledDuringRender = (root, setterName) => {
61166
60389
  return found;
61167
60390
  };
61168
60391
  //#endregion
61169
- //#region src/plugin/rules/state-and-effects/utils/create-external-location-invalidation-checker.ts
61170
- const HISTORY_LOCATION_MUTATION_METHOD_NAMES = new Set(["pushState", "replaceState"]);
61171
- const AGGREGATE_MUTATION_METHOD_NAMES = new Set([...MUTATING_ARRAY_METHODS, ...MUTATING_COLLECTION_METHODS]);
61172
- const OBJECT_AGGREGATE_MUTATION_METHOD_NAMES = new Set([
61173
- "assign",
61174
- "defineProperties",
61175
- "defineProperty",
61176
- "setPrototypeOf"
61177
- ]);
61178
- const REFLECT_AGGREGATE_MUTATION_METHOD_NAMES = new Set([
61179
- "defineProperty",
61180
- "deleteProperty",
61181
- "set",
61182
- "setPrototypeOf"
61183
- ]);
61184
- const LOCATION_CHANGE_EVENT_NAMES = new Set(["hashchange", "popstate"]);
61185
- const containsGlobalLocationSnapshotRead = (node, scopes) => {
61186
- let didFindLocationSnapshotRead = false;
61187
- walkAst(node, (child) => {
61188
- if (didFindLocationSnapshotRead) return false;
61189
- if (child !== node && isFunctionLike$1(child) && !executesDuringRender(findTransparentExpressionRoot(child), scopes)) return false;
61190
- if (!isProvenGlobalNamespaceReference(child, "location", scopes)) return;
61191
- didFindLocationSnapshotRead = true;
61192
- return false;
61193
- });
61194
- return didFindLocationSnapshotRead;
61195
- };
61196
- const resolveExactLocalOrReactCallbackFunction = (expression, scopes) => {
61197
- const localFunction = resolveExactLocalFunction(expression, scopes);
61198
- if (isFunctionLike$1(localFunction)) return localFunction;
61199
- const unwrappedExpression = stripParenExpression(expression);
61200
- let callbackSource = unwrappedExpression;
61201
- if (isNodeOfType(unwrappedExpression, "Identifier")) {
61202
- const callbackSymbol = resolveConstIdentifierAlias(unwrappedExpression, scopes);
61203
- if (callbackSymbol?.kind !== "const" || !callbackSymbol.initializer) return null;
61204
- callbackSource = callbackSymbol.initializer;
61205
- }
61206
- const unwrappedCallbackSource = stripParenExpression(callbackSource);
61207
- if (!isNodeOfType(unwrappedCallbackSource, "CallExpression") || !isReactApiCall(unwrappedCallbackSource, "useCallback", scopes, { resolveNamedAliases: true })) return null;
61208
- const callback = unwrappedCallbackSource.arguments?.[0];
61209
- if (!callback || isNodeOfType(callback, "SpreadElement")) return null;
61210
- return resolveExactLocalFunction(stripParenExpression(callback), scopes);
61211
- };
61212
- const bindingReadsExactGlobalLocationSnapshot = (bindingIdentifier, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
61213
- const symbol = scopes.symbolFor(bindingIdentifier);
61214
- if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
61215
- 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;
61216
- const resolvedFunction = resolveExactLocalOrReactCallbackFunction(bindingIdentifier, scopes);
61217
- const initializer = isFunctionLike$1(resolvedFunction) ? resolvedFunction.body : getDirectUnreassignedInitializer(symbol);
61218
- if (!initializer) return false;
61219
- const nextVisitedSymbolIds = new Set(visitedSymbolIds);
61220
- nextVisitedSymbolIds.add(symbol.id);
61221
- if (containsGlobalLocationSnapshotRead(initializer, scopes)) return true;
61222
- let didFindAliasedLocationSnapshotRead = false;
61223
- walkAst(initializer, (child) => {
61224
- if (didFindAliasedLocationSnapshotRead) return false;
61225
- if (child !== initializer && isFunctionLike$1(child) && !executesDuringRender(findTransparentExpressionRoot(child), scopes)) return false;
61226
- if (!isNodeOfType(child, "Identifier")) return;
61227
- if (bindingReadsExactGlobalLocationSnapshot(child, scopes, nextVisitedSymbolIds)) {
61228
- didFindAliasedLocationSnapshotRead = true;
61229
- return false;
61230
- }
61231
- });
61232
- return didFindAliasedLocationSnapshotRead;
61233
- };
61234
- const hasRenderReachableLocationSnapshotRead = (componentBody, renderReachableExpressions, directRenderNames, scopes) => {
61235
- if (renderReachableExpressions.some((expression) => containsGlobalLocationSnapshotRead(expression, scopes))) return true;
61236
- for (const statement of componentBody.body ?? []) {
61237
- if (isNodeOfType(statement, "FunctionDeclaration") && statement.id) {
61238
- if (directRenderNames.has(statement.id.name) && bindingReadsExactGlobalLocationSnapshot(statement.id, scopes)) return true;
61239
- continue;
61240
- }
61241
- if (!isNodeOfType(statement, "VariableDeclaration")) continue;
61242
- for (const declarator of statement.declarations ?? []) {
61243
- if (!isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
61244
- if (!directRenderNames.has(declarator.id.name)) continue;
61245
- if (bindingReadsExactGlobalLocationSnapshot(declarator.id, scopes)) return true;
61246
- }
61247
- }
61248
- return false;
61249
- };
61250
- const isGlobalHistoryLocationMutation = (node, scopes) => {
61251
- if (!isNodeOfType(node, "CallExpression")) return false;
61252
- const callee = stripParenExpression(node.callee);
61253
- if (!isNodeOfType(callee, "MemberExpression")) return false;
61254
- const methodName = getStaticPropertyName(callee);
61255
- return Boolean(methodName && HISTORY_LOCATION_MUTATION_METHOD_NAMES.has(methodName) && isProvenGlobalNamespaceReference(callee.object, "history", scopes));
61256
- };
61257
- const getStaticLocationChangeEvent = (node) => {
61258
- if (!node) return null;
61259
- const eventNameNode = stripParenExpression(node);
61260
- if (!isNodeOfType(eventNameNode, "Literal") || typeof eventNameNode.value !== "string") return null;
61261
- return LOCATION_CHANGE_EVENT_NAMES.has(eventNameNode.value) ? eventNameNode.value : null;
61262
- };
61263
- const addToSetIndex = (index, key, value) => {
61264
- const indexedValues = index.get(key) ?? /* @__PURE__ */ new Set();
61265
- indexedValues.add(value);
61266
- index.set(key, indexedValues);
61267
- };
61268
- const getSynchronousInvocationExpression = (functionNode, scopes) => {
61269
- const functionExpressionRoot = findTransparentExpressionRoot(functionNode);
61270
- if (!executesDuringRender(functionExpressionRoot, scopes)) return null;
61271
- const parent = functionExpressionRoot.parent;
61272
- return isNodeOfType(parent, "CallExpression") || isNodeOfType(parent, "NewExpression") ? parent : null;
61273
- };
61274
- const getLocationListenerOperation = (callExpression, scopes) => {
61275
- if (!isNodeOfType(callExpression, "CallExpression")) return null;
61276
- const callee = stripParenExpression(callExpression.callee);
61277
- const methodName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : isNodeOfType(callee, "Identifier") ? callee.name : null;
61278
- if (methodName !== "addEventListener" && methodName !== "removeEventListener") return null;
61279
- if (!(isNodeOfType(callee, "MemberExpression") ? isProvenGlobalObjectReference(callee.object, scopes) : isNodeOfType(callee, "Identifier") && scopes.isGlobalReference(callee))) return null;
61280
- const eventName = getStaticLocationChangeEvent(callExpression.arguments?.[0]);
61281
- if (!eventName) return null;
61282
- const listenerExpression = callExpression.arguments?.[1];
61283
- if (!listenerExpression || isNodeOfType(listenerExpression, "SpreadElement")) return null;
61284
- const listenerFunction = resolveExactLocalOrReactCallbackFunction(listenerExpression, scopes);
61285
- if (!isFunctionLike$1(listenerFunction)) return null;
61286
- const captureArgument = callExpression.arguments?.[2];
61287
- const capture = isNodeOfType(captureArgument, "SpreadElement") ? null : resolveEventListenerCapture(captureArgument, {
61288
- allowComputedString: true,
61289
- allowIndeterminateEntries: true
61290
- });
61291
- return {
61292
- operation: methodName === "addEventListener" ? "add" : "remove",
61293
- registration: {
61294
- callExpression,
61295
- listenerFunction,
61296
- capture,
61297
- eventName
61298
- }
61299
- };
61300
- };
61301
- const buildLocationInvalidationIndex = (componentBody, componentFunction, context) => {
61302
- const index = {
61303
- componentFunction,
61304
- context,
61305
- effectCallbacks: /* @__PURE__ */ new Set(),
61306
- expressionsByOwner: /* @__PURE__ */ new Map(),
61307
- historyMutationsByOwner: /* @__PURE__ */ new Map(),
61308
- awaitExpressionsByOwner: /* @__PURE__ */ new Map(),
61309
- callSitesByFunction: /* @__PURE__ */ new Map(),
61310
- calledFunctionByExpression: /* @__PURE__ */ new Map(),
61311
- synchronousInvocationsByFunction: /* @__PURE__ */ new Map(),
61312
- synchronousCallbacksByExpression: /* @__PURE__ */ new Map(),
61313
- callsByCalleeSymbolId: /* @__PURE__ */ new Map(),
61314
- identifierCalls: /* @__PURE__ */ new Set(),
61315
- listenerRegistrations: [],
61316
- listenerRemovals: [],
61317
- mountedListenerFunctions: /* @__PURE__ */ new Set(),
61318
- mutationExecutionsByOwner: /* @__PURE__ */ new Map(),
61319
- synchronousMutationResultByFunction: /* @__PURE__ */ new Map()
61320
- };
61321
- walkAst(componentBody, (child) => {
61322
- if (isFunctionLike$1(child)) {
61323
- const invocationExpression = getSynchronousInvocationExpression(child, context.scopes);
61324
- if (invocationExpression) {
61325
- addToSetIndex(index.synchronousInvocationsByFunction, child, invocationExpression);
61326
- addToSetIndex(index.synchronousCallbacksByExpression, invocationExpression, child);
61327
- }
61328
- return;
61329
- }
61330
- const owner = context.cfg.enclosingFunction(child);
61331
- if (!owner) return;
61332
- if (isNodeOfType(child, "AwaitExpression")) {
61333
- addToSetIndex(index.awaitExpressionsByOwner, owner, child);
61334
- return;
61335
- }
61336
- if (!isNodeOfType(child, "CallExpression") && !isNodeOfType(child, "NewExpression")) return;
61337
- addToSetIndex(index.expressionsByOwner, owner, child);
61338
- if (isNodeOfType(child, "CallExpression")) {
61339
- if (isGlobalHistoryLocationMutation(child, context.scopes)) addToSetIndex(index.historyMutationsByOwner, owner, child);
61340
- if (isNodeOfType(stripParenExpression(child.callee), "Identifier")) index.identifierCalls.add(child);
61341
- const calledFunction = resolveExactLocalOrReactCallbackFunction(child.callee, context.scopes);
61342
- if (isFunctionLike$1(calledFunction)) {
61343
- addToSetIndex(index.callSitesByFunction, calledFunction, child);
61344
- index.calledFunctionByExpression.set(child, calledFunction);
61345
- }
61346
- if (context.cfg.enclosingFunction(child) === componentFunction && isReactApiCall(child, EFFECT_HOOK_NAMES$1, context.scopes, {
61347
- allowGlobalReactNamespace: true,
61348
- allowUnboundBareCalls: true,
61349
- resolveNamedAliases: true
61350
- })) {
61351
- const effectCallback = getEffectCallback(child, context.scopes);
61352
- if (isFunctionLike$1(effectCallback)) index.effectCallbacks.add(effectCallback);
61353
- }
61354
- const listenerOperation = getLocationListenerOperation(child, context.scopes);
61355
- if (listenerOperation?.operation === "add") index.listenerRegistrations.push(listenerOperation.registration);
61356
- else if (listenerOperation) index.listenerRemovals.push(listenerOperation.registration);
61357
- }
61358
- });
61359
- return index;
61360
- };
61361
- const isDescendantWithoutFunctionBoundary = (descendant, ancestor) => {
61362
- let current = descendant;
61363
- while (current && current !== ancestor) {
61364
- if (current !== descendant && isFunctionLike$1(current)) return false;
61365
- current = current.parent;
61366
- }
61367
- return current === ancestor;
61368
- };
61369
- const areInMutuallyExclusiveConditionalBranches = (firstNode, secondNode) => {
61370
- const firstBranches = /* @__PURE__ */ new Map();
61371
- let current = firstNode;
61372
- while (current?.parent) {
61373
- const parent = current.parent;
61374
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current)) firstBranches.set(parent, current);
61375
- if (current !== firstNode && isFunctionLike$1(current)) break;
61376
- current = parent;
61377
- }
61378
- current = secondNode;
61379
- while (current?.parent) {
61380
- const parent = current.parent;
61381
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === current || parent.alternate === current)) {
61382
- const firstBranch = firstBranches.get(parent);
61383
- if (firstBranch && firstBranch !== current) return true;
61384
- }
61385
- if (current !== secondNode && isFunctionLike$1(current)) break;
61386
- current = parent;
61387
- }
61388
- return false;
61389
- };
61390
- const collectStableBooleanGuardConstraints = (node, functionBoundary, scopes) => {
61391
- const constraintsBySymbolId = /* @__PURE__ */ new Map();
61392
- const recordConstraint = (test, requiredTestValue) => {
61393
- let expression = stripParenExpression(test);
61394
- let requiredValue = requiredTestValue;
61395
- while (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") {
61396
- requiredValue = !requiredValue;
61397
- expression = stripParenExpression(expression.argument);
61398
- }
61399
- if (!isNodeOfType(expression, "Identifier")) return;
61400
- const symbol = scopes.symbolFor(expression);
61401
- if (!symbol || symbol.references.some((reference) => reference.flag !== "read")) return;
61402
- const existingConstraint = constraintsBySymbolId.get(symbol.id);
61403
- constraintsBySymbolId.set(symbol.id, existingConstraint === void 0 || existingConstraint === requiredValue ? requiredValue : null);
61404
- };
61405
- let current = node;
61406
- while (current?.parent && current !== functionBoundary) {
61407
- const parent = current.parent;
61408
- if (isNodeOfType(parent, "IfStatement")) {
61409
- if (parent.consequent === current) recordConstraint(parent.test, true);
61410
- if (parent.alternate === current) recordConstraint(parent.test, false);
61411
- } else if (isNodeOfType(parent, "ConditionalExpression")) {
61412
- if (parent.consequent === current) recordConstraint(parent.test, true);
61413
- if (parent.alternate === current) recordConstraint(parent.test, false);
61414
- } else if (isNodeOfType(parent, "LogicalExpression") && parent.right === current) {
61415
- if (parent.operator === "&&") recordConstraint(parent.left, true);
61416
- if (parent.operator === "||") recordConstraint(parent.left, false);
61417
- }
61418
- if (current !== node && isFunctionLike$1(current)) break;
61419
- current = parent;
61420
- }
61421
- return constraintsBySymbolId;
61422
- };
61423
- const haveContradictoryStableBooleanGuards = (firstNode, secondNode, functionBoundary, scopes) => {
61424
- const firstConstraints = collectStableBooleanGuardConstraints(firstNode, functionBoundary, scopes);
61425
- const secondConstraints = collectStableBooleanGuardConstraints(secondNode, functionBoundary, scopes);
61426
- for (const requiredValue of firstConstraints.values()) if (requiredValue === null) return true;
61427
- for (const requiredValue of secondConstraints.values()) if (requiredValue === null) return true;
61428
- for (const [symbolId, firstRequiredValue] of firstConstraints) {
61429
- const secondRequiredValue = secondConstraints.get(symbolId);
61430
- if (firstRequiredValue !== null && secondRequiredValue !== void 0 && secondRequiredValue !== null && firstRequiredValue !== secondRequiredValue) return true;
61431
- }
61432
- return false;
61433
- };
61434
- const isInsideIntrinsicReactEventHandlerAttribute = (node, functionBoundary) => {
61435
- let current = node;
61436
- while (current && current !== functionBoundary) {
61437
- if (isEventHandlerAttribute(current) && isJsxAttributeOnIntrinsicHtmlElement(current)) return true;
61438
- current = current.parent;
61439
- }
61440
- return false;
61441
- };
61442
- const getTransparentExpressionBindingIdentifier = (expression) => {
61443
- const expressionRoot = findTransparentExpressionRoot(expression);
61444
- const parent = expressionRoot.parent;
61445
- if (isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier")) return parent.id;
61446
- if (isNodeOfType(parent, "AssignmentExpression") && parent.right === expressionRoot && isNodeOfType(parent.left, "Identifier")) return parent.left;
61447
- return null;
61448
- };
61449
- const getIntrinsicReactEventHandlerBindingIdentifier = (functionNode, index) => {
61450
- if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
61451
- const functionExpressionRoot = findTransparentExpressionRoot(functionNode);
61452
- const directTransparentBindingIdentifier = getTransparentExpressionBindingIdentifier(functionExpressionRoot);
61453
- if (directTransparentBindingIdentifier) return directTransparentBindingIdentifier;
61454
- const callbackCall = functionExpressionRoot.parent;
61455
- if (!isNodeOfType(callbackCall, "CallExpression") || callbackCall.arguments?.[0] !== functionExpressionRoot || !isReactApiCall(callbackCall, "useCallback", index.context.scopes, { resolveNamedAliases: true })) return null;
61456
- return getTransparentExpressionBindingIdentifier(callbackCall);
61457
- };
61458
- const isExclusiveIntrinsicReactEventHandler = (functionNode, index) => {
61459
- const bindingIdentifier = getIntrinsicReactEventHandlerBindingIdentifier(functionNode, index);
61460
- if (!bindingIdentifier) return isInsideIntrinsicReactEventHandlerAttribute(functionNode, index.componentFunction);
61461
- const bindingSymbol = index.context.scopes.symbolFor(bindingIdentifier);
61462
- return Boolean(bindingSymbol && bindingSymbol.references.length > 0 && bindingSymbol.references.every((reference) => isInsideIntrinsicReactEventHandlerAttribute(reference.identifier, index.componentFunction)));
61463
- };
61464
- const canNodeReachNode = (sourceNode, targetNode, index) => {
61465
- const { context } = index;
61466
- if (!isNodeReachableWithinFunction(sourceNode, context)) return false;
61467
- if (!isNodeReachableWithinFunction(targetNode, context)) return false;
61468
- const sourceOwner = context.cfg.enclosingFunction(sourceNode);
61469
- const targetOwner = context.cfg.enclosingFunction(targetNode);
61470
- if (!sourceOwner || sourceOwner !== targetOwner) return false;
61471
- if (areInMutuallyExclusiveConditionalBranches(sourceNode, targetNode)) return false;
61472
- if (haveContradictoryStableBooleanGuards(sourceNode, targetNode, sourceOwner, context.scopes)) return false;
61473
- if (isDescendantWithoutFunctionBoundary(targetNode, sourceNode)) return false;
61474
- if (isDescendantWithoutFunctionBoundary(sourceNode, targetNode)) return true;
61475
- const functionCfg = context.cfg.cfgFor(sourceOwner);
61476
- const sourceBlock = functionCfg?.blockOf(sourceNode);
61477
- const targetBlock = functionCfg?.blockOf(targetNode);
61478
- if (!functionCfg || !sourceBlock || !targetBlock) return false;
61479
- if (sourceBlock === targetBlock) {
61480
- const sourceStart = getRangeStart(sourceNode);
61481
- const targetStart = getRangeStart(targetNode);
61482
- return sourceStart !== null && targetStart !== null && sourceStart < targetStart;
61483
- }
61484
- const visitedBlocks = new Set([sourceBlock]);
61485
- const pendingBlocks = sourceBlock.successors.filter((edge) => edge.kind !== "throw").map((edge) => edge.to);
61486
- while (pendingBlocks.length > 0) {
61487
- const block = pendingBlocks.pop();
61488
- if (!block || visitedBlocks.has(block)) continue;
61489
- if (block === targetBlock) return true;
61490
- visitedBlocks.add(block);
61491
- for (const edge of block.successors) if (edge.kind !== "throw") pendingBlocks.push(edge.to);
61492
- }
61493
- return false;
61494
- };
61495
- const getInlineIntrinsicHandlerJsxElement = (functionNode) => {
61496
- let currentNode = functionNode;
61497
- while (currentNode) {
61498
- if (isNodeOfType(currentNode, "JSXAttribute") && isEventHandlerAttribute(currentNode) && isJsxAttributeOnIntrinsicHtmlElement(currentNode)) {
61499
- const openingElement = currentNode.parent;
61500
- return isNodeOfType(openingElement?.parent, "JSXElement") ? openingElement.parent : null;
61501
- }
61502
- if (currentNode !== functionNode && isFunctionLike$1(currentNode)) return null;
61503
- currentNode = currentNode.parent;
61504
- }
61505
- return null;
61506
- };
61507
- const isProvenNonEscapingVoidRead = (identifier) => {
61508
- const expressionRoot = findTransparentExpressionRoot(identifier);
61509
- return isNodeOfType(expressionRoot.parent, "UnaryExpression") && expressionRoot.parent.operator === "void" && expressionRoot.parent.argument === expressionRoot;
61510
- };
61511
- const getDirectReadonlyValueAliasSymbol = (referenceIdentifier, sourceSymbol, index) => {
61512
- const referenceRoot = findTransparentExpressionRoot(referenceIdentifier);
61513
- const parent = referenceRoot.parent;
61514
- if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier")) {
61515
- const aliasSymbol = index.context.scopes.symbolFor(parent.id);
61516
- const aliasInitializer = aliasSymbol ? getDirectConstInitializer(aliasSymbol) : null;
61517
- const unwrappedInitializer = aliasInitializer ? stripParenExpression(aliasInitializer) : null;
61518
- return aliasSymbol && isNodeOfType(unwrappedInitializer, "Identifier") && index.context.scopes.symbolFor(unwrappedInitializer) === sourceSymbol ? aliasSymbol : null;
61519
- }
61520
- if (!isNodeOfType(parent, "CallExpression") || parent.arguments?.[0] !== referenceRoot || !isReactApiCall(parent, "useCallback", index.context.scopes, { resolveNamedAliases: true })) return null;
61521
- const callbackResultRoot = findTransparentExpressionRoot(parent);
61522
- const resultParent = callbackResultRoot.parent;
61523
- if (!isNodeOfType(resultParent, "VariableDeclarator") || resultParent.init !== callbackResultRoot || !isNodeOfType(resultParent.id, "Identifier")) return null;
61524
- const callbackResultSymbol = index.context.scopes.symbolFor(resultParent.id);
61525
- return callbackResultSymbol && getDirectConstInitializer(callbackResultSymbol) === callbackResultRoot ? callbackResultSymbol : null;
61526
- };
61527
- const isKnownAggregateMutationReference = (referenceIdentifier, index) => {
61528
- const referenceRoot = findTransparentExpressionRoot(referenceIdentifier);
61529
- const parent = referenceRoot.parent;
61530
- if (isNodeOfType(parent, "MemberExpression") && parent.object === referenceRoot) {
61531
- const memberRoot = findTransparentExpressionRoot(parent);
61532
- const callExpression = memberRoot.parent;
61533
- const methodName = getStaticPropertyName(parent);
61534
- if (isNodeOfType(callExpression, "CallExpression") && callExpression.callee === memberRoot && methodName !== null && AGGREGATE_MUTATION_METHOD_NAMES.has(methodName)) return true;
61535
- }
61536
- if (!isNodeOfType(parent, "CallExpression") || parent.arguments?.[0] !== referenceRoot) return false;
61537
- const callee = stripParenExpression(parent.callee);
61538
- if (!isNodeOfType(callee, "MemberExpression")) return false;
61539
- const methodName = getStaticPropertyName(callee);
61540
- if (methodName === null) return false;
61541
- 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);
61542
- };
61543
- const collectExactReadonlyValueEscapeAnchors = (symbol, index, valueFunctionNode, options = {}, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
61544
- if (visitedSymbolIds.has(symbol.id)) return null;
61545
- if (symbol.references.some((reference) => reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier) || options.rejectKnownMutations && isKnownAggregateMutationReference(reference.identifier, index))) return null;
61546
- const nextVisitedSymbolIds = new Set(visitedSymbolIds);
61547
- nextVisitedSymbolIds.add(symbol.id);
61548
- const escapeAnchors = [];
61549
- for (const reference of symbol.references) {
61550
- if (valueFunctionNode && index.context.cfg.enclosingFunction(reference.identifier) === valueFunctionNode || isProvenNonEscapingVoidRead(reference.identifier)) continue;
61551
- const aliasSymbol = getDirectReadonlyValueAliasSymbol(reference.identifier, symbol, index);
61552
- if (!aliasSymbol) {
61553
- escapeAnchors.push(reference.identifier);
61554
- continue;
61555
- }
61556
- const aliasEscapeAnchors = collectExactReadonlyValueEscapeAnchors(aliasSymbol, index, valueFunctionNode, options, nextVisitedSymbolIds);
61557
- if (!aliasEscapeAnchors) return null;
61558
- escapeAnchors.push(...aliasEscapeAnchors);
61559
- }
61560
- return escapeAnchors.length > 0 ? escapeAnchors : null;
61561
- };
61562
- const findReadonlyJsxAggregateRoot = (jsxElement) => {
61563
- let aggregateRoot = findTransparentExpressionRoot(jsxElement);
61564
- while (aggregateRoot.parent) {
61565
- const parent = aggregateRoot.parent;
61566
- if ((isNodeOfType(parent, "JSXElement") || isNodeOfType(parent, "JSXFragment")) && parent.children?.some((child) => child === aggregateRoot)) {
61567
- aggregateRoot = parent;
61568
- continue;
61569
- }
61570
- if (isNodeOfType(parent, "ArrayExpression") && parent.elements?.some((element) => element === aggregateRoot)) {
61571
- aggregateRoot = findTransparentExpressionRoot(parent);
61572
- continue;
61573
- }
61574
- if (isNodeOfType(parent, "Property") && parent.value === aggregateRoot && parent.kind === "init" && !parent.computed && isNodeOfType(parent.parent, "ObjectExpression")) {
61575
- aggregateRoot = findTransparentExpressionRoot(parent.parent);
61576
- continue;
61577
- }
61578
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === aggregateRoot || parent.alternate === aggregateRoot)) {
61579
- aggregateRoot = findTransparentExpressionRoot(parent);
61580
- continue;
61581
- }
61582
- if (isNodeOfType(parent, "LogicalExpression") && (parent.right === aggregateRoot || parent.left === aggregateRoot && parent.operator !== "&&")) {
61583
- aggregateRoot = findTransparentExpressionRoot(parent);
61584
- continue;
61585
- }
61586
- if (isNodeOfType(parent, "SequenceExpression") && getFinalSequenceExpressionValue(parent) === getFinalSequenceExpressionValue(aggregateRoot)) {
61587
- aggregateRoot = findTransparentExpressionRoot(parent);
61588
- continue;
61589
- }
61590
- break;
61591
- }
61592
- return aggregateRoot;
61593
- };
61594
- const getReadonlyJsxValueEscapeAnchors = (jsxElement, index) => {
61595
- const jsxValueRoot = findReadonlyJsxAggregateRoot(jsxElement);
61596
- const parent = jsxValueRoot.parent;
61597
- if (!isNodeOfType(parent, "VariableDeclarator") || parent.init !== jsxValueRoot) return [jsxValueRoot];
61598
- if (!isNodeOfType(parent.id, "Identifier")) return null;
61599
- const bindingSymbol = index.context.scopes.symbolFor(parent.id);
61600
- if (!bindingSymbol || getDirectConstInitializer(bindingSymbol) !== jsxValueRoot) return null;
61601
- return collectExactReadonlyValueEscapeAnchors(bindingSymbol, index, null, { rejectKnownMutations: true });
61602
- };
61603
- const getExclusiveIntrinsicReactEventHandlerAnchors = (functionNode, index) => {
61604
- if (!isExclusiveIntrinsicReactEventHandler(functionNode, index)) return null;
61605
- const bindingIdentifier = getIntrinsicReactEventHandlerBindingIdentifier(functionNode, index);
61606
- if (!bindingIdentifier) {
61607
- const jsxElement = getInlineIntrinsicHandlerJsxElement(functionNode);
61608
- return jsxElement ? getReadonlyJsxValueEscapeAnchors(jsxElement, index) : null;
61609
- }
61610
- return index.context.scopes.symbolFor(bindingIdentifier)?.references.map((reference) => reference.identifier) ?? null;
61611
- };
61612
- const getReadonlyFunctionEscapeAnchors = (functionNode, index) => {
61613
- const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
61614
- if (!bindingIdentifier) return null;
61615
- const bindingSymbol = index.context.scopes.symbolFor(bindingIdentifier);
61616
- if (!bindingSymbol || resolveExactLocalFunction(bindingIdentifier, index.context.scopes) !== functionNode) return null;
61617
- return collectExactReadonlyValueEscapeAnchors(bindingSymbol, index, functionNode);
61618
- };
61619
- const isAliasInitializedBeforeExecution = (aliasDeclaration, executionNode, index, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
61620
- if (!isNodeReachableWithinFunction(aliasDeclaration, index.context)) return false;
61621
- const aliasOwner = index.context.cfg.enclosingFunction(aliasDeclaration);
61622
- const executionOwner = index.context.cfg.enclosingFunction(executionNode);
61623
- if (!aliasOwner || !executionOwner) return false;
61624
- if (aliasOwner === executionOwner) return canNodeReachNode(aliasDeclaration, executionNode, index);
61625
- if (visitedFunctionNodes.has(executionOwner)) return false;
61626
- const nextVisitedFunctionNodes = new Set(visitedFunctionNodes);
61627
- nextVisitedFunctionNodes.add(executionOwner);
61628
- if (aliasOwner === index.componentFunction && (index.effectCallbacks.has(executionOwner) || index.mountedListenerFunctions.has(executionOwner))) return doNodesCoverEveryPathFromFunctionEntry(index.componentFunction, [aliasDeclaration], index.context, { ignoreThrowEdges: true });
61629
- const invocations = new Set([...index.callSitesByFunction.get(executionOwner) ?? [], ...index.synchronousInvocationsByFunction.get(executionOwner) ?? []]);
61630
- if (invocations.size > 0) return [...invocations].every((invocation) => isAliasInitializedBeforeExecution(aliasDeclaration, invocation, index, nextVisitedFunctionNodes));
61631
- const deferredExecutionAnchors = getExclusiveIntrinsicReactEventHandlerAnchors(executionOwner, index);
61632
- if (deferredExecutionAnchors) return deferredExecutionAnchors.every((anchor) => isAliasInitializedBeforeExecution(aliasDeclaration, anchor, index, nextVisitedFunctionNodes));
61633
- const functionEscapeAnchors = getReadonlyFunctionEscapeAnchors(executionOwner, index);
61634
- return Boolean(functionEscapeAnchors?.every((anchor) => isAliasInitializedBeforeExecution(aliasDeclaration, anchor, index, nextVisitedFunctionNodes)));
61635
- };
61636
- const resolveExactReadonlyCalleeSymbolId = (callExpression, index) => {
61637
- if (!isNodeOfType(callExpression, "CallExpression")) return null;
61638
- const callee = stripParenExpression(callExpression.callee);
61639
- if (!isNodeOfType(callee, "Identifier")) return null;
61640
- const visitedSymbolIds = /* @__PURE__ */ new Set();
61641
- let symbol = index.context.scopes.symbolFor(callee);
61642
- while (symbol) {
61643
- if (visitedSymbolIds.has(symbol.id)) return null;
61644
- visitedSymbolIds.add(symbol.id);
61645
- const initializer = getDirectConstInitializer(symbol);
61646
- if (!initializer) return symbol.id;
61647
- if (symbol.references.some((reference) => reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier)) || !isAliasInitializedBeforeExecution(symbol.declarationNode, callExpression, index)) return null;
61648
- const unwrappedInitializer = stripParenExpression(initializer);
61649
- if (!isNodeOfType(unwrappedInitializer, "Identifier")) return symbol.id;
61650
- const initializerSymbol = index.context.scopes.symbolFor(unwrappedInitializer);
61651
- if (!initializerSymbol || !isAliasInitializedBeforeExecution(initializerSymbol.declarationNode, symbol.declarationNode, index)) return null;
61652
- symbol = initializerSymbol;
61653
- }
61654
- return null;
61655
- };
61656
- const collectExactReadonlyCalleeCalls = (index) => {
61657
- for (const callExpression of index.identifierCalls) {
61658
- const calleeSymbolId = resolveExactReadonlyCalleeSymbolId(callExpression, index);
61659
- if (calleeSymbolId !== null) addToSetIndex(index.callsByCalleeSymbolId, calleeSymbolId, callExpression);
61660
- }
61661
- };
61662
- const canNodeReachNormalFunctionExit = (node, functionNode, index) => {
61663
- const functionCfg = index.context.cfg.cfgFor(functionNode);
61664
- const sourceBlock = functionCfg?.blockOf(node);
61665
- if (!functionCfg || !sourceBlock) return false;
61666
- const visitedBlocks = /* @__PURE__ */ new Set();
61667
- const pendingBlocks = [sourceBlock];
61668
- while (pendingBlocks.length > 0) {
61669
- const block = pendingBlocks.pop();
61670
- if (!block || visitedBlocks.has(block)) continue;
61671
- visitedBlocks.add(block);
61672
- for (const edge of block.successors) {
61673
- if (edge.kind === "throw") continue;
61674
- if (edge.to === functionCfg.exit) return true;
61675
- pendingBlocks.push(edge.to);
61676
- }
61677
- }
61678
- return false;
61679
- };
61680
- const canExecuteBeforeAsyncSuspension = (node, functionNode, index) => {
61681
- if (!isFunctionLike$1(functionNode) || !functionNode.async) return isNodeReachableWithinFunction(node, index.context);
61682
- const functionCfg = index.context.cfg.cfgFor(functionNode);
61683
- const targetBlock = functionCfg?.blockOf(node);
61684
- if (!functionCfg || !targetBlock) return false;
61685
- const awaitsByBlock = /* @__PURE__ */ new Map();
61686
- for (const awaitExpression of index.awaitExpressionsByOwner.get(functionNode) ?? []) {
61687
- const awaitBlock = functionCfg.blockOf(awaitExpression);
61688
- if (!awaitBlock) continue;
61689
- const blockAwaits = awaitsByBlock.get(awaitBlock) ?? [];
61690
- blockAwaits.push(awaitExpression);
61691
- awaitsByBlock.set(awaitBlock, blockAwaits);
61692
- }
61693
- const visitedBlocks = /* @__PURE__ */ new Set();
61694
- const pendingBlocks = [functionCfg.entry];
61695
- const targetStart = getRangeStart(node);
61696
- while (pendingBlocks.length > 0) {
61697
- const block = pendingBlocks.pop();
61698
- if (!block || visitedBlocks.has(block)) continue;
61699
- visitedBlocks.add(block);
61700
- const blockAwaits = awaitsByBlock.get(block) ?? [];
61701
- if (block === targetBlock) return !blockAwaits.some((awaitExpression) => {
61702
- if (isNodeOfType(awaitExpression, "AwaitExpression") && isDescendantWithoutFunctionBoundary(node, awaitExpression.argument)) return false;
61703
- if (isDescendantWithoutFunctionBoundary(awaitExpression, node)) return true;
61704
- const awaitStart = getRangeStart(awaitExpression);
61705
- return awaitStart !== null && targetStart !== null && awaitStart < targetStart;
61706
- });
61707
- if (blockAwaits.length > 0) continue;
61708
- for (const edge of block.successors) if (edge.kind !== "throw") pendingBlocks.push(edge.to);
61709
- }
61710
- return false;
61711
- };
61712
- 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);
61713
- const functionMaySynchronouslyMutateLocation = (functionNode, index, visitingFunctions, cycleAffectedFunctions) => {
61714
- const cachedResult = index.synchronousMutationResultByFunction.get(functionNode);
61715
- if (cachedResult !== void 0) return cachedResult;
61716
- if (!isFunctionLike$1(functionNode) || functionNode.generator) return false;
61717
- if (visitingFunctions.has(functionNode)) {
61718
- let didReachCycleEntry = false;
61719
- for (const visitingFunction of visitingFunctions) {
61720
- if (visitingFunction === functionNode) didReachCycleEntry = true;
61721
- if (didReachCycleEntry) cycleAffectedFunctions.add(visitingFunction);
61722
- }
61723
- return false;
61724
- }
61725
- visitingFunctions.add(functionNode);
61726
- const doesMutateSynchronously = [...collectLocationMutationExecutions(functionNode, index, visitingFunctions, cycleAffectedFunctions)].some((mutationExecution) => canExecuteBeforeAsyncSuspension(mutationExecution, functionNode, index) && canNodeReachNormalFunctionExit(mutationExecution, functionNode, index));
61727
- visitingFunctions.delete(functionNode);
61728
- if (doesMutateSynchronously || !cycleAffectedFunctions.has(functionNode)) index.synchronousMutationResultByFunction.set(functionNode, doesMutateSynchronously);
61729
- return doesMutateSynchronously;
61730
- };
61731
- const collectLocationMutationExecutions = (functionNode, index, visitingFunctions = /* @__PURE__ */ new Set(), cycleAffectedFunctions = /* @__PURE__ */ new Set()) => {
61732
- const cachedExecutions = index.mutationExecutionsByOwner.get(functionNode);
61733
- if (cachedExecutions) return cachedExecutions;
61734
- const mutationExecutions = new Set(index.historyMutationsByOwner.get(functionNode) ?? []);
61735
- for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
61736
- const calledFunction = index.calledFunctionByExpression.get(expression);
61737
- if (calledFunction && functionMaySynchronouslyMutateLocation(calledFunction, index, visitingFunctions, cycleAffectedFunctions)) mutationExecutions.add(expression);
61738
- for (const callbackFunction of index.synchronousCallbacksByExpression.get(expression) ?? []) if (functionMaySynchronouslyMutateLocation(callbackFunction, index, visitingFunctions, cycleAffectedFunctions)) mutationExecutions.add(expression);
61739
- }
61740
- if (!cycleAffectedFunctions.has(functionNode)) index.mutationExecutionsByOwner.set(functionNode, mutationExecutions);
61741
- return mutationExecutions;
61742
- };
61743
- const isDefinitelyMatchingLocationListenerRemoval = (registration, removal) => registration.eventName === removal.eventName && registration.listenerFunction === removal.listenerFunction && registration.capture !== null && removal.capture !== null && registration.capture === removal.capture;
61744
- const functionMustSynchronouslyRemoveLocationListener = (functionNode, registration, index, visitingFunctions) => {
61745
- if (!isFunctionLike$1(functionNode) || functionNode.generator) return false;
61746
- if (visitingFunctions.has(functionNode)) return false;
61747
- const nextVisitingFunctions = new Set(visitingFunctions);
61748
- nextVisitingFunctions.add(functionNode);
61749
- const removalExecutions = [];
61750
- for (const removal of index.listenerRemovals) {
61751
- if (!isDefinitelyMatchingLocationListenerRemoval(registration, removal)) continue;
61752
- if (index.context.cfg.enclosingFunction(removal.callExpression) !== functionNode) continue;
61753
- if (canExecuteBeforeAsyncSuspension(removal.callExpression, functionNode, index)) removalExecutions.push(removal.callExpression);
61754
- }
61755
- for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
61756
- const calledFunction = index.calledFunctionByExpression.get(expression);
61757
- if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index) && functionMustSynchronouslyRemoveLocationListener(calledFunction, registration, index, nextVisitingFunctions)) removalExecutions.push(expression);
61758
- }
61759
- return doNodesCoverEveryPathFromFunctionEntry(functionNode, removalExecutions, index.context);
61760
- };
61761
- const collectSynchronousLocationListenerRemovalExecutions = (functionNode, registration, index) => {
61762
- const removalExecutions = /* @__PURE__ */ new Set();
61763
- 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);
61764
- for (const expression of index.expressionsByOwner.get(functionNode) ?? []) {
61765
- const calledFunction = index.calledFunctionByExpression.get(expression);
61766
- if (calledFunction && canExecuteBeforeAsyncSuspension(expression, functionNode, index) && functionMustSynchronouslyRemoveLocationListener(calledFunction, registration, index, /* @__PURE__ */ new Set())) removalExecutions.add(expression);
61767
- }
61768
- return removalExecutions;
61769
- };
61770
- const collectImpliedExpressionExecutionBoundaries = (node, owner) => {
61771
- const expressionBoundaries = [];
61772
- let currentChild = node;
61773
- let currentParent = currentChild.parent ?? null;
61774
- while (currentParent && currentParent !== owner) {
61775
- if (isNodeOfType(currentParent, "ConditionalExpression") && currentParent.test === currentChild) {
61776
- const staticTestValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.test));
61777
- if (staticTestValue !== null) expressionBoundaries.push(staticTestValue ? currentParent.consequent : currentParent.alternate);
61778
- }
61779
- if (isNodeOfType(currentParent, "LogicalExpression") && currentParent.left === currentChild) {
61780
- const staticLeftValue = readStaticBoolean(getFinalSequenceExpressionValue(currentParent.left));
61781
- if (currentParent.operator === "&&" && staticLeftValue === true || currentParent.operator === "||" && staticLeftValue === false) expressionBoundaries.push(currentParent.right);
61782
- }
61783
- 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);
61784
- currentChild = currentParent;
61785
- currentParent = currentChild.parent ?? null;
61786
- }
61787
- return expressionBoundaries;
61788
- };
61789
- const canExecutionReachFunctionExitWithoutListenerRemoval = (executionNode, registration, index) => {
61790
- if (!isNodeReachableWithinFunction(executionNode, index.context)) return false;
61791
- const owner = index.context.cfg.enclosingFunction(executionNode);
61792
- if (!owner) return false;
61793
- const functionCfg = index.context.cfg.cfgFor(owner);
61794
- const sourceBlock = functionCfg?.blockOf(executionNode);
61795
- if (!functionCfg || !sourceBlock) return false;
61796
- const expressionBoundaries = collectImpliedExpressionExecutionBoundaries(executionNode, owner);
61797
- const matchingRemovalsByBlock = /* @__PURE__ */ new Map();
61798
- for (const removalExecution of collectSynchronousLocationListenerRemovalExecutions(owner, registration, index)) {
61799
- const removalBlock = functionCfg.blockOf(removalExecution);
61800
- if (!removalBlock) continue;
61801
- const blockRemovals = matchingRemovalsByBlock.get(removalBlock) ?? [];
61802
- blockRemovals.push(removalExecution);
61803
- matchingRemovalsByBlock.set(removalBlock, blockRemovals);
61804
- }
61805
- const sourceStart = getRangeStart(executionNode);
61806
- const hasRemovalAfterExecution = (block) => {
61807
- const removals = (matchingRemovalsByBlock.get(block) ?? []).filter((removal) => {
61808
- if (block !== sourceBlock) return true;
61809
- const removalStart = getRangeStart(removal);
61810
- return sourceStart !== null && removalStart !== null && sourceStart < removalStart;
61811
- });
61812
- if (collectExpressionPathCoverageNodes(owner, removals, index.context).size > 0) return true;
61813
- if (block !== sourceBlock) return false;
61814
- return expressionBoundaries.some((expressionBoundary) => collectExpressionPathCoverageNodes(owner, removals, index.context, expressionBoundary).size > 0);
61815
- };
61816
- const visitedBlocks = /* @__PURE__ */ new Set();
61817
- const pendingBlocks = [sourceBlock];
61818
- while (pendingBlocks.length > 0) {
61819
- const block = pendingBlocks.pop();
61820
- if (!block || visitedBlocks.has(block)) continue;
61821
- visitedBlocks.add(block);
61822
- if (hasRemovalAfterExecution(block)) continue;
61823
- for (const edge of block.successors) {
61824
- if (edge.kind === "throw") continue;
61825
- if (edge.to === functionCfg.exit) return true;
61826
- pendingBlocks.push(edge.to);
61827
- }
61828
- }
61829
- return false;
61830
- };
61831
- const isListenerActiveAtMountedExit = (executionNode, registration, index, visitedExecutions = /* @__PURE__ */ new Set()) => {
61832
- if (visitedExecutions.has(executionNode)) return false;
61833
- if (!canExecutionReachFunctionExitWithoutListenerRemoval(executionNode, registration, index)) return false;
61834
- const owner = index.context.cfg.enclosingFunction(executionNode);
61835
- if (!owner) return false;
61836
- if (owner === index.componentFunction || index.effectCallbacks.has(owner)) return true;
61837
- const nextVisitedExecutions = new Set(visitedExecutions);
61838
- nextVisitedExecutions.add(executionNode);
61839
- for (const callSite of index.callSitesByFunction.get(owner) ?? []) if (isListenerActiveAtMountedExit(callSite, registration, index, nextVisitedExecutions)) return true;
61840
- for (const invocation of index.synchronousInvocationsByFunction.get(owner) ?? []) if (isListenerActiveAtMountedExit(invocation, registration, index, nextVisitedExecutions)) return true;
61841
- return false;
61842
- };
61843
- const collectMountedListenerFunctions = (index) => {
61844
- for (const registration of index.listenerRegistrations) if (isListenerActiveAtMountedExit(registration.callExpression, registration, index)) index.mountedListenerFunctions.add(registration.listenerFunction);
61845
- };
61846
- const setterArgumentMutatesLocation = (setterCall, index) => {
61847
- if (!isNodeOfType(setterCall, "CallExpression")) return false;
61848
- return (setterCall.arguments ?? []).some((argument) => {
61849
- if (isNodeOfType(argument, "SpreadElement")) return false;
61850
- const updaterFunction = resolveExactLocalFunction(argument, index.context.scopes);
61851
- return Boolean(isFunctionLike$1(updaterFunction) && functionMaySynchronouslyMutateLocation(updaterFunction, index, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set()));
61852
- });
61853
- };
61854
- const executionAnchorInvalidatesLocationSnapshot = (executionAnchor, index, visitedExecutions = /* @__PURE__ */ new Set()) => {
61855
- if (visitedExecutions.has(executionAnchor)) return false;
61856
- if (!isNodeReachableWithinFunction(executionAnchor, index.context)) return false;
61857
- const owner = index.context.cfg.enclosingFunction(executionAnchor);
61858
- if (!owner) return false;
61859
- if (index.mountedListenerFunctions.has(owner)) return true;
61860
- if (setterArgumentMutatesLocation(executionAnchor, index) || [...collectLocationMutationExecutions(owner, index)].some((mutationExecution) => canNodeReachNode(mutationExecution, executionAnchor, index) || canReactBatchMutationAfterExecution(executionAnchor, mutationExecution, owner, index))) return true;
61861
- const nextVisitedExecutions = new Set(visitedExecutions);
61862
- nextVisitedExecutions.add(executionAnchor);
61863
- for (const callSite of index.callSitesByFunction.get(owner) ?? []) if (executionAnchorInvalidatesLocationSnapshot(callSite, index, nextVisitedExecutions)) return true;
61864
- for (const invocation of index.synchronousInvocationsByFunction.get(owner) ?? []) if (executionAnchorInvalidatesLocationSnapshot(invocation, index, nextVisitedExecutions)) return true;
61865
- return false;
61866
- };
61867
- const setterInvalidatesGlobalLocationSnapshot = (setterBindingIdentifier, index) => {
61868
- const setterSymbol = index.context.scopes.symbolFor(setterBindingIdentifier);
61869
- if (!setterSymbol) return false;
61870
- return [...index.callsByCalleeSymbolId.get(setterSymbol.id) ?? []].some((setterCall) => executionAnchorInvalidatesLocationSnapshot(setterCall, index));
61871
- };
61872
- const createExternalLocationInvalidationChecker = ({ componentBody, componentFunction, context, directRenderNames, renderReachableExpressions }) => {
61873
- if (!hasRenderReachableLocationSnapshotRead(componentBody, renderReachableExpressions, directRenderNames, context.scopes)) return () => false;
61874
- const locationInvalidationIndex = buildLocationInvalidationIndex(componentBody, componentFunction, context);
61875
- collectMountedListenerFunctions(locationInvalidationIndex);
61876
- collectExactReadonlyCalleeCalls(locationInvalidationIndex);
61877
- return (setterBindingIdentifier) => setterInvalidatesGlobalLocationSnapshot(setterBindingIdentifier, locationInvalidationIndex);
61878
- };
61879
- //#endregion
61880
60392
  //#region src/plugin/rules/state-and-effects/rerender-state-only-in-handlers.ts
61881
60393
  const isInsideConditionTest = (identifier, stopAt) => {
61882
60394
  let current = identifier;
@@ -61998,8 +60510,7 @@ const rerenderStateOnlyInHandlers = defineRule({
61998
60510
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
61999
60511
  const bindings = collectUseStateBindings(componentBody, context.scopes);
62000
60512
  if (bindings.length === 0) return;
62001
- const renderReachableExpressions = collectRenderReachableExpressions(componentBody);
62002
- if (renderReachableExpressions.length === 0) return;
60513
+ if (collectRenderReachableExpressions(componentBody).length === 0) return;
62003
60514
  const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
62004
60515
  const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
62005
60516
  const directRenderNames = collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames);
@@ -62013,22 +60524,12 @@ const rerenderStateOnlyInHandlers = defineRule({
62013
60524
  for (const effectInfo of effectInfos) for (const dependencyName of effectInfo.dependencyNames) if (!selfEchoValueNames.has(dependencyName)) effectConsumedNames.add(dependencyName);
62014
60525
  for (const hookArgumentName of collectCustomHookArgumentNames(componentBody, context.scopes)) effectConsumedNames.add(hookArgumentName);
62015
60526
  for (const reachableName of expandTransitiveDependencies(effectConsumedNames, dependencyGraph)) renderReachableNames.add(reachableName);
62016
- const componentFunction = context.cfg.enclosingFunction(componentBody);
62017
- const doesSetterInvalidateExternalLocation = isFunctionLike$1(componentFunction) ? createExternalLocationInvalidationChecker({
62018
- componentBody,
62019
- componentFunction,
62020
- context,
62021
- directRenderNames,
62022
- renderReachableExpressions
62023
- }) : () => false;
62024
60527
  const calledSetterNames = /* @__PURE__ */ new Set();
62025
60528
  walkAst(componentBody, (child) => {
62026
60529
  if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && setterNames.has(child.callee.name)) calledSetterNames.add(child.callee.name);
62027
60530
  });
62028
60531
  for (const binding of bindings) {
62029
60532
  if (renderReachableNames.has(binding.valueName)) continue;
62030
- const setterBindingIdentifier = isNodeOfType(binding.declarator.id, "ArrayPattern") ? binding.declarator.id.elements?.[1] : null;
62031
- if (setterBindingIdentifier && doesSetterInvalidateExternalLocation(setterBindingIdentifier)) continue;
62032
60533
  if (binding.valueName === "_" || binding.valueName.startsWith("_")) continue;
62033
60534
  const setterSuffix = binding.setterName.slice(3);
62034
60535
  if (/^(TriggerRender|ForceUpdate|Rerender|ForceRender|Tick|Bump|BumpVersion|InvalidateRender|Refresh|Repaint)$/i.test(setterSuffix)) continue;
@@ -68469,7 +66970,6 @@ const countOwnScopeHookCalls = (functionNode, scopes, settings, countedFunctionN
68469
66970
  };
68470
66971
  const MIN_HOOK_CALLS_FOR_RENDER_SCOPE = 2;
68471
66972
  const RENDER_SCOPE_FACTORY_NAME_PATTERN = /^_?(?:init|create|make|build)(?:[A-Z0-9_]|$)/;
68472
- const HOOK_NAMESPACE_NAME_PATTERN = /Hooks?$/;
68473
66973
  const isLocalNonHookFunctionCallee = (call, scopes, settings, countedFunctionNodes) => {
68474
66974
  const callee = call.callee;
68475
66975
  if (!isNodeOfType(callee, "Identifier")) return false;
@@ -68494,15 +66994,6 @@ const isPackageImportedNonReactHookCallee = (call) => {
68494
66994
  if (PATH_ALIAS_IMPORT_PATTERN.test(importSource)) return false;
68495
66995
  return !isReactEcosystemImportSource(importSource);
68496
66996
  };
68497
- const isDefaultImportedClassApiMemberCallee = (call) => {
68498
- const callee = call.callee;
68499
- if (!isNodeOfType(callee, "MemberExpression")) return false;
68500
- if (!isNodeOfType(callee.object, "Identifier")) return false;
68501
- if (HOOK_NAMESPACE_NAME_PATTERN.test(callee.object.name)) return false;
68502
- const importBinding = getImportBindingForName(call, callee.object.name);
68503
- if (!importBinding || importBinding.exportedName !== "default") return false;
68504
- return !REACT_RUNTIME_MODULE_SOURCES.has(importBinding.source);
68505
- };
68506
66997
  const isProjectOwnedMdxComponentsGetter = (call) => {
68507
66998
  const callee = call.callee;
68508
66999
  if (!isNodeOfType(callee, "Identifier") || callee.name !== "useMDXComponents") return false;
@@ -68652,7 +67143,6 @@ const rulesOfHooks = defineRule({
68652
67143
  return;
68653
67144
  }
68654
67145
  if (isInsideClassComponent(node)) {
68655
- if (isPackageImportedNonReactHookCallee(node) || isDefaultImportedClassApiMemberCallee(node)) return;
68656
67146
  context.report({
68657
67147
  node: node.callee,
68658
67148
  message: buildClassComponentMessage(hookName)
@@ -68785,7 +67275,7 @@ const secretInFallback = defineRule({
68785
67275
  recommendation: "Remove the literal fallback and fail closed (throw when the variable is unset). The hardcoded value is a committed secret, and the `??`/`||` default makes the app run with it in any environment that forgot to set the var.",
68786
67276
  scan: scanByPattern({
68787
67277
  shouldScan: (file) => isProductionSourcePath(file.relativePath),
68788
- pattern: /\bprocess\.env\.(?!(?:(?:(?:NEXT|EXPO|GATSBY|NUXT|REACT_APP|VITE)_)?PUBLIC_[A-Z0-9_]*(?<!_SECRET)(?<!_PRIVATE_KEY)(?<!_PASSWORD)(?<!_PASSWD)|[A-Z0-9_]*(?:PUBLISHABLE|ANON)_KEY)(?![A-Z0-9_]))[A-Z][A-Z0-9_]*(?:SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE_KEY|API_?KEY|APIKEY|ACCESS_KEY|CLIENT_SECRET|CREDENTIAL|SIGNING_KEY|ENCRYPTION_KEY|WEBHOOK_SECRET|SERVICE_ROLE)[A-Z0-9_]*(?<!_(?:NAME|HEADER|ENDPOINT|URL|URI|ID|PREFIX|SUFFIX|PARAM|PARAMS|FIELD|ISSUER|AUDIENCE|ALGORITHM|ALG|REGION|BUCKET|HOST|HOSTNAME|PORT|PATH|VERSION|SCOPE|TYPE|FORMAT|EXPIRY|TTL))\s*(?:\?\?|\|\|)\s*(["'`])(?![0-9]+["'`])(?![a-z][a-z0-9]*(?:[_-][a-z0-9]+)*[_-](?:token|secret|key|password|passwd|credential)s?["'`])(?![\w-]{0,12}0{8,}["'`])(?!test test test test test test test test test test test junk["'`])(?!(?:changeme|change[_-]?me|placeholder|your[_-]|example|sample|dummy|development|local|todo|replace[_-]?me|https?:\/\/|x{3,}|\*{3,}))[^"'`\n]{8,}\1/i,
67278
+ pattern: /\bprocess\.env\.(?!(?:(?:(?:NEXT|EXPO|GATSBY|NUXT|REACT_APP|VITE)_)?PUBLIC_[A-Z0-9_]*(?<!_SECRET)(?<!_PRIVATE_KEY)(?<!_PASSWORD)(?<!_PASSWD)|[A-Z0-9_]*(?:PUBLISHABLE|ANON)_KEY)(?![A-Z0-9_]))[A-Z][A-Z0-9_]*(?:SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE_KEY|API_?KEY|APIKEY|ACCESS_KEY|CLIENT_SECRET|CREDENTIAL|SIGNING_KEY|ENCRYPTION_KEY|WEBHOOK_SECRET|SERVICE_ROLE)[A-Z0-9_]*(?<!_(?:NAME|HEADER|ENDPOINT|URL|URI|ID|PREFIX|SUFFIX|PARAM|PARAMS|FIELD|ISSUER|AUDIENCE|ALGORITHM|ALG|REGION|BUCKET|HOST|HOSTNAME|PORT|PATH|VERSION|SCOPE|TYPE|FORMAT|EXPIRY|TTL))\s*(?:\?\?|\|\|)\s*(["'`])(?![0-9]+["'`])(?![a-z][a-z0-9]*(?:[_-][a-z0-9]+)*[_-](?:token|secret|key|password|passwd|credential)s?["'`])(?![\w-]{0,12}0{8,}["'`])(?!(?:changeme|change[_-]?me|placeholder|your[_-]|example|sample|dummy|development|local|todo|replace[_-]?me|https?:\/\/|x{3,}|\*{3,}))[^"'`\n]{8,}\1/i,
68789
67279
  message: "A secret env var has a hardcoded string fallback: the literal is a committed secret and the app fails open (uses it) when the variable is unset."
68790
67280
  })
68791
67281
  });
@@ -69055,30 +67545,7 @@ const isAuthGuardName = (calleeName) => {
69055
67545
  //#endregion
69056
67546
  //#region src/plugin/utils/is-non-privileged-server-action.ts
69057
67547
  const NON_DATA_EFFECT_FUNCTION_NAMES = new Set([...CACHE_REVALIDATION_FUNCTION_NAMES, ...NEXTJS_NAVIGATION_FUNCTIONS]);
69058
- const LOCALE_PREFERENCE_VERB_TOKENS = new Set(["set", "update"]);
69059
- const LOCALE_PREFERENCE_NOUN_TOKENS = new Set(["language", "locale"]);
69060
- const COOKIE_DELETION_METHOD_NAME = "delete";
69061
- const isCacheOrNavigationCall = (node) => {
69062
- if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier")) return false;
69063
- return NON_DATA_EFFECT_FUNCTION_NAMES.has(node.callee.name) && getImportSourceForName(node, node.callee.name) !== null;
69064
- };
69065
- const isCallerScopedLocalePreferenceCall = (node) => {
69066
- if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier") || node.arguments?.length !== 1) return false;
69067
- const nameTokens = tokenizeIdentifierWords(node.callee.name);
69068
- if (!nameTokens.some((token) => LOCALE_PREFERENCE_VERB_TOKENS.has(token))) return false;
69069
- if (!nameTokens.some((token) => LOCALE_PREFERENCE_NOUN_TOKENS.has(token))) return false;
69070
- const importSource = getImportSourceForName(node, node.callee.name)?.toLowerCase();
69071
- return Boolean(importSource?.includes("i18n") || importSource?.includes("locale"));
69072
- };
69073
- const isCallerScopedCookieCall = (node, cookieBindingNames) => {
69074
- if (!isNodeOfType(node, "CallExpression")) return false;
69075
- if (isNodeOfType(node.callee, "Identifier")) return node.callee.name === "cookies" && getImportSourceForName(node, node.callee.name) === "next/headers";
69076
- if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== COOKIE_DELETION_METHOD_NAME) return false;
69077
- const receiver = stripParenExpression(node.callee.object);
69078
- if (isNodeOfType(receiver, "Identifier")) return cookieBindingNames.has(receiver.name);
69079
- return isCookiesOrAwaitedCookiesCall(receiver) && getImportSourceForName(receiver, "cookies") === "next/headers";
69080
- };
69081
- const isKnownNonDataEffectCall = (node, cookieBindingNames) => isCacheOrNavigationCall(node) || isCallerScopedLocalePreferenceCall(node) || isCallerScopedCookieCall(node, cookieBindingNames);
67548
+ const isCacheOrNavigationCall = (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && NON_DATA_EFFECT_FUNCTION_NAMES.has(node.callee.name) && getImportSourceForName(node, node.callee.name) !== null;
69082
67549
  const unwrapExpression = (node) => {
69083
67550
  let current = node;
69084
67551
  while (current) {
@@ -69094,10 +67561,10 @@ const unwrapExpression = (node) => {
69094
67561
  }
69095
67562
  return null;
69096
67563
  };
69097
- const isDataExposingValue = (node, cookieBindingNames) => {
67564
+ const isDataExposingValue = (node) => {
69098
67565
  const value = unwrapExpression(node);
69099
67566
  if (!value) return false;
69100
- if (isKnownNonDataEffectCall(value, cookieBindingNames)) return false;
67567
+ if (isCacheOrNavigationCall(value)) return false;
69101
67568
  return !isLiteralOnlyExpression(value);
69102
67569
  };
69103
67570
  const isLiteralOnlyExpression = (node) => {
@@ -69115,23 +67582,22 @@ const getReturnedOrThrownArgument = (node) => {
69115
67582
  if (isNodeOfType(node, "ThrowStatement")) return node.argument ?? null;
69116
67583
  return null;
69117
67584
  };
69118
- const isDataExposingReturnOrThrow = (node, cookieBindingNames) => isDataExposingValue(getReturnedOrThrownArgument(node), cookieBindingNames);
69119
- const isPrivilegedEffect = (node, cookieBindingNames) => isNodeOfType(node, "CallExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete" || isDataExposingReturnOrThrow(node, cookieBindingNames);
67585
+ const isDataExposingReturnOrThrow = (node) => isDataExposingValue(getReturnedOrThrownArgument(node));
67586
+ const isPrivilegedEffect = (node) => isNodeOfType(node, "CallExpression") || isNodeOfType(node, "TaggedTemplateExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "AssignmentExpression") || isNodeOfType(node, "UpdateExpression") || isNodeOfType(node, "UnaryExpression") && node.operator === "delete" || isDataExposingReturnOrThrow(node);
69120
67587
  const isNonPrivilegedServerAction = (functionNode) => {
69121
67588
  const functionBody = functionNode.body;
69122
67589
  if (!functionBody) return false;
69123
- const cookieBindingNames = getImportSourceForName(functionBody, "cookies") === "next/headers" ? collectLocallyScopedCookieBindings(functionBody) : /* @__PURE__ */ new Set();
69124
- if (!isNodeOfType(functionBody, "BlockStatement") && isDataExposingValue(functionBody, cookieBindingNames)) return false;
67590
+ if (!isNodeOfType(functionBody, "BlockStatement") && isDataExposingValue(functionBody)) return false;
69125
67591
  let hasNonDataEffectCall = false;
69126
67592
  let hasPrivilegedEffect = false;
69127
67593
  walkAst(functionBody, (child) => {
69128
67594
  if (hasPrivilegedEffect) return false;
69129
67595
  if (child !== functionBody && isFunctionLike$1(child)) return false;
69130
- if (isKnownNonDataEffectCall(child, cookieBindingNames)) {
67596
+ if (isCacheOrNavigationCall(child)) {
69131
67597
  hasNonDataEffectCall = true;
69132
67598
  return;
69133
67599
  }
69134
- if (isPrivilegedEffect(child, cookieBindingNames)) {
67600
+ if (isPrivilegedEffect(child)) {
69135
67601
  hasPrivilegedEffect = true;
69136
67602
  return false;
69137
67603
  }
@@ -69210,34 +67676,6 @@ const getAuthScanRoots = (functionNode) => {
69210
67676
  if (isNodeOfType(bodyNode, "BlockStatement")) return (bodyNode.body ?? []).slice(0, 10);
69211
67677
  return [bodyNode];
69212
67678
  };
69213
- const COMPONENT_NAME_PATTERN = /^[A-Z]/;
69214
- const TEST_APP_SOURCE_PATH_PATTERN = /(?:^|[/\\])test[/\\](?:app|src)[/\\]/;
69215
- const containsJsxOutsideNestedFunctions = (rootNode) => {
69216
- let containsJsx = false;
69217
- walkAst(rootNode, (child) => {
69218
- if (containsJsx) return false;
69219
- if (child !== rootNode && isFunctionLike$1(child)) return false;
69220
- if (isNodeOfType(child, "JSXElement") || isNodeOfType(child, "JSXFragment")) {
69221
- containsJsx = true;
69222
- return false;
69223
- }
69224
- });
69225
- return containsJsx;
69226
- };
69227
- const isComponentLikeServerExport = (candidate) => {
69228
- if (!COMPONENT_NAME_PATTERN.test(candidate.displayName)) return false;
69229
- const functionBody = candidate.functionNode.body;
69230
- if (!isNodeOfType(functionBody, "BlockStatement")) return containsJsxOutsideNestedFunctions(functionBody);
69231
- let hasReturnedJsx = false;
69232
- walkAst(functionBody, (child) => {
69233
- if (hasReturnedJsx) return false;
69234
- if (child !== functionBody && isFunctionLike$1(child)) return false;
69235
- if (!isNodeOfType(child, "ReturnStatement") || !child.argument) return;
69236
- hasReturnedJsx = containsJsxOutsideNestedFunctions(child.argument);
69237
- return false;
69238
- });
69239
- return hasReturnedJsx;
69240
- };
69241
67679
  const CREDENTIAL_MERGE_TAIL_TOKENS = {
69242
67680
  sign: new Set([
69243
67681
  "in",
@@ -69302,7 +67740,6 @@ const isCredentialEstablishingActionName = (actionName) => {
69302
67740
  const hasPublicNameToken = (actionName) => tokenizeIdentifierWords(actionName).includes("public");
69303
67741
  const inspectServerAction = (candidate, fileHasUseServerDirective, allowedFunctionNames, context) => {
69304
67742
  if (!(fileHasUseServerDirective || hasUseServerDirective(candidate.functionNode))) return;
69305
- if (isComponentLikeServerExport(candidate)) return;
69306
67743
  if (isCredentialEstablishingActionName(candidate.displayName)) return;
69307
67744
  if (hasPublicNameToken(candidate.displayName)) return;
69308
67745
  if (containsAuthCheck(getAuthScanRoots(candidate.functionNode), allowedFunctionNames, GENERIC_AUTH_METHOD_NAMES)) return;
@@ -69340,42 +67777,18 @@ const serverAuthActions = defineRule({
69340
67777
  title: "Unauthenticated server action can be called directly",
69341
67778
  severity: "error",
69342
67779
  recommendation: "Check auth before touching data because exported server actions can be called directly by unauthenticated clients.",
69343
- create: skipNonProductionFiles((context) => {
69344
- const shouldSkipTestAppSource = Boolean(context.filename && TEST_APP_SOURCE_PATH_PATTERN.test(context.filename));
67780
+ create: (context) => {
69345
67781
  let fileHasUseServerDirective = false;
69346
- let programNode = null;
69347
- const inspectedFunctions = /* @__PURE__ */ new Set();
69348
67782
  const customAuthFunctionNames = getReactDoctorStringArraySetting(context.settings, "serverAuthFunctionNames");
69349
67783
  const allowedFunctionNames = customAuthFunctionNames.length > 0 ? new Set([...AUTH_FUNCTION_NAMES, ...customAuthFunctionNames]) : AUTH_FUNCTION_NAMES;
69350
- const inspect = (candidate) => {
69351
- if (inspectedFunctions.has(candidate.functionNode)) return;
69352
- inspectedFunctions.add(candidate.functionNode);
69353
- inspectServerAction(candidate, fileHasUseServerDirective, allowedFunctionNames, context);
69354
- };
67784
+ const inspect = (candidate) => inspectServerAction(candidate, fileHasUseServerDirective, allowedFunctionNames, context);
69355
67785
  return {
69356
- Program(currentProgramNode) {
69357
- programNode = currentProgramNode;
69358
- fileHasUseServerDirective = hasDirective(currentProgramNode, "use server");
67786
+ Program(programNode) {
67787
+ fileHasUseServerDirective = hasDirective(programNode, "use server");
69359
67788
  },
69360
67789
  ExportNamedDeclaration(node) {
69361
- if (shouldSkipTestAppSource) return;
69362
67790
  const declaration = node.declaration;
69363
- if (!declaration) {
69364
- if (!programNode || node.source || node.exportKind === "type") return;
69365
- for (const specifier of node.specifiers ?? []) {
69366
- if (!isNodeOfType(specifier, "ExportSpecifier") || specifier.exportKind === "type") continue;
69367
- const exportedName = isNodeOfType(specifier.exported, "Identifier") ? specifier.exported.name : isNodeOfType(specifier.exported, "Literal") && typeof specifier.exported.value === "string" ? specifier.exported.value : null;
69368
- if (!exportedName) continue;
69369
- const exportedValue = findExportedValue(programNode, exportedName);
69370
- if (!isAsyncFunctionLikeNode(exportedValue)) continue;
69371
- inspect({
69372
- functionNode: exportedValue,
69373
- displayName: isNodeOfType(specifier.local, "Identifier") ? specifier.local.name : exportedName,
69374
- reportNode: specifier.local ?? specifier
69375
- });
69376
- }
69377
- return;
69378
- }
67791
+ if (!declaration) return;
69379
67792
  if (isAsyncFunctionLikeNode(declaration)) {
69380
67793
  if (!isNodeOfType(declaration, "FunctionDeclaration")) return;
69381
67794
  inspect({
@@ -69388,23 +67801,11 @@ const serverAuthActions = defineRule({
69388
67801
  if (isNodeOfType(declaration, "VariableDeclaration")) for (const candidate of collectCandidatesFromVariableDeclaration(declaration)) inspect(candidate);
69389
67802
  },
69390
67803
  ExportDefaultDeclaration(node) {
69391
- if (shouldSkipTestAppSource) return;
69392
- const directCandidate = getCandidateFromDefaultDeclaration(node);
69393
- if (directCandidate) {
69394
- inspect(directCandidate);
69395
- return;
69396
- }
69397
- if (!programNode) return;
69398
- const resolvedDefaultExport = findExportedValue(programNode, "default");
69399
- const candidate = isAsyncFunctionLikeNode(resolvedDefaultExport) ? {
69400
- functionNode: resolvedDefaultExport,
69401
- displayName: isNodeOfType(node.declaration, "Identifier") ? node.declaration.name : "default",
69402
- reportNode: node.declaration ?? node
69403
- } : null;
67804
+ const candidate = getCandidateFromDefaultDeclaration(node);
69404
67805
  if (candidate) inspect(candidate);
69405
67806
  }
69406
67807
  };
69407
- })
67808
+ }
69408
67809
  });
69409
67810
  //#endregion
69410
67811
  //#region src/plugin/rules/server/server-cache-with-object-literal.ts
@@ -69484,7 +67885,6 @@ const serverDedupProps = defineRule({
69484
67885
  for (const attr of node.attributes ?? []) {
69485
67886
  if (!isNodeOfType(attr, "JSXAttribute")) continue;
69486
67887
  if (!isNodeOfType(attr.name, "JSXIdentifier")) continue;
69487
- if (attr.name.name === "key") continue;
69488
67888
  if (!isNodeOfType(attr.value, "JSXExpressionContainer")) continue;
69489
67889
  const expression = attr.value.expression;
69490
67890
  if (!expression) continue;
@@ -73305,6 +71705,18 @@ const reactDoctorRules = [
73305
71705
  requires: [...new Set(["react", ...jsxNoScriptUrl.requires ?? []])]
73306
71706
  }
73307
71707
  },
71708
+ {
71709
+ key: "react-doctor/jsx-no-target-blank",
71710
+ id: "jsx-no-target-blank",
71711
+ source: "react-doctor",
71712
+ originallyExternal: true,
71713
+ rule: {
71714
+ ...jsxNoTargetBlank,
71715
+ framework: "global",
71716
+ category: "Security",
71717
+ requires: [...new Set(["react", ...jsxNoTargetBlank.requires ?? []])]
71718
+ }
71719
+ },
73308
71720
  {
73309
71721
  key: "react-doctor/jsx-no-undef",
73310
71722
  id: "jsx-no-undef",