oxlint-plugin-react-doctor 0.7.3-dev.6b70b32 → 0.7.3-dev.f91ede7

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.
package/dist/index.d.ts CHANGED
@@ -4422,6 +4422,29 @@ declare const REACT_DOCTOR_RULES: readonly [{
4422
4422
  readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
4423
4423
  readonly create: (context: RuleContext) => RuleVisitors;
4424
4424
  };
4425
+ }, {
4426
+ readonly key: "react-doctor/no-indeterminate-attribute";
4427
+ readonly id: "no-indeterminate-attribute";
4428
+ readonly source: "react-doctor";
4429
+ readonly originallyExternal: false;
4430
+ readonly rule: {
4431
+ readonly framework: "global";
4432
+ readonly category: "Bugs";
4433
+ readonly id: string;
4434
+ readonly title?: string;
4435
+ readonly severity: RuleSeverity;
4436
+ readonly requires?: ReadonlyArray<Capability>;
4437
+ readonly disabledWhen?: ReadonlyArray<Capability>;
4438
+ readonly tags?: ReadonlyArray<string>;
4439
+ readonly matchByOccurrence?: boolean;
4440
+ readonly defaultEnabled?: boolean;
4441
+ readonly lifecycle?: "retired";
4442
+ readonly scan?: FileScan;
4443
+ readonly committedFilesOnly?: boolean;
4444
+ readonly recommendation?: string;
4445
+ readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
4446
+ readonly create: (context: RuleContext) => RuleVisitors;
4447
+ };
4425
4448
  }, {
4426
4449
  readonly key: "react-doctor/no-initialize-state";
4427
4450
  readonly id: "no-initialize-state";
@@ -4813,6 +4836,29 @@ declare const REACT_DOCTOR_RULES: readonly [{
4813
4836
  readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
4814
4837
  readonly create: (context: RuleContext) => RuleVisitors;
4815
4838
  };
4839
+ }, {
4840
+ readonly key: "react-doctor/no-match-media-in-state-initializer";
4841
+ readonly id: "no-match-media-in-state-initializer";
4842
+ readonly source: "react-doctor";
4843
+ readonly originallyExternal: false;
4844
+ readonly rule: {
4845
+ readonly framework: "global";
4846
+ readonly category: "Bugs";
4847
+ readonly requires: readonly Capability[];
4848
+ readonly id: string;
4849
+ readonly title?: string;
4850
+ readonly severity: RuleSeverity;
4851
+ readonly disabledWhen?: ReadonlyArray<Capability>;
4852
+ readonly tags?: ReadonlyArray<string>;
4853
+ readonly matchByOccurrence?: boolean;
4854
+ readonly defaultEnabled?: boolean;
4855
+ readonly lifecycle?: "retired";
4856
+ readonly scan?: FileScan;
4857
+ readonly committedFilesOnly?: boolean;
4858
+ readonly recommendation?: string;
4859
+ readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
4860
+ readonly create: (context: RuleContext) => RuleVisitors;
4861
+ };
4816
4862
  }, {
4817
4863
  readonly key: "react-doctor/no-mirror-prop-effect";
4818
4864
  readonly id: "no-mirror-prop-effect";
@@ -13527,6 +13573,29 @@ declare const RULES: readonly [{
13527
13573
  readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
13528
13574
  readonly create: (context: RuleContext) => RuleVisitors;
13529
13575
  };
13576
+ }, {
13577
+ readonly key: "react-doctor/no-indeterminate-attribute";
13578
+ readonly id: "no-indeterminate-attribute";
13579
+ readonly source: "react-doctor";
13580
+ readonly originallyExternal: false;
13581
+ readonly rule: {
13582
+ readonly framework: "global";
13583
+ readonly category: "Bugs";
13584
+ readonly id: string;
13585
+ readonly title?: string;
13586
+ readonly severity: RuleSeverity;
13587
+ readonly requires?: ReadonlyArray<Capability>;
13588
+ readonly disabledWhen?: ReadonlyArray<Capability>;
13589
+ readonly tags?: ReadonlyArray<string>;
13590
+ readonly matchByOccurrence?: boolean;
13591
+ readonly defaultEnabled?: boolean;
13592
+ readonly lifecycle?: "retired";
13593
+ readonly scan?: FileScan;
13594
+ readonly committedFilesOnly?: boolean;
13595
+ readonly recommendation?: string;
13596
+ readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
13597
+ readonly create: (context: RuleContext) => RuleVisitors;
13598
+ };
13530
13599
  }, {
13531
13600
  readonly key: "react-doctor/no-initialize-state";
13532
13601
  readonly id: "no-initialize-state";
@@ -13918,6 +13987,29 @@ declare const RULES: readonly [{
13918
13987
  readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
13919
13988
  readonly create: (context: RuleContext) => RuleVisitors;
13920
13989
  };
13990
+ }, {
13991
+ readonly key: "react-doctor/no-match-media-in-state-initializer";
13992
+ readonly id: "no-match-media-in-state-initializer";
13993
+ readonly source: "react-doctor";
13994
+ readonly originallyExternal: false;
13995
+ readonly rule: {
13996
+ readonly framework: "global";
13997
+ readonly category: "Bugs";
13998
+ readonly requires: readonly Capability[];
13999
+ readonly id: string;
14000
+ readonly title?: string;
14001
+ readonly severity: RuleSeverity;
14002
+ readonly disabledWhen?: ReadonlyArray<Capability>;
14003
+ readonly tags?: ReadonlyArray<string>;
14004
+ readonly matchByOccurrence?: boolean;
14005
+ readonly defaultEnabled?: boolean;
14006
+ readonly lifecycle?: "retired";
14007
+ readonly scan?: FileScan;
14008
+ readonly committedFilesOnly?: boolean;
14009
+ readonly recommendation?: string;
14010
+ readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
14011
+ readonly create: (context: RuleContext) => RuleVisitors;
14012
+ };
13921
14013
  }, {
13922
14014
  readonly key: "react-doctor/no-mirror-prop-effect";
13923
14015
  readonly id: "no-mirror-prop-effect";
package/dist/index.js CHANGED
@@ -2215,7 +2215,7 @@ const anchorAmbiguousText = defineRule({
2215
2215
  });
2216
2216
  //#endregion
2217
2217
  //#region src/plugin/rules/a11y/anchor-has-content.ts
2218
- const MESSAGE$60 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
2218
+ const MESSAGE$61 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
2219
2219
  const isTransComponentsTemplate = (node) => {
2220
2220
  let current = node.parent;
2221
2221
  while (current) {
@@ -2251,7 +2251,7 @@ const anchorHasContent = defineRule({
2251
2251
  if (isTransComponentsTemplate(node)) return;
2252
2252
  context.report({
2253
2253
  node: opening.name,
2254
- message: MESSAGE$60
2254
+ message: MESSAGE$61
2255
2255
  });
2256
2256
  } };
2257
2257
  }
@@ -2682,7 +2682,7 @@ const parseJsxValue = (value) => {
2682
2682
  };
2683
2683
  //#endregion
2684
2684
  //#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
2685
- const MESSAGE$59 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
2685
+ const MESSAGE$60 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
2686
2686
  const mayBeContentEditable = (node) => {
2687
2687
  const attribute = hasJsxPropIgnoreCase(node.attributes, "contenteditable");
2688
2688
  if (!attribute) return false;
@@ -2713,7 +2713,7 @@ const ariaActivedescendantHasTabindex = defineRule({
2713
2713
  if (tabIndexValue === null || tabIndexValue >= -1) return;
2714
2714
  context.report({
2715
2715
  node: node.name,
2716
- message: MESSAGE$59
2716
+ message: MESSAGE$60
2717
2717
  });
2718
2718
  return;
2719
2719
  }
@@ -2721,7 +2721,7 @@ const ariaActivedescendantHasTabindex = defineRule({
2721
2721
  if (mayBeContentEditable(node)) return;
2722
2722
  context.report({
2723
2723
  node: node.name,
2724
- message: MESSAGE$59
2724
+ message: MESSAGE$60
2725
2725
  });
2726
2726
  } })
2727
2727
  });
@@ -5139,7 +5139,7 @@ const asyncParallel = defineRule({
5139
5139
  });
5140
5140
  //#endregion
5141
5141
  //#region src/plugin/rules/security/auth-token-in-web-storage.ts
5142
- const MESSAGE$58 = "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.";
5142
+ const MESSAGE$59 = "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.";
5143
5143
  const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
5144
5144
  const STORAGE_GLOBALS = new Set([
5145
5145
  "window",
@@ -5200,7 +5200,7 @@ const authTokenInWebStorage = defineRule({
5200
5200
  if (keyString === null || !isAuthCredentialKey(keyString)) return;
5201
5201
  context.report({
5202
5202
  node,
5203
- message: MESSAGE$58
5203
+ message: MESSAGE$59
5204
5204
  });
5205
5205
  },
5206
5206
  AssignmentExpression(node) {
@@ -5211,7 +5211,7 @@ const authTokenInWebStorage = defineRule({
5211
5211
  if (!propertyName || !isAuthCredentialKey(propertyName)) return;
5212
5212
  context.report({
5213
5213
  node: target,
5214
- message: MESSAGE$58
5214
+ message: MESSAGE$59
5215
5215
  });
5216
5216
  }
5217
5217
  }))
@@ -5350,7 +5350,7 @@ const CI_INSTALL_NEAR_SECRET_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b
5350
5350
  const INSTALL_COMMAND_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b/i;
5351
5351
  const SECRET_REFERENCE_PATTERN = /\bsecrets\.[A-Z0-9_]+/;
5352
5352
  const IGNORE_SCRIPTS_FLAG_PATTERN = /--ignore-scripts\b/;
5353
- const MESSAGE$57 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
5353
+ const MESSAGE$58 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
5354
5354
  const isWorkflowPath = (relativePath) => /(?:^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(relativePath);
5355
5355
  const scanWorkflowContent = (content) => {
5356
5356
  const lines = content.split("\n");
@@ -5395,7 +5395,7 @@ const scanWorkflowContent = (content) => {
5395
5395
  const installLineOffset = Math.max(step.lines.findIndex((stepLine) => INSTALL_COMMAND_PATTERN.test(stepLine)), 0);
5396
5396
  const installColumnIndex = step.lines[installLineOffset].search(INSTALL_COMMAND_PATTERN);
5397
5397
  return [{
5398
- message: MESSAGE$57,
5398
+ message: MESSAGE$58,
5399
5399
  line: step.startLineIndex + installLineOffset + 1,
5400
5400
  column: (installColumnIndex === -1 ? 0 : installColumnIndex) + 1
5401
5401
  }];
@@ -5405,7 +5405,7 @@ const scanWorkflowContent = (content) => {
5405
5405
  const scanNonWorkflowConfig = scanByPattern({
5406
5406
  shouldScan: (file) => isConfigOrCiPath(file.relativePath) && !file.relativePath.endsWith("package.json") && !isWorkflowPath(file.relativePath),
5407
5407
  pattern: CI_INSTALL_NEAR_SECRET_PATTERN,
5408
- message: MESSAGE$57
5408
+ message: MESSAGE$58
5409
5409
  });
5410
5410
  const scan = (file) => {
5411
5411
  if (isWorkflowPath(file.relativePath)) return scanWorkflowContent(file.content);
@@ -5945,7 +5945,7 @@ const isPureEventBlockerHandler = (attribute) => {
5945
5945
  };
5946
5946
  //#endregion
5947
5947
  //#region src/plugin/rules/a11y/click-events-have-key-events.ts
5948
- const MESSAGE$56 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
5948
+ const MESSAGE$57 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
5949
5949
  const KEY_HANDLERS = [
5950
5950
  "onKeyUp",
5951
5951
  "onKeyDown",
@@ -6114,7 +6114,7 @@ const clickEventsHaveKeyEvents = defineRule({
6114
6114
  if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
6115
6115
  context.report({
6116
6116
  node: node.name,
6117
- message: MESSAGE$56
6117
+ message: MESSAGE$57
6118
6118
  });
6119
6119
  } };
6120
6120
  }
@@ -6343,7 +6343,7 @@ const getClassNameLiteral = (classAttribute) => {
6343
6343
  };
6344
6344
  //#endregion
6345
6345
  //#region src/plugin/rules/a11y/control-has-associated-label.ts
6346
- const MESSAGE$55 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
6346
+ const MESSAGE$56 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
6347
6347
  const NON_OPERABLE_ELEMENTS = new Set([
6348
6348
  "td",
6349
6349
  "th",
@@ -6781,7 +6781,7 @@ const controlHasAssociatedLabel = defineRule({
6781
6781
  if (candidate.enclosingBindingName !== null && labelEmbeddedNames.has(candidate.enclosingBindingName)) continue;
6782
6782
  context.report({
6783
6783
  node: candidate.opening,
6784
- message: MESSAGE$55
6784
+ message: MESSAGE$56
6785
6785
  });
6786
6786
  }
6787
6787
  }
@@ -7288,7 +7288,7 @@ const noVagueButtonLabel = defineRule({
7288
7288
  });
7289
7289
  //#endregion
7290
7290
  //#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
7291
- const MESSAGE$54 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
7291
+ const MESSAGE$55 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
7292
7292
  const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
7293
7293
  const NAME_PROVIDING_ATTRIBUTES = [
7294
7294
  "aria-label",
@@ -7313,7 +7313,7 @@ const dialogHasAccessibleName = defineRule({
7313
7313
  if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
7314
7314
  context.report({
7315
7315
  node: node.name,
7316
- message: MESSAGE$54
7316
+ message: MESSAGE$55
7317
7317
  });
7318
7318
  } };
7319
7319
  }
@@ -7353,7 +7353,7 @@ const isEs6Component = (node) => {
7353
7353
  };
7354
7354
  //#endregion
7355
7355
  //#region src/plugin/rules/react-builtins/display-name.ts
7356
- const MESSAGE$53 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
7356
+ const MESSAGE$54 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
7357
7357
  const DEFAULT_ADDITIONAL_HOCS = [
7358
7358
  "observer",
7359
7359
  "lazy",
@@ -7546,7 +7546,7 @@ const displayName = defineRule({
7546
7546
  const reportAt = (node) => {
7547
7547
  context.report({
7548
7548
  node,
7549
- message: MESSAGE$53
7549
+ message: MESSAGE$54
7550
7550
  });
7551
7551
  };
7552
7552
  return {
@@ -11037,7 +11037,7 @@ const forbidElements = defineRule({
11037
11037
  });
11038
11038
  //#endregion
11039
11039
  //#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
11040
- const MESSAGE$52 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
11040
+ const MESSAGE$53 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
11041
11041
  const forwardRefUsesRef = defineRule({
11042
11042
  id: "forward-ref-uses-ref",
11043
11043
  title: "forwardRef without ref parameter",
@@ -11057,7 +11057,7 @@ const forwardRefUsesRef = defineRule({
11057
11057
  if (isNodeOfType(onlyParam, "RestElement")) return;
11058
11058
  context.report({
11059
11059
  node: inner,
11060
- message: MESSAGE$52
11060
+ message: MESSAGE$53
11061
11061
  });
11062
11062
  } })
11063
11063
  });
@@ -11098,7 +11098,7 @@ const gitProviderUrlInjectionRisk = defineRule({
11098
11098
  });
11099
11099
  //#endregion
11100
11100
  //#region src/plugin/rules/a11y/heading-has-content.ts
11101
- const MESSAGE$51 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
11101
+ const MESSAGE$52 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
11102
11102
  const DEFAULT_HEADING_TAGS = [
11103
11103
  "h1",
11104
11104
  "h2",
@@ -11132,7 +11132,7 @@ const headingHasContent = defineRule({
11132
11132
  for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
11133
11133
  context.report({
11134
11134
  node,
11135
- message: MESSAGE$51
11135
+ message: MESSAGE$52
11136
11136
  });
11137
11137
  } };
11138
11138
  }
@@ -11270,7 +11270,7 @@ const hooksNoNanInDeps = defineRule({
11270
11270
  });
11271
11271
  //#endregion
11272
11272
  //#region src/plugin/rules/a11y/html-has-lang.ts
11273
- const MESSAGE$50 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
11273
+ const MESSAGE$51 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
11274
11274
  const resolveSettings$39 = (settings) => {
11275
11275
  const reactDoctor = settings?.["react-doctor"];
11276
11276
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
@@ -11317,13 +11317,13 @@ const htmlHasLang = defineRule({
11317
11317
  if (!lang) {
11318
11318
  context.report({
11319
11319
  node: node.name,
11320
- message: MESSAGE$50
11320
+ message: MESSAGE$51
11321
11321
  });
11322
11322
  return;
11323
11323
  }
11324
11324
  if (evaluateLang(lang.value) === "empty") context.report({
11325
11325
  node: lang,
11326
- message: MESSAGE$50
11326
+ message: MESSAGE$51
11327
11327
  });
11328
11328
  } };
11329
11329
  }
@@ -11567,7 +11567,7 @@ const htmlNoNestedInteractive = defineRule({
11567
11567
  });
11568
11568
  //#endregion
11569
11569
  //#region src/plugin/rules/a11y/iframe-has-title.ts
11570
- const MESSAGE$49 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
11570
+ const MESSAGE$50 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
11571
11571
  const evaluateTitleValue = (value) => {
11572
11572
  if (!value) return "missing";
11573
11573
  if (isNodeOfType(value, "Literal")) {
@@ -11607,14 +11607,14 @@ const iframeHasTitle = defineRule({
11607
11607
  if (!titleAttr) {
11608
11608
  if (hasSpread || tag === "iframe") context.report({
11609
11609
  node: node.name,
11610
- message: MESSAGE$49
11610
+ message: MESSAGE$50
11611
11611
  });
11612
11612
  return;
11613
11613
  }
11614
11614
  const verdict = evaluateTitleValue(titleAttr.value);
11615
11615
  if (verdict === "missing" || verdict === "empty") context.report({
11616
11616
  node: titleAttr,
11617
- message: MESSAGE$49
11617
+ message: MESSAGE$50
11618
11618
  });
11619
11619
  } })
11620
11620
  });
@@ -11739,7 +11739,7 @@ const iframeMissingSandbox = defineRule({
11739
11739
  });
11740
11740
  //#endregion
11741
11741
  //#region src/plugin/rules/a11y/img-redundant-alt.ts
11742
- const MESSAGE$48 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
11742
+ const MESSAGE$49 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
11743
11743
  const DEFAULT_COMPONENTS = ["img"];
11744
11744
  const DEFAULT_REDUNDANT_WORDS = [
11745
11745
  "image",
@@ -11804,7 +11804,7 @@ const imgRedundantAlt = defineRule({
11804
11804
  if (!altAttribute) return;
11805
11805
  if (altValueRedundant(altAttribute, settings.words)) context.report({
11806
11806
  node: altAttribute,
11807
- message: MESSAGE$48
11807
+ message: MESSAGE$49
11808
11808
  });
11809
11809
  } };
11810
11810
  }
@@ -13574,7 +13574,7 @@ const isStaticRegExpConstruction = (node) => {
13574
13574
  const flagsArgument = node.arguments?.[1];
13575
13575
  return isNodeOfType(node.callee, "Identifier") && node.callee.name === "RegExp" && isStaticPattern(patternArgument) && (flagsArgument === void 0 || isStaticPattern(flagsArgument));
13576
13576
  };
13577
- const MESSAGE$47 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
13577
+ const MESSAGE$48 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
13578
13578
  const jsHoistRegexp = defineRule({
13579
13579
  id: "js-hoist-regexp",
13580
13580
  title: "RegExp built inside a loop",
@@ -13585,13 +13585,13 @@ const jsHoistRegexp = defineRule({
13585
13585
  NewExpression(node) {
13586
13586
  if (isStaticRegExpConstruction(node)) context.report({
13587
13587
  node,
13588
- message: MESSAGE$47
13588
+ message: MESSAGE$48
13589
13589
  });
13590
13590
  },
13591
13591
  CallExpression(node) {
13592
13592
  if (isStaticRegExpConstruction(node)) context.report({
13593
13593
  node,
13594
- message: MESSAGE$47
13594
+ message: MESSAGE$48
13595
13595
  });
13596
13596
  }
13597
13597
  }, { treatIteratorCallbacksAsLoops: true })
@@ -15673,7 +15673,7 @@ const jsxMaxDepth = defineRule({
15673
15673
  });
15674
15674
  //#endregion
15675
15675
  //#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
15676
- const MESSAGE$46 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
15676
+ const MESSAGE$47 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
15677
15677
  const LITERAL_TEXT_TAGS = new Set([
15678
15678
  "code",
15679
15679
  "pre",
@@ -15743,7 +15743,7 @@ const jsxNoCommentTextnodes = defineRule({
15743
15743
  if (isDeliberateStyledCommentToken(node)) return;
15744
15744
  context.report({
15745
15745
  node,
15746
- message: MESSAGE$46
15746
+ message: MESSAGE$47
15747
15747
  });
15748
15748
  } })
15749
15749
  });
@@ -15774,7 +15774,7 @@ const isInsideFunctionScope = (node) => {
15774
15774
  };
15775
15775
  //#endregion
15776
15776
  //#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
15777
- const MESSAGE$45 = "Every reader of this context redraws on each render because you build its `value` inline.";
15777
+ const MESSAGE$46 = "Every reader of this context redraws on each render because you build its `value` inline.";
15778
15778
  const CONTEXT_MODULES$1 = [
15779
15779
  "react",
15780
15780
  "use-context-selector",
@@ -15872,7 +15872,7 @@ const jsxNoConstructedContextValues = defineRule({
15872
15872
  if (!isConstructedValue(innerExpression)) continue;
15873
15873
  context.report({
15874
15874
  node: attribute,
15875
- message: MESSAGE$45
15875
+ message: MESSAGE$46
15876
15876
  });
15877
15877
  }
15878
15878
  }
@@ -16539,7 +16539,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
16539
16539
  ];
16540
16540
  //#endregion
16541
16541
  //#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
16542
- const MESSAGE$44 = "This child redraws every render because the prop gets a brand new array each time.";
16542
+ const MESSAGE$45 = "This child redraws every render because the prop gets a brand new array each time.";
16543
16543
  const isDataArrayPropName = (propName) => {
16544
16544
  if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
16545
16545
  for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -16626,7 +16626,7 @@ const jsxNoNewArrayAsProp = defineRule({
16626
16626
  if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
16627
16627
  context.report({
16628
16628
  node,
16629
- message: MESSAGE$44
16629
+ message: MESSAGE$45
16630
16630
  });
16631
16631
  }
16632
16632
  };
@@ -16884,7 +16884,7 @@ const SAFE_RECEIVER_NAMES = new Set([
16884
16884
  ]);
16885
16885
  //#endregion
16886
16886
  //#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
16887
- const MESSAGE$43 = "This child redraws every render because the prop gets a brand new function each time.";
16887
+ const MESSAGE$44 = "This child redraws every render because the prop gets a brand new function each time.";
16888
16888
  const isAccessorPredicateName = (propName) => {
16889
16889
  for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
16890
16890
  if (propName.length <= prefix.length) continue;
@@ -17090,7 +17090,7 @@ const jsxNoNewFunctionAsProp = defineRule({
17090
17090
  if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
17091
17091
  context.report({
17092
17092
  node,
17093
- message: MESSAGE$43
17093
+ message: MESSAGE$44
17094
17094
  });
17095
17095
  }
17096
17096
  };
@@ -17310,7 +17310,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
17310
17310
  ];
17311
17311
  //#endregion
17312
17312
  //#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
17313
- const MESSAGE$42 = "This child redraws every render because the prop gets a brand new object each time.";
17313
+ const MESSAGE$43 = "This child redraws every render because the prop gets a brand new object each time.";
17314
17314
  const isConfigObjectPropName = (propName) => {
17315
17315
  if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
17316
17316
  for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -17399,7 +17399,7 @@ const jsxNoNewObjectAsProp = defineRule({
17399
17399
  if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
17400
17400
  context.report({
17401
17401
  node,
17402
- message: MESSAGE$42
17402
+ message: MESSAGE$43
17403
17403
  });
17404
17404
  }
17405
17405
  };
@@ -17407,7 +17407,7 @@ const jsxNoNewObjectAsProp = defineRule({
17407
17407
  });
17408
17408
  //#endregion
17409
17409
  //#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
17410
- const MESSAGE$41 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
17410
+ const MESSAGE$42 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
17411
17411
  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;
17412
17412
  const resolveSettings$29 = (settings) => {
17413
17413
  const reactDoctor = settings?.["react-doctor"];
@@ -17448,7 +17448,7 @@ const jsxNoScriptUrl = defineRule({
17448
17448
  if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
17449
17449
  if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
17450
17450
  node: attribute,
17451
- message: MESSAGE$41
17451
+ message: MESSAGE$42
17452
17452
  });
17453
17453
  }
17454
17454
  } };
@@ -18072,7 +18072,7 @@ const jsxPropsNoSpreadMulti = defineRule({
18072
18072
  });
18073
18073
  //#endregion
18074
18074
  //#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
18075
- const MESSAGE$40 = "You can't tell what props reach this element when you spread them.";
18075
+ const MESSAGE$41 = "You can't tell what props reach this element when you spread them.";
18076
18076
  const resolveSettings$25 = (settings) => {
18077
18077
  const reactDoctor = settings?.["react-doctor"];
18078
18078
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
@@ -18115,7 +18115,7 @@ const jsxPropsNoSpreading = defineRule({
18115
18115
  }
18116
18116
  context.report({
18117
18117
  node: attribute,
18118
- message: MESSAGE$40
18118
+ message: MESSAGE$41
18119
18119
  });
18120
18120
  didReportInFile = true;
18121
18121
  return;
@@ -18390,7 +18390,7 @@ const labelHasAssociatedControl = defineRule({
18390
18390
  });
18391
18391
  //#endregion
18392
18392
  //#region src/plugin/rules/a11y/lang.ts
18393
- const MESSAGE$39 = "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`.";
18393
+ const MESSAGE$40 = "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`.";
18394
18394
  const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
18395
18395
  "aa",
18396
18396
  "ab",
@@ -18870,7 +18870,7 @@ const lang = defineRule({
18870
18870
  if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
18871
18871
  context.report({
18872
18872
  node: langAttr,
18873
- message: MESSAGE$39
18873
+ message: MESSAGE$40
18874
18874
  });
18875
18875
  return;
18876
18876
  }
@@ -18879,7 +18879,7 @@ const lang = defineRule({
18879
18879
  if (value === null) return;
18880
18880
  if (!isValidLangTag(value)) context.report({
18881
18881
  node: langAttr,
18882
- message: MESSAGE$39
18882
+ message: MESSAGE$40
18883
18883
  });
18884
18884
  } })
18885
18885
  });
@@ -18924,7 +18924,7 @@ const mdxSsrExecutionRisk = defineRule({
18924
18924
  });
18925
18925
  //#endregion
18926
18926
  //#region src/plugin/rules/a11y/media-has-caption.ts
18927
- const MESSAGE$38 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
18927
+ const MESSAGE$39 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
18928
18928
  const DEFAULT_AUDIO = ["audio"];
18929
18929
  const DEFAULT_VIDEO = ["video"];
18930
18930
  const DEFAULT_TRACK = ["track"];
@@ -19013,7 +19013,7 @@ const mediaHasCaption = defineRule({
19013
19013
  if (!parent || !isNodeOfType(parent, "JSXElement")) {
19014
19014
  context.report({
19015
19015
  node: node.name,
19016
- message: MESSAGE$38
19016
+ message: MESSAGE$39
19017
19017
  });
19018
19018
  return;
19019
19019
  }
@@ -19032,7 +19032,7 @@ const mediaHasCaption = defineRule({
19032
19032
  return kindValue.value.toLowerCase() === "captions";
19033
19033
  })) context.report({
19034
19034
  node: node.name,
19035
- message: MESSAGE$38
19035
+ message: MESSAGE$39
19036
19036
  });
19037
19037
  } };
19038
19038
  }
@@ -21302,7 +21302,7 @@ const nextjsNoVercelOgImport = defineRule({
21302
21302
  });
21303
21303
  //#endregion
21304
21304
  //#region src/plugin/rules/a11y/no-access-key.ts
21305
- const MESSAGE$37 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
21305
+ const MESSAGE$38 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
21306
21306
  const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
21307
21307
  const noAccessKey = defineRule({
21308
21308
  id: "no-access-key",
@@ -21321,7 +21321,7 @@ const noAccessKey = defineRule({
21321
21321
  if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
21322
21322
  context.report({
21323
21323
  node: accessKey,
21324
- message: MESSAGE$37
21324
+ message: MESSAGE$38
21325
21325
  });
21326
21326
  return;
21327
21327
  }
@@ -21331,7 +21331,7 @@ const noAccessKey = defineRule({
21331
21331
  if (isUndefinedIdentifier(expression)) return;
21332
21332
  context.report({
21333
21333
  node: accessKey,
21334
- message: MESSAGE$37
21334
+ message: MESSAGE$38
21335
21335
  });
21336
21336
  }
21337
21337
  } };
@@ -22199,7 +22199,7 @@ const noAdjustStateOnPropChange = defineRule({
22199
22199
  });
22200
22200
  //#endregion
22201
22201
  //#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
22202
- const MESSAGE$36 = "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.";
22202
+ const MESSAGE$37 = "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.";
22203
22203
  const ALWAYS_FOCUSABLE_TAGS = new Set([
22204
22204
  "button",
22205
22205
  "embed",
@@ -22311,7 +22311,7 @@ const noAriaHiddenOnFocusable = defineRule({
22311
22311
  const isImplicitlyFocusable = isNativelyFocusable(tag, node);
22312
22312
  if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
22313
22313
  node: ariaHidden,
22314
- message: MESSAGE$36
22314
+ message: MESSAGE$37
22315
22315
  });
22316
22316
  } })
22317
22317
  });
@@ -23192,7 +23192,7 @@ const isAllLiteralArrayExpression = (node) => {
23192
23192
  };
23193
23193
  //#endregion
23194
23194
  //#region src/plugin/rules/react-builtins/no-array-index-key.ts
23195
- const MESSAGE$35 = "Your users can see & submit the wrong data when this list reorders.";
23195
+ const MESSAGE$36 = "Your users can see & submit the wrong data when this list reorders.";
23196
23196
  const SECOND_INDEX_METHODS = new Set([
23197
23197
  "every",
23198
23198
  "filter",
@@ -23311,14 +23311,14 @@ const noArrayIndexKey = defineRule({
23311
23311
  if (propName !== "key") continue;
23312
23312
  if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
23313
23313
  node: property,
23314
- message: MESSAGE$35
23314
+ message: MESSAGE$36
23315
23315
  });
23316
23316
  }
23317
23317
  } })
23318
23318
  });
23319
23319
  //#endregion
23320
23320
  //#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
23321
- const MESSAGE$34 = "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.";
23321
+ const MESSAGE$35 = "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.";
23322
23322
  const noAsyncEffectCallback = defineRule({
23323
23323
  id: "no-async-effect-callback",
23324
23324
  title: "Async effect callback",
@@ -23332,13 +23332,13 @@ const noAsyncEffectCallback = defineRule({
23332
23332
  if (!callback.async) return;
23333
23333
  context.report({
23334
23334
  node: callback,
23335
- message: MESSAGE$34
23335
+ message: MESSAGE$35
23336
23336
  });
23337
23337
  } })
23338
23338
  });
23339
23339
  //#endregion
23340
23340
  //#region src/plugin/rules/a11y/no-autofocus.ts
23341
- const MESSAGE$33 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
23341
+ const MESSAGE$34 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
23342
23342
  const resolveSettings$21 = (settings) => {
23343
23343
  const reactDoctor = settings?.["react-doctor"];
23344
23344
  return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
@@ -23435,7 +23435,7 @@ const noAutofocus = defineRule({
23435
23435
  if (isConditionallyRendered(node)) return;
23436
23436
  context.report({
23437
23437
  node: autoFocusAttribute,
23438
- message: MESSAGE$33
23438
+ message: MESSAGE$34
23439
23439
  });
23440
23440
  } };
23441
23441
  }
@@ -24472,7 +24472,7 @@ const noChainStateUpdates = defineRule({
24472
24472
  });
24473
24473
  //#endregion
24474
24474
  //#region src/plugin/rules/react-builtins/no-children-prop.ts
24475
- const MESSAGE$32 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
24475
+ const MESSAGE$33 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
24476
24476
  const noChildrenProp = defineRule({
24477
24477
  id: "no-children-prop",
24478
24478
  title: "Children passed as a prop",
@@ -24484,7 +24484,7 @@ const noChildrenProp = defineRule({
24484
24484
  if (node.name.name !== "children") return;
24485
24485
  context.report({
24486
24486
  node: node.name,
24487
- message: MESSAGE$32
24487
+ message: MESSAGE$33
24488
24488
  });
24489
24489
  },
24490
24490
  CallExpression(node) {
@@ -24497,7 +24497,7 @@ const noChildrenProp = defineRule({
24497
24497
  const propertyKey = property.key;
24498
24498
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
24499
24499
  node: propertyKey,
24500
- message: MESSAGE$32
24500
+ message: MESSAGE$33
24501
24501
  });
24502
24502
  }
24503
24503
  }
@@ -24505,7 +24505,7 @@ const noChildrenProp = defineRule({
24505
24505
  });
24506
24506
  //#endregion
24507
24507
  //#region src/plugin/rules/react-builtins/no-clone-element.ts
24508
- const MESSAGE$31 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
24508
+ const MESSAGE$32 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
24509
24509
  const noCloneElement = defineRule({
24510
24510
  id: "no-clone-element",
24511
24511
  title: "cloneElement makes child props fragile",
@@ -24518,7 +24518,7 @@ const noCloneElement = defineRule({
24518
24518
  if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
24519
24519
  if (isImportedFromModule(node, "cloneElement", "react")) context.report({
24520
24520
  node: callee,
24521
- message: MESSAGE$31
24521
+ message: MESSAGE$32
24522
24522
  });
24523
24523
  return;
24524
24524
  }
@@ -24531,14 +24531,14 @@ const noCloneElement = defineRule({
24531
24531
  if (!isImportedFromModule(node, callee.object.name, "react")) return;
24532
24532
  context.report({
24533
24533
  node: callee,
24534
- message: MESSAGE$31
24534
+ message: MESSAGE$32
24535
24535
  });
24536
24536
  }
24537
24537
  } })
24538
24538
  });
24539
24539
  //#endregion
24540
24540
  //#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
24541
- const MESSAGE$30 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
24541
+ const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
24542
24542
  const CONTEXT_MODULES = [
24543
24543
  "react",
24544
24544
  "use-context-selector",
@@ -24574,13 +24574,13 @@ const noCreateContextInRender = defineRule({
24574
24574
  if (!componentOrHookName) return;
24575
24575
  context.report({
24576
24576
  node,
24577
- message: `${MESSAGE$30} (called inside "${componentOrHookName}")`
24577
+ message: `${MESSAGE$31} (called inside "${componentOrHookName}")`
24578
24578
  });
24579
24579
  } })
24580
24580
  });
24581
24581
  //#endregion
24582
24582
  //#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
24583
- const MESSAGE$29 = "`createRef()` in a function component allocates a brand-new ref on every render, so it never holds a value between renders. Use the `useRef()` hook instead.";
24583
+ const MESSAGE$30 = "`createRef()` in a function component allocates a brand-new ref on every render, so it never holds a value between renders. Use the `useRef()` hook instead.";
24584
24584
  const isUseMemoCallbackArgument = (functionNode) => {
24585
24585
  const parent = functionNode.parent;
24586
24586
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
@@ -24610,7 +24610,7 @@ const noCreateRefInFunctionComponent = defineRule({
24610
24610
  if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes))) return;
24611
24611
  context.report({
24612
24612
  node,
24613
- message: MESSAGE$29
24613
+ message: MESSAGE$30
24614
24614
  });
24615
24615
  } })
24616
24616
  });
@@ -24750,7 +24750,7 @@ const noCreateStoreInRender = defineRule({
24750
24750
  });
24751
24751
  //#endregion
24752
24752
  //#region src/plugin/rules/react-builtins/no-danger.ts
24753
- const MESSAGE$28 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
24753
+ const MESSAGE$29 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
24754
24754
  const noDanger = defineRule({
24755
24755
  id: "no-danger",
24756
24756
  title: "Raw HTML injection can run unsafe markup",
@@ -24764,7 +24764,7 @@ const noDanger = defineRule({
24764
24764
  if (!propAttribute) return;
24765
24765
  context.report({
24766
24766
  node: propAttribute.name,
24767
- message: MESSAGE$28
24767
+ message: MESSAGE$29
24768
24768
  });
24769
24769
  },
24770
24770
  CallExpression(node) {
@@ -24776,7 +24776,7 @@ const noDanger = defineRule({
24776
24776
  const propertyKey = property.key;
24777
24777
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
24778
24778
  node: propertyKey,
24779
- message: MESSAGE$28
24779
+ message: MESSAGE$29
24780
24780
  });
24781
24781
  }
24782
24782
  }
@@ -24799,7 +24799,7 @@ const isMeaningfulJsxChild = (child) => {
24799
24799
  };
24800
24800
  //#endregion
24801
24801
  //#region src/plugin/rules/react-builtins/no-danger-with-children.ts
24802
- const MESSAGE$27 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
24802
+ const MESSAGE$28 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
24803
24803
  const mergePropsShape = (target, source) => {
24804
24804
  target.hasDangerously ||= source.hasDangerously;
24805
24805
  target.hasChildren ||= source.hasChildren;
@@ -24874,7 +24874,7 @@ const noDangerWithChildren = defineRule({
24874
24874
  if (!hasChildrenProp && !hasNestedChildren) return;
24875
24875
  if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
24876
24876
  node: opening,
24877
- message: MESSAGE$27
24877
+ message: MESSAGE$28
24878
24878
  });
24879
24879
  },
24880
24880
  CallExpression(node) {
@@ -24887,7 +24887,7 @@ const noDangerWithChildren = defineRule({
24887
24887
  const positionalChildren = node.arguments.slice(2);
24888
24888
  if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
24889
24889
  node,
24890
- message: MESSAGE$27
24890
+ message: MESSAGE$28
24891
24891
  });
24892
24892
  }
24893
24893
  })
@@ -26114,7 +26114,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
26114
26114
  //#endregion
26115
26115
  //#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
26116
26116
  const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
26117
- const MESSAGE$26 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
26117
+ const MESSAGE$27 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
26118
26118
  const getNodeStart = (node) => "start" in node && typeof node.start === "number" ? node.start : -1;
26119
26119
  const getEnclosingLifecycleFunction = (setStateCall) => {
26120
26120
  let ancestor = setStateCall.parent;
@@ -26242,7 +26242,7 @@ const noDidMountSetState = defineRule({
26242
26242
  }
26243
26243
  context.report({
26244
26244
  node: node.callee,
26245
- message: MESSAGE$26
26245
+ message: MESSAGE$27
26246
26246
  });
26247
26247
  } };
26248
26248
  }
@@ -26250,7 +26250,7 @@ const noDidMountSetState = defineRule({
26250
26250
  //#endregion
26251
26251
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
26252
26252
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
26253
- const MESSAGE$25 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
26253
+ const MESSAGE$26 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
26254
26254
  const EQUALITY_OPERATORS = new Set([
26255
26255
  "==",
26256
26256
  "===",
@@ -26362,7 +26362,7 @@ const noDidUpdateSetState = defineRule({
26362
26362
  if (isInsideDiffGuard(node)) return;
26363
26363
  context.report({
26364
26364
  node: node.callee,
26365
- message: MESSAGE$25
26365
+ message: MESSAGE$26
26366
26366
  });
26367
26367
  } };
26368
26368
  }
@@ -26385,7 +26385,7 @@ const isStateMemberExpression = (node) => {
26385
26385
  };
26386
26386
  //#endregion
26387
26387
  //#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
26388
- const MESSAGE$24 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
26388
+ const MESSAGE$25 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
26389
26389
  const shouldIgnoreMutation = (node) => {
26390
26390
  let isConstructor = false;
26391
26391
  let isInsideCallExpression = false;
@@ -26407,7 +26407,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
26407
26407
  if (shouldIgnoreMutation(reportNode)) return;
26408
26408
  context.report({
26409
26409
  node: reportNode,
26410
- message: MESSAGE$24
26410
+ message: MESSAGE$25
26411
26411
  });
26412
26412
  };
26413
26413
  const noDirectMutationState = defineRule({
@@ -26753,7 +26753,7 @@ const noDocumentStartViewTransition = defineRule({
26753
26753
  });
26754
26754
  //#endregion
26755
26755
  //#region src/plugin/rules/js-performance/no-document-write.ts
26756
- const MESSAGE$23 = "`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.";
26756
+ const MESSAGE$24 = "`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.";
26757
26757
  const WRITE_METHODS = new Set(["write", "writeln"]);
26758
26758
  const noDocumentWrite = defineRule({
26759
26759
  id: "no-document-write",
@@ -26768,7 +26768,7 @@ const noDocumentWrite = defineRule({
26768
26768
  if (!isNodeOfType(callee.property, "Identifier") || !WRITE_METHODS.has(callee.property.name)) return;
26769
26769
  context.report({
26770
26770
  node,
26771
- message: MESSAGE$23
26771
+ message: MESSAGE$24
26772
26772
  });
26773
26773
  } })
26774
26774
  });
@@ -28903,7 +28903,7 @@ const ALLOWED_NAMESPACES = new Set([
28903
28903
  "ReactDOM",
28904
28904
  "ReactDom"
28905
28905
  ]);
28906
- const MESSAGE$22 = "`findDOMNode` crashes your app in React 19 because it was removed.";
28906
+ const MESSAGE$23 = "`findDOMNode` crashes your app in React 19 because it was removed.";
28907
28907
  const noFindDomNode = defineRule({
28908
28908
  id: "no-find-dom-node",
28909
28909
  title: "findDOMNode breaks component encapsulation",
@@ -28914,7 +28914,7 @@ const noFindDomNode = defineRule({
28914
28914
  if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
28915
28915
  if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
28916
28916
  node: callee,
28917
- message: MESSAGE$22
28917
+ message: MESSAGE$23
28918
28918
  });
28919
28919
  return;
28920
28920
  }
@@ -28925,7 +28925,7 @@ const noFindDomNode = defineRule({
28925
28925
  if (callee.property.name !== "findDOMNode") return;
28926
28926
  context.report({
28927
28927
  node: callee.property,
28928
- message: MESSAGE$22
28928
+ message: MESSAGE$23
28929
28929
  });
28930
28930
  }
28931
28931
  } })
@@ -29470,7 +29470,7 @@ const noGrayOnColoredBackground = defineRule({
29470
29470
  });
29471
29471
  //#endregion
29472
29472
  //#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
29473
- const MESSAGE$21 = "`<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.";
29473
+ const MESSAGE$22 = "`<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.";
29474
29474
  const noImgLazyWithHighFetchpriority = defineRule({
29475
29475
  id: "no-img-lazy-with-high-fetchpriority",
29476
29476
  title: "Lazy image with high fetchPriority",
@@ -29484,11 +29484,158 @@ const noImgLazyWithHighFetchpriority = defineRule({
29484
29484
  if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
29485
29485
  context.report({
29486
29486
  node: node.name,
29487
- message: MESSAGE$21
29487
+ message: MESSAGE$22
29488
29488
  });
29489
29489
  } })
29490
29490
  });
29491
29491
  //#endregion
29492
+ //#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
29493
+ const MESSAGE$21 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
29494
+ const REACT_USE_REF_OPTIONS = {
29495
+ allowGlobalReactNamespace: false,
29496
+ allowUnboundBareCalls: false
29497
+ };
29498
+ const hasVisibleBindingNamed = (node, bindingName, scopes) => {
29499
+ let scope = scopes.scopeFor(node);
29500
+ while (true) {
29501
+ if (scope.symbolsByName.has(bindingName)) return true;
29502
+ if (!scope.parent) return false;
29503
+ scope = scope.parent;
29504
+ }
29505
+ };
29506
+ const hasEnclosingTypeParameterNamed = (node, typeParameterName) => {
29507
+ let ancestor = node.parent;
29508
+ while (ancestor) {
29509
+ if ("typeParameters" in ancestor) {
29510
+ const typeParameters = ancestor.typeParameters;
29511
+ if (typeParameters && isNodeOfType(typeParameters, "TSTypeParameterDeclaration") && typeParameters.params.some((typeParameter) => isNodeOfType(typeParameter, "TSTypeParameter") && isNodeOfType(typeParameter.name, "Identifier") && typeParameter.name.name === typeParameterName)) return true;
29512
+ }
29513
+ ancestor = ancestor.parent;
29514
+ }
29515
+ return false;
29516
+ };
29517
+ const isHtmlInputElementType = (typeNode, scopes) => {
29518
+ if (isNodeOfType(typeNode, "TSTypeReference")) return isNodeOfType(typeNode.typeName, "Identifier") && typeNode.typeName.name === "HTMLInputElement" && !hasVisibleBindingNamed(typeNode, "HTMLInputElement", scopes) && !hasEnclosingTypeParameterNamed(typeNode, "HTMLInputElement");
29519
+ if (!isNodeOfType(typeNode, "TSUnionType")) return false;
29520
+ let hasHtmlInputElementMember = false;
29521
+ for (const unionMember of typeNode.types) {
29522
+ if (isNodeOfType(unionMember, "TSNullKeyword") || isNodeOfType(unionMember, "TSUndefinedKeyword")) continue;
29523
+ if (!isHtmlInputElementType(unionMember, scopes)) return false;
29524
+ hasHtmlInputElementMember = true;
29525
+ }
29526
+ return hasHtmlInputElementMember;
29527
+ };
29528
+ const getBindingTypeAnnotation = (symbol) => {
29529
+ if (!isNodeOfType(symbol.bindingIdentifier, "Identifier")) return null;
29530
+ const annotation = symbol.bindingIdentifier.typeAnnotation;
29531
+ if (!annotation || !isNodeOfType(annotation, "TSTypeAnnotation")) return null;
29532
+ return annotation.typeAnnotation;
29533
+ };
29534
+ const getDirectConstInitializer = (symbol) => {
29535
+ if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
29536
+ return symbol.initializer;
29537
+ };
29538
+ const hasTypedHtmlInputRefOrigin = (rawExpression, scopes) => {
29539
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
29540
+ let expression = stripParenExpression(rawExpression);
29541
+ while (isNodeOfType(expression, "Identifier")) {
29542
+ const symbol = scopes.symbolFor(expression);
29543
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
29544
+ const initializer = getDirectConstInitializer(symbol);
29545
+ if (!initializer) return false;
29546
+ visitedSymbolIds.add(symbol.id);
29547
+ expression = stripParenExpression(initializer);
29548
+ }
29549
+ if (!isNodeOfType(expression, "CallExpression")) return false;
29550
+ if (!isReactApiCall(expression, "useRef", scopes, REACT_USE_REF_OPTIONS)) return false;
29551
+ if (!isNodeOfType(expression.typeArguments, "TSTypeParameterInstantiation")) return false;
29552
+ const typeArgument = expression.typeArguments.params[0];
29553
+ return Boolean(typeArgument && isHtmlInputElementType(typeArgument, scopes));
29554
+ };
29555
+ const isProvenHtmlInputElement = (rawExpression, scopes) => {
29556
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
29557
+ let expression = stripParenExpression(rawExpression);
29558
+ while (true) {
29559
+ if (isNodeOfType(expression, "MemberExpression") && !expression.computed && isNodeOfType(expression.property, "Identifier") && expression.property.name === "current") return hasTypedHtmlInputRefOrigin(expression.object, scopes);
29560
+ if (!isNodeOfType(expression, "Identifier")) return false;
29561
+ const symbol = scopes.symbolFor(expression);
29562
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
29563
+ const typeAnnotation = getBindingTypeAnnotation(symbol);
29564
+ if (typeAnnotation && isHtmlInputElementType(typeAnnotation, scopes)) return true;
29565
+ const initializer = getDirectConstInitializer(symbol);
29566
+ if (!initializer) return false;
29567
+ visitedSymbolIds.add(symbol.id);
29568
+ expression = stripParenExpression(initializer);
29569
+ }
29570
+ };
29571
+ const getIndeterminateAttributeReceiver = (node) => {
29572
+ const callee = stripParenExpression(node.callee);
29573
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "setAttribute" && callee.property.name !== "toggleAttribute") return null;
29574
+ const attributeName = node.arguments[0];
29575
+ if (!isNodeOfType(attributeName, "Literal") || attributeName.value !== "indeterminate") return null;
29576
+ if (callee.property.name === "setAttribute") {
29577
+ const attributeValue = node.arguments[1];
29578
+ return attributeValue && !isNodeOfType(attributeValue, "SpreadElement") ? callee.object : null;
29579
+ }
29580
+ if (node.arguments.length === 1) return callee.object;
29581
+ const forceArgument = node.arguments[1];
29582
+ if (!forceArgument || isNodeOfType(forceArgument, "SpreadElement")) return null;
29583
+ const forceExpression = stripParenExpression(forceArgument);
29584
+ if (!isNodeOfType(forceExpression, "Literal") || forceExpression.value !== true) return null;
29585
+ return callee.object;
29586
+ };
29587
+ const noIndeterminateAttribute = defineRule({
29588
+ id: "no-indeterminate-attribute",
29589
+ title: "Indeterminate checkbox state set as an attribute",
29590
+ severity: "warn",
29591
+ recommendation: "Assign the checkbox element's `indeterminate` DOM property, usually through a ref, because the HTML attribute does not control its visual state.",
29592
+ create: (context) => {
29593
+ if (classifyReactNativeFileTarget(context) === "react-native") return {};
29594
+ return {
29595
+ JSXOpeningElement(node) {
29596
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "input") return;
29597
+ let typeAttribute = null;
29598
+ let typeAttributeIndex = null;
29599
+ let indeterminateAttribute = null;
29600
+ let indeterminateAttributeIndex = null;
29601
+ let lastSpreadIndex = null;
29602
+ for (let attributeIndex = 0; attributeIndex < node.attributes.length; attributeIndex++) {
29603
+ const attribute = node.attributes[attributeIndex];
29604
+ if (isNodeOfType(attribute, "JSXSpreadAttribute")) {
29605
+ lastSpreadIndex = attributeIndex;
29606
+ continue;
29607
+ }
29608
+ if (!isNodeOfType(attribute, "JSXAttribute") || !isNodeOfType(attribute.name, "JSXIdentifier")) continue;
29609
+ if (attribute.name.name === "type") {
29610
+ typeAttribute = attribute;
29611
+ typeAttributeIndex = attributeIndex;
29612
+ } else if (attribute.name.name === "indeterminate") {
29613
+ indeterminateAttribute = attribute;
29614
+ indeterminateAttributeIndex = attributeIndex;
29615
+ }
29616
+ }
29617
+ if (!typeAttribute || typeAttributeIndex === null || !indeterminateAttribute || indeterminateAttributeIndex === null) return;
29618
+ if (lastSpreadIndex !== null && (lastSpreadIndex > typeAttributeIndex || lastSpreadIndex > indeterminateAttributeIndex)) return;
29619
+ const inputTypeValues = getJsxPropStaticStringValues(typeAttribute, context.scopes);
29620
+ if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
29621
+ context.report({
29622
+ node: indeterminateAttribute,
29623
+ message: MESSAGE$21
29624
+ });
29625
+ },
29626
+ CallExpression(node) {
29627
+ const receiver = getIndeterminateAttributeReceiver(node);
29628
+ if (!receiver) return;
29629
+ if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
29630
+ context.report({
29631
+ node,
29632
+ message: MESSAGE$21
29633
+ });
29634
+ }
29635
+ };
29636
+ }
29637
+ });
29638
+ //#endregion
29492
29639
  //#region src/plugin/utils/contains-non-deterministic-source.ts
29493
29640
  const NON_DETERMINISTIC_MEMBER_CALLS = new Set([
29494
29641
  "Math.random",
@@ -31013,6 +31160,64 @@ const noManyBooleanProps = defineRule({
31013
31160
  }
31014
31161
  });
31015
31162
  //#endregion
31163
+ //#region src/plugin/utils/is-global-match-media-call.ts
31164
+ const isGlobalMatchMediaCall = (node, scopes) => {
31165
+ if (!isNodeOfType(node, "CallExpression")) return false;
31166
+ const callee = stripParenExpression(node.callee);
31167
+ if (isNodeOfType(callee, "Identifier")) return callee.name === "matchMedia" && scopes.isGlobalReference(callee);
31168
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "matchMedia") return false;
31169
+ const receiver = stripParenExpression(callee.object);
31170
+ return isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && scopes.isGlobalReference(receiver);
31171
+ };
31172
+ //#endregion
31173
+ //#region src/plugin/rules/performance/no-match-media-in-state-initializer.ts
31174
+ const REACT_USE_STATE_OPTIONS = {
31175
+ allowGlobalReactNamespace: true,
31176
+ allowUnboundBareCalls: false
31177
+ };
31178
+ const isReactUseStateCall = (node, context) => isReactApiCall(node, "useState", context.scopes, REACT_USE_STATE_OPTIONS);
31179
+ const findDirectMatchMediaCall = (root, context) => {
31180
+ let matchMediaCall = null;
31181
+ walkAst(root, (visitedNode) => {
31182
+ if (matchMediaCall) return false;
31183
+ if (isFunctionLike$1(visitedNode)) return false;
31184
+ if (isGlobalMatchMediaCall(visitedNode, context.scopes)) {
31185
+ matchMediaCall = visitedNode;
31186
+ return false;
31187
+ }
31188
+ });
31189
+ return matchMediaCall;
31190
+ };
31191
+ const findMatchMediaCallDuringInitialization = (initializer, context) => {
31192
+ const rootInitializer = stripParenExpression(initializer);
31193
+ if (!isFunctionLike$1(rootInitializer)) return findDirectMatchMediaCall(rootInitializer, context);
31194
+ if (rootInitializer.async || rootInitializer.generator) return null;
31195
+ return findDirectMatchMediaCall(rootInitializer.body, context);
31196
+ };
31197
+ const noMatchMediaInStateInitializer = defineRule({
31198
+ id: "no-match-media-in-state-initializer",
31199
+ title: "matchMedia in state initializer",
31200
+ severity: "warn",
31201
+ category: "Correctness",
31202
+ disabledWhen: ["vite", "cra"],
31203
+ recommendation: "Prefer CSS media queries for layout, or subscribe with `useSyncExternalStore` and provide a stable server snapshot.",
31204
+ create: (context) => {
31205
+ if (isTestlikeFilename(context.filename)) return {};
31206
+ if (classifyReactNativeFileTarget(context) === "react-native") return {};
31207
+ return { CallExpression(node) {
31208
+ if (!isReactUseStateCall(node, context)) return;
31209
+ const initializer = node.arguments?.[0];
31210
+ if (!initializer || initializer.type === "SpreadElement") return;
31211
+ const matchMediaCall = findMatchMediaCallDuringInitialization(initializer, context);
31212
+ if (!matchMediaCall) return;
31213
+ context.report({
31214
+ node: matchMediaCall,
31215
+ message: "`matchMedia()` in a useState initializer can cause an SSR crash or seed different server and hydration state. Prefer CSS media queries for layout, or use `useSyncExternalStore` with a stable server snapshot."
31216
+ });
31217
+ } };
31218
+ }
31219
+ });
31220
+ //#endregion
31016
31221
  //#region src/plugin/rules/state-and-effects/no-mirror-prop-effect.ts
31017
31222
  const getPropRootName = (expression, propNames) => {
31018
31223
  const rootName = getRootIdentifierName(expression, { followCallChains: true });
@@ -54903,6 +55108,17 @@ const reactDoctorRules = [
54903
55108
  requires: [...new Set(["react", ...noImgLazyWithHighFetchpriority.requires ?? []])]
54904
55109
  }
54905
55110
  },
55111
+ {
55112
+ key: "react-doctor/no-indeterminate-attribute",
55113
+ id: "no-indeterminate-attribute",
55114
+ source: "react-doctor",
55115
+ originallyExternal: false,
55116
+ rule: {
55117
+ ...noIndeterminateAttribute,
55118
+ framework: "global",
55119
+ category: "Bugs"
55120
+ }
55121
+ },
54906
55122
  {
54907
55123
  key: "react-doctor/no-initialize-state",
54908
55124
  id: "no-initialize-state",
@@ -55097,6 +55313,18 @@ const reactDoctorRules = [
55097
55313
  category: "Maintainability"
55098
55314
  }
55099
55315
  },
55316
+ {
55317
+ key: "react-doctor/no-match-media-in-state-initializer",
55318
+ id: "no-match-media-in-state-initializer",
55319
+ source: "react-doctor",
55320
+ originallyExternal: false,
55321
+ rule: {
55322
+ ...noMatchMediaInStateInitializer,
55323
+ framework: "global",
55324
+ category: "Bugs",
55325
+ requires: [...new Set(["react", ...noMatchMediaInStateInitializer.requires ?? []])]
55326
+ }
55327
+ },
55100
55328
  {
55101
55329
  key: "react-doctor/no-mirror-prop-effect",
55102
55330
  id: "no-mirror-prop-effect",
@@ -57963,7 +58191,9 @@ const CROSS_FILE_RULE_IDS = new Set([
57963
58191
  "nextjs-no-use-search-params-without-suspense",
57964
58192
  "no-dynamic-import-path",
57965
58193
  "no-full-lodash-import",
58194
+ "no-indeterminate-attribute",
57966
58195
  "no-locale-format-in-render",
58196
+ "no-match-media-in-state-initializer",
57967
58197
  "no-mutating-reducer-state",
57968
58198
  "prefer-dynamic-import",
57969
58199
  "rendering-hydration-mismatch-time",
@@ -58098,7 +58328,9 @@ const CROSS_FILE_DEPENDENCY_COLLECTORS = new Map([
58098
58328
  ["nextjs-no-use-search-params-without-suspense", collectNextjsSearchParamsDependencies],
58099
58329
  ["no-dynamic-import-path", collectNearestManifestDependencies],
58100
58330
  ["no-full-lodash-import", collectNearestManifestDependencies],
58331
+ ["no-indeterminate-attribute", collectNearestManifestDependencies],
58101
58332
  ["no-locale-format-in-render", collectNearestManifestDependencies],
58333
+ ["no-match-media-in-state-initializer", collectNearestManifestDependencies],
58102
58334
  ["no-mutating-reducer-state", collectMutatingReducerDependencies],
58103
58335
  ["prefer-dynamic-import", collectNearestManifestDependencies],
58104
58336
  ["rendering-hydration-mismatch-time", collectNearestManifestDependencies],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.3-dev.6b70b32",
3
+ "version": "0.7.3-dev.f91ede7",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",