oxlint-plugin-react-doctor 0.5.6-dev.f45cb29 → 0.5.7-dev.242bf69

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 -1
  2. package/dist/index.js +502 -243
  3. package/package.json +2 -2
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
  });
@@ -2965,7 +3019,7 @@ const ABSTRACT_ROLES = new Set([
2965
3019
  "widget",
2966
3020
  "window"
2967
3021
  ]);
2968
- const PRESENTATION_ROLES$2 = new Set(["presentation", "none"]);
3022
+ const PRESENTATION_ROLES$1 = new Set(["presentation", "none"]);
2969
3023
  //#endregion
2970
3024
  //#region src/plugin/rules/a11y/aria-role.ts
2971
3025
  const buildBaseMessage = (suffix) => `This \`role\` is not a valid ARIA role, so assistive tech cannot expose it correctly. Use a real, non-abstract role.${suffix}`;
@@ -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
  });
@@ -3104,6 +3158,76 @@ const AUTH_FUNCTION_NAMES = new Set([
3104
3158
  "getAuth",
3105
3159
  "validateSession"
3106
3160
  ]);
3161
+ const AUTH_STRONG_TOKEN_PATTERN = /^auth(?:n|z|ed|enticate[ds]?|enticating|entication|orize[ds]?|orizing|orization|orizer)?$/;
3162
+ const AUTH_STANDALONE_NOUN_TOKENS = new Set([
3163
+ "signedin",
3164
+ "loggedin",
3165
+ "signin"
3166
+ ]);
3167
+ const AUTH_ASSERTIVE_VERB_TOKENS = new Set([
3168
+ "require",
3169
+ "ensure",
3170
+ "assert",
3171
+ "verify",
3172
+ "validate",
3173
+ "check",
3174
+ "protect",
3175
+ "enforce",
3176
+ "guard",
3177
+ "gate",
3178
+ "restrict",
3179
+ "is",
3180
+ "has",
3181
+ "can",
3182
+ "must"
3183
+ ]);
3184
+ const AUTH_GETTER_VERB_TOKENS = new Set([
3185
+ "get",
3186
+ "fetch",
3187
+ "load",
3188
+ "read",
3189
+ "resolve",
3190
+ "retrieve",
3191
+ "use"
3192
+ ]);
3193
+ const AUTH_QUALIFIER_TOKENS = new Set([
3194
+ "current",
3195
+ "my",
3196
+ "own"
3197
+ ]);
3198
+ const AUTH_STRONG_NOUN_TOKENS = new Set([
3199
+ "session",
3200
+ "sessions",
3201
+ "login",
3202
+ "admin",
3203
+ "admins",
3204
+ "superadmin",
3205
+ "superuser",
3206
+ "role",
3207
+ "roles",
3208
+ "permission",
3209
+ "permissions",
3210
+ "jwt",
3211
+ "identity",
3212
+ "principal",
3213
+ "credential",
3214
+ "credentials"
3215
+ ]);
3216
+ const AUTH_WEAK_NOUN_TOKENS = new Set([
3217
+ "user",
3218
+ "users",
3219
+ "account",
3220
+ "accounts",
3221
+ "token",
3222
+ "tokens",
3223
+ "access",
3224
+ "me",
3225
+ "viewer",
3226
+ "caller",
3227
+ "subject",
3228
+ "scope",
3229
+ "scopes"
3230
+ ]);
3107
3231
  const GENERIC_AUTH_METHOD_NAMES = new Set(["getUser"]);
3108
3232
  const AUTH_OBJECT_PATTERN = /(?:^|[._])(?:auth|authn|authz|clerk|session|jwt|firebase|supabase|nextauth|kinde|workos|stytch|descope|cognito|propelauth|lucia)/i;
3109
3233
  const SECRET_PATTERNS = [
@@ -4202,7 +4326,7 @@ const asyncParallel = defineRule({
4202
4326
  });
4203
4327
  //#endregion
4204
4328
  //#region src/plugin/rules/security/auth-token-in-web-storage.ts
4205
- 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.";
4206
4330
  const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
4207
4331
  const STORAGE_GLOBALS = new Set([
4208
4332
  "window",
@@ -4236,7 +4360,7 @@ const authTokenInWebStorage = defineRule({
4236
4360
  if (!SENSITIVE_KEY_PATTERN.test(keyArgument.value)) return;
4237
4361
  context.report({
4238
4362
  node,
4239
- message: MESSAGE$55
4363
+ message: MESSAGE$57
4240
4364
  });
4241
4365
  },
4242
4366
  AssignmentExpression(node) {
@@ -4247,7 +4371,7 @@ const authTokenInWebStorage = defineRule({
4247
4371
  if (!propertyName || !SENSITIVE_KEY_PATTERN.test(propertyName)) return;
4248
4372
  context.report({
4249
4373
  node: target,
4250
- message: MESSAGE$55
4374
+ message: MESSAGE$57
4251
4375
  });
4252
4376
  }
4253
4377
  })
@@ -4586,6 +4710,14 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
4586
4710
  }
4587
4711
  });
4588
4712
  //#endregion
4713
+ //#region src/plugin/utils/is-presentation-role.ts
4714
+ const isPresentationRole = (openingElement) => {
4715
+ const roleAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "role");
4716
+ if (!roleAttribute) return false;
4717
+ const value = getJsxPropStringValue(roleAttribute);
4718
+ return value !== null && PRESENTATION_ROLES$1.has(value);
4719
+ };
4720
+ //#endregion
4589
4721
  //#region src/plugin/utils/is-pure-event-blocker-handler.ts
4590
4722
  const BLOCKER_METHOD_NAMES = new Set([
4591
4723
  "stopPropagation",
@@ -4623,8 +4755,7 @@ const isPureEventBlockerHandler = (attribute) => {
4623
4755
  };
4624
4756
  //#endregion
4625
4757
  //#region src/plugin/rules/a11y/click-events-have-key-events.ts
4626
- const PRESENTATION_ROLES$1 = new Set(["presentation", "none"]);
4627
- const MESSAGE$54 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
4758
+ const MESSAGE$56 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
4628
4759
  const KEY_HANDLERS = [
4629
4760
  "onKeyUp",
4630
4761
  "onKeyDown",
@@ -4648,15 +4779,11 @@ const clickEventsHaveKeyEvents = defineRule({
4648
4779
  if (!onClick) return;
4649
4780
  if (isPureEventBlockerHandler(onClick)) return;
4650
4781
  if (isHiddenFromScreenReader(node, context.settings)) return;
4651
- const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
4652
- if (roleAttribute) {
4653
- const roleValue = getJsxPropStringValue(roleAttribute);
4654
- if (roleValue && PRESENTATION_ROLES$1.has(roleValue)) return;
4655
- }
4782
+ if (isPresentationRole(node)) return;
4656
4783
  if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
4657
4784
  context.report({
4658
4785
  node: node.name,
4659
- message: MESSAGE$54
4786
+ message: MESSAGE$56
4660
4787
  });
4661
4788
  } };
4662
4789
  }
@@ -4771,7 +4898,7 @@ const isReactComponentName = (name) => {
4771
4898
  };
4772
4899
  //#endregion
4773
4900
  //#region src/plugin/rules/a11y/control-has-associated-label.ts
4774
- 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`.";
4901
+ 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`.";
4775
4902
  const DEFAULT_IGNORE_ELEMENTS = ["link", "canvas"];
4776
4903
  const DEFAULT_LABELLING_PROPS = [
4777
4904
  "alt",
@@ -4932,7 +5059,7 @@ const controlHasAssociatedLabel = defineRule({
4932
5059
  for (const child of node.children) if (checkChildForLabel(child, 1, checkContext)) return;
4933
5060
  context.report({
4934
5061
  node: opening,
4935
- message: MESSAGE$53
5062
+ message: MESSAGE$55
4936
5063
  });
4937
5064
  } };
4938
5065
  }
@@ -5362,7 +5489,7 @@ const noVagueButtonLabel = defineRule({
5362
5489
  const hasJsxSpreadAttribute$1 = (attributes) => attributes.some((attribute) => isNodeOfType(attribute, "JSXSpreadAttribute"));
5363
5490
  //#endregion
5364
5491
  //#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
5365
- 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.";
5492
+ 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.";
5366
5493
  const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
5367
5494
  const NAME_PROVIDING_ATTRIBUTES = [
5368
5495
  "aria-label",
@@ -5385,7 +5512,7 @@ const dialogHasAccessibleName = defineRule({
5385
5512
  if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
5386
5513
  context.report({
5387
5514
  node: node.name,
5388
- message: MESSAGE$52
5515
+ message: MESSAGE$54
5389
5516
  });
5390
5517
  } })
5391
5518
  });
@@ -5424,7 +5551,7 @@ const isEs6Component = (node) => {
5424
5551
  };
5425
5552
  //#endregion
5426
5553
  //#region src/plugin/rules/react-builtins/display-name.ts
5427
- const MESSAGE$51 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
5554
+ const MESSAGE$53 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
5428
5555
  const DEFAULT_ADDITIONAL_HOCS = [
5429
5556
  "observer",
5430
5557
  "lazy",
@@ -5623,11 +5750,11 @@ const displayName = defineRule({
5623
5750
  category: "Architecture",
5624
5751
  create: (context) => {
5625
5752
  const settings = resolveSettings$44(context.settings);
5626
- const ignoreNamed = settings.ignoreTranspilerName ? false : true;
5753
+ const ignoreNamed = !settings.ignoreTranspilerName;
5627
5754
  const reportAt = (node) => {
5628
5755
  context.report({
5629
5756
  node,
5630
- message: MESSAGE$51
5757
+ message: MESSAGE$53
5631
5758
  });
5632
5759
  };
5633
5760
  return {
@@ -7775,7 +7902,7 @@ const forbidElements = defineRule({
7775
7902
  });
7776
7903
  //#endregion
7777
7904
  //#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
7778
- const MESSAGE$50 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
7905
+ const MESSAGE$52 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
7779
7906
  const forwardRefUsesRef = defineRule({
7780
7907
  id: "forward-ref-uses-ref",
7781
7908
  title: "forwardRef without ref parameter",
@@ -7795,7 +7922,7 @@ const forwardRefUsesRef = defineRule({
7795
7922
  if (isNodeOfType(onlyParam, "RestElement")) return;
7796
7923
  context.report({
7797
7924
  node: inner,
7798
- message: MESSAGE$50
7925
+ message: MESSAGE$52
7799
7926
  });
7800
7927
  } })
7801
7928
  });
@@ -7832,7 +7959,7 @@ const gitProviderUrlInjectionRisk = defineRule({
7832
7959
  });
7833
7960
  //#endregion
7834
7961
  //#region src/plugin/rules/a11y/heading-has-content.ts
7835
- 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`.";
7962
+ 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`.";
7836
7963
  const DEFAULT_HEADING_TAGS = [
7837
7964
  "h1",
7838
7965
  "h2",
@@ -7865,7 +7992,7 @@ const headingHasContent = defineRule({
7865
7992
  if (isHiddenFromScreenReader(node, context.settings)) return;
7866
7993
  context.report({
7867
7994
  node,
7868
- message: MESSAGE$49
7995
+ message: MESSAGE$51
7869
7996
  });
7870
7997
  } };
7871
7998
  }
@@ -8003,7 +8130,7 @@ const hooksNoNanInDeps = defineRule({
8003
8130
  });
8004
8131
  //#endregion
8005
8132
  //#region src/plugin/rules/a11y/html-has-lang.ts
8006
- const MESSAGE$48 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
8133
+ const MESSAGE$50 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
8007
8134
  const resolveSettings$38 = (settings) => {
8008
8135
  const reactDoctor = settings?.["react-doctor"];
8009
8136
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
@@ -8046,26 +8173,17 @@ const htmlHasLang = defineRule({
8046
8173
  return { JSXOpeningElement(node) {
8047
8174
  const tag = getElementType(node, context.settings);
8048
8175
  if (!tagSet.has(tag)) return;
8049
- const hasSpread = node.attributes.some((attribute) => isNodeOfType(attribute, "JSXSpreadAttribute"));
8050
8176
  const lang = hasJsxPropIgnoreCase(node.attributes, "lang");
8051
8177
  if (!lang) {
8052
8178
  context.report({
8053
8179
  node: node.name,
8054
- message: MESSAGE$48
8180
+ message: MESSAGE$50
8055
8181
  });
8056
8182
  return;
8057
8183
  }
8058
- const verdict = evaluateLang(lang.value);
8059
- if (verdict === "missing" || verdict === "empty") {
8060
- context.report({
8061
- node: lang,
8062
- message: MESSAGE$48
8063
- });
8064
- return;
8065
- }
8066
- if (hasSpread && !lang) context.report({
8067
- node: node.name,
8068
- message: MESSAGE$48
8184
+ if (evaluateLang(lang.value) === "empty") context.report({
8185
+ node: lang,
8186
+ message: MESSAGE$50
8069
8187
  });
8070
8188
  } };
8071
8189
  }
@@ -8279,7 +8397,7 @@ const htmlNoNestedInteractive = defineRule({
8279
8397
  });
8280
8398
  //#endregion
8281
8399
  //#region src/plugin/rules/a11y/iframe-has-title.ts
8282
- const MESSAGE$47 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
8400
+ const MESSAGE$49 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
8283
8401
  const evaluateTitleValue = (value) => {
8284
8402
  if (!value) return "missing";
8285
8403
  if (isNodeOfType(value, "Literal")) {
@@ -8319,14 +8437,14 @@ const iframeHasTitle = defineRule({
8319
8437
  if (!titleAttr) {
8320
8438
  if (hasSpread || tag === "iframe") context.report({
8321
8439
  node: node.name,
8322
- message: MESSAGE$47
8440
+ message: MESSAGE$49
8323
8441
  });
8324
8442
  return;
8325
8443
  }
8326
8444
  const verdict = evaluateTitleValue(titleAttr.value);
8327
8445
  if (verdict === "missing" || verdict === "empty") context.report({
8328
8446
  node: titleAttr,
8329
- message: MESSAGE$47
8447
+ message: MESSAGE$49
8330
8448
  });
8331
8449
  } })
8332
8450
  });
@@ -8430,7 +8548,7 @@ const iframeMissingSandbox = defineRule({
8430
8548
  });
8431
8549
  //#endregion
8432
8550
  //#region src/plugin/rules/a11y/img-redundant-alt.ts
8433
- const MESSAGE$46 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
8551
+ const MESSAGE$48 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
8434
8552
  const DEFAULT_COMPONENTS = ["img"];
8435
8553
  const DEFAULT_REDUNDANT_WORDS = [
8436
8554
  "image",
@@ -8495,7 +8613,7 @@ const imgRedundantAlt = defineRule({
8495
8613
  if (!altAttribute) return;
8496
8614
  if (altValueRedundant(altAttribute, settings.words)) context.report({
8497
8615
  node: altAttribute,
8498
- message: MESSAGE$46
8616
+ message: MESSAGE$48
8499
8617
  });
8500
8618
  } };
8501
8619
  }
@@ -8778,14 +8896,6 @@ const isNonInteractiveElement = (elementType, openingElement) => {
8778
8896
  //#region src/plugin/utils/is-non-interactive-role.ts
8779
8897
  const isNonInteractiveRole = (role) => NON_INTERACTIVE_ROLES.has(role);
8780
8898
  //#endregion
8781
- //#region src/plugin/utils/is-presentation-role.ts
8782
- const isPresentationRole = (openingElement) => {
8783
- const roleAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "role");
8784
- if (!roleAttribute) return false;
8785
- const value = getJsxPropStringValue(roleAttribute);
8786
- return value !== null && PRESENTATION_ROLES$2.has(value);
8787
- };
8788
- //#endregion
8789
8899
  //#region src/plugin/rules/a11y/interactive-supports-focus.ts
8790
8900
  const buildTabbableMessage = (role) => `Keyboard users can't tab to this '${role}' because it isn't focusable, so add \`tabIndex={0}\`.`;
8791
8901
  const buildFocusableMessage = (role) => `Keyboard users can't focus this '${role}' because it can't receive focus, so add \`tabIndex={0}\` or \`tabIndex={-1}\`.`;
@@ -10560,6 +10670,24 @@ const hasJsxKeyAttribute = (openingElement) => {
10560
10670
  return false;
10561
10671
  };
10562
10672
  //#endregion
10673
+ //#region src/plugin/utils/is-non-children-jsx-attribute-value.ts
10674
+ const ascendThroughJsxValueWrappers = (node) => {
10675
+ let current = node;
10676
+ while (current.parent) {
10677
+ const parent = current.parent;
10678
+ if (!(isNodeOfType(parent, "ChainExpression") || isNodeOfType(parent, "TSAsExpression") || isNodeOfType(parent, "TSSatisfiesExpression") || isNodeOfType(parent, "TSNonNullExpression") || isNodeOfType(parent, "LogicalExpression") || isNodeOfType(parent, "ConditionalExpression") && parent.test !== current)) break;
10679
+ current = parent;
10680
+ }
10681
+ return current;
10682
+ };
10683
+ const isNonChildrenJsxAttributeValue = (node) => {
10684
+ const container = ascendThroughJsxValueWrappers(node).parent;
10685
+ if (!container || !isNodeOfType(container, "JSXExpressionContainer")) return false;
10686
+ const attribute = container.parent;
10687
+ if (!attribute || !isNodeOfType(attribute, "JSXAttribute")) return false;
10688
+ return getJsxAttributeName(attribute.name) !== "children";
10689
+ };
10690
+ //#endregion
10563
10691
  //#region src/plugin/rules/react-builtins/jsx-key.ts
10564
10692
  const ITERATOR_METHOD_NAMES = new Set([
10565
10693
  "map",
@@ -10598,6 +10726,7 @@ const findEnclosingIteratorContext = (jsxNode) => {
10598
10726
  const arrayParent = parent.parent;
10599
10727
  if (arrayParent && isNodeOfType(arrayParent, "Property")) return null;
10600
10728
  if (arrayParent && isNodeOfType(arrayParent, "ArrayExpression")) return null;
10729
+ if (isNonChildrenJsxAttributeValue(parent)) return null;
10601
10730
  return { kind: "array" };
10602
10731
  } else if (isNodeOfType(parent, "CallExpression")) {
10603
10732
  const callee = parent.callee;
@@ -10610,10 +10739,13 @@ const findEnclosingIteratorContext = (jsxNode) => {
10610
10739
  if (!targetArg) return null;
10611
10740
  let walker = current;
10612
10741
  while (walker && walker !== parent) {
10613
- if (walker === targetArg) return {
10614
- kind: "iterator",
10615
- callExpression: parent
10616
- };
10742
+ if (walker === targetArg) {
10743
+ if (isNonChildrenJsxAttributeValue(parent)) return null;
10744
+ return {
10745
+ kind: "iterator",
10746
+ callExpression: parent
10747
+ };
10748
+ }
10617
10749
  walker = walker.parent ?? null;
10618
10750
  }
10619
10751
  return null;
@@ -10852,7 +10984,7 @@ const jsxMaxDepth = defineRule({
10852
10984
  });
10853
10985
  //#endregion
10854
10986
  //#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
10855
- const MESSAGE$45 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
10987
+ const MESSAGE$47 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
10856
10988
  const LITERAL_TEXT_TAGS = new Set([
10857
10989
  "code",
10858
10990
  "pre",
@@ -10888,7 +11020,7 @@ const jsxNoCommentTextnodes = defineRule({
10888
11020
  if (isInsideLiteralTextTag(node)) return;
10889
11021
  context.report({
10890
11022
  node,
10891
- message: MESSAGE$45
11023
+ message: MESSAGE$47
10892
11024
  });
10893
11025
  } })
10894
11026
  });
@@ -10919,7 +11051,7 @@ const isInsideFunctionScope = (node) => {
10919
11051
  };
10920
11052
  //#endregion
10921
11053
  //#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
10922
- const MESSAGE$44 = "Every reader of this context redraws on each render because you build its `value` inline.";
11054
+ const MESSAGE$46 = "Every reader of this context redraws on each render because you build its `value` inline.";
10923
11055
  const CONTEXT_MODULES$1 = [
10924
11056
  "react",
10925
11057
  "use-context-selector",
@@ -11017,7 +11149,7 @@ const jsxNoConstructedContextValues = defineRule({
11017
11149
  if (!isConstructedValue(innerExpression)) continue;
11018
11150
  context.report({
11019
11151
  node: attribute,
11020
- message: MESSAGE$44
11152
+ message: MESSAGE$46
11021
11153
  });
11022
11154
  }
11023
11155
  }
@@ -11103,7 +11235,7 @@ const isJsxAttributeOnIntrinsicHtmlElement = (attribute) => {
11103
11235
  };
11104
11236
  //#endregion
11105
11237
  //#region src/plugin/rules/react-builtins/jsx-no-jsx-as-prop.ts
11106
- const MESSAGE$43 = "This child redraws every render because the prop gets brand new JSX each time.";
11238
+ const MESSAGE$45 = "This child redraws every render because the prop gets brand new JSX each time.";
11107
11239
  const KNOWN_SLOT_PROP_NAMES = new Set([
11108
11240
  "icon",
11109
11241
  "Icon",
@@ -11372,7 +11504,7 @@ const jsxNoJsxAsProp = defineRule({
11372
11504
  if (!isJsxProducingExpression(expressionNode) && !followsRenderLocalJsxBinding(expressionNode, node)) return;
11373
11505
  context.report({
11374
11506
  node,
11375
- message: MESSAGE$43
11507
+ message: MESSAGE$45
11376
11508
  });
11377
11509
  }
11378
11510
  };
@@ -11660,7 +11792,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
11660
11792
  ];
11661
11793
  //#endregion
11662
11794
  //#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
11663
- const MESSAGE$42 = "This child redraws every render because the prop gets a brand new array each time.";
11795
+ const MESSAGE$44 = "This child redraws every render because the prop gets a brand new array each time.";
11664
11796
  const isDataArrayPropName = (propName) => {
11665
11797
  if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
11666
11798
  for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -11744,7 +11876,7 @@ const jsxNoNewArrayAsProp = defineRule({
11744
11876
  if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
11745
11877
  context.report({
11746
11878
  node,
11747
- message: MESSAGE$42
11879
+ message: MESSAGE$44
11748
11880
  });
11749
11881
  }
11750
11882
  };
@@ -12002,7 +12134,7 @@ const SAFE_RECEIVER_NAMES = new Set([
12002
12134
  ]);
12003
12135
  //#endregion
12004
12136
  //#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
12005
- const MESSAGE$41 = "This child redraws every render because the prop gets a brand new function each time.";
12137
+ const MESSAGE$43 = "This child redraws every render because the prop gets a brand new function each time.";
12006
12138
  const isAccessorPredicateName = (propName) => {
12007
12139
  for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
12008
12140
  if (propName.length <= prefix.length) continue;
@@ -12208,7 +12340,7 @@ const jsxNoNewFunctionAsProp = defineRule({
12208
12340
  if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
12209
12341
  context.report({
12210
12342
  node,
12211
- message: MESSAGE$41
12343
+ message: MESSAGE$43
12212
12344
  });
12213
12345
  }
12214
12346
  };
@@ -12428,7 +12560,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
12428
12560
  ];
12429
12561
  //#endregion
12430
12562
  //#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
12431
- const MESSAGE$40 = "This child redraws every render because the prop gets a brand new object each time.";
12563
+ const MESSAGE$42 = "This child redraws every render because the prop gets a brand new object each time.";
12432
12564
  const isConfigObjectPropName = (propName) => {
12433
12565
  if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
12434
12566
  for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -12516,7 +12648,7 @@ const jsxNoNewObjectAsProp = defineRule({
12516
12648
  if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
12517
12649
  context.report({
12518
12650
  node,
12519
- message: MESSAGE$40
12651
+ message: MESSAGE$42
12520
12652
  });
12521
12653
  }
12522
12654
  };
@@ -12524,7 +12656,7 @@ const jsxNoNewObjectAsProp = defineRule({
12524
12656
  });
12525
12657
  //#endregion
12526
12658
  //#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
12527
- const MESSAGE$39 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
12659
+ const MESSAGE$41 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
12528
12660
  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;
12529
12661
  const resolveSettings$28 = (settings) => {
12530
12662
  const reactDoctor = settings?.["react-doctor"];
@@ -12565,7 +12697,7 @@ const jsxNoScriptUrl = defineRule({
12565
12697
  if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
12566
12698
  if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
12567
12699
  node: attribute,
12568
- message: MESSAGE$39
12700
+ message: MESSAGE$41
12569
12701
  });
12570
12702
  }
12571
12703
  } };
@@ -12880,7 +13012,7 @@ const jsxPropsNoSpreadMulti = defineRule({
12880
13012
  });
12881
13013
  //#endregion
12882
13014
  //#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
12883
- const MESSAGE$38 = "You can't tell what props reach this element when you spread them.";
13015
+ const MESSAGE$40 = "You can't tell what props reach this element when you spread them.";
12884
13016
  const resolveSettings$25 = (settings) => {
12885
13017
  const reactDoctor = settings?.["react-doctor"];
12886
13018
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
@@ -12921,7 +13053,7 @@ const jsxPropsNoSpreading = defineRule({
12921
13053
  }
12922
13054
  context.report({
12923
13055
  node: attribute,
12924
- message: MESSAGE$38
13056
+ message: MESSAGE$40
12925
13057
  });
12926
13058
  }
12927
13059
  } };
@@ -13149,7 +13281,7 @@ const labelHasAssociatedControl = defineRule({
13149
13281
  });
13150
13282
  //#endregion
13151
13283
  //#region src/plugin/rules/a11y/lang.ts
13152
- 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`.";
13284
+ 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`.";
13153
13285
  const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
13154
13286
  "aa",
13155
13287
  "ab",
@@ -13361,7 +13493,7 @@ const lang = defineRule({
13361
13493
  if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
13362
13494
  context.report({
13363
13495
  node: langAttr,
13364
- message: MESSAGE$37
13496
+ message: MESSAGE$39
13365
13497
  });
13366
13498
  return;
13367
13499
  }
@@ -13370,7 +13502,7 @@ const lang = defineRule({
13370
13502
  if (value === null) return;
13371
13503
  if (!isValidLangTag(value)) context.report({
13372
13504
  node: langAttr,
13373
- message: MESSAGE$37
13505
+ message: MESSAGE$39
13374
13506
  });
13375
13507
  } })
13376
13508
  });
@@ -13396,6 +13528,7 @@ const mcpToolCapabilityRisk = defineRule({
13396
13528
  shouldScan: (file) => isProductionSourcePath(file.relativePath),
13397
13529
  pattern: /\bserver\.\s*tool\s*\(|\bregisterTool\s*\(|\bsetRequestHandler\s*\(\s*CallToolRequestSchema/,
13398
13530
  requireAll: [/\bfrom\s+["']@modelcontextprotocol\/sdk[^"']*["']|\bMcpServer\b|\bMcpAgent\b/, AGENT_TOOL_DANGEROUS_CAPABILITY_PATTERN],
13531
+ ignoreStringLiterals: true,
13399
13532
  message: "An MCP tool/resource/prompt handler appears to expose file, shell, network, or code-execution capability."
13400
13533
  })
13401
13534
  });
@@ -13414,7 +13547,7 @@ const mdxSsrExecutionRisk = defineRule({
13414
13547
  });
13415
13548
  //#endregion
13416
13549
  //#region src/plugin/rules/a11y/media-has-caption.ts
13417
- const MESSAGE$36 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
13550
+ const MESSAGE$38 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
13418
13551
  const DEFAULT_AUDIO = ["audio"];
13419
13552
  const DEFAULT_VIDEO = ["video"];
13420
13553
  const DEFAULT_TRACK = ["track"];
@@ -13455,7 +13588,7 @@ const mediaHasCaption = defineRule({
13455
13588
  if (!parent || !isNodeOfType(parent, "JSXElement")) {
13456
13589
  context.report({
13457
13590
  node: node.name,
13458
- message: MESSAGE$36
13591
+ message: MESSAGE$38
13459
13592
  });
13460
13593
  return;
13461
13594
  }
@@ -13472,7 +13605,7 @@ const mediaHasCaption = defineRule({
13472
13605
  return kindValue.value.toLowerCase() === "captions";
13473
13606
  })) context.report({
13474
13607
  node: node.name,
13475
- message: MESSAGE$36
13608
+ message: MESSAGE$38
13476
13609
  });
13477
13610
  } };
13478
13611
  }
@@ -15273,7 +15406,7 @@ const nextjsNoVercelOgImport = defineRule({
15273
15406
  });
15274
15407
  //#endregion
15275
15408
  //#region src/plugin/rules/a11y/no-access-key.ts
15276
- const MESSAGE$35 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
15409
+ const MESSAGE$37 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
15277
15410
  const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
15278
15411
  const noAccessKey = defineRule({
15279
15412
  id: "no-access-key",
@@ -15290,7 +15423,7 @@ const noAccessKey = defineRule({
15290
15423
  if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
15291
15424
  context.report({
15292
15425
  node: accessKey,
15293
- message: MESSAGE$35
15426
+ message: MESSAGE$37
15294
15427
  });
15295
15428
  return;
15296
15429
  }
@@ -15300,7 +15433,7 @@ const noAccessKey = defineRule({
15300
15433
  if (isUndefinedIdentifier(expression)) return;
15301
15434
  context.report({
15302
15435
  node: accessKey,
15303
- message: MESSAGE$35
15436
+ message: MESSAGE$37
15304
15437
  });
15305
15438
  }
15306
15439
  } })
@@ -15783,7 +15916,7 @@ const noAdjustStateOnPropChange = defineRule({
15783
15916
  });
15784
15917
  //#endregion
15785
15918
  //#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
15786
- 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.";
15919
+ 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.";
15787
15920
  const noAriaHiddenOnFocusable = defineRule({
15788
15921
  id: "no-aria-hidden-on-focusable",
15789
15922
  title: "aria-hidden on focusable element",
@@ -15810,7 +15943,7 @@ const noAriaHiddenOnFocusable = defineRule({
15810
15943
  const isImplicitlyFocusable = isInteractiveElement(tag, node);
15811
15944
  if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
15812
15945
  node: ariaHidden,
15813
- message: MESSAGE$34
15946
+ message: MESSAGE$36
15814
15947
  });
15815
15948
  } })
15816
15949
  });
@@ -16178,7 +16311,7 @@ const noArrayIndexAsKey = defineRule({
16178
16311
  });
16179
16312
  //#endregion
16180
16313
  //#region src/plugin/rules/react-builtins/no-array-index-key.ts
16181
- const MESSAGE$33 = "Your users can see & submit the wrong data when this list reorders.";
16314
+ const MESSAGE$35 = "Your users can see & submit the wrong data when this list reorders.";
16182
16315
  const SECOND_INDEX_METHODS = new Set([
16183
16316
  "every",
16184
16317
  "filter",
@@ -16382,7 +16515,7 @@ const noArrayIndexKey = defineRule({
16382
16515
  }
16383
16516
  context.report({
16384
16517
  node: keyAttribute,
16385
- message: MESSAGE$33
16518
+ message: MESSAGE$35
16386
16519
  });
16387
16520
  },
16388
16521
  CallExpression(node) {
@@ -16402,7 +16535,7 @@ const noArrayIndexKey = defineRule({
16402
16535
  if (propName !== "key") continue;
16403
16536
  if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
16404
16537
  node: property,
16405
- message: MESSAGE$33
16538
+ message: MESSAGE$35
16406
16539
  });
16407
16540
  }
16408
16541
  }
@@ -16410,7 +16543,7 @@ const noArrayIndexKey = defineRule({
16410
16543
  });
16411
16544
  //#endregion
16412
16545
  //#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
16413
- 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.";
16546
+ 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.";
16414
16547
  const noAsyncEffectCallback = defineRule({
16415
16548
  id: "no-async-effect-callback",
16416
16549
  title: "Async effect callback",
@@ -16424,13 +16557,13 @@ const noAsyncEffectCallback = defineRule({
16424
16557
  if (!callback.async) return;
16425
16558
  context.report({
16426
16559
  node: callback,
16427
- message: MESSAGE$32
16560
+ message: MESSAGE$34
16428
16561
  });
16429
16562
  } })
16430
16563
  });
16431
16564
  //#endregion
16432
16565
  //#region src/plugin/rules/a11y/no-autofocus.ts
16433
- 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.";
16566
+ 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.";
16434
16567
  const resolveSettings$21 = (settings) => {
16435
16568
  const reactDoctor = settings?.["react-doctor"];
16436
16569
  return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
@@ -16486,7 +16619,7 @@ const noAutofocus = defineRule({
16486
16619
  }
16487
16620
  context.report({
16488
16621
  node: autoFocusAttribute,
16489
- message: MESSAGE$31
16622
+ message: MESSAGE$33
16490
16623
  });
16491
16624
  } };
16492
16625
  }
@@ -16990,7 +17123,7 @@ const noChainStateUpdates = defineRule({
16990
17123
  });
16991
17124
  //#endregion
16992
17125
  //#region src/plugin/rules/react-builtins/no-children-prop.ts
16993
- const MESSAGE$30 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
17126
+ const MESSAGE$32 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
16994
17127
  const noChildrenProp = defineRule({
16995
17128
  id: "no-children-prop",
16996
17129
  title: "Children passed as a prop",
@@ -17002,7 +17135,7 @@ const noChildrenProp = defineRule({
17002
17135
  if (node.name.name !== "children") return;
17003
17136
  context.report({
17004
17137
  node: node.name,
17005
- message: MESSAGE$30
17138
+ message: MESSAGE$32
17006
17139
  });
17007
17140
  },
17008
17141
  CallExpression(node) {
@@ -17015,7 +17148,7 @@ const noChildrenProp = defineRule({
17015
17148
  const propertyKey = property.key;
17016
17149
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
17017
17150
  node: propertyKey,
17018
- message: MESSAGE$30
17151
+ message: MESSAGE$32
17019
17152
  });
17020
17153
  }
17021
17154
  }
@@ -17023,7 +17156,7 @@ const noChildrenProp = defineRule({
17023
17156
  });
17024
17157
  //#endregion
17025
17158
  //#region src/plugin/rules/react-builtins/no-clone-element.ts
17026
- const MESSAGE$29 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
17159
+ const MESSAGE$31 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
17027
17160
  const noCloneElement = defineRule({
17028
17161
  id: "no-clone-element",
17029
17162
  title: "cloneElement makes child props fragile",
@@ -17036,7 +17169,7 @@ const noCloneElement = defineRule({
17036
17169
  if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
17037
17170
  if (isImportedFromModule(node, "cloneElement", "react")) context.report({
17038
17171
  node: callee,
17039
- message: MESSAGE$29
17172
+ message: MESSAGE$31
17040
17173
  });
17041
17174
  return;
17042
17175
  }
@@ -17049,7 +17182,7 @@ const noCloneElement = defineRule({
17049
17182
  if (!isImportedFromModule(node, callee.object.name, "react")) return;
17050
17183
  context.report({
17051
17184
  node: callee,
17052
- message: MESSAGE$29
17185
+ message: MESSAGE$31
17053
17186
  });
17054
17187
  }
17055
17188
  } })
@@ -17098,7 +17231,7 @@ const enclosingComponentOrHookName = (node) => {
17098
17231
  };
17099
17232
  //#endregion
17100
17233
  //#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
17101
- const MESSAGE$28 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
17234
+ const MESSAGE$30 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
17102
17235
  const CONTEXT_MODULES = [
17103
17236
  "react",
17104
17237
  "use-context-selector",
@@ -17134,13 +17267,13 @@ const noCreateContextInRender = defineRule({
17134
17267
  if (!componentOrHookName) return;
17135
17268
  context.report({
17136
17269
  node,
17137
- message: `${MESSAGE$28} (called inside "${componentOrHookName}")`
17270
+ message: `${MESSAGE$30} (called inside "${componentOrHookName}")`
17138
17271
  });
17139
17272
  } })
17140
17273
  });
17141
17274
  //#endregion
17142
17275
  //#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
17143
- 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.";
17276
+ 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.";
17144
17277
  const noCreateRefInFunctionComponent = defineRule({
17145
17278
  id: "no-create-ref-in-function-component",
17146
17279
  title: "createRef in function component",
@@ -17159,7 +17292,7 @@ const noCreateRefInFunctionComponent = defineRule({
17159
17292
  if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes))) return;
17160
17293
  context.report({
17161
17294
  node,
17162
- message: MESSAGE$27
17295
+ message: MESSAGE$29
17163
17296
  });
17164
17297
  } })
17165
17298
  });
@@ -17299,12 +17432,13 @@ const noCreateStoreInRender = defineRule({
17299
17432
  });
17300
17433
  //#endregion
17301
17434
  //#region src/plugin/rules/react-builtins/no-danger.ts
17302
- const MESSAGE$26 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
17435
+ const MESSAGE$28 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
17303
17436
  const noDanger = defineRule({
17304
17437
  id: "no-danger",
17305
17438
  title: "Raw HTML injection can run unsafe markup",
17306
17439
  severity: "warn",
17307
17440
  category: "Security",
17441
+ defaultEnabled: false,
17308
17442
  recommendation: "Render trusted content as React children so attacker-controlled HTML cannot run in users' browsers.",
17309
17443
  create: (context) => ({
17310
17444
  JSXOpeningElement(node) {
@@ -17312,7 +17446,7 @@ const noDanger = defineRule({
17312
17446
  if (!propAttribute) return;
17313
17447
  context.report({
17314
17448
  node: propAttribute.name,
17315
- message: MESSAGE$26
17449
+ message: MESSAGE$28
17316
17450
  });
17317
17451
  },
17318
17452
  CallExpression(node) {
@@ -17324,7 +17458,7 @@ const noDanger = defineRule({
17324
17458
  const propertyKey = property.key;
17325
17459
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
17326
17460
  node: propertyKey,
17327
- message: MESSAGE$26
17461
+ message: MESSAGE$28
17328
17462
  });
17329
17463
  }
17330
17464
  }
@@ -17332,7 +17466,7 @@ const noDanger = defineRule({
17332
17466
  });
17333
17467
  //#endregion
17334
17468
  //#region src/plugin/rules/react-builtins/no-danger-with-children.ts
17335
- const MESSAGE$25 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
17469
+ const MESSAGE$27 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
17336
17470
  const isLineBreak = (child) => {
17337
17471
  if (!isNodeOfType(child, "JSXText")) return false;
17338
17472
  return child.value.trim().length === 0 && child.value.includes("\n");
@@ -17402,7 +17536,7 @@ const noDangerWithChildren = defineRule({
17402
17536
  if (!hasChildrenProp && !hasNestedChildren) return;
17403
17537
  if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
17404
17538
  node: opening,
17405
- message: MESSAGE$25
17539
+ message: MESSAGE$27
17406
17540
  });
17407
17541
  },
17408
17542
  CallExpression(node) {
@@ -17414,7 +17548,7 @@ const noDangerWithChildren = defineRule({
17414
17548
  if (!propsShape.hasDangerously) return;
17415
17549
  if (node.arguments.length >= 3 || propsShape.hasChildren) context.report({
17416
17550
  node,
17417
- message: MESSAGE$25
17551
+ message: MESSAGE$27
17418
17552
  });
17419
17553
  }
17420
17554
  })
@@ -17991,7 +18125,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
17991
18125
  //#endregion
17992
18126
  //#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
17993
18127
  const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
17994
- const MESSAGE$24 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
18128
+ const MESSAGE$26 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
17995
18129
  const resolveSettings$20 = (settings) => {
17996
18130
  const reactDoctor = settings?.["react-doctor"];
17997
18131
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidMountSetState ?? {} : {}).mode ?? "allowed" };
@@ -18010,7 +18144,7 @@ const noDidMountSetState = defineRule({
18010
18144
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$2, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
18011
18145
  context.report({
18012
18146
  node: node.callee,
18013
- message: MESSAGE$24
18147
+ message: MESSAGE$26
18014
18148
  });
18015
18149
  } };
18016
18150
  }
@@ -18018,7 +18152,7 @@ const noDidMountSetState = defineRule({
18018
18152
  //#endregion
18019
18153
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
18020
18154
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
18021
- const MESSAGE$23 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
18155
+ const MESSAGE$25 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
18022
18156
  const resolveSettings$19 = (settings) => {
18023
18157
  const reactDoctor = settings?.["react-doctor"];
18024
18158
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidUpdateSetState ?? {} : {}).mode ?? "allowed" };
@@ -18037,7 +18171,7 @@ const noDidUpdateSetState = defineRule({
18037
18171
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
18038
18172
  context.report({
18039
18173
  node: node.callee,
18040
- message: MESSAGE$23
18174
+ message: MESSAGE$25
18041
18175
  });
18042
18176
  } };
18043
18177
  }
@@ -18060,7 +18194,7 @@ const isStateMemberExpression = (node) => {
18060
18194
  };
18061
18195
  //#endregion
18062
18196
  //#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
18063
- const MESSAGE$22 = "Your users see stale data because mutating `this.state` by hand never redraws & gets overwritten.";
18197
+ const MESSAGE$24 = "Your users see stale data because mutating `this.state` by hand never redraws & gets overwritten.";
18064
18198
  const shouldIgnoreMutation = (node) => {
18065
18199
  let isConstructor = false;
18066
18200
  let isInsideCallExpression = false;
@@ -18082,7 +18216,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
18082
18216
  if (shouldIgnoreMutation(reportNode)) return;
18083
18217
  context.report({
18084
18218
  node: reportNode,
18085
- message: MESSAGE$22
18219
+ message: MESSAGE$24
18086
18220
  });
18087
18221
  };
18088
18222
  const noDirectMutationState = defineRule({
@@ -18292,6 +18426,26 @@ const noDocumentStartViewTransition = defineRule({
18292
18426
  } })
18293
18427
  });
18294
18428
  //#endregion
18429
+ //#region src/plugin/rules/js-performance/no-document-write.ts
18430
+ 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.";
18431
+ const WRITE_METHODS = new Set(["write", "writeln"]);
18432
+ const noDocumentWrite = defineRule({
18433
+ id: "no-document-write",
18434
+ title: "document.write/writeln",
18435
+ severity: "warn",
18436
+ recommendation: "Don't use `document.write()`/`document.writeln()`. Append DOM nodes or set `innerHTML`/`textContent` on a specific element instead.",
18437
+ create: (context) => ({ CallExpression(node) {
18438
+ const callee = node.callee;
18439
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
18440
+ if (!isNodeOfType(callee.object, "Identifier") || callee.object.name !== "document") return;
18441
+ if (!isNodeOfType(callee.property, "Identifier") || !WRITE_METHODS.has(callee.property.name)) return;
18442
+ context.report({
18443
+ node,
18444
+ message: MESSAGE$23
18445
+ });
18446
+ } })
18447
+ });
18448
+ //#endregion
18295
18449
  //#region src/plugin/rules/bundle-size/no-dynamic-import-path.ts
18296
18450
  const noDynamicImportPath = defineRule({
18297
18451
  id: "no-dynamic-import-path",
@@ -19670,7 +19824,7 @@ const ALLOWED_NAMESPACES = new Set([
19670
19824
  "ReactDOM",
19671
19825
  "ReactDom"
19672
19826
  ]);
19673
- const MESSAGE$21 = "`findDOMNode` crashes your app in React 19 because it was removed.";
19827
+ const MESSAGE$22 = "`findDOMNode` crashes your app in React 19 because it was removed.";
19674
19828
  const noFindDomNode = defineRule({
19675
19829
  id: "no-find-dom-node",
19676
19830
  title: "findDOMNode breaks component encapsulation",
@@ -19681,7 +19835,7 @@ const noFindDomNode = defineRule({
19681
19835
  if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
19682
19836
  context.report({
19683
19837
  node: callee,
19684
- message: MESSAGE$21
19838
+ message: MESSAGE$22
19685
19839
  });
19686
19840
  return;
19687
19841
  }
@@ -19692,7 +19846,7 @@ const noFindDomNode = defineRule({
19692
19846
  if (callee.property.name !== "findDOMNode") return;
19693
19847
  context.report({
19694
19848
  node: callee.property,
19695
- message: MESSAGE$21
19849
+ message: MESSAGE$22
19696
19850
  });
19697
19851
  }
19698
19852
  } })
@@ -19934,7 +20088,7 @@ const noGrayOnColoredBackground = defineRule({
19934
20088
  });
19935
20089
  //#endregion
19936
20090
  //#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
19937
- 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.";
20091
+ 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.";
19938
20092
  const noImgLazyWithHighFetchpriority = defineRule({
19939
20093
  id: "no-img-lazy-with-high-fetchpriority",
19940
20094
  title: "Lazy image with high fetchPriority",
@@ -19948,7 +20102,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
19948
20102
  if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
19949
20103
  context.report({
19950
20104
  node: node.name,
19951
- message: MESSAGE$20
20105
+ message: MESSAGE$21
19952
20106
  });
19953
20107
  } })
19954
20108
  });
@@ -20045,15 +20199,20 @@ const noInlineExhaustiveStyle = defineRule({
20045
20199
  severity: "warn",
20046
20200
  tags: ["test-noise", "react-jsx-only"],
20047
20201
  recommendation: "Move the styles to a CSS class, CSS module, Tailwind utilities, or a styled component. Big inline objects are hard to read and rebuild on every update.",
20048
- create: (context) => ({ JSXAttribute(node) {
20049
- const expression = getInlineStyleExpression(node);
20050
- if (!expression) return;
20051
- const propertyCount = expression.properties?.filter((property) => isNodeOfType(property, "Property")).length ?? 0;
20052
- if (propertyCount >= 8) context.report({
20053
- node: expression,
20054
- message: `This inline style has ${propertyCount} properties, which is hard to read & rebuilds every render. Move it to a CSS class, CSS module, or styled component.`
20055
- });
20056
- } })
20202
+ create: (context) => {
20203
+ if (isGeneratedImageRenderContext(context)) return {};
20204
+ return { JSXAttribute(node) {
20205
+ const expression = getInlineStyleExpression(node);
20206
+ if (!expression) return;
20207
+ const propertyCount = expression.properties?.filter((property) => isNodeOfType(property, "Property")).length ?? 0;
20208
+ if (propertyCount < 8) return;
20209
+ if (isGeneratedImageRenderContext(context, node.parent ?? void 0)) return;
20210
+ context.report({
20211
+ node: expression,
20212
+ message: `This inline style has ${propertyCount} properties, which is hard to read & rebuilds every render. Move it to a CSS class, CSS module, or styled component.`
20213
+ });
20214
+ } };
20215
+ }
20057
20216
  });
20058
20217
  //#endregion
20059
20218
  //#region src/plugin/rules/performance/no-inline-prop-on-memo-component.ts
@@ -20183,7 +20342,7 @@ const noIsMounted = defineRule({
20183
20342
  });
20184
20343
  //#endregion
20185
20344
  //#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
20186
- 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)`.";
20345
+ 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)`.";
20187
20346
  const isJsonMethodCall = (node, method) => {
20188
20347
  if (!isNodeOfType(node, "CallExpression")) return false;
20189
20348
  const callee = node.callee;
@@ -20200,13 +20359,13 @@ const noJsonParseStringifyClone = defineRule({
20200
20359
  if (!firstArgument || !isJsonMethodCall(firstArgument, "stringify")) return;
20201
20360
  context.report({
20202
20361
  node,
20203
- message: MESSAGE$19
20362
+ message: MESSAGE$20
20204
20363
  });
20205
20364
  } })
20206
20365
  });
20207
20366
  //#endregion
20208
20367
  //#region src/plugin/rules/correctness/no-jsx-element-type.ts
20209
- const MESSAGE$18 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
20368
+ const MESSAGE$19 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
20210
20369
  const isJsxElementTypeReference = (node) => {
20211
20370
  if (!isNodeOfType(node, "TSTypeReference")) return false;
20212
20371
  const typeName = node.typeName;
@@ -20223,7 +20382,7 @@ const checkReturnType = (context, returnType) => {
20223
20382
  if (!typeAnnotation) return;
20224
20383
  if (isJsxElementTypeReference(typeAnnotation)) context.report({
20225
20384
  node: typeAnnotation,
20226
- message: MESSAGE$18
20385
+ message: MESSAGE$19
20227
20386
  });
20228
20387
  };
20229
20388
  const noJsxElementType = defineRule({
@@ -20678,7 +20837,7 @@ const noMoment = defineRule({
20678
20837
  });
20679
20838
  //#endregion
20680
20839
  //#region src/plugin/rules/react-builtins/no-multi-comp.ts
20681
- const MESSAGE$17 = "This file declares several components, so each component is harder to find, test, and change.";
20840
+ const MESSAGE$18 = "This file declares several components, so each component is harder to find, test, and change.";
20682
20841
  const resolveSettings$16 = (settings) => {
20683
20842
  const reactDoctor = settings?.["react-doctor"];
20684
20843
  return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
@@ -21000,7 +21159,7 @@ const noMultiComp = defineRule({
21000
21159
  if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
21001
21160
  for (const component of flagged.slice(1)) context.report({
21002
21161
  node: component.reportNode,
21003
- message: MESSAGE$17
21162
+ message: MESSAGE$18
21004
21163
  });
21005
21164
  } };
21006
21165
  }
@@ -21168,7 +21327,7 @@ const resolveReducerFunction = (node, currentFilename) => {
21168
21327
  };
21169
21328
  //#endregion
21170
21329
  //#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
21171
- const MESSAGE$16 = "This reducer changes state in place, so your update is silently skipped.";
21330
+ const MESSAGE$17 = "This reducer changes state in place, so your update is silently skipped.";
21172
21331
  const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
21173
21332
  "copyWithin",
21174
21333
  "fill",
@@ -21378,7 +21537,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
21378
21537
  reportedNodes.add(options.crossFileConsumerCallSite);
21379
21538
  context.report({
21380
21539
  node: options.crossFileConsumerCallSite,
21381
- message: `${MESSAGE$16} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
21540
+ message: `${MESSAGE$17} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
21382
21541
  });
21383
21542
  return;
21384
21543
  }
@@ -21387,7 +21546,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
21387
21546
  reportedNodes.add(mutation.node);
21388
21547
  context.report({
21389
21548
  node: mutation.node,
21390
- message: MESSAGE$16
21549
+ message: MESSAGE$17
21391
21550
  });
21392
21551
  }
21393
21552
  };
@@ -21659,7 +21818,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
21659
21818
  });
21660
21819
  //#endregion
21661
21820
  //#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
21662
- const MESSAGE$15 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
21821
+ const MESSAGE$16 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
21663
21822
  const resolveSettings$14 = (settings) => {
21664
21823
  const reactDoctor = settings?.["react-doctor"];
21665
21824
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noNoninteractiveTabindex ?? {} : {};
@@ -21687,7 +21846,7 @@ const noNoninteractiveTabindex = defineRule({
21687
21846
  if (numeric === null) {
21688
21847
  if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues) context.report({
21689
21848
  node: tabIndex,
21690
- message: MESSAGE$15
21849
+ message: MESSAGE$16
21691
21850
  });
21692
21851
  return;
21693
21852
  }
@@ -21700,7 +21859,7 @@ const noNoninteractiveTabindex = defineRule({
21700
21859
  if (!roleAttribute) {
21701
21860
  context.report({
21702
21861
  node: tabIndex,
21703
- message: MESSAGE$15
21862
+ message: MESSAGE$16
21704
21863
  });
21705
21864
  return;
21706
21865
  }
@@ -21714,7 +21873,7 @@ const noNoninteractiveTabindex = defineRule({
21714
21873
  }
21715
21874
  context.report({
21716
21875
  node: tabIndex,
21717
- message: MESSAGE$15
21876
+ message: MESSAGE$16
21718
21877
  });
21719
21878
  } };
21720
21879
  }
@@ -22405,7 +22564,7 @@ const noRandomKey = defineRule({
22405
22564
  });
22406
22565
  //#endregion
22407
22566
  //#region src/plugin/rules/react-builtins/no-react-children.ts
22408
- const MESSAGE$14 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
22567
+ const MESSAGE$15 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
22409
22568
  const isChildrenIdentifier = (node, contextNode) => {
22410
22569
  if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
22411
22570
  return isImportedFromModule(contextNode, "Children", "react");
@@ -22431,13 +22590,13 @@ const noReactChildren = defineRule({
22431
22590
  if (isChildrenIdentifier(memberObject, node)) {
22432
22591
  context.report({
22433
22592
  node: calleeOuter,
22434
- message: MESSAGE$14
22593
+ message: MESSAGE$15
22435
22594
  });
22436
22595
  return;
22437
22596
  }
22438
22597
  if (isReactNamespaceMember(memberObject, node)) context.report({
22439
22598
  node: calleeOuter,
22440
- message: MESSAGE$14
22599
+ message: MESSAGE$15
22441
22600
  });
22442
22601
  } })
22443
22602
  });
@@ -22760,7 +22919,7 @@ const noRenderPropChildren = defineRule({
22760
22919
  });
22761
22920
  //#endregion
22762
22921
  //#region src/plugin/rules/react-builtins/no-render-return-value.ts
22763
- const MESSAGE$13 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
22922
+ const MESSAGE$14 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
22764
22923
  const isReactDomRenderCall = (node) => {
22765
22924
  if (!isNodeOfType(node.callee, "MemberExpression")) return false;
22766
22925
  if (!isNodeOfType(node.callee.object, "Identifier")) return false;
@@ -22784,7 +22943,7 @@ const noRenderReturnValue = defineRule({
22784
22943
  if (!isUsedAsReturnValue(node.parent)) return;
22785
22944
  context.report({
22786
22945
  node: node.callee,
22787
- message: MESSAGE$13
22946
+ message: MESSAGE$14
22788
22947
  });
22789
22948
  } })
22790
22949
  });
@@ -22944,11 +23103,17 @@ const classifySecretFileExposure = (filename, options = {}) => {
22944
23103
  return "unknown";
22945
23104
  };
22946
23105
  //#endregion
22947
- //#region src/plugin/utils/get-identifier-trailing-word.ts
22948
- const getIdentifierTrailingWord = (identifierName) => {
22949
- return identifierName.match(/[A-Z]+(?=[A-Z][a-z]|\b)|[A-Z]?[a-z]+|\d+/g)?.at(-1)?.toLowerCase() ?? identifierName.toLowerCase();
23106
+ //#region src/plugin/utils/tokenize-identifier-words.ts
23107
+ const IDENTIFIER_WORD_PATTERN = /[A-Z]+(?=[A-Z][a-z]|\b)|[A-Z]?[a-z]+|\d+/g;
23108
+ const tokenizeIdentifierWords = (identifierName) => {
23109
+ const words = identifierName.match(IDENTIFIER_WORD_PATTERN);
23110
+ if (!words) return [];
23111
+ return words.map((word) => word.toLowerCase());
22950
23112
  };
22951
23113
  //#endregion
23114
+ //#region src/plugin/utils/get-identifier-trailing-word.ts
23115
+ const getIdentifierTrailingWord = (identifierName) => tokenizeIdentifierWords(identifierName).at(-1) ?? identifierName.toLowerCase();
23116
+ //#endregion
22952
23117
  //#region src/plugin/constants/tanstack.ts
22953
23118
  const TANSTACK_ROUTE_FILE_PATTERN = /\/routes\//;
22954
23119
  const TANSTACK_ROOT_ROUTE_FILE_PATTERN = /__root\.(tsx?|jsx?)$/;
@@ -23476,7 +23641,7 @@ const getParentComponent = (node) => {
23476
23641
  };
23477
23642
  //#endregion
23478
23643
  //#region src/plugin/rules/react-builtins/no-set-state.ts
23479
- const MESSAGE$12 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
23644
+ const MESSAGE$13 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
23480
23645
  const noSetState = defineRule({
23481
23646
  id: "no-set-state",
23482
23647
  title: "Local class state forbidden",
@@ -23491,7 +23656,7 @@ const noSetState = defineRule({
23491
23656
  if (!getParentComponent(node)) return;
23492
23657
  context.report({
23493
23658
  node: node.callee,
23494
- message: MESSAGE$12
23659
+ message: MESSAGE$13
23495
23660
  });
23496
23661
  } })
23497
23662
  });
@@ -23653,7 +23818,7 @@ const isAbstractRole = (openingElement, settings) => {
23653
23818
  };
23654
23819
  //#endregion
23655
23820
  //#region src/plugin/rules/a11y/no-static-element-interactions.ts
23656
- 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.";
23821
+ 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.";
23657
23822
  const DEFAULT_HANDLERS = [
23658
23823
  "onClick",
23659
23824
  "onMouseDown",
@@ -23713,7 +23878,7 @@ const noStaticElementInteractions = defineRule({
23713
23878
  if (!roleAttribute || !roleAttribute.value) {
23714
23879
  context.report({
23715
23880
  node: node.name,
23716
- message: MESSAGE$11
23881
+ message: MESSAGE$12
23717
23882
  });
23718
23883
  return;
23719
23884
  }
@@ -23723,19 +23888,66 @@ const noStaticElementInteractions = defineRule({
23723
23888
  if (firstRole && (isInteractiveRole(firstRole) || isNonInteractiveRole(firstRole))) return;
23724
23889
  context.report({
23725
23890
  node: node.name,
23726
- message: MESSAGE$11
23891
+ message: MESSAGE$12
23727
23892
  });
23728
23893
  return;
23729
23894
  }
23730
23895
  if (isNodeOfType(attributeValue, "JSXExpressionContainer") && settings.allowExpressionValues) return;
23731
23896
  context.report({
23732
23897
  node: node.name,
23733
- message: MESSAGE$11
23898
+ message: MESSAGE$12
23734
23899
  });
23735
23900
  } };
23736
23901
  }
23737
23902
  });
23738
23903
  //#endregion
23904
+ //#region src/plugin/rules/react-builtins/no-string-false-on-boolean-attribute.ts
23905
+ const BOOLEAN_ATTRIBUTES = new Set([
23906
+ "disabled",
23907
+ "checked",
23908
+ "readonly",
23909
+ "required",
23910
+ "selected",
23911
+ "multiple",
23912
+ "autofocus",
23913
+ "autoplay",
23914
+ "controls",
23915
+ "loop",
23916
+ "muted",
23917
+ "open",
23918
+ "reversed",
23919
+ "default",
23920
+ "novalidate",
23921
+ "formnovalidate",
23922
+ "playsinline",
23923
+ "itemscope",
23924
+ "allowfullscreen"
23925
+ ]);
23926
+ const noStringFalseOnBooleanAttribute = defineRule({
23927
+ id: "no-string-false-on-boolean-attribute",
23928
+ title: "String true/false on a boolean attribute",
23929
+ severity: "warn",
23930
+ 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.",
23931
+ create: (context) => ({ JSXOpeningElement(node) {
23932
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
23933
+ const firstCharacter = node.name.name.charCodeAt(0);
23934
+ if (firstCharacter < 97 || firstCharacter > 122) return;
23935
+ for (const attribute of node.attributes) {
23936
+ if (!isNodeOfType(attribute, "JSXAttribute")) continue;
23937
+ if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
23938
+ if (!BOOLEAN_ATTRIBUTES.has(attribute.name.name.toLowerCase())) continue;
23939
+ const value = getJsxPropStringValue(attribute);
23940
+ if (value !== "false" && value !== "true") continue;
23941
+ const attributeName = attribute.name.name;
23942
+ 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}\``;
23943
+ context.report({
23944
+ node: attribute,
23945
+ message: `\`${attributeName}="${value}"\` passes the string "${value}", ${guidance}.`
23946
+ });
23947
+ }
23948
+ } })
23949
+ });
23950
+ //#endregion
23739
23951
  //#region src/plugin/rules/react-builtins/no-string-refs.ts
23740
23952
  const STRING_IN_REF_MESSAGE = "Your component can't reach this node because string refs don't work in modern React.";
23741
23953
  const THIS_REFS_MESSAGE = "Your component can't reach its nodes because `this.refs` is empty in modern React.";
@@ -23786,6 +23998,27 @@ const noStringRefs = defineRule({
23786
23998
  }
23787
23999
  });
23788
24000
  //#endregion
24001
+ //#region src/plugin/rules/js-performance/no-sync-xhr.ts
24002
+ 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)`).";
24003
+ const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
24004
+ const noSyncXhr = defineRule({
24005
+ id: "no-sync-xhr",
24006
+ title: "Synchronous XMLHttpRequest",
24007
+ severity: "warn",
24008
+ recommendation: "Never open an XMLHttpRequest synchronously (`async` = `false`). It blocks the main thread. Use `fetch()` or pass `true` and handle the response asynchronously.",
24009
+ create: (context) => ({ CallExpression(node) {
24010
+ const callee = node.callee;
24011
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
24012
+ if (!isNodeOfType(callee.property, "Identifier") || callee.property.name !== "open") return;
24013
+ const asyncArgument = node.arguments?.[2];
24014
+ if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
24015
+ context.report({
24016
+ node,
24017
+ message: MESSAGE$11
24018
+ });
24019
+ } })
24020
+ });
24021
+ //#endregion
23789
24022
  //#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
23790
24023
  const MESSAGE$10 = "This value is `undefined` because function components have no `this`.";
23791
24024
  const isInsideClassMethod = (node, customClassFactoryNames) => {
@@ -25195,15 +25428,8 @@ const expressionContainsJsxOrCreateElement = (root) => {
25195
25428
  visit(root);
25196
25429
  return found;
25197
25430
  };
25198
- const classExtendsReactComponent$1 = (classNode) => {
25199
- const superClass = classNode.superClass;
25200
- if (!superClass) return false;
25201
- if (isNodeOfType(superClass, "Identifier") && (superClass.name === "Component" || superClass.name === "PureComponent")) return true;
25202
- if (isNodeOfType(superClass, "MemberExpression") && isNodeOfType(superClass.object, "Identifier") && superClass.object.name === "React" && isNodeOfType(superClass.property, "Identifier") && (superClass.property.name === "Component" || superClass.property.name === "PureComponent")) return true;
25203
- return false;
25204
- };
25205
25431
  const isReactClassComponent = (classNode) => {
25206
- if (classExtendsReactComponent$1(classNode)) return true;
25432
+ if (isEs6Component(classNode)) return true;
25207
25433
  return expressionContainsJsxOrCreateElement(classNode);
25208
25434
  };
25209
25435
  const findEnclosingComponent = (node) => {
@@ -25363,7 +25589,7 @@ const noUnstableNestedComponents = defineRule({
25363
25589
  create: (context) => {
25364
25590
  const settings = resolveSettings$8(context.settings);
25365
25591
  const renderPropRegex = compileGlob(settings.propNamePattern);
25366
- const reportCandidate = (candidateNode, reportNode, candidateName) => {
25592
+ const reportCandidate = (candidateNode, reportNode) => {
25367
25593
  if (isFirstArgumentOfHocCall(candidateNode)) return;
25368
25594
  if (isReturnOfMapCallback(candidateNode)) return;
25369
25595
  const propInfo = isComponentDeclaredInProp(candidateNode);
@@ -25384,7 +25610,7 @@ const noUnstableNestedComponents = defineRule({
25384
25610
  const inferredName = inferFunctionLikeName(node);
25385
25611
  const propInfo = isComponentDeclaredInProp(node);
25386
25612
  if (!(inferredName !== null && isReactComponentName(inferredName) || propInfo !== null || isObjectCallbackCandidate(node))) return;
25387
- reportCandidate(node, node, inferredName);
25613
+ reportCandidate(node, node);
25388
25614
  };
25389
25615
  return {
25390
25616
  FunctionDeclaration: checkFunctionLike,
@@ -25394,18 +25620,18 @@ const noUnstableNestedComponents = defineRule({
25394
25620
  if (!node.id) return;
25395
25621
  if (!isReactComponentName(node.id.name)) return;
25396
25622
  if (!isReactClassComponent(node)) return;
25397
- reportCandidate(node, node, node.id.name);
25623
+ reportCandidate(node, node);
25398
25624
  },
25399
25625
  ClassExpression(node) {
25400
25626
  const inferredName = node.id?.name ?? inferFunctionLikeName(node);
25401
25627
  if (!inferredName || !isReactComponentName(inferredName)) return;
25402
25628
  if (!isReactClassComponent(node)) return;
25403
- reportCandidate(node, node, inferredName);
25629
+ reportCandidate(node, node);
25404
25630
  },
25405
25631
  CallExpression(node) {
25406
25632
  if (!isHocCallee$1(node)) return;
25407
25633
  if (!hocCallContainsComponent(node)) return;
25408
- reportCandidate(node, node, null);
25634
+ reportCandidate(node, node);
25409
25635
  }
25410
25636
  };
25411
25637
  }
@@ -25845,13 +26071,6 @@ const skipTsExpression = (expression) => {
25845
26071
  if (expression.type === "TSAsExpression" || expression.type === "TSSatisfiesExpression" || expression.type === "TSNonNullExpression") return skipTsExpression(expression.expression);
25846
26072
  return expression;
25847
26073
  };
25848
- const classExtendsReactComponent = (classNode) => {
25849
- const superClass = classNode.superClass;
25850
- if (!superClass) return false;
25851
- if (isNodeOfType(superClass, "Identifier") && (superClass.name === "Component" || superClass.name === "PureComponent")) return true;
25852
- if (isNodeOfType(superClass, "MemberExpression") && isNodeOfType(superClass.object, "Identifier") && superClass.object.name === "React" && isNodeOfType(superClass.property, "Identifier") && (superClass.property.name === "Component" || superClass.property.name === "PureComponent")) return true;
25853
- return false;
25854
- };
25855
26074
  const isReactCreateContext = (initializer) => {
25856
26075
  if (!initializer) return false;
25857
26076
  const expression = skipTsExpression(initializer);
@@ -26042,7 +26261,7 @@ const onlyExportComponents = defineRule({
26042
26261
  if (stripped.id) {
26043
26262
  const idNode = stripped.id;
26044
26263
  isExportedNodeIds.add(stripped);
26045
- if (isReactComponentName(idNode.name) && classExtendsReactComponent(stripped)) hasReactExport = true;
26264
+ if (isReactComponentName(idNode.name) && isEs6Component(stripped)) hasReactExport = true;
26046
26265
  else exports.push({
26047
26266
  kind: "non-component",
26048
26267
  reportNode: idNode
@@ -26102,7 +26321,7 @@ const onlyExportComponents = defineRule({
26102
26321
  exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state));
26103
26322
  } else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
26104
26323
  isExportedNodeIds.add(declaration);
26105
- if (isReactComponentName(declaration.id.name) && classExtendsReactComponent(declaration)) exports.push({ kind: "react-component" });
26324
+ if (isReactComponentName(declaration.id.name) && isEs6Component(declaration)) exports.push({ kind: "react-component" });
26106
26325
  else exports.push({
26107
26326
  kind: "non-component",
26108
26327
  reportNode: declaration.id
@@ -34895,6 +35114,47 @@ const serverAfterNonblocking = defineRule({
34895
35114
  }
34896
35115
  });
34897
35116
  //#endregion
35117
+ //#region src/plugin/utils/is-auth-guard-name.ts
35118
+ const SIGNED_IN_HEAD_TOKENS = new Set([
35119
+ "signed",
35120
+ "logged",
35121
+ "sign"
35122
+ ]);
35123
+ const mergeSignedInTokens = (tokens) => {
35124
+ const mergedTokens = [];
35125
+ for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) {
35126
+ const currentToken = tokens[tokenIndex];
35127
+ if (SIGNED_IN_HEAD_TOKENS.has(currentToken) && tokens[tokenIndex + 1] === "in") {
35128
+ mergedTokens.push(`${currentToken}in`);
35129
+ tokenIndex += 1;
35130
+ continue;
35131
+ }
35132
+ mergedTokens.push(currentToken);
35133
+ }
35134
+ return mergedTokens;
35135
+ };
35136
+ const isAuthGuardName = (calleeName) => {
35137
+ const tokens = mergeSignedInTokens(tokenizeIdentifierWords(calleeName));
35138
+ if (tokens.length === 0) return false;
35139
+ let hasAssertiveVerb = false;
35140
+ let hasGetterVerb = false;
35141
+ let hasQualifier = false;
35142
+ let hasStrongNoun = false;
35143
+ let hasWeakNoun = false;
35144
+ for (const token of tokens) {
35145
+ if (AUTH_STRONG_TOKEN_PATTERN.test(token) || AUTH_STANDALONE_NOUN_TOKENS.has(token)) return true;
35146
+ if (AUTH_ASSERTIVE_VERB_TOKENS.has(token)) hasAssertiveVerb = true;
35147
+ if (AUTH_GETTER_VERB_TOKENS.has(token)) hasGetterVerb = true;
35148
+ if (AUTH_QUALIFIER_TOKENS.has(token)) hasQualifier = true;
35149
+ if (AUTH_STRONG_NOUN_TOKENS.has(token)) hasStrongNoun = true;
35150
+ if (AUTH_WEAK_NOUN_TOKENS.has(token)) hasWeakNoun = true;
35151
+ }
35152
+ if (hasAssertiveVerb && (hasStrongNoun || hasWeakNoun)) return true;
35153
+ if (hasGetterVerb && hasStrongNoun) return true;
35154
+ if (hasQualifier && hasWeakNoun) return true;
35155
+ return false;
35156
+ };
35157
+ //#endregion
34898
35158
  //#region src/plugin/rules/server/server-auth-actions.ts
34899
35159
  const isAsyncFunctionLikeNode = (node) => {
34900
35160
  if (!node) return false;
@@ -34937,9 +35197,13 @@ const isMemberCallAuthRelated = (receiverNode, methodName, genericMethodNames) =
34937
35197
  const getAuthCallName = (callExpression, allowedFunctionNames, genericMethodNames) => {
34938
35198
  const calleeNode = unwrapTypeWrappedCallee(callExpression.callee);
34939
35199
  if (!calleeNode) return null;
34940
- if (isNodeOfType(calleeNode, "Identifier")) return allowedFunctionNames.has(calleeNode.name) ? calleeNode.name : null;
35200
+ if (isNodeOfType(calleeNode, "Identifier")) {
35201
+ const calleeName = calleeNode.name;
35202
+ return allowedFunctionNames.has(calleeName) || isAuthGuardName(calleeName) ? calleeName : null;
35203
+ }
34941
35204
  if (isNodeOfType(calleeNode, "MemberExpression") && isNodeOfType(calleeNode.property, "Identifier")) {
34942
35205
  const methodName = calleeNode.property.name;
35206
+ if (isAuthGuardName(methodName)) return methodName;
34943
35207
  if (!allowedFunctionNames.has(methodName)) return null;
34944
35208
  if (!isMemberCallAuthRelated(calleeNode.object, methodName, genericMethodNames)) return null;
34945
35209
  return methodName;
@@ -35161,6 +35425,7 @@ const serverFetchWithoutRevalidate = defineRule({
35161
35425
  CallExpression(node) {
35162
35426
  if (!isServerSideFile) return;
35163
35427
  if (!isFetchCall(node)) return;
35428
+ if (isMutatingFetchCall(node)) return;
35164
35429
  const optionsArg = node.arguments?.[1];
35165
35430
  if (optionsArg && objectExpressionHasNextRevalidate(optionsArg)) return;
35166
35431
  const urlArg = node.arguments?.[0];
@@ -35316,13 +35581,7 @@ const serverNoMutableModuleState = defineRule({
35316
35581
  const collectDeclaredNames = (declaration) => {
35317
35582
  const names = /* @__PURE__ */ new Set();
35318
35583
  if (!isNodeOfType(declaration, "VariableDeclaration")) return names;
35319
- for (const declarator of declaration.declarations ?? []) if (isNodeOfType(declarator.id, "Identifier")) names.add(declarator.id.name);
35320
- else if (isNodeOfType(declarator.id, "ObjectPattern")) {
35321
- for (const property of declarator.id.properties ?? []) if (isNodeOfType(property, "Property") && isNodeOfType(property.value, "Identifier")) names.add(property.value.name);
35322
- else if (isNodeOfType(property, "RestElement") && isNodeOfType(property.argument, "Identifier")) names.add(property.argument.name);
35323
- } else if (isNodeOfType(declarator.id, "ArrayPattern")) {
35324
- for (const element of declarator.id.elements ?? []) if (isNodeOfType(element, "Identifier")) names.add(element.name);
35325
- }
35584
+ for (const declarator of declaration.declarations ?? []) collectPatternNames(declarator.id, names);
35326
35585
  return names;
35327
35586
  };
35328
35587
  const declarationStartsWithAwait = (declaration) => {
@@ -35332,11 +35591,15 @@ const declarationStartsWithAwait = (declaration) => {
35332
35591
  };
35333
35592
  const declarationReadsAnyName = (declaration, names) => {
35334
35593
  if (names.size === 0) return false;
35594
+ if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
35335
35595
  let didRead = false;
35336
- walkAst(declaration, (child) => {
35337
- if (didRead) return;
35338
- if (isNodeOfType(child, "Identifier") && names.has(child.name)) didRead = true;
35339
- });
35596
+ for (const declarator of declaration.declarations ?? []) {
35597
+ if (!declarator.init) continue;
35598
+ walkAst(declarator.init, (child) => {
35599
+ if (didRead) return;
35600
+ if (isNodeOfType(child, "Identifier") && names.has(child.name)) didRead = true;
35601
+ });
35602
+ }
35340
35603
  return didRead;
35341
35604
  };
35342
35605
  const serverSequentialIndependentAwait = defineRule({
@@ -36596,7 +36859,7 @@ const urlPrefilledPrivilegedAction = defineRule({
36596
36859
  recommendation: "Require server-side validation and explicit confirmation for URL-sourced invite, role, permission, redirect, or sharing parameters.",
36597
36860
  scan: scanByPattern({
36598
36861
  shouldScan: (file) => isClientSourcePath(file.relativePath),
36599
- 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,
36862
+ 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,
36600
36863
  message: "Client code reads sensitive action state from the URL, which can pre-fill invites, roles, redirects, or sharing flows with attacker values."
36601
36864
  })
36602
36865
  });
@@ -39052,6 +39315,17 @@ const reactDoctorRules = [
39052
39315
  requires: [...new Set(["react", ...noDocumentStartViewTransition.requires ?? []])]
39053
39316
  }
39054
39317
  },
39318
+ {
39319
+ key: "react-doctor/no-document-write",
39320
+ id: "no-document-write",
39321
+ source: "react-doctor",
39322
+ originallyExternal: false,
39323
+ rule: {
39324
+ ...noDocumentWrite,
39325
+ framework: "global",
39326
+ category: "Performance"
39327
+ }
39328
+ },
39055
39329
  {
39056
39330
  key: "react-doctor/no-dynamic-import-path",
39057
39331
  id: "no-dynamic-import-path",
@@ -39861,6 +40135,18 @@ const reactDoctorRules = [
39861
40135
  requires: [...new Set(["react", ...noStaticElementInteractions.requires ?? []])]
39862
40136
  }
39863
40137
  },
40138
+ {
40139
+ key: "react-doctor/no-string-false-on-boolean-attribute",
40140
+ id: "no-string-false-on-boolean-attribute",
40141
+ source: "react-doctor",
40142
+ originallyExternal: false,
40143
+ rule: {
40144
+ ...noStringFalseOnBooleanAttribute,
40145
+ framework: "global",
40146
+ category: "Bugs",
40147
+ requires: [...new Set(["react", ...noStringFalseOnBooleanAttribute.requires ?? []])]
40148
+ }
40149
+ },
39864
40150
  {
39865
40151
  key: "react-doctor/no-string-refs",
39866
40152
  id: "no-string-refs",
@@ -39873,6 +40159,17 @@ const reactDoctorRules = [
39873
40159
  requires: [...new Set(["react", ...noStringRefs.requires ?? []])]
39874
40160
  }
39875
40161
  },
40162
+ {
40163
+ key: "react-doctor/no-sync-xhr",
40164
+ id: "no-sync-xhr",
40165
+ source: "react-doctor",
40166
+ originallyExternal: false,
40167
+ rule: {
40168
+ ...noSyncXhr,
40169
+ framework: "global",
40170
+ category: "Performance"
40171
+ }
40172
+ },
39876
40173
  {
39877
40174
  key: "react-doctor/no-this-in-sfc",
39878
40175
  id: "no-this-in-sfc",
@@ -41981,32 +42278,6 @@ const computeUnconditionalSet = (cfg) => {
41981
42278
  }
41982
42279
  return unconditional;
41983
42280
  };
41984
- const computeDominatesExit = (cfg) => {
41985
- const reachableToExit = /* @__PURE__ */ new Set();
41986
- const queue = [cfg.exit];
41987
- while (queue.length > 0) {
41988
- const block = queue.shift();
41989
- if (reachableToExit.has(block)) continue;
41990
- reachableToExit.add(block);
41991
- for (const edge of block.predecessors) queue.push(edge.from);
41992
- }
41993
- const dominatesExit = /* @__PURE__ */ new Set();
41994
- const visit = (block) => {
41995
- if (block === cfg.exit) return true;
41996
- if (dominatesExit.has(block)) return true;
41997
- if (block.successors.length === 0) return false;
41998
- dominatesExit.add(block);
41999
- let allReach = true;
42000
- for (const edge of block.successors) if (!visit(edge.to)) {
42001
- allReach = false;
42002
- break;
42003
- }
42004
- if (!allReach) dominatesExit.delete(block);
42005
- return allReach;
42006
- };
42007
- for (const block of cfg.blocks) visit(block);
42008
- return dominatesExit;
42009
- };
42010
42281
  const analyzeControlFlow = (program) => {
42011
42282
  nextBlockId = 0;
42012
42283
  const functionCfgs = /* @__PURE__ */ new Map();
@@ -42014,8 +42285,7 @@ const analyzeControlFlow = (program) => {
42014
42285
  const cfg = buildFunctionCfg(functionNode, body);
42015
42286
  functionCfgs.set(functionNode, {
42016
42287
  cfg,
42017
- unconditionalSet: computeUnconditionalSet(cfg),
42018
- dominatesExitSet: computeDominatesExit(cfg)
42288
+ unconditionalSet: computeUnconditionalSet(cfg)
42019
42289
  });
42020
42290
  };
42021
42291
  if (isNodeOfType(program, "Program")) buildFor(program, {
@@ -42058,20 +42328,10 @@ const analyzeControlFlow = (program) => {
42058
42328
  if (!block) return true;
42059
42329
  return entry.unconditionalSet.has(block);
42060
42330
  };
42061
- const dominatesExit = (node) => {
42062
- const owner = enclosingFunction(node);
42063
- if (!owner) return true;
42064
- const entry = functionCfgs.get(owner);
42065
- if (!entry) return true;
42066
- const block = entry.cfg.blockOf(node);
42067
- if (!block) return true;
42068
- return entry.dominatesExitSet.has(block);
42069
- };
42070
42331
  return {
42071
42332
  cfgFor,
42072
42333
  enclosingFunction,
42073
- isUnconditionalFromEntry,
42074
- dominatesExit
42334
+ isUnconditionalFromEntry
42075
42335
  };
42076
42336
  };
42077
42337
  //#endregion
@@ -42096,8 +42356,7 @@ const buildFallbackScopes = () => ({
42096
42356
  const FALLBACK_CFG = {
42097
42357
  cfgFor: () => null,
42098
42358
  enclosingFunction: () => null,
42099
- isUnconditionalFromEntry: () => false,
42100
- dominatesExit: () => false
42359
+ isUnconditionalFromEntry: () => false
42101
42360
  };
42102
42361
  const wrapWithSemanticContext = (rule) => ({
42103
42362
  ...rule,