oxlint-plugin-react-doctor 0.5.6-dev.b08ca1c → 0.5.6-dev.b317164

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +126 -0
  2. package/dist/index.js +312 -137
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -890,24 +890,64 @@ const advancedEventHandlerRefs = defineRule({
890
890
  });
891
891
  //#endregion
892
892
  //#region src/plugin/rules/security-scan/utils/strip-comments-preserving-positions.ts
893
- const stripCommentsPreservingPositions = (content) => {
893
+ const WHITESPACE_PATTERN = /\s/;
894
+ const quotedLiteralHasWhitespace = (content, openQuoteIndex, delimiter) => {
895
+ for (let cursor = openQuoteIndex + 1; cursor < content.length; cursor += 1) {
896
+ const character = content[cursor];
897
+ if (character === "\\") {
898
+ cursor += 1;
899
+ continue;
900
+ }
901
+ if (character === delimiter) return false;
902
+ if (WHITESPACE_PATTERN.test(character)) return true;
903
+ }
904
+ return false;
905
+ };
906
+ const blankNonCodePreservingPositions = (content, blankStringContents) => {
894
907
  const characters = content.split("");
895
908
  let stringDelimiter = null;
909
+ let isBlankingString = false;
910
+ const templateExpressionDepths = [];
896
911
  let index = 0;
912
+ const blankUnlessNewline = (offset) => {
913
+ if (offset < content.length && content[offset] !== "\n") characters[offset] = " ";
914
+ };
897
915
  while (index < content.length) {
898
916
  const character = content[index];
899
917
  const nextCharacter = content[index + 1];
900
918
  if (stringDelimiter !== null) {
901
919
  if (character === "\\") {
920
+ if (isBlankingString) {
921
+ blankUnlessNewline(index);
922
+ blankUnlessNewline(index + 1);
923
+ }
902
924
  index += 2;
903
925
  continue;
904
926
  }
905
- if (character === stringDelimiter) stringDelimiter = null;
927
+ if (character === stringDelimiter) {
928
+ stringDelimiter = null;
929
+ index += 1;
930
+ continue;
931
+ }
932
+ if (blankStringContents && stringDelimiter === "`" && character === "$" && nextCharacter === "{") {
933
+ templateExpressionDepths.push(0);
934
+ stringDelimiter = null;
935
+ index += 2;
936
+ continue;
937
+ }
938
+ if (isBlankingString) blankUnlessNewline(index);
906
939
  index += 1;
907
940
  continue;
908
941
  }
909
- if (character === "\"" || character === "'" || character === "`") {
942
+ if (character === "\"" || character === "'") {
910
943
  stringDelimiter = character;
944
+ isBlankingString = blankStringContents && quotedLiteralHasWhitespace(content, index, character);
945
+ index += 1;
946
+ continue;
947
+ }
948
+ if (character === "`") {
949
+ stringDelimiter = "`";
950
+ isBlankingString = blankStringContents;
911
951
  index += 1;
912
952
  continue;
913
953
  }
@@ -926,29 +966,42 @@ const stripCommentsPreservingPositions = (content) => {
926
966
  index += 2;
927
967
  break;
928
968
  }
929
- if (content[index] !== "\n") characters[index] = " ";
969
+ blankUnlessNewline(index);
930
970
  index += 1;
931
971
  }
932
972
  continue;
933
973
  }
974
+ if (templateExpressionDepths.length > 0) {
975
+ const innermost = templateExpressionDepths.length - 1;
976
+ if (character === "{") templateExpressionDepths[innermost] += 1;
977
+ else if (character === "}") if (templateExpressionDepths[innermost] === 0) {
978
+ templateExpressionDepths.pop();
979
+ stringDelimiter = "`";
980
+ isBlankingString = blankStringContents;
981
+ } else templateExpressionDepths[innermost] -= 1;
982
+ }
934
983
  index += 1;
935
984
  }
936
985
  return characters.join("");
937
986
  };
987
+ const stripCommentsPreservingPositions = (content) => blankNonCodePreservingPositions(content, false);
988
+ const stripCommentsAndStringLiteralsPreservingPositions = (content) => blankNonCodePreservingPositions(content, true);
938
989
  //#endregion
939
990
  //#region src/plugin/rules/security-scan/utils/scan-by-pattern.ts
940
991
  const strippedContentCache = /* @__PURE__ */ new WeakMap();
941
- const getScannableContent = (file) => {
992
+ const stringStrippedContentCache = /* @__PURE__ */ new WeakMap();
993
+ const getScannableContent = (file, ignoreStringLiterals = false) => {
942
994
  if (!SOURCE_FILE_PATTERN.test(file.relativePath)) return file.content;
943
- const cachedContent = strippedContentCache.get(file);
995
+ const cache = ignoreStringLiterals ? stringStrippedContentCache : strippedContentCache;
996
+ const cachedContent = cache.get(file);
944
997
  if (cachedContent !== void 0) return cachedContent;
945
- const strippedContent = stripCommentsPreservingPositions(file.content);
946
- strippedContentCache.set(file, strippedContent);
998
+ const strippedContent = ignoreStringLiterals ? stripCommentsAndStringLiteralsPreservingPositions(file.content) : stripCommentsPreservingPositions(file.content);
999
+ cache.set(file, strippedContent);
947
1000
  return strippedContent;
948
1001
  };
949
- const scanByPattern = ({ shouldScan, pattern, requireAll, suppressWhen, message }) => (file) => {
1002
+ const scanByPattern = ({ shouldScan, pattern, requireAll, suppressWhen, ignoreStringLiterals, message }) => (file) => {
950
1003
  if (!shouldScan(file)) return [];
951
- const content = getScannableContent(file);
1004
+ const content = getScannableContent(file, ignoreStringLiterals);
952
1005
  if (requireAll !== void 0 && !requireAll.every((gate) => gate.test(content))) return [];
953
1006
  const matchedPattern = (pattern instanceof RegExp ? [pattern] : pattern).find((candidate) => candidate.test(content));
954
1007
  if (matchedPattern === void 0) return [];
@@ -973,6 +1026,7 @@ const agentToolCapabilityRisk = defineRule({
973
1026
  shouldScan: (file) => isProductionSourcePath(file.relativePath) && AGENT_TOOL_CONTEXT_PATH_PATTERN.test(file.relativePath),
974
1027
  pattern: AGENT_TOOL_DEFINITION_PATTERN,
975
1028
  requireAll: [AGENT_TOOL_DANGEROUS_CAPABILITY_PATTERN],
1029
+ ignoreStringLiterals: true,
976
1030
  message: "An agent-callable tool appears to expose network, filesystem, shell, or code-execution capability."
977
1031
  })
978
1032
  });
@@ -1861,7 +1915,7 @@ const anchorAmbiguousText = defineRule({
1861
1915
  });
1862
1916
  //#endregion
1863
1917
  //#region src/plugin/rules/a11y/anchor-has-content.ts
1864
- const MESSAGE$57 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
1918
+ const MESSAGE$59 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
1865
1919
  const anchorHasContent = defineRule({
1866
1920
  id: "anchor-has-content",
1867
1921
  title: "Anchor has no content",
@@ -1877,7 +1931,7 @@ const anchorHasContent = defineRule({
1877
1931
  for (const attribute of ["title", "aria-label"]) if (hasJsxPropIgnoreCase(opening.attributes, attribute)) return;
1878
1932
  context.report({
1879
1933
  node: opening.name,
1880
- message: MESSAGE$57
1934
+ message: MESSAGE$59
1881
1935
  });
1882
1936
  } })
1883
1937
  });
@@ -2271,7 +2325,7 @@ const parseJsxValue = (value) => {
2271
2325
  };
2272
2326
  //#endregion
2273
2327
  //#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
2274
- const MESSAGE$56 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
2328
+ const MESSAGE$58 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
2275
2329
  const ariaActivedescendantHasTabindex = defineRule({
2276
2330
  id: "aria-activedescendant-has-tabindex",
2277
2331
  title: "aria-activedescendant missing tabindex",
@@ -2289,14 +2343,14 @@ const ariaActivedescendantHasTabindex = defineRule({
2289
2343
  if (tabIndexValue === null || tabIndexValue >= -1) return;
2290
2344
  context.report({
2291
2345
  node: node.name,
2292
- message: MESSAGE$56
2346
+ message: MESSAGE$58
2293
2347
  });
2294
2348
  return;
2295
2349
  }
2296
2350
  if (isInteractiveElement(tag, node)) return;
2297
2351
  context.report({
2298
2352
  node: node.name,
2299
- message: MESSAGE$56
2353
+ message: MESSAGE$58
2300
2354
  });
2301
2355
  } })
2302
2356
  });
@@ -3085,7 +3139,7 @@ const artifactBaasAuthoritySurface = defineRule({
3085
3139
  scan: scanByPattern({
3086
3140
  shouldScan: (file) => isBrowserArtifactPath(file.relativePath, file.isGeneratedBundle),
3087
3141
  pattern: /\b(?:collection\s*\(\s*["'](?:boosts|sessions|sessions_admin|users|orgs|candidateJobs|conversations|documents|profiles)|from\s*\(\s*["'](?:users|profiles|documents|organizations|memberships)|creatorID|creatorId|providerId|ghostOrg|ownerId|orgId|tenantId|workspaceId|role|roles|isAdmin|SuperAdmin)\b/i,
3088
- requireAll: [/\b(?:initializeApp|firebase|firestore|getFirestore|createClient)\b[\s\S]{0,700}\b(?:apiKey|authDomain|projectId|databaseURL|storageBucket|supabase|SUPABASE_URL)\b|\b(?:apiKey|authDomain|projectId|databaseURL|storageBucket)\b[\s\S]{0,700}\b(?:firebase|firestore|getFirestore|initializeApp)\b/i],
3142
+ requireAll: [/\b(?:initializeApp|firebase|firestore|getFirestore)\b[\s\S]{0,700}\b(?:apiKey|authDomain|projectId|databaseURL|storageBucket)\b|\b(?:apiKey|authDomain|projectId|databaseURL|storageBucket)\b[\s\S]{0,700}\b(?:firebase|firestore|getFirestore|initializeApp)\b|\bcreateClient\b[\s\S]{0,700}\b(?:supabase|SUPABASE_URL)\b|\b(?:supabase|SUPABASE_URL)\b[\s\S]{0,700}\bcreateClient\b/i],
3089
3143
  message: "A browser artifact exposes Firebase/Supabase config together with sensitive collections or authorization fields."
3090
3144
  })
3091
3145
  });
@@ -4272,7 +4326,7 @@ const asyncParallel = defineRule({
4272
4326
  });
4273
4327
  //#endregion
4274
4328
  //#region src/plugin/rules/security/auth-token-in-web-storage.ts
4275
- const MESSAGE$55 = "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.";
4329
+ const MESSAGE$57 = "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.";
4276
4330
  const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
4277
4331
  const STORAGE_GLOBALS = new Set([
4278
4332
  "window",
@@ -4306,7 +4360,7 @@ const authTokenInWebStorage = defineRule({
4306
4360
  if (!SENSITIVE_KEY_PATTERN.test(keyArgument.value)) return;
4307
4361
  context.report({
4308
4362
  node,
4309
- message: MESSAGE$55
4363
+ message: MESSAGE$57
4310
4364
  });
4311
4365
  },
4312
4366
  AssignmentExpression(node) {
@@ -4317,7 +4371,7 @@ const authTokenInWebStorage = defineRule({
4317
4371
  if (!propertyName || !SENSITIVE_KEY_PATTERN.test(propertyName)) return;
4318
4372
  context.report({
4319
4373
  node: target,
4320
- message: MESSAGE$55
4374
+ message: MESSAGE$57
4321
4375
  });
4322
4376
  }
4323
4377
  })
@@ -4694,7 +4748,7 @@ const isPureEventBlockerHandler = (attribute) => {
4694
4748
  //#endregion
4695
4749
  //#region src/plugin/rules/a11y/click-events-have-key-events.ts
4696
4750
  const PRESENTATION_ROLES$1 = new Set(["presentation", "none"]);
4697
- const MESSAGE$54 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
4751
+ const MESSAGE$56 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
4698
4752
  const KEY_HANDLERS = [
4699
4753
  "onKeyUp",
4700
4754
  "onKeyDown",
@@ -4726,7 +4780,7 @@ const clickEventsHaveKeyEvents = defineRule({
4726
4780
  if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
4727
4781
  context.report({
4728
4782
  node: node.name,
4729
- message: MESSAGE$54
4783
+ message: MESSAGE$56
4730
4784
  });
4731
4785
  } };
4732
4786
  }
@@ -4841,7 +4895,7 @@ const isReactComponentName = (name) => {
4841
4895
  };
4842
4896
  //#endregion
4843
4897
  //#region src/plugin/rules/a11y/control-has-associated-label.ts
4844
- const MESSAGE$53 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
4898
+ const MESSAGE$55 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
4845
4899
  const DEFAULT_IGNORE_ELEMENTS = ["link", "canvas"];
4846
4900
  const DEFAULT_LABELLING_PROPS = [
4847
4901
  "alt",
@@ -5002,7 +5056,7 @@ const controlHasAssociatedLabel = defineRule({
5002
5056
  for (const child of node.children) if (checkChildForLabel(child, 1, checkContext)) return;
5003
5057
  context.report({
5004
5058
  node: opening,
5005
- message: MESSAGE$53
5059
+ message: MESSAGE$55
5006
5060
  });
5007
5061
  } };
5008
5062
  }
@@ -5432,7 +5486,7 @@ const noVagueButtonLabel = defineRule({
5432
5486
  const hasJsxSpreadAttribute$1 = (attributes) => attributes.some((attribute) => isNodeOfType(attribute, "JSXSpreadAttribute"));
5433
5487
  //#endregion
5434
5488
  //#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
5435
- const MESSAGE$52 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
5489
+ const MESSAGE$54 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
5436
5490
  const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
5437
5491
  const NAME_PROVIDING_ATTRIBUTES = [
5438
5492
  "aria-label",
@@ -5455,7 +5509,7 @@ const dialogHasAccessibleName = defineRule({
5455
5509
  if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
5456
5510
  context.report({
5457
5511
  node: node.name,
5458
- message: MESSAGE$52
5512
+ message: MESSAGE$54
5459
5513
  });
5460
5514
  } })
5461
5515
  });
@@ -5494,7 +5548,7 @@ const isEs6Component = (node) => {
5494
5548
  };
5495
5549
  //#endregion
5496
5550
  //#region src/plugin/rules/react-builtins/display-name.ts
5497
- const MESSAGE$51 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
5551
+ const MESSAGE$53 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
5498
5552
  const DEFAULT_ADDITIONAL_HOCS = [
5499
5553
  "observer",
5500
5554
  "lazy",
@@ -5697,7 +5751,7 @@ const displayName = defineRule({
5697
5751
  const reportAt = (node) => {
5698
5752
  context.report({
5699
5753
  node,
5700
- message: MESSAGE$51
5754
+ message: MESSAGE$53
5701
5755
  });
5702
5756
  };
5703
5757
  return {
@@ -7845,7 +7899,7 @@ const forbidElements = defineRule({
7845
7899
  });
7846
7900
  //#endregion
7847
7901
  //#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
7848
- const MESSAGE$50 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
7902
+ const MESSAGE$52 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
7849
7903
  const forwardRefUsesRef = defineRule({
7850
7904
  id: "forward-ref-uses-ref",
7851
7905
  title: "forwardRef without ref parameter",
@@ -7865,7 +7919,7 @@ const forwardRefUsesRef = defineRule({
7865
7919
  if (isNodeOfType(onlyParam, "RestElement")) return;
7866
7920
  context.report({
7867
7921
  node: inner,
7868
- message: MESSAGE$50
7922
+ message: MESSAGE$52
7869
7923
  });
7870
7924
  } })
7871
7925
  });
@@ -7902,7 +7956,7 @@ const gitProviderUrlInjectionRisk = defineRule({
7902
7956
  });
7903
7957
  //#endregion
7904
7958
  //#region src/plugin/rules/a11y/heading-has-content.ts
7905
- const MESSAGE$49 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
7959
+ const MESSAGE$51 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
7906
7960
  const DEFAULT_HEADING_TAGS = [
7907
7961
  "h1",
7908
7962
  "h2",
@@ -7935,7 +7989,7 @@ const headingHasContent = defineRule({
7935
7989
  if (isHiddenFromScreenReader(node, context.settings)) return;
7936
7990
  context.report({
7937
7991
  node,
7938
- message: MESSAGE$49
7992
+ message: MESSAGE$51
7939
7993
  });
7940
7994
  } };
7941
7995
  }
@@ -8073,7 +8127,7 @@ const hooksNoNanInDeps = defineRule({
8073
8127
  });
8074
8128
  //#endregion
8075
8129
  //#region src/plugin/rules/a11y/html-has-lang.ts
8076
- const MESSAGE$48 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
8130
+ const MESSAGE$50 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
8077
8131
  const resolveSettings$38 = (settings) => {
8078
8132
  const reactDoctor = settings?.["react-doctor"];
8079
8133
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
@@ -8121,7 +8175,7 @@ const htmlHasLang = defineRule({
8121
8175
  if (!lang) {
8122
8176
  context.report({
8123
8177
  node: node.name,
8124
- message: MESSAGE$48
8178
+ message: MESSAGE$50
8125
8179
  });
8126
8180
  return;
8127
8181
  }
@@ -8129,13 +8183,13 @@ const htmlHasLang = defineRule({
8129
8183
  if (verdict === "missing" || verdict === "empty") {
8130
8184
  context.report({
8131
8185
  node: lang,
8132
- message: MESSAGE$48
8186
+ message: MESSAGE$50
8133
8187
  });
8134
8188
  return;
8135
8189
  }
8136
8190
  if (hasSpread && !lang) context.report({
8137
8191
  node: node.name,
8138
- message: MESSAGE$48
8192
+ message: MESSAGE$50
8139
8193
  });
8140
8194
  } };
8141
8195
  }
@@ -8349,7 +8403,7 @@ const htmlNoNestedInteractive = defineRule({
8349
8403
  });
8350
8404
  //#endregion
8351
8405
  //#region src/plugin/rules/a11y/iframe-has-title.ts
8352
- const MESSAGE$47 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
8406
+ const MESSAGE$49 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
8353
8407
  const evaluateTitleValue = (value) => {
8354
8408
  if (!value) return "missing";
8355
8409
  if (isNodeOfType(value, "Literal")) {
@@ -8389,14 +8443,14 @@ const iframeHasTitle = defineRule({
8389
8443
  if (!titleAttr) {
8390
8444
  if (hasSpread || tag === "iframe") context.report({
8391
8445
  node: node.name,
8392
- message: MESSAGE$47
8446
+ message: MESSAGE$49
8393
8447
  });
8394
8448
  return;
8395
8449
  }
8396
8450
  const verdict = evaluateTitleValue(titleAttr.value);
8397
8451
  if (verdict === "missing" || verdict === "empty") context.report({
8398
8452
  node: titleAttr,
8399
- message: MESSAGE$47
8453
+ message: MESSAGE$49
8400
8454
  });
8401
8455
  } })
8402
8456
  });
@@ -8500,7 +8554,7 @@ const iframeMissingSandbox = defineRule({
8500
8554
  });
8501
8555
  //#endregion
8502
8556
  //#region src/plugin/rules/a11y/img-redundant-alt.ts
8503
- const MESSAGE$46 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
8557
+ const MESSAGE$48 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
8504
8558
  const DEFAULT_COMPONENTS = ["img"];
8505
8559
  const DEFAULT_REDUNDANT_WORDS = [
8506
8560
  "image",
@@ -8565,7 +8619,7 @@ const imgRedundantAlt = defineRule({
8565
8619
  if (!altAttribute) return;
8566
8620
  if (altValueRedundant(altAttribute, settings.words)) context.report({
8567
8621
  node: altAttribute,
8568
- message: MESSAGE$46
8622
+ message: MESSAGE$48
8569
8623
  });
8570
8624
  } };
8571
8625
  }
@@ -10922,7 +10976,7 @@ const jsxMaxDepth = defineRule({
10922
10976
  });
10923
10977
  //#endregion
10924
10978
  //#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
10925
- const MESSAGE$45 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
10979
+ const MESSAGE$47 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
10926
10980
  const LITERAL_TEXT_TAGS = new Set([
10927
10981
  "code",
10928
10982
  "pre",
@@ -10958,7 +11012,7 @@ const jsxNoCommentTextnodes = defineRule({
10958
11012
  if (isInsideLiteralTextTag(node)) return;
10959
11013
  context.report({
10960
11014
  node,
10961
- message: MESSAGE$45
11015
+ message: MESSAGE$47
10962
11016
  });
10963
11017
  } })
10964
11018
  });
@@ -10989,7 +11043,7 @@ const isInsideFunctionScope = (node) => {
10989
11043
  };
10990
11044
  //#endregion
10991
11045
  //#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
10992
- const MESSAGE$44 = "Every reader of this context redraws on each render because you build its `value` inline.";
11046
+ const MESSAGE$46 = "Every reader of this context redraws on each render because you build its `value` inline.";
10993
11047
  const CONTEXT_MODULES$1 = [
10994
11048
  "react",
10995
11049
  "use-context-selector",
@@ -11087,7 +11141,7 @@ const jsxNoConstructedContextValues = defineRule({
11087
11141
  if (!isConstructedValue(innerExpression)) continue;
11088
11142
  context.report({
11089
11143
  node: attribute,
11090
- message: MESSAGE$44
11144
+ message: MESSAGE$46
11091
11145
  });
11092
11146
  }
11093
11147
  }
@@ -11173,7 +11227,7 @@ const isJsxAttributeOnIntrinsicHtmlElement = (attribute) => {
11173
11227
  };
11174
11228
  //#endregion
11175
11229
  //#region src/plugin/rules/react-builtins/jsx-no-jsx-as-prop.ts
11176
- const MESSAGE$43 = "This child redraws every render because the prop gets brand new JSX each time.";
11230
+ const MESSAGE$45 = "This child redraws every render because the prop gets brand new JSX each time.";
11177
11231
  const KNOWN_SLOT_PROP_NAMES = new Set([
11178
11232
  "icon",
11179
11233
  "Icon",
@@ -11442,7 +11496,7 @@ const jsxNoJsxAsProp = defineRule({
11442
11496
  if (!isJsxProducingExpression(expressionNode) && !followsRenderLocalJsxBinding(expressionNode, node)) return;
11443
11497
  context.report({
11444
11498
  node,
11445
- message: MESSAGE$43
11499
+ message: MESSAGE$45
11446
11500
  });
11447
11501
  }
11448
11502
  };
@@ -11730,7 +11784,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
11730
11784
  ];
11731
11785
  //#endregion
11732
11786
  //#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
11733
- const MESSAGE$42 = "This child redraws every render because the prop gets a brand new array each time.";
11787
+ const MESSAGE$44 = "This child redraws every render because the prop gets a brand new array each time.";
11734
11788
  const isDataArrayPropName = (propName) => {
11735
11789
  if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
11736
11790
  for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -11814,7 +11868,7 @@ const jsxNoNewArrayAsProp = defineRule({
11814
11868
  if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
11815
11869
  context.report({
11816
11870
  node,
11817
- message: MESSAGE$42
11871
+ message: MESSAGE$44
11818
11872
  });
11819
11873
  }
11820
11874
  };
@@ -12072,7 +12126,7 @@ const SAFE_RECEIVER_NAMES = new Set([
12072
12126
  ]);
12073
12127
  //#endregion
12074
12128
  //#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
12075
- const MESSAGE$41 = "This child redraws every render because the prop gets a brand new function each time.";
12129
+ const MESSAGE$43 = "This child redraws every render because the prop gets a brand new function each time.";
12076
12130
  const isAccessorPredicateName = (propName) => {
12077
12131
  for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
12078
12132
  if (propName.length <= prefix.length) continue;
@@ -12278,7 +12332,7 @@ const jsxNoNewFunctionAsProp = defineRule({
12278
12332
  if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
12279
12333
  context.report({
12280
12334
  node,
12281
- message: MESSAGE$41
12335
+ message: MESSAGE$43
12282
12336
  });
12283
12337
  }
12284
12338
  };
@@ -12498,7 +12552,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
12498
12552
  ];
12499
12553
  //#endregion
12500
12554
  //#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
12501
- const MESSAGE$40 = "This child redraws every render because the prop gets a brand new object each time.";
12555
+ const MESSAGE$42 = "This child redraws every render because the prop gets a brand new object each time.";
12502
12556
  const isConfigObjectPropName = (propName) => {
12503
12557
  if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
12504
12558
  for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -12586,7 +12640,7 @@ const jsxNoNewObjectAsProp = defineRule({
12586
12640
  if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
12587
12641
  context.report({
12588
12642
  node,
12589
- message: MESSAGE$40
12643
+ message: MESSAGE$42
12590
12644
  });
12591
12645
  }
12592
12646
  };
@@ -12594,7 +12648,7 @@ const jsxNoNewObjectAsProp = defineRule({
12594
12648
  });
12595
12649
  //#endregion
12596
12650
  //#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
12597
- const MESSAGE$39 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
12651
+ const MESSAGE$41 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
12598
12652
  const JAVASCRIPT_URL_PATTERN = /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;
12599
12653
  const resolveSettings$28 = (settings) => {
12600
12654
  const reactDoctor = settings?.["react-doctor"];
@@ -12635,7 +12689,7 @@ const jsxNoScriptUrl = defineRule({
12635
12689
  if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
12636
12690
  if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
12637
12691
  node: attribute,
12638
- message: MESSAGE$39
12692
+ message: MESSAGE$41
12639
12693
  });
12640
12694
  }
12641
12695
  } };
@@ -12950,7 +13004,7 @@ const jsxPropsNoSpreadMulti = defineRule({
12950
13004
  });
12951
13005
  //#endregion
12952
13006
  //#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
12953
- const MESSAGE$38 = "You can't tell what props reach this element when you spread them.";
13007
+ const MESSAGE$40 = "You can't tell what props reach this element when you spread them.";
12954
13008
  const resolveSettings$25 = (settings) => {
12955
13009
  const reactDoctor = settings?.["react-doctor"];
12956
13010
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
@@ -12991,7 +13045,7 @@ const jsxPropsNoSpreading = defineRule({
12991
13045
  }
12992
13046
  context.report({
12993
13047
  node: attribute,
12994
- message: MESSAGE$38
13048
+ message: MESSAGE$40
12995
13049
  });
12996
13050
  }
12997
13051
  } };
@@ -13219,7 +13273,7 @@ const labelHasAssociatedControl = defineRule({
13219
13273
  });
13220
13274
  //#endregion
13221
13275
  //#region src/plugin/rules/a11y/lang.ts
13222
- const MESSAGE$37 = "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`.";
13276
+ const MESSAGE$39 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
13223
13277
  const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
13224
13278
  "aa",
13225
13279
  "ab",
@@ -13431,7 +13485,7 @@ const lang = defineRule({
13431
13485
  if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
13432
13486
  context.report({
13433
13487
  node: langAttr,
13434
- message: MESSAGE$37
13488
+ message: MESSAGE$39
13435
13489
  });
13436
13490
  return;
13437
13491
  }
@@ -13440,7 +13494,7 @@ const lang = defineRule({
13440
13494
  if (value === null) return;
13441
13495
  if (!isValidLangTag(value)) context.report({
13442
13496
  node: langAttr,
13443
- message: MESSAGE$37
13497
+ message: MESSAGE$39
13444
13498
  });
13445
13499
  } })
13446
13500
  });
@@ -13466,6 +13520,7 @@ const mcpToolCapabilityRisk = defineRule({
13466
13520
  shouldScan: (file) => isProductionSourcePath(file.relativePath),
13467
13521
  pattern: /\bserver\.\s*tool\s*\(|\bregisterTool\s*\(|\bsetRequestHandler\s*\(\s*CallToolRequestSchema/,
13468
13522
  requireAll: [/\bfrom\s+["']@modelcontextprotocol\/sdk[^"']*["']|\bMcpServer\b|\bMcpAgent\b/, AGENT_TOOL_DANGEROUS_CAPABILITY_PATTERN],
13523
+ ignoreStringLiterals: true,
13469
13524
  message: "An MCP tool/resource/prompt handler appears to expose file, shell, network, or code-execution capability."
13470
13525
  })
13471
13526
  });
@@ -13484,7 +13539,7 @@ const mdxSsrExecutionRisk = defineRule({
13484
13539
  });
13485
13540
  //#endregion
13486
13541
  //#region src/plugin/rules/a11y/media-has-caption.ts
13487
- const MESSAGE$36 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
13542
+ const MESSAGE$38 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
13488
13543
  const DEFAULT_AUDIO = ["audio"];
13489
13544
  const DEFAULT_VIDEO = ["video"];
13490
13545
  const DEFAULT_TRACK = ["track"];
@@ -13525,7 +13580,7 @@ const mediaHasCaption = defineRule({
13525
13580
  if (!parent || !isNodeOfType(parent, "JSXElement")) {
13526
13581
  context.report({
13527
13582
  node: node.name,
13528
- message: MESSAGE$36
13583
+ message: MESSAGE$38
13529
13584
  });
13530
13585
  return;
13531
13586
  }
@@ -13542,7 +13597,7 @@ const mediaHasCaption = defineRule({
13542
13597
  return kindValue.value.toLowerCase() === "captions";
13543
13598
  })) context.report({
13544
13599
  node: node.name,
13545
- message: MESSAGE$36
13600
+ message: MESSAGE$38
13546
13601
  });
13547
13602
  } };
13548
13603
  }
@@ -15343,7 +15398,7 @@ const nextjsNoVercelOgImport = defineRule({
15343
15398
  });
15344
15399
  //#endregion
15345
15400
  //#region src/plugin/rules/a11y/no-access-key.ts
15346
- const MESSAGE$35 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
15401
+ const MESSAGE$37 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
15347
15402
  const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
15348
15403
  const noAccessKey = defineRule({
15349
15404
  id: "no-access-key",
@@ -15360,7 +15415,7 @@ const noAccessKey = defineRule({
15360
15415
  if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
15361
15416
  context.report({
15362
15417
  node: accessKey,
15363
- message: MESSAGE$35
15418
+ message: MESSAGE$37
15364
15419
  });
15365
15420
  return;
15366
15421
  }
@@ -15370,7 +15425,7 @@ const noAccessKey = defineRule({
15370
15425
  if (isUndefinedIdentifier(expression)) return;
15371
15426
  context.report({
15372
15427
  node: accessKey,
15373
- message: MESSAGE$35
15428
+ message: MESSAGE$37
15374
15429
  });
15375
15430
  }
15376
15431
  } })
@@ -15853,7 +15908,7 @@ const noAdjustStateOnPropChange = defineRule({
15853
15908
  });
15854
15909
  //#endregion
15855
15910
  //#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
15856
- const MESSAGE$34 = "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.";
15911
+ const MESSAGE$36 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
15857
15912
  const noAriaHiddenOnFocusable = defineRule({
15858
15913
  id: "no-aria-hidden-on-focusable",
15859
15914
  title: "aria-hidden on focusable element",
@@ -15880,7 +15935,7 @@ const noAriaHiddenOnFocusable = defineRule({
15880
15935
  const isImplicitlyFocusable = isInteractiveElement(tag, node);
15881
15936
  if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
15882
15937
  node: ariaHidden,
15883
- message: MESSAGE$34
15938
+ message: MESSAGE$36
15884
15939
  });
15885
15940
  } })
15886
15941
  });
@@ -16248,7 +16303,7 @@ const noArrayIndexAsKey = defineRule({
16248
16303
  });
16249
16304
  //#endregion
16250
16305
  //#region src/plugin/rules/react-builtins/no-array-index-key.ts
16251
- const MESSAGE$33 = "Your users can see & submit the wrong data when this list reorders.";
16306
+ const MESSAGE$35 = "Your users can see & submit the wrong data when this list reorders.";
16252
16307
  const SECOND_INDEX_METHODS = new Set([
16253
16308
  "every",
16254
16309
  "filter",
@@ -16452,7 +16507,7 @@ const noArrayIndexKey = defineRule({
16452
16507
  }
16453
16508
  context.report({
16454
16509
  node: keyAttribute,
16455
- message: MESSAGE$33
16510
+ message: MESSAGE$35
16456
16511
  });
16457
16512
  },
16458
16513
  CallExpression(node) {
@@ -16472,7 +16527,7 @@ const noArrayIndexKey = defineRule({
16472
16527
  if (propName !== "key") continue;
16473
16528
  if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
16474
16529
  node: property,
16475
- message: MESSAGE$33
16530
+ message: MESSAGE$35
16476
16531
  });
16477
16532
  }
16478
16533
  }
@@ -16480,7 +16535,7 @@ const noArrayIndexKey = defineRule({
16480
16535
  });
16481
16536
  //#endregion
16482
16537
  //#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
16483
- const MESSAGE$32 = "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.";
16538
+ const MESSAGE$34 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
16484
16539
  const noAsyncEffectCallback = defineRule({
16485
16540
  id: "no-async-effect-callback",
16486
16541
  title: "Async effect callback",
@@ -16494,13 +16549,13 @@ const noAsyncEffectCallback = defineRule({
16494
16549
  if (!callback.async) return;
16495
16550
  context.report({
16496
16551
  node: callback,
16497
- message: MESSAGE$32
16552
+ message: MESSAGE$34
16498
16553
  });
16499
16554
  } })
16500
16555
  });
16501
16556
  //#endregion
16502
16557
  //#region src/plugin/rules/a11y/no-autofocus.ts
16503
- const MESSAGE$31 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
16558
+ const MESSAGE$33 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
16504
16559
  const resolveSettings$21 = (settings) => {
16505
16560
  const reactDoctor = settings?.["react-doctor"];
16506
16561
  return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
@@ -16556,7 +16611,7 @@ const noAutofocus = defineRule({
16556
16611
  }
16557
16612
  context.report({
16558
16613
  node: autoFocusAttribute,
16559
- message: MESSAGE$31
16614
+ message: MESSAGE$33
16560
16615
  });
16561
16616
  } };
16562
16617
  }
@@ -17060,7 +17115,7 @@ const noChainStateUpdates = defineRule({
17060
17115
  });
17061
17116
  //#endregion
17062
17117
  //#region src/plugin/rules/react-builtins/no-children-prop.ts
17063
- const MESSAGE$30 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
17118
+ const MESSAGE$32 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
17064
17119
  const noChildrenProp = defineRule({
17065
17120
  id: "no-children-prop",
17066
17121
  title: "Children passed as a prop",
@@ -17072,7 +17127,7 @@ const noChildrenProp = defineRule({
17072
17127
  if (node.name.name !== "children") return;
17073
17128
  context.report({
17074
17129
  node: node.name,
17075
- message: MESSAGE$30
17130
+ message: MESSAGE$32
17076
17131
  });
17077
17132
  },
17078
17133
  CallExpression(node) {
@@ -17085,7 +17140,7 @@ const noChildrenProp = defineRule({
17085
17140
  const propertyKey = property.key;
17086
17141
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
17087
17142
  node: propertyKey,
17088
- message: MESSAGE$30
17143
+ message: MESSAGE$32
17089
17144
  });
17090
17145
  }
17091
17146
  }
@@ -17093,7 +17148,7 @@ const noChildrenProp = defineRule({
17093
17148
  });
17094
17149
  //#endregion
17095
17150
  //#region src/plugin/rules/react-builtins/no-clone-element.ts
17096
- const MESSAGE$29 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
17151
+ const MESSAGE$31 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
17097
17152
  const noCloneElement = defineRule({
17098
17153
  id: "no-clone-element",
17099
17154
  title: "cloneElement makes child props fragile",
@@ -17106,7 +17161,7 @@ const noCloneElement = defineRule({
17106
17161
  if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
17107
17162
  if (isImportedFromModule(node, "cloneElement", "react")) context.report({
17108
17163
  node: callee,
17109
- message: MESSAGE$29
17164
+ message: MESSAGE$31
17110
17165
  });
17111
17166
  return;
17112
17167
  }
@@ -17119,7 +17174,7 @@ const noCloneElement = defineRule({
17119
17174
  if (!isImportedFromModule(node, callee.object.name, "react")) return;
17120
17175
  context.report({
17121
17176
  node: callee,
17122
- message: MESSAGE$29
17177
+ message: MESSAGE$31
17123
17178
  });
17124
17179
  }
17125
17180
  } })
@@ -17168,7 +17223,7 @@ const enclosingComponentOrHookName = (node) => {
17168
17223
  };
17169
17224
  //#endregion
17170
17225
  //#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
17171
- const MESSAGE$28 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
17226
+ const MESSAGE$30 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
17172
17227
  const CONTEXT_MODULES = [
17173
17228
  "react",
17174
17229
  "use-context-selector",
@@ -17204,13 +17259,13 @@ const noCreateContextInRender = defineRule({
17204
17259
  if (!componentOrHookName) return;
17205
17260
  context.report({
17206
17261
  node,
17207
- message: `${MESSAGE$28} (called inside "${componentOrHookName}")`
17262
+ message: `${MESSAGE$30} (called inside "${componentOrHookName}")`
17208
17263
  });
17209
17264
  } })
17210
17265
  });
17211
17266
  //#endregion
17212
17267
  //#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
17213
- const MESSAGE$27 = "`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.";
17268
+ const MESSAGE$29 = "`createRef()` in a function component allocates a brand-new ref on every render, so it never holds a value between renders. Use the `useRef()` hook instead.";
17214
17269
  const noCreateRefInFunctionComponent = defineRule({
17215
17270
  id: "no-create-ref-in-function-component",
17216
17271
  title: "createRef in function component",
@@ -17229,7 +17284,7 @@ const noCreateRefInFunctionComponent = defineRule({
17229
17284
  if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes))) return;
17230
17285
  context.report({
17231
17286
  node,
17232
- message: MESSAGE$27
17287
+ message: MESSAGE$29
17233
17288
  });
17234
17289
  } })
17235
17290
  });
@@ -17369,7 +17424,7 @@ const noCreateStoreInRender = defineRule({
17369
17424
  });
17370
17425
  //#endregion
17371
17426
  //#region src/plugin/rules/react-builtins/no-danger.ts
17372
- const MESSAGE$26 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
17427
+ const MESSAGE$28 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
17373
17428
  const noDanger = defineRule({
17374
17429
  id: "no-danger",
17375
17430
  title: "Raw HTML injection can run unsafe markup",
@@ -17382,7 +17437,7 @@ const noDanger = defineRule({
17382
17437
  if (!propAttribute) return;
17383
17438
  context.report({
17384
17439
  node: propAttribute.name,
17385
- message: MESSAGE$26
17440
+ message: MESSAGE$28
17386
17441
  });
17387
17442
  },
17388
17443
  CallExpression(node) {
@@ -17394,7 +17449,7 @@ const noDanger = defineRule({
17394
17449
  const propertyKey = property.key;
17395
17450
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
17396
17451
  node: propertyKey,
17397
- message: MESSAGE$26
17452
+ message: MESSAGE$28
17398
17453
  });
17399
17454
  }
17400
17455
  }
@@ -17402,7 +17457,7 @@ const noDanger = defineRule({
17402
17457
  });
17403
17458
  //#endregion
17404
17459
  //#region src/plugin/rules/react-builtins/no-danger-with-children.ts
17405
- const MESSAGE$25 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
17460
+ const MESSAGE$27 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
17406
17461
  const isLineBreak = (child) => {
17407
17462
  if (!isNodeOfType(child, "JSXText")) return false;
17408
17463
  return child.value.trim().length === 0 && child.value.includes("\n");
@@ -17472,7 +17527,7 @@ const noDangerWithChildren = defineRule({
17472
17527
  if (!hasChildrenProp && !hasNestedChildren) return;
17473
17528
  if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
17474
17529
  node: opening,
17475
- message: MESSAGE$25
17530
+ message: MESSAGE$27
17476
17531
  });
17477
17532
  },
17478
17533
  CallExpression(node) {
@@ -17484,7 +17539,7 @@ const noDangerWithChildren = defineRule({
17484
17539
  if (!propsShape.hasDangerously) return;
17485
17540
  if (node.arguments.length >= 3 || propsShape.hasChildren) context.report({
17486
17541
  node,
17487
- message: MESSAGE$25
17542
+ message: MESSAGE$27
17488
17543
  });
17489
17544
  }
17490
17545
  })
@@ -18061,7 +18116,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
18061
18116
  //#endregion
18062
18117
  //#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
18063
18118
  const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
18064
- const MESSAGE$24 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
18119
+ const MESSAGE$26 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
18065
18120
  const resolveSettings$20 = (settings) => {
18066
18121
  const reactDoctor = settings?.["react-doctor"];
18067
18122
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidMountSetState ?? {} : {}).mode ?? "allowed" };
@@ -18080,7 +18135,7 @@ const noDidMountSetState = defineRule({
18080
18135
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$2, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
18081
18136
  context.report({
18082
18137
  node: node.callee,
18083
- message: MESSAGE$24
18138
+ message: MESSAGE$26
18084
18139
  });
18085
18140
  } };
18086
18141
  }
@@ -18088,7 +18143,7 @@ const noDidMountSetState = defineRule({
18088
18143
  //#endregion
18089
18144
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
18090
18145
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
18091
- const MESSAGE$23 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
18146
+ const MESSAGE$25 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
18092
18147
  const resolveSettings$19 = (settings) => {
18093
18148
  const reactDoctor = settings?.["react-doctor"];
18094
18149
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidUpdateSetState ?? {} : {}).mode ?? "allowed" };
@@ -18107,7 +18162,7 @@ const noDidUpdateSetState = defineRule({
18107
18162
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
18108
18163
  context.report({
18109
18164
  node: node.callee,
18110
- message: MESSAGE$23
18165
+ message: MESSAGE$25
18111
18166
  });
18112
18167
  } };
18113
18168
  }
@@ -18130,7 +18185,7 @@ const isStateMemberExpression = (node) => {
18130
18185
  };
18131
18186
  //#endregion
18132
18187
  //#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
18133
- const MESSAGE$22 = "Your users see stale data because mutating `this.state` by hand never redraws & gets overwritten.";
18188
+ const MESSAGE$24 = "Your users see stale data because mutating `this.state` by hand never redraws & gets overwritten.";
18134
18189
  const shouldIgnoreMutation = (node) => {
18135
18190
  let isConstructor = false;
18136
18191
  let isInsideCallExpression = false;
@@ -18152,7 +18207,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
18152
18207
  if (shouldIgnoreMutation(reportNode)) return;
18153
18208
  context.report({
18154
18209
  node: reportNode,
18155
- message: MESSAGE$22
18210
+ message: MESSAGE$24
18156
18211
  });
18157
18212
  };
18158
18213
  const noDirectMutationState = defineRule({
@@ -18362,6 +18417,26 @@ const noDocumentStartViewTransition = defineRule({
18362
18417
  } })
18363
18418
  });
18364
18419
  //#endregion
18420
+ //#region src/plugin/rules/js-performance/no-document-write.ts
18421
+ const MESSAGE$23 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
18422
+ const WRITE_METHODS = new Set(["write", "writeln"]);
18423
+ const noDocumentWrite = defineRule({
18424
+ id: "no-document-write",
18425
+ title: "document.write/writeln",
18426
+ severity: "warn",
18427
+ recommendation: "Don't use `document.write()`/`document.writeln()`. Append DOM nodes or set `innerHTML`/`textContent` on a specific element instead.",
18428
+ create: (context) => ({ CallExpression(node) {
18429
+ const callee = node.callee;
18430
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
18431
+ if (!isNodeOfType(callee.object, "Identifier") || callee.object.name !== "document") return;
18432
+ if (!isNodeOfType(callee.property, "Identifier") || !WRITE_METHODS.has(callee.property.name)) return;
18433
+ context.report({
18434
+ node,
18435
+ message: MESSAGE$23
18436
+ });
18437
+ } })
18438
+ });
18439
+ //#endregion
18365
18440
  //#region src/plugin/rules/bundle-size/no-dynamic-import-path.ts
18366
18441
  const noDynamicImportPath = defineRule({
18367
18442
  id: "no-dynamic-import-path",
@@ -19740,7 +19815,7 @@ const ALLOWED_NAMESPACES = new Set([
19740
19815
  "ReactDOM",
19741
19816
  "ReactDom"
19742
19817
  ]);
19743
- const MESSAGE$21 = "`findDOMNode` crashes your app in React 19 because it was removed.";
19818
+ const MESSAGE$22 = "`findDOMNode` crashes your app in React 19 because it was removed.";
19744
19819
  const noFindDomNode = defineRule({
19745
19820
  id: "no-find-dom-node",
19746
19821
  title: "findDOMNode breaks component encapsulation",
@@ -19751,7 +19826,7 @@ const noFindDomNode = defineRule({
19751
19826
  if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
19752
19827
  context.report({
19753
19828
  node: callee,
19754
- message: MESSAGE$21
19829
+ message: MESSAGE$22
19755
19830
  });
19756
19831
  return;
19757
19832
  }
@@ -19762,7 +19837,7 @@ const noFindDomNode = defineRule({
19762
19837
  if (callee.property.name !== "findDOMNode") return;
19763
19838
  context.report({
19764
19839
  node: callee.property,
19765
- message: MESSAGE$21
19840
+ message: MESSAGE$22
19766
19841
  });
19767
19842
  }
19768
19843
  } })
@@ -20004,7 +20079,7 @@ const noGrayOnColoredBackground = defineRule({
20004
20079
  });
20005
20080
  //#endregion
20006
20081
  //#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
20007
- const MESSAGE$20 = "`<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.";
20082
+ const MESSAGE$21 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
20008
20083
  const noImgLazyWithHighFetchpriority = defineRule({
20009
20084
  id: "no-img-lazy-with-high-fetchpriority",
20010
20085
  title: "Lazy image with high fetchPriority",
@@ -20018,7 +20093,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
20018
20093
  if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
20019
20094
  context.report({
20020
20095
  node: node.name,
20021
- message: MESSAGE$20
20096
+ message: MESSAGE$21
20022
20097
  });
20023
20098
  } })
20024
20099
  });
@@ -20253,7 +20328,7 @@ const noIsMounted = defineRule({
20253
20328
  });
20254
20329
  //#endregion
20255
20330
  //#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
20256
- const MESSAGE$19 = "`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)`.";
20331
+ 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)`.";
20257
20332
  const isJsonMethodCall = (node, method) => {
20258
20333
  if (!isNodeOfType(node, "CallExpression")) return false;
20259
20334
  const callee = node.callee;
@@ -20270,13 +20345,13 @@ const noJsonParseStringifyClone = defineRule({
20270
20345
  if (!firstArgument || !isJsonMethodCall(firstArgument, "stringify")) return;
20271
20346
  context.report({
20272
20347
  node,
20273
- message: MESSAGE$19
20348
+ message: MESSAGE$20
20274
20349
  });
20275
20350
  } })
20276
20351
  });
20277
20352
  //#endregion
20278
20353
  //#region src/plugin/rules/correctness/no-jsx-element-type.ts
20279
- const MESSAGE$18 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
20354
+ const MESSAGE$19 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
20280
20355
  const isJsxElementTypeReference = (node) => {
20281
20356
  if (!isNodeOfType(node, "TSTypeReference")) return false;
20282
20357
  const typeName = node.typeName;
@@ -20293,7 +20368,7 @@ const checkReturnType = (context, returnType) => {
20293
20368
  if (!typeAnnotation) return;
20294
20369
  if (isJsxElementTypeReference(typeAnnotation)) context.report({
20295
20370
  node: typeAnnotation,
20296
- message: MESSAGE$18
20371
+ message: MESSAGE$19
20297
20372
  });
20298
20373
  };
20299
20374
  const noJsxElementType = defineRule({
@@ -20748,7 +20823,7 @@ const noMoment = defineRule({
20748
20823
  });
20749
20824
  //#endregion
20750
20825
  //#region src/plugin/rules/react-builtins/no-multi-comp.ts
20751
- const MESSAGE$17 = "This file declares several components, so each component is harder to find, test, and change.";
20826
+ const MESSAGE$18 = "This file declares several components, so each component is harder to find, test, and change.";
20752
20827
  const resolveSettings$16 = (settings) => {
20753
20828
  const reactDoctor = settings?.["react-doctor"];
20754
20829
  return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
@@ -21070,7 +21145,7 @@ const noMultiComp = defineRule({
21070
21145
  if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
21071
21146
  for (const component of flagged.slice(1)) context.report({
21072
21147
  node: component.reportNode,
21073
- message: MESSAGE$17
21148
+ message: MESSAGE$18
21074
21149
  });
21075
21150
  } };
21076
21151
  }
@@ -21238,7 +21313,7 @@ const resolveReducerFunction = (node, currentFilename) => {
21238
21313
  };
21239
21314
  //#endregion
21240
21315
  //#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
21241
- const MESSAGE$16 = "This reducer changes state in place, so your update is silently skipped.";
21316
+ const MESSAGE$17 = "This reducer changes state in place, so your update is silently skipped.";
21242
21317
  const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
21243
21318
  "copyWithin",
21244
21319
  "fill",
@@ -21448,7 +21523,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
21448
21523
  reportedNodes.add(options.crossFileConsumerCallSite);
21449
21524
  context.report({
21450
21525
  node: options.crossFileConsumerCallSite,
21451
- message: `${MESSAGE$16} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
21526
+ message: `${MESSAGE$17} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
21452
21527
  });
21453
21528
  return;
21454
21529
  }
@@ -21457,7 +21532,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
21457
21532
  reportedNodes.add(mutation.node);
21458
21533
  context.report({
21459
21534
  node: mutation.node,
21460
- message: MESSAGE$16
21535
+ message: MESSAGE$17
21461
21536
  });
21462
21537
  }
21463
21538
  };
@@ -21729,7 +21804,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
21729
21804
  });
21730
21805
  //#endregion
21731
21806
  //#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
21732
- const MESSAGE$15 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
21807
+ const MESSAGE$16 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
21733
21808
  const resolveSettings$14 = (settings) => {
21734
21809
  const reactDoctor = settings?.["react-doctor"];
21735
21810
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noNoninteractiveTabindex ?? {} : {};
@@ -21757,7 +21832,7 @@ const noNoninteractiveTabindex = defineRule({
21757
21832
  if (numeric === null) {
21758
21833
  if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues) context.report({
21759
21834
  node: tabIndex,
21760
- message: MESSAGE$15
21835
+ message: MESSAGE$16
21761
21836
  });
21762
21837
  return;
21763
21838
  }
@@ -21770,7 +21845,7 @@ const noNoninteractiveTabindex = defineRule({
21770
21845
  if (!roleAttribute) {
21771
21846
  context.report({
21772
21847
  node: tabIndex,
21773
- message: MESSAGE$15
21848
+ message: MESSAGE$16
21774
21849
  });
21775
21850
  return;
21776
21851
  }
@@ -21784,7 +21859,7 @@ const noNoninteractiveTabindex = defineRule({
21784
21859
  }
21785
21860
  context.report({
21786
21861
  node: tabIndex,
21787
- message: MESSAGE$15
21862
+ message: MESSAGE$16
21788
21863
  });
21789
21864
  } };
21790
21865
  }
@@ -22475,7 +22550,7 @@ const noRandomKey = defineRule({
22475
22550
  });
22476
22551
  //#endregion
22477
22552
  //#region src/plugin/rules/react-builtins/no-react-children.ts
22478
- const MESSAGE$14 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
22553
+ const MESSAGE$15 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
22479
22554
  const isChildrenIdentifier = (node, contextNode) => {
22480
22555
  if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
22481
22556
  return isImportedFromModule(contextNode, "Children", "react");
@@ -22501,13 +22576,13 @@ const noReactChildren = defineRule({
22501
22576
  if (isChildrenIdentifier(memberObject, node)) {
22502
22577
  context.report({
22503
22578
  node: calleeOuter,
22504
- message: MESSAGE$14
22579
+ message: MESSAGE$15
22505
22580
  });
22506
22581
  return;
22507
22582
  }
22508
22583
  if (isReactNamespaceMember(memberObject, node)) context.report({
22509
22584
  node: calleeOuter,
22510
- message: MESSAGE$14
22585
+ message: MESSAGE$15
22511
22586
  });
22512
22587
  } })
22513
22588
  });
@@ -22830,7 +22905,7 @@ const noRenderPropChildren = defineRule({
22830
22905
  });
22831
22906
  //#endregion
22832
22907
  //#region src/plugin/rules/react-builtins/no-render-return-value.ts
22833
- const MESSAGE$13 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
22908
+ const MESSAGE$14 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
22834
22909
  const isReactDomRenderCall = (node) => {
22835
22910
  if (!isNodeOfType(node.callee, "MemberExpression")) return false;
22836
22911
  if (!isNodeOfType(node.callee.object, "Identifier")) return false;
@@ -22854,7 +22929,7 @@ const noRenderReturnValue = defineRule({
22854
22929
  if (!isUsedAsReturnValue(node.parent)) return;
22855
22930
  context.report({
22856
22931
  node: node.callee,
22857
- message: MESSAGE$13
22932
+ message: MESSAGE$14
22858
22933
  });
22859
22934
  } })
22860
22935
  });
@@ -23552,7 +23627,7 @@ const getParentComponent = (node) => {
23552
23627
  };
23553
23628
  //#endregion
23554
23629
  //#region src/plugin/rules/react-builtins/no-set-state.ts
23555
- const MESSAGE$12 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
23630
+ const MESSAGE$13 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
23556
23631
  const noSetState = defineRule({
23557
23632
  id: "no-set-state",
23558
23633
  title: "Local class state forbidden",
@@ -23567,7 +23642,7 @@ const noSetState = defineRule({
23567
23642
  if (!getParentComponent(node)) return;
23568
23643
  context.report({
23569
23644
  node: node.callee,
23570
- message: MESSAGE$12
23645
+ message: MESSAGE$13
23571
23646
  });
23572
23647
  } })
23573
23648
  });
@@ -23729,7 +23804,7 @@ const isAbstractRole = (openingElement, settings) => {
23729
23804
  };
23730
23805
  //#endregion
23731
23806
  //#region src/plugin/rules/a11y/no-static-element-interactions.ts
23732
- const MESSAGE$11 = "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.";
23807
+ 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.";
23733
23808
  const DEFAULT_HANDLERS = [
23734
23809
  "onClick",
23735
23810
  "onMouseDown",
@@ -23789,7 +23864,7 @@ const noStaticElementInteractions = defineRule({
23789
23864
  if (!roleAttribute || !roleAttribute.value) {
23790
23865
  context.report({
23791
23866
  node: node.name,
23792
- message: MESSAGE$11
23867
+ message: MESSAGE$12
23793
23868
  });
23794
23869
  return;
23795
23870
  }
@@ -23799,19 +23874,66 @@ const noStaticElementInteractions = defineRule({
23799
23874
  if (firstRole && (isInteractiveRole(firstRole) || isNonInteractiveRole(firstRole))) return;
23800
23875
  context.report({
23801
23876
  node: node.name,
23802
- message: MESSAGE$11
23877
+ message: MESSAGE$12
23803
23878
  });
23804
23879
  return;
23805
23880
  }
23806
23881
  if (isNodeOfType(attributeValue, "JSXExpressionContainer") && settings.allowExpressionValues) return;
23807
23882
  context.report({
23808
23883
  node: node.name,
23809
- message: MESSAGE$11
23884
+ message: MESSAGE$12
23810
23885
  });
23811
23886
  } };
23812
23887
  }
23813
23888
  });
23814
23889
  //#endregion
23890
+ //#region src/plugin/rules/react-builtins/no-string-false-on-boolean-attribute.ts
23891
+ const BOOLEAN_ATTRIBUTES = new Set([
23892
+ "disabled",
23893
+ "checked",
23894
+ "readonly",
23895
+ "required",
23896
+ "selected",
23897
+ "multiple",
23898
+ "autofocus",
23899
+ "autoplay",
23900
+ "controls",
23901
+ "loop",
23902
+ "muted",
23903
+ "open",
23904
+ "reversed",
23905
+ "default",
23906
+ "novalidate",
23907
+ "formnovalidate",
23908
+ "playsinline",
23909
+ "itemscope",
23910
+ "allowfullscreen"
23911
+ ]);
23912
+ const noStringFalseOnBooleanAttribute = defineRule({
23913
+ id: "no-string-false-on-boolean-attribute",
23914
+ title: "String true/false on a boolean attribute",
23915
+ severity: "warn",
23916
+ recommendation: "Use the boolean form on boolean attributes: `disabled` / `disabled={true}` / `disabled={false}`, not `disabled=\"false\"`. A non-empty string is truthy, so `=\"false\"` actually turns the attribute ON.",
23917
+ create: (context) => ({ JSXOpeningElement(node) {
23918
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
23919
+ const firstCharacter = node.name.name.charCodeAt(0);
23920
+ if (firstCharacter < 97 || firstCharacter > 122) return;
23921
+ for (const attribute of node.attributes) {
23922
+ if (!isNodeOfType(attribute, "JSXAttribute")) continue;
23923
+ if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
23924
+ if (!BOOLEAN_ATTRIBUTES.has(attribute.name.name.toLowerCase())) continue;
23925
+ const value = getJsxPropStringValue(attribute);
23926
+ if (value !== "false" && value !== "true") continue;
23927
+ const attributeName = attribute.name.name;
23928
+ const guidance = value === "false" ? `which React treats as truthy, so the attribute is applied even though you wrote "false". Use \`${attributeName}={false}\` (or omit the attribute) to keep it off` : `but a boolean attribute takes a boolean, not the string "true". Use \`${attributeName}\` or \`${attributeName}={true}\``;
23929
+ context.report({
23930
+ node: attribute,
23931
+ message: `\`${attributeName}="${value}"\` passes the string "${value}", ${guidance}.`
23932
+ });
23933
+ }
23934
+ } })
23935
+ });
23936
+ //#endregion
23815
23937
  //#region src/plugin/rules/react-builtins/no-string-refs.ts
23816
23938
  const STRING_IN_REF_MESSAGE = "Your component can't reach this node because string refs don't work in modern React.";
23817
23939
  const THIS_REFS_MESSAGE = "Your component can't reach its nodes because `this.refs` is empty in modern React.";
@@ -23862,6 +23984,27 @@ const noStringRefs = defineRule({
23862
23984
  }
23863
23985
  });
23864
23986
  //#endregion
23987
+ //#region src/plugin/rules/js-performance/no-sync-xhr.ts
23988
+ 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)`).";
23989
+ const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
23990
+ const noSyncXhr = defineRule({
23991
+ id: "no-sync-xhr",
23992
+ title: "Synchronous XMLHttpRequest",
23993
+ severity: "warn",
23994
+ recommendation: "Never open an XMLHttpRequest synchronously (`async` = `false`). It blocks the main thread. Use `fetch()` or pass `true` and handle the response asynchronously.",
23995
+ create: (context) => ({ CallExpression(node) {
23996
+ const callee = node.callee;
23997
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
23998
+ if (!isNodeOfType(callee.property, "Identifier") || callee.property.name !== "open") return;
23999
+ const asyncArgument = node.arguments?.[2];
24000
+ if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
24001
+ context.report({
24002
+ node,
24003
+ message: MESSAGE$11
24004
+ });
24005
+ } })
24006
+ });
24007
+ //#endregion
23865
24008
  //#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
23866
24009
  const MESSAGE$10 = "This value is `undefined` because function components have no `this`.";
23867
24010
  const isInsideClassMethod = (node, customClassFactoryNames) => {
@@ -35437,13 +35580,7 @@ const serverNoMutableModuleState = defineRule({
35437
35580
  const collectDeclaredNames = (declaration) => {
35438
35581
  const names = /* @__PURE__ */ new Set();
35439
35582
  if (!isNodeOfType(declaration, "VariableDeclaration")) return names;
35440
- for (const declarator of declaration.declarations ?? []) if (isNodeOfType(declarator.id, "Identifier")) names.add(declarator.id.name);
35441
- else if (isNodeOfType(declarator.id, "ObjectPattern")) {
35442
- for (const property of declarator.id.properties ?? []) if (isNodeOfType(property, "Property") && isNodeOfType(property.value, "Identifier")) names.add(property.value.name);
35443
- else if (isNodeOfType(property, "RestElement") && isNodeOfType(property.argument, "Identifier")) names.add(property.argument.name);
35444
- } else if (isNodeOfType(declarator.id, "ArrayPattern")) {
35445
- for (const element of declarator.id.elements ?? []) if (isNodeOfType(element, "Identifier")) names.add(element.name);
35446
- }
35583
+ for (const declarator of declaration.declarations ?? []) collectPatternNames(declarator.id, names);
35447
35584
  return names;
35448
35585
  };
35449
35586
  const declarationStartsWithAwait = (declaration) => {
@@ -35453,11 +35590,15 @@ const declarationStartsWithAwait = (declaration) => {
35453
35590
  };
35454
35591
  const declarationReadsAnyName = (declaration, names) => {
35455
35592
  if (names.size === 0) return false;
35593
+ if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
35456
35594
  let didRead = false;
35457
- walkAst(declaration, (child) => {
35458
- if (didRead) return;
35459
- if (isNodeOfType(child, "Identifier") && names.has(child.name)) didRead = true;
35460
- });
35595
+ for (const declarator of declaration.declarations ?? []) {
35596
+ if (!declarator.init) continue;
35597
+ walkAst(declarator.init, (child) => {
35598
+ if (didRead) return;
35599
+ if (isNodeOfType(child, "Identifier") && names.has(child.name)) didRead = true;
35600
+ });
35601
+ }
35461
35602
  return didRead;
35462
35603
  };
35463
35604
  const serverSequentialIndependentAwait = defineRule({
@@ -36717,7 +36858,7 @@ const urlPrefilledPrivilegedAction = defineRule({
36717
36858
  recommendation: "Require server-side validation and explicit confirmation for URL-sourced invite, role, permission, redirect, or sharing parameters.",
36718
36859
  scan: scanByPattern({
36719
36860
  shouldScan: (file) => isClientSourcePath(file.relativePath),
36720
- pattern: /(?<!(?:safe|valid|sanitiz|relativ|allowlist|whitelist)[\w$]*\(\s*(?:new\s+)?)\b(?:searchParams|useSearchParams\s*\(\s*\)|URLSearchParams\s*\([^)]{0,120}\))(?:[?!])?\.get(?:All)?\s*\(\s*["'](?:userstoinvite|role|permission|sharingaction|invite|admin|next|continue|returnTo|redirect_uri|callbackUrl)["']|\bsearchParams\.(?:userstoinvite|role|permission|sharingaction|invite|admin|returnTo|redirect_uri|callbackUrl)\b/i,
36861
+ pattern: /(?<!(?:safe|valid|sanitiz|relativ|allowlist|whitelist)[\w$]*\(\s*(?:new\s+)?(?:[\w$]+\s*\.\s*){0,4})\b(?:searchParams|useSearchParams\s*\(\s*\)|URLSearchParams\s*\([^)]{0,120}\))(?:[?!])?\.get(?:All)?\s*\(\s*["'](?:userstoinvite|role|permission|sharingaction|invite|admin|next|continue|returnTo|redirect_uri|callbackUrl)["']|\bsearchParams\.(?:userstoinvite|role|permission|sharingaction|invite|admin|returnTo|redirect_uri|callbackUrl)\b/i,
36721
36862
  message: "Client code reads sensitive action state from the URL, which can pre-fill invites, roles, redirects, or sharing flows with attacker values."
36722
36863
  })
36723
36864
  });
@@ -39173,6 +39314,17 @@ const reactDoctorRules = [
39173
39314
  requires: [...new Set(["react", ...noDocumentStartViewTransition.requires ?? []])]
39174
39315
  }
39175
39316
  },
39317
+ {
39318
+ key: "react-doctor/no-document-write",
39319
+ id: "no-document-write",
39320
+ source: "react-doctor",
39321
+ originallyExternal: false,
39322
+ rule: {
39323
+ ...noDocumentWrite,
39324
+ framework: "global",
39325
+ category: "Performance"
39326
+ }
39327
+ },
39176
39328
  {
39177
39329
  key: "react-doctor/no-dynamic-import-path",
39178
39330
  id: "no-dynamic-import-path",
@@ -39982,6 +40134,18 @@ const reactDoctorRules = [
39982
40134
  requires: [...new Set(["react", ...noStaticElementInteractions.requires ?? []])]
39983
40135
  }
39984
40136
  },
40137
+ {
40138
+ key: "react-doctor/no-string-false-on-boolean-attribute",
40139
+ id: "no-string-false-on-boolean-attribute",
40140
+ source: "react-doctor",
40141
+ originallyExternal: false,
40142
+ rule: {
40143
+ ...noStringFalseOnBooleanAttribute,
40144
+ framework: "global",
40145
+ category: "Bugs",
40146
+ requires: [...new Set(["react", ...noStringFalseOnBooleanAttribute.requires ?? []])]
40147
+ }
40148
+ },
39985
40149
  {
39986
40150
  key: "react-doctor/no-string-refs",
39987
40151
  id: "no-string-refs",
@@ -39994,6 +40158,17 @@ const reactDoctorRules = [
39994
40158
  requires: [...new Set(["react", ...noStringRefs.requires ?? []])]
39995
40159
  }
39996
40160
  },
40161
+ {
40162
+ key: "react-doctor/no-sync-xhr",
40163
+ id: "no-sync-xhr",
40164
+ source: "react-doctor",
40165
+ originallyExternal: false,
40166
+ rule: {
40167
+ ...noSyncXhr,
40168
+ framework: "global",
40169
+ category: "Performance"
40170
+ }
40171
+ },
39997
40172
  {
39998
40173
  key: "react-doctor/no-this-in-sfc",
39999
40174
  id: "no-this-in-sfc",