oxlint-plugin-react-doctor 0.5.6-dev.8908f98 → 0.5.6-dev.937a7ca

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +126 -0
  2. package/dist/index.js +451 -150
  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
  });
@@ -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
  })
@@ -4624,7 +4748,7 @@ const isPureEventBlockerHandler = (attribute) => {
4624
4748
  //#endregion
4625
4749
  //#region src/plugin/rules/a11y/click-events-have-key-events.ts
4626
4750
  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`.";
4751
+ const MESSAGE$56 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
4628
4752
  const KEY_HANDLERS = [
4629
4753
  "onKeyUp",
4630
4754
  "onKeyDown",
@@ -4656,7 +4780,7 @@ const clickEventsHaveKeyEvents = defineRule({
4656
4780
  if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
4657
4781
  context.report({
4658
4782
  node: node.name,
4659
- message: MESSAGE$54
4783
+ message: MESSAGE$56
4660
4784
  });
4661
4785
  } };
4662
4786
  }
@@ -4771,7 +4895,7 @@ const isReactComponentName = (name) => {
4771
4895
  };
4772
4896
  //#endregion
4773
4897
  //#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`.";
4898
+ const MESSAGE$55 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
4775
4899
  const DEFAULT_IGNORE_ELEMENTS = ["link", "canvas"];
4776
4900
  const DEFAULT_LABELLING_PROPS = [
4777
4901
  "alt",
@@ -4932,7 +5056,7 @@ const controlHasAssociatedLabel = defineRule({
4932
5056
  for (const child of node.children) if (checkChildForLabel(child, 1, checkContext)) return;
4933
5057
  context.report({
4934
5058
  node: opening,
4935
- message: MESSAGE$53
5059
+ message: MESSAGE$55
4936
5060
  });
4937
5061
  } };
4938
5062
  }
@@ -5362,7 +5486,7 @@ const noVagueButtonLabel = defineRule({
5362
5486
  const hasJsxSpreadAttribute$1 = (attributes) => attributes.some((attribute) => isNodeOfType(attribute, "JSXSpreadAttribute"));
5363
5487
  //#endregion
5364
5488
  //#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.";
5489
+ const MESSAGE$54 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
5366
5490
  const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
5367
5491
  const NAME_PROVIDING_ATTRIBUTES = [
5368
5492
  "aria-label",
@@ -5385,7 +5509,7 @@ const dialogHasAccessibleName = defineRule({
5385
5509
  if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
5386
5510
  context.report({
5387
5511
  node: node.name,
5388
- message: MESSAGE$52
5512
+ message: MESSAGE$54
5389
5513
  });
5390
5514
  } })
5391
5515
  });
@@ -5424,7 +5548,7 @@ const isEs6Component = (node) => {
5424
5548
  };
5425
5549
  //#endregion
5426
5550
  //#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`.";
5551
+ const MESSAGE$53 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
5428
5552
  const DEFAULT_ADDITIONAL_HOCS = [
5429
5553
  "observer",
5430
5554
  "lazy",
@@ -5627,7 +5751,7 @@ const displayName = defineRule({
5627
5751
  const reportAt = (node) => {
5628
5752
  context.report({
5629
5753
  node,
5630
- message: MESSAGE$51
5754
+ message: MESSAGE$53
5631
5755
  });
5632
5756
  };
5633
5757
  return {
@@ -7775,7 +7899,7 @@ const forbidElements = defineRule({
7775
7899
  });
7776
7900
  //#endregion
7777
7901
  //#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`.";
7902
+ const MESSAGE$52 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
7779
7903
  const forwardRefUsesRef = defineRule({
7780
7904
  id: "forward-ref-uses-ref",
7781
7905
  title: "forwardRef without ref parameter",
@@ -7795,7 +7919,7 @@ const forwardRefUsesRef = defineRule({
7795
7919
  if (isNodeOfType(onlyParam, "RestElement")) return;
7796
7920
  context.report({
7797
7921
  node: inner,
7798
- message: MESSAGE$50
7922
+ message: MESSAGE$52
7799
7923
  });
7800
7924
  } })
7801
7925
  });
@@ -7832,7 +7956,7 @@ const gitProviderUrlInjectionRisk = defineRule({
7832
7956
  });
7833
7957
  //#endregion
7834
7958
  //#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`.";
7959
+ const MESSAGE$51 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
7836
7960
  const DEFAULT_HEADING_TAGS = [
7837
7961
  "h1",
7838
7962
  "h2",
@@ -7865,7 +7989,7 @@ const headingHasContent = defineRule({
7865
7989
  if (isHiddenFromScreenReader(node, context.settings)) return;
7866
7990
  context.report({
7867
7991
  node,
7868
- message: MESSAGE$49
7992
+ message: MESSAGE$51
7869
7993
  });
7870
7994
  } };
7871
7995
  }
@@ -8003,7 +8127,7 @@ const hooksNoNanInDeps = defineRule({
8003
8127
  });
8004
8128
  //#endregion
8005
8129
  //#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`.";
8130
+ const MESSAGE$50 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
8007
8131
  const resolveSettings$38 = (settings) => {
8008
8132
  const reactDoctor = settings?.["react-doctor"];
8009
8133
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
@@ -8051,7 +8175,7 @@ const htmlHasLang = defineRule({
8051
8175
  if (!lang) {
8052
8176
  context.report({
8053
8177
  node: node.name,
8054
- message: MESSAGE$48
8178
+ message: MESSAGE$50
8055
8179
  });
8056
8180
  return;
8057
8181
  }
@@ -8059,13 +8183,13 @@ const htmlHasLang = defineRule({
8059
8183
  if (verdict === "missing" || verdict === "empty") {
8060
8184
  context.report({
8061
8185
  node: lang,
8062
- message: MESSAGE$48
8186
+ message: MESSAGE$50
8063
8187
  });
8064
8188
  return;
8065
8189
  }
8066
8190
  if (hasSpread && !lang) context.report({
8067
8191
  node: node.name,
8068
- message: MESSAGE$48
8192
+ message: MESSAGE$50
8069
8193
  });
8070
8194
  } };
8071
8195
  }
@@ -8279,7 +8403,7 @@ const htmlNoNestedInteractive = defineRule({
8279
8403
  });
8280
8404
  //#endregion
8281
8405
  //#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.";
8406
+ const MESSAGE$49 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
8283
8407
  const evaluateTitleValue = (value) => {
8284
8408
  if (!value) return "missing";
8285
8409
  if (isNodeOfType(value, "Literal")) {
@@ -8319,14 +8443,14 @@ const iframeHasTitle = defineRule({
8319
8443
  if (!titleAttr) {
8320
8444
  if (hasSpread || tag === "iframe") context.report({
8321
8445
  node: node.name,
8322
- message: MESSAGE$47
8446
+ message: MESSAGE$49
8323
8447
  });
8324
8448
  return;
8325
8449
  }
8326
8450
  const verdict = evaluateTitleValue(titleAttr.value);
8327
8451
  if (verdict === "missing" || verdict === "empty") context.report({
8328
8452
  node: titleAttr,
8329
- message: MESSAGE$47
8453
+ message: MESSAGE$49
8330
8454
  });
8331
8455
  } })
8332
8456
  });
@@ -8430,7 +8554,7 @@ const iframeMissingSandbox = defineRule({
8430
8554
  });
8431
8555
  //#endregion
8432
8556
  //#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.";
8557
+ const MESSAGE$48 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
8434
8558
  const DEFAULT_COMPONENTS = ["img"];
8435
8559
  const DEFAULT_REDUNDANT_WORDS = [
8436
8560
  "image",
@@ -8495,7 +8619,7 @@ const imgRedundantAlt = defineRule({
8495
8619
  if (!altAttribute) return;
8496
8620
  if (altValueRedundant(altAttribute, settings.words)) context.report({
8497
8621
  node: altAttribute,
8498
- message: MESSAGE$46
8622
+ message: MESSAGE$48
8499
8623
  });
8500
8624
  } };
8501
8625
  }
@@ -10852,7 +10976,7 @@ const jsxMaxDepth = defineRule({
10852
10976
  });
10853
10977
  //#endregion
10854
10978
  //#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.";
10979
+ const MESSAGE$47 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
10856
10980
  const LITERAL_TEXT_TAGS = new Set([
10857
10981
  "code",
10858
10982
  "pre",
@@ -10888,7 +11012,7 @@ const jsxNoCommentTextnodes = defineRule({
10888
11012
  if (isInsideLiteralTextTag(node)) return;
10889
11013
  context.report({
10890
11014
  node,
10891
- message: MESSAGE$45
11015
+ message: MESSAGE$47
10892
11016
  });
10893
11017
  } })
10894
11018
  });
@@ -10919,7 +11043,7 @@ const isInsideFunctionScope = (node) => {
10919
11043
  };
10920
11044
  //#endregion
10921
11045
  //#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.";
11046
+ const MESSAGE$46 = "Every reader of this context redraws on each render because you build its `value` inline.";
10923
11047
  const CONTEXT_MODULES$1 = [
10924
11048
  "react",
10925
11049
  "use-context-selector",
@@ -11017,7 +11141,7 @@ const jsxNoConstructedContextValues = defineRule({
11017
11141
  if (!isConstructedValue(innerExpression)) continue;
11018
11142
  context.report({
11019
11143
  node: attribute,
11020
- message: MESSAGE$44
11144
+ message: MESSAGE$46
11021
11145
  });
11022
11146
  }
11023
11147
  }
@@ -11103,7 +11227,7 @@ const isJsxAttributeOnIntrinsicHtmlElement = (attribute) => {
11103
11227
  };
11104
11228
  //#endregion
11105
11229
  //#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.";
11230
+ const MESSAGE$45 = "This child redraws every render because the prop gets brand new JSX each time.";
11107
11231
  const KNOWN_SLOT_PROP_NAMES = new Set([
11108
11232
  "icon",
11109
11233
  "Icon",
@@ -11372,7 +11496,7 @@ const jsxNoJsxAsProp = defineRule({
11372
11496
  if (!isJsxProducingExpression(expressionNode) && !followsRenderLocalJsxBinding(expressionNode, node)) return;
11373
11497
  context.report({
11374
11498
  node,
11375
- message: MESSAGE$43
11499
+ message: MESSAGE$45
11376
11500
  });
11377
11501
  }
11378
11502
  };
@@ -11660,7 +11784,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
11660
11784
  ];
11661
11785
  //#endregion
11662
11786
  //#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.";
11787
+ const MESSAGE$44 = "This child redraws every render because the prop gets a brand new array each time.";
11664
11788
  const isDataArrayPropName = (propName) => {
11665
11789
  if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
11666
11790
  for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -11744,7 +11868,7 @@ const jsxNoNewArrayAsProp = defineRule({
11744
11868
  if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
11745
11869
  context.report({
11746
11870
  node,
11747
- message: MESSAGE$42
11871
+ message: MESSAGE$44
11748
11872
  });
11749
11873
  }
11750
11874
  };
@@ -12002,7 +12126,7 @@ const SAFE_RECEIVER_NAMES = new Set([
12002
12126
  ]);
12003
12127
  //#endregion
12004
12128
  //#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.";
12129
+ const MESSAGE$43 = "This child redraws every render because the prop gets a brand new function each time.";
12006
12130
  const isAccessorPredicateName = (propName) => {
12007
12131
  for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
12008
12132
  if (propName.length <= prefix.length) continue;
@@ -12208,7 +12332,7 @@ const jsxNoNewFunctionAsProp = defineRule({
12208
12332
  if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
12209
12333
  context.report({
12210
12334
  node,
12211
- message: MESSAGE$41
12335
+ message: MESSAGE$43
12212
12336
  });
12213
12337
  }
12214
12338
  };
@@ -12428,7 +12552,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
12428
12552
  ];
12429
12553
  //#endregion
12430
12554
  //#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.";
12555
+ const MESSAGE$42 = "This child redraws every render because the prop gets a brand new object each time.";
12432
12556
  const isConfigObjectPropName = (propName) => {
12433
12557
  if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
12434
12558
  for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -12516,7 +12640,7 @@ const jsxNoNewObjectAsProp = defineRule({
12516
12640
  if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
12517
12641
  context.report({
12518
12642
  node,
12519
- message: MESSAGE$40
12643
+ message: MESSAGE$42
12520
12644
  });
12521
12645
  }
12522
12646
  };
@@ -12524,7 +12648,7 @@ const jsxNoNewObjectAsProp = defineRule({
12524
12648
  });
12525
12649
  //#endregion
12526
12650
  //#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.";
12651
+ const MESSAGE$41 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
12528
12652
  const JAVASCRIPT_URL_PATTERN = /j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;
12529
12653
  const resolveSettings$28 = (settings) => {
12530
12654
  const reactDoctor = settings?.["react-doctor"];
@@ -12565,7 +12689,7 @@ const jsxNoScriptUrl = defineRule({
12565
12689
  if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
12566
12690
  if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
12567
12691
  node: attribute,
12568
- message: MESSAGE$39
12692
+ message: MESSAGE$41
12569
12693
  });
12570
12694
  }
12571
12695
  } };
@@ -12880,7 +13004,7 @@ const jsxPropsNoSpreadMulti = defineRule({
12880
13004
  });
12881
13005
  //#endregion
12882
13006
  //#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.";
13007
+ const MESSAGE$40 = "You can't tell what props reach this element when you spread them.";
12884
13008
  const resolveSettings$25 = (settings) => {
12885
13009
  const reactDoctor = settings?.["react-doctor"];
12886
13010
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
@@ -12921,7 +13045,7 @@ const jsxPropsNoSpreading = defineRule({
12921
13045
  }
12922
13046
  context.report({
12923
13047
  node: attribute,
12924
- message: MESSAGE$38
13048
+ message: MESSAGE$40
12925
13049
  });
12926
13050
  }
12927
13051
  } };
@@ -13149,7 +13273,7 @@ const labelHasAssociatedControl = defineRule({
13149
13273
  });
13150
13274
  //#endregion
13151
13275
  //#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`.";
13276
+ const MESSAGE$39 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
13153
13277
  const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
13154
13278
  "aa",
13155
13279
  "ab",
@@ -13361,7 +13485,7 @@ const lang = defineRule({
13361
13485
  if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
13362
13486
  context.report({
13363
13487
  node: langAttr,
13364
- message: MESSAGE$37
13488
+ message: MESSAGE$39
13365
13489
  });
13366
13490
  return;
13367
13491
  }
@@ -13370,7 +13494,7 @@ const lang = defineRule({
13370
13494
  if (value === null) return;
13371
13495
  if (!isValidLangTag(value)) context.report({
13372
13496
  node: langAttr,
13373
- message: MESSAGE$37
13497
+ message: MESSAGE$39
13374
13498
  });
13375
13499
  } })
13376
13500
  });
@@ -13396,6 +13520,7 @@ const mcpToolCapabilityRisk = defineRule({
13396
13520
  shouldScan: (file) => isProductionSourcePath(file.relativePath),
13397
13521
  pattern: /\bserver\.\s*tool\s*\(|\bregisterTool\s*\(|\bsetRequestHandler\s*\(\s*CallToolRequestSchema/,
13398
13522
  requireAll: [/\bfrom\s+["']@modelcontextprotocol\/sdk[^"']*["']|\bMcpServer\b|\bMcpAgent\b/, AGENT_TOOL_DANGEROUS_CAPABILITY_PATTERN],
13523
+ ignoreStringLiterals: true,
13399
13524
  message: "An MCP tool/resource/prompt handler appears to expose file, shell, network, or code-execution capability."
13400
13525
  })
13401
13526
  });
@@ -13414,7 +13539,7 @@ const mdxSsrExecutionRisk = defineRule({
13414
13539
  });
13415
13540
  //#endregion
13416
13541
  //#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>`.";
13542
+ const MESSAGE$38 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
13418
13543
  const DEFAULT_AUDIO = ["audio"];
13419
13544
  const DEFAULT_VIDEO = ["video"];
13420
13545
  const DEFAULT_TRACK = ["track"];
@@ -13455,7 +13580,7 @@ const mediaHasCaption = defineRule({
13455
13580
  if (!parent || !isNodeOfType(parent, "JSXElement")) {
13456
13581
  context.report({
13457
13582
  node: node.name,
13458
- message: MESSAGE$36
13583
+ message: MESSAGE$38
13459
13584
  });
13460
13585
  return;
13461
13586
  }
@@ -13472,7 +13597,7 @@ const mediaHasCaption = defineRule({
13472
13597
  return kindValue.value.toLowerCase() === "captions";
13473
13598
  })) context.report({
13474
13599
  node: node.name,
13475
- message: MESSAGE$36
13600
+ message: MESSAGE$38
13476
13601
  });
13477
13602
  } };
13478
13603
  }
@@ -15273,7 +15398,7 @@ const nextjsNoVercelOgImport = defineRule({
15273
15398
  });
15274
15399
  //#endregion
15275
15400
  //#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.";
15401
+ const MESSAGE$37 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
15277
15402
  const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
15278
15403
  const noAccessKey = defineRule({
15279
15404
  id: "no-access-key",
@@ -15290,7 +15415,7 @@ const noAccessKey = defineRule({
15290
15415
  if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
15291
15416
  context.report({
15292
15417
  node: accessKey,
15293
- message: MESSAGE$35
15418
+ message: MESSAGE$37
15294
15419
  });
15295
15420
  return;
15296
15421
  }
@@ -15300,7 +15425,7 @@ const noAccessKey = defineRule({
15300
15425
  if (isUndefinedIdentifier(expression)) return;
15301
15426
  context.report({
15302
15427
  node: accessKey,
15303
- message: MESSAGE$35
15428
+ message: MESSAGE$37
15304
15429
  });
15305
15430
  }
15306
15431
  } })
@@ -15783,7 +15908,7 @@ const noAdjustStateOnPropChange = defineRule({
15783
15908
  });
15784
15909
  //#endregion
15785
15910
  //#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.";
15911
+ const MESSAGE$36 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
15787
15912
  const noAriaHiddenOnFocusable = defineRule({
15788
15913
  id: "no-aria-hidden-on-focusable",
15789
15914
  title: "aria-hidden on focusable element",
@@ -15810,7 +15935,7 @@ const noAriaHiddenOnFocusable = defineRule({
15810
15935
  const isImplicitlyFocusable = isInteractiveElement(tag, node);
15811
15936
  if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
15812
15937
  node: ariaHidden,
15813
- message: MESSAGE$34
15938
+ message: MESSAGE$36
15814
15939
  });
15815
15940
  } })
15816
15941
  });
@@ -16178,7 +16303,7 @@ const noArrayIndexAsKey = defineRule({
16178
16303
  });
16179
16304
  //#endregion
16180
16305
  //#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.";
16306
+ const MESSAGE$35 = "Your users can see & submit the wrong data when this list reorders.";
16182
16307
  const SECOND_INDEX_METHODS = new Set([
16183
16308
  "every",
16184
16309
  "filter",
@@ -16382,7 +16507,7 @@ const noArrayIndexKey = defineRule({
16382
16507
  }
16383
16508
  context.report({
16384
16509
  node: keyAttribute,
16385
- message: MESSAGE$33
16510
+ message: MESSAGE$35
16386
16511
  });
16387
16512
  },
16388
16513
  CallExpression(node) {
@@ -16402,7 +16527,7 @@ const noArrayIndexKey = defineRule({
16402
16527
  if (propName !== "key") continue;
16403
16528
  if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
16404
16529
  node: property,
16405
- message: MESSAGE$33
16530
+ message: MESSAGE$35
16406
16531
  });
16407
16532
  }
16408
16533
  }
@@ -16410,7 +16535,7 @@ const noArrayIndexKey = defineRule({
16410
16535
  });
16411
16536
  //#endregion
16412
16537
  //#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.";
16538
+ const MESSAGE$34 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
16414
16539
  const noAsyncEffectCallback = defineRule({
16415
16540
  id: "no-async-effect-callback",
16416
16541
  title: "Async effect callback",
@@ -16424,13 +16549,13 @@ const noAsyncEffectCallback = defineRule({
16424
16549
  if (!callback.async) return;
16425
16550
  context.report({
16426
16551
  node: callback,
16427
- message: MESSAGE$32
16552
+ message: MESSAGE$34
16428
16553
  });
16429
16554
  } })
16430
16555
  });
16431
16556
  //#endregion
16432
16557
  //#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.";
16558
+ const MESSAGE$33 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
16434
16559
  const resolveSettings$21 = (settings) => {
16435
16560
  const reactDoctor = settings?.["react-doctor"];
16436
16561
  return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
@@ -16486,7 +16611,7 @@ const noAutofocus = defineRule({
16486
16611
  }
16487
16612
  context.report({
16488
16613
  node: autoFocusAttribute,
16489
- message: MESSAGE$31
16614
+ message: MESSAGE$33
16490
16615
  });
16491
16616
  } };
16492
16617
  }
@@ -16990,7 +17115,7 @@ const noChainStateUpdates = defineRule({
16990
17115
  });
16991
17116
  //#endregion
16992
17117
  //#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.";
17118
+ const MESSAGE$32 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
16994
17119
  const noChildrenProp = defineRule({
16995
17120
  id: "no-children-prop",
16996
17121
  title: "Children passed as a prop",
@@ -17002,7 +17127,7 @@ const noChildrenProp = defineRule({
17002
17127
  if (node.name.name !== "children") return;
17003
17128
  context.report({
17004
17129
  node: node.name,
17005
- message: MESSAGE$30
17130
+ message: MESSAGE$32
17006
17131
  });
17007
17132
  },
17008
17133
  CallExpression(node) {
@@ -17015,7 +17140,7 @@ const noChildrenProp = defineRule({
17015
17140
  const propertyKey = property.key;
17016
17141
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
17017
17142
  node: propertyKey,
17018
- message: MESSAGE$30
17143
+ message: MESSAGE$32
17019
17144
  });
17020
17145
  }
17021
17146
  }
@@ -17023,7 +17148,7 @@ const noChildrenProp = defineRule({
17023
17148
  });
17024
17149
  //#endregion
17025
17150
  //#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.";
17151
+ const MESSAGE$31 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
17027
17152
  const noCloneElement = defineRule({
17028
17153
  id: "no-clone-element",
17029
17154
  title: "cloneElement makes child props fragile",
@@ -17036,7 +17161,7 @@ const noCloneElement = defineRule({
17036
17161
  if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
17037
17162
  if (isImportedFromModule(node, "cloneElement", "react")) context.report({
17038
17163
  node: callee,
17039
- message: MESSAGE$29
17164
+ message: MESSAGE$31
17040
17165
  });
17041
17166
  return;
17042
17167
  }
@@ -17049,7 +17174,7 @@ const noCloneElement = defineRule({
17049
17174
  if (!isImportedFromModule(node, callee.object.name, "react")) return;
17050
17175
  context.report({
17051
17176
  node: callee,
17052
- message: MESSAGE$29
17177
+ message: MESSAGE$31
17053
17178
  });
17054
17179
  }
17055
17180
  } })
@@ -17098,7 +17223,7 @@ const enclosingComponentOrHookName = (node) => {
17098
17223
  };
17099
17224
  //#endregion
17100
17225
  //#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.";
17226
+ const MESSAGE$30 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
17102
17227
  const CONTEXT_MODULES = [
17103
17228
  "react",
17104
17229
  "use-context-selector",
@@ -17134,13 +17259,13 @@ const noCreateContextInRender = defineRule({
17134
17259
  if (!componentOrHookName) return;
17135
17260
  context.report({
17136
17261
  node,
17137
- message: `${MESSAGE$28} (called inside "${componentOrHookName}")`
17262
+ message: `${MESSAGE$30} (called inside "${componentOrHookName}")`
17138
17263
  });
17139
17264
  } })
17140
17265
  });
17141
17266
  //#endregion
17142
17267
  //#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.";
17268
+ const MESSAGE$29 = "`createRef()` in a function component allocates a brand-new ref on every render, so it never holds a value between renders. Use the `useRef()` hook instead.";
17144
17269
  const noCreateRefInFunctionComponent = defineRule({
17145
17270
  id: "no-create-ref-in-function-component",
17146
17271
  title: "createRef in function component",
@@ -17159,7 +17284,7 @@ const noCreateRefInFunctionComponent = defineRule({
17159
17284
  if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes))) return;
17160
17285
  context.report({
17161
17286
  node,
17162
- message: MESSAGE$27
17287
+ message: MESSAGE$29
17163
17288
  });
17164
17289
  } })
17165
17290
  });
@@ -17299,7 +17424,7 @@ const noCreateStoreInRender = defineRule({
17299
17424
  });
17300
17425
  //#endregion
17301
17426
  //#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.";
17427
+ const MESSAGE$28 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
17303
17428
  const noDanger = defineRule({
17304
17429
  id: "no-danger",
17305
17430
  title: "Raw HTML injection can run unsafe markup",
@@ -17312,7 +17437,7 @@ const noDanger = defineRule({
17312
17437
  if (!propAttribute) return;
17313
17438
  context.report({
17314
17439
  node: propAttribute.name,
17315
- message: MESSAGE$26
17440
+ message: MESSAGE$28
17316
17441
  });
17317
17442
  },
17318
17443
  CallExpression(node) {
@@ -17324,7 +17449,7 @@ const noDanger = defineRule({
17324
17449
  const propertyKey = property.key;
17325
17450
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
17326
17451
  node: propertyKey,
17327
- message: MESSAGE$26
17452
+ message: MESSAGE$28
17328
17453
  });
17329
17454
  }
17330
17455
  }
@@ -17332,7 +17457,7 @@ const noDanger = defineRule({
17332
17457
  });
17333
17458
  //#endregion
17334
17459
  //#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`.";
17460
+ const MESSAGE$27 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
17336
17461
  const isLineBreak = (child) => {
17337
17462
  if (!isNodeOfType(child, "JSXText")) return false;
17338
17463
  return child.value.trim().length === 0 && child.value.includes("\n");
@@ -17402,7 +17527,7 @@ const noDangerWithChildren = defineRule({
17402
17527
  if (!hasChildrenProp && !hasNestedChildren) return;
17403
17528
  if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
17404
17529
  node: opening,
17405
- message: MESSAGE$25
17530
+ message: MESSAGE$27
17406
17531
  });
17407
17532
  },
17408
17533
  CallExpression(node) {
@@ -17414,7 +17539,7 @@ const noDangerWithChildren = defineRule({
17414
17539
  if (!propsShape.hasDangerously) return;
17415
17540
  if (node.arguments.length >= 3 || propsShape.hasChildren) context.report({
17416
17541
  node,
17417
- message: MESSAGE$25
17542
+ message: MESSAGE$27
17418
17543
  });
17419
17544
  }
17420
17545
  })
@@ -17991,7 +18116,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
17991
18116
  //#endregion
17992
18117
  //#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
17993
18118
  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`.";
18119
+ const MESSAGE$26 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
17995
18120
  const resolveSettings$20 = (settings) => {
17996
18121
  const reactDoctor = settings?.["react-doctor"];
17997
18122
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidMountSetState ?? {} : {}).mode ?? "allowed" };
@@ -18010,7 +18135,7 @@ const noDidMountSetState = defineRule({
18010
18135
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$2, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
18011
18136
  context.report({
18012
18137
  node: node.callee,
18013
- message: MESSAGE$24
18138
+ message: MESSAGE$26
18014
18139
  });
18015
18140
  } };
18016
18141
  }
@@ -18018,7 +18143,7 @@ const noDidMountSetState = defineRule({
18018
18143
  //#endregion
18019
18144
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
18020
18145
  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.";
18146
+ const MESSAGE$25 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
18022
18147
  const resolveSettings$19 = (settings) => {
18023
18148
  const reactDoctor = settings?.["react-doctor"];
18024
18149
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidUpdateSetState ?? {} : {}).mode ?? "allowed" };
@@ -18037,7 +18162,7 @@ const noDidUpdateSetState = defineRule({
18037
18162
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
18038
18163
  context.report({
18039
18164
  node: node.callee,
18040
- message: MESSAGE$23
18165
+ message: MESSAGE$25
18041
18166
  });
18042
18167
  } };
18043
18168
  }
@@ -18060,7 +18185,7 @@ const isStateMemberExpression = (node) => {
18060
18185
  };
18061
18186
  //#endregion
18062
18187
  //#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.";
18188
+ const MESSAGE$24 = "Your users see stale data because mutating `this.state` by hand never redraws & gets overwritten.";
18064
18189
  const shouldIgnoreMutation = (node) => {
18065
18190
  let isConstructor = false;
18066
18191
  let isInsideCallExpression = false;
@@ -18082,7 +18207,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
18082
18207
  if (shouldIgnoreMutation(reportNode)) return;
18083
18208
  context.report({
18084
18209
  node: reportNode,
18085
- message: MESSAGE$22
18210
+ message: MESSAGE$24
18086
18211
  });
18087
18212
  };
18088
18213
  const noDirectMutationState = defineRule({
@@ -18292,6 +18417,26 @@ const noDocumentStartViewTransition = defineRule({
18292
18417
  } })
18293
18418
  });
18294
18419
  //#endregion
18420
+ //#region src/plugin/rules/js-performance/no-document-write.ts
18421
+ const MESSAGE$23 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
18422
+ const WRITE_METHODS = new Set(["write", "writeln"]);
18423
+ const noDocumentWrite = defineRule({
18424
+ id: "no-document-write",
18425
+ title: "document.write/writeln",
18426
+ severity: "warn",
18427
+ recommendation: "Don't use `document.write()`/`document.writeln()`. Append DOM nodes or set `innerHTML`/`textContent` on a specific element instead.",
18428
+ create: (context) => ({ CallExpression(node) {
18429
+ const callee = node.callee;
18430
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
18431
+ if (!isNodeOfType(callee.object, "Identifier") || callee.object.name !== "document") return;
18432
+ if (!isNodeOfType(callee.property, "Identifier") || !WRITE_METHODS.has(callee.property.name)) return;
18433
+ context.report({
18434
+ node,
18435
+ message: MESSAGE$23
18436
+ });
18437
+ } })
18438
+ });
18439
+ //#endregion
18295
18440
  //#region src/plugin/rules/bundle-size/no-dynamic-import-path.ts
18296
18441
  const noDynamicImportPath = defineRule({
18297
18442
  id: "no-dynamic-import-path",
@@ -19670,7 +19815,7 @@ const ALLOWED_NAMESPACES = new Set([
19670
19815
  "ReactDOM",
19671
19816
  "ReactDom"
19672
19817
  ]);
19673
- const MESSAGE$21 = "`findDOMNode` crashes your app in React 19 because it was removed.";
19818
+ const MESSAGE$22 = "`findDOMNode` crashes your app in React 19 because it was removed.";
19674
19819
  const noFindDomNode = defineRule({
19675
19820
  id: "no-find-dom-node",
19676
19821
  title: "findDOMNode breaks component encapsulation",
@@ -19681,7 +19826,7 @@ const noFindDomNode = defineRule({
19681
19826
  if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
19682
19827
  context.report({
19683
19828
  node: callee,
19684
- message: MESSAGE$21
19829
+ message: MESSAGE$22
19685
19830
  });
19686
19831
  return;
19687
19832
  }
@@ -19692,7 +19837,7 @@ const noFindDomNode = defineRule({
19692
19837
  if (callee.property.name !== "findDOMNode") return;
19693
19838
  context.report({
19694
19839
  node: callee.property,
19695
- message: MESSAGE$21
19840
+ message: MESSAGE$22
19696
19841
  });
19697
19842
  }
19698
19843
  } })
@@ -19934,7 +20079,7 @@ const noGrayOnColoredBackground = defineRule({
19934
20079
  });
19935
20080
  //#endregion
19936
20081
  //#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.";
20082
+ const MESSAGE$21 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
19938
20083
  const noImgLazyWithHighFetchpriority = defineRule({
19939
20084
  id: "no-img-lazy-with-high-fetchpriority",
19940
20085
  title: "Lazy image with high fetchPriority",
@@ -19948,7 +20093,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
19948
20093
  if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
19949
20094
  context.report({
19950
20095
  node: node.name,
19951
- message: MESSAGE$20
20096
+ message: MESSAGE$21
19952
20097
  });
19953
20098
  } })
19954
20099
  });
@@ -20045,15 +20190,20 @@ const noInlineExhaustiveStyle = defineRule({
20045
20190
  severity: "warn",
20046
20191
  tags: ["test-noise", "react-jsx-only"],
20047
20192
  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
- } })
20193
+ create: (context) => {
20194
+ if (isGeneratedImageRenderContext(context)) return {};
20195
+ return { JSXAttribute(node) {
20196
+ const expression = getInlineStyleExpression(node);
20197
+ if (!expression) return;
20198
+ const propertyCount = expression.properties?.filter((property) => isNodeOfType(property, "Property")).length ?? 0;
20199
+ if (propertyCount < 8) return;
20200
+ if (isGeneratedImageRenderContext(context, node.parent ?? void 0)) return;
20201
+ context.report({
20202
+ node: expression,
20203
+ 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.`
20204
+ });
20205
+ } };
20206
+ }
20057
20207
  });
20058
20208
  //#endregion
20059
20209
  //#region src/plugin/rules/performance/no-inline-prop-on-memo-component.ts
@@ -20183,7 +20333,7 @@ const noIsMounted = defineRule({
20183
20333
  });
20184
20334
  //#endregion
20185
20335
  //#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)`.";
20336
+ 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
20337
  const isJsonMethodCall = (node, method) => {
20188
20338
  if (!isNodeOfType(node, "CallExpression")) return false;
20189
20339
  const callee = node.callee;
@@ -20200,13 +20350,13 @@ const noJsonParseStringifyClone = defineRule({
20200
20350
  if (!firstArgument || !isJsonMethodCall(firstArgument, "stringify")) return;
20201
20351
  context.report({
20202
20352
  node,
20203
- message: MESSAGE$19
20353
+ message: MESSAGE$20
20204
20354
  });
20205
20355
  } })
20206
20356
  });
20207
20357
  //#endregion
20208
20358
  //#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.";
20359
+ const MESSAGE$19 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
20210
20360
  const isJsxElementTypeReference = (node) => {
20211
20361
  if (!isNodeOfType(node, "TSTypeReference")) return false;
20212
20362
  const typeName = node.typeName;
@@ -20223,7 +20373,7 @@ const checkReturnType = (context, returnType) => {
20223
20373
  if (!typeAnnotation) return;
20224
20374
  if (isJsxElementTypeReference(typeAnnotation)) context.report({
20225
20375
  node: typeAnnotation,
20226
- message: MESSAGE$18
20376
+ message: MESSAGE$19
20227
20377
  });
20228
20378
  };
20229
20379
  const noJsxElementType = defineRule({
@@ -20678,7 +20828,7 @@ const noMoment = defineRule({
20678
20828
  });
20679
20829
  //#endregion
20680
20830
  //#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.";
20831
+ const MESSAGE$18 = "This file declares several components, so each component is harder to find, test, and change.";
20682
20832
  const resolveSettings$16 = (settings) => {
20683
20833
  const reactDoctor = settings?.["react-doctor"];
20684
20834
  return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
@@ -21000,7 +21150,7 @@ const noMultiComp = defineRule({
21000
21150
  if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
21001
21151
  for (const component of flagged.slice(1)) context.report({
21002
21152
  node: component.reportNode,
21003
- message: MESSAGE$17
21153
+ message: MESSAGE$18
21004
21154
  });
21005
21155
  } };
21006
21156
  }
@@ -21168,7 +21318,7 @@ const resolveReducerFunction = (node, currentFilename) => {
21168
21318
  };
21169
21319
  //#endregion
21170
21320
  //#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.";
21321
+ const MESSAGE$17 = "This reducer changes state in place, so your update is silently skipped.";
21172
21322
  const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
21173
21323
  "copyWithin",
21174
21324
  "fill",
@@ -21378,7 +21528,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
21378
21528
  reportedNodes.add(options.crossFileConsumerCallSite);
21379
21529
  context.report({
21380
21530
  node: options.crossFileConsumerCallSite,
21381
- message: `${MESSAGE$16} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
21531
+ message: `${MESSAGE$17} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
21382
21532
  });
21383
21533
  return;
21384
21534
  }
@@ -21387,7 +21537,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
21387
21537
  reportedNodes.add(mutation.node);
21388
21538
  context.report({
21389
21539
  node: mutation.node,
21390
- message: MESSAGE$16
21540
+ message: MESSAGE$17
21391
21541
  });
21392
21542
  }
21393
21543
  };
@@ -21659,7 +21809,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
21659
21809
  });
21660
21810
  //#endregion
21661
21811
  //#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.";
21812
+ const MESSAGE$16 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
21663
21813
  const resolveSettings$14 = (settings) => {
21664
21814
  const reactDoctor = settings?.["react-doctor"];
21665
21815
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noNoninteractiveTabindex ?? {} : {};
@@ -21687,7 +21837,7 @@ const noNoninteractiveTabindex = defineRule({
21687
21837
  if (numeric === null) {
21688
21838
  if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues) context.report({
21689
21839
  node: tabIndex,
21690
- message: MESSAGE$15
21840
+ message: MESSAGE$16
21691
21841
  });
21692
21842
  return;
21693
21843
  }
@@ -21700,7 +21850,7 @@ const noNoninteractiveTabindex = defineRule({
21700
21850
  if (!roleAttribute) {
21701
21851
  context.report({
21702
21852
  node: tabIndex,
21703
- message: MESSAGE$15
21853
+ message: MESSAGE$16
21704
21854
  });
21705
21855
  return;
21706
21856
  }
@@ -21714,7 +21864,7 @@ const noNoninteractiveTabindex = defineRule({
21714
21864
  }
21715
21865
  context.report({
21716
21866
  node: tabIndex,
21717
- message: MESSAGE$15
21867
+ message: MESSAGE$16
21718
21868
  });
21719
21869
  } };
21720
21870
  }
@@ -22405,7 +22555,7 @@ const noRandomKey = defineRule({
22405
22555
  });
22406
22556
  //#endregion
22407
22557
  //#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.";
22558
+ 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
22559
  const isChildrenIdentifier = (node, contextNode) => {
22410
22560
  if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
22411
22561
  return isImportedFromModule(contextNode, "Children", "react");
@@ -22431,13 +22581,13 @@ const noReactChildren = defineRule({
22431
22581
  if (isChildrenIdentifier(memberObject, node)) {
22432
22582
  context.report({
22433
22583
  node: calleeOuter,
22434
- message: MESSAGE$14
22584
+ message: MESSAGE$15
22435
22585
  });
22436
22586
  return;
22437
22587
  }
22438
22588
  if (isReactNamespaceMember(memberObject, node)) context.report({
22439
22589
  node: calleeOuter,
22440
- message: MESSAGE$14
22590
+ message: MESSAGE$15
22441
22591
  });
22442
22592
  } })
22443
22593
  });
@@ -22760,7 +22910,7 @@ const noRenderPropChildren = defineRule({
22760
22910
  });
22761
22911
  //#endregion
22762
22912
  //#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.";
22913
+ const MESSAGE$14 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
22764
22914
  const isReactDomRenderCall = (node) => {
22765
22915
  if (!isNodeOfType(node.callee, "MemberExpression")) return false;
22766
22916
  if (!isNodeOfType(node.callee.object, "Identifier")) return false;
@@ -22784,7 +22934,7 @@ const noRenderReturnValue = defineRule({
22784
22934
  if (!isUsedAsReturnValue(node.parent)) return;
22785
22935
  context.report({
22786
22936
  node: node.callee,
22787
- message: MESSAGE$13
22937
+ message: MESSAGE$14
22788
22938
  });
22789
22939
  } })
22790
22940
  });
@@ -22944,11 +23094,17 @@ const classifySecretFileExposure = (filename, options = {}) => {
22944
23094
  return "unknown";
22945
23095
  };
22946
23096
  //#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();
23097
+ //#region src/plugin/utils/tokenize-identifier-words.ts
23098
+ const IDENTIFIER_WORD_PATTERN = /[A-Z]+(?=[A-Z][a-z]|\b)|[A-Z]?[a-z]+|\d+/g;
23099
+ const tokenizeIdentifierWords = (identifierName) => {
23100
+ const words = identifierName.match(IDENTIFIER_WORD_PATTERN);
23101
+ if (!words) return [];
23102
+ return words.map((word) => word.toLowerCase());
22950
23103
  };
22951
23104
  //#endregion
23105
+ //#region src/plugin/utils/get-identifier-trailing-word.ts
23106
+ const getIdentifierTrailingWord = (identifierName) => tokenizeIdentifierWords(identifierName).at(-1) ?? identifierName.toLowerCase();
23107
+ //#endregion
22952
23108
  //#region src/plugin/constants/tanstack.ts
22953
23109
  const TANSTACK_ROUTE_FILE_PATTERN = /\/routes\//;
22954
23110
  const TANSTACK_ROOT_ROUTE_FILE_PATTERN = /__root\.(tsx?|jsx?)$/;
@@ -23476,7 +23632,7 @@ const getParentComponent = (node) => {
23476
23632
  };
23477
23633
  //#endregion
23478
23634
  //#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.";
23635
+ const MESSAGE$13 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
23480
23636
  const noSetState = defineRule({
23481
23637
  id: "no-set-state",
23482
23638
  title: "Local class state forbidden",
@@ -23491,7 +23647,7 @@ const noSetState = defineRule({
23491
23647
  if (!getParentComponent(node)) return;
23492
23648
  context.report({
23493
23649
  node: node.callee,
23494
- message: MESSAGE$12
23650
+ message: MESSAGE$13
23495
23651
  });
23496
23652
  } })
23497
23653
  });
@@ -23653,7 +23809,7 @@ const isAbstractRole = (openingElement, settings) => {
23653
23809
  };
23654
23810
  //#endregion
23655
23811
  //#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.";
23812
+ 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
23813
  const DEFAULT_HANDLERS = [
23658
23814
  "onClick",
23659
23815
  "onMouseDown",
@@ -23713,7 +23869,7 @@ const noStaticElementInteractions = defineRule({
23713
23869
  if (!roleAttribute || !roleAttribute.value) {
23714
23870
  context.report({
23715
23871
  node: node.name,
23716
- message: MESSAGE$11
23872
+ message: MESSAGE$12
23717
23873
  });
23718
23874
  return;
23719
23875
  }
@@ -23723,19 +23879,66 @@ const noStaticElementInteractions = defineRule({
23723
23879
  if (firstRole && (isInteractiveRole(firstRole) || isNonInteractiveRole(firstRole))) return;
23724
23880
  context.report({
23725
23881
  node: node.name,
23726
- message: MESSAGE$11
23882
+ message: MESSAGE$12
23727
23883
  });
23728
23884
  return;
23729
23885
  }
23730
23886
  if (isNodeOfType(attributeValue, "JSXExpressionContainer") && settings.allowExpressionValues) return;
23731
23887
  context.report({
23732
23888
  node: node.name,
23733
- message: MESSAGE$11
23889
+ message: MESSAGE$12
23734
23890
  });
23735
23891
  } };
23736
23892
  }
23737
23893
  });
23738
23894
  //#endregion
23895
+ //#region src/plugin/rules/react-builtins/no-string-false-on-boolean-attribute.ts
23896
+ const BOOLEAN_ATTRIBUTES = new Set([
23897
+ "disabled",
23898
+ "checked",
23899
+ "readonly",
23900
+ "required",
23901
+ "selected",
23902
+ "multiple",
23903
+ "autofocus",
23904
+ "autoplay",
23905
+ "controls",
23906
+ "loop",
23907
+ "muted",
23908
+ "open",
23909
+ "reversed",
23910
+ "default",
23911
+ "novalidate",
23912
+ "formnovalidate",
23913
+ "playsinline",
23914
+ "itemscope",
23915
+ "allowfullscreen"
23916
+ ]);
23917
+ const noStringFalseOnBooleanAttribute = defineRule({
23918
+ id: "no-string-false-on-boolean-attribute",
23919
+ title: "String true/false on a boolean attribute",
23920
+ severity: "warn",
23921
+ 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.",
23922
+ create: (context) => ({ JSXOpeningElement(node) {
23923
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
23924
+ const firstCharacter = node.name.name.charCodeAt(0);
23925
+ if (firstCharacter < 97 || firstCharacter > 122) return;
23926
+ for (const attribute of node.attributes) {
23927
+ if (!isNodeOfType(attribute, "JSXAttribute")) continue;
23928
+ if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
23929
+ if (!BOOLEAN_ATTRIBUTES.has(attribute.name.name.toLowerCase())) continue;
23930
+ const value = getJsxPropStringValue(attribute);
23931
+ if (value !== "false" && value !== "true") continue;
23932
+ const attributeName = attribute.name.name;
23933
+ 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}\``;
23934
+ context.report({
23935
+ node: attribute,
23936
+ message: `\`${attributeName}="${value}"\` passes the string "${value}", ${guidance}.`
23937
+ });
23938
+ }
23939
+ } })
23940
+ });
23941
+ //#endregion
23739
23942
  //#region src/plugin/rules/react-builtins/no-string-refs.ts
23740
23943
  const STRING_IN_REF_MESSAGE = "Your component can't reach this node because string refs don't work in modern React.";
23741
23944
  const THIS_REFS_MESSAGE = "Your component can't reach its nodes because `this.refs` is empty in modern React.";
@@ -23786,6 +23989,27 @@ const noStringRefs = defineRule({
23786
23989
  }
23787
23990
  });
23788
23991
  //#endregion
23992
+ //#region src/plugin/rules/js-performance/no-sync-xhr.ts
23993
+ 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)`).";
23994
+ const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
23995
+ const noSyncXhr = defineRule({
23996
+ id: "no-sync-xhr",
23997
+ title: "Synchronous XMLHttpRequest",
23998
+ severity: "warn",
23999
+ recommendation: "Never open an XMLHttpRequest synchronously (`async` = `false`). It blocks the main thread. Use `fetch()` or pass `true` and handle the response asynchronously.",
24000
+ create: (context) => ({ CallExpression(node) {
24001
+ const callee = node.callee;
24002
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
24003
+ if (!isNodeOfType(callee.property, "Identifier") || callee.property.name !== "open") return;
24004
+ const asyncArgument = node.arguments?.[2];
24005
+ if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
24006
+ context.report({
24007
+ node,
24008
+ message: MESSAGE$11
24009
+ });
24010
+ } })
24011
+ });
24012
+ //#endregion
23789
24013
  //#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
23790
24014
  const MESSAGE$10 = "This value is `undefined` because function components have no `this`.";
23791
24015
  const isInsideClassMethod = (node, customClassFactoryNames) => {
@@ -34895,6 +35119,47 @@ const serverAfterNonblocking = defineRule({
34895
35119
  }
34896
35120
  });
34897
35121
  //#endregion
35122
+ //#region src/plugin/utils/is-auth-guard-name.ts
35123
+ const SIGNED_IN_HEAD_TOKENS = new Set([
35124
+ "signed",
35125
+ "logged",
35126
+ "sign"
35127
+ ]);
35128
+ const mergeSignedInTokens = (tokens) => {
35129
+ const mergedTokens = [];
35130
+ for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) {
35131
+ const currentToken = tokens[tokenIndex];
35132
+ if (SIGNED_IN_HEAD_TOKENS.has(currentToken) && tokens[tokenIndex + 1] === "in") {
35133
+ mergedTokens.push(`${currentToken}in`);
35134
+ tokenIndex += 1;
35135
+ continue;
35136
+ }
35137
+ mergedTokens.push(currentToken);
35138
+ }
35139
+ return mergedTokens;
35140
+ };
35141
+ const isAuthGuardName = (calleeName) => {
35142
+ const tokens = mergeSignedInTokens(tokenizeIdentifierWords(calleeName));
35143
+ if (tokens.length === 0) return false;
35144
+ let hasAssertiveVerb = false;
35145
+ let hasGetterVerb = false;
35146
+ let hasQualifier = false;
35147
+ let hasStrongNoun = false;
35148
+ let hasWeakNoun = false;
35149
+ for (const token of tokens) {
35150
+ if (AUTH_STRONG_TOKEN_PATTERN.test(token) || AUTH_STANDALONE_NOUN_TOKENS.has(token)) return true;
35151
+ if (AUTH_ASSERTIVE_VERB_TOKENS.has(token)) hasAssertiveVerb = true;
35152
+ if (AUTH_GETTER_VERB_TOKENS.has(token)) hasGetterVerb = true;
35153
+ if (AUTH_QUALIFIER_TOKENS.has(token)) hasQualifier = true;
35154
+ if (AUTH_STRONG_NOUN_TOKENS.has(token)) hasStrongNoun = true;
35155
+ if (AUTH_WEAK_NOUN_TOKENS.has(token)) hasWeakNoun = true;
35156
+ }
35157
+ if (hasAssertiveVerb && (hasStrongNoun || hasWeakNoun)) return true;
35158
+ if (hasGetterVerb && hasStrongNoun) return true;
35159
+ if (hasQualifier && hasWeakNoun) return true;
35160
+ return false;
35161
+ };
35162
+ //#endregion
34898
35163
  //#region src/plugin/rules/server/server-auth-actions.ts
34899
35164
  const isAsyncFunctionLikeNode = (node) => {
34900
35165
  if (!node) return false;
@@ -34937,9 +35202,13 @@ const isMemberCallAuthRelated = (receiverNode, methodName, genericMethodNames) =
34937
35202
  const getAuthCallName = (callExpression, allowedFunctionNames, genericMethodNames) => {
34938
35203
  const calleeNode = unwrapTypeWrappedCallee(callExpression.callee);
34939
35204
  if (!calleeNode) return null;
34940
- if (isNodeOfType(calleeNode, "Identifier")) return allowedFunctionNames.has(calleeNode.name) ? calleeNode.name : null;
35205
+ if (isNodeOfType(calleeNode, "Identifier")) {
35206
+ const calleeName = calleeNode.name;
35207
+ return allowedFunctionNames.has(calleeName) || isAuthGuardName(calleeName) ? calleeName : null;
35208
+ }
34941
35209
  if (isNodeOfType(calleeNode, "MemberExpression") && isNodeOfType(calleeNode.property, "Identifier")) {
34942
35210
  const methodName = calleeNode.property.name;
35211
+ if (isAuthGuardName(methodName)) return methodName;
34943
35212
  if (!allowedFunctionNames.has(methodName)) return null;
34944
35213
  if (!isMemberCallAuthRelated(calleeNode.object, methodName, genericMethodNames)) return null;
34945
35214
  return methodName;
@@ -35316,13 +35585,7 @@ const serverNoMutableModuleState = defineRule({
35316
35585
  const collectDeclaredNames = (declaration) => {
35317
35586
  const names = /* @__PURE__ */ new Set();
35318
35587
  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
- }
35588
+ for (const declarator of declaration.declarations ?? []) collectPatternNames(declarator.id, names);
35326
35589
  return names;
35327
35590
  };
35328
35591
  const declarationStartsWithAwait = (declaration) => {
@@ -35332,11 +35595,15 @@ const declarationStartsWithAwait = (declaration) => {
35332
35595
  };
35333
35596
  const declarationReadsAnyName = (declaration, names) => {
35334
35597
  if (names.size === 0) return false;
35598
+ if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
35335
35599
  let didRead = false;
35336
- walkAst(declaration, (child) => {
35337
- if (didRead) return;
35338
- if (isNodeOfType(child, "Identifier") && names.has(child.name)) didRead = true;
35339
- });
35600
+ for (const declarator of declaration.declarations ?? []) {
35601
+ if (!declarator.init) continue;
35602
+ walkAst(declarator.init, (child) => {
35603
+ if (didRead) return;
35604
+ if (isNodeOfType(child, "Identifier") && names.has(child.name)) didRead = true;
35605
+ });
35606
+ }
35340
35607
  return didRead;
35341
35608
  };
35342
35609
  const serverSequentialIndependentAwait = defineRule({
@@ -36596,7 +36863,7 @@ const urlPrefilledPrivilegedAction = defineRule({
36596
36863
  recommendation: "Require server-side validation and explicit confirmation for URL-sourced invite, role, permission, redirect, or sharing parameters.",
36597
36864
  scan: scanByPattern({
36598
36865
  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,
36866
+ 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
36867
  message: "Client code reads sensitive action state from the URL, which can pre-fill invites, roles, redirects, or sharing flows with attacker values."
36601
36868
  })
36602
36869
  });
@@ -39052,6 +39319,17 @@ const reactDoctorRules = [
39052
39319
  requires: [...new Set(["react", ...noDocumentStartViewTransition.requires ?? []])]
39053
39320
  }
39054
39321
  },
39322
+ {
39323
+ key: "react-doctor/no-document-write",
39324
+ id: "no-document-write",
39325
+ source: "react-doctor",
39326
+ originallyExternal: false,
39327
+ rule: {
39328
+ ...noDocumentWrite,
39329
+ framework: "global",
39330
+ category: "Performance"
39331
+ }
39332
+ },
39055
39333
  {
39056
39334
  key: "react-doctor/no-dynamic-import-path",
39057
39335
  id: "no-dynamic-import-path",
@@ -39861,6 +40139,18 @@ const reactDoctorRules = [
39861
40139
  requires: [...new Set(["react", ...noStaticElementInteractions.requires ?? []])]
39862
40140
  }
39863
40141
  },
40142
+ {
40143
+ key: "react-doctor/no-string-false-on-boolean-attribute",
40144
+ id: "no-string-false-on-boolean-attribute",
40145
+ source: "react-doctor",
40146
+ originallyExternal: false,
40147
+ rule: {
40148
+ ...noStringFalseOnBooleanAttribute,
40149
+ framework: "global",
40150
+ category: "Bugs",
40151
+ requires: [...new Set(["react", ...noStringFalseOnBooleanAttribute.requires ?? []])]
40152
+ }
40153
+ },
39864
40154
  {
39865
40155
  key: "react-doctor/no-string-refs",
39866
40156
  id: "no-string-refs",
@@ -39873,6 +40163,17 @@ const reactDoctorRules = [
39873
40163
  requires: [...new Set(["react", ...noStringRefs.requires ?? []])]
39874
40164
  }
39875
40165
  },
40166
+ {
40167
+ key: "react-doctor/no-sync-xhr",
40168
+ id: "no-sync-xhr",
40169
+ source: "react-doctor",
40170
+ originallyExternal: false,
40171
+ rule: {
40172
+ ...noSyncXhr,
40173
+ framework: "global",
40174
+ category: "Performance"
40175
+ }
40176
+ },
39876
40177
  {
39877
40178
  key: "react-doctor/no-this-in-sfc",
39878
40179
  id: "no-this-in-sfc",