oxlint-plugin-react-doctor 0.8.1-dev.a4eaedd → 0.8.1-dev.a667b45

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 +47 -47
  2. package/dist/index.js +571 -738
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -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;
@@ -3173,7 +3173,7 @@ const getJsxPropStaticStringValues = (attribute, scopes) => getJsxPropStaticStri
3173
3173
  const getJsxPropExhaustiveStaticStringValues = (attribute, scopes) => getJsxPropStaticStringValuesWithMode(attribute, scopes, null, true);
3174
3174
  //#endregion
3175
3175
  //#region src/plugin/rules/a11y/anchor-has-content.ts
3176
- const MESSAGE$63 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
3176
+ const MESSAGE$62 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
3177
3177
  const hasLinkRole = (roleValue) => roleValue.trim().split(/\s+/).find((roleToken) => VALID_ARIA_ROLES.has(roleToken)) === "link";
3178
3178
  const isTransComponentsTemplate = (node) => {
3179
3179
  let current = node.parent;
@@ -3215,7 +3215,7 @@ const anchorHasContent = defineRule({
3215
3215
  if (isTransComponentsTemplate(node)) return;
3216
3216
  context.report({
3217
3217
  node: opening.name,
3218
- message: MESSAGE$63
3218
+ message: MESSAGE$62
3219
3219
  });
3220
3220
  } };
3221
3221
  }
@@ -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) {
@@ -3703,7 +3703,7 @@ const parseJsxValue = (value) => {
3703
3703
  };
3704
3704
  //#endregion
3705
3705
  //#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
3706
- const MESSAGE$62 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
3706
+ const MESSAGE$61 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
3707
3707
  const ariaActivedescendantHasTabindex = defineRule({
3708
3708
  id: "aria-activedescendant-has-tabindex",
3709
3709
  title: "aria-activedescendant missing tabindex",
@@ -3721,7 +3721,7 @@ const ariaActivedescendantHasTabindex = defineRule({
3721
3721
  if (tabIndexValue === null || tabIndexValue >= -1) return;
3722
3722
  context.report({
3723
3723
  node: node.name,
3724
- message: MESSAGE$62
3724
+ message: MESSAGE$61
3725
3725
  });
3726
3726
  return;
3727
3727
  }
@@ -3729,7 +3729,7 @@ const ariaActivedescendantHasTabindex = defineRule({
3729
3729
  if (canContentEditableBeTabbable(node, context.scopes, context.settings)) return;
3730
3730
  context.report({
3731
3731
  node: node.name,
3732
- message: MESSAGE$62
3732
+ message: MESSAGE$61
3733
3733
  });
3734
3734
  } })
3735
3735
  });
@@ -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;
@@ -6827,7 +6827,7 @@ const stripThisParameter = (parameters) => {
6827
6827
  };
6828
6828
  //#endregion
6829
6829
  //#region src/plugin/rules/security/auth-token-in-web-storage.ts
6830
- const MESSAGE$61 = "Storing an auth token in `localStorage`/`sessionStorage` exposes it to any XSS on the page: JavaScript can read web storage and exfiltrate the token. Keep tokens in an `HttpOnly`, `Secure`, `SameSite` cookie instead.";
6830
+ const MESSAGE$60 = "Storing an auth token in `localStorage`/`sessionStorage` exposes it to any XSS on the page: JavaScript can read web storage and exfiltrate the token. Keep tokens in an `HttpOnly`, `Secure`, `SameSite` cookie instead.";
6831
6831
  const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
6832
6832
  const STORAGE_GLOBALS = new Set([
6833
6833
  "window",
@@ -6999,7 +6999,7 @@ const authTokenInWebStorage = defineRule({
6999
6999
  })) return;
7000
7000
  context.report({
7001
7001
  node,
7002
- message: MESSAGE$61
7002
+ message: MESSAGE$60
7003
7003
  });
7004
7004
  },
7005
7005
  AssignmentExpression(node) {
@@ -7010,7 +7010,7 @@ const authTokenInWebStorage = defineRule({
7010
7010
  if (!propertyName || !isAuthCredentialKey(propertyName)) return;
7011
7011
  context.report({
7012
7012
  node: target,
7013
- message: MESSAGE$61
7013
+ message: MESSAGE$60
7014
7014
  });
7015
7015
  }
7016
7016
  }))
@@ -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;
@@ -7149,7 +7149,7 @@ const CI_INSTALL_NEAR_SECRET_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b
7149
7149
  const INSTALL_COMMAND_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b/i;
7150
7150
  const SECRET_REFERENCE_PATTERN = /\bsecrets\.[A-Z0-9_]+/;
7151
7151
  const IGNORE_SCRIPTS_FLAG_PATTERN = /--ignore-scripts\b/;
7152
- const MESSAGE$60 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
7152
+ const MESSAGE$59 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
7153
7153
  const isWorkflowPath = (relativePath) => /(?:^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(relativePath);
7154
7154
  const scanWorkflowContent = (content) => {
7155
7155
  const lines = content.split("\n");
@@ -7194,7 +7194,7 @@ const scanWorkflowContent = (content) => {
7194
7194
  const installLineOffset = Math.max(step.lines.findIndex((stepLine) => INSTALL_COMMAND_PATTERN.test(stepLine)), 0);
7195
7195
  const installColumnIndex = step.lines[installLineOffset].search(INSTALL_COMMAND_PATTERN);
7196
7196
  return [{
7197
- message: MESSAGE$60,
7197
+ message: MESSAGE$59,
7198
7198
  line: step.startLineIndex + installLineOffset + 1,
7199
7199
  column: (installColumnIndex === -1 ? 0 : installColumnIndex) + 1
7200
7200
  }];
@@ -7204,7 +7204,7 @@ const scanWorkflowContent = (content) => {
7204
7204
  const scanNonWorkflowConfig = scanByPattern({
7205
7205
  shouldScan: (file) => isConfigOrCiPath(file.relativePath) && !file.relativePath.endsWith("package.json") && !isWorkflowPath(file.relativePath),
7206
7206
  pattern: CI_INSTALL_NEAR_SECRET_PATTERN,
7207
- message: MESSAGE$60
7207
+ message: MESSAGE$59
7208
7208
  });
7209
7209
  const scan = (file) => {
7210
7210
  if (isWorkflowPath(file.relativePath)) return scanWorkflowContent(file.content);
@@ -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,
@@ -7954,7 +7954,7 @@ const isPureEventBlockerHandler = (attribute) => {
7954
7954
  };
7955
7955
  //#endregion
7956
7956
  //#region src/plugin/rules/a11y/click-events-have-key-events.ts
7957
- const MESSAGE$59 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
7957
+ const MESSAGE$58 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
7958
7958
  const KEY_HANDLERS = [
7959
7959
  "onKeyUp",
7960
7960
  "onKeyDown",
@@ -8165,7 +8165,7 @@ const clickEventsHaveKeyEvents = defineRule({
8165
8165
  if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler) || spreadEventValues.has(handler.toLowerCase()))) return;
8166
8166
  context.report({
8167
8167
  node: node.name,
8168
- message: MESSAGE$59
8168
+ message: MESSAGE$58
8169
8169
  });
8170
8170
  } };
8171
8171
  }
@@ -10257,7 +10257,7 @@ const getClassNameLiteral = (classAttribute) => {
10257
10257
  };
10258
10258
  //#endregion
10259
10259
  //#region src/plugin/rules/a11y/control-has-associated-label.ts
10260
- const MESSAGE$58 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
10260
+ const MESSAGE$57 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
10261
10261
  const NON_OPERABLE_ELEMENTS = new Set([
10262
10262
  "td",
10263
10263
  "th",
@@ -10380,7 +10380,7 @@ const WRAPPER_LABEL_PROP = "label";
10380
10380
  const SELECT_ELEMENT = "select";
10381
10381
  const DEFAULT_DEPTH = 5;
10382
10382
  const MAX_DEPTH = 25;
10383
- const resolveSettings$45 = (settings) => {
10383
+ const resolveSettings$46 = (settings) => {
10384
10384
  const reactDoctor = settings?.["react-doctor"];
10385
10385
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.controlHasAssociatedLabel ?? {} : {};
10386
10386
  return {
@@ -10713,7 +10713,7 @@ const controlHasAssociatedLabel = defineRule({
10713
10713
  recommendation: "Give every interactive control a label screen readers can read.",
10714
10714
  category: "Accessibility",
10715
10715
  create: (context) => {
10716
- const settings = resolveSettings$45(context.settings);
10716
+ const settings = resolveSettings$46(context.settings);
10717
10717
  const isTestlikeFile = isTestlikeFilename(context.filename);
10718
10718
  const iconComponentNames = /* @__PURE__ */ new Set();
10719
10719
  const iconNamespaceNames = /* @__PURE__ */ new Set();
@@ -10787,7 +10787,7 @@ const controlHasAssociatedLabel = defineRule({
10787
10787
  if (candidate.enclosingBindingName !== null && labelEmbeddedNames.has(candidate.enclosingBindingName)) continue;
10788
10788
  context.report({
10789
10789
  node: candidate.opening,
10790
- message: MESSAGE$58
10790
+ message: MESSAGE$57
10791
10791
  });
10792
10792
  }
10793
10793
  }
@@ -12594,7 +12594,7 @@ const noVagueButtonLabel = defineRule({
12594
12594
  });
12595
12595
  //#endregion
12596
12596
  //#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
12597
- const MESSAGE$57 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
12597
+ const MESSAGE$56 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
12598
12598
  const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
12599
12599
  const NAME_PROVIDING_ATTRIBUTES = [
12600
12600
  "aria-label",
@@ -12619,7 +12619,7 @@ const dialogHasAccessibleName = defineRule({
12619
12619
  if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
12620
12620
  context.report({
12621
12621
  node: node.name,
12622
- message: MESSAGE$57
12622
+ message: MESSAGE$56
12623
12623
  });
12624
12624
  } };
12625
12625
  }
@@ -12659,13 +12659,13 @@ const isEs6Component = (node) => {
12659
12659
  };
12660
12660
  //#endregion
12661
12661
  //#region src/plugin/rules/react-builtins/display-name.ts
12662
- const MESSAGE$56 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
12662
+ const MESSAGE$55 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
12663
12663
  const DEFAULT_ADDITIONAL_HOCS = [
12664
12664
  "observer",
12665
12665
  "lazy",
12666
12666
  "withTracking"
12667
12667
  ];
12668
- const resolveSettings$44 = (settings) => {
12668
+ const resolveSettings$45 = (settings) => {
12669
12669
  const reactDoctor = settings?.["react-doctor"];
12670
12670
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.displayName ?? {} : {};
12671
12671
  const additionalHoCs = new Set(ruleSettings.additionalHoCs ?? DEFAULT_ADDITIONAL_HOCS);
@@ -12847,12 +12847,12 @@ const displayName = defineRule({
12847
12847
  recommendation: "Give each component a `displayName` so DevTools shows a clear name.",
12848
12848
  category: "Architecture",
12849
12849
  create: (context) => {
12850
- const settings = resolveSettings$44(context.settings);
12850
+ const settings = resolveSettings$45(context.settings);
12851
12851
  const ignoreNamed = !settings.ignoreTranspilerName;
12852
12852
  const reportAt = (node) => {
12853
12853
  context.report({
12854
12854
  node,
12855
- message: MESSAGE$56
12855
+ message: MESSAGE$55
12856
12856
  });
12857
12857
  };
12858
12858
  return {
@@ -14147,7 +14147,7 @@ const resolveExpressionKey = (expression, context, visitedSymbolIds = /* @__PURE
14147
14147
  return objectKey ? `${objectKey}.${unwrappedExpression.property.name}` : null;
14148
14148
  }
14149
14149
  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)}`;
14150
+ if (isNodeOfType(unwrappedExpression, "Literal") && (typeof unwrappedExpression.value === "string" || typeof unwrappedExpression.value === "number")) return `literal:${String(unwrappedExpression.value)}`;
14151
14151
  if (isFunctionLike$1(unwrappedExpression)) {
14152
14152
  const rangeStart = getRangeStart(unwrappedExpression);
14153
14153
  return rangeStart === null ? null : `function:${rangeStart}`;
@@ -14176,11 +14176,9 @@ const resolveForEachProjection = (expression, context) => {
14176
14176
  const collectionKey = resolveExpressionKey(forEachCallee.object, context);
14177
14177
  if (!collectionKey) return null;
14178
14178
  const firstParameter = callbackNode.params[0];
14179
- const assignmentPattern = isNodeOfType(symbol.bindingIdentifier.parent, "AssignmentPattern") && symbol.bindingIdentifier.parent.left === symbol.bindingIdentifier ? symbol.bindingIdentifier.parent : null;
14180
- const propertyName = isNodeOfType(firstParameter, "ObjectPattern") ? getDestructuredBindingPropertyName(symbol.bindingIdentifier) : null;
14181
- const defaultValueKey = assignmentPattern ? resolveExpressionKey(assignmentPattern.right, context) : null;
14182
- if (assignmentPattern && !defaultValueKey) return null;
14183
- const parameterProjection = firstParameter === symbol.bindingIdentifier ? "value" : propertyName && defaultValueKey ? `${propertyName}=default:${defaultValueKey}` : propertyName;
14179
+ const bindingProperty = symbol.bindingIdentifier.parent;
14180
+ const propertyName = isNodeOfType(firstParameter, "ObjectPattern") && isNodeOfType(bindingProperty, "Property") && bindingProperty.parent === firstParameter && bindingProperty.value === symbol.bindingIdentifier ? getStaticPropertyKeyName(bindingProperty) : null;
14181
+ const parameterProjection = firstParameter === symbol.bindingIdentifier ? "value" : propertyName;
14184
14182
  if (!parameterProjection) return null;
14185
14183
  return {
14186
14184
  collectionKey,
@@ -14192,23 +14190,13 @@ const resolveForEachProjectionKey = (expression, context) => {
14192
14190
  return projection ? `forEach:${projection.collectionKey}:${projection.projectionKey}` : null;
14193
14191
  };
14194
14192
  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
14193
  const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqueOptionsIdentity) => {
14206
14194
  const capture = resolveEventListenerCapture(optionsNode, { allowIndeterminateEntries: true });
14207
14195
  if (capture !== null) return `capture:${String(capture)}`;
14208
14196
  if (!optionsNode) return null;
14209
14197
  const unwrappedOptions = stripParenExpression(optionsNode);
14210
14198
  if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) {
14211
- const optionsKey = allowOpaqueOptionsIdentity ? resolveEventListenerCaptureValueIdentityKey(unwrappedOptions, context) : null;
14199
+ const optionsKey = allowOpaqueOptionsIdentity ? resolveResourceIdentityKey(unwrappedOptions, context) : null;
14212
14200
  return optionsKey ? `options:${optionsKey}` : null;
14213
14201
  }
14214
14202
  let captureKey = "capture:false";
@@ -14223,7 +14211,7 @@ const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqu
14223
14211
  continue;
14224
14212
  }
14225
14213
  if (propertyName === "capture") {
14226
- const propertyValueKey = resolveEventListenerCaptureValueIdentityKey(property.value, context);
14214
+ const propertyValueKey = resolveResourceIdentityKey(property.value, context);
14227
14215
  captureKey = propertyValueKey ? `capture-value:${propertyValueKey}` : null;
14228
14216
  }
14229
14217
  }
@@ -14823,24 +14811,7 @@ const findLiveExpressionGuardForRelease = (releaseCall, owner, expressionKey, co
14823
14811
  return null;
14824
14812
  };
14825
14813
  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) => {
14814
+ const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
14844
14815
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
14845
14816
  const functionCfg = context.cfg.cfgFor(callback);
14846
14817
  const usageBlock = functionCfg?.blockOf(usage.node);
@@ -14851,8 +14822,7 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context, allowUnreleasedPat
14851
14822
  if (!isNodeOfType(child, "CallExpression")) return;
14852
14823
  const releaseStart = getRangeStart(child);
14853
14824
  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;
14825
+ if (releaseStart === null || releaseStart >= usageStart || functionCfg.blockOf(child) !== usageBlock && !handleGuard) return;
14856
14826
  if (doesReleaseCallMatchUsage(child, usage, context)) {
14857
14827
  matchingReleaseAnchors.push(handleGuard ?? child);
14858
14828
  return;
@@ -14860,7 +14830,7 @@ const hasRerunReleaseBeforeUsage = (callback, usage, context, allowUnreleasedPat
14860
14830
  const helperFunction = resolveStableValue(child.callee, context);
14861
14831
  if (helperFunction && isFunctionLike$1(helperFunction) && doesCleanupFunctionReleaseUsage(helperFunction, usage, context)) matchingReleaseAnchors.push(handleGuard ?? child);
14862
14832
  });
14863
- return allowUnreleasedPathsWithoutUsage ? doMatchingNodesCoverEveryPathBeforeUsage(usage.node, matchingReleaseAnchors, callback, context) : doNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
14833
+ return doNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
14864
14834
  };
14865
14835
  const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
14866
14836
  const componentFunction = findEnclosingFunction$1(callback);
@@ -14880,7 +14850,7 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
14880
14850
  });
14881
14851
  return didFindUnmountCleanup;
14882
14852
  };
14883
- const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasStableUnmountCleanupForUsage(callback, usage, context) && hasRerunReleaseBeforeUsage(callback, usage, context, usage.registrationVerbName === "setTimeout");
14853
+ const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
14884
14854
  const collectBlockingBooleanStates = (expression, blockedExpressionValue, guardNode, context) => {
14885
14855
  const unwrappedExpression = stripParenExpression(expression);
14886
14856
  if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, guardNode, context);
@@ -19046,7 +19016,7 @@ const compileGlob = (pattern) => {
19046
19016
  };
19047
19017
  //#endregion
19048
19018
  //#region src/plugin/rules/react-builtins/forbid-component-props.ts
19049
- const resolveSettings$43 = (settings) => {
19019
+ const resolveSettings$44 = (settings) => {
19050
19020
  const reactDoctor = settings?.["react-doctor"];
19051
19021
  return { forbid: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidComponentProps ?? {} : {}).forbid ?? [] };
19052
19022
  };
@@ -19098,7 +19068,7 @@ const forbidComponentProps = defineRule({
19098
19068
  recommendation: "Configure blocked component props so callers cannot bypass the component API contract.",
19099
19069
  category: "Architecture",
19100
19070
  create: (context) => {
19101
- const entries = resolveSettings$43(context.settings).forbid.map(normalizeEntry);
19071
+ const entries = resolveSettings$44(context.settings).forbid.map(normalizeEntry);
19102
19072
  return { JSXOpeningElement(node) {
19103
19073
  if (entries.length === 0) return;
19104
19074
  if (!isSupportedJsxName(node.name)) return;
@@ -19127,7 +19097,7 @@ const forbidComponentProps = defineRule({
19127
19097
  //#endregion
19128
19098
  //#region src/plugin/rules/react-builtins/forbid-dom-props.ts
19129
19099
  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) => {
19100
+ const resolveSettings$43 = (settings) => {
19131
19101
  const reactDoctor = settings?.["react-doctor"];
19132
19102
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidDomProps ?? {} : {};
19133
19103
  const map = /* @__PURE__ */ new Map();
@@ -19149,7 +19119,7 @@ const forbidDomProps = defineRule({
19149
19119
  recommendation: "Configure blocked DOM props so plain HTML tags stay on the agreed DOM API surface.",
19150
19120
  category: "Architecture",
19151
19121
  create: (context) => {
19152
- const forbidMap = resolveSettings$42(context.settings);
19122
+ const forbidMap = resolveSettings$43(context.settings);
19153
19123
  return { JSXOpeningElement(node) {
19154
19124
  if (forbidMap.size === 0) return;
19155
19125
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
@@ -19212,7 +19182,7 @@ const isReactFunctionCall = (node, expectedCall) => {
19212
19182
  //#endregion
19213
19183
  //#region src/plugin/rules/react-builtins/forbid-elements.ts
19214
19184
  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) => {
19185
+ const resolveSettings$42 = (settings) => {
19216
19186
  const reactDoctor = settings?.["react-doctor"];
19217
19187
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.forbidElements ?? {} : {};
19218
19188
  const map = /* @__PURE__ */ new Map();
@@ -19228,7 +19198,7 @@ const forbidElements = defineRule({
19228
19198
  recommendation: "Configure blocked elements so code stays on the approved UI primitives.",
19229
19199
  category: "Architecture",
19230
19200
  create: (context) => {
19231
- const forbidMap = resolveSettings$41(context.settings);
19201
+ const forbidMap = resolveSettings$42(context.settings);
19232
19202
  return {
19233
19203
  JSXOpeningElement(node) {
19234
19204
  if (forbidMap.size === 0) return;
@@ -19264,7 +19234,7 @@ const forbidElements = defineRule({
19264
19234
  });
19265
19235
  //#endregion
19266
19236
  //#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
19267
- const MESSAGE$55 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
19237
+ const MESSAGE$54 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
19268
19238
  const forwardRefUsesRef = defineRule({
19269
19239
  id: "forward-ref-uses-ref",
19270
19240
  title: "forwardRef without ref parameter",
@@ -19287,7 +19257,7 @@ const forwardRefUsesRef = defineRule({
19287
19257
  if (isNodeOfType(onlyParam, "RestElement")) return;
19288
19258
  context.report({
19289
19259
  node: inner,
19290
- message: MESSAGE$55
19260
+ message: MESSAGE$54
19291
19261
  });
19292
19262
  } })
19293
19263
  });
@@ -19328,7 +19298,7 @@ const gitProviderUrlInjectionRisk = defineRule({
19328
19298
  });
19329
19299
  //#endregion
19330
19300
  //#region src/plugin/rules/a11y/heading-has-content.ts
19331
- const MESSAGE$54 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
19301
+ const MESSAGE$53 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
19332
19302
  const DEFAULT_HEADING_TAGS = [
19333
19303
  "h1",
19334
19304
  "h2",
@@ -19337,7 +19307,7 @@ const DEFAULT_HEADING_TAGS = [
19337
19307
  "h5",
19338
19308
  "h6"
19339
19309
  ];
19340
- const resolveSettings$40 = (settings) => {
19310
+ const resolveSettings$41 = (settings) => {
19341
19311
  const reactDoctor = settings?.["react-doctor"];
19342
19312
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.headingHasContent ?? {} : {};
19343
19313
  return { headingTags: new Set([...DEFAULT_HEADING_TAGS, ...ruleSettings.components ?? []]) };
@@ -19350,7 +19320,7 @@ const headingHasContent = defineRule({
19350
19320
  recommendation: "Put readable text in every heading.",
19351
19321
  category: "Accessibility",
19352
19322
  create: (context) => {
19353
- const settings = resolveSettings$40(context.settings);
19323
+ const settings = resolveSettings$41(context.settings);
19354
19324
  return { JSXOpeningElement(node) {
19355
19325
  const elementType = getElementType(node, context.settings);
19356
19326
  if (!settings.headingTags.has(elementType)) return;
@@ -19362,7 +19332,7 @@ const headingHasContent = defineRule({
19362
19332
  for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
19363
19333
  context.report({
19364
19334
  node,
19365
- message: MESSAGE$54
19335
+ message: MESSAGE$53
19366
19336
  });
19367
19337
  } };
19368
19338
  }
@@ -19371,7 +19341,7 @@ const headingHasContent = defineRule({
19371
19341
  //#region src/plugin/rules/react-builtins/hook-use-state.ts
19372
19342
  const REQUIRE_DESTRUCTURE_MESSAGE = "`useState` should be destructured as `[value, setValue]` so readers can see the state value and setter together.";
19373
19343
  const NAMING_CONVENTION_MESSAGE = "This `useState` setter does not match its value name, so updates are harder to trace.";
19374
- const resolveSettings$39 = (settings) => {
19344
+ const resolveSettings$40 = (settings) => {
19375
19345
  const reactDoctor = settings?.["react-doctor"];
19376
19346
  return { allowDestructuredState: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.hookUseState ?? {} : {}).allowDestructuredState ?? false };
19377
19347
  };
@@ -19398,7 +19368,7 @@ const hookUseState = defineRule({
19398
19368
  recommendation: "Destructure `useState` as `const [thing, setThing] = useState(…)` so state reads and writes stay visible together.",
19399
19369
  category: "Architecture",
19400
19370
  create: (context) => {
19401
- const { allowDestructuredState } = resolveSettings$39(context.settings);
19371
+ const { allowDestructuredState } = resolveSettings$40(context.settings);
19402
19372
  return { CallExpression(node) {
19403
19373
  const callExpressionNode = node;
19404
19374
  if (!isReactFunctionCall(callExpressionNode, "useState")) return;
@@ -19530,8 +19500,8 @@ const hooksNoNanInDeps = defineRule({
19530
19500
  });
19531
19501
  //#endregion
19532
19502
  //#region src/plugin/rules/a11y/html-has-lang.ts
19533
- const MESSAGE$53 = "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) => {
19503
+ const MESSAGE$52 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
19504
+ const resolveSettings$39 = (settings) => {
19535
19505
  const reactDoctor = settings?.["react-doctor"];
19536
19506
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
19537
19507
  };
@@ -19568,7 +19538,7 @@ const htmlHasLang = defineRule({
19568
19538
  recommendation: "Set `<html lang=\"…\">` so screen readers know the page language.",
19569
19539
  category: "Accessibility",
19570
19540
  create: (context) => {
19571
- const settings = resolveSettings$38(context.settings);
19541
+ const settings = resolveSettings$39(context.settings);
19572
19542
  const tagSet = new Set(settings.htmlTags);
19573
19543
  return { JSXOpeningElement(node) {
19574
19544
  const tag = getElementType(node, context.settings);
@@ -19577,13 +19547,13 @@ const htmlHasLang = defineRule({
19577
19547
  if (!lang) {
19578
19548
  context.report({
19579
19549
  node: node.name,
19580
- message: MESSAGE$53
19550
+ message: MESSAGE$52
19581
19551
  });
19582
19552
  return;
19583
19553
  }
19584
19554
  if (evaluateLang(lang.value) === "empty") context.report({
19585
19555
  node: lang,
19586
- message: MESSAGE$53
19556
+ message: MESSAGE$52
19587
19557
  });
19588
19558
  } };
19589
19559
  }
@@ -19856,7 +19826,7 @@ const isJsxFragmentElement = (node, scopes) => {
19856
19826
  };
19857
19827
  //#endregion
19858
19828
  //#region src/plugin/rules/a11y/iframe-has-title.ts
19859
- const MESSAGE$52 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
19829
+ const MESSAGE$51 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
19860
19830
  const isStaticallyAriaHidden = (openingElement) => {
19861
19831
  const ariaHiddenAttribute = getAuthoritativeJsxAttribute(openingElement.attributes, "aria-hidden", false);
19862
19832
  if (!ariaHiddenAttribute) return false;
@@ -19945,14 +19915,14 @@ const iframeHasTitle = defineRule({
19945
19915
  if (!titleAttr) {
19946
19916
  if (hasSpread || tag === "iframe") context.report({
19947
19917
  node: node.name,
19948
- message: MESSAGE$52
19918
+ message: MESSAGE$51
19949
19919
  });
19950
19920
  return;
19951
19921
  }
19952
19922
  const verdict = evaluateTitleValue(titleAttr.value);
19953
19923
  if (verdict === "missing" || verdict === "empty") context.report({
19954
19924
  node: titleAttr,
19955
- message: MESSAGE$52
19925
+ message: MESSAGE$51
19956
19926
  });
19957
19927
  } })
19958
19928
  });
@@ -20077,14 +20047,14 @@ const iframeMissingSandbox = defineRule({
20077
20047
  });
20078
20048
  //#endregion
20079
20049
  //#region src/plugin/rules/a11y/img-redundant-alt.ts
20080
- const MESSAGE$51 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
20050
+ const MESSAGE$50 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
20081
20051
  const DEFAULT_COMPONENTS = ["img"];
20082
20052
  const DEFAULT_REDUNDANT_WORDS = [
20083
20053
  "image",
20084
20054
  "photo",
20085
20055
  "picture"
20086
20056
  ];
20087
- const resolveSettings$37 = (settings) => {
20057
+ const resolveSettings$38 = (settings) => {
20088
20058
  const reactDoctor = settings?.["react-doctor"];
20089
20059
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.imgRedundantAlt ?? {} : {};
20090
20060
  return {
@@ -20132,7 +20102,7 @@ const imgRedundantAlt = defineRule({
20132
20102
  category: "Accessibility",
20133
20103
  create: (context) => {
20134
20104
  if (isGeneratedImageRenderContext(context)) return {};
20135
- const settings = resolveSettings$37(context.settings);
20105
+ const settings = resolveSettings$38(context.settings);
20136
20106
  return { JSXOpeningElement(node) {
20137
20107
  const tag = getElementType(node, context.settings);
20138
20108
  if (!settings.components.includes(tag)) return;
@@ -20142,7 +20112,7 @@ const imgRedundantAlt = defineRule({
20142
20112
  if (!altAttribute) return;
20143
20113
  if (altValueRedundant(altAttribute, settings.words)) context.report({
20144
20114
  node: altAttribute,
20145
- message: MESSAGE$51
20115
+ message: MESSAGE$50
20146
20116
  });
20147
20117
  } };
20148
20118
  }
@@ -20440,7 +20410,7 @@ const COMPOSITE_ITEM_ROLES = new Set([
20440
20410
  "row",
20441
20411
  "gridcell"
20442
20412
  ]);
20443
- const resolveSettings$36 = (settings) => {
20413
+ const resolveSettings$37 = (settings) => {
20444
20414
  const reactDoctor = settings?.["react-doctor"];
20445
20415
  return { tabbable: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.interactiveSupportsFocus ?? {} : {}).tabbable ?? DEFAULT_TABBABLE_ROLES };
20446
20416
  };
@@ -20452,7 +20422,7 @@ const interactiveSupportsFocus = defineRule({
20452
20422
  recommendation: "Add keyboard focus support so users can reach interactive elements without a pointer.",
20453
20423
  category: "Accessibility",
20454
20424
  create: (context) => {
20455
- const settings = resolveSettings$36(context.settings);
20425
+ const settings = resolveSettings$37(context.settings);
20456
20426
  const tabbableSet = new Set(settings.tabbable);
20457
20427
  return { JSXOpeningElement(node) {
20458
20428
  if (isLocalTestScaffoldJsx(node, context)) return;
@@ -20674,7 +20644,7 @@ const collectHandlerReferencedNames = (root) => {
20674
20644
  //#endregion
20675
20645
  //#region src/plugin/rules/jotai/jotai-select-atom-in-render-body.ts
20676
20646
  const JOTAI_SELECT_ATOM_SOURCES = ["jotai/utils", "jotai"];
20677
- const COMPONENT_NAME_PATTERN$1 = /^[A-Z]/;
20647
+ const COMPONENT_NAME_PATTERN = /^[A-Z]/;
20678
20648
  const isImportedSelectAtom = (callExpression) => {
20679
20649
  if (!isNodeOfType(callExpression.callee, "Identifier")) return false;
20680
20650
  const localName = callExpression.callee.name;
@@ -20710,14 +20680,14 @@ const getHandlerNamedBindingName = (functionNode) => {
20710
20680
  const containingFunctionIsComponentOrHook = (functionNode) => {
20711
20681
  if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.id) {
20712
20682
  const declaredName = functionNode.id.name;
20713
- return COMPONENT_NAME_PATTERN$1.test(declaredName) || HOOK_NAME_PATTERN$3.test(declaredName);
20683
+ return COMPONENT_NAME_PATTERN.test(declaredName) || HOOK_NAME_PATTERN$3.test(declaredName);
20714
20684
  }
20715
20685
  let cursor = functionNode.parent ?? null;
20716
20686
  while (cursor && isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
20717
20687
  if (!cursor) return false;
20718
20688
  if (!isNodeOfType(cursor, "VariableDeclarator")) return false;
20719
20689
  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);
20690
+ return COMPONENT_NAME_PATTERN.test(cursor.id.name) || HOOK_NAME_PATTERN$3.test(cursor.id.name);
20721
20691
  };
20722
20692
  const isBindingInvokedOnRenderPath = (root, bindingName) => {
20723
20693
  let didFindRenderPathInvocation = false;
@@ -22106,7 +22076,7 @@ const getHoistableRegExpConstructionKind = (node, context) => {
22106
22076
  if (!STATEFUL_REGEXP_FLAGS_PATTERN.test(effectiveFlags)) return "stateless";
22107
22077
  return isSafeStatefulReplaceAllSearch(node, effectiveFlags, context) ? "statefulReplaceAll" : null;
22108
22078
  };
22109
- const MESSAGE$50 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
22079
+ const MESSAGE$49 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
22110
22080
  const jsHoistRegexp = defineRule({
22111
22081
  id: "js-hoist-regexp",
22112
22082
  title: "RegExp built inside a loop",
@@ -22123,7 +22093,7 @@ const jsHoistRegexp = defineRule({
22123
22093
  if (constructionKind === "statefulReplaceAll" && cachedEnvironmentHazard === "replaceAllIntegrityLost") return;
22124
22094
  context.report({
22125
22095
  node,
22126
- message: MESSAGE$50
22096
+ message: MESSAGE$49
22127
22097
  });
22128
22098
  };
22129
22099
  return createLoopAwareVisitors({
@@ -23748,7 +23718,7 @@ const jsTosortedImmutable = defineRule({
23748
23718
  const NEVER_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
23749
23719
  const ALWAYS_MESSAGE$2 = () => "This boolean prop style disagrees with the project setting, so equivalent true props are harder to scan consistently.";
23750
23720
  const FALSE_OMITTED_MESSAGE = (attributeName) => `\`${attributeName}={false}\` does nothing, so the explicit false value adds noise without changing output.`;
23751
- const resolveSettings$35 = (settings) => {
23721
+ const resolveSettings$36 = (settings) => {
23752
23722
  const reactDoctor = settings?.["react-doctor"];
23753
23723
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxBooleanValue ?? {} : {};
23754
23724
  return {
@@ -23766,7 +23736,7 @@ const jsxBooleanValue = defineRule({
23766
23736
  recommendation: "Use one boolean-attribute style so equivalent true props scan the same across the codebase.",
23767
23737
  category: "Architecture",
23768
23738
  create: (context) => {
23769
- const settings = resolveSettings$35(context.settings);
23739
+ const settings = resolveSettings$36(context.settings);
23770
23740
  const alwaysSet = new Set(settings.always);
23771
23741
  const neverSet = new Set(settings.never);
23772
23742
  return { JSXAttribute(node) {
@@ -23820,7 +23790,7 @@ const jsxBooleanValue = defineRule({
23820
23790
  const UNNECESSARY_BRACES_MESSAGE = "These curly braces wrap a literal value, so they add JSX noise without changing output.";
23821
23791
  const REQUIRED_BRACES_MESSAGE = "This JSX value needs `{ }` so React reads it as an expression instead of text.";
23822
23792
  const isAllowedMode = (value) => value === "always" || value === "never" || value === "ignore";
23823
- const resolveSettings$34 = (settings) => {
23793
+ const resolveSettings$35 = (settings) => {
23824
23794
  const reactDoctor = settings?.["react-doctor"];
23825
23795
  const ruleSettingsRaw = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxCurlyBracePresence : void 0;
23826
23796
  if (isAllowedMode(ruleSettingsRaw)) return {
@@ -23916,7 +23886,7 @@ const jsxCurlyBracePresence = defineRule({
23916
23886
  recommendation: "Use one JSX literal style so equivalent markup scans the same without extra JSX noise.",
23917
23887
  category: "Architecture",
23918
23888
  create: (context) => {
23919
- const settings = resolveSettings$34(context.settings);
23889
+ const settings = resolveSettings$35(context.settings);
23920
23890
  return {
23921
23891
  JSXAttribute(node) {
23922
23892
  const value = node.value;
@@ -23999,7 +23969,7 @@ const containsJsxElement = (root) => {
23999
23969
  //#region src/plugin/rules/react-builtins/jsx-filename-extension.ts
24000
23970
  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
23971
  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) => {
23972
+ const resolveSettings$34 = (settings) => {
24003
23973
  const reactDoctor = settings?.["react-doctor"];
24004
23974
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFilenameExtension ?? {} : {};
24005
23975
  return {
@@ -24021,7 +23991,7 @@ const jsxFilenameExtension = defineRule({
24021
23991
  recommendation: "Name JSX files with the configured extension so file names accurately signal which files contain JSX.",
24022
23992
  category: "Architecture",
24023
23993
  create: (context) => {
24024
- const settings = resolveSettings$33(context.settings);
23994
+ const settings = resolveSettings$34(context.settings);
24025
23995
  const allowedExtensions = normalizeExtensions(settings.extensions);
24026
23996
  const filename = normalizeFilename(context.filename ?? "fixture.tsx");
24027
23997
  const extensionOnly = path.extname(filename).slice(1);
@@ -24062,7 +24032,7 @@ const jsxFilenameExtension = defineRule({
24062
24032
  //#region src/plugin/rules/react-builtins/jsx-fragments.ts
24063
24033
  const SYNTAX_MESSAGE = "`<React.Fragment>` is used where shorthand fragments are configured, so similar wrappers look different across the codebase.";
24064
24034
  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) => {
24035
+ const resolveSettings$33 = (settings) => {
24066
24036
  const reactDoctor = settings?.["react-doctor"];
24067
24037
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxFragments ?? {} : {}).mode ?? "syntax" };
24068
24038
  };
@@ -24074,7 +24044,7 @@ const jsxFragments = defineRule({
24074
24044
  recommendation: "Use one fragment style so identical wrappers do not look different across files.",
24075
24045
  category: "Architecture",
24076
24046
  create: (context) => {
24077
- const { mode } = resolveSettings$32(context.settings);
24047
+ const { mode } = resolveSettings$33(context.settings);
24078
24048
  return {
24079
24049
  JSXElement(node) {
24080
24050
  if (mode !== "syntax") return;
@@ -24118,7 +24088,7 @@ const buildPropRegex = (handlerPropPrefix) => {
24118
24088
  const escaped = tokens.map((token) => token.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("|");
24119
24089
  return new RegExp(`^(${escaped})[A-Z].*$`);
24120
24090
  };
24121
- const resolveSettings$31 = (settings) => {
24091
+ const resolveSettings$32 = (settings) => {
24122
24092
  const reactDoctor = settings?.["react-doctor"];
24123
24093
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxHandlerNames ?? {} : {};
24124
24094
  let handlerPrefix = DEFAULT_HANDLER_PREFIX;
@@ -24194,7 +24164,7 @@ const jsxHandlerNames = defineRule({
24194
24164
  recommendation: "Use the `on…` prefix for event-handler props and `handle…` for handlers so readers can trace event flow.",
24195
24165
  category: "Architecture",
24196
24166
  create: (context) => {
24197
- const settings = resolveSettings$31(context.settings);
24167
+ const settings = resolveSettings$32(context.settings);
24198
24168
  return { JSXAttribute(node) {
24199
24169
  if (settings.ignoreComponentNames.length > 0) {
24200
24170
  const opening = node.parent;
@@ -24269,19 +24239,6 @@ const hasJsxKeyAttribute = (openingElement) => {
24269
24239
  return false;
24270
24240
  };
24271
24241
  //#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
24242
  //#region src/plugin/utils/is-const-declared-binding.ts
24286
24243
  const BINDING_PATTERN_ANCESTOR_TYPES = new Set([
24287
24244
  "VariableDeclarator",
@@ -24326,17 +24283,11 @@ const ITERATOR_METHOD_NAMES$1 = new Set([
24326
24283
  "flatMap",
24327
24284
  "from"
24328
24285
  ]);
24329
- const RENDERING_CALL_NAMES = new Set([
24330
- "createPortal",
24331
- "hydrate",
24332
- "hydrateRoot",
24333
- "render"
24334
- ]);
24335
24286
  const MISSING_KEY_ARRAY = "Your users can see the wrong data when this array reorders.";
24336
24287
  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.";
24288
+ const KEY_AFTER_SPREAD = "Place this `key` before the `{...spread}` so the JSX transform can always extract it.";
24338
24289
  const DUPLICATE_KEY = (keyValue) => `Your users can see the wrong data because two elements share the key "${keyValue}".`;
24339
- const resolveSettings$30 = (settings) => {
24290
+ const resolveSettings$31 = (settings) => {
24340
24291
  const reactDoctor = settings?.["react-doctor"];
24341
24292
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxKey ?? {} : {};
24342
24293
  return {
@@ -24350,33 +24301,6 @@ const findArrayVariableDeclarator = (arrayExpression) => {
24350
24301
  if (ancestor && isNodeOfType(ancestor, "VariableDeclarator") && ancestor.init === wrapped && isNodeOfType(ancestor.id, "Identifier")) return ancestor;
24351
24302
  return null;
24352
24303
  };
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
24304
  const isIdentityIteratorCallback = (callback) => {
24381
24305
  const callbackFunction = stripParenExpression(callback);
24382
24306
  if (!isNodeOfType(callbackFunction, "ArrowFunctionExpression") && !isNodeOfType(callbackFunction, "FunctionExpression")) return true;
@@ -24473,7 +24397,7 @@ const findNamedCallbackIteratorCall = (functionNode) => {
24473
24397
  namedCallbackIteratorCallCache.set(functionNode, iteratorCall);
24474
24398
  return iteratorCall;
24475
24399
  };
24476
- const findEnclosingIteratorContext = (jsxNode, scopes) => {
24400
+ const findEnclosingIteratorContext = (jsxNode) => {
24477
24401
  let current = jsxNode;
24478
24402
  let isOutsideContainingFunction = false;
24479
24403
  let didSeeReturnStatement = false;
@@ -24495,8 +24419,6 @@ const findEnclosingIteratorContext = (jsxNode, scopes) => {
24495
24419
  isOutsideContainingFunction = true;
24496
24420
  } else if (isNodeOfType(parent, "ArrayExpression")) {
24497
24421
  if (isOutsideContainingFunction) return null;
24498
- if (isArrayNestedInObjectProperty(parent)) return null;
24499
- if (isArrayPassedToNonRenderingCall(parent, scopes)) return null;
24500
24422
  const arrayParent = parent.parent;
24501
24423
  if (arrayParent && isNodeOfType(arrayParent, "Property")) return null;
24502
24424
  if (arrayParent && isNodeOfType(arrayParent, "ArrayExpression")) return null;
@@ -24578,15 +24500,9 @@ const isWithinChildrenToArray = (jsxNode) => {
24578
24500
  const isKeyPropertyName = (propertyKey) => {
24579
24501
  if (isNodeOfType(propertyKey, "Identifier")) return propertyKey.name === "key";
24580
24502
  if (isNodeOfType(propertyKey, "Literal")) return String(propertyKey.value) === "key";
24581
- return false;
24503
+ return true;
24582
24504
  };
24583
24505
  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
24506
  const isFunctionParameterPropsRest = (bindingIdentifier) => {
24591
24507
  const restElement = bindingIdentifier.parent;
24592
24508
  if (!restElement || !isNodeOfType(restElement, "RestElement")) return false;
@@ -24596,7 +24512,7 @@ const isFunctionParameterPropsRest = (bindingIdentifier) => {
24596
24512
  const parameterNode = patternParent && isNodeOfType(patternParent, "AssignmentPattern") && patternParent.left === objectPattern ? patternParent : objectPattern;
24597
24513
  const functionNode = parameterNode.parent;
24598
24514
  if (!functionNode || !isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "FunctionDeclaration")) return false;
24599
- return isComponentFunction$1(functionNode) && functionNode.params[0] === parameterNode;
24515
+ return functionNode.params.some((param) => param === parameterNode);
24600
24516
  };
24601
24517
  const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
24602
24518
  const restElement = bindingIdentifier.parent;
@@ -24609,14 +24525,14 @@ const isRestBindingWithKeyExtracted = (bindingIdentifier) => {
24609
24525
  }
24610
24526
  return false;
24611
24527
  };
24612
- const isBindingAssignedKey = (scopeOwner, bindingName) => {
24613
- let didFindKeyAssignment = false;
24528
+ const isBindingObjectMutated = (scopeOwner, bindingName) => {
24529
+ let didFindMutation = false;
24614
24530
  walkAst(scopeOwner, (node) => {
24615
- if (didFindKeyAssignment) return false;
24531
+ if (didFindMutation) return false;
24616
24532
  if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression")) {
24617
24533
  const assignedObject = stripParenExpression(node.left.object);
24618
- if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName && isKeyPropertyName(node.left.property)) {
24619
- didFindKeyAssignment = true;
24534
+ if (isNodeOfType(assignedObject, "Identifier") && assignedObject.name === bindingName) {
24535
+ didFindMutation = true;
24620
24536
  return false;
24621
24537
  }
24622
24538
  }
@@ -24624,63 +24540,59 @@ const isBindingAssignedKey = (scopeOwner, bindingName) => {
24624
24540
  const callee = node.callee;
24625
24541
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Object" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "assign") {
24626
24542
  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
- }
24543
+ if (firstArgument && isNodeOfType(firstArgument, "Identifier") && firstArgument.name === bindingName) {
24544
+ didFindMutation = true;
24545
+ return false;
24634
24546
  }
24635
24547
  }
24636
24548
  }
24637
24549
  });
24638
- return didFindKeyAssignment;
24550
+ return didFindMutation;
24639
24551
  };
24640
- const spreadExpressionHasKey = (expression, depth) => {
24552
+ const spreadExpressionCanCarryKey = (expression, depth) => {
24641
24553
  const inner = stripParenExpression(expression);
24642
24554
  if (isNodeOfType(inner, "ObjectExpression")) {
24643
24555
  for (const property of inner.properties) {
24644
24556
  if (isNodeOfType(property, "SpreadElement")) {
24645
- if (depth >= 3) continue;
24646
- if (spreadExpressionHasKey(property.argument, depth + 1)) return true;
24557
+ if (depth >= 3) return true;
24558
+ if (spreadExpressionCanCarryKey(property.argument, depth + 1)) return true;
24647
24559
  continue;
24648
24560
  }
24649
- if (!isNodeOfType(property, "Property")) continue;
24561
+ if (!isNodeOfType(property, "Property")) return true;
24562
+ if (property.computed) return true;
24650
24563
  if (isKeyPropertyName(property.key)) return true;
24651
24564
  }
24652
24565
  return false;
24653
24566
  }
24654
24567
  if (isNodeOfType(inner, "ConditionalExpression")) {
24655
- if (depth >= 3) return false;
24656
- return spreadExpressionHasKey(inner.consequent, depth + 1) || spreadExpressionHasKey(inner.alternate, depth + 1);
24568
+ if (depth >= 3) return true;
24569
+ return spreadExpressionCanCarryKey(inner.consequent, depth + 1) || spreadExpressionCanCarryKey(inner.alternate, depth + 1);
24657
24570
  }
24658
24571
  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);
24572
+ if (depth >= 3) return true;
24573
+ if (inner.operator === "&&") return spreadExpressionCanCarryKey(inner.right, depth + 1);
24574
+ return spreadExpressionCanCarryKey(inner.left, depth + 1) || spreadExpressionCanCarryKey(inner.right, depth + 1);
24662
24575
  }
24663
24576
  if (isNodeOfType(inner, "Literal")) return false;
24664
24577
  if (isThisPropsMember(inner)) return false;
24665
24578
  if (isNodeOfType(inner, "Identifier")) {
24666
24579
  if (inner.name === "undefined") return false;
24667
- if (depth >= 3) return false;
24580
+ if (depth >= 3) return true;
24668
24581
  const binding = findVariableInitializer(inner, inner.name);
24669
- if (!binding) return false;
24582
+ if (!binding) return true;
24670
24583
  if (isRestBindingWithKeyExtracted(binding.bindingIdentifier)) return false;
24671
24584
  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);
24585
+ if (!isConstDeclaredBinding(binding) || !binding.initializer) return true;
24586
+ if (isBindingObjectMutated(binding.scopeOwner, inner.name)) return true;
24587
+ return spreadExpressionCanCarryKey(binding.initializer, depth + 1);
24676
24588
  }
24677
- return false;
24589
+ return true;
24678
24590
  };
24679
- const spreadCanOverwriteKey = (spreadAttribute) => spreadExpressionHasKey(spreadAttribute.argument, 0);
24591
+ const spreadCanOverwriteKey = (spreadAttribute) => spreadExpressionCanCarryKey(spreadAttribute.argument, 0);
24680
24592
  const checkKeyBeforeSpread = (context, openingElement) => {
24681
24593
  let keyIndex = null;
24682
24594
  let keyAttribute = null;
24683
- let lastKeyCarryingSpreadIndex = null;
24595
+ let firstKeyCarryingSpreadIndex = null;
24684
24596
  for (let attributeIndex = 0; attributeIndex < openingElement.attributes.length; attributeIndex++) {
24685
24597
  const attribute = openingElement.attributes[attributeIndex];
24686
24598
  if (isNodeOfType(attribute, "JSXAttribute")) {
@@ -24688,11 +24600,11 @@ const checkKeyBeforeSpread = (context, openingElement) => {
24688
24600
  keyIndex = attributeIndex;
24689
24601
  keyAttribute = attribute;
24690
24602
  }
24691
- } else if (isNodeOfType(attribute, "JSXSpreadAttribute") && spreadCanOverwriteKey(attribute)) lastKeyCarryingSpreadIndex = attributeIndex;
24603
+ } else if (isNodeOfType(attribute, "JSXSpreadAttribute") && firstKeyCarryingSpreadIndex === null && spreadCanOverwriteKey(attribute)) firstKeyCarryingSpreadIndex = attributeIndex;
24692
24604
  }
24693
- if (keyIndex !== null && lastKeyCarryingSpreadIndex !== null && lastKeyCarryingSpreadIndex > keyIndex && keyAttribute) context.report({
24605
+ if (keyIndex !== null && firstKeyCarryingSpreadIndex !== null && firstKeyCarryingSpreadIndex < keyIndex && keyAttribute) context.report({
24694
24606
  node: keyAttribute,
24695
- message: KEY_BEFORE_SPREAD
24607
+ message: KEY_AFTER_SPREAD
24696
24608
  });
24697
24609
  };
24698
24610
  const getKeyAttributeValueString = (openingElement) => {
@@ -24736,7 +24648,7 @@ const jsxKey = defineRule({
24736
24648
  severity: "error",
24737
24649
  recommendation: "Add a stable `key` prop so React can keep list items matched to the right data when the list changes.",
24738
24650
  create: (context) => {
24739
- const settings = resolveSettings$30(context.settings);
24651
+ const settings = resolveSettings$31(context.settings);
24740
24652
  return {
24741
24653
  JSXElement(node) {
24742
24654
  const openingElement = node.openingElement;
@@ -24754,7 +24666,7 @@ const jsxKey = defineRule({
24754
24666
  else seenKeys.add(keyValue.keyValue);
24755
24667
  }
24756
24668
  }
24757
- const enclosingContext = findEnclosingIteratorContext(node, context.scopes);
24669
+ const enclosingContext = findEnclosingIteratorContext(node);
24758
24670
  if (!enclosingContext) return;
24759
24671
  if (isWithinChildrenToArray(node)) return;
24760
24672
  if (hasJsxKeyAttribute(openingElement)) return;
@@ -24790,7 +24702,7 @@ const jsxKey = defineRule({
24790
24702
  //#region src/plugin/rules/react-builtins/jsx-max-depth.ts
24791
24703
  const buildMessage$19 = (depth, max) => `This JSX is hard to read at ${depth} levels deep, past the limit of ${max}.`;
24792
24704
  const DEFAULT_MAX_DEPTH = 14;
24793
- const resolveSettings$29 = (settings) => {
24705
+ const resolveSettings$30 = (settings) => {
24794
24706
  const reactDoctor = settings?.["react-doctor"];
24795
24707
  return { max: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxMaxDepth ?? {} : {}).max ?? DEFAULT_MAX_DEPTH };
24796
24708
  };
@@ -24859,7 +24771,7 @@ const jsxMaxDepth = defineRule({
24859
24771
  recommendation: "Pull deeply nested JSX into smaller components so it's easier to read.",
24860
24772
  category: "Architecture",
24861
24773
  create: (context) => {
24862
- const { max } = resolveSettings$29(context.settings);
24774
+ const { max } = resolveSettings$30(context.settings);
24863
24775
  const deepestLeafPerTree = /* @__PURE__ */ new Map();
24864
24776
  const checkNode = (node) => {
24865
24777
  if (!isLeafJsxNode(node)) return;
@@ -24890,7 +24802,7 @@ const jsxMaxDepth = defineRule({
24890
24802
  });
24891
24803
  //#endregion
24892
24804
  //#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
24893
- const MESSAGE$49 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
24805
+ const MESSAGE$48 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
24894
24806
  const LITERAL_TEXT_TAGS = new Set([
24895
24807
  "code",
24896
24808
  "pre",
@@ -24960,7 +24872,7 @@ const jsxNoCommentTextnodes = defineRule({
24960
24872
  if (isDeliberateStyledCommentToken(node)) return;
24961
24873
  context.report({
24962
24874
  node,
24963
- message: MESSAGE$49
24875
+ message: MESSAGE$48
24964
24876
  });
24965
24877
  } })
24966
24878
  });
@@ -24991,7 +24903,7 @@ const isInsideFunctionScope = (node) => {
24991
24903
  };
24992
24904
  //#endregion
24993
24905
  //#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
24994
- const MESSAGE$48 = "Every reader of this context redraws on each render because you build its `value` inline.";
24906
+ const MESSAGE$47 = "Every reader of this context redraws on each render because you build its `value` inline.";
24995
24907
  const CONTEXT_MODULES$1 = [
24996
24908
  "react",
24997
24909
  "use-context-selector",
@@ -25094,7 +25006,7 @@ const jsxNoConstructedContextValues = defineRule({
25094
25006
  if (!isConstructedValue(innerExpression)) continue;
25095
25007
  context.report({
25096
25008
  node: attribute,
25097
- message: MESSAGE$48
25009
+ message: MESSAGE$47
25098
25010
  });
25099
25011
  }
25100
25012
  }
@@ -25960,7 +25872,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
25960
25872
  ];
25961
25873
  //#endregion
25962
25874
  //#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
25963
- const MESSAGE$47 = "This child redraws every render because the prop gets a brand new array each time.";
25875
+ const MESSAGE$46 = "This child redraws every render because the prop gets a brand new array each time.";
25964
25876
  const isDataArrayPropName = (propName) => {
25965
25877
  if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
25966
25878
  for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -26047,7 +25959,7 @@ const jsxNoNewArrayAsProp = defineRule({
26047
25959
  if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
26048
25960
  context.report({
26049
25961
  node,
26050
- message: MESSAGE$47
25962
+ message: MESSAGE$46
26051
25963
  });
26052
25964
  }
26053
25965
  };
@@ -26305,7 +26217,7 @@ const SAFE_RECEIVER_NAMES = new Set([
26305
26217
  ]);
26306
26218
  //#endregion
26307
26219
  //#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
26308
- const MESSAGE$46 = "This child redraws every render because the prop gets a brand new function each time.";
26220
+ const MESSAGE$45 = "This child redraws every render because the prop gets a brand new function each time.";
26309
26221
  const isAccessorPredicateName = (propName) => {
26310
26222
  for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
26311
26223
  if (propName.length <= prefix.length) continue;
@@ -26512,7 +26424,7 @@ const jsxNoNewFunctionAsProp = defineRule({
26512
26424
  if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
26513
26425
  context.report({
26514
26426
  node,
26515
- message: MESSAGE$46
26427
+ message: MESSAGE$45
26516
26428
  });
26517
26429
  }
26518
26430
  };
@@ -26732,7 +26644,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
26732
26644
  ];
26733
26645
  //#endregion
26734
26646
  //#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
26735
- const MESSAGE$45 = "This child redraws every render because the prop gets a brand new object each time.";
26647
+ const MESSAGE$44 = "This child redraws every render because the prop gets a brand new object each time.";
26736
26648
  const isConfigObjectPropName = (propName) => {
26737
26649
  if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
26738
26650
  for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -26821,7 +26733,7 @@ const jsxNoNewObjectAsProp = defineRule({
26821
26733
  if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
26822
26734
  context.report({
26823
26735
  node,
26824
- message: MESSAGE$45
26736
+ message: MESSAGE$44
26825
26737
  });
26826
26738
  }
26827
26739
  };
@@ -26829,9 +26741,9 @@ const jsxNoNewObjectAsProp = defineRule({
26829
26741
  });
26830
26742
  //#endregion
26831
26743
  //#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
26832
- const MESSAGE$44 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
26744
+ const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
26833
26745
  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) => {
26746
+ const resolveSettings$29 = (settings) => {
26835
26747
  const reactDoctor = settings?.["react-doctor"];
26836
26748
  if (typeof reactDoctor !== "object" || reactDoctor === null) return {};
26837
26749
  return reactDoctor.jsxNoScriptUrl ?? {};
@@ -26853,7 +26765,7 @@ const jsxNoScriptUrl = defineRule({
26853
26765
  recommendation: "Use real event handlers instead of `javascript:` URLs so injected URL text cannot execute as code.",
26854
26766
  category: "Security",
26855
26767
  create: (context) => {
26856
- const options = resolveSettings$28(context.settings);
26768
+ const options = resolveSettings$29(context.settings);
26857
26769
  return { JSXOpeningElement(node) {
26858
26770
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
26859
26771
  const elementName = resolveJsxElementType(node);
@@ -26867,7 +26779,284 @@ const jsxNoScriptUrl = defineRule({
26867
26779
  if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
26868
26780
  if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
26869
26781
  node: attribute,
26870
- message: MESSAGE$44
26782
+ message: MESSAGE$43
26783
+ });
26784
+ }
26785
+ } };
26786
+ }
26787
+ });
26788
+ //#endregion
26789
+ //#region src/plugin/rules/react-builtins/jsx-no-target-blank.ts
26790
+ const NOREFERRER_MESSAGE = "`target=\"_blank\"` without `rel=\"noreferrer\"` lets the linked page hijack your tab to a phishing site.";
26791
+ const NOOPENER_MESSAGE = "`target=\"_blank\"` without `rel` lets the linked page hijack your tab to a phishing site.";
26792
+ const SPREAD_MESSAGE = "A spread here can add `target=\"_blank\"`, letting the linked page hijack your tab to a phishing site.";
26793
+ const resolveSettings$28 = (settings) => {
26794
+ const reactDoctor = settings?.["react-doctor"];
26795
+ const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxNoTargetBlank ?? {} : {};
26796
+ const reactSettings = typeof settings?.react === "object" && settings.react !== null ? settings.react : {};
26797
+ const linkComponents = /* @__PURE__ */ new Map();
26798
+ for (const entry of reactSettings.linkComponents ?? []) if (typeof entry === "string") linkComponents.set(entry, ["href"]);
26799
+ else if (typeof entry === "object" && entry !== null) {
26800
+ const linkAttribute = entry.linkAttribute ?? "href";
26801
+ linkComponents.set(entry.name, Array.isArray(linkAttribute) ? linkAttribute : [linkAttribute]);
26802
+ }
26803
+ const formComponents = /* @__PURE__ */ new Map();
26804
+ for (const entry of reactSettings.formComponents ?? []) if (typeof entry === "string") formComponents.set(entry, ["action"]);
26805
+ else if (typeof entry === "object" && entry !== null) {
26806
+ const formAttribute = entry.formAttribute ?? "action";
26807
+ formComponents.set(entry.name, Array.isArray(formAttribute) ? formAttribute : [formAttribute]);
26808
+ }
26809
+ return {
26810
+ enforceDynamicLinks: ruleSettings.enforceDynamicLinks ?? "always",
26811
+ warnOnSpreadAttributes: ruleSettings.warnOnSpreadAttributes ?? false,
26812
+ allowReferrer: ruleSettings.allowReferrer ?? false,
26813
+ links: ruleSettings.links ?? true,
26814
+ forms: ruleSettings.forms ?? false,
26815
+ linkComponents,
26816
+ formComponents
26817
+ };
26818
+ };
26819
+ const isExternalLink = (href) => href.includes("//");
26820
+ const matchHrefExpression = (expression, state) => {
26821
+ if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") {
26822
+ if (isExternalLink(expression.value)) state.isExternal = true;
26823
+ return;
26824
+ }
26825
+ if (isNodeOfType(expression, "Identifier")) {
26826
+ state.isDynamic = true;
26827
+ return;
26828
+ }
26829
+ if (isNodeOfType(expression, "ConditionalExpression")) {
26830
+ matchHrefExpression(expression.consequent, state);
26831
+ matchHrefExpression(expression.alternate, state);
26832
+ }
26833
+ };
26834
+ const checkHref = (attributeValue, enforceDynamicLinks) => {
26835
+ const state = {
26836
+ isExternal: false,
26837
+ isDynamic: false
26838
+ };
26839
+ if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") state.isExternal = isExternalLink(attributeValue.value);
26840
+ else if (isNodeOfType(attributeValue, "JSXExpressionContainer")) matchHrefExpression(attributeValue.expression, state);
26841
+ if (enforceDynamicLinks === "never") return !state.isExternal || state.isDynamic;
26842
+ return !(state.isExternal || state.isDynamic);
26843
+ };
26844
+ const checkRelValue = (text, allowReferrer) => {
26845
+ const tokens = text.split(/\s+/);
26846
+ if (allowReferrer) return tokens.includes("noopener") || tokens.includes("noreferrer");
26847
+ return tokens.some((token) => token.toLowerCase() === "noreferrer");
26848
+ };
26849
+ const matchRelExpression = (expression, allowReferrer) => {
26850
+ const empty = {
26851
+ combined: false,
26852
+ testName: "",
26853
+ consequent: false,
26854
+ alternate: false
26855
+ };
26856
+ if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
26857
+ combined: checkRelValue(expression.value, allowReferrer),
26858
+ testName: "",
26859
+ consequent: false,
26860
+ alternate: false
26861
+ };
26862
+ if (isNodeOfType(expression, "ConditionalExpression")) {
26863
+ const consequent = matchRelExpression(expression.consequent, allowReferrer);
26864
+ const alternate = matchRelExpression(expression.alternate, allowReferrer);
26865
+ const test = expression.test;
26866
+ if (isNodeOfType(test, "Identifier")) return {
26867
+ combined: consequent.combined && alternate.combined,
26868
+ testName: test.name,
26869
+ consequent: consequent.combined,
26870
+ alternate: alternate.combined
26871
+ };
26872
+ return {
26873
+ combined: consequent.combined && alternate.combined,
26874
+ testName: "",
26875
+ consequent: consequent.combined,
26876
+ alternate: alternate.combined
26877
+ };
26878
+ }
26879
+ return empty;
26880
+ };
26881
+ const checkRel = (attributeValue, allowReferrer) => {
26882
+ const empty = {
26883
+ combined: false,
26884
+ testName: "",
26885
+ consequent: false,
26886
+ alternate: false
26887
+ };
26888
+ if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
26889
+ combined: checkRelValue(attributeValue.value, allowReferrer),
26890
+ testName: "",
26891
+ consequent: false,
26892
+ alternate: false
26893
+ };
26894
+ if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
26895
+ const expression = attributeValue.expression;
26896
+ if (expression.type === "JSXEmptyExpression") return empty;
26897
+ return matchRelExpression(expression, allowReferrer);
26898
+ }
26899
+ return empty;
26900
+ };
26901
+ const matchTargetExpression = (expression) => {
26902
+ const empty = {
26903
+ combined: false,
26904
+ testName: "",
26905
+ consequent: false,
26906
+ alternate: false
26907
+ };
26908
+ if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return {
26909
+ combined: expression.value.toLowerCase() === "_blank",
26910
+ testName: "",
26911
+ consequent: false,
26912
+ alternate: false
26913
+ };
26914
+ if (isNodeOfType(expression, "ConditionalExpression")) {
26915
+ const consequent = matchTargetExpression(expression.consequent);
26916
+ const alternate = matchTargetExpression(expression.alternate);
26917
+ const test = expression.test;
26918
+ const combined = consequent.combined || alternate.combined;
26919
+ if (isNodeOfType(test, "Identifier")) return {
26920
+ combined,
26921
+ testName: test.name,
26922
+ consequent: consequent.combined,
26923
+ alternate: alternate.combined
26924
+ };
26925
+ return {
26926
+ combined,
26927
+ testName: "",
26928
+ consequent: consequent.combined,
26929
+ alternate: alternate.combined
26930
+ };
26931
+ }
26932
+ return empty;
26933
+ };
26934
+ const checkTarget = (attributeValue) => {
26935
+ if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return {
26936
+ combined: attributeValue.value.toLowerCase() === "_blank",
26937
+ testName: "",
26938
+ consequent: false,
26939
+ alternate: false
26940
+ };
26941
+ if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
26942
+ const expression = attributeValue.expression;
26943
+ if (expression.type === "JSXEmptyExpression") return {
26944
+ combined: false,
26945
+ testName: "",
26946
+ consequent: false,
26947
+ alternate: false
26948
+ };
26949
+ return matchTargetExpression(expression);
26950
+ }
26951
+ return {
26952
+ combined: false,
26953
+ testName: "",
26954
+ consequent: false,
26955
+ alternate: false
26956
+ };
26957
+ };
26958
+ const jsxNoTargetBlank = defineRule({
26959
+ id: "jsx-no-target-blank",
26960
+ title: "Unsafe target=_blank link",
26961
+ severity: "warn",
26962
+ recommendation: "Add `rel=\"noreferrer\"` (or `\"noopener\"`) when using `target=\"_blank\"`.",
26963
+ category: "Security",
26964
+ create: (context) => {
26965
+ const settings = resolveSettings$28(context.settings);
26966
+ const isLink = (tagName) => {
26967
+ if (!settings.links) return false;
26968
+ if (tagName === "a") return true;
26969
+ return settings.linkComponents.has(tagName);
26970
+ };
26971
+ const isForm = (tagName) => {
26972
+ if (!settings.forms) return false;
26973
+ if (tagName === "form") return true;
26974
+ return settings.formComponents.has(tagName);
26975
+ };
26976
+ return { JSXOpeningElement(node) {
26977
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
26978
+ const tagName = resolveJsxElementType(node);
26979
+ if (!tagName) return;
26980
+ if (!isLink(tagName) && !isForm(tagName)) return;
26981
+ const linkAttributeNames = settings.linkComponents.get(tagName) ?? ["href"];
26982
+ const formAttributeNames = settings.formComponents.get(tagName) ?? ["action"];
26983
+ let targetTuple = {
26984
+ combined: false,
26985
+ testName: "",
26986
+ consequent: false,
26987
+ alternate: false
26988
+ };
26989
+ let relTuple = {
26990
+ combined: false,
26991
+ testName: "",
26992
+ consequent: false,
26993
+ alternate: false
26994
+ };
26995
+ let isHrefValid = true;
26996
+ let hasHrefValue = false;
26997
+ let warnSpread = false;
26998
+ let targetReportNode = node.name;
26999
+ let spreadReportNode = null;
27000
+ for (const attribute of node.attributes) {
27001
+ if (isNodeOfType(attribute, "JSXSpreadAttribute")) {
27002
+ if (settings.warnOnSpreadAttributes) {
27003
+ warnSpread = true;
27004
+ spreadReportNode = attribute;
27005
+ targetTuple = {
27006
+ combined: false,
27007
+ testName: "",
27008
+ consequent: false,
27009
+ alternate: false
27010
+ };
27011
+ relTuple = {
27012
+ combined: false,
27013
+ testName: "",
27014
+ consequent: false,
27015
+ alternate: false
27016
+ };
27017
+ isHrefValid = false;
27018
+ hasHrefValue = true;
27019
+ }
27020
+ continue;
27021
+ }
27022
+ if (!isNodeOfType(attribute, "JSXAttribute")) continue;
27023
+ const attributeName = attribute.name;
27024
+ if (!isNodeOfType(attributeName, "JSXIdentifier")) continue;
27025
+ const propName = attributeName.name;
27026
+ const value = attribute.value;
27027
+ if (propName === "target") {
27028
+ if (value) {
27029
+ targetTuple = checkTarget(value);
27030
+ targetReportNode = value;
27031
+ }
27032
+ } else if (propName === "href" || propName === "action" || linkAttributeNames.includes(propName) || formAttributeNames.includes(propName)) {
27033
+ if (value) {
27034
+ hasHrefValue = true;
27035
+ isHrefValid = checkHref(value, settings.enforceDynamicLinks);
27036
+ }
27037
+ } else if (propName === "rel" && value) relTuple = checkRel(value, settings.allowReferrer);
27038
+ }
27039
+ if (warnSpread) {
27040
+ if (hasHrefValue && isHrefValid || relTuple.combined) return;
27041
+ context.report({
27042
+ node: spreadReportNode ?? node,
27043
+ message: SPREAD_MESSAGE
27044
+ });
27045
+ return;
27046
+ }
27047
+ if (!isHrefValid) {
27048
+ if (targetTuple.testName !== "" && targetTuple.testName === relTuple.testName) {
27049
+ const consequentBad = targetTuple.consequent && !relTuple.consequent;
27050
+ const alternateBad = targetTuple.alternate && !relTuple.alternate;
27051
+ if (consequentBad || alternateBad) context.report({
27052
+ node: targetReportNode,
27053
+ message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
27054
+ });
27055
+ return;
27056
+ }
27057
+ if (targetTuple.combined && !relTuple.combined) context.report({
27058
+ node: targetReportNode,
27059
+ message: settings.allowReferrer ? NOOPENER_MESSAGE : NOREFERRER_MESSAGE
26871
27060
  });
26872
27061
  }
26873
27062
  } };
@@ -27210,7 +27399,7 @@ const jsxPropsNoSpreadMulti = defineRule({
27210
27399
  });
27211
27400
  //#endregion
27212
27401
  //#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
27213
- const MESSAGE$43 = "You can't tell what props reach this element when you spread them.";
27402
+ const MESSAGE$42 = "You can't tell what props reach this element when you spread them.";
27214
27403
  const resolveSettings$25 = (settings) => {
27215
27404
  const reactDoctor = settings?.["react-doctor"];
27216
27405
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
@@ -27253,7 +27442,7 @@ const jsxPropsNoSpreading = defineRule({
27253
27442
  }
27254
27443
  context.report({
27255
27444
  node: attribute,
27256
- message: MESSAGE$43
27445
+ message: MESSAGE$42
27257
27446
  });
27258
27447
  didReportInFile = true;
27259
27448
  return;
@@ -27529,7 +27718,7 @@ const labelHasAssociatedControl = defineRule({
27529
27718
  });
27530
27719
  //#endregion
27531
27720
  //#region src/plugin/rules/a11y/lang.ts
27532
- const MESSAGE$42 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
27721
+ const MESSAGE$41 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
27533
27722
  const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
27534
27723
  "aa",
27535
27724
  "ab",
@@ -28009,7 +28198,7 @@ const lang = defineRule({
28009
28198
  if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
28010
28199
  context.report({
28011
28200
  node: langAttr,
28012
- message: MESSAGE$42
28201
+ message: MESSAGE$41
28013
28202
  });
28014
28203
  return;
28015
28204
  }
@@ -28018,7 +28207,7 @@ const lang = defineRule({
28018
28207
  if (value === null) return;
28019
28208
  if (!isValidLangTag(value)) context.report({
28020
28209
  node: langAttr,
28021
- message: MESSAGE$42
28210
+ message: MESSAGE$41
28022
28211
  });
28023
28212
  } })
28024
28213
  });
@@ -28063,13 +28252,13 @@ const mdxSsrExecutionRisk = defineRule({
28063
28252
  recommendation: "Use a constrained compiler for untrusted content, disable expressions/raw HTML, sandbox renderers, and avoid caching attacker-controlled output across tenants.",
28064
28253
  scan: scanByPattern({
28065
28254
  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,
28255
+ 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
28256
  message: "MDX/markdown rendering code may evaluate user or repository content during SSR or static generation."
28068
28257
  })
28069
28258
  });
28070
28259
  //#endregion
28071
28260
  //#region src/plugin/rules/a11y/media-has-caption.ts
28072
- const MESSAGE$41 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
28261
+ const MESSAGE$40 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
28073
28262
  const DEFAULT_AUDIO = ["audio"];
28074
28263
  const DEFAULT_VIDEO = ["video"];
28075
28264
  const DEFAULT_TRACK = ["track"];
@@ -28159,7 +28348,7 @@ const mediaHasCaption = defineRule({
28159
28348
  if (!parent || !isNodeOfType(parent, "JSXElement")) {
28160
28349
  context.report({
28161
28350
  node: node.name,
28162
- message: MESSAGE$41
28351
+ message: MESSAGE$40
28163
28352
  });
28164
28353
  return;
28165
28354
  }
@@ -28178,7 +28367,7 @@ const mediaHasCaption = defineRule({
28178
28367
  return kindValue.value.toLowerCase() === "captions";
28179
28368
  })) context.report({
28180
28369
  node: node.name,
28181
- message: MESSAGE$41
28370
+ message: MESSAGE$40
28182
28371
  });
28183
28372
  } };
28184
28373
  }
@@ -28531,30 +28720,6 @@ const hasAncestorLayoutMatching = (pageFilename, matchesLayout) => {
28531
28720
  const hasAncestorMetadataLayout = (pageFilename) => hasAncestorLayoutMatching(pageFilename, (layoutPath) => METADATA_EXPORT_NAMES.some((exportName) => doesModuleExportName(layoutPath, exportName)));
28532
28721
  //#endregion
28533
28722
  //#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
28723
  const nextjsMissingMetadata = defineRule({
28559
28724
  id: "nextjs-missing-metadata",
28560
28725
  title: "Page missing metadata for search previews",
@@ -28574,7 +28739,6 @@ const nextjsMissingMetadata = defineRule({
28574
28739
  if (isNodeOfType(declaration, "FunctionDeclaration")) return declaration.id?.name === "generateMetadata";
28575
28740
  return (statement.specifiers ?? []).some((specifier) => isNodeOfType(specifier, "ExportSpecifier") && isNodeOfType(specifier.exported, "Identifier") && METADATA_EXPORT_NAMES.includes(specifier.exported.name));
28576
28741
  })) return;
28577
- if (isRedirectOnlyDefaultExport(programNode, context)) return;
28578
28742
  if (hasAncestorMetadataLayout(context.filename ?? "")) return;
28579
28743
  context.report({
28580
28744
  node: programNode,
@@ -30231,7 +30395,7 @@ const nextjsNoVercelOgImport = defineRule({
30231
30395
  });
30232
30396
  //#endregion
30233
30397
  //#region src/plugin/rules/a11y/no-access-key.ts
30234
- const MESSAGE$40 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
30398
+ const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
30235
30399
  const isUndefinedIdentifier$1 = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
30236
30400
  const noAccessKey = defineRule({
30237
30401
  id: "no-access-key",
@@ -30250,7 +30414,7 @@ const noAccessKey = defineRule({
30250
30414
  if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
30251
30415
  context.report({
30252
30416
  node: accessKey,
30253
- message: MESSAGE$40
30417
+ message: MESSAGE$39
30254
30418
  });
30255
30419
  return;
30256
30420
  }
@@ -30260,7 +30424,7 @@ const noAccessKey = defineRule({
30260
30424
  if (isUndefinedIdentifier$1(expression)) return;
30261
30425
  context.report({
30262
30426
  node: accessKey,
30263
- message: MESSAGE$40
30427
+ message: MESSAGE$39
30264
30428
  });
30265
30429
  }
30266
30430
  } };
@@ -32603,7 +32767,7 @@ const noAdjustStateOnPropChange = defineRule({
32603
32767
  });
32604
32768
  //#endregion
32605
32769
  //#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
32606
- const MESSAGE$39 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
32770
+ const MESSAGE$38 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
32607
32771
  const ALWAYS_FOCUSABLE_TAGS = new Set([
32608
32772
  "button",
32609
32773
  "embed",
@@ -32715,7 +32879,7 @@ const noAriaHiddenOnFocusable = defineRule({
32715
32879
  const isImplicitlyFocusable = isNativelyFocusable(tag, node);
32716
32880
  if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
32717
32881
  node: ariaHidden,
32718
- message: MESSAGE$39
32882
+ message: MESSAGE$38
32719
32883
  });
32720
32884
  } })
32721
32885
  });
@@ -33762,7 +33926,7 @@ const isAllLiteralArrayExpression = (node) => {
33762
33926
  };
33763
33927
  //#endregion
33764
33928
  //#region src/plugin/rules/react-builtins/no-array-index-key.ts
33765
- const MESSAGE$38 = "Your users can see & submit the wrong data when this list reorders.";
33929
+ const MESSAGE$37 = "Your users can see & submit the wrong data when this list reorders.";
33766
33930
  const SECOND_INDEX_METHODS = new Set([
33767
33931
  "every",
33768
33932
  "filter",
@@ -33881,14 +34045,14 @@ const noArrayIndexKey = defineRule({
33881
34045
  if (propName !== "key") continue;
33882
34046
  if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
33883
34047
  node: property,
33884
- message: MESSAGE$38
34048
+ message: MESSAGE$37
33885
34049
  });
33886
34050
  }
33887
34051
  } })
33888
34052
  });
33889
34053
  //#endregion
33890
34054
  //#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
33891
- const MESSAGE$37 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
34055
+ const MESSAGE$36 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
33892
34056
  const noAsyncEffectCallback = defineRule({
33893
34057
  id: "no-async-effect-callback",
33894
34058
  title: "Async effect callback",
@@ -33906,13 +34070,13 @@ const noAsyncEffectCallback = defineRule({
33906
34070
  if (!callback.async) return;
33907
34071
  context.report({
33908
34072
  node: callback,
33909
- message: MESSAGE$37
34073
+ message: MESSAGE$36
33910
34074
  });
33911
34075
  } })
33912
34076
  });
33913
34077
  //#endregion
33914
34078
  //#region src/plugin/rules/a11y/no-autofocus.ts
33915
- const MESSAGE$36 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
34079
+ const MESSAGE$35 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
33916
34080
  const resolveSettings$21 = (settings) => {
33917
34081
  const reactDoctor = settings?.["react-doctor"];
33918
34082
  return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
@@ -34009,7 +34173,7 @@ const noAutofocus = defineRule({
34009
34173
  if (isConditionallyRendered(node)) return;
34010
34174
  context.report({
34011
34175
  node: autoFocusAttribute,
34012
- message: MESSAGE$36
34176
+ message: MESSAGE$35
34013
34177
  });
34014
34178
  } };
34015
34179
  }
@@ -35338,7 +35502,7 @@ const noChainStateUpdates = defineRule({
35338
35502
  });
35339
35503
  //#endregion
35340
35504
  //#region src/plugin/rules/react-builtins/no-children-prop.ts
35341
- const MESSAGE$35 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
35505
+ const MESSAGE$34 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
35342
35506
  const noChildrenProp = defineRule({
35343
35507
  id: "no-children-prop",
35344
35508
  title: "Children passed as a prop",
@@ -35350,7 +35514,7 @@ const noChildrenProp = defineRule({
35350
35514
  if (node.name.name !== "children") return;
35351
35515
  context.report({
35352
35516
  node: node.name,
35353
- message: MESSAGE$35
35517
+ message: MESSAGE$34
35354
35518
  });
35355
35519
  },
35356
35520
  CallExpression(node) {
@@ -35363,7 +35527,7 @@ const noChildrenProp = defineRule({
35363
35527
  const propertyKey = property.key;
35364
35528
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
35365
35529
  node: propertyKey,
35366
- message: MESSAGE$35
35530
+ message: MESSAGE$34
35367
35531
  });
35368
35532
  }
35369
35533
  }
@@ -35371,7 +35535,7 @@ const noChildrenProp = defineRule({
35371
35535
  });
35372
35536
  //#endregion
35373
35537
  //#region src/plugin/rules/react-builtins/no-clone-element.ts
35374
- const MESSAGE$34 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
35538
+ const MESSAGE$33 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
35375
35539
  const noCloneElement = defineRule({
35376
35540
  id: "no-clone-element",
35377
35541
  title: "cloneElement makes child props fragile",
@@ -35384,7 +35548,7 @@ const noCloneElement = defineRule({
35384
35548
  if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
35385
35549
  if (isImportedFromModule(node, "cloneElement", "react")) context.report({
35386
35550
  node: callee,
35387
- message: MESSAGE$34
35551
+ message: MESSAGE$33
35388
35552
  });
35389
35553
  return;
35390
35554
  }
@@ -35397,7 +35561,7 @@ const noCloneElement = defineRule({
35397
35561
  if (!isImportedFromModule(node, callee.object.name, "react")) return;
35398
35562
  context.report({
35399
35563
  node: callee,
35400
- message: MESSAGE$34
35564
+ message: MESSAGE$33
35401
35565
  });
35402
35566
  }
35403
35567
  } })
@@ -35417,7 +35581,7 @@ const getCallMethodName = (callee) => {
35417
35581
  };
35418
35582
  //#endregion
35419
35583
  //#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
35420
- const MESSAGE$33 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
35584
+ const MESSAGE$32 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
35421
35585
  const CONTEXT_MODULES = [
35422
35586
  "react",
35423
35587
  "use-context-selector",
@@ -35465,7 +35629,7 @@ const noCreateContextInRender = defineRule({
35465
35629
  if (!componentOrHookName) return;
35466
35630
  context.report({
35467
35631
  node,
35468
- message: `${MESSAGE$33} (called inside "${componentOrHookName}")`
35632
+ message: `${MESSAGE$32} (called inside "${componentOrHookName}")`
35469
35633
  });
35470
35634
  } })
35471
35635
  });
@@ -36212,7 +36376,7 @@ const isProvenOneShotTestingLibraryComponent = (functionNode, filename, scopes)
36212
36376
  };
36213
36377
  //#endregion
36214
36378
  //#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
36215
- const MESSAGE$32 = "`createRef()` may escape or be observed beyond the render that created it, so a later render can replace the ref object and detach the observed one. Hoist a `useRef()` call to the component's unconditional top level instead.";
36379
+ const MESSAGE$31 = "`createRef()` may escape or be observed beyond the render that created it, so a later render can replace the ref object and detach the observed one. Hoist a `useRef()` call to the component's unconditional top level instead.";
36216
36380
  const isUseMemoCallbackArgument = (functionNode, scopes) => {
36217
36381
  const parent = functionNode.parent;
36218
36382
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
@@ -36266,7 +36430,7 @@ const noCreateRefInFunctionComponent = defineRule({
36266
36430
  if (isCreateRefResultWriteOnly(node, context.filename, context.scopes)) return;
36267
36431
  context.report({
36268
36432
  node,
36269
- message: MESSAGE$32
36433
+ message: MESSAGE$31
36270
36434
  });
36271
36435
  } })
36272
36436
  });
@@ -36406,7 +36570,7 @@ const noCreateStoreInRender = defineRule({
36406
36570
  });
36407
36571
  //#endregion
36408
36572
  //#region src/plugin/rules/react-builtins/no-danger.ts
36409
- const MESSAGE$31 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
36573
+ const MESSAGE$30 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
36410
36574
  const noDanger = defineRule({
36411
36575
  id: "no-danger",
36412
36576
  title: "Raw HTML injection can run unsafe markup",
@@ -36420,7 +36584,7 @@ const noDanger = defineRule({
36420
36584
  if (!propAttribute) return;
36421
36585
  context.report({
36422
36586
  node: propAttribute.name,
36423
- message: MESSAGE$31
36587
+ message: MESSAGE$30
36424
36588
  });
36425
36589
  },
36426
36590
  CallExpression(node) {
@@ -36432,7 +36596,7 @@ const noDanger = defineRule({
36432
36596
  const propertyKey = property.key;
36433
36597
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
36434
36598
  node: propertyKey,
36435
- message: MESSAGE$31
36599
+ message: MESSAGE$30
36436
36600
  });
36437
36601
  }
36438
36602
  }
@@ -36455,7 +36619,7 @@ const isMeaningfulJsxChild = (child) => {
36455
36619
  };
36456
36620
  //#endregion
36457
36621
  //#region src/plugin/rules/react-builtins/no-danger-with-children.ts
36458
- const MESSAGE$30 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
36622
+ const MESSAGE$29 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
36459
36623
  const mergePropsShape = (target, source) => {
36460
36624
  target.hasDangerously ||= source.hasDangerously;
36461
36625
  target.hasChildren ||= source.hasChildren;
@@ -36530,7 +36694,7 @@ const noDangerWithChildren = defineRule({
36530
36694
  if (!hasChildrenProp && !hasNestedChildren) return;
36531
36695
  if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
36532
36696
  node: opening,
36533
- message: MESSAGE$30
36697
+ message: MESSAGE$29
36534
36698
  });
36535
36699
  },
36536
36700
  CallExpression(node) {
@@ -36543,7 +36707,7 @@ const noDangerWithChildren = defineRule({
36543
36707
  const positionalChildren = node.arguments.slice(2);
36544
36708
  if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
36545
36709
  node,
36546
- message: MESSAGE$30
36710
+ message: MESSAGE$29
36547
36711
  });
36548
36712
  }
36549
36713
  })
@@ -36833,6 +36997,19 @@ const noDefaultProps = defineRule({
36833
36997
  } })
36834
36998
  });
36835
36999
  //#endregion
37000
+ //#region src/plugin/utils/is-component-function.ts
37001
+ const isFunctionAssignedToComponent = (functionNode) => {
37002
+ let cursor = functionNode.parent ?? null;
37003
+ while (isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
37004
+ if (isNodeOfType(cursor, "VariableDeclarator") && isNodeOfType(cursor.id, "Identifier") && isUppercaseName(cursor.id.name)) return true;
37005
+ return isNodeOfType(cursor, "ExportDefaultDeclaration");
37006
+ };
37007
+ const isComponentFunction$1 = (node) => {
37008
+ if (!isFunctionLike$1(node)) return false;
37009
+ if (isNodeOfType(node, "FunctionDeclaration")) return !node.id || node.id.name === "default" || isUppercaseName(node.id.name) || isNodeOfType(node.parent, "ExportDefaultDeclaration");
37010
+ return isFunctionAssignedToComponent(node);
37011
+ };
37012
+ //#endregion
36836
37013
  //#region src/plugin/utils/create-component-prop-stack-tracker.ts
36837
37014
  const extractDestructuredPropNames = (params) => {
36838
37015
  const propNames = /* @__PURE__ */ new Set();
@@ -37518,7 +37695,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
37518
37695
  //#endregion
37519
37696
  //#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
37520
37697
  const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
37521
- const MESSAGE$29 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
37698
+ const MESSAGE$28 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
37522
37699
  const getEnclosingLifecycleFunction = (setStateCall) => {
37523
37700
  let ancestor = setStateCall.parent;
37524
37701
  while (ancestor) {
@@ -37645,7 +37822,7 @@ const noDidMountSetState = defineRule({
37645
37822
  }
37646
37823
  context.report({
37647
37824
  node: node.callee,
37648
- message: MESSAGE$29
37825
+ message: MESSAGE$28
37649
37826
  });
37650
37827
  } };
37651
37828
  }
@@ -37663,7 +37840,7 @@ const findEnclosingClass = (node) => {
37663
37840
  //#endregion
37664
37841
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
37665
37842
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
37666
- const MESSAGE$28 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
37843
+ const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
37667
37844
  const DIFFERENCE_OPERATORS = new Set(["!=", "!=="]);
37668
37845
  const EQUALITY_OPERATORS = new Set([
37669
37846
  "==",
@@ -38057,7 +38234,7 @@ const noDidUpdateSetState = defineRule({
38057
38234
  if (isInsideDiffGuard(node, context.scopes)) return;
38058
38235
  context.report({
38059
38236
  node: node.callee,
38060
- message: MESSAGE$28
38237
+ message: MESSAGE$27
38061
38238
  });
38062
38239
  } };
38063
38240
  }
@@ -38080,7 +38257,7 @@ const isStateMemberExpression = (node) => {
38080
38257
  };
38081
38258
  //#endregion
38082
38259
  //#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
38083
- const MESSAGE$27 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
38260
+ const MESSAGE$26 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
38084
38261
  const shouldIgnoreMutation = (node) => {
38085
38262
  let isConstructor = false;
38086
38263
  let isInsideCallExpression = false;
@@ -38102,7 +38279,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
38102
38279
  if (shouldIgnoreMutation(reportNode)) return;
38103
38280
  context.report({
38104
38281
  node: reportNode,
38105
- message: MESSAGE$27
38282
+ message: MESSAGE$26
38106
38283
  });
38107
38284
  };
38108
38285
  const noDirectMutationState = defineRule({
@@ -38453,7 +38630,7 @@ const noDocumentStartViewTransition = defineRule({
38453
38630
  });
38454
38631
  //#endregion
38455
38632
  //#region src/plugin/rules/js-performance/no-document-write.ts
38456
- const MESSAGE$26 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
38633
+ const MESSAGE$25 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
38457
38634
  const WRITE_METHODS = new Set(["write", "writeln"]);
38458
38635
  const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
38459
38636
  const noDocumentWrite = defineRule({
@@ -38470,7 +38647,7 @@ const noDocumentWrite = defineRule({
38470
38647
  if (methodName === null || !WRITE_METHODS.has(methodName)) return;
38471
38648
  context.report({
38472
38649
  node,
38473
- message: MESSAGE$26
38650
+ message: MESSAGE$25
38474
38651
  });
38475
38652
  } })
38476
38653
  });
@@ -41810,7 +41987,7 @@ const ALLOWED_NAMESPACES = new Set([
41810
41987
  "ReactDOM",
41811
41988
  "ReactDom"
41812
41989
  ]);
41813
- const MESSAGE$25 = "`findDOMNode` crashes your app in React 19 because it was removed.";
41990
+ const MESSAGE$24 = "`findDOMNode` crashes your app in React 19 because it was removed.";
41814
41991
  const noFindDomNode = defineRule({
41815
41992
  id: "no-find-dom-node",
41816
41993
  title: "findDOMNode breaks component encapsulation",
@@ -41821,7 +41998,7 @@ const noFindDomNode = defineRule({
41821
41998
  if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
41822
41999
  if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
41823
42000
  node: callee,
41824
- message: MESSAGE$25
42001
+ message: MESSAGE$24
41825
42002
  });
41826
42003
  return;
41827
42004
  }
@@ -41832,7 +42009,7 @@ const noFindDomNode = defineRule({
41832
42009
  if (callee.property.name !== "findDOMNode") return;
41833
42010
  context.report({
41834
42011
  node: callee.property,
41835
- message: MESSAGE$25
42012
+ message: MESSAGE$24
41836
42013
  });
41837
42014
  }
41838
42015
  } })
@@ -42970,7 +43147,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
42970
43147
  });
42971
43148
  //#endregion
42972
43149
  //#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
42973
- const MESSAGE$24 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
43150
+ const MESSAGE$23 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
42974
43151
  const noImgLazyWithHighFetchpriority = defineRule({
42975
43152
  id: "no-img-lazy-with-high-fetchpriority",
42976
43153
  title: "Lazy image with high fetchPriority",
@@ -42984,7 +43161,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
42984
43161
  if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
42985
43162
  context.report({
42986
43163
  node: node.name,
42987
- message: MESSAGE$24
43164
+ message: MESSAGE$23
42988
43165
  });
42989
43166
  } })
42990
43167
  });
@@ -43173,7 +43350,7 @@ const noImpureStateUpdater = defineRule({
43173
43350
  });
43174
43351
  //#endregion
43175
43352
  //#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
43176
- const MESSAGE$23 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
43353
+ const MESSAGE$22 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
43177
43354
  const REACT_USE_REF_OPTIONS = {
43178
43355
  allowGlobalReactNamespace: false,
43179
43356
  allowUnboundBareCalls: false
@@ -43274,7 +43451,7 @@ const noIndeterminateAttribute = defineRule({
43274
43451
  if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
43275
43452
  context.report({
43276
43453
  node: indeterminateAttribute,
43277
- message: MESSAGE$23
43454
+ message: MESSAGE$22
43278
43455
  });
43279
43456
  },
43280
43457
  CallExpression(node) {
@@ -43283,7 +43460,7 @@ const noIndeterminateAttribute = defineRule({
43283
43460
  if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
43284
43461
  context.report({
43285
43462
  node,
43286
- message: MESSAGE$23
43463
+ message: MESSAGE$22
43287
43464
  });
43288
43465
  }
43289
43466
  };
@@ -43571,7 +43748,7 @@ const noIsMounted = defineRule({
43571
43748
  });
43572
43749
  //#endregion
43573
43750
  //#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
43574
- const MESSAGE$22 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializing: it is slow on large objects and silently drops `undefined`, functions, `Date`/`Map`/`Set`, and cyclic references. Use `structuredClone(x)`.";
43751
+ const MESSAGE$21 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializing: it is slow on large objects and silently drops `undefined`, functions, `Date`/`Map`/`Set`, and cyclic references. Use `structuredClone(x)`.";
43575
43752
  const isJsonMethodCall = (node, method) => {
43576
43753
  if (!isNodeOfType(node, "CallExpression")) return false;
43577
43754
  const callee = node.callee;
@@ -43635,13 +43812,13 @@ const noJsonParseStringifyClone = defineRule({
43635
43812
  if (isCatchParameterRoundTrip(firstArgument)) return;
43636
43813
  context.report({
43637
43814
  node,
43638
- message: MESSAGE$22
43815
+ message: MESSAGE$21
43639
43816
  });
43640
43817
  } })
43641
43818
  });
43642
43819
  //#endregion
43643
43820
  //#region src/plugin/rules/correctness/no-jsx-element-type.ts
43644
- const MESSAGE$21 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
43821
+ const MESSAGE$20 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
43645
43822
  const isJsxElementTypeReference = (node) => {
43646
43823
  if (!isNodeOfType(node, "TSTypeReference")) return false;
43647
43824
  const typeName = node.typeName;
@@ -43693,7 +43870,7 @@ const noJsxElementType = defineRule({
43693
43870
  if (isJsxImported) return;
43694
43871
  for (const typeAnnotation of flaggedAnnotations) context.report({
43695
43872
  node: typeAnnotation,
43696
- message: MESSAGE$21
43873
+ message: MESSAGE$20
43697
43874
  });
43698
43875
  }
43699
43876
  };
@@ -44974,7 +45151,7 @@ const noMoment = defineRule({
44974
45151
  });
44975
45152
  //#endregion
44976
45153
  //#region src/plugin/rules/react-builtins/no-multi-comp.ts
44977
- const MESSAGE$20 = "This file declares several components, so each component is harder to find, test, and change.";
45154
+ const MESSAGE$19 = "This file declares several components, so each component is harder to find, test, and change.";
44978
45155
  const resolveSettings$16 = (settings) => {
44979
45156
  const reactDoctor = settings?.["react-doctor"];
44980
45157
  return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
@@ -45326,7 +45503,7 @@ const noMultiComp = defineRule({
45326
45503
  if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
45327
45504
  for (const component of flagged.slice(1)) context.report({
45328
45505
  node: component.reportNode,
45329
- message: MESSAGE$20
45506
+ message: MESSAGE$19
45330
45507
  });
45331
45508
  } };
45332
45509
  }
@@ -45539,7 +45716,7 @@ const resolveReducerFunction = (node, currentFilename) => {
45539
45716
  };
45540
45717
  //#endregion
45541
45718
  //#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
45542
- const MESSAGE$19 = "This reducer changes state in place, so your update is silently skipped.";
45719
+ const MESSAGE$18 = "This reducer changes state in place, so your update is silently skipped.";
45543
45720
  const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
45544
45721
  "copyWithin",
45545
45722
  "fill",
@@ -45743,7 +45920,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
45743
45920
  reportedNodes.add(options.crossFileConsumerCallSite);
45744
45921
  context.report({
45745
45922
  node: options.crossFileConsumerCallSite,
45746
- message: `${MESSAGE$19} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
45923
+ message: `${MESSAGE$18} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
45747
45924
  });
45748
45925
  return;
45749
45926
  }
@@ -45752,7 +45929,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
45752
45929
  reportedNodes.add(mutation.node);
45753
45930
  context.report({
45754
45931
  node: mutation.node,
45755
- message: MESSAGE$19
45932
+ message: MESSAGE$18
45756
45933
  });
45757
45934
  }
45758
45935
  };
@@ -46159,7 +46336,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
46159
46336
  });
46160
46337
  //#endregion
46161
46338
  //#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
46162
- const MESSAGE$18 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
46339
+ const MESSAGE$17 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
46163
46340
  const KEYBOARD_HANDLER_PROP_NAMES = [
46164
46341
  "onKeyDown",
46165
46342
  "onKeyUp",
@@ -46278,7 +46455,7 @@ const noNoninteractiveTabindex = defineRule({
46278
46455
  if (numeric === null) {
46279
46456
  if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues && !isKeyboardOperable(node) && !isFocusOperable(node) && !hasJsxSpreadAttribute$1(node.attributes)) context.report({
46280
46457
  node: tabIndex,
46281
- message: MESSAGE$18
46458
+ message: MESSAGE$17
46282
46459
  });
46283
46460
  return;
46284
46461
  }
@@ -46300,7 +46477,7 @@ const noNoninteractiveTabindex = defineRule({
46300
46477
  if (!roleAttribute) {
46301
46478
  context.report({
46302
46479
  node: tabIndex,
46303
- message: MESSAGE$18
46480
+ message: MESSAGE$17
46304
46481
  });
46305
46482
  return;
46306
46483
  }
@@ -46314,7 +46491,7 @@ const noNoninteractiveTabindex = defineRule({
46314
46491
  }
46315
46492
  context.report({
46316
46493
  node: tabIndex,
46317
- message: MESSAGE$18
46494
+ message: MESSAGE$17
46318
46495
  });
46319
46496
  } };
46320
46497
  }
@@ -48520,7 +48697,7 @@ const noRandomKey = defineRule({
48520
48697
  });
48521
48698
  //#endregion
48522
48699
  //#region src/plugin/rules/react-builtins/no-react-children.ts
48523
- const MESSAGE$17 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
48700
+ const MESSAGE$16 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
48524
48701
  const isChildrenIdentifier = (node, contextNode) => {
48525
48702
  if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
48526
48703
  return isImportedFromModule(contextNode, "Children", "react");
@@ -48546,13 +48723,13 @@ const noReactChildren = defineRule({
48546
48723
  if (isChildrenIdentifier(memberObject, node)) {
48547
48724
  context.report({
48548
48725
  node: calleeOuter,
48549
- message: MESSAGE$17
48726
+ message: MESSAGE$16
48550
48727
  });
48551
48728
  return;
48552
48729
  }
48553
48730
  if (isReactNamespaceMember(memberObject, node)) context.report({
48554
48731
  node: calleeOuter,
48555
- message: MESSAGE$17
48732
+ message: MESSAGE$16
48556
48733
  });
48557
48734
  } })
48558
48735
  });
@@ -49434,7 +49611,7 @@ const noRenderPropChildren = defineRule({
49434
49611
  });
49435
49612
  //#endregion
49436
49613
  //#region src/plugin/rules/react-builtins/no-render-return-value.ts
49437
- const MESSAGE$16 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
49614
+ const MESSAGE$15 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
49438
49615
  const isReactDomRenderCall = (node) => isGlobalMethodCall(node, "ReactDOM", "render");
49439
49616
  const isUsedAsReturnValue = (parent) => {
49440
49617
  if (!parent) return false;
@@ -49452,7 +49629,7 @@ const noRenderReturnValue = defineRule({
49452
49629
  if (!isUsedAsReturnValue(node.parent)) return;
49453
49630
  context.report({
49454
49631
  node: node.callee,
49455
- message: MESSAGE$16
49632
+ message: MESSAGE$15
49456
49633
  });
49457
49634
  } })
49458
49635
  });
@@ -50879,7 +51056,7 @@ const noSelfUpdatingEffect = defineRule({
50879
51056
  });
50880
51057
  //#endregion
50881
51058
  //#region src/plugin/rules/react-builtins/no-set-state.ts
50882
- const MESSAGE$15 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
51059
+ const MESSAGE$14 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
50883
51060
  const noSetState = defineRule({
50884
51061
  id: "no-set-state",
50885
51062
  title: "Local class state forbidden",
@@ -50894,7 +51071,7 @@ const noSetState = defineRule({
50894
51071
  if (!getParentComponent(node)) return;
50895
51072
  context.report({
50896
51073
  node: node.callee,
50897
- message: MESSAGE$15
51074
+ message: MESSAGE$14
50898
51075
  });
50899
51076
  } })
50900
51077
  });
@@ -51264,7 +51441,7 @@ const isAbstractRole = (openingElement, settings) => {
51264
51441
  };
51265
51442
  //#endregion
51266
51443
  //#region src/plugin/rules/a11y/no-static-element-interactions.ts
51267
- const MESSAGE$14 = "Screen reader users can't tell this click handler is interactive because it has no `role`, so add a `role` or use a button or link.";
51444
+ const MESSAGE$13 = "Screen reader users can't tell this click handler is interactive because it has no `role`, so add a `role` or use a button or link.";
51268
51445
  const DEFAULT_HANDLERS = [
51269
51446
  "onClick",
51270
51447
  "onMouseDown",
@@ -51379,7 +51556,7 @@ const noStaticElementInteractions = defineRule({
51379
51556
  if (!roleAttribute || !roleAttribute.value) {
51380
51557
  context.report({
51381
51558
  node: node.name,
51382
- message: MESSAGE$14
51559
+ message: MESSAGE$13
51383
51560
  });
51384
51561
  return;
51385
51562
  }
@@ -51389,7 +51566,7 @@ const noStaticElementInteractions = defineRule({
51389
51566
  if (isRecognizedRoleString(attributeValue.value)) return;
51390
51567
  context.report({
51391
51568
  node: node.name,
51392
- message: MESSAGE$14
51569
+ message: MESSAGE$13
51393
51570
  });
51394
51571
  return;
51395
51572
  }
@@ -51397,7 +51574,7 @@ const noStaticElementInteractions = defineRule({
51397
51574
  if (!settings.allowExpressionValues) {
51398
51575
  context.report({
51399
51576
  node: node.name,
51400
- message: MESSAGE$14
51577
+ message: MESSAGE$13
51401
51578
  });
51402
51579
  return;
51403
51580
  }
@@ -51405,7 +51582,7 @@ const noStaticElementInteractions = defineRule({
51405
51582
  if (isStaticNullishExpression(expression)) {
51406
51583
  context.report({
51407
51584
  node: node.name,
51408
- message: MESSAGE$14
51585
+ message: MESSAGE$13
51409
51586
  });
51410
51587
  return;
51411
51588
  }
@@ -51415,7 +51592,7 @@ const noStaticElementInteractions = defineRule({
51415
51592
  if (branches.some((branch) => branch.role !== null && isRecognizedRoleString(branch.role))) return;
51416
51593
  context.report({
51417
51594
  node: node.name,
51418
- message: MESSAGE$14
51595
+ message: MESSAGE$13
51419
51596
  });
51420
51597
  return;
51421
51598
  }
@@ -51423,7 +51600,7 @@ const noStaticElementInteractions = defineRule({
51423
51600
  }
51424
51601
  context.report({
51425
51602
  node: node.name,
51426
- message: MESSAGE$14
51603
+ message: MESSAGE$13
51427
51604
  });
51428
51605
  } };
51429
51606
  }
@@ -51529,7 +51706,7 @@ const noStringRefs = defineRule({
51529
51706
  });
51530
51707
  //#endregion
51531
51708
  //#region src/plugin/rules/js-performance/no-sync-xhr.ts
51532
- const MESSAGE$13 = "A synchronous `XMLHttpRequest` (`.open(method, url, false)`) freezes the main thread until the request finishes, blocking all rendering and input. Use `fetch()` or an async XHR (`open(method, url, true)`).";
51709
+ const MESSAGE$12 = "A synchronous `XMLHttpRequest` (`.open(method, url, false)`) freezes the main thread until the request finishes, blocking all rendering and input. Use `fetch()` or an async XHR (`open(method, url, true)`).";
51533
51710
  const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
51534
51711
  const PUBLIC_ASSET_PATH_PATTERN = /(?:^|\/)public\//i;
51535
51712
  const noSyncXhr = defineRule({
@@ -51550,7 +51727,7 @@ const noSyncXhr = defineRule({
51550
51727
  if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
51551
51728
  context.report({
51552
51729
  node,
51553
- message: MESSAGE$13
51730
+ message: MESSAGE$12
51554
51731
  });
51555
51732
  };
51556
51733
  return {
@@ -51575,7 +51752,7 @@ const noSyncXhr = defineRule({
51575
51752
  });
51576
51753
  //#endregion
51577
51754
  //#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
51578
- const MESSAGE$12 = "This value is `undefined` because function components have no `this`.";
51755
+ const MESSAGE$11 = "This value is `undefined` because function components have no `this`.";
51579
51756
  const isInsideClassMethod = (node, customClassFactoryNames) => {
51580
51757
  let ancestor = node.parent;
51581
51758
  while (ancestor) {
@@ -51663,7 +51840,7 @@ const noThisInSfc = defineRule({
51663
51840
  if (functionHasOwnThisMemberWrite(enclosingFunction)) return;
51664
51841
  context.report({
51665
51842
  node,
51666
- message: MESSAGE$12
51843
+ message: MESSAGE$11
51667
51844
  });
51668
51845
  } };
51669
51846
  }
@@ -54033,7 +54210,7 @@ const noWideLetterSpacing = defineRule({
54033
54210
  //#endregion
54034
54211
  //#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
54035
54212
  const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
54036
- const MESSAGE$11 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
54213
+ const MESSAGE$10 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
54037
54214
  const resolveSettings$7 = (settings) => {
54038
54215
  const reactDoctor = settings?.["react-doctor"];
54039
54216
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
@@ -54067,7 +54244,7 @@ const noWillUpdateSetState = defineRule({
54067
54244
  if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
54068
54245
  context.report({
54069
54246
  node: node.callee,
54070
- message: MESSAGE$11
54247
+ message: MESSAGE$10
54071
54248
  });
54072
54249
  } };
54073
54250
  }
@@ -55867,7 +56044,7 @@ const preactNoRenderArguments = defineRule({
55867
56044
  });
55868
56045
  //#endregion
55869
56046
  //#region src/plugin/rules/preact/preact-prefer-ondblclick.ts
55870
- const MESSAGE$10 = "Your users get no response from `onDoubleClick` in Preact core, where it never fires, so use `onDblClick` instead, which matches the DOM event name.";
56047
+ const MESSAGE$9 = "Your users get no response from `onDoubleClick` in Preact core, where it never fires, so use `onDblClick` instead, which matches the DOM event name.";
55871
56048
  const preactPreferOndblclick = defineRule({
55872
56049
  id: "preact-prefer-ondblclick",
55873
56050
  title: "onDoubleClick instead of onDblClick",
@@ -55882,7 +56059,7 @@ const preactPreferOndblclick = defineRule({
55882
56059
  if (!onDoubleClickAttribute) return;
55883
56060
  context.report({
55884
56061
  node: onDoubleClickAttribute,
55885
- message: MESSAGE$10
56062
+ message: MESSAGE$9
55886
56063
  });
55887
56064
  } })
55888
56065
  });
@@ -56122,7 +56299,7 @@ const preferExplicitVariants = defineRule({
56122
56299
  });
56123
56300
  //#endregion
56124
56301
  //#region src/plugin/rules/react-builtins/prefer-function-component.ts
56125
- const MESSAGE$9 = "This class component keeps behavior in lifecycle methods, so state and effects are harder to follow than in a hook-based function component.";
56302
+ const MESSAGE$8 = "This class component keeps behavior in lifecycle methods, so state and effects are harder to follow than in a hook-based function component.";
56126
56303
  const resolveSettings$4 = (settings) => {
56127
56304
  const reactDoctor = settings?.["react-doctor"];
56128
56305
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.preferFunctionComponent ?? {} : {};
@@ -56161,7 +56338,7 @@ const preferFunctionComponent = defineRule({
56161
56338
  const reportNode = node.id ?? node;
56162
56339
  context.report({
56163
56340
  node: reportNode,
56164
- message: MESSAGE$9
56341
+ message: MESSAGE$8
56165
56342
  });
56166
56343
  };
56167
56344
  return {
@@ -57482,8 +57659,7 @@ const preferUseReducer = defineRule({
57482
57659
  //#endregion
57483
57660
  //#region src/plugin/rules/security-scan/utils/is-public-debug-artifact-path.ts
57484
57661
  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);
57662
+ 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
57663
  //#endregion
57488
57664
  //#region src/plugin/rules/security-scan/public-debug-artifact.ts
57489
57665
  const publicDebugArtifact = defineRule({
@@ -58362,7 +58538,7 @@ const reactCompilerNoManualMemoization = defineRule({
58362
58538
  });
58363
58539
  //#endregion
58364
58540
  //#region src/plugin/rules/react-builtins/react-in-jsx-scope.ts
58365
- const MESSAGE$8 = "This JSX crashes because `React` isn't in scope.";
58541
+ const MESSAGE$7 = "This JSX crashes because `React` isn't in scope.";
58366
58542
  const reactInJsxScope = defineRule({
58367
58543
  id: "react-in-jsx-scope",
58368
58544
  title: "React not in scope for JSX",
@@ -58374,21 +58550,21 @@ const reactInJsxScope = defineRule({
58374
58550
  if (findVariableInitializer(node, "React")) return;
58375
58551
  context.report({
58376
58552
  node: node.name,
58377
- message: MESSAGE$8
58553
+ message: MESSAGE$7
58378
58554
  });
58379
58555
  },
58380
58556
  JSXFragment(node) {
58381
58557
  if (findVariableInitializer(node, "React")) return;
58382
58558
  context.report({
58383
58559
  node: node.openingFragment,
58384
- message: MESSAGE$8
58560
+ message: MESSAGE$7
58385
58561
  });
58386
58562
  }
58387
58563
  })
58388
58564
  });
58389
58565
  //#endregion
58390
58566
  //#region src/plugin/rules/security/react-markdown-unsanitized-raw-html.ts
58391
- const MESSAGE$7 = "React Markdown parses dynamic raw HTML when `rehype-raw` is enabled. Add `rehype-sanitize` to `rehypePlugins` or sanitize the markdown before rendering it.";
58567
+ const MESSAGE$6 = "React Markdown parses dynamic raw HTML when `rehype-raw` is enabled. Add `rehype-sanitize` to `rehypePlugins` or sanitize the markdown before rendering it.";
58392
58568
  const REACT_MARKDOWN_MODULE = "react-markdown";
58393
58569
  const REHYPE_RAW_MODULE = "rehype-raw";
58394
58570
  const REHYPE_SANITIZE_MODULE = "rehype-sanitize";
@@ -58540,7 +58716,7 @@ const reactMarkdownUnsanitizedRawHtml = defineRule({
58540
58716
  if (pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_SANITIZE_MODULE, context.scopes, /* @__PURE__ */ new Set())) || !hasDynamicUnsanitizedChildren(node, context.scopes)) return;
58541
58717
  context.report({
58542
58718
  node,
58543
- message: MESSAGE$7
58719
+ message: MESSAGE$6
58544
58720
  });
58545
58721
  } }))
58546
58722
  });
@@ -58695,7 +58871,7 @@ const reduxUseselectorInlineDerivation = defineRule({
58695
58871
  });
58696
58872
  //#endregion
58697
58873
  //#region src/plugin/rules/state-and-effects/redux-useselector-returns-new-collection.ts
58698
- const MESSAGE$6 = "Your component re-renders on every dispatched action when useSelector returns a new object or array.";
58874
+ const MESSAGE$5 = "Your component re-renders on every dispatched action when useSelector returns a new object or array.";
58699
58875
  const isConciseBodyReturningCollection = (functionNode) => {
58700
58876
  if (!isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression")) return false;
58701
58877
  const rawBody = functionNode.body;
@@ -58730,7 +58906,7 @@ const reduxUseselectorReturnsNewCollection = defineRule({
58730
58906
  if (!isConciseBodyReturningCollection(selectorArgument)) return;
58731
58907
  context.report({
58732
58908
  node,
58733
- message: MESSAGE$6
58909
+ message: MESSAGE$5
58734
58910
  });
58735
58911
  }
58736
58912
  };
@@ -59785,7 +59961,7 @@ const renderingUsetransitionLoading = defineRule({
59785
59961
  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
59962
  //#endregion
59787
59963
  //#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);
59964
+ const isRepositorySecretExamplePath = (relativePath) => /(?:^|\/)\.env\.(?:example|sample|template|dist|defaults?)$|(?:^|\/)[^/]*(?:example|sample|template)[^/]*\.(?:env|json|pem|key)$/i.test(relativePath);
59789
59965
  const repositorySecretFile = defineRule({
59790
59966
  id: "repository-secret-file",
59791
59967
  title: "Secret file checked into repository",
@@ -59857,7 +60033,7 @@ const functionBodyHasReturnWithValue = (functionNode) => {
59857
60033
  //#endregion
59858
60034
  //#region src/plugin/rules/react-builtins/require-render-return.ts
59859
60035
  const RENDER_METHOD_NAME = "render";
59860
- const MESSAGE$5 = "Your users see nothing because this `render` method returns nothing.";
60036
+ const MESSAGE$4 = "Your users see nothing because this `render` method returns nothing.";
59861
60037
  const isStaticRenderKey = (key) => {
59862
60038
  if (isNodeOfType(key, "Identifier")) return key.name === RENDER_METHOD_NAME;
59863
60039
  if (isNodeOfType(key, "Literal")) return key.value === RENDER_METHOD_NAME;
@@ -59917,7 +60093,7 @@ const requireRenderReturn = defineRule({
59917
60093
  if (functionBodyHasReturnWithValue(functionNode)) return;
59918
60094
  context.report({
59919
60095
  node: host.reportNode,
59920
- message: MESSAGE$5
60096
+ message: MESSAGE$4
59921
60097
  });
59922
60098
  };
59923
60099
  return {
@@ -68469,7 +68645,6 @@ const countOwnScopeHookCalls = (functionNode, scopes, settings, countedFunctionN
68469
68645
  };
68470
68646
  const MIN_HOOK_CALLS_FOR_RENDER_SCOPE = 2;
68471
68647
  const RENDER_SCOPE_FACTORY_NAME_PATTERN = /^_?(?:init|create|make|build)(?:[A-Z0-9_]|$)/;
68472
- const HOOK_NAMESPACE_NAME_PATTERN = /Hooks?$/;
68473
68648
  const isLocalNonHookFunctionCallee = (call, scopes, settings, countedFunctionNodes) => {
68474
68649
  const callee = call.callee;
68475
68650
  if (!isNodeOfType(callee, "Identifier")) return false;
@@ -68494,15 +68669,6 @@ const isPackageImportedNonReactHookCallee = (call) => {
68494
68669
  if (PATH_ALIAS_IMPORT_PATTERN.test(importSource)) return false;
68495
68670
  return !isReactEcosystemImportSource(importSource);
68496
68671
  };
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
68672
  const isProjectOwnedMdxComponentsGetter = (call) => {
68507
68673
  const callee = call.callee;
68508
68674
  if (!isNodeOfType(callee, "Identifier") || callee.name !== "useMDXComponents") return false;
@@ -68652,7 +68818,6 @@ const rulesOfHooks = defineRule({
68652
68818
  return;
68653
68819
  }
68654
68820
  if (isInsideClassComponent(node)) {
68655
- if (isPackageImportedNonReactHookCallee(node) || isDefaultImportedClassApiMemberCallee(node)) return;
68656
68821
  context.report({
68657
68822
  node: node.callee,
68658
68823
  message: buildClassComponentMessage(hookName)
@@ -68759,7 +68924,7 @@ const rulesOfHooks = defineRule({
68759
68924
  });
68760
68925
  //#endregion
68761
68926
  //#region src/plugin/rules/a11y/scope.ts
68762
- const MESSAGE$4 = "The `scope` attribute only works on `<th>` cells, so screen readers get no table-header help from it here.";
68927
+ const MESSAGE$3 = "The `scope` attribute only works on `<th>` cells, so screen readers get no table-header help from it here.";
68763
68928
  const scope = defineRule({
68764
68929
  id: "scope",
68765
68930
  title: "scope attribute on non-th element",
@@ -68774,7 +68939,7 @@ const scope = defineRule({
68774
68939
  if (!HTML_TAGS.has(tag)) return;
68775
68940
  if (tag !== "th") context.report({
68776
68941
  node: scopeAttribute,
68777
- message: MESSAGE$4
68942
+ message: MESSAGE$3
68778
68943
  });
68779
68944
  } })
68780
68945
  });
@@ -68785,13 +68950,13 @@ const secretInFallback = defineRule({
68785
68950
  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
68951
  scan: scanByPattern({
68787
68952
  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,
68953
+ 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
68954
  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
68955
  })
68791
68956
  });
68792
68957
  //#endregion
68793
68958
  //#region src/plugin/rules/react-builtins/self-closing-comp.ts
68794
- const MESSAGE$3 = "This tag has no children, so the closing tag adds noise without changing output.";
68959
+ const MESSAGE$2 = "This tag has no children, so the closing tag adds noise without changing output.";
68795
68960
  const resolveSettings$2 = (settings) => {
68796
68961
  const reactDoctor = settings?.["react-doctor"];
68797
68962
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.selfClosingComp ?? {} : {};
@@ -68830,7 +68995,7 @@ const selfClosingComp = defineRule({
68830
68995
  if (isHtmlTag && !settings.html) return;
68831
68996
  context.report({
68832
68997
  node: node.openingElement,
68833
- message: MESSAGE$3
68998
+ message: MESSAGE$2
68834
68999
  });
68835
69000
  } };
68836
69001
  }
@@ -69055,30 +69220,7 @@ const isAuthGuardName = (calleeName) => {
69055
69220
  //#endregion
69056
69221
  //#region src/plugin/utils/is-non-privileged-server-action.ts
69057
69222
  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);
69223
+ 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
69224
  const unwrapExpression = (node) => {
69083
69225
  let current = node;
69084
69226
  while (current) {
@@ -69094,10 +69236,10 @@ const unwrapExpression = (node) => {
69094
69236
  }
69095
69237
  return null;
69096
69238
  };
69097
- const isDataExposingValue = (node, cookieBindingNames) => {
69239
+ const isDataExposingValue = (node) => {
69098
69240
  const value = unwrapExpression(node);
69099
69241
  if (!value) return false;
69100
- if (isKnownNonDataEffectCall(value, cookieBindingNames)) return false;
69242
+ if (isCacheOrNavigationCall(value)) return false;
69101
69243
  return !isLiteralOnlyExpression(value);
69102
69244
  };
69103
69245
  const isLiteralOnlyExpression = (node) => {
@@ -69115,23 +69257,22 @@ const getReturnedOrThrownArgument = (node) => {
69115
69257
  if (isNodeOfType(node, "ThrowStatement")) return node.argument ?? null;
69116
69258
  return null;
69117
69259
  };
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);
69260
+ const isDataExposingReturnOrThrow = (node) => isDataExposingValue(getReturnedOrThrownArgument(node));
69261
+ 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
69262
  const isNonPrivilegedServerAction = (functionNode) => {
69121
69263
  const functionBody = functionNode.body;
69122
69264
  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;
69265
+ if (!isNodeOfType(functionBody, "BlockStatement") && isDataExposingValue(functionBody)) return false;
69125
69266
  let hasNonDataEffectCall = false;
69126
69267
  let hasPrivilegedEffect = false;
69127
69268
  walkAst(functionBody, (child) => {
69128
69269
  if (hasPrivilegedEffect) return false;
69129
69270
  if (child !== functionBody && isFunctionLike$1(child)) return false;
69130
- if (isKnownNonDataEffectCall(child, cookieBindingNames)) {
69271
+ if (isCacheOrNavigationCall(child)) {
69131
69272
  hasNonDataEffectCall = true;
69132
69273
  return;
69133
69274
  }
69134
- if (isPrivilegedEffect(child, cookieBindingNames)) {
69275
+ if (isPrivilegedEffect(child)) {
69135
69276
  hasPrivilegedEffect = true;
69136
69277
  return false;
69137
69278
  }
@@ -69210,34 +69351,6 @@ const getAuthScanRoots = (functionNode) => {
69210
69351
  if (isNodeOfType(bodyNode, "BlockStatement")) return (bodyNode.body ?? []).slice(0, 10);
69211
69352
  return [bodyNode];
69212
69353
  };
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
69354
  const CREDENTIAL_MERGE_TAIL_TOKENS = {
69242
69355
  sign: new Set([
69243
69356
  "in",
@@ -69302,7 +69415,6 @@ const isCredentialEstablishingActionName = (actionName) => {
69302
69415
  const hasPublicNameToken = (actionName) => tokenizeIdentifierWords(actionName).includes("public");
69303
69416
  const inspectServerAction = (candidate, fileHasUseServerDirective, allowedFunctionNames, context) => {
69304
69417
  if (!(fileHasUseServerDirective || hasUseServerDirective(candidate.functionNode))) return;
69305
- if (isComponentLikeServerExport(candidate)) return;
69306
69418
  if (isCredentialEstablishingActionName(candidate.displayName)) return;
69307
69419
  if (hasPublicNameToken(candidate.displayName)) return;
69308
69420
  if (containsAuthCheck(getAuthScanRoots(candidate.functionNode), allowedFunctionNames, GENERIC_AUTH_METHOD_NAMES)) return;
@@ -69340,42 +69452,18 @@ const serverAuthActions = defineRule({
69340
69452
  title: "Unauthenticated server action can be called directly",
69341
69453
  severity: "error",
69342
69454
  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));
69455
+ create: (context) => {
69345
69456
  let fileHasUseServerDirective = false;
69346
- let programNode = null;
69347
- const inspectedFunctions = /* @__PURE__ */ new Set();
69348
69457
  const customAuthFunctionNames = getReactDoctorStringArraySetting(context.settings, "serverAuthFunctionNames");
69349
69458
  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
- };
69459
+ const inspect = (candidate) => inspectServerAction(candidate, fileHasUseServerDirective, allowedFunctionNames, context);
69355
69460
  return {
69356
- Program(currentProgramNode) {
69357
- programNode = currentProgramNode;
69358
- fileHasUseServerDirective = hasDirective(currentProgramNode, "use server");
69461
+ Program(programNode) {
69462
+ fileHasUseServerDirective = hasDirective(programNode, "use server");
69359
69463
  },
69360
69464
  ExportNamedDeclaration(node) {
69361
- if (shouldSkipTestAppSource) return;
69362
69465
  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
- }
69466
+ if (!declaration) return;
69379
69467
  if (isAsyncFunctionLikeNode(declaration)) {
69380
69468
  if (!isNodeOfType(declaration, "FunctionDeclaration")) return;
69381
69469
  inspect({
@@ -69388,23 +69476,11 @@ const serverAuthActions = defineRule({
69388
69476
  if (isNodeOfType(declaration, "VariableDeclaration")) for (const candidate of collectCandidatesFromVariableDeclaration(declaration)) inspect(candidate);
69389
69477
  },
69390
69478
  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;
69479
+ const candidate = getCandidateFromDefaultDeclaration(node);
69404
69480
  if (candidate) inspect(candidate);
69405
69481
  }
69406
69482
  };
69407
- })
69483
+ }
69408
69484
  });
69409
69485
  //#endregion
69410
69486
  //#region src/plugin/rules/server/server-cache-with-object-literal.ts
@@ -69484,7 +69560,6 @@ const serverDedupProps = defineRule({
69484
69560
  for (const attr of node.attributes ?? []) {
69485
69561
  if (!isNodeOfType(attr, "JSXAttribute")) continue;
69486
69562
  if (!isNodeOfType(attr.name, "JSXIdentifier")) continue;
69487
- if (attr.name.name === "key") continue;
69488
69563
  if (!isNodeOfType(attr.value, "JSXExpressionContainer")) continue;
69489
69564
  const expression = attr.value.expression;
69490
69565
  if (!expression) continue;
@@ -70214,7 +70289,7 @@ const collectJsxRuntimeImports = (program) => {
70214
70289
  };
70215
70290
  //#endregion
70216
70291
  //#region src/plugin/rules/react-builtins/style-prop-object.ts
70217
- const MESSAGE$2 = "Your styles don't render because you passed the `style` prop a string instead of an object.";
70292
+ const MESSAGE$1 = "Your styles don't render because you passed the `style` prop a string instead of an object.";
70218
70293
  const resolveSettings = (settings) => {
70219
70294
  const reactDoctor = settings?.["react-doctor"];
70220
70295
  return { allow: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.stylePropObject ?? {} : {}).allow ?? [] };
@@ -70321,7 +70396,7 @@ const stylePropObject = defineRule({
70321
70396
  if (isNodeOfType(value, "Literal") && typeof value.value === "string") {
70322
70397
  context.report({
70323
70398
  node: attribute,
70324
- message: MESSAGE$2
70399
+ message: MESSAGE$1
70325
70400
  });
70326
70401
  return;
70327
70402
  }
@@ -70329,7 +70404,7 @@ const stylePropObject = defineRule({
70329
70404
  const innerExpression = value.expression;
70330
70405
  if (innerExpression && innerExpression.type !== "JSXEmptyExpression" && isInvalidStyleExpression(innerExpression)) context.report({
70331
70406
  node: attribute,
70332
- message: MESSAGE$2
70407
+ message: MESSAGE$1
70333
70408
  });
70334
70409
  }
70335
70410
  }
@@ -70352,7 +70427,7 @@ const stylePropObject = defineRule({
70352
70427
  if (!(isNodeOfType(key, "Identifier") && key.name === "style" || isNodeOfType(key, "Literal") && key.value === "style")) continue;
70353
70428
  if (isInvalidStyleExpression(property.value)) context.report({
70354
70429
  node: property,
70355
- message: MESSAGE$2
70430
+ message: MESSAGE$1
70356
70431
  });
70357
70432
  }
70358
70433
  }
@@ -70688,7 +70763,7 @@ const svgFilterClickjackingRisk = defineRule({
70688
70763
  });
70689
70764
  //#endregion
70690
70765
  //#region src/plugin/rules/a11y/tabindex-no-positive.ts
70691
- const MESSAGE$1 = "Keyboard users get jumped out of the normal order by a positive `tabIndex`, so use `0` or `-1`.";
70766
+ const MESSAGE = "Keyboard users get jumped out of the normal order by a positive `tabIndex`, so use `0` or `-1`.";
70692
70767
  const tabindexNoPositive = defineRule({
70693
70768
  id: "tabindex-no-positive",
70694
70769
  title: "Positive tabindex value",
@@ -70708,7 +70783,7 @@ const tabindexNoPositive = defineRule({
70708
70783
  } else if (tabIndex.value && isNodeOfType(tabIndex.value, "JSXExpressionContainer")) numericValue = parseJsxValue(tabIndex.value);
70709
70784
  if (numericValue !== null && numericValue > 0) context.report({
70710
70785
  node: tabIndex,
70711
- message: MESSAGE$1
70786
+ message: MESSAGE
70712
70787
  });
70713
70788
  } })
70714
70789
  });
@@ -71614,248 +71689,6 @@ const useLazyMotion = defineRule({
71614
71689
  } })
71615
71690
  });
71616
71691
  //#endregion
71617
- //#region src/plugin/rules/valtio/valtio-no-proxy-read-in-render.ts
71618
- const VALTIO_REACT_MODULE_SOURCES = new Set(["valtio", "valtio/react"]);
71619
- const REACT_DEPENDENCY_ARRAY_HOOK_NAMES = new Set([
71620
- "useCallback",
71621
- "useEffect",
71622
- "useInsertionEffect",
71623
- "useLayoutEffect",
71624
- "useMemo"
71625
- ]);
71626
- const MESSAGE = "Read this Valtio value from the useSnapshot result during render. Keep proxy reads for callbacks so render uses the tracked, consistent snapshot.";
71627
- const isValtioImportSymbol = (symbol) => {
71628
- if (symbol.kind !== "import") return false;
71629
- const importDeclaration = symbol.declarationNode.parent;
71630
- return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && VALTIO_REACT_MODULE_SOURCES.has(importDeclaration.source.value));
71631
- };
71632
- const isValtioUseSnapshotCall = (node, scopes) => {
71633
- if (!isNodeOfType(node, "CallExpression")) return false;
71634
- const callee = stripParenExpression(node.callee);
71635
- if (isNodeOfType(callee, "Identifier")) {
71636
- const symbol = resolveConstIdentifierAlias(callee, scopes);
71637
- return Boolean(symbol && isValtioImportSymbol(symbol) && getImportedName(symbol.declarationNode) === "useSnapshot");
71638
- }
71639
- if (!isNodeOfType(callee, "MemberExpression") || getStaticPropertyName(callee) !== "useSnapshot") return false;
71640
- const receiver = stripParenExpression(callee.object);
71641
- if (!isNodeOfType(receiver, "Identifier")) return false;
71642
- const namespaceSymbol = resolveConstIdentifierAlias(receiver, scopes);
71643
- return Boolean(namespaceSymbol && isValtioImportSymbol(namespaceSymbol) && isNodeOfType(namespaceSymbol.declarationNode, "ImportNamespaceSpecifier"));
71644
- };
71645
- const collectPatternBindingIdentifiers = (pattern) => {
71646
- if (isNodeOfType(pattern, "Identifier")) return [pattern];
71647
- if (isNodeOfType(pattern, "AssignmentPattern")) return collectPatternBindingIdentifiers(pattern.left);
71648
- if (isNodeOfType(pattern, "RestElement")) return collectPatternBindingIdentifiers(pattern.argument);
71649
- if (isNodeOfType(pattern, "ArrayPattern")) return pattern.elements.flatMap((element) => element ? collectPatternBindingIdentifiers(element) : []);
71650
- if (isNodeOfType(pattern, "ObjectPattern")) return pattern.properties.flatMap((property) => {
71651
- if (isNodeOfType(property, "Property")) return collectPatternBindingIdentifiers(property.value);
71652
- return isNodeOfType(property, "RestElement") ? collectPatternBindingIdentifiers(property.argument) : [];
71653
- });
71654
- return [];
71655
- };
71656
- const collectAssignmentWriteTargets = (target) => {
71657
- const candidate = stripParenExpression(target);
71658
- if (isNodeOfType(candidate, "AssignmentPattern")) return collectAssignmentWriteTargets(candidate.left);
71659
- if (isNodeOfType(candidate, "RestElement")) return collectAssignmentWriteTargets(candidate.argument);
71660
- if (isNodeOfType(candidate, "ArrayPattern")) return candidate.elements.flatMap((element) => element ? collectAssignmentWriteTargets(element) : []);
71661
- if (isNodeOfType(candidate, "ObjectPattern")) return candidate.properties.flatMap((property) => isNodeOfType(property, "Property") ? collectAssignmentWriteTargets(property.value) : collectAssignmentWriteTargets(property.argument));
71662
- return [candidate];
71663
- };
71664
- const findPatternPathToBinding = (pattern, bindingIdentifier) => {
71665
- if (pattern === bindingIdentifier) return [];
71666
- if (isNodeOfType(pattern, "AssignmentPattern")) return findPatternPathToBinding(pattern.left, bindingIdentifier);
71667
- if (isNodeOfType(pattern, "RestElement")) return null;
71668
- if (isNodeOfType(pattern, "ArrayPattern")) {
71669
- for (const [elementIndex, element] of pattern.elements.entries()) {
71670
- if (!element) continue;
71671
- const nestedPath = findPatternPathToBinding(element, bindingIdentifier);
71672
- if (nestedPath) return [String(elementIndex), ...nestedPath];
71673
- }
71674
- return null;
71675
- }
71676
- if (!isNodeOfType(pattern, "ObjectPattern")) return null;
71677
- for (const property of pattern.properties) {
71678
- if (!isNodeOfType(property, "Property")) continue;
71679
- const nestedPath = findPatternPathToBinding(property.value, bindingIdentifier);
71680
- if (!nestedPath) continue;
71681
- const propertyName = getStaticPropertyKeyName(property);
71682
- return propertyName === null ? null : [propertyName, ...nestedPath];
71683
- }
71684
- return null;
71685
- };
71686
- const resolveProxyPath = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set(), allowedAliasSymbolIds = null, collectedAliasCaptures = null) => {
71687
- const candidate = stripParenExpression(expression);
71688
- if (isNodeOfType(candidate, "MemberExpression")) {
71689
- const receiverPath = resolveProxyPath(candidate.object, scopes, visitedSymbolIds, allowedAliasSymbolIds, collectedAliasCaptures);
71690
- if (!receiverPath) return null;
71691
- return {
71692
- ...receiverPath,
71693
- properties: [...receiverPath.properties, getStaticPropertyName(candidate)]
71694
- };
71695
- }
71696
- if (!isNodeOfType(candidate, "Identifier")) return null;
71697
- const symbol = scopes.symbolFor(candidate);
71698
- if (!symbol) return scopes.isGlobalReference(candidate) ? {
71699
- rootKey: `global:${candidate.name}`,
71700
- properties: []
71701
- } : null;
71702
- if (visitedSymbolIds.has(symbol.id)) return null;
71703
- if (symbol.kind === "const" && symbol.initializer && isNodeOfType(symbol.declarationNode, "VariableDeclarator")) {
71704
- const directAlias = symbol.declarationNode.id === symbol.bindingIdentifier;
71705
- const initializerNode = directAlias ? symbol.initializer : symbol.declarationNode.init;
71706
- if (!initializerNode) return {
71707
- rootKey: `symbol:${symbol.id}`,
71708
- properties: []
71709
- };
71710
- const initializer = stripParenExpression(initializerNode);
71711
- const canResolveDirectAlias = directAlias && (isNodeOfType(initializer, "Identifier") || isNodeOfType(initializer, "MemberExpression"));
71712
- const patternPath = directAlias ? null : findPatternPathToBinding(symbol.declarationNode.id, symbol.bindingIdentifier);
71713
- if ((canResolveDirectAlias || patternPath) && (allowedAliasSymbolIds === null || allowedAliasSymbolIds.has(symbol.id))) {
71714
- const nextVisitedSymbolIds = new Set(visitedSymbolIds);
71715
- nextVisitedSymbolIds.add(symbol.id);
71716
- const initializerPath = resolveProxyPath(initializer, scopes, nextVisitedSymbolIds, allowedAliasSymbolIds, collectedAliasCaptures);
71717
- if (initializerPath) {
71718
- const aliasPath = {
71719
- ...initializerPath,
71720
- properties: [...initializerPath.properties, ...patternPath ?? []]
71721
- };
71722
- const declarationEnd = symbol.declarationNode.range?.[1];
71723
- if (typeof declarationEnd === "number") collectedAliasCaptures?.set(symbol.id, {
71724
- declarationEnd,
71725
- path: aliasPath
71726
- });
71727
- return aliasPath;
71728
- }
71729
- }
71730
- }
71731
- return {
71732
- rootKey: `symbol:${symbol.id}`,
71733
- properties: []
71734
- };
71735
- };
71736
- const isScopeWithin = (candidateScope, ancestorScope) => {
71737
- let currentScope = candidateScope;
71738
- while (currentScope) {
71739
- if (currentScope === ancestorScope) return true;
71740
- currentScope = currentScope.parent;
71741
- }
71742
- return false;
71743
- };
71744
- const isPathPrefix = (prefix, candidate) => {
71745
- if (prefix.rootKey !== candidate.rootKey || prefix.properties.length > candidate.properties.length) return false;
71746
- return prefix.properties.every((propertyName, propertyIndex) => propertyName !== null && propertyName === candidate.properties[propertyIndex]);
71747
- };
71748
- const findOutermostMemberRead = (identifier) => {
71749
- let currentExpression = findTransparentExpressionRoot(identifier);
71750
- for (;;) {
71751
- const parent = currentExpression.parent;
71752
- if (!isNodeOfType(parent, "MemberExpression") || stripParenExpression(parent.object) !== stripParenExpression(currentExpression)) return currentExpression;
71753
- currentExpression = findTransparentExpressionRoot(parent);
71754
- }
71755
- };
71756
- const isReadPositionWithinAssignmentTarget = (expression) => {
71757
- let currentNode = expression;
71758
- let parentNode = currentNode.parent;
71759
- while (parentNode) {
71760
- if (isNodeOfType(parentNode, "MemberExpression") && parentNode.computed && parentNode.property === currentNode || isNodeOfType(parentNode, "Property") && parentNode.computed && parentNode.key === currentNode || isNodeOfType(parentNode, "AssignmentPattern") && parentNode.right === currentNode) return true;
71761
- if (isNodeOfType(parentNode, "AssignmentExpression") || isNodeOfType(parentNode, "UpdateExpression") || isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "delete" || isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return false;
71762
- currentNode = parentNode;
71763
- parentNode = currentNode.parent;
71764
- }
71765
- return false;
71766
- };
71767
- const isSnapshotArgument = (expression, scopes) => {
71768
- const expressionRoot = findTransparentExpressionRoot(expression);
71769
- const parent = expressionRoot.parent;
71770
- return Boolean(parent && isNodeOfType(parent, "CallExpression") && isValtioUseSnapshotCall(parent, scopes) && parent.arguments[0] === expressionRoot);
71771
- };
71772
- const isStableProxyDependency = (expression, readPath, matchingTarget, scopes) => {
71773
- if (readPath.properties.length !== matchingTarget.path.properties.length || !isPathPrefix(matchingTarget.path, readPath)) return false;
71774
- const dependencyArray = findTransparentExpressionRoot(expression).parent;
71775
- if (!isNodeOfType(dependencyArray, "ArrayExpression")) return false;
71776
- const hookCall = dependencyArray.parent;
71777
- return Boolean(isNodeOfType(hookCall, "CallExpression") && hookCall.arguments[1] === dependencyArray && isReactApiCall(hookCall, REACT_DEPENDENCY_ARRAY_HOOK_NAMES, scopes, { allowGlobalReactNamespace: true }));
71778
- };
71779
- const getSnapshotTarget = (callExpression, context) => {
71780
- if (!isValtioUseSnapshotCall(callExpression, context.scopes)) return null;
71781
- const proxyArgument = callExpression.arguments[0];
71782
- if (!proxyArgument || isNodeOfType(proxyArgument, "SpreadElement")) return null;
71783
- const path = resolveProxyPath(proxyArgument, context.scopes);
71784
- if (!path || path.properties.some((propertyName) => propertyName === null)) return null;
71785
- const callExpressionRoot = findTransparentExpressionRoot(callExpression);
71786
- const declarator = callExpressionRoot.parent;
71787
- if (!isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== callExpressionRoot || !isNodeOfType(declarator.parent, "VariableDeclaration")) return null;
71788
- const ownerFunction = findRenderPhaseComponentOrHook(callExpression, context.scopes);
71789
- if (!ownerFunction) return null;
71790
- const snapshotBindingScopes = collectPatternBindingIdentifiers(declarator.id).flatMap((bindingIdentifier) => {
71791
- const bindingSymbol = context.scopes.symbolFor(bindingIdentifier);
71792
- return bindingSymbol ? [bindingSymbol.scope] : [];
71793
- });
71794
- const declarationEnd = declarator.range?.[1];
71795
- if (snapshotBindingScopes.length === 0 || typeof declarationEnd !== "number") return null;
71796
- return {
71797
- declarationEnd,
71798
- ownerFunction,
71799
- path,
71800
- snapshotBindingScopes
71801
- };
71802
- };
71803
- const wasTargetReplacedBeforeRead = (target, readPosition, writeTargets, readAliasCaptures, context) => writeTargets.some((writeTarget) => {
71804
- const writeNode = findTransparentExpressionRoot(writeTarget).parent;
71805
- const writePosition = writeNode?.range?.[0];
71806
- if (!writeNode || typeof writePosition !== "number" || writePosition <= target.declarationEnd || writePosition >= readPosition || findRenderPhaseComponentOrHook(writeNode, context.scopes) !== target.ownerFunction) return false;
71807
- const writePath = resolveProxyPath(writeTarget, context.scopes, /* @__PURE__ */ new Set(), null);
71808
- if (!writePath || !isPathPrefix(writePath, target.path)) return false;
71809
- return ![...readAliasCaptures.values()].some((aliasCapture) => aliasCapture.declarationEnd < writePosition && isPathPrefix(writePath, aliasCapture.path));
71810
- });
71811
- const valtioNoProxyReadInRender = defineRule({
71812
- id: "valtio-no-proxy-read-in-render",
71813
- title: "Valtio proxy read during render",
71814
- severity: "warn",
71815
- recommendation: "Read reactive render values from useSnapshot. Reserve the mutable proxy for event handlers, effects, and other callbacks that need the latest value.",
71816
- requires: ["valtio:1"],
71817
- create: (context) => {
71818
- const snapshotTargets = [];
71819
- const identifierCandidates = [];
71820
- const writeTargets = [];
71821
- return {
71822
- CallExpression(node) {
71823
- const snapshotTarget = getSnapshotTarget(node, context);
71824
- if (snapshotTarget) snapshotTargets.push(snapshotTarget);
71825
- },
71826
- Identifier(node) {
71827
- if (context.scopes.referenceFor(node)) identifierCandidates.push(node);
71828
- },
71829
- AssignmentExpression(node) {
71830
- writeTargets.push(...collectAssignmentWriteTargets(node.left));
71831
- },
71832
- UpdateExpression(node) {
71833
- writeTargets.push(node.argument);
71834
- },
71835
- "Program:exit"() {
71836
- const reportedExpressions = /* @__PURE__ */ new Set();
71837
- for (const identifier of identifierCandidates) {
71838
- const readExpression = findOutermostMemberRead(identifier);
71839
- if (reportedExpressions.has(readExpression) || isWithinAssignmentTarget(readExpression) && !isReadPositionWithinAssignmentTarget(readExpression) || isSnapshotArgument(readExpression, context.scopes)) continue;
71840
- const readAliasCaptures = /* @__PURE__ */ new Map();
71841
- const readPath = resolveProxyPath(readExpression, context.scopes, /* @__PURE__ */ new Set(), null, readAliasCaptures);
71842
- const readPosition = readExpression.range?.[0];
71843
- const ownerFunction = findRenderPhaseComponentOrHook(readExpression, context.scopes);
71844
- if (!readPath || typeof readPosition !== "number" || !ownerFunction) continue;
71845
- const readScope = context.scopes.scopeFor(readExpression);
71846
- const matchingTarget = snapshotTargets.find((target) => target.ownerFunction === ownerFunction && target.declarationEnd < readPosition && target.snapshotBindingScopes.some((bindingScope) => isScopeWithin(readScope, bindingScope)) && isPathPrefix(target.path, readPath) && (readAliasCaptures.size === 0 || [...readAliasCaptures.values()].some((aliasCapture) => isPathPrefix(aliasCapture.path, target.path) || isPathPrefix(target.path, aliasCapture.path) && readPath.properties.length > aliasCapture.path.properties.length)) && !wasTargetReplacedBeforeRead(target, readPosition, writeTargets, readAliasCaptures, context));
71847
- if (!matchingTarget || isStableProxyDependency(readExpression, readPath, matchingTarget, context.scopes)) continue;
71848
- reportedExpressions.add(readExpression);
71849
- context.report({
71850
- node: readExpression,
71851
- message: MESSAGE
71852
- });
71853
- }
71854
- }
71855
- };
71856
- }
71857
- });
71858
- //#endregion
71859
71692
  //#region src/plugin/rules/react-builtins/void-dom-elements-no-children.ts
71860
71693
  const VOID_DOM_ELEMENTS = new Set([
71861
71694
  "area",
@@ -73547,6 +73380,18 @@ const reactDoctorRules = [
73547
73380
  requires: [...new Set(["react", ...jsxNoScriptUrl.requires ?? []])]
73548
73381
  }
73549
73382
  },
73383
+ {
73384
+ key: "react-doctor/jsx-no-target-blank",
73385
+ id: "jsx-no-target-blank",
73386
+ source: "react-doctor",
73387
+ originallyExternal: true,
73388
+ rule: {
73389
+ ...jsxNoTargetBlank,
73390
+ framework: "global",
73391
+ category: "Security",
73392
+ requires: [...new Set(["react", ...jsxNoTargetBlank.requires ?? []])]
73393
+ }
73394
+ },
73550
73395
  {
73551
73396
  key: "react-doctor/jsx-no-undef",
73552
73397
  id: "jsx-no-undef",
@@ -77008,18 +76853,6 @@ const reactDoctorRules = [
77008
76853
  category: "Performance"
77009
76854
  }
77010
76855
  },
77011
- {
77012
- key: "react-doctor/valtio-no-proxy-read-in-render",
77013
- id: "valtio-no-proxy-read-in-render",
77014
- source: "react-doctor",
77015
- originallyExternal: false,
77016
- rule: {
77017
- ...valtioNoProxyReadInRender,
77018
- framework: "global",
77019
- category: "Bugs",
77020
- requires: [...new Set(["react", ...valtioNoProxyReadInRender.requires ?? []])]
77021
- }
77022
- },
77023
76856
  {
77024
76857
  key: "react-doctor/void-dom-elements-no-children",
77025
76858
  id: "void-dom-elements-no-children",