oxlint-plugin-react-doctor 0.7.7-dev.ec144d9 → 0.7.7-dev.fbc804e

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.js CHANGED
@@ -7,6 +7,13 @@ import { analyze } from "eslint-scope";
7
7
  //#region src/plugin/utils/is-node-of-type.ts
8
8
  const isNodeOfType = (node, type) => node !== null && typeof node === "object" && "type" in node && node.type === type;
9
9
  //#endregion
10
+ //#region src/plugin/utils/is-type-only-import.ts
11
+ const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
12
+ if (!specifiers || specifiers.length === 0) return false;
13
+ return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
14
+ };
15
+ const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
16
+ //#endregion
10
17
  //#region src/plugin/utils/non-react-jsx-dialect.ts
11
18
  const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
12
19
  "solid-js",
@@ -21,17 +28,33 @@ const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
21
28
  "vidode"
22
29
  ]);
23
30
  const NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES = ["solid-js", "@builder.io/qwik"];
31
+ const REACT_JSX_DIALECT_PACKAGE_PREFIXES = [
32
+ "react",
33
+ "react-dom",
34
+ "preact"
35
+ ];
24
36
  const startsWithAny = (source, prefixes) => prefixes.some((prefix) => source === prefix || source.startsWith(`${prefix}/`));
25
- const fileImportsNonReactJsxDialect = (program) => {
37
+ const collectJsxRuntimeImports = (program) => {
38
+ let hasNonReactRuntime = false;
39
+ let hasReactRuntime = false;
26
40
  for (const statement of program.body) {
27
41
  if (!isNodeOfType(statement, "ImportDeclaration")) continue;
28
- const source = statement.source;
42
+ const importDeclaration = statement;
43
+ if (isTypeOnlyImport(importDeclaration)) continue;
44
+ const source = importDeclaration.source;
29
45
  const value = source && typeof source.value === "string" ? source.value : null;
30
46
  if (!value) continue;
31
- if (NON_REACT_JSX_DIALECT_PACKAGES.has(value)) return true;
32
- if (startsWithAny(value, NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES)) return true;
47
+ if (NON_REACT_JSX_DIALECT_PACKAGES.has(value) || startsWithAny(value, NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES)) hasNonReactRuntime = true;
48
+ if (startsWithAny(value, REACT_JSX_DIALECT_PACKAGE_PREFIXES)) hasReactRuntime = true;
33
49
  }
34
- return false;
50
+ return {
51
+ hasNonReactRuntime,
52
+ hasReactRuntime
53
+ };
54
+ };
55
+ const fileImportsNonReactJsxDialect = (program) => {
56
+ const runtimeImports = collectJsxRuntimeImports(program);
57
+ return runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
35
58
  };
36
59
  const jsxAttributeIsNonReactDialectMarker = (openingNode) => {
37
60
  for (const attribute of openingNode.attributes) {
@@ -244,6 +267,7 @@ const VISITOR_NODE_NAME_PATTERN = /^[A-Z]/;
244
267
  const wrapCreateForReactJsxOnly = (create) => ((context) => {
245
268
  const innerVisitors = create(context);
246
269
  let fileIsNonReactJsx = false;
270
+ let fileImportsReactRuntime = false;
247
271
  const wrappedVisitors = {};
248
272
  for (const [key, visitor] of Object.entries(innerVisitors)) {
249
273
  if (typeof visitor !== "function") {
@@ -256,14 +280,16 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
256
280
  }
257
281
  if (key === "Program") {
258
282
  wrappedVisitors.Program = (node) => {
259
- fileIsNonReactJsx = fileImportsNonReactJsxDialect(node);
283
+ const runtimeImports = collectJsxRuntimeImports(node);
284
+ fileImportsReactRuntime = runtimeImports.hasReactRuntime;
285
+ fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
260
286
  visitor(node);
261
287
  };
262
288
  continue;
263
289
  }
264
290
  if (key === "JSXOpeningElement") {
265
291
  wrappedVisitors.JSXOpeningElement = (node) => {
266
- if (!fileIsNonReactJsx && jsxAttributeIsNonReactDialectMarker(node)) fileIsNonReactJsx = true;
292
+ if (!fileImportsReactRuntime && !fileIsNonReactJsx && jsxAttributeIsNonReactDialectMarker(node)) fileIsNonReactJsx = true;
267
293
  if (fileIsNonReactJsx) return;
268
294
  visitor(node);
269
295
  };
@@ -275,7 +301,9 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
275
301
  };
276
302
  }
277
303
  if (!("Program" in wrappedVisitors)) wrappedVisitors.Program = (node) => {
278
- fileIsNonReactJsx = fileImportsNonReactJsxDialect(node);
304
+ const runtimeImports = collectJsxRuntimeImports(node);
305
+ fileImportsReactRuntime = runtimeImports.hasReactRuntime;
306
+ fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
279
307
  };
280
308
  return wrappedVisitors;
281
309
  });
@@ -2312,7 +2340,7 @@ const anchorAmbiguousText = defineRule({
2312
2340
  });
2313
2341
  //#endregion
2314
2342
  //#region src/plugin/rules/a11y/anchor-has-content.ts
2315
- const MESSAGE$61 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
2343
+ const MESSAGE$62 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
2316
2344
  const isTransComponentsTemplate = (node) => {
2317
2345
  let current = node.parent;
2318
2346
  while (current) {
@@ -2348,7 +2376,7 @@ const anchorHasContent = defineRule({
2348
2376
  if (isTransComponentsTemplate(node)) return;
2349
2377
  context.report({
2350
2378
  node: opening.name,
2351
- message: MESSAGE$61
2379
+ message: MESSAGE$62
2352
2380
  });
2353
2381
  } };
2354
2382
  }
@@ -2780,7 +2808,7 @@ const parseJsxValue = (value) => {
2780
2808
  };
2781
2809
  //#endregion
2782
2810
  //#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
2783
- const MESSAGE$60 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
2811
+ const MESSAGE$61 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
2784
2812
  const mayBeContentEditable = (node) => {
2785
2813
  const attribute = hasJsxPropIgnoreCase(node.attributes, "contenteditable");
2786
2814
  if (!attribute) return false;
@@ -2811,7 +2839,7 @@ const ariaActivedescendantHasTabindex = defineRule({
2811
2839
  if (tabIndexValue === null || tabIndexValue >= -1) return;
2812
2840
  context.report({
2813
2841
  node: node.name,
2814
- message: MESSAGE$60
2842
+ message: MESSAGE$61
2815
2843
  });
2816
2844
  return;
2817
2845
  }
@@ -2819,7 +2847,7 @@ const ariaActivedescendantHasTabindex = defineRule({
2819
2847
  if (mayBeContentEditable(node)) return;
2820
2848
  context.report({
2821
2849
  node: node.name,
2822
- message: MESSAGE$60
2850
+ message: MESSAGE$61
2823
2851
  });
2824
2852
  } })
2825
2853
  });
@@ -5224,7 +5252,7 @@ const asyncParallel = defineRule({
5224
5252
  });
5225
5253
  //#endregion
5226
5254
  //#region src/plugin/rules/security/auth-token-in-web-storage.ts
5227
- 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.";
5255
+ 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.";
5228
5256
  const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
5229
5257
  const STORAGE_GLOBALS = new Set([
5230
5258
  "window",
@@ -5289,7 +5317,7 @@ const authTokenInWebStorage = defineRule({
5289
5317
  if (keyString === null || !isAuthCredentialKey(keyString)) return;
5290
5318
  context.report({
5291
5319
  node,
5292
- message: MESSAGE$59
5320
+ message: MESSAGE$60
5293
5321
  });
5294
5322
  },
5295
5323
  AssignmentExpression(node) {
@@ -5300,7 +5328,7 @@ const authTokenInWebStorage = defineRule({
5300
5328
  if (!propertyName || !isAuthCredentialKey(propertyName)) return;
5301
5329
  context.report({
5302
5330
  node: target,
5303
- message: MESSAGE$59
5331
+ message: MESSAGE$60
5304
5332
  });
5305
5333
  }
5306
5334
  }))
@@ -5439,7 +5467,7 @@ const CI_INSTALL_NEAR_SECRET_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b
5439
5467
  const INSTALL_COMMAND_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b/i;
5440
5468
  const SECRET_REFERENCE_PATTERN = /\bsecrets\.[A-Z0-9_]+/;
5441
5469
  const IGNORE_SCRIPTS_FLAG_PATTERN = /--ignore-scripts\b/;
5442
- const MESSAGE$58 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
5470
+ const MESSAGE$59 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
5443
5471
  const isWorkflowPath = (relativePath) => /(?:^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(relativePath);
5444
5472
  const scanWorkflowContent = (content) => {
5445
5473
  const lines = content.split("\n");
@@ -5484,7 +5512,7 @@ const scanWorkflowContent = (content) => {
5484
5512
  const installLineOffset = Math.max(step.lines.findIndex((stepLine) => INSTALL_COMMAND_PATTERN.test(stepLine)), 0);
5485
5513
  const installColumnIndex = step.lines[installLineOffset].search(INSTALL_COMMAND_PATTERN);
5486
5514
  return [{
5487
- message: MESSAGE$58,
5515
+ message: MESSAGE$59,
5488
5516
  line: step.startLineIndex + installLineOffset + 1,
5489
5517
  column: (installColumnIndex === -1 ? 0 : installColumnIndex) + 1
5490
5518
  }];
@@ -5494,7 +5522,7 @@ const scanWorkflowContent = (content) => {
5494
5522
  const scanNonWorkflowConfig = scanByPattern({
5495
5523
  shouldScan: (file) => isConfigOrCiPath(file.relativePath) && !file.relativePath.endsWith("package.json") && !isWorkflowPath(file.relativePath),
5496
5524
  pattern: CI_INSTALL_NEAR_SECRET_PATTERN,
5497
- message: MESSAGE$58
5525
+ message: MESSAGE$59
5498
5526
  });
5499
5527
  const scan = (file) => {
5500
5528
  if (isWorkflowPath(file.relativePath)) return scanWorkflowContent(file.content);
@@ -6263,7 +6291,7 @@ const isPureEventBlockerHandler = (attribute) => {
6263
6291
  };
6264
6292
  //#endregion
6265
6293
  //#region src/plugin/rules/a11y/click-events-have-key-events.ts
6266
- const MESSAGE$57 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
6294
+ const MESSAGE$58 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
6267
6295
  const KEY_HANDLERS = [
6268
6296
  "onKeyUp",
6269
6297
  "onKeyDown",
@@ -6474,7 +6502,7 @@ const clickEventsHaveKeyEvents = defineRule({
6474
6502
  if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler) || spreadEventValues.has(handler.toLowerCase()))) return;
6475
6503
  context.report({
6476
6504
  node: node.name,
6477
- message: MESSAGE$57
6505
+ message: MESSAGE$58
6478
6506
  });
6479
6507
  } };
6480
6508
  }
@@ -8727,7 +8755,7 @@ const getClassNameLiteral = (classAttribute) => {
8727
8755
  };
8728
8756
  //#endregion
8729
8757
  //#region src/plugin/rules/a11y/control-has-associated-label.ts
8730
- 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`.";
8758
+ 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`.";
8731
8759
  const NON_OPERABLE_ELEMENTS = new Set([
8732
8760
  "td",
8733
8761
  "th",
@@ -9165,7 +9193,7 @@ const controlHasAssociatedLabel = defineRule({
9165
9193
  if (candidate.enclosingBindingName !== null && labelEmbeddedNames.has(candidate.enclosingBindingName)) continue;
9166
9194
  context.report({
9167
9195
  node: candidate.opening,
9168
- message: MESSAGE$56
9196
+ message: MESSAGE$57
9169
9197
  });
9170
9198
  }
9171
9199
  }
@@ -9926,7 +9954,7 @@ const noVagueButtonLabel = defineRule({
9926
9954
  });
9927
9955
  //#endregion
9928
9956
  //#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
9929
- 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.";
9957
+ 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.";
9930
9958
  const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
9931
9959
  const NAME_PROVIDING_ATTRIBUTES = [
9932
9960
  "aria-label",
@@ -9951,7 +9979,7 @@ const dialogHasAccessibleName = defineRule({
9951
9979
  if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
9952
9980
  context.report({
9953
9981
  node: node.name,
9954
- message: MESSAGE$55
9982
+ message: MESSAGE$56
9955
9983
  });
9956
9984
  } };
9957
9985
  }
@@ -9991,7 +10019,7 @@ const isEs6Component = (node) => {
9991
10019
  };
9992
10020
  //#endregion
9993
10021
  //#region src/plugin/rules/react-builtins/display-name.ts
9994
- const MESSAGE$54 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
10022
+ const MESSAGE$55 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
9995
10023
  const DEFAULT_ADDITIONAL_HOCS = [
9996
10024
  "observer",
9997
10025
  "lazy",
@@ -10184,7 +10212,7 @@ const displayName = defineRule({
10184
10212
  const reportAt = (node) => {
10185
10213
  context.report({
10186
10214
  node,
10187
- message: MESSAGE$54
10215
+ message: MESSAGE$55
10188
10216
  });
10189
10217
  };
10190
10218
  return {
@@ -14747,7 +14775,7 @@ const forbidElements = defineRule({
14747
14775
  });
14748
14776
  //#endregion
14749
14777
  //#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
14750
- const MESSAGE$53 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
14778
+ const MESSAGE$54 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
14751
14779
  const forwardRefUsesRef = defineRule({
14752
14780
  id: "forward-ref-uses-ref",
14753
14781
  title: "forwardRef without ref parameter",
@@ -14770,7 +14798,7 @@ const forwardRefUsesRef = defineRule({
14770
14798
  if (isNodeOfType(onlyParam, "RestElement")) return;
14771
14799
  context.report({
14772
14800
  node: inner,
14773
- message: MESSAGE$53
14801
+ message: MESSAGE$54
14774
14802
  });
14775
14803
  } })
14776
14804
  });
@@ -14811,7 +14839,7 @@ const gitProviderUrlInjectionRisk = defineRule({
14811
14839
  });
14812
14840
  //#endregion
14813
14841
  //#region src/plugin/rules/a11y/heading-has-content.ts
14814
- 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`.";
14842
+ 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`.";
14815
14843
  const DEFAULT_HEADING_TAGS = [
14816
14844
  "h1",
14817
14845
  "h2",
@@ -14845,7 +14873,7 @@ const headingHasContent = defineRule({
14845
14873
  for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
14846
14874
  context.report({
14847
14875
  node,
14848
- message: MESSAGE$52
14876
+ message: MESSAGE$53
14849
14877
  });
14850
14878
  } };
14851
14879
  }
@@ -15009,7 +15037,7 @@ const hooksNoNanInDeps = defineRule({
15009
15037
  });
15010
15038
  //#endregion
15011
15039
  //#region src/plugin/rules/a11y/html-has-lang.ts
15012
- const MESSAGE$51 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
15040
+ const MESSAGE$52 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
15013
15041
  const resolveSettings$39 = (settings) => {
15014
15042
  const reactDoctor = settings?.["react-doctor"];
15015
15043
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
@@ -15056,13 +15084,13 @@ const htmlHasLang = defineRule({
15056
15084
  if (!lang) {
15057
15085
  context.report({
15058
15086
  node: node.name,
15059
- message: MESSAGE$51
15087
+ message: MESSAGE$52
15060
15088
  });
15061
15089
  return;
15062
15090
  }
15063
15091
  if (evaluateLang(lang.value) === "empty") context.report({
15064
15092
  node: lang,
15065
- message: MESSAGE$51
15093
+ message: MESSAGE$52
15066
15094
  });
15067
15095
  } };
15068
15096
  }
@@ -15302,7 +15330,7 @@ const htmlNoNestedInteractive = defineRule({
15302
15330
  });
15303
15331
  //#endregion
15304
15332
  //#region src/plugin/rules/a11y/iframe-has-title.ts
15305
- const MESSAGE$50 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
15333
+ const MESSAGE$51 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
15306
15334
  const evaluateTitleValue = (value) => {
15307
15335
  if (!value) return "missing";
15308
15336
  if (isNodeOfType(value, "Literal")) {
@@ -15342,14 +15370,14 @@ const iframeHasTitle = defineRule({
15342
15370
  if (!titleAttr) {
15343
15371
  if (hasSpread || tag === "iframe") context.report({
15344
15372
  node: node.name,
15345
- message: MESSAGE$50
15373
+ message: MESSAGE$51
15346
15374
  });
15347
15375
  return;
15348
15376
  }
15349
15377
  const verdict = evaluateTitleValue(titleAttr.value);
15350
15378
  if (verdict === "missing" || verdict === "empty") context.report({
15351
15379
  node: titleAttr,
15352
- message: MESSAGE$50
15380
+ message: MESSAGE$51
15353
15381
  });
15354
15382
  } })
15355
15383
  });
@@ -15474,7 +15502,7 @@ const iframeMissingSandbox = defineRule({
15474
15502
  });
15475
15503
  //#endregion
15476
15504
  //#region src/plugin/rules/a11y/img-redundant-alt.ts
15477
- const MESSAGE$49 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
15505
+ const MESSAGE$50 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
15478
15506
  const DEFAULT_COMPONENTS = ["img"];
15479
15507
  const DEFAULT_REDUNDANT_WORDS = [
15480
15508
  "image",
@@ -15539,7 +15567,7 @@ const imgRedundantAlt = defineRule({
15539
15567
  if (!altAttribute) return;
15540
15568
  if (altValueRedundant(altAttribute, settings.words)) context.report({
15541
15569
  node: altAttribute,
15542
- message: MESSAGE$49
15570
+ message: MESSAGE$50
15543
15571
  });
15544
15572
  } };
15545
15573
  }
@@ -17509,7 +17537,7 @@ const getHoistableRegExpConstructionKind = (node, context) => {
17509
17537
  if (!STATEFUL_REGEXP_FLAGS_PATTERN.test(effectiveFlags)) return "stateless";
17510
17538
  return isSafeStatefulReplaceAllSearch(node, effectiveFlags, context) ? "statefulReplaceAll" : null;
17511
17539
  };
17512
- const MESSAGE$48 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
17540
+ const MESSAGE$49 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
17513
17541
  const jsHoistRegexp = defineRule({
17514
17542
  id: "js-hoist-regexp",
17515
17543
  title: "RegExp built inside a loop",
@@ -17526,7 +17554,7 @@ const jsHoistRegexp = defineRule({
17526
17554
  if (constructionKind === "statefulReplaceAll" && cachedEnvironmentHazard === "replaceAllIntegrityLost") return;
17527
17555
  context.report({
17528
17556
  node,
17529
- message: MESSAGE$48
17557
+ message: MESSAGE$49
17530
17558
  });
17531
17559
  };
17532
17560
  return createLoopAwareVisitors({
@@ -20146,7 +20174,7 @@ const jsxMaxDepth = defineRule({
20146
20174
  });
20147
20175
  //#endregion
20148
20176
  //#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
20149
- const MESSAGE$47 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
20177
+ const MESSAGE$48 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
20150
20178
  const LITERAL_TEXT_TAGS = new Set([
20151
20179
  "code",
20152
20180
  "pre",
@@ -20216,7 +20244,7 @@ const jsxNoCommentTextnodes = defineRule({
20216
20244
  if (isDeliberateStyledCommentToken(node)) return;
20217
20245
  context.report({
20218
20246
  node,
20219
- message: MESSAGE$47
20247
+ message: MESSAGE$48
20220
20248
  });
20221
20249
  } })
20222
20250
  });
@@ -20247,7 +20275,7 @@ const isInsideFunctionScope = (node) => {
20247
20275
  };
20248
20276
  //#endregion
20249
20277
  //#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
20250
- const MESSAGE$46 = "Every reader of this context redraws on each render because you build its `value` inline.";
20278
+ const MESSAGE$47 = "Every reader of this context redraws on each render because you build its `value` inline.";
20251
20279
  const CONTEXT_MODULES$1 = [
20252
20280
  "react",
20253
20281
  "use-context-selector",
@@ -20345,7 +20373,7 @@ const jsxNoConstructedContextValues = defineRule({
20345
20373
  if (!isConstructedValue(innerExpression)) continue;
20346
20374
  context.report({
20347
20375
  node: attribute,
20348
- message: MESSAGE$46
20376
+ message: MESSAGE$47
20349
20377
  });
20350
20378
  }
20351
20379
  }
@@ -21200,7 +21228,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
21200
21228
  ];
21201
21229
  //#endregion
21202
21230
  //#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
21203
- const MESSAGE$45 = "This child redraws every render because the prop gets a brand new array each time.";
21231
+ const MESSAGE$46 = "This child redraws every render because the prop gets a brand new array each time.";
21204
21232
  const isDataArrayPropName = (propName) => {
21205
21233
  if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
21206
21234
  for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -21287,7 +21315,7 @@ const jsxNoNewArrayAsProp = defineRule({
21287
21315
  if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
21288
21316
  context.report({
21289
21317
  node,
21290
- message: MESSAGE$45
21318
+ message: MESSAGE$46
21291
21319
  });
21292
21320
  }
21293
21321
  };
@@ -21545,7 +21573,7 @@ const SAFE_RECEIVER_NAMES = new Set([
21545
21573
  ]);
21546
21574
  //#endregion
21547
21575
  //#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
21548
- const MESSAGE$44 = "This child redraws every render because the prop gets a brand new function each time.";
21576
+ const MESSAGE$45 = "This child redraws every render because the prop gets a brand new function each time.";
21549
21577
  const isAccessorPredicateName = (propName) => {
21550
21578
  for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
21551
21579
  if (propName.length <= prefix.length) continue;
@@ -21751,7 +21779,7 @@ const jsxNoNewFunctionAsProp = defineRule({
21751
21779
  if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
21752
21780
  context.report({
21753
21781
  node,
21754
- message: MESSAGE$44
21782
+ message: MESSAGE$45
21755
21783
  });
21756
21784
  }
21757
21785
  };
@@ -21971,7 +21999,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
21971
21999
  ];
21972
22000
  //#endregion
21973
22001
  //#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
21974
- const MESSAGE$43 = "This child redraws every render because the prop gets a brand new object each time.";
22002
+ const MESSAGE$44 = "This child redraws every render because the prop gets a brand new object each time.";
21975
22003
  const isConfigObjectPropName = (propName) => {
21976
22004
  if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
21977
22005
  for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -22060,7 +22088,7 @@ const jsxNoNewObjectAsProp = defineRule({
22060
22088
  if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
22061
22089
  context.report({
22062
22090
  node,
22063
- message: MESSAGE$43
22091
+ message: MESSAGE$44
22064
22092
  });
22065
22093
  }
22066
22094
  };
@@ -22068,7 +22096,7 @@ const jsxNoNewObjectAsProp = defineRule({
22068
22096
  });
22069
22097
  //#endregion
22070
22098
  //#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
22071
- const MESSAGE$42 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
22099
+ const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
22072
22100
  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;
22073
22101
  const resolveSettings$29 = (settings) => {
22074
22102
  const reactDoctor = settings?.["react-doctor"];
@@ -22106,7 +22134,7 @@ const jsxNoScriptUrl = defineRule({
22106
22134
  if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
22107
22135
  if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
22108
22136
  node: attribute,
22109
- message: MESSAGE$42
22137
+ message: MESSAGE$43
22110
22138
  });
22111
22139
  }
22112
22140
  } };
@@ -22726,7 +22754,7 @@ const jsxPropsNoSpreadMulti = defineRule({
22726
22754
  });
22727
22755
  //#endregion
22728
22756
  //#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
22729
- const MESSAGE$41 = "You can't tell what props reach this element when you spread them.";
22757
+ const MESSAGE$42 = "You can't tell what props reach this element when you spread them.";
22730
22758
  const resolveSettings$25 = (settings) => {
22731
22759
  const reactDoctor = settings?.["react-doctor"];
22732
22760
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
@@ -22769,7 +22797,7 @@ const jsxPropsNoSpreading = defineRule({
22769
22797
  }
22770
22798
  context.report({
22771
22799
  node: attribute,
22772
- message: MESSAGE$41
22800
+ message: MESSAGE$42
22773
22801
  });
22774
22802
  didReportInFile = true;
22775
22803
  return;
@@ -23045,7 +23073,7 @@ const labelHasAssociatedControl = defineRule({
23045
23073
  });
23046
23074
  //#endregion
23047
23075
  //#region src/plugin/rules/a11y/lang.ts
23048
- 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`.";
23076
+ 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`.";
23049
23077
  const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
23050
23078
  "aa",
23051
23079
  "ab",
@@ -23525,7 +23553,7 @@ const lang = defineRule({
23525
23553
  if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
23526
23554
  context.report({
23527
23555
  node: langAttr,
23528
- message: MESSAGE$40
23556
+ message: MESSAGE$41
23529
23557
  });
23530
23558
  return;
23531
23559
  }
@@ -23534,7 +23562,7 @@ const lang = defineRule({
23534
23562
  if (value === null) return;
23535
23563
  if (!isValidLangTag(value)) context.report({
23536
23564
  node: langAttr,
23537
- message: MESSAGE$40
23565
+ message: MESSAGE$41
23538
23566
  });
23539
23567
  } })
23540
23568
  });
@@ -23585,7 +23613,7 @@ const mdxSsrExecutionRisk = defineRule({
23585
23613
  });
23586
23614
  //#endregion
23587
23615
  //#region src/plugin/rules/a11y/media-has-caption.ts
23588
- const MESSAGE$39 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
23616
+ const MESSAGE$40 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
23589
23617
  const DEFAULT_AUDIO = ["audio"];
23590
23618
  const DEFAULT_VIDEO = ["video"];
23591
23619
  const DEFAULT_TRACK = ["track"];
@@ -23674,7 +23702,7 @@ const mediaHasCaption = defineRule({
23674
23702
  if (!parent || !isNodeOfType(parent, "JSXElement")) {
23675
23703
  context.report({
23676
23704
  node: node.name,
23677
- message: MESSAGE$39
23705
+ message: MESSAGE$40
23678
23706
  });
23679
23707
  return;
23680
23708
  }
@@ -23693,7 +23721,7 @@ const mediaHasCaption = defineRule({
23693
23721
  return kindValue.value.toLowerCase() === "captions";
23694
23722
  })) context.report({
23695
23723
  node: node.name,
23696
- message: MESSAGE$39
23724
+ message: MESSAGE$40
23697
23725
  });
23698
23726
  } };
23699
23727
  }
@@ -25236,7 +25264,7 @@ const nextjsNoVercelOgImport = defineRule({
25236
25264
  });
25237
25265
  //#endregion
25238
25266
  //#region src/plugin/rules/a11y/no-access-key.ts
25239
- const MESSAGE$38 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
25267
+ const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
25240
25268
  const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
25241
25269
  const noAccessKey = defineRule({
25242
25270
  id: "no-access-key",
@@ -25255,7 +25283,7 @@ const noAccessKey = defineRule({
25255
25283
  if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
25256
25284
  context.report({
25257
25285
  node: accessKey,
25258
- message: MESSAGE$38
25286
+ message: MESSAGE$39
25259
25287
  });
25260
25288
  return;
25261
25289
  }
@@ -25265,7 +25293,7 @@ const noAccessKey = defineRule({
25265
25293
  if (isUndefinedIdentifier(expression)) return;
25266
25294
  context.report({
25267
25295
  node: accessKey,
25268
- message: MESSAGE$38
25296
+ message: MESSAGE$39
25269
25297
  });
25270
25298
  }
25271
25299
  } };
@@ -27120,7 +27148,7 @@ const noAdjustStateOnPropChange = defineRule({
27120
27148
  });
27121
27149
  //#endregion
27122
27150
  //#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
27123
- 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.";
27151
+ 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.";
27124
27152
  const ALWAYS_FOCUSABLE_TAGS = new Set([
27125
27153
  "button",
27126
27154
  "embed",
@@ -27232,7 +27260,7 @@ const noAriaHiddenOnFocusable = defineRule({
27232
27260
  const isImplicitlyFocusable = isNativelyFocusable(tag, node);
27233
27261
  if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
27234
27262
  node: ariaHidden,
27235
- message: MESSAGE$37
27263
+ message: MESSAGE$38
27236
27264
  });
27237
27265
  } })
27238
27266
  });
@@ -28154,7 +28182,7 @@ const isAllLiteralArrayExpression = (node) => {
28154
28182
  };
28155
28183
  //#endregion
28156
28184
  //#region src/plugin/rules/react-builtins/no-array-index-key.ts
28157
- const MESSAGE$36 = "Your users can see & submit the wrong data when this list reorders.";
28185
+ const MESSAGE$37 = "Your users can see & submit the wrong data when this list reorders.";
28158
28186
  const SECOND_INDEX_METHODS = new Set([
28159
28187
  "every",
28160
28188
  "filter",
@@ -28273,14 +28301,14 @@ const noArrayIndexKey = defineRule({
28273
28301
  if (propName !== "key") continue;
28274
28302
  if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
28275
28303
  node: property,
28276
- message: MESSAGE$36
28304
+ message: MESSAGE$37
28277
28305
  });
28278
28306
  }
28279
28307
  } })
28280
28308
  });
28281
28309
  //#endregion
28282
28310
  //#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
28283
- 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.";
28311
+ 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.";
28284
28312
  const noAsyncEffectCallback = defineRule({
28285
28313
  id: "no-async-effect-callback",
28286
28314
  title: "Async effect callback",
@@ -28298,13 +28326,13 @@ const noAsyncEffectCallback = defineRule({
28298
28326
  if (!callback.async) return;
28299
28327
  context.report({
28300
28328
  node: callback,
28301
- message: MESSAGE$35
28329
+ message: MESSAGE$36
28302
28330
  });
28303
28331
  } })
28304
28332
  });
28305
28333
  //#endregion
28306
28334
  //#region src/plugin/rules/a11y/no-autofocus.ts
28307
- 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.";
28335
+ 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.";
28308
28336
  const resolveSettings$21 = (settings) => {
28309
28337
  const reactDoctor = settings?.["react-doctor"];
28310
28338
  return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
@@ -28401,7 +28429,7 @@ const noAutofocus = defineRule({
28401
28429
  if (isConditionallyRendered(node)) return;
28402
28430
  context.report({
28403
28431
  node: autoFocusAttribute,
28404
- message: MESSAGE$34
28432
+ message: MESSAGE$35
28405
28433
  });
28406
28434
  } };
28407
28435
  }
@@ -29374,7 +29402,7 @@ const noChainStateUpdates = defineRule({
29374
29402
  });
29375
29403
  //#endregion
29376
29404
  //#region src/plugin/rules/react-builtins/no-children-prop.ts
29377
- const MESSAGE$33 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
29405
+ const MESSAGE$34 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
29378
29406
  const noChildrenProp = defineRule({
29379
29407
  id: "no-children-prop",
29380
29408
  title: "Children passed as a prop",
@@ -29386,7 +29414,7 @@ const noChildrenProp = defineRule({
29386
29414
  if (node.name.name !== "children") return;
29387
29415
  context.report({
29388
29416
  node: node.name,
29389
- message: MESSAGE$33
29417
+ message: MESSAGE$34
29390
29418
  });
29391
29419
  },
29392
29420
  CallExpression(node) {
@@ -29399,7 +29427,7 @@ const noChildrenProp = defineRule({
29399
29427
  const propertyKey = property.key;
29400
29428
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
29401
29429
  node: propertyKey,
29402
- message: MESSAGE$33
29430
+ message: MESSAGE$34
29403
29431
  });
29404
29432
  }
29405
29433
  }
@@ -29407,7 +29435,7 @@ const noChildrenProp = defineRule({
29407
29435
  });
29408
29436
  //#endregion
29409
29437
  //#region src/plugin/rules/react-builtins/no-clone-element.ts
29410
- const MESSAGE$32 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
29438
+ const MESSAGE$33 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
29411
29439
  const noCloneElement = defineRule({
29412
29440
  id: "no-clone-element",
29413
29441
  title: "cloneElement makes child props fragile",
@@ -29420,7 +29448,7 @@ const noCloneElement = defineRule({
29420
29448
  if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
29421
29449
  if (isImportedFromModule(node, "cloneElement", "react")) context.report({
29422
29450
  node: callee,
29423
- message: MESSAGE$32
29451
+ message: MESSAGE$33
29424
29452
  });
29425
29453
  return;
29426
29454
  }
@@ -29433,7 +29461,7 @@ const noCloneElement = defineRule({
29433
29461
  if (!isImportedFromModule(node, callee.object.name, "react")) return;
29434
29462
  context.report({
29435
29463
  node: callee,
29436
- message: MESSAGE$32
29464
+ message: MESSAGE$33
29437
29465
  });
29438
29466
  }
29439
29467
  } })
@@ -29453,7 +29481,7 @@ const getCallMethodName = (callee) => {
29453
29481
  };
29454
29482
  //#endregion
29455
29483
  //#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
29456
- const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
29484
+ const MESSAGE$32 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
29457
29485
  const CONTEXT_MODULES = [
29458
29486
  "react",
29459
29487
  "use-context-selector",
@@ -29501,13 +29529,13 @@ const noCreateContextInRender = defineRule({
29501
29529
  if (!componentOrHookName) return;
29502
29530
  context.report({
29503
29531
  node,
29504
- message: `${MESSAGE$31} (called inside "${componentOrHookName}")`
29532
+ message: `${MESSAGE$32} (called inside "${componentOrHookName}")`
29505
29533
  });
29506
29534
  } })
29507
29535
  });
29508
29536
  //#endregion
29509
29537
  //#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
29510
- 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.";
29538
+ const MESSAGE$31 = "`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.";
29511
29539
  const isUseMemoCallbackArgument = (functionNode) => {
29512
29540
  const parent = functionNode.parent;
29513
29541
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
@@ -29537,7 +29565,7 @@ const noCreateRefInFunctionComponent = defineRule({
29537
29565
  if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes, context.cfg))) return;
29538
29566
  context.report({
29539
29567
  node,
29540
- message: MESSAGE$30
29568
+ message: MESSAGE$31
29541
29569
  });
29542
29570
  } })
29543
29571
  });
@@ -29677,7 +29705,7 @@ const noCreateStoreInRender = defineRule({
29677
29705
  });
29678
29706
  //#endregion
29679
29707
  //#region src/plugin/rules/react-builtins/no-danger.ts
29680
- const MESSAGE$29 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
29708
+ const MESSAGE$30 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
29681
29709
  const noDanger = defineRule({
29682
29710
  id: "no-danger",
29683
29711
  title: "Raw HTML injection can run unsafe markup",
@@ -29691,7 +29719,7 @@ const noDanger = defineRule({
29691
29719
  if (!propAttribute) return;
29692
29720
  context.report({
29693
29721
  node: propAttribute.name,
29694
- message: MESSAGE$29
29722
+ message: MESSAGE$30
29695
29723
  });
29696
29724
  },
29697
29725
  CallExpression(node) {
@@ -29703,7 +29731,7 @@ const noDanger = defineRule({
29703
29731
  const propertyKey = property.key;
29704
29732
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
29705
29733
  node: propertyKey,
29706
- message: MESSAGE$29
29734
+ message: MESSAGE$30
29707
29735
  });
29708
29736
  }
29709
29737
  }
@@ -29726,7 +29754,7 @@ const isMeaningfulJsxChild = (child) => {
29726
29754
  };
29727
29755
  //#endregion
29728
29756
  //#region src/plugin/rules/react-builtins/no-danger-with-children.ts
29729
- const MESSAGE$28 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
29757
+ const MESSAGE$29 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
29730
29758
  const mergePropsShape = (target, source) => {
29731
29759
  target.hasDangerously ||= source.hasDangerously;
29732
29760
  target.hasChildren ||= source.hasChildren;
@@ -29801,7 +29829,7 @@ const noDangerWithChildren = defineRule({
29801
29829
  if (!hasChildrenProp && !hasNestedChildren) return;
29802
29830
  if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
29803
29831
  node: opening,
29804
- message: MESSAGE$28
29832
+ message: MESSAGE$29
29805
29833
  });
29806
29834
  },
29807
29835
  CallExpression(node) {
@@ -29814,7 +29842,7 @@ const noDangerWithChildren = defineRule({
29814
29842
  const positionalChildren = node.arguments.slice(2);
29815
29843
  if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
29816
29844
  node,
29817
- message: MESSAGE$28
29845
+ message: MESSAGE$29
29818
29846
  });
29819
29847
  }
29820
29848
  })
@@ -30779,7 +30807,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
30779
30807
  //#endregion
30780
30808
  //#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
30781
30809
  const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
30782
- const MESSAGE$27 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
30810
+ const MESSAGE$28 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
30783
30811
  const getNodeStart = (node) => "start" in node && typeof node.start === "number" ? node.start : -1;
30784
30812
  const getEnclosingLifecycleFunction = (setStateCall) => {
30785
30813
  let ancestor = setStateCall.parent;
@@ -30907,7 +30935,7 @@ const noDidMountSetState = defineRule({
30907
30935
  }
30908
30936
  context.report({
30909
30937
  node: node.callee,
30910
- message: MESSAGE$27
30938
+ message: MESSAGE$28
30911
30939
  });
30912
30940
  } };
30913
30941
  }
@@ -30915,7 +30943,7 @@ const noDidMountSetState = defineRule({
30915
30943
  //#endregion
30916
30944
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
30917
30945
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
30918
- const MESSAGE$26 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
30946
+ const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
30919
30947
  const EQUALITY_OPERATORS = new Set([
30920
30948
  "==",
30921
30949
  "===",
@@ -31089,7 +31117,7 @@ const noDidUpdateSetState = defineRule({
31089
31117
  if (isInsideDiffGuard(node)) return;
31090
31118
  context.report({
31091
31119
  node: node.callee,
31092
- message: MESSAGE$26
31120
+ message: MESSAGE$27
31093
31121
  });
31094
31122
  } };
31095
31123
  }
@@ -31112,7 +31140,7 @@ const isStateMemberExpression = (node) => {
31112
31140
  };
31113
31141
  //#endregion
31114
31142
  //#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
31115
- 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.";
31143
+ 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.";
31116
31144
  const shouldIgnoreMutation = (node) => {
31117
31145
  let isConstructor = false;
31118
31146
  let isInsideCallExpression = false;
@@ -31134,7 +31162,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
31134
31162
  if (shouldIgnoreMutation(reportNode)) return;
31135
31163
  context.report({
31136
31164
  node: reportNode,
31137
- message: MESSAGE$25
31165
+ message: MESSAGE$26
31138
31166
  });
31139
31167
  };
31140
31168
  const noDirectMutationState = defineRule({
@@ -31485,7 +31513,7 @@ const noDocumentStartViewTransition = defineRule({
31485
31513
  });
31486
31514
  //#endregion
31487
31515
  //#region src/plugin/rules/js-performance/no-document-write.ts
31488
- 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.";
31516
+ 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.";
31489
31517
  const WRITE_METHODS = new Set(["write", "writeln"]);
31490
31518
  const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
31491
31519
  const noDocumentWrite = defineRule({
@@ -31502,7 +31530,7 @@ const noDocumentWrite = defineRule({
31502
31530
  if (methodName === null || !WRITE_METHODS.has(methodName)) return;
31503
31531
  context.report({
31504
31532
  node,
31505
- message: MESSAGE$24
31533
+ message: MESSAGE$25
31506
31534
  });
31507
31535
  } })
31508
31536
  });
@@ -33720,7 +33748,7 @@ const ALLOWED_NAMESPACES = new Set([
33720
33748
  "ReactDOM",
33721
33749
  "ReactDom"
33722
33750
  ]);
33723
- const MESSAGE$23 = "`findDOMNode` crashes your app in React 19 because it was removed.";
33751
+ const MESSAGE$24 = "`findDOMNode` crashes your app in React 19 because it was removed.";
33724
33752
  const noFindDomNode = defineRule({
33725
33753
  id: "no-find-dom-node",
33726
33754
  title: "findDOMNode breaks component encapsulation",
@@ -33731,7 +33759,7 @@ const noFindDomNode = defineRule({
33731
33759
  if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
33732
33760
  if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
33733
33761
  node: callee,
33734
- message: MESSAGE$23
33762
+ message: MESSAGE$24
33735
33763
  });
33736
33764
  return;
33737
33765
  }
@@ -33742,7 +33770,7 @@ const noFindDomNode = defineRule({
33742
33770
  if (callee.property.name !== "findDOMNode") return;
33743
33771
  context.report({
33744
33772
  node: callee.property,
33745
- message: MESSAGE$23
33773
+ message: MESSAGE$24
33746
33774
  });
33747
33775
  }
33748
33776
  } })
@@ -33951,13 +33979,6 @@ const isPublishedLibraryPackage = (filename) => {
33951
33979
  return manifest.private !== true && typeof manifest.peerDependencies === "object" && manifest.peerDependencies !== null && "react" in manifest.peerDependencies;
33952
33980
  };
33953
33981
  //#endregion
33954
- //#region src/plugin/utils/is-type-only-import.ts
33955
- const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
33956
- if (!specifiers || specifiers.length === 0) return false;
33957
- return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
33958
- };
33959
- const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
33960
- //#endregion
33961
33982
  //#region src/plugin/rules/bundle-size/no-full-lodash-import.ts
33962
33983
  const isLibraryDevPage = (filename) => {
33963
33984
  if (!filename) return false;
@@ -34656,7 +34677,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
34656
34677
  });
34657
34678
  //#endregion
34658
34679
  //#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
34659
- 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.";
34680
+ 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.";
34660
34681
  const noImgLazyWithHighFetchpriority = defineRule({
34661
34682
  id: "no-img-lazy-with-high-fetchpriority",
34662
34683
  title: "Lazy image with high fetchPriority",
@@ -34670,7 +34691,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
34670
34691
  if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
34671
34692
  context.report({
34672
34693
  node: node.name,
34673
- message: MESSAGE$22
34694
+ message: MESSAGE$23
34674
34695
  });
34675
34696
  } })
34676
34697
  });
@@ -34859,7 +34880,7 @@ const noImpureStateUpdater = defineRule({
34859
34880
  });
34860
34881
  //#endregion
34861
34882
  //#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
34862
- const MESSAGE$21 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
34883
+ const MESSAGE$22 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
34863
34884
  const REACT_USE_REF_OPTIONS = {
34864
34885
  allowGlobalReactNamespace: false,
34865
34886
  allowUnboundBareCalls: false
@@ -34960,7 +34981,7 @@ const noIndeterminateAttribute = defineRule({
34960
34981
  if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
34961
34982
  context.report({
34962
34983
  node: indeterminateAttribute,
34963
- message: MESSAGE$21
34984
+ message: MESSAGE$22
34964
34985
  });
34965
34986
  },
34966
34987
  CallExpression(node) {
@@ -34969,7 +34990,7 @@ const noIndeterminateAttribute = defineRule({
34969
34990
  if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
34970
34991
  context.report({
34971
34992
  node,
34972
- message: MESSAGE$21
34993
+ message: MESSAGE$22
34973
34994
  });
34974
34995
  }
34975
34996
  };
@@ -35257,7 +35278,7 @@ const noIsMounted = defineRule({
35257
35278
  });
35258
35279
  //#endregion
35259
35280
  //#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
35260
- const MESSAGE$20 = "`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)`.";
35281
+ 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)`.";
35261
35282
  const isJsonMethodCall = (node, method) => {
35262
35283
  if (!isNodeOfType(node, "CallExpression")) return false;
35263
35284
  const callee = node.callee;
@@ -35321,13 +35342,13 @@ const noJsonParseStringifyClone = defineRule({
35321
35342
  if (isCatchParameterRoundTrip(firstArgument)) return;
35322
35343
  context.report({
35323
35344
  node,
35324
- message: MESSAGE$20
35345
+ message: MESSAGE$21
35325
35346
  });
35326
35347
  } })
35327
35348
  });
35328
35349
  //#endregion
35329
35350
  //#region src/plugin/rules/correctness/no-jsx-element-type.ts
35330
- const MESSAGE$19 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
35351
+ const MESSAGE$20 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
35331
35352
  const isJsxElementTypeReference = (node) => {
35332
35353
  if (!isNodeOfType(node, "TSTypeReference")) return false;
35333
35354
  const typeName = node.typeName;
@@ -35381,7 +35402,7 @@ const noJsxElementType = defineRule({
35381
35402
  if (isJsxImported) return;
35382
35403
  for (const typeAnnotation of flaggedAnnotations) context.report({
35383
35404
  node: typeAnnotation,
35384
- message: MESSAGE$19
35405
+ message: MESSAGE$20
35385
35406
  });
35386
35407
  }
35387
35408
  };
@@ -35613,6 +35634,221 @@ const noLegacyClassLifecycles = defineRule({
35613
35634
  }
35614
35635
  });
35615
35636
  //#endregion
35637
+ //#region src/plugin/utils/is-proven-react-class-component.ts
35638
+ const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
35639
+ const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
35640
+ const expression = stripParenExpression(node);
35641
+ if (isNodeOfType(expression, "MemberExpression")) {
35642
+ const propertyName = getStaticPropertyName(expression);
35643
+ const receiver = stripParenExpression(expression.object);
35644
+ return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
35645
+ }
35646
+ if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
35647
+ if (!isNodeOfType(expression, "Identifier")) return false;
35648
+ const symbol = scopes.symbolFor(expression);
35649
+ if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
35650
+ visitedSymbolIds.add(symbol.id);
35651
+ if (isImportedFromReact(symbol)) {
35652
+ const importedName = getImportedName(symbol.declarationNode);
35653
+ return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
35654
+ }
35655
+ if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
35656
+ return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
35657
+ };
35658
+ const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
35659
+ if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
35660
+ visitedClassNodes.add(classNode);
35661
+ return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
35662
+ };
35663
+ //#endregion
35664
+ //#region src/plugin/utils/function-contains-proven-react-hook-call.ts
35665
+ const functionContainsProvenReactHookCall = (functionNode, scopes) => {
35666
+ if (!isFunctionLike$2(functionNode)) return false;
35667
+ let containsReactHookCall = false;
35668
+ walkAst(functionNode.body, (node) => {
35669
+ if (containsReactHookCall) return false;
35670
+ if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
35671
+ if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
35672
+ containsReactHookCall = true;
35673
+ return false;
35674
+ }
35675
+ });
35676
+ return containsReactHookCall;
35677
+ };
35678
+ //#endregion
35679
+ //#region src/plugin/utils/function-returns-props-children.ts
35680
+ const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
35681
+ if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
35682
+ const firstParameter = stripParenExpression(functionNode.params[0]);
35683
+ const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
35684
+ const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
35685
+ const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
35686
+ if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
35687
+ for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
35688
+ const propertyValue = stripParenExpression(property.value);
35689
+ const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
35690
+ if (!isNodeOfType(childrenBinding, "Identifier")) continue;
35691
+ const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
35692
+ if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
35693
+ }
35694
+ }
35695
+ return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
35696
+ const candidate = stripParenExpression(expression);
35697
+ if (isNodeOfType(candidate, "Identifier")) {
35698
+ const symbol = scopes.symbolFor(candidate);
35699
+ return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
35700
+ }
35701
+ if (!isNodeOfType(candidate, "MemberExpression")) return false;
35702
+ if (getStaticPropertyName(candidate) !== "children") return false;
35703
+ const receiver = stripParenExpression(candidate.object);
35704
+ if (!isNodeOfType(receiver, "Identifier")) return false;
35705
+ const receiverSymbol = scopes.symbolFor(receiver);
35706
+ if (!receiverSymbol || !propsParameterSymbol) return false;
35707
+ return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
35708
+ }, controlFlow);
35709
+ };
35710
+ //#endregion
35711
+ //#region src/plugin/utils/function-returns-only-null.ts
35712
+ const isNullExpression = (expression) => {
35713
+ const candidate = stripParenExpression(expression);
35714
+ return isNodeOfType(candidate, "Literal") && candidate.value === null;
35715
+ };
35716
+ const functionReturnsOnlyNull = (functionNode) => {
35717
+ if (!isFunctionLike$2(functionNode)) return false;
35718
+ if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
35719
+ const returnStatements = collectFunctionReturnStatements(functionNode);
35720
+ return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
35721
+ };
35722
+ //#endregion
35723
+ //#region src/plugin/utils/is-proven-styled-component-expression.ts
35724
+ const findFactoryRoot = (node) => {
35725
+ const candidate = stripParenExpression(node);
35726
+ if (isNodeOfType(candidate, "Identifier")) return candidate;
35727
+ if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
35728
+ if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
35729
+ return null;
35730
+ };
35731
+ const findFactoryPropertyName = (node) => {
35732
+ const candidate = stripParenExpression(node);
35733
+ if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
35734
+ if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
35735
+ return null;
35736
+ };
35737
+ const isProvenStyledComponentExpression = (expression, scopes) => {
35738
+ const candidate = stripParenExpression(expression);
35739
+ if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
35740
+ const factoryRoot = findFactoryRoot(candidate.tag);
35741
+ if (!factoryRoot) return false;
35742
+ const factoryPropertyName = findFactoryPropertyName(candidate.tag);
35743
+ if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
35744
+ const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
35745
+ if (!symbol || symbol.kind !== "import") return false;
35746
+ if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
35747
+ const importDeclaration = symbol.declarationNode.parent;
35748
+ return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
35749
+ };
35750
+ //#endregion
35751
+ //#region src/plugin/utils/is-proven-react-component-symbol.ts
35752
+ const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
35753
+ const LEGACY_REACT_COMPONENT_FACTORY_NAMES = new Set(["createClass", "createReactClass"]);
35754
+ const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
35755
+ const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
35756
+ const candidate = stripParenExpression(expression);
35757
+ if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
35758
+ if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
35759
+ if (isProvenStyledComponentExpression(candidate, scopes)) return true;
35760
+ if (isNodeOfType(candidate, "Identifier")) {
35761
+ const symbol = scopes.symbolFor(candidate);
35762
+ if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
35763
+ visitedSymbolIds.add(symbol.id);
35764
+ if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
35765
+ if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
35766
+ return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
35767
+ }
35768
+ if (!isNodeOfType(candidate, "CallExpression")) return false;
35769
+ if (!hasStableCallTarget(candidate, scopes)) return false;
35770
+ const factoryCallee = stripParenExpression(candidate.callee);
35771
+ if (isNodeOfType(factoryCallee, "Identifier") && isDefaultImportFromModule(factoryCallee, factoryCallee.name, "create-react-class") || isReactApiCall(candidate, LEGACY_REACT_COMPONENT_FACTORY_NAMES, scopes)) return true;
35772
+ if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
35773
+ const wrappedComponent = candidate.arguments[0];
35774
+ return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
35775
+ }
35776
+ if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
35777
+ const factory = candidate.arguments[0];
35778
+ if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
35779
+ const unwrappedFactory = stripParenExpression(factory);
35780
+ if (!isInlineFunctionExpression(unwrappedFactory)) return false;
35781
+ if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
35782
+ const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
35783
+ const returnedExpression = returnStatements[0]?.argument;
35784
+ return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
35785
+ };
35786
+ const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
35787
+ const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
35788
+ for (const candidateSymbol of candidateSymbols) {
35789
+ if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
35790
+ if (isComponentDeclaration(candidateSymbol.declarationNode)) {
35791
+ if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
35792
+ continue;
35793
+ }
35794
+ const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
35795
+ if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
35796
+ if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
35797
+ continue;
35798
+ }
35799
+ if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
35800
+ if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
35801
+ continue;
35802
+ }
35803
+ if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
35804
+ }
35805
+ return false;
35806
+ };
35807
+ //#endregion
35808
+ //#region src/plugin/utils/symbol-has-react-component-type-annotation.ts
35809
+ const REACT_COMPONENT_TYPE_NAMES = new Set([
35810
+ "ComponentClass",
35811
+ "ComponentType",
35812
+ "FC",
35813
+ "FunctionComponent"
35814
+ ]);
35815
+ const findVisibleSymbol = (identifier, scopes) => {
35816
+ if (!isNodeOfType(identifier, "Identifier")) return null;
35817
+ let scope = scopes.scopeFor(identifier);
35818
+ while (true) {
35819
+ const symbol = scope.symbolsByName.get(identifier.name);
35820
+ if (symbol) return symbol;
35821
+ if (!scope.parent) return null;
35822
+ scope = scope.parent;
35823
+ }
35824
+ };
35825
+ const isReactNamespaceType = (identifier, scopes) => {
35826
+ const symbol = findVisibleSymbol(identifier, scopes);
35827
+ return Boolean(symbol && isImportedFromReact(symbol) && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
35828
+ };
35829
+ const isReactComponentType = (typeNode, scopes, visitedSymbolIds) => {
35830
+ if (!typeNode) return false;
35831
+ if (isNodeOfType(typeNode, "TSTypeAnnotation")) return isReactComponentType(typeNode.typeAnnotation, scopes, visitedSymbolIds);
35832
+ if (isNodeOfType(typeNode, "TSIntersectionType")) return (typeNode.types ?? []).some((member) => isReactComponentType(member, scopes, visitedSymbolIds));
35833
+ if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
35834
+ const typeName = typeNode.typeName;
35835
+ if (isNodeOfType(typeName, "TSQualifiedName")) return isNodeOfType(typeName.left, "Identifier") && isNodeOfType(typeName.right, "Identifier") && REACT_COMPONENT_TYPE_NAMES.has(typeName.right.name) && isReactNamespaceType(typeName.left, scopes);
35836
+ if (!isNodeOfType(typeName, "Identifier")) return false;
35837
+ const typeSymbol = findVisibleSymbol(typeName, scopes);
35838
+ if (!typeSymbol || visitedSymbolIds.has(typeSymbol.id)) return false;
35839
+ if (isImportedFromReact(typeSymbol)) {
35840
+ const importedName = getImportedName(typeSymbol.declarationNode);
35841
+ return Boolean(importedName && REACT_COMPONENT_TYPE_NAMES.has(importedName));
35842
+ }
35843
+ if (!isNodeOfType(typeSymbol.declarationNode, "TSTypeAliasDeclaration")) return false;
35844
+ visitedSymbolIds.add(typeSymbol.id);
35845
+ return isReactComponentType(typeSymbol.declarationNode.typeAnnotation, scopes, visitedSymbolIds);
35846
+ };
35847
+ const symbolHasReactComponentTypeAnnotation = (symbol, scopes) => {
35848
+ const bindingIdentifier = symbol.bindingIdentifier;
35849
+ return isNodeOfType(bindingIdentifier, "Identifier") && isReactComponentType(bindingIdentifier.typeAnnotation, scopes, /* @__PURE__ */ new Set());
35850
+ };
35851
+ //#endregion
35616
35852
  //#region src/plugin/rules/architecture/no-legacy-context-api.ts
35617
35853
  const LEGACY_CONTEXT_NAMES = new Set([
35618
35854
  "childContextTypes",
@@ -35623,15 +35859,6 @@ const buildLegacyContextMessage = (memberName) => {
35623
35859
  if (memberName === "childContextTypes" || memberName === "getChildContext") return `${memberName} uses the old context API that React 19 removes, so your provider stops passing data. Switch to \`createContext\` with \`<MyContext.Provider value={...}>\` & read it with \`useContext()\`, moving every consumer together.`;
35624
35860
  return "contextTypes uses the old context API that React 19 removes, so your component stops receiving context. Use `static contextType = MyContext` or `useContext()` in a function component, & update the provider too.";
35625
35861
  };
35626
- const isInsideClassBody = (node) => {
35627
- let current = node.parent;
35628
- while (current) {
35629
- if (isNodeOfType(current, "ClassBody")) return true;
35630
- if (isFunctionLike$2(current)) return false;
35631
- current = current.parent;
35632
- }
35633
- return false;
35634
- };
35635
35862
  const noLegacyContextApi = defineRule({
35636
35863
  id: "no-legacy-context-api",
35637
35864
  title: "Legacy context API",
@@ -35646,6 +35873,7 @@ const noLegacyContextApi = defineRule({
35646
35873
  if (!isNodeOfType(memberNode, "MethodDefinition") && !isNodeOfType(memberNode, "PropertyDefinition")) return;
35647
35874
  if (!isNodeOfType(memberNode.key, "Identifier")) return;
35648
35875
  if (!LEGACY_CONTEXT_NAMES.has(memberNode.key.name)) return;
35876
+ if (memberNode.key.name === "getChildContext" ? memberNode.static : !memberNode.static) return;
35649
35877
  context.report({
35650
35878
  node: memberNode.key,
35651
35879
  message: buildLegacyContextMessage(memberNode.key.name)
@@ -35653,6 +35881,8 @@ const noLegacyContextApi = defineRule({
35653
35881
  };
35654
35882
  return {
35655
35883
  ClassBody(node) {
35884
+ const classNode = node.parent;
35885
+ if (!classNode || !isProvenReactClassComponent(classNode, context.scopes)) return;
35656
35886
  for (const member of node.body ?? []) checkMember(member);
35657
35887
  },
35658
35888
  AssignmentExpression(node) {
@@ -35662,9 +35892,11 @@ const noLegacyContextApi = defineRule({
35662
35892
  if (left.computed) return;
35663
35893
  if (!isNodeOfType(left.property, "Identifier")) return;
35664
35894
  if (!LEGACY_CONTEXT_NAMES.has(left.property.name)) return;
35665
- if (!isNodeOfType(left.object, "Identifier")) return;
35666
- if (!isUppercaseName(left.object.name)) return;
35667
- if (isInsideClassBody(node)) return;
35895
+ if (left.property.name === "getChildContext") return;
35896
+ const component = stripParenExpression(left.object);
35897
+ if (!isNodeOfType(component, "Identifier")) return;
35898
+ const symbol = context.scopes.symbolFor(component);
35899
+ if (!symbol || !isProvenReactComponentSymbol(symbol, context.scopes, context.cfg, component) && (hasSymbolWriteBefore(symbol, component, context.scopes) || !symbolHasReactComponentTypeAnnotation(symbol, context.scopes))) return;
35668
35900
  context.report({
35669
35901
  node: left,
35670
35902
  message: buildLegacyContextMessage(left.property.name)
@@ -36486,7 +36718,7 @@ const noMoment = defineRule({
36486
36718
  });
36487
36719
  //#endregion
36488
36720
  //#region src/plugin/rules/react-builtins/no-multi-comp.ts
36489
- const MESSAGE$18 = "This file declares several components, so each component is harder to find, test, and change.";
36721
+ const MESSAGE$19 = "This file declares several components, so each component is harder to find, test, and change.";
36490
36722
  const resolveSettings$16 = (settings) => {
36491
36723
  const reactDoctor = settings?.["react-doctor"];
36492
36724
  return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
@@ -36845,7 +37077,7 @@ const noMultiComp = defineRule({
36845
37077
  if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
36846
37078
  for (const component of flagged.slice(1)) context.report({
36847
37079
  node: component.reportNode,
36848
- message: MESSAGE$18
37080
+ message: MESSAGE$19
36849
37081
  });
36850
37082
  } };
36851
37083
  }
@@ -37058,7 +37290,7 @@ const resolveReducerFunction = (node, currentFilename) => {
37058
37290
  };
37059
37291
  //#endregion
37060
37292
  //#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
37061
- const MESSAGE$17 = "This reducer changes state in place, so your update is silently skipped.";
37293
+ const MESSAGE$18 = "This reducer changes state in place, so your update is silently skipped.";
37062
37294
  const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
37063
37295
  "copyWithin",
37064
37296
  "fill",
@@ -37267,7 +37499,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
37267
37499
  reportedNodes.add(options.crossFileConsumerCallSite);
37268
37500
  context.report({
37269
37501
  node: options.crossFileConsumerCallSite,
37270
- message: `${MESSAGE$17} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
37502
+ message: `${MESSAGE$18} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
37271
37503
  });
37272
37504
  return;
37273
37505
  }
@@ -37276,7 +37508,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
37276
37508
  reportedNodes.add(mutation.node);
37277
37509
  context.report({
37278
37510
  node: mutation.node,
37279
- message: MESSAGE$17
37511
+ message: MESSAGE$18
37280
37512
  });
37281
37513
  }
37282
37514
  };
@@ -37683,7 +37915,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
37683
37915
  });
37684
37916
  //#endregion
37685
37917
  //#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
37686
- const MESSAGE$16 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
37918
+ const MESSAGE$17 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
37687
37919
  const KEYBOARD_HANDLER_PROP_NAMES = [
37688
37920
  "onKeyDown",
37689
37921
  "onKeyUp",
@@ -37802,7 +38034,7 @@ const noNoninteractiveTabindex = defineRule({
37802
38034
  if (numeric === null) {
37803
38035
  if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues && !isKeyboardOperable(node) && !isFocusOperable(node) && !hasJsxSpreadAttribute$1(node.attributes)) context.report({
37804
38036
  node: tabIndex,
37805
- message: MESSAGE$16
38037
+ message: MESSAGE$17
37806
38038
  });
37807
38039
  return;
37808
38040
  }
@@ -37824,7 +38056,7 @@ const noNoninteractiveTabindex = defineRule({
37824
38056
  if (!roleAttribute) {
37825
38057
  context.report({
37826
38058
  node: tabIndex,
37827
- message: MESSAGE$16
38059
+ message: MESSAGE$17
37828
38060
  });
37829
38061
  return;
37830
38062
  }
@@ -37838,7 +38070,7 @@ const noNoninteractiveTabindex = defineRule({
37838
38070
  }
37839
38071
  context.report({
37840
38072
  node: tabIndex,
37841
- message: MESSAGE$16
38073
+ message: MESSAGE$17
37842
38074
  });
37843
38075
  } };
37844
38076
  }
@@ -39257,174 +39489,6 @@ const noPropCallbackInRender = defineRule({
39257
39489
  } })
39258
39490
  });
39259
39491
  //#endregion
39260
- //#region src/plugin/utils/is-proven-react-class-component.ts
39261
- const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
39262
- const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
39263
- const expression = stripParenExpression(node);
39264
- if (isNodeOfType(expression, "MemberExpression")) {
39265
- const propertyName = getStaticPropertyName(expression);
39266
- const receiver = stripParenExpression(expression.object);
39267
- return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
39268
- }
39269
- if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
39270
- if (!isNodeOfType(expression, "Identifier")) return false;
39271
- const symbol = scopes.symbolFor(expression);
39272
- if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
39273
- visitedSymbolIds.add(symbol.id);
39274
- if (isImportedFromReact(symbol)) {
39275
- const importedName = getImportedName(symbol.declarationNode);
39276
- return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
39277
- }
39278
- if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
39279
- return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
39280
- };
39281
- const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
39282
- if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
39283
- visitedClassNodes.add(classNode);
39284
- return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
39285
- };
39286
- //#endregion
39287
- //#region src/plugin/utils/function-contains-proven-react-hook-call.ts
39288
- const functionContainsProvenReactHookCall = (functionNode, scopes) => {
39289
- if (!isFunctionLike$2(functionNode)) return false;
39290
- let containsReactHookCall = false;
39291
- walkAst(functionNode.body, (node) => {
39292
- if (containsReactHookCall) return false;
39293
- if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
39294
- if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
39295
- containsReactHookCall = true;
39296
- return false;
39297
- }
39298
- });
39299
- return containsReactHookCall;
39300
- };
39301
- //#endregion
39302
- //#region src/plugin/utils/function-returns-props-children.ts
39303
- const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
39304
- if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
39305
- const firstParameter = stripParenExpression(functionNode.params[0]);
39306
- const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
39307
- const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
39308
- const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
39309
- if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
39310
- for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
39311
- const propertyValue = stripParenExpression(property.value);
39312
- const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
39313
- if (!isNodeOfType(childrenBinding, "Identifier")) continue;
39314
- const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
39315
- if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
39316
- }
39317
- }
39318
- return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
39319
- const candidate = stripParenExpression(expression);
39320
- if (isNodeOfType(candidate, "Identifier")) {
39321
- const symbol = scopes.symbolFor(candidate);
39322
- return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
39323
- }
39324
- if (!isNodeOfType(candidate, "MemberExpression")) return false;
39325
- if (getStaticPropertyName(candidate) !== "children") return false;
39326
- const receiver = stripParenExpression(candidate.object);
39327
- if (!isNodeOfType(receiver, "Identifier")) return false;
39328
- const receiverSymbol = scopes.symbolFor(receiver);
39329
- if (!receiverSymbol || !propsParameterSymbol) return false;
39330
- return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
39331
- }, controlFlow);
39332
- };
39333
- //#endregion
39334
- //#region src/plugin/utils/function-returns-only-null.ts
39335
- const isNullExpression = (expression) => {
39336
- const candidate = stripParenExpression(expression);
39337
- return isNodeOfType(candidate, "Literal") && candidate.value === null;
39338
- };
39339
- const functionReturnsOnlyNull = (functionNode) => {
39340
- if (!isFunctionLike$2(functionNode)) return false;
39341
- if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
39342
- const returnStatements = collectFunctionReturnStatements(functionNode);
39343
- return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
39344
- };
39345
- //#endregion
39346
- //#region src/plugin/utils/is-proven-styled-component-expression.ts
39347
- const findFactoryRoot = (node) => {
39348
- const candidate = stripParenExpression(node);
39349
- if (isNodeOfType(candidate, "Identifier")) return candidate;
39350
- if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
39351
- if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
39352
- return null;
39353
- };
39354
- const findFactoryPropertyName = (node) => {
39355
- const candidate = stripParenExpression(node);
39356
- if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
39357
- if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
39358
- return null;
39359
- };
39360
- const isProvenStyledComponentExpression = (expression, scopes) => {
39361
- const candidate = stripParenExpression(expression);
39362
- if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
39363
- const factoryRoot = findFactoryRoot(candidate.tag);
39364
- if (!factoryRoot) return false;
39365
- const factoryPropertyName = findFactoryPropertyName(candidate.tag);
39366
- if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
39367
- const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
39368
- if (!symbol || symbol.kind !== "import") return false;
39369
- if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
39370
- const importDeclaration = symbol.declarationNode.parent;
39371
- return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
39372
- };
39373
- //#endregion
39374
- //#region src/plugin/utils/is-proven-react-component-symbol.ts
39375
- const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
39376
- const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
39377
- const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
39378
- const candidate = stripParenExpression(expression);
39379
- if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
39380
- if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
39381
- if (isProvenStyledComponentExpression(candidate, scopes)) return true;
39382
- if (isNodeOfType(candidate, "Identifier")) {
39383
- const symbol = scopes.symbolFor(candidate);
39384
- if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
39385
- visitedSymbolIds.add(symbol.id);
39386
- if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
39387
- if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
39388
- return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
39389
- }
39390
- if (!isNodeOfType(candidate, "CallExpression")) return false;
39391
- if (!hasStableCallTarget(candidate, scopes)) return false;
39392
- if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
39393
- const wrappedComponent = candidate.arguments[0];
39394
- return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
39395
- }
39396
- if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
39397
- const factory = candidate.arguments[0];
39398
- if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
39399
- const unwrappedFactory = stripParenExpression(factory);
39400
- if (!isInlineFunctionExpression(unwrappedFactory)) return false;
39401
- if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
39402
- const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
39403
- const returnedExpression = returnStatements[0]?.argument;
39404
- return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
39405
- };
39406
- const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
39407
- const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
39408
- for (const candidateSymbol of candidateSymbols) {
39409
- if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
39410
- if (isComponentDeclaration(candidateSymbol.declarationNode)) {
39411
- if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
39412
- continue;
39413
- }
39414
- const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
39415
- if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
39416
- if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
39417
- continue;
39418
- }
39419
- if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
39420
- if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
39421
- continue;
39422
- }
39423
- if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
39424
- }
39425
- return false;
39426
- };
39427
- //#endregion
39428
39492
  //#region src/plugin/rules/architecture/no-prop-types.ts
39429
39493
  const PROP_TYPES_PROPERTY = "propTypes";
39430
39494
  const isPropTypesKey = (key, computed) => {
@@ -39632,7 +39696,7 @@ const noRandomKey = defineRule({
39632
39696
  });
39633
39697
  //#endregion
39634
39698
  //#region src/plugin/rules/react-builtins/no-react-children.ts
39635
- const MESSAGE$15 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
39699
+ const MESSAGE$16 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
39636
39700
  const isChildrenIdentifier = (node, contextNode) => {
39637
39701
  if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
39638
39702
  return isImportedFromModule(contextNode, "Children", "react");
@@ -39658,13 +39722,13 @@ const noReactChildren = defineRule({
39658
39722
  if (isChildrenIdentifier(memberObject, node)) {
39659
39723
  context.report({
39660
39724
  node: calleeOuter,
39661
- message: MESSAGE$15
39725
+ message: MESSAGE$16
39662
39726
  });
39663
39727
  return;
39664
39728
  }
39665
39729
  if (isReactNamespaceMember(memberObject, node)) context.report({
39666
39730
  node: calleeOuter,
39667
- message: MESSAGE$15
39731
+ message: MESSAGE$16
39668
39732
  });
39669
39733
  } })
39670
39734
  });
@@ -40345,7 +40409,7 @@ const noRenderPropChildren = defineRule({
40345
40409
  });
40346
40410
  //#endregion
40347
40411
  //#region src/plugin/rules/react-builtins/no-render-return-value.ts
40348
- const MESSAGE$14 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
40412
+ const MESSAGE$15 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
40349
40413
  const isReactDomRenderCall = (node) => isGlobalMethodCall(node, "ReactDOM", "render");
40350
40414
  const isUsedAsReturnValue = (parent) => {
40351
40415
  if (!parent) return false;
@@ -40363,7 +40427,7 @@ const noRenderReturnValue = defineRule({
40363
40427
  if (!isUsedAsReturnValue(node.parent)) return;
40364
40428
  context.report({
40365
40429
  node: node.callee,
40366
- message: MESSAGE$14
40430
+ message: MESSAGE$15
40367
40431
  });
40368
40432
  } })
40369
40433
  });
@@ -41172,7 +41236,7 @@ const noSelfUpdatingEffect = defineRule({
41172
41236
  });
41173
41237
  //#endregion
41174
41238
  //#region src/plugin/rules/react-builtins/no-set-state.ts
41175
- const MESSAGE$13 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
41239
+ const MESSAGE$14 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
41176
41240
  const noSetState = defineRule({
41177
41241
  id: "no-set-state",
41178
41242
  title: "Local class state forbidden",
@@ -41187,7 +41251,7 @@ const noSetState = defineRule({
41187
41251
  if (!getParentComponent(node)) return;
41188
41252
  context.report({
41189
41253
  node: node.callee,
41190
- message: MESSAGE$13
41254
+ message: MESSAGE$14
41191
41255
  });
41192
41256
  } })
41193
41257
  });
@@ -41557,7 +41621,7 @@ const isAbstractRole = (openingElement, settings) => {
41557
41621
  };
41558
41622
  //#endregion
41559
41623
  //#region src/plugin/rules/a11y/no-static-element-interactions.ts
41560
- const MESSAGE$12 = "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.";
41624
+ 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.";
41561
41625
  const DEFAULT_HANDLERS = [
41562
41626
  "onClick",
41563
41627
  "onMouseDown",
@@ -41672,7 +41736,7 @@ const noStaticElementInteractions = defineRule({
41672
41736
  if (!roleAttribute || !roleAttribute.value) {
41673
41737
  context.report({
41674
41738
  node: node.name,
41675
- message: MESSAGE$12
41739
+ message: MESSAGE$13
41676
41740
  });
41677
41741
  return;
41678
41742
  }
@@ -41682,7 +41746,7 @@ const noStaticElementInteractions = defineRule({
41682
41746
  if (isRecognizedRoleString(attributeValue.value)) return;
41683
41747
  context.report({
41684
41748
  node: node.name,
41685
- message: MESSAGE$12
41749
+ message: MESSAGE$13
41686
41750
  });
41687
41751
  return;
41688
41752
  }
@@ -41690,7 +41754,7 @@ const noStaticElementInteractions = defineRule({
41690
41754
  if (!settings.allowExpressionValues) {
41691
41755
  context.report({
41692
41756
  node: node.name,
41693
- message: MESSAGE$12
41757
+ message: MESSAGE$13
41694
41758
  });
41695
41759
  return;
41696
41760
  }
@@ -41698,7 +41762,7 @@ const noStaticElementInteractions = defineRule({
41698
41762
  if (isStaticNullishExpression(expression)) {
41699
41763
  context.report({
41700
41764
  node: node.name,
41701
- message: MESSAGE$12
41765
+ message: MESSAGE$13
41702
41766
  });
41703
41767
  return;
41704
41768
  }
@@ -41708,7 +41772,7 @@ const noStaticElementInteractions = defineRule({
41708
41772
  if (branches.some((branch) => branch.role !== null && isRecognizedRoleString(branch.role))) return;
41709
41773
  context.report({
41710
41774
  node: node.name,
41711
- message: MESSAGE$12
41775
+ message: MESSAGE$13
41712
41776
  });
41713
41777
  return;
41714
41778
  }
@@ -41716,7 +41780,7 @@ const noStaticElementInteractions = defineRule({
41716
41780
  }
41717
41781
  context.report({
41718
41782
  node: node.name,
41719
- message: MESSAGE$12
41783
+ message: MESSAGE$13
41720
41784
  });
41721
41785
  } };
41722
41786
  }
@@ -41822,7 +41886,7 @@ const noStringRefs = defineRule({
41822
41886
  });
41823
41887
  //#endregion
41824
41888
  //#region src/plugin/rules/js-performance/no-sync-xhr.ts
41825
- const MESSAGE$11 = "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)`).";
41889
+ 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)`).";
41826
41890
  const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
41827
41891
  const PUBLIC_ASSET_PATH_PATTERN = /(?:^|\/)public\//i;
41828
41892
  const noSyncXhr = defineRule({
@@ -41843,7 +41907,7 @@ const noSyncXhr = defineRule({
41843
41907
  if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
41844
41908
  context.report({
41845
41909
  node,
41846
- message: MESSAGE$11
41910
+ message: MESSAGE$12
41847
41911
  });
41848
41912
  };
41849
41913
  return {
@@ -41868,7 +41932,7 @@ const noSyncXhr = defineRule({
41868
41932
  });
41869
41933
  //#endregion
41870
41934
  //#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
41871
- const MESSAGE$10 = "This value is `undefined` because function components have no `this`.";
41935
+ const MESSAGE$11 = "This value is `undefined` because function components have no `this`.";
41872
41936
  const isInsideClassMethod = (node, customClassFactoryNames) => {
41873
41937
  let ancestor = node.parent;
41874
41938
  while (ancestor) {
@@ -41956,7 +42020,7 @@ const noThisInSfc = defineRule({
41956
42020
  if (functionHasOwnThisMemberWrite(enclosingFunction)) return;
41957
42021
  context.report({
41958
42022
  node,
41959
- message: MESSAGE$10
42023
+ message: MESSAGE$11
41960
42024
  });
41961
42025
  } };
41962
42026
  }
@@ -43837,6 +43901,7 @@ const noUnstableNestedComponents = defineRule({
43837
43901
  severity: "warn",
43838
43902
  recommendation: "Move nested components to module scope so React does not remount them and lose state on every render.",
43839
43903
  category: "Performance",
43904
+ tags: ["react-jsx-only"],
43840
43905
  create: (context) => {
43841
43906
  const settings = resolveSettings$8(context.settings);
43842
43907
  const renderPropRegex = compileGlob(settings.propNamePattern);
@@ -44103,7 +44168,7 @@ const noWideLetterSpacing = defineRule({
44103
44168
  //#endregion
44104
44169
  //#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
44105
44170
  const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
44106
- const MESSAGE$9 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
44171
+ const MESSAGE$10 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
44107
44172
  const resolveSettings$7 = (settings) => {
44108
44173
  const reactDoctor = settings?.["react-doctor"];
44109
44174
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
@@ -44137,7 +44202,7 @@ const noWillUpdateSetState = defineRule({
44137
44202
  if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
44138
44203
  context.report({
44139
44204
  node: node.callee,
44140
- message: MESSAGE$9
44205
+ message: MESSAGE$10
44141
44206
  });
44142
44207
  } };
44143
44208
  }
@@ -45133,7 +45198,7 @@ const preactNoRenderArguments = defineRule({
45133
45198
  });
45134
45199
  //#endregion
45135
45200
  //#region src/plugin/rules/preact/preact-prefer-ondblclick.ts
45136
- const MESSAGE$8 = "Your users get no response from `onDoubleClick` in Preact core, where it never fires, so use `onDblClick` instead, which matches the DOM event name.";
45201
+ 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.";
45137
45202
  const preactPreferOndblclick = defineRule({
45138
45203
  id: "preact-prefer-ondblclick",
45139
45204
  title: "onDoubleClick instead of onDblClick",
@@ -45148,7 +45213,7 @@ const preactPreferOndblclick = defineRule({
45148
45213
  if (!onDoubleClickAttribute) return;
45149
45214
  context.report({
45150
45215
  node: onDoubleClickAttribute,
45151
- message: MESSAGE$8
45216
+ message: MESSAGE$9
45152
45217
  });
45153
45218
  } })
45154
45219
  });
@@ -45399,7 +45464,7 @@ const preferExplicitVariants = defineRule({
45399
45464
  });
45400
45465
  //#endregion
45401
45466
  //#region src/plugin/rules/react-builtins/prefer-function-component.ts
45402
- const MESSAGE$7 = "This class component keeps behavior in lifecycle methods, so state and effects are harder to follow than in a hook-based function component.";
45467
+ 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.";
45403
45468
  const resolveSettings$4 = (settings) => {
45404
45469
  const reactDoctor = settings?.["react-doctor"];
45405
45470
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.preferFunctionComponent ?? {} : {};
@@ -45438,7 +45503,7 @@ const preferFunctionComponent = defineRule({
45438
45503
  const reportNode = node.id ?? node;
45439
45504
  context.report({
45440
45505
  node: reportNode,
45441
- message: MESSAGE$7
45506
+ message: MESSAGE$8
45442
45507
  });
45443
45508
  };
45444
45509
  return {
@@ -47547,7 +47612,7 @@ const reactCompilerNoManualMemoization = defineRule({
47547
47612
  });
47548
47613
  //#endregion
47549
47614
  //#region src/plugin/rules/react-builtins/react-in-jsx-scope.ts
47550
- const MESSAGE$6 = "This JSX crashes because `React` isn't in scope.";
47615
+ const MESSAGE$7 = "This JSX crashes because `React` isn't in scope.";
47551
47616
  const reactInJsxScope = defineRule({
47552
47617
  id: "react-in-jsx-scope",
47553
47618
  title: "React not in scope for JSX",
@@ -47559,19 +47624,190 @@ const reactInJsxScope = defineRule({
47559
47624
  if (findVariableInitializer(node, "React")) return;
47560
47625
  context.report({
47561
47626
  node: node.name,
47562
- message: MESSAGE$6
47627
+ message: MESSAGE$7
47563
47628
  });
47564
47629
  },
47565
47630
  JSXFragment(node) {
47566
47631
  if (findVariableInitializer(node, "React")) return;
47567
47632
  context.report({
47568
47633
  node: node.openingFragment,
47569
- message: MESSAGE$6
47634
+ message: MESSAGE$7
47570
47635
  });
47571
47636
  }
47572
47637
  })
47573
47638
  });
47574
47639
  //#endregion
47640
+ //#region src/plugin/rules/security/react-markdown-unsanitized-raw-html.ts
47641
+ 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.";
47642
+ const REACT_MARKDOWN_MODULE = "react-markdown";
47643
+ const REHYPE_RAW_MODULE = "rehype-raw";
47644
+ const REHYPE_SANITIZE_MODULE = "rehype-sanitize";
47645
+ const DOMPURIFY_MODULES = new Set(["dompurify", "isomorphic-dompurify"]);
47646
+ const REACT_MARKDOWN_NAMED_EXPORTS = new Set(["MarkdownAsync", "MarkdownHooks"]);
47647
+ const REACT_MARKDOWN_NAMESPACE_EXPORTS = new Set(["default", ...REACT_MARKDOWN_NAMED_EXPORTS]);
47648
+ const DEFAULT_EXPORT_NAMES = new Set(["default"]);
47649
+ const getImportDeclaration = (symbol) => {
47650
+ if (symbol.kind !== "import") return null;
47651
+ const importDeclaration = symbol.declarationNode.parent;
47652
+ return isNodeOfType(importDeclaration, "ImportDeclaration") ? importDeclaration : null;
47653
+ };
47654
+ const isImportFromModule = (symbol, moduleName) => getImportDeclaration(symbol)?.source.value === moduleName;
47655
+ const isDefaultImportSymbol = (symbol, moduleName) => {
47656
+ if (!isImportFromModule(symbol, moduleName)) return false;
47657
+ return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "default";
47658
+ };
47659
+ const resolveImportedIdentifier = (node, scopes) => {
47660
+ if (!isNodeOfType(node, "Identifier") && !isNodeOfType(node, "JSXIdentifier")) return null;
47661
+ const symbol = resolveConstIdentifierAlias(node, scopes);
47662
+ return symbol?.kind === "import" ? symbol : null;
47663
+ };
47664
+ const getStaticMemberParts = (node) => {
47665
+ if (isNodeOfType(node, "MemberExpression")) {
47666
+ if (node.computed || !isNodeOfType(node.property, "Identifier")) return null;
47667
+ return {
47668
+ object: node.object,
47669
+ propertyName: node.property.name
47670
+ };
47671
+ }
47672
+ if (isNodeOfType(node, "JSXMemberExpression")) {
47673
+ if (!isNodeOfType(node.property, "JSXIdentifier")) return null;
47674
+ return {
47675
+ object: node.object,
47676
+ propertyName: node.property.name
47677
+ };
47678
+ }
47679
+ return null;
47680
+ };
47681
+ const isNamespaceMemberFromModule = (node, moduleName, memberNames, scopes) => {
47682
+ const memberParts = getStaticMemberParts(node);
47683
+ if (!memberParts || !memberNames.has(memberParts.propertyName)) return false;
47684
+ const namespaceSymbol = resolveImportedIdentifier(memberParts.object, scopes);
47685
+ return Boolean(namespaceSymbol && isImportFromModule(namespaceSymbol, moduleName) && isNodeOfType(namespaceSymbol.declarationNode, "ImportNamespaceSpecifier"));
47686
+ };
47687
+ const isReactMarkdownComponent = (node, scopes) => {
47688
+ if (isNodeOfType(node, "JSXIdentifier")) {
47689
+ const symbol = resolveImportedIdentifier(node, scopes);
47690
+ if (!symbol || !isImportFromModule(symbol, REACT_MARKDOWN_MODULE)) return false;
47691
+ if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) return true;
47692
+ const importedName = getImportedName(symbol.declarationNode);
47693
+ return importedName === "default" || Boolean(importedName && REACT_MARKDOWN_NAMED_EXPORTS.has(importedName));
47694
+ }
47695
+ return isNamespaceMemberFromModule(node, REACT_MARKDOWN_MODULE, REACT_MARKDOWN_NAMESPACE_EXPORTS, scopes);
47696
+ };
47697
+ const isPluginFromModule = (rawNode, moduleName, scopes, visitedSymbolIds) => {
47698
+ const node = stripParenExpression(rawNode);
47699
+ if (isNodeOfType(node, "Identifier")) {
47700
+ const symbol = resolveConstIdentifierAlias(node, scopes);
47701
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
47702
+ if (isDefaultImportSymbol(symbol, moduleName)) return true;
47703
+ if (symbol.kind !== "const" || !symbol.initializer) return false;
47704
+ visitedSymbolIds.add(symbol.id);
47705
+ return isPluginFromModule(symbol.initializer, moduleName, scopes, visitedSymbolIds);
47706
+ }
47707
+ if (isNamespaceMemberFromModule(node, moduleName, DEFAULT_EXPORT_NAMES, scopes)) return true;
47708
+ if (!isNodeOfType(node, "ArrayExpression")) return false;
47709
+ for (const element of node.elements) {
47710
+ if (!element || isNodeOfType(element, "SpreadElement")) continue;
47711
+ return isPluginFromModule(element, moduleName, scopes, visitedSymbolIds);
47712
+ }
47713
+ return false;
47714
+ };
47715
+ const collectPluginEntries = (rawNode, scopes, visitedSymbolIds) => {
47716
+ const node = stripParenExpression(rawNode);
47717
+ if (isNodeOfType(node, "Identifier")) {
47718
+ const symbol = scopes.referenceFor(node)?.resolvedSymbol;
47719
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
47720
+ visitedSymbolIds.add(symbol.id);
47721
+ return collectPluginEntries(symbol.initializer, scopes, visitedSymbolIds);
47722
+ }
47723
+ if (!isNodeOfType(node, "ArrayExpression")) return null;
47724
+ const entries = [];
47725
+ for (const element of node.elements) {
47726
+ if (!element) continue;
47727
+ if (isNodeOfType(element, "SpreadElement")) {
47728
+ const spreadEntries = collectPluginEntries(element.argument, scopes, new Set(visitedSymbolIds));
47729
+ if (spreadEntries === null) return null;
47730
+ entries.push(...spreadEntries);
47731
+ continue;
47732
+ }
47733
+ entries.push(element);
47734
+ }
47735
+ return entries;
47736
+ };
47737
+ const findEffectiveExplicitAttribute = (attributes, attributeName) => {
47738
+ for (let attributeIndex = attributes.length - 1; attributeIndex >= 0; attributeIndex -= 1) {
47739
+ const attribute = attributes[attributeIndex];
47740
+ if (!attribute || isNodeOfType(attribute, "JSXSpreadAttribute")) return null;
47741
+ if (isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && attribute.name.name === attributeName) return attribute;
47742
+ }
47743
+ return null;
47744
+ };
47745
+ const getAttributeExpression = (attribute) => {
47746
+ if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
47747
+ return isNodeOfType(attribute.value.expression, "JSXEmptyExpression") ? null : attribute.value.expression;
47748
+ };
47749
+ const isDomPurifyNamespace = (node, scopes) => {
47750
+ const symbol = resolveImportedIdentifier(node, scopes);
47751
+ if (!symbol) return false;
47752
+ const importDeclaration = getImportDeclaration(symbol);
47753
+ if (!importDeclaration || !DOMPURIFY_MODULES.has(String(importDeclaration.source.value))) return false;
47754
+ return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
47755
+ };
47756
+ const isDomPurifySanitizeCall = (node, scopes) => {
47757
+ if (!isNodeOfType(node, "CallExpression")) return false;
47758
+ const memberParts = getStaticMemberParts(stripParenExpression(node.callee));
47759
+ return Boolean(memberParts && memberParts.propertyName === "sanitize" && isDomPurifyNamespace(memberParts.object, scopes));
47760
+ };
47761
+ const isStaticOrSanitizedMarkdownExpression = (rawNode, scopes, visitedSymbolIds) => {
47762
+ const node = stripParenExpression(rawNode);
47763
+ if (isNodeOfType(node, "Literal")) return true;
47764
+ if (isNodeOfType(node, "TemplateLiteral")) return node.expressions.every((expression) => isStaticOrSanitizedMarkdownExpression(expression, scopes, new Set(visitedSymbolIds)));
47765
+ if (isDomPurifySanitizeCall(node, scopes)) return true;
47766
+ if (isNodeOfType(node, "ConditionalExpression")) return isStaticOrSanitizedMarkdownExpression(node.consequent, scopes, new Set(visitedSymbolIds)) && isStaticOrSanitizedMarkdownExpression(node.alternate, scopes, new Set(visitedSymbolIds));
47767
+ if (isNodeOfType(node, "BinaryExpression") && node.operator === "+") return isStaticOrSanitizedMarkdownExpression(node.left, scopes, new Set(visitedSymbolIds)) && isStaticOrSanitizedMarkdownExpression(node.right, scopes, new Set(visitedSymbolIds));
47768
+ if (!isNodeOfType(node, "Identifier")) return false;
47769
+ const symbol = scopes.referenceFor(node)?.resolvedSymbol;
47770
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
47771
+ visitedSymbolIds.add(symbol.id);
47772
+ return isStaticOrSanitizedMarkdownExpression(symbol.initializer, scopes, visitedSymbolIds);
47773
+ };
47774
+ const hasDynamicUnsanitizedChildren = (openingElement, scopes) => {
47775
+ const jsxElement = openingElement.parent;
47776
+ if (!isNodeOfType(jsxElement, "JSXElement")) return false;
47777
+ const meaningfulChildren = jsxElement.children.filter((child) => !isNodeOfType(child, "JSXText") || child.value.trim().length > 0);
47778
+ if (meaningfulChildren.length > 0) return meaningfulChildren.some((child) => {
47779
+ if (isNodeOfType(child, "JSXText")) return false;
47780
+ if (!isNodeOfType(child, "JSXExpressionContainer")) return true;
47781
+ if (isNodeOfType(child.expression, "JSXEmptyExpression")) return false;
47782
+ return !isStaticOrSanitizedMarkdownExpression(child.expression, scopes, /* @__PURE__ */ new Set());
47783
+ });
47784
+ const childrenAttribute = findEffectiveExplicitAttribute(openingElement.attributes, "children");
47785
+ if (!childrenAttribute) return false;
47786
+ const childrenExpression = getAttributeExpression(childrenAttribute);
47787
+ return Boolean(childrenExpression && !isStaticOrSanitizedMarkdownExpression(childrenExpression, scopes, /* @__PURE__ */ new Set()));
47788
+ };
47789
+ const reactMarkdownUnsanitizedRawHtml = defineRule({
47790
+ id: "react-markdown-unsanitized-raw-html",
47791
+ title: "Unsanitized raw HTML in React Markdown",
47792
+ severity: "warn",
47793
+ recommendation: "Add `rehype-sanitize` to `rehypePlugins` or sanitize dynamic markdown before rendering. `skipHtml` does not disable HTML already parsed by `rehype-raw`.",
47794
+ create: skipNonProductionFiles((context) => ({ JSXOpeningElement(node) {
47795
+ if (!isReactMarkdownComponent(node.name, context.scopes)) return;
47796
+ const pluginsAttribute = findEffectiveExplicitAttribute(node.attributes, "rehypePlugins");
47797
+ if (!pluginsAttribute) return;
47798
+ const pluginsExpression = getAttributeExpression(pluginsAttribute);
47799
+ if (!pluginsExpression) return;
47800
+ const pluginEntries = collectPluginEntries(pluginsExpression, context.scopes, /* @__PURE__ */ new Set());
47801
+ if (pluginEntries === null) return;
47802
+ if (!pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_RAW_MODULE, context.scopes, /* @__PURE__ */ new Set()))) return;
47803
+ if (pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_SANITIZE_MODULE, context.scopes, /* @__PURE__ */ new Set())) || !hasDynamicUnsanitizedChildren(node, context.scopes)) return;
47804
+ context.report({
47805
+ node,
47806
+ message: MESSAGE$6
47807
+ });
47808
+ } }))
47809
+ });
47810
+ //#endregion
47575
47811
  //#region src/plugin/utils/collect-react-redux-selector-aliases.ts
47576
47812
  const REACT_REDUX_MODULE = "react-redux";
47577
47813
  const collectReactReduxSelectorAliases = (programRoot) => {
@@ -62805,6 +63041,17 @@ const reactDoctorRules = [
62805
63041
  requires: [...new Set(["react", ...reactInJsxScope.requires ?? []])]
62806
63042
  }
62807
63043
  },
63044
+ {
63045
+ key: "react-doctor/react-markdown-unsanitized-raw-html",
63046
+ id: "react-markdown-unsanitized-raw-html",
63047
+ source: "react-doctor",
63048
+ originallyExternal: false,
63049
+ rule: {
63050
+ ...reactMarkdownUnsanitizedRawHtml,
63051
+ framework: "global",
63052
+ category: "Security"
63053
+ }
63054
+ },
62808
63055
  {
62809
63056
  key: "react-doctor/redux-useselector-inline-derivation",
62810
63057
  id: "redux-useselector-inline-derivation",