oxlint-plugin-react-doctor 0.5.6 → 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 +420 -1
  2. package/dist/index.js +846 -291
  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$51 = "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$51
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$50 = "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$50
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$50
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 = [
@@ -4201,6 +4325,58 @@ const asyncParallel = defineRule({
4201
4325
  }
4202
4326
  });
4203
4327
  //#endregion
4328
+ //#region src/plugin/rules/security/auth-token-in-web-storage.ts
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.";
4330
+ const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
4331
+ const STORAGE_GLOBALS = new Set([
4332
+ "window",
4333
+ "globalThis",
4334
+ "self"
4335
+ ]);
4336
+ const SENSITIVE_KEY_PATTERN = /token|jwt|secret|password|passwd|credential|api[-_]?key|bearer|private[-_]?key/i;
4337
+ const isWebStorageObject = (node) => {
4338
+ if (isNodeOfType(node, "Identifier")) return STORAGE_NAMES.has(node.name);
4339
+ if (isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.object, "Identifier") && STORAGE_GLOBALS.has(node.object.name) && isNodeOfType(node.property, "Identifier")) return STORAGE_NAMES.has(node.property.name);
4340
+ return false;
4341
+ };
4342
+ const staticMemberName = (member) => {
4343
+ if (!member.computed && isNodeOfType(member.property, "Identifier")) return member.property.name;
4344
+ if (member.computed && isNodeOfType(member.property, "Literal") && typeof member.property.value === "string") return member.property.value;
4345
+ return null;
4346
+ };
4347
+ const authTokenInWebStorage = defineRule({
4348
+ id: "auth-token-in-web-storage",
4349
+ title: "Auth token in web storage",
4350
+ severity: "warn",
4351
+ recommendation: "Don't persist auth tokens (JWTs, access/refresh tokens, secrets) in `localStorage`/`sessionStorage`; they're readable by any XSS. Use an `HttpOnly` cookie set by the server.",
4352
+ create: (context) => ({
4353
+ CallExpression(node) {
4354
+ const callee = node.callee;
4355
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
4356
+ if (!isNodeOfType(callee.property, "Identifier") || callee.property.name !== "setItem") return;
4357
+ if (!isWebStorageObject(callee.object)) return;
4358
+ const keyArgument = node.arguments?.[0];
4359
+ if (!keyArgument || !isNodeOfType(keyArgument, "Literal") || typeof keyArgument.value !== "string") return;
4360
+ if (!SENSITIVE_KEY_PATTERN.test(keyArgument.value)) return;
4361
+ context.report({
4362
+ node,
4363
+ message: MESSAGE$57
4364
+ });
4365
+ },
4366
+ AssignmentExpression(node) {
4367
+ const target = node.left;
4368
+ if (!isNodeOfType(target, "MemberExpression")) return;
4369
+ if (!isWebStorageObject(target.object)) return;
4370
+ const propertyName = staticMemberName(target);
4371
+ if (!propertyName || !SENSITIVE_KEY_PATTERN.test(propertyName)) return;
4372
+ context.report({
4373
+ node: target,
4374
+ message: MESSAGE$57
4375
+ });
4376
+ }
4377
+ })
4378
+ });
4379
+ //#endregion
4204
4380
  //#region src/plugin/rules/a11y/autocomplete-valid.ts
4205
4381
  const buildMessage$25 = (value) => `Users who rely on autofill can't fill this field because \`${value}\` isn't a known token, so use a valid \`autoComplete\` token.`;
4206
4382
  const AUTOFILL_TOKENS = new Set([
@@ -4534,6 +4710,14 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
4534
4710
  }
4535
4711
  });
4536
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
4537
4721
  //#region src/plugin/utils/is-pure-event-blocker-handler.ts
4538
4722
  const BLOCKER_METHOD_NAMES = new Set([
4539
4723
  "stopPropagation",
@@ -4571,8 +4755,7 @@ const isPureEventBlockerHandler = (attribute) => {
4571
4755
  };
4572
4756
  //#endregion
4573
4757
  //#region src/plugin/rules/a11y/click-events-have-key-events.ts
4574
- const PRESENTATION_ROLES$1 = new Set(["presentation", "none"]);
4575
- const MESSAGE$49 = "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`.";
4576
4759
  const KEY_HANDLERS = [
4577
4760
  "onKeyUp",
4578
4761
  "onKeyDown",
@@ -4596,15 +4779,11 @@ const clickEventsHaveKeyEvents = defineRule({
4596
4779
  if (!onClick) return;
4597
4780
  if (isPureEventBlockerHandler(onClick)) return;
4598
4781
  if (isHiddenFromScreenReader(node, context.settings)) return;
4599
- const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
4600
- if (roleAttribute) {
4601
- const roleValue = getJsxPropStringValue(roleAttribute);
4602
- if (roleValue && PRESENTATION_ROLES$1.has(roleValue)) return;
4603
- }
4782
+ if (isPresentationRole(node)) return;
4604
4783
  if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
4605
4784
  context.report({
4606
4785
  node: node.name,
4607
- message: MESSAGE$49
4786
+ message: MESSAGE$56
4608
4787
  });
4609
4788
  } };
4610
4789
  }
@@ -4719,7 +4898,7 @@ const isReactComponentName = (name) => {
4719
4898
  };
4720
4899
  //#endregion
4721
4900
  //#region src/plugin/rules/a11y/control-has-associated-label.ts
4722
- const MESSAGE$48 = "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`.";
4723
4902
  const DEFAULT_IGNORE_ELEMENTS = ["link", "canvas"];
4724
4903
  const DEFAULT_LABELLING_PROPS = [
4725
4904
  "alt",
@@ -4880,7 +5059,7 @@ const controlHasAssociatedLabel = defineRule({
4880
5059
  for (const child of node.children) if (checkChildForLabel(child, 1, checkContext)) return;
4881
5060
  context.report({
4882
5061
  node: opening,
4883
- message: MESSAGE$48
5062
+ message: MESSAGE$55
4884
5063
  });
4885
5064
  } };
4886
5065
  }
@@ -5306,6 +5485,38 @@ const noVagueButtonLabel = defineRule({
5306
5485
  } })
5307
5486
  });
5308
5487
  //#endregion
5488
+ //#region src/plugin/utils/has-jsx-spread-attribute.ts
5489
+ const hasJsxSpreadAttribute$1 = (attributes) => attributes.some((attribute) => isNodeOfType(attribute, "JSXSpreadAttribute"));
5490
+ //#endregion
5491
+ //#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
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.";
5493
+ const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
5494
+ const NAME_PROVIDING_ATTRIBUTES = [
5495
+ "aria-label",
5496
+ "aria-labelledby",
5497
+ "title"
5498
+ ];
5499
+ const dialogHasAccessibleName = defineRule({
5500
+ id: "dialog-has-accessible-name",
5501
+ title: "Dialog without accessible name",
5502
+ severity: "warn",
5503
+ recommendation: "Give every `<dialog>` / `role=\"dialog\"` an accessible name with `aria-label` or `aria-labelledby` (referencing the dialog's title element).",
5504
+ create: (context) => ({ JSXOpeningElement(node) {
5505
+ if (!isNodeOfType(node.name, "JSXIdentifier")) return;
5506
+ const tagName = node.name.name;
5507
+ if (tagName[0] !== tagName[0]?.toLowerCase()) return;
5508
+ const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
5509
+ const roleValue = roleAttribute ? getJsxPropStringValue(roleAttribute) : null;
5510
+ if (!(tagName === "dialog" || roleValue !== null && DIALOG_ROLES.has(roleValue))) return;
5511
+ if (hasJsxSpreadAttribute$1(node.attributes)) return;
5512
+ if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
5513
+ context.report({
5514
+ node: node.name,
5515
+ message: MESSAGE$54
5516
+ });
5517
+ } })
5518
+ });
5519
+ //#endregion
5309
5520
  //#region src/plugin/utils/is-es5-component.ts
5310
5521
  const PRAGMA$2 = "React";
5311
5522
  const CREATE_CLASS = "createReactClass";
@@ -5340,7 +5551,7 @@ const isEs6Component = (node) => {
5340
5551
  };
5341
5552
  //#endregion
5342
5553
  //#region src/plugin/rules/react-builtins/display-name.ts
5343
- const MESSAGE$47 = "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`.";
5344
5555
  const DEFAULT_ADDITIONAL_HOCS = [
5345
5556
  "observer",
5346
5557
  "lazy",
@@ -5539,11 +5750,11 @@ const displayName = defineRule({
5539
5750
  category: "Architecture",
5540
5751
  create: (context) => {
5541
5752
  const settings = resolveSettings$44(context.settings);
5542
- const ignoreNamed = settings.ignoreTranspilerName ? false : true;
5753
+ const ignoreNamed = !settings.ignoreTranspilerName;
5543
5754
  const reportAt = (node) => {
5544
5755
  context.report({
5545
5756
  node,
5546
- message: MESSAGE$47
5757
+ message: MESSAGE$53
5547
5758
  });
5548
5759
  };
5549
5760
  return {
@@ -7691,7 +7902,7 @@ const forbidElements = defineRule({
7691
7902
  });
7692
7903
  //#endregion
7693
7904
  //#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
7694
- const MESSAGE$46 = "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`.";
7695
7906
  const forwardRefUsesRef = defineRule({
7696
7907
  id: "forward-ref-uses-ref",
7697
7908
  title: "forwardRef without ref parameter",
@@ -7711,7 +7922,7 @@ const forwardRefUsesRef = defineRule({
7711
7922
  if (isNodeOfType(onlyParam, "RestElement")) return;
7712
7923
  context.report({
7713
7924
  node: inner,
7714
- message: MESSAGE$46
7925
+ message: MESSAGE$52
7715
7926
  });
7716
7927
  } })
7717
7928
  });
@@ -7748,7 +7959,7 @@ const gitProviderUrlInjectionRisk = defineRule({
7748
7959
  });
7749
7960
  //#endregion
7750
7961
  //#region src/plugin/rules/a11y/heading-has-content.ts
7751
- const MESSAGE$45 = "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`.";
7752
7963
  const DEFAULT_HEADING_TAGS = [
7753
7964
  "h1",
7754
7965
  "h2",
@@ -7781,7 +7992,7 @@ const headingHasContent = defineRule({
7781
7992
  if (isHiddenFromScreenReader(node, context.settings)) return;
7782
7993
  context.report({
7783
7994
  node,
7784
- message: MESSAGE$45
7995
+ message: MESSAGE$51
7785
7996
  });
7786
7997
  } };
7787
7998
  }
@@ -7919,7 +8130,7 @@ const hooksNoNanInDeps = defineRule({
7919
8130
  });
7920
8131
  //#endregion
7921
8132
  //#region src/plugin/rules/a11y/html-has-lang.ts
7922
- const MESSAGE$44 = "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`.";
7923
8134
  const resolveSettings$38 = (settings) => {
7924
8135
  const reactDoctor = settings?.["react-doctor"];
7925
8136
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
@@ -7962,26 +8173,17 @@ const htmlHasLang = defineRule({
7962
8173
  return { JSXOpeningElement(node) {
7963
8174
  const tag = getElementType(node, context.settings);
7964
8175
  if (!tagSet.has(tag)) return;
7965
- const hasSpread = node.attributes.some((attribute) => isNodeOfType(attribute, "JSXSpreadAttribute"));
7966
8176
  const lang = hasJsxPropIgnoreCase(node.attributes, "lang");
7967
8177
  if (!lang) {
7968
8178
  context.report({
7969
8179
  node: node.name,
7970
- message: MESSAGE$44
8180
+ message: MESSAGE$50
7971
8181
  });
7972
8182
  return;
7973
8183
  }
7974
- const verdict = evaluateLang(lang.value);
7975
- if (verdict === "missing" || verdict === "empty") {
7976
- context.report({
7977
- node: lang,
7978
- message: MESSAGE$44
7979
- });
7980
- return;
7981
- }
7982
- if (hasSpread && !lang) context.report({
7983
- node: node.name,
7984
- message: MESSAGE$44
8184
+ if (evaluateLang(lang.value) === "empty") context.report({
8185
+ node: lang,
8186
+ message: MESSAGE$50
7985
8187
  });
7986
8188
  } };
7987
8189
  }
@@ -8195,7 +8397,7 @@ const htmlNoNestedInteractive = defineRule({
8195
8397
  });
8196
8398
  //#endregion
8197
8399
  //#region src/plugin/rules/a11y/iframe-has-title.ts
8198
- const MESSAGE$43 = "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.";
8199
8401
  const evaluateTitleValue = (value) => {
8200
8402
  if (!value) return "missing";
8201
8403
  if (isNodeOfType(value, "Literal")) {
@@ -8235,14 +8437,14 @@ const iframeHasTitle = defineRule({
8235
8437
  if (!titleAttr) {
8236
8438
  if (hasSpread || tag === "iframe") context.report({
8237
8439
  node: node.name,
8238
- message: MESSAGE$43
8440
+ message: MESSAGE$49
8239
8441
  });
8240
8442
  return;
8241
8443
  }
8242
8444
  const verdict = evaluateTitleValue(titleAttr.value);
8243
8445
  if (verdict === "missing" || verdict === "empty") context.report({
8244
8446
  node: titleAttr,
8245
- message: MESSAGE$43
8447
+ message: MESSAGE$49
8246
8448
  });
8247
8449
  } })
8248
8450
  });
@@ -8346,7 +8548,7 @@ const iframeMissingSandbox = defineRule({
8346
8548
  });
8347
8549
  //#endregion
8348
8550
  //#region src/plugin/rules/a11y/img-redundant-alt.ts
8349
- const MESSAGE$42 = "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.";
8350
8552
  const DEFAULT_COMPONENTS = ["img"];
8351
8553
  const DEFAULT_REDUNDANT_WORDS = [
8352
8554
  "image",
@@ -8411,7 +8613,7 @@ const imgRedundantAlt = defineRule({
8411
8613
  if (!altAttribute) return;
8412
8614
  if (altValueRedundant(altAttribute, settings.words)) context.report({
8413
8615
  node: altAttribute,
8414
- message: MESSAGE$42
8616
+ message: MESSAGE$48
8415
8617
  });
8416
8618
  } };
8417
8619
  }
@@ -8694,14 +8896,6 @@ const isNonInteractiveElement = (elementType, openingElement) => {
8694
8896
  //#region src/plugin/utils/is-non-interactive-role.ts
8695
8897
  const isNonInteractiveRole = (role) => NON_INTERACTIVE_ROLES.has(role);
8696
8898
  //#endregion
8697
- //#region src/plugin/utils/is-presentation-role.ts
8698
- const isPresentationRole = (openingElement) => {
8699
- const roleAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "role");
8700
- if (!roleAttribute) return false;
8701
- const value = getJsxPropStringValue(roleAttribute);
8702
- return value !== null && PRESENTATION_ROLES$2.has(value);
8703
- };
8704
- //#endregion
8705
8899
  //#region src/plugin/rules/a11y/interactive-supports-focus.ts
8706
8900
  const buildTabbableMessage = (role) => `Keyboard users can't tab to this '${role}' because it isn't focusable, so add \`tabIndex={0}\`.`;
8707
8901
  const buildFocusableMessage = (role) => `Keyboard users can't focus this '${role}' because it can't receive focus, so add \`tabIndex={0}\` or \`tabIndex={-1}\`.`;
@@ -10476,6 +10670,24 @@ const hasJsxKeyAttribute = (openingElement) => {
10476
10670
  return false;
10477
10671
  };
10478
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
10479
10691
  //#region src/plugin/rules/react-builtins/jsx-key.ts
10480
10692
  const ITERATOR_METHOD_NAMES = new Set([
10481
10693
  "map",
@@ -10514,6 +10726,7 @@ const findEnclosingIteratorContext = (jsxNode) => {
10514
10726
  const arrayParent = parent.parent;
10515
10727
  if (arrayParent && isNodeOfType(arrayParent, "Property")) return null;
10516
10728
  if (arrayParent && isNodeOfType(arrayParent, "ArrayExpression")) return null;
10729
+ if (isNonChildrenJsxAttributeValue(parent)) return null;
10517
10730
  return { kind: "array" };
10518
10731
  } else if (isNodeOfType(parent, "CallExpression")) {
10519
10732
  const callee = parent.callee;
@@ -10526,10 +10739,13 @@ const findEnclosingIteratorContext = (jsxNode) => {
10526
10739
  if (!targetArg) return null;
10527
10740
  let walker = current;
10528
10741
  while (walker && walker !== parent) {
10529
- if (walker === targetArg) return {
10530
- kind: "iterator",
10531
- callExpression: parent
10532
- };
10742
+ if (walker === targetArg) {
10743
+ if (isNonChildrenJsxAttributeValue(parent)) return null;
10744
+ return {
10745
+ kind: "iterator",
10746
+ callExpression: parent
10747
+ };
10748
+ }
10533
10749
  walker = walker.parent ?? null;
10534
10750
  }
10535
10751
  return null;
@@ -10768,7 +10984,7 @@ const jsxMaxDepth = defineRule({
10768
10984
  });
10769
10985
  //#endregion
10770
10986
  //#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
10771
- const MESSAGE$41 = "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.";
10772
10988
  const LITERAL_TEXT_TAGS = new Set([
10773
10989
  "code",
10774
10990
  "pre",
@@ -10804,7 +11020,7 @@ const jsxNoCommentTextnodes = defineRule({
10804
11020
  if (isInsideLiteralTextTag(node)) return;
10805
11021
  context.report({
10806
11022
  node,
10807
- message: MESSAGE$41
11023
+ message: MESSAGE$47
10808
11024
  });
10809
11025
  } })
10810
11026
  });
@@ -10835,7 +11051,7 @@ const isInsideFunctionScope = (node) => {
10835
11051
  };
10836
11052
  //#endregion
10837
11053
  //#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
10838
- const MESSAGE$40 = "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.";
10839
11055
  const CONTEXT_MODULES$1 = [
10840
11056
  "react",
10841
11057
  "use-context-selector",
@@ -10933,7 +11149,7 @@ const jsxNoConstructedContextValues = defineRule({
10933
11149
  if (!isConstructedValue(innerExpression)) continue;
10934
11150
  context.report({
10935
11151
  node: attribute,
10936
- message: MESSAGE$40
11152
+ message: MESSAGE$46
10937
11153
  });
10938
11154
  }
10939
11155
  }
@@ -11019,7 +11235,7 @@ const isJsxAttributeOnIntrinsicHtmlElement = (attribute) => {
11019
11235
  };
11020
11236
  //#endregion
11021
11237
  //#region src/plugin/rules/react-builtins/jsx-no-jsx-as-prop.ts
11022
- const MESSAGE$39 = "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.";
11023
11239
  const KNOWN_SLOT_PROP_NAMES = new Set([
11024
11240
  "icon",
11025
11241
  "Icon",
@@ -11288,7 +11504,7 @@ const jsxNoJsxAsProp = defineRule({
11288
11504
  if (!isJsxProducingExpression(expressionNode) && !followsRenderLocalJsxBinding(expressionNode, node)) return;
11289
11505
  context.report({
11290
11506
  node,
11291
- message: MESSAGE$39
11507
+ message: MESSAGE$45
11292
11508
  });
11293
11509
  }
11294
11510
  };
@@ -11576,7 +11792,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
11576
11792
  ];
11577
11793
  //#endregion
11578
11794
  //#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
11579
- const MESSAGE$38 = "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.";
11580
11796
  const isDataArrayPropName = (propName) => {
11581
11797
  if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
11582
11798
  for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -11660,7 +11876,7 @@ const jsxNoNewArrayAsProp = defineRule({
11660
11876
  if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
11661
11877
  context.report({
11662
11878
  node,
11663
- message: MESSAGE$38
11879
+ message: MESSAGE$44
11664
11880
  });
11665
11881
  }
11666
11882
  };
@@ -11918,7 +12134,7 @@ const SAFE_RECEIVER_NAMES = new Set([
11918
12134
  ]);
11919
12135
  //#endregion
11920
12136
  //#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
11921
- const MESSAGE$37 = "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.";
11922
12138
  const isAccessorPredicateName = (propName) => {
11923
12139
  for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
11924
12140
  if (propName.length <= prefix.length) continue;
@@ -12124,7 +12340,7 @@ const jsxNoNewFunctionAsProp = defineRule({
12124
12340
  if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
12125
12341
  context.report({
12126
12342
  node,
12127
- message: MESSAGE$37
12343
+ message: MESSAGE$43
12128
12344
  });
12129
12345
  }
12130
12346
  };
@@ -12344,7 +12560,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
12344
12560
  ];
12345
12561
  //#endregion
12346
12562
  //#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
12347
- const MESSAGE$36 = "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.";
12348
12564
  const isConfigObjectPropName = (propName) => {
12349
12565
  if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
12350
12566
  for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -12432,7 +12648,7 @@ const jsxNoNewObjectAsProp = defineRule({
12432
12648
  if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
12433
12649
  context.report({
12434
12650
  node,
12435
- message: MESSAGE$36
12651
+ message: MESSAGE$42
12436
12652
  });
12437
12653
  }
12438
12654
  };
@@ -12440,7 +12656,7 @@ const jsxNoNewObjectAsProp = defineRule({
12440
12656
  });
12441
12657
  //#endregion
12442
12658
  //#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
12443
- const MESSAGE$35 = "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.";
12444
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;
12445
12661
  const resolveSettings$28 = (settings) => {
12446
12662
  const reactDoctor = settings?.["react-doctor"];
@@ -12481,7 +12697,7 @@ const jsxNoScriptUrl = defineRule({
12481
12697
  if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
12482
12698
  if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
12483
12699
  node: attribute,
12484
- message: MESSAGE$35
12700
+ message: MESSAGE$41
12485
12701
  });
12486
12702
  }
12487
12703
  } };
@@ -12796,7 +13012,7 @@ const jsxPropsNoSpreadMulti = defineRule({
12796
13012
  });
12797
13013
  //#endregion
12798
13014
  //#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
12799
- const MESSAGE$34 = "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.";
12800
13016
  const resolveSettings$25 = (settings) => {
12801
13017
  const reactDoctor = settings?.["react-doctor"];
12802
13018
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
@@ -12837,7 +13053,7 @@ const jsxPropsNoSpreading = defineRule({
12837
13053
  }
12838
13054
  context.report({
12839
13055
  node: attribute,
12840
- message: MESSAGE$34
13056
+ message: MESSAGE$40
12841
13057
  });
12842
13058
  }
12843
13059
  } };
@@ -13065,7 +13281,7 @@ const labelHasAssociatedControl = defineRule({
13065
13281
  });
13066
13282
  //#endregion
13067
13283
  //#region src/plugin/rules/a11y/lang.ts
13068
- const MESSAGE$33 = "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`.";
13069
13285
  const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
13070
13286
  "aa",
13071
13287
  "ab",
@@ -13277,7 +13493,7 @@ const lang = defineRule({
13277
13493
  if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
13278
13494
  context.report({
13279
13495
  node: langAttr,
13280
- message: MESSAGE$33
13496
+ message: MESSAGE$39
13281
13497
  });
13282
13498
  return;
13283
13499
  }
@@ -13286,7 +13502,7 @@ const lang = defineRule({
13286
13502
  if (value === null) return;
13287
13503
  if (!isValidLangTag(value)) context.report({
13288
13504
  node: langAttr,
13289
- message: MESSAGE$33
13505
+ message: MESSAGE$39
13290
13506
  });
13291
13507
  } })
13292
13508
  });
@@ -13312,6 +13528,7 @@ const mcpToolCapabilityRisk = defineRule({
13312
13528
  shouldScan: (file) => isProductionSourcePath(file.relativePath),
13313
13529
  pattern: /\bserver\.\s*tool\s*\(|\bregisterTool\s*\(|\bsetRequestHandler\s*\(\s*CallToolRequestSchema/,
13314
13530
  requireAll: [/\bfrom\s+["']@modelcontextprotocol\/sdk[^"']*["']|\bMcpServer\b|\bMcpAgent\b/, AGENT_TOOL_DANGEROUS_CAPABILITY_PATTERN],
13531
+ ignoreStringLiterals: true,
13315
13532
  message: "An MCP tool/resource/prompt handler appears to expose file, shell, network, or code-execution capability."
13316
13533
  })
13317
13534
  });
@@ -13330,7 +13547,7 @@ const mdxSsrExecutionRisk = defineRule({
13330
13547
  });
13331
13548
  //#endregion
13332
13549
  //#region src/plugin/rules/a11y/media-has-caption.ts
13333
- const MESSAGE$32 = "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>`.";
13334
13551
  const DEFAULT_AUDIO = ["audio"];
13335
13552
  const DEFAULT_VIDEO = ["video"];
13336
13553
  const DEFAULT_TRACK = ["track"];
@@ -13371,7 +13588,7 @@ const mediaHasCaption = defineRule({
13371
13588
  if (!parent || !isNodeOfType(parent, "JSXElement")) {
13372
13589
  context.report({
13373
13590
  node: node.name,
13374
- message: MESSAGE$32
13591
+ message: MESSAGE$38
13375
13592
  });
13376
13593
  return;
13377
13594
  }
@@ -13388,7 +13605,7 @@ const mediaHasCaption = defineRule({
13388
13605
  return kindValue.value.toLowerCase() === "captions";
13389
13606
  })) context.report({
13390
13607
  node: node.name,
13391
- message: MESSAGE$32
13608
+ message: MESSAGE$38
13392
13609
  });
13393
13610
  } };
13394
13611
  }
@@ -15189,7 +15406,7 @@ const nextjsNoVercelOgImport = defineRule({
15189
15406
  });
15190
15407
  //#endregion
15191
15408
  //#region src/plugin/rules/a11y/no-access-key.ts
15192
- const MESSAGE$31 = "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.";
15193
15410
  const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
15194
15411
  const noAccessKey = defineRule({
15195
15412
  id: "no-access-key",
@@ -15206,7 +15423,7 @@ const noAccessKey = defineRule({
15206
15423
  if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
15207
15424
  context.report({
15208
15425
  node: accessKey,
15209
- message: MESSAGE$31
15426
+ message: MESSAGE$37
15210
15427
  });
15211
15428
  return;
15212
15429
  }
@@ -15216,7 +15433,7 @@ const noAccessKey = defineRule({
15216
15433
  if (isUndefinedIdentifier(expression)) return;
15217
15434
  context.report({
15218
15435
  node: accessKey,
15219
- message: MESSAGE$31
15436
+ message: MESSAGE$37
15220
15437
  });
15221
15438
  }
15222
15439
  } })
@@ -15699,7 +15916,7 @@ const noAdjustStateOnPropChange = defineRule({
15699
15916
  });
15700
15917
  //#endregion
15701
15918
  //#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
15702
- const MESSAGE$30 = "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.";
15703
15920
  const noAriaHiddenOnFocusable = defineRule({
15704
15921
  id: "no-aria-hidden-on-focusable",
15705
15922
  title: "aria-hidden on focusable element",
@@ -15726,7 +15943,7 @@ const noAriaHiddenOnFocusable = defineRule({
15726
15943
  const isImplicitlyFocusable = isInteractiveElement(tag, node);
15727
15944
  if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
15728
15945
  node: ariaHidden,
15729
- message: MESSAGE$30
15946
+ message: MESSAGE$36
15730
15947
  });
15731
15948
  } })
15732
15949
  });
@@ -16094,7 +16311,7 @@ const noArrayIndexAsKey = defineRule({
16094
16311
  });
16095
16312
  //#endregion
16096
16313
  //#region src/plugin/rules/react-builtins/no-array-index-key.ts
16097
- const MESSAGE$29 = "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.";
16098
16315
  const SECOND_INDEX_METHODS = new Set([
16099
16316
  "every",
16100
16317
  "filter",
@@ -16298,7 +16515,7 @@ const noArrayIndexKey = defineRule({
16298
16515
  }
16299
16516
  context.report({
16300
16517
  node: keyAttribute,
16301
- message: MESSAGE$29
16518
+ message: MESSAGE$35
16302
16519
  });
16303
16520
  },
16304
16521
  CallExpression(node) {
@@ -16318,15 +16535,35 @@ const noArrayIndexKey = defineRule({
16318
16535
  if (propName !== "key") continue;
16319
16536
  if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
16320
16537
  node: property,
16321
- message: MESSAGE$29
16538
+ message: MESSAGE$35
16322
16539
  });
16323
16540
  }
16324
16541
  }
16325
16542
  })
16326
16543
  });
16327
16544
  //#endregion
16545
+ //#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
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.";
16547
+ const noAsyncEffectCallback = defineRule({
16548
+ id: "no-async-effect-callback",
16549
+ title: "Async effect callback",
16550
+ severity: "warn",
16551
+ recommendation: "Don't make the effect callback `async`. Define an async function inside the effect and call it, then return a real cleanup function if you need one.",
16552
+ create: (context) => ({ CallExpression(node) {
16553
+ if (!isHookCall$1(node, EFFECT_HOOK_NAMES$1)) return;
16554
+ const callback = getEffectCallback(node);
16555
+ if (!callback) return;
16556
+ if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
16557
+ if (!callback.async) return;
16558
+ context.report({
16559
+ node: callback,
16560
+ message: MESSAGE$34
16561
+ });
16562
+ } })
16563
+ });
16564
+ //#endregion
16328
16565
  //#region src/plugin/rules/a11y/no-autofocus.ts
16329
- const MESSAGE$28 = "`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.";
16330
16567
  const resolveSettings$21 = (settings) => {
16331
16568
  const reactDoctor = settings?.["react-doctor"];
16332
16569
  return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
@@ -16382,7 +16619,7 @@ const noAutofocus = defineRule({
16382
16619
  }
16383
16620
  context.report({
16384
16621
  node: autoFocusAttribute,
16385
- message: MESSAGE$28
16622
+ message: MESSAGE$33
16386
16623
  });
16387
16624
  } };
16388
16625
  }
@@ -16632,6 +16869,109 @@ const noBarrelImport = defineRule({
16632
16869
  }
16633
16870
  });
16634
16871
  //#endregion
16872
+ //#region src/plugin/utils/function-contains-react-render-output.ts
16873
+ const NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES = new Set([
16874
+ "FunctionDeclaration",
16875
+ "FunctionExpression",
16876
+ "ArrowFunctionExpression",
16877
+ "ClassDeclaration",
16878
+ "ClassExpression"
16879
+ ]);
16880
+ const isReactImport$1 = (symbol) => {
16881
+ let importDeclaration = symbol.declarationNode?.parent;
16882
+ while (importDeclaration && !isNodeOfType(importDeclaration, "ImportDeclaration")) importDeclaration = importDeclaration.parent ?? null;
16883
+ if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration")) return false;
16884
+ return importDeclaration.source.value === "react";
16885
+ };
16886
+ const getImportedName = (symbol) => {
16887
+ if (symbol.kind !== "import") return null;
16888
+ if (!isReactImport$1(symbol)) return null;
16889
+ return getImportedName$1(symbol.declarationNode) ?? null;
16890
+ };
16891
+ const isReactNamespaceImport = (symbol) => {
16892
+ if (symbol.kind !== "import") return false;
16893
+ if (!isReactImport$1(symbol)) return false;
16894
+ return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier");
16895
+ };
16896
+ const isReactCreateElementIdentifierCall = (callee, scopes) => {
16897
+ if (!isNodeOfType(callee, "Identifier")) return false;
16898
+ const symbol = scopes.symbolFor(callee);
16899
+ return Boolean(symbol && getImportedName(symbol) === "createElement");
16900
+ };
16901
+ const isReactCreateElementMemberCall = (callee, scopes) => {
16902
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
16903
+ if (callee.computed) return false;
16904
+ if (!isNodeOfType(callee.object, "Identifier")) return false;
16905
+ if (!isNodeOfType(callee.property, "Identifier")) return false;
16906
+ if (callee.property.name !== "createElement") return false;
16907
+ const symbol = scopes.symbolFor(callee.object);
16908
+ return Boolean(symbol && isReactNamespaceImport(symbol));
16909
+ };
16910
+ const isReactCreateElementCall = (node, scopes) => {
16911
+ if (!isNodeOfType(node, "CallExpression")) return false;
16912
+ return isReactCreateElementIdentifierCall(node.callee, scopes) || isReactCreateElementMemberCall(node.callee, scopes);
16913
+ };
16914
+ const containsRenderOutput = (node, rootNode, scopes) => {
16915
+ if (node !== rootNode && NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type)) return false;
16916
+ if (node.type === "JSXElement" || node.type === "JSXFragment") return true;
16917
+ if (isReactCreateElementCall(node, scopes)) return true;
16918
+ const nodeRecord = node;
16919
+ for (const key of Object.keys(nodeRecord)) {
16920
+ if (key === "parent") continue;
16921
+ const child = nodeRecord[key];
16922
+ if (Array.isArray(child)) {
16923
+ for (const innerChild of child) if (isAstNode(innerChild) && containsRenderOutput(innerChild, rootNode, scopes)) return true;
16924
+ } else if (isAstNode(child) && containsRenderOutput(child, rootNode, scopes)) return true;
16925
+ }
16926
+ return false;
16927
+ };
16928
+ const functionContainsReactRenderOutput = (functionNode, scopes) => containsRenderOutput(functionNode, functionNode, scopes);
16929
+ //#endregion
16930
+ //#region src/plugin/utils/is-component-declaration.ts
16931
+ const isComponentDeclaration = (node) => isNodeOfType(node, "FunctionDeclaration") && node.id !== null && Boolean(node.id?.name) && isUppercaseName(node.id.name);
16932
+ //#endregion
16933
+ //#region src/plugin/rules/react-builtins/no-call-component-as-function.ts
16934
+ const message = (name) => `\`${name}\` is a component, so calling it as a plain function (\`${name}(...)\`) runs it outside React: its hooks break, it gets no fiber/state, and memoization is lost. Render it as \`<${name} />\` instead.`;
16935
+ const symbolIsLocalComponent = (symbol, context) => {
16936
+ const declaration = symbol.declarationNode;
16937
+ if (isComponentDeclaration(declaration)) return functionContainsReactRenderOutput(declaration, context.scopes);
16938
+ if (isComponentAssignment(declaration) && symbol.initializer) return functionContainsReactRenderOutput(symbol.initializer, context.scopes);
16939
+ return false;
16940
+ };
16941
+ const noCallComponentAsFunction = defineRule({
16942
+ id: "no-call-component-as-function",
16943
+ title: "Component called as a function",
16944
+ severity: "warn",
16945
+ tags: ["test-noise"],
16946
+ recommendation: "Render components as JSX (`<Component />`), never call them like functions (`Component(props)`). A direct call runs the component outside React and breaks hooks, state, and memoization.",
16947
+ create: (context) => {
16948
+ const renderedJsxNames = /* @__PURE__ */ new Set();
16949
+ const candidateCalls = [];
16950
+ return {
16951
+ JSXOpeningElement(node) {
16952
+ if (isNodeOfType(node.name, "JSXIdentifier") && isUppercaseName(node.name.name)) renderedJsxNames.add(node.name.name);
16953
+ },
16954
+ CallExpression(node) {
16955
+ if (isNodeOfType(node.callee, "Identifier") && isUppercaseName(node.callee.name)) candidateCalls.push({
16956
+ node,
16957
+ callee: node.callee,
16958
+ name: node.callee.name
16959
+ });
16960
+ },
16961
+ "Program:exit"() {
16962
+ for (const candidate of candidateCalls) {
16963
+ const symbol = context.scopes.symbolFor(candidate.callee);
16964
+ if (!symbol) continue;
16965
+ if (symbolIsLocalComponent(symbol, context) || symbol.kind === "import" && renderedJsxNames.has(candidate.name)) context.report({
16966
+ node: candidate.node,
16967
+ message: message(candidate.name)
16968
+ });
16969
+ }
16970
+ }
16971
+ };
16972
+ }
16973
+ });
16974
+ //#endregion
16635
16975
  //#region src/plugin/utils/is-setter-identifier.ts
16636
16976
  const isSetterIdentifier = (name) => SETTER_PATTERN.test(name);
16637
16977
  //#endregion
@@ -16783,7 +17123,7 @@ const noChainStateUpdates = defineRule({
16783
17123
  });
16784
17124
  //#endregion
16785
17125
  //#region src/plugin/rules/react-builtins/no-children-prop.ts
16786
- const MESSAGE$27 = "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.";
16787
17127
  const noChildrenProp = defineRule({
16788
17128
  id: "no-children-prop",
16789
17129
  title: "Children passed as a prop",
@@ -16795,7 +17135,7 @@ const noChildrenProp = defineRule({
16795
17135
  if (node.name.name !== "children") return;
16796
17136
  context.report({
16797
17137
  node: node.name,
16798
- message: MESSAGE$27
17138
+ message: MESSAGE$32
16799
17139
  });
16800
17140
  },
16801
17141
  CallExpression(node) {
@@ -16808,7 +17148,7 @@ const noChildrenProp = defineRule({
16808
17148
  const propertyKey = property.key;
16809
17149
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
16810
17150
  node: propertyKey,
16811
- message: MESSAGE$27
17151
+ message: MESSAGE$32
16812
17152
  });
16813
17153
  }
16814
17154
  }
@@ -16816,7 +17156,7 @@ const noChildrenProp = defineRule({
16816
17156
  });
16817
17157
  //#endregion
16818
17158
  //#region src/plugin/rules/react-builtins/no-clone-element.ts
16819
- const MESSAGE$26 = "`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.";
16820
17160
  const noCloneElement = defineRule({
16821
17161
  id: "no-clone-element",
16822
17162
  title: "cloneElement makes child props fragile",
@@ -16829,7 +17169,7 @@ const noCloneElement = defineRule({
16829
17169
  if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
16830
17170
  if (isImportedFromModule(node, "cloneElement", "react")) context.report({
16831
17171
  node: callee,
16832
- message: MESSAGE$26
17172
+ message: MESSAGE$31
16833
17173
  });
16834
17174
  return;
16835
17175
  }
@@ -16842,7 +17182,7 @@ const noCloneElement = defineRule({
16842
17182
  if (!isImportedFromModule(node, callee.object.name, "react")) return;
16843
17183
  context.report({
16844
17184
  node: callee,
16845
- message: MESSAGE$26
17185
+ message: MESSAGE$31
16846
17186
  });
16847
17187
  }
16848
17188
  } })
@@ -16891,7 +17231,7 @@ const enclosingComponentOrHookName = (node) => {
16891
17231
  };
16892
17232
  //#endregion
16893
17233
  //#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
16894
- const MESSAGE$25 = "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.";
16895
17235
  const CONTEXT_MODULES = [
16896
17236
  "react",
16897
17237
  "use-context-selector",
@@ -16927,7 +17267,32 @@ const noCreateContextInRender = defineRule({
16927
17267
  if (!componentOrHookName) return;
16928
17268
  context.report({
16929
17269
  node,
16930
- message: `${MESSAGE$25} (called inside "${componentOrHookName}")`
17270
+ message: `${MESSAGE$30} (called inside "${componentOrHookName}")`
17271
+ });
17272
+ } })
17273
+ });
17274
+ //#endregion
17275
+ //#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
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.";
17277
+ const noCreateRefInFunctionComponent = defineRule({
17278
+ id: "no-create-ref-in-function-component",
17279
+ title: "createRef in function component",
17280
+ severity: "warn",
17281
+ recommendation: "Replace `createRef()` with the `useRef()` hook inside function components and hooks. `createRef` is only for class components.",
17282
+ create: (context) => ({ CallExpression(node) {
17283
+ if (!isReactFunctionCall(node, "createRef")) return;
17284
+ if (isNodeOfType(node.callee, "Identifier")) {
17285
+ const symbol = context.scopes.symbolFor(node.callee);
17286
+ if (symbol && symbol.kind !== "import") return;
17287
+ }
17288
+ const enclosingFunction = nearestEnclosingFunction(node);
17289
+ if (!enclosingFunction) return;
17290
+ const displayName = componentOrHookDisplayNameForFunction(enclosingFunction);
17291
+ if (!displayName) return;
17292
+ if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes))) return;
17293
+ context.report({
17294
+ node,
17295
+ message: MESSAGE$29
16931
17296
  });
16932
17297
  } })
16933
17298
  });
@@ -17067,12 +17432,13 @@ const noCreateStoreInRender = defineRule({
17067
17432
  });
17068
17433
  //#endregion
17069
17434
  //#region src/plugin/rules/react-builtins/no-danger.ts
17070
- const MESSAGE$24 = "`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.";
17071
17436
  const noDanger = defineRule({
17072
17437
  id: "no-danger",
17073
17438
  title: "Raw HTML injection can run unsafe markup",
17074
17439
  severity: "warn",
17075
17440
  category: "Security",
17441
+ defaultEnabled: false,
17076
17442
  recommendation: "Render trusted content as React children so attacker-controlled HTML cannot run in users' browsers.",
17077
17443
  create: (context) => ({
17078
17444
  JSXOpeningElement(node) {
@@ -17080,7 +17446,7 @@ const noDanger = defineRule({
17080
17446
  if (!propAttribute) return;
17081
17447
  context.report({
17082
17448
  node: propAttribute.name,
17083
- message: MESSAGE$24
17449
+ message: MESSAGE$28
17084
17450
  });
17085
17451
  },
17086
17452
  CallExpression(node) {
@@ -17092,7 +17458,7 @@ const noDanger = defineRule({
17092
17458
  const propertyKey = property.key;
17093
17459
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
17094
17460
  node: propertyKey,
17095
- message: MESSAGE$24
17461
+ message: MESSAGE$28
17096
17462
  });
17097
17463
  }
17098
17464
  }
@@ -17100,7 +17466,7 @@ const noDanger = defineRule({
17100
17466
  });
17101
17467
  //#endregion
17102
17468
  //#region src/plugin/rules/react-builtins/no-danger-with-children.ts
17103
- const MESSAGE$23 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
17469
+ const MESSAGE$27 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
17104
17470
  const isLineBreak = (child) => {
17105
17471
  if (!isNodeOfType(child, "JSXText")) return false;
17106
17472
  return child.value.trim().length === 0 && child.value.includes("\n");
@@ -17170,7 +17536,7 @@ const noDangerWithChildren = defineRule({
17170
17536
  if (!hasChildrenProp && !hasNestedChildren) return;
17171
17537
  if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
17172
17538
  node: opening,
17173
- message: MESSAGE$23
17539
+ message: MESSAGE$27
17174
17540
  });
17175
17541
  },
17176
17542
  CallExpression(node) {
@@ -17182,7 +17548,7 @@ const noDangerWithChildren = defineRule({
17182
17548
  if (!propsShape.hasDangerously) return;
17183
17549
  if (node.arguments.length >= 3 || propsShape.hasChildren) context.report({
17184
17550
  node,
17185
- message: MESSAGE$23
17551
+ message: MESSAGE$27
17186
17552
  });
17187
17553
  }
17188
17554
  })
@@ -17759,7 +18125,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
17759
18125
  //#endregion
17760
18126
  //#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
17761
18127
  const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
17762
- const MESSAGE$22 = "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`.";
17763
18129
  const resolveSettings$20 = (settings) => {
17764
18130
  const reactDoctor = settings?.["react-doctor"];
17765
18131
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidMountSetState ?? {} : {}).mode ?? "allowed" };
@@ -17778,7 +18144,7 @@ const noDidMountSetState = defineRule({
17778
18144
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$2, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
17779
18145
  context.report({
17780
18146
  node: node.callee,
17781
- message: MESSAGE$22
18147
+ message: MESSAGE$26
17782
18148
  });
17783
18149
  } };
17784
18150
  }
@@ -17786,7 +18152,7 @@ const noDidMountSetState = defineRule({
17786
18152
  //#endregion
17787
18153
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
17788
18154
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
17789
- const MESSAGE$21 = "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.";
17790
18156
  const resolveSettings$19 = (settings) => {
17791
18157
  const reactDoctor = settings?.["react-doctor"];
17792
18158
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noDidUpdateSetState ?? {} : {}).mode ?? "allowed" };
@@ -17805,7 +18171,7 @@ const noDidUpdateSetState = defineRule({
17805
18171
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
17806
18172
  context.report({
17807
18173
  node: node.callee,
17808
- message: MESSAGE$21
18174
+ message: MESSAGE$25
17809
18175
  });
17810
18176
  } };
17811
18177
  }
@@ -17828,7 +18194,7 @@ const isStateMemberExpression = (node) => {
17828
18194
  };
17829
18195
  //#endregion
17830
18196
  //#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
17831
- const MESSAGE$20 = "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.";
17832
18198
  const shouldIgnoreMutation = (node) => {
17833
18199
  let isConstructor = false;
17834
18200
  let isInsideCallExpression = false;
@@ -17850,7 +18216,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
17850
18216
  if (shouldIgnoreMutation(reportNode)) return;
17851
18217
  context.report({
17852
18218
  node: reportNode,
17853
- message: MESSAGE$20
18219
+ message: MESSAGE$24
17854
18220
  });
17855
18221
  };
17856
18222
  const noDirectMutationState = defineRule({
@@ -18060,6 +18426,26 @@ const noDocumentStartViewTransition = defineRule({
18060
18426
  } })
18061
18427
  });
18062
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
18063
18449
  //#region src/plugin/rules/bundle-size/no-dynamic-import-path.ts
18064
18450
  const noDynamicImportPath = defineRule({
18065
18451
  id: "no-dynamic-import-path",
@@ -19438,7 +19824,7 @@ const ALLOWED_NAMESPACES = new Set([
19438
19824
  "ReactDOM",
19439
19825
  "ReactDom"
19440
19826
  ]);
19441
- const MESSAGE$19 = "`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.";
19442
19828
  const noFindDomNode = defineRule({
19443
19829
  id: "no-find-dom-node",
19444
19830
  title: "findDOMNode breaks component encapsulation",
@@ -19449,7 +19835,7 @@ const noFindDomNode = defineRule({
19449
19835
  if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
19450
19836
  context.report({
19451
19837
  node: callee,
19452
- message: MESSAGE$19
19838
+ message: MESSAGE$22
19453
19839
  });
19454
19840
  return;
19455
19841
  }
@@ -19460,7 +19846,7 @@ const noFindDomNode = defineRule({
19460
19846
  if (callee.property.name !== "findDOMNode") return;
19461
19847
  context.report({
19462
19848
  node: callee.property,
19463
- message: MESSAGE$19
19849
+ message: MESSAGE$22
19464
19850
  });
19465
19851
  }
19466
19852
  } })
@@ -19523,64 +19909,6 @@ const noGenericHandlerNames = defineRule({
19523
19909
  } })
19524
19910
  });
19525
19911
  //#endregion
19526
- //#region src/plugin/utils/function-contains-react-render-output.ts
19527
- const NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES = new Set([
19528
- "FunctionDeclaration",
19529
- "FunctionExpression",
19530
- "ArrowFunctionExpression",
19531
- "ClassDeclaration",
19532
- "ClassExpression"
19533
- ]);
19534
- const isReactImport$1 = (symbol) => {
19535
- let importDeclaration = symbol.declarationNode?.parent;
19536
- while (importDeclaration && !isNodeOfType(importDeclaration, "ImportDeclaration")) importDeclaration = importDeclaration.parent ?? null;
19537
- if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration")) return false;
19538
- return importDeclaration.source.value === "react";
19539
- };
19540
- const getImportedName = (symbol) => {
19541
- if (symbol.kind !== "import") return null;
19542
- if (!isReactImport$1(symbol)) return null;
19543
- return getImportedName$1(symbol.declarationNode) ?? null;
19544
- };
19545
- const isReactNamespaceImport = (symbol) => {
19546
- if (symbol.kind !== "import") return false;
19547
- if (!isReactImport$1(symbol)) return false;
19548
- return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier");
19549
- };
19550
- const isReactCreateElementIdentifierCall = (callee, scopes) => {
19551
- if (!isNodeOfType(callee, "Identifier")) return false;
19552
- const symbol = scopes.symbolFor(callee);
19553
- return Boolean(symbol && getImportedName(symbol) === "createElement");
19554
- };
19555
- const isReactCreateElementMemberCall = (callee, scopes) => {
19556
- if (!isNodeOfType(callee, "MemberExpression")) return false;
19557
- if (callee.computed) return false;
19558
- if (!isNodeOfType(callee.object, "Identifier")) return false;
19559
- if (!isNodeOfType(callee.property, "Identifier")) return false;
19560
- if (callee.property.name !== "createElement") return false;
19561
- const symbol = scopes.symbolFor(callee.object);
19562
- return Boolean(symbol && isReactNamespaceImport(symbol));
19563
- };
19564
- const isReactCreateElementCall = (node, scopes) => {
19565
- if (!isNodeOfType(node, "CallExpression")) return false;
19566
- return isReactCreateElementIdentifierCall(node.callee, scopes) || isReactCreateElementMemberCall(node.callee, scopes);
19567
- };
19568
- const containsRenderOutput = (node, rootNode, scopes) => {
19569
- if (node !== rootNode && NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type)) return false;
19570
- if (node.type === "JSXElement" || node.type === "JSXFragment") return true;
19571
- if (isReactCreateElementCall(node, scopes)) return true;
19572
- const nodeRecord = node;
19573
- for (const key of Object.keys(nodeRecord)) {
19574
- if (key === "parent") continue;
19575
- const child = nodeRecord[key];
19576
- if (Array.isArray(child)) {
19577
- for (const innerChild of child) if (isAstNode(innerChild) && containsRenderOutput(innerChild, rootNode, scopes)) return true;
19578
- } else if (isAstNode(child) && containsRenderOutput(child, rootNode, scopes)) return true;
19579
- }
19580
- return false;
19581
- };
19582
- const functionContainsReactRenderOutput = (functionNode, scopes) => containsRenderOutput(functionNode, functionNode, scopes);
19583
- //#endregion
19584
19912
  //#region src/plugin/rules/architecture/no-giant-component.ts
19585
19913
  const noGiantComponent = defineRule({
19586
19914
  id: "no-giant-component",
@@ -19759,6 +20087,26 @@ const noGrayOnColoredBackground = defineRule({
19759
20087
  } })
19760
20088
  });
19761
20089
  //#endregion
20090
+ //#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
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.";
20092
+ const noImgLazyWithHighFetchpriority = defineRule({
20093
+ id: "no-img-lazy-with-high-fetchpriority",
20094
+ title: "Lazy image with high fetchPriority",
20095
+ severity: "warn",
20096
+ recommendation: "Don't combine `loading=\"lazy\"` with `fetchPriority=\"high\"`. A high-priority image (usually the LCP) should load eagerly; a lazy image is by definition not high priority.",
20097
+ create: (context) => ({ JSXOpeningElement(node) {
20098
+ if (!isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "img") return;
20099
+ const loadingAttribute = hasJsxPropIgnoreCase(node.attributes, "loading");
20100
+ if (!loadingAttribute || getJsxPropStringValue(loadingAttribute)?.toLowerCase() !== "lazy") return;
20101
+ const fetchPriorityAttribute = hasJsxPropIgnoreCase(node.attributes, "fetchPriority");
20102
+ if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
20103
+ context.report({
20104
+ node: node.name,
20105
+ message: MESSAGE$21
20106
+ });
20107
+ } })
20108
+ });
20109
+ //#endregion
19762
20110
  //#region src/plugin/rules/state-and-effects/no-initialize-state.ts
19763
20111
  const noInitializeState = defineRule({
19764
20112
  id: "no-initialize-state",
@@ -19851,15 +20199,20 @@ const noInlineExhaustiveStyle = defineRule({
19851
20199
  severity: "warn",
19852
20200
  tags: ["test-noise", "react-jsx-only"],
19853
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.",
19854
- create: (context) => ({ JSXAttribute(node) {
19855
- const expression = getInlineStyleExpression(node);
19856
- if (!expression) return;
19857
- const propertyCount = expression.properties?.filter((property) => isNodeOfType(property, "Property")).length ?? 0;
19858
- if (propertyCount >= 8) context.report({
19859
- node: expression,
19860
- 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.`
19861
- });
19862
- } })
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
+ }
19863
20216
  });
19864
20217
  //#endregion
19865
20218
  //#region src/plugin/rules/performance/no-inline-prop-on-memo-component.ts
@@ -19988,8 +20341,31 @@ const noIsMounted = defineRule({
19988
20341
  } })
19989
20342
  });
19990
20343
  //#endregion
20344
+ //#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
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)`.";
20346
+ const isJsonMethodCall = (node, method) => {
20347
+ if (!isNodeOfType(node, "CallExpression")) return false;
20348
+ const callee = node.callee;
20349
+ return isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier") && callee.object.name === "JSON" && isNodeOfType(callee.property, "Identifier") && callee.property.name === method;
20350
+ };
20351
+ const noJsonParseStringifyClone = defineRule({
20352
+ id: "no-json-parse-stringify-clone",
20353
+ title: "JSON parse/stringify deep clone",
20354
+ severity: "warn",
20355
+ recommendation: "Replace `JSON.parse(JSON.stringify(value))` with `structuredClone(value)`. It is faster and preserves Dates, Maps, Sets, and cyclic references.",
20356
+ create: (context) => ({ CallExpression(node) {
20357
+ if (!isJsonMethodCall(node, "parse")) return;
20358
+ const firstArgument = node.arguments?.[0];
20359
+ if (!firstArgument || !isJsonMethodCall(firstArgument, "stringify")) return;
20360
+ context.report({
20361
+ node,
20362
+ message: MESSAGE$20
20363
+ });
20364
+ } })
20365
+ });
20366
+ //#endregion
19991
20367
  //#region src/plugin/rules/correctness/no-jsx-element-type.ts
19992
- 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.";
19993
20369
  const isJsxElementTypeReference = (node) => {
19994
20370
  if (!isNodeOfType(node, "TSTypeReference")) return false;
19995
20371
  const typeName = node.typeName;
@@ -20006,7 +20382,7 @@ const checkReturnType = (context, returnType) => {
20006
20382
  if (!typeAnnotation) return;
20007
20383
  if (isJsxElementTypeReference(typeAnnotation)) context.report({
20008
20384
  node: typeAnnotation,
20009
- message: MESSAGE$18
20385
+ message: MESSAGE$19
20010
20386
  });
20011
20387
  };
20012
20388
  const noJsxElementType = defineRule({
@@ -20310,9 +20686,6 @@ const noLongTransitionDuration = defineRule({
20310
20686
  const BOOLEAN_PROP_PREFIX_PATTERN = /^(?:is|has|should|can|show|hide|enable|disable|with)[A-Z]/;
20311
20687
  const isBooleanPrefixedPropName = (propName) => BOOLEAN_PROP_PREFIX_PATTERN.test(propName);
20312
20688
  //#endregion
20313
- //#region src/plugin/utils/is-component-declaration.ts
20314
- const isComponentDeclaration = (node) => isNodeOfType(node, "FunctionDeclaration") && node.id !== null && Boolean(node.id?.name) && isUppercaseName(node.id.name);
20315
- //#endregion
20316
20689
  //#region src/plugin/rules/architecture/no-many-boolean-props.ts
20317
20690
  const collectBooleanLikePropsFromBody = (componentBody, propsParamName) => {
20318
20691
  const found = /* @__PURE__ */ new Set();
@@ -20464,7 +20837,7 @@ const noMoment = defineRule({
20464
20837
  });
20465
20838
  //#endregion
20466
20839
  //#region src/plugin/rules/react-builtins/no-multi-comp.ts
20467
- 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.";
20468
20841
  const resolveSettings$16 = (settings) => {
20469
20842
  const reactDoctor = settings?.["react-doctor"];
20470
20843
  return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
@@ -20786,7 +21159,7 @@ const noMultiComp = defineRule({
20786
21159
  if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
20787
21160
  for (const component of flagged.slice(1)) context.report({
20788
21161
  node: component.reportNode,
20789
- message: MESSAGE$17
21162
+ message: MESSAGE$18
20790
21163
  });
20791
21164
  } };
20792
21165
  }
@@ -20954,7 +21327,7 @@ const resolveReducerFunction = (node, currentFilename) => {
20954
21327
  };
20955
21328
  //#endregion
20956
21329
  //#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
20957
- 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.";
20958
21331
  const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
20959
21332
  "copyWithin",
20960
21333
  "fill",
@@ -21164,7 +21537,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
21164
21537
  reportedNodes.add(options.crossFileConsumerCallSite);
21165
21538
  context.report({
21166
21539
  node: options.crossFileConsumerCallSite,
21167
- message: `${MESSAGE$16} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
21540
+ message: `${MESSAGE$17} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
21168
21541
  });
21169
21542
  return;
21170
21543
  }
@@ -21173,7 +21546,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
21173
21546
  reportedNodes.add(mutation.node);
21174
21547
  context.report({
21175
21548
  node: mutation.node,
21176
- message: MESSAGE$16
21549
+ message: MESSAGE$17
21177
21550
  });
21178
21551
  }
21179
21552
  };
@@ -21445,7 +21818,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
21445
21818
  });
21446
21819
  //#endregion
21447
21820
  //#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
21448
- 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.";
21449
21822
  const resolveSettings$14 = (settings) => {
21450
21823
  const reactDoctor = settings?.["react-doctor"];
21451
21824
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noNoninteractiveTabindex ?? {} : {};
@@ -21473,7 +21846,7 @@ const noNoninteractiveTabindex = defineRule({
21473
21846
  if (numeric === null) {
21474
21847
  if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues) context.report({
21475
21848
  node: tabIndex,
21476
- message: MESSAGE$15
21849
+ message: MESSAGE$16
21477
21850
  });
21478
21851
  return;
21479
21852
  }
@@ -21486,7 +21859,7 @@ const noNoninteractiveTabindex = defineRule({
21486
21859
  if (!roleAttribute) {
21487
21860
  context.report({
21488
21861
  node: tabIndex,
21489
- message: MESSAGE$15
21862
+ message: MESSAGE$16
21490
21863
  });
21491
21864
  return;
21492
21865
  }
@@ -21500,7 +21873,7 @@ const noNoninteractiveTabindex = defineRule({
21500
21873
  }
21501
21874
  context.report({
21502
21875
  node: tabIndex,
21503
- message: MESSAGE$15
21876
+ message: MESSAGE$16
21504
21877
  });
21505
21878
  } };
21506
21879
  }
@@ -22191,7 +22564,7 @@ const noRandomKey = defineRule({
22191
22564
  });
22192
22565
  //#endregion
22193
22566
  //#region src/plugin/rules/react-builtins/no-react-children.ts
22194
- 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.";
22195
22568
  const isChildrenIdentifier = (node, contextNode) => {
22196
22569
  if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
22197
22570
  return isImportedFromModule(contextNode, "Children", "react");
@@ -22217,13 +22590,13 @@ const noReactChildren = defineRule({
22217
22590
  if (isChildrenIdentifier(memberObject, node)) {
22218
22591
  context.report({
22219
22592
  node: calleeOuter,
22220
- message: MESSAGE$14
22593
+ message: MESSAGE$15
22221
22594
  });
22222
22595
  return;
22223
22596
  }
22224
22597
  if (isReactNamespaceMember(memberObject, node)) context.report({
22225
22598
  node: calleeOuter,
22226
- message: MESSAGE$14
22599
+ message: MESSAGE$15
22227
22600
  });
22228
22601
  } })
22229
22602
  });
@@ -22546,7 +22919,7 @@ const noRenderPropChildren = defineRule({
22546
22919
  });
22547
22920
  //#endregion
22548
22921
  //#region src/plugin/rules/react-builtins/no-render-return-value.ts
22549
- 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.";
22550
22923
  const isReactDomRenderCall = (node) => {
22551
22924
  if (!isNodeOfType(node.callee, "MemberExpression")) return false;
22552
22925
  if (!isNodeOfType(node.callee.object, "Identifier")) return false;
@@ -22570,7 +22943,7 @@ const noRenderReturnValue = defineRule({
22570
22943
  if (!isUsedAsReturnValue(node.parent)) return;
22571
22944
  context.report({
22572
22945
  node: node.callee,
22573
- message: MESSAGE$13
22946
+ message: MESSAGE$14
22574
22947
  });
22575
22948
  } })
22576
22949
  });
@@ -22730,11 +23103,17 @@ const classifySecretFileExposure = (filename, options = {}) => {
22730
23103
  return "unknown";
22731
23104
  };
22732
23105
  //#endregion
22733
- //#region src/plugin/utils/get-identifier-trailing-word.ts
22734
- const getIdentifierTrailingWord = (identifierName) => {
22735
- 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());
22736
23112
  };
22737
23113
  //#endregion
23114
+ //#region src/plugin/utils/get-identifier-trailing-word.ts
23115
+ const getIdentifierTrailingWord = (identifierName) => tokenizeIdentifierWords(identifierName).at(-1) ?? identifierName.toLowerCase();
23116
+ //#endregion
22738
23117
  //#region src/plugin/constants/tanstack.ts
22739
23118
  const TANSTACK_ROUTE_FILE_PATTERN = /\/routes\//;
22740
23119
  const TANSTACK_ROOT_ROUTE_FILE_PATTERN = /__root\.(tsx?|jsx?)$/;
@@ -23262,7 +23641,7 @@ const getParentComponent = (node) => {
23262
23641
  };
23263
23642
  //#endregion
23264
23643
  //#region src/plugin/rules/react-builtins/no-set-state.ts
23265
- 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.";
23266
23645
  const noSetState = defineRule({
23267
23646
  id: "no-set-state",
23268
23647
  title: "Local class state forbidden",
@@ -23277,7 +23656,7 @@ const noSetState = defineRule({
23277
23656
  if (!getParentComponent(node)) return;
23278
23657
  context.report({
23279
23658
  node: node.callee,
23280
- message: MESSAGE$12
23659
+ message: MESSAGE$13
23281
23660
  });
23282
23661
  } })
23283
23662
  });
@@ -23439,7 +23818,7 @@ const isAbstractRole = (openingElement, settings) => {
23439
23818
  };
23440
23819
  //#endregion
23441
23820
  //#region src/plugin/rules/a11y/no-static-element-interactions.ts
23442
- 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.";
23443
23822
  const DEFAULT_HANDLERS = [
23444
23823
  "onClick",
23445
23824
  "onMouseDown",
@@ -23499,7 +23878,7 @@ const noStaticElementInteractions = defineRule({
23499
23878
  if (!roleAttribute || !roleAttribute.value) {
23500
23879
  context.report({
23501
23880
  node: node.name,
23502
- message: MESSAGE$11
23881
+ message: MESSAGE$12
23503
23882
  });
23504
23883
  return;
23505
23884
  }
@@ -23509,19 +23888,66 @@ const noStaticElementInteractions = defineRule({
23509
23888
  if (firstRole && (isInteractiveRole(firstRole) || isNonInteractiveRole(firstRole))) return;
23510
23889
  context.report({
23511
23890
  node: node.name,
23512
- message: MESSAGE$11
23891
+ message: MESSAGE$12
23513
23892
  });
23514
23893
  return;
23515
23894
  }
23516
23895
  if (isNodeOfType(attributeValue, "JSXExpressionContainer") && settings.allowExpressionValues) return;
23517
23896
  context.report({
23518
23897
  node: node.name,
23519
- message: MESSAGE$11
23898
+ message: MESSAGE$12
23520
23899
  });
23521
23900
  } };
23522
23901
  }
23523
23902
  });
23524
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
23525
23951
  //#region src/plugin/rules/react-builtins/no-string-refs.ts
23526
23952
  const STRING_IN_REF_MESSAGE = "Your component can't reach this node because string refs don't work in modern React.";
23527
23953
  const THIS_REFS_MESSAGE = "Your component can't reach its nodes because `this.refs` is empty in modern React.";
@@ -23572,6 +23998,27 @@ const noStringRefs = defineRule({
23572
23998
  }
23573
23999
  });
23574
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
23575
24022
  //#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
23576
24023
  const MESSAGE$10 = "This value is `undefined` because function components have no `this`.";
23577
24024
  const isInsideClassMethod = (node, customClassFactoryNames) => {
@@ -24981,15 +25428,8 @@ const expressionContainsJsxOrCreateElement = (root) => {
24981
25428
  visit(root);
24982
25429
  return found;
24983
25430
  };
24984
- const classExtendsReactComponent$1 = (classNode) => {
24985
- const superClass = classNode.superClass;
24986
- if (!superClass) return false;
24987
- if (isNodeOfType(superClass, "Identifier") && (superClass.name === "Component" || superClass.name === "PureComponent")) return true;
24988
- 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;
24989
- return false;
24990
- };
24991
25431
  const isReactClassComponent = (classNode) => {
24992
- if (classExtendsReactComponent$1(classNode)) return true;
25432
+ if (isEs6Component(classNode)) return true;
24993
25433
  return expressionContainsJsxOrCreateElement(classNode);
24994
25434
  };
24995
25435
  const findEnclosingComponent = (node) => {
@@ -25149,7 +25589,7 @@ const noUnstableNestedComponents = defineRule({
25149
25589
  create: (context) => {
25150
25590
  const settings = resolveSettings$8(context.settings);
25151
25591
  const renderPropRegex = compileGlob(settings.propNamePattern);
25152
- const reportCandidate = (candidateNode, reportNode, candidateName) => {
25592
+ const reportCandidate = (candidateNode, reportNode) => {
25153
25593
  if (isFirstArgumentOfHocCall(candidateNode)) return;
25154
25594
  if (isReturnOfMapCallback(candidateNode)) return;
25155
25595
  const propInfo = isComponentDeclaredInProp(candidateNode);
@@ -25170,7 +25610,7 @@ const noUnstableNestedComponents = defineRule({
25170
25610
  const inferredName = inferFunctionLikeName(node);
25171
25611
  const propInfo = isComponentDeclaredInProp(node);
25172
25612
  if (!(inferredName !== null && isReactComponentName(inferredName) || propInfo !== null || isObjectCallbackCandidate(node))) return;
25173
- reportCandidate(node, node, inferredName);
25613
+ reportCandidate(node, node);
25174
25614
  };
25175
25615
  return {
25176
25616
  FunctionDeclaration: checkFunctionLike,
@@ -25180,18 +25620,18 @@ const noUnstableNestedComponents = defineRule({
25180
25620
  if (!node.id) return;
25181
25621
  if (!isReactComponentName(node.id.name)) return;
25182
25622
  if (!isReactClassComponent(node)) return;
25183
- reportCandidate(node, node, node.id.name);
25623
+ reportCandidate(node, node);
25184
25624
  },
25185
25625
  ClassExpression(node) {
25186
25626
  const inferredName = node.id?.name ?? inferFunctionLikeName(node);
25187
25627
  if (!inferredName || !isReactComponentName(inferredName)) return;
25188
25628
  if (!isReactClassComponent(node)) return;
25189
- reportCandidate(node, node, inferredName);
25629
+ reportCandidate(node, node);
25190
25630
  },
25191
25631
  CallExpression(node) {
25192
25632
  if (!isHocCallee$1(node)) return;
25193
25633
  if (!hocCallContainsComponent(node)) return;
25194
- reportCandidate(node, node, null);
25634
+ reportCandidate(node, node);
25195
25635
  }
25196
25636
  };
25197
25637
  }
@@ -25631,13 +26071,6 @@ const skipTsExpression = (expression) => {
25631
26071
  if (expression.type === "TSAsExpression" || expression.type === "TSSatisfiesExpression" || expression.type === "TSNonNullExpression") return skipTsExpression(expression.expression);
25632
26072
  return expression;
25633
26073
  };
25634
- const classExtendsReactComponent = (classNode) => {
25635
- const superClass = classNode.superClass;
25636
- if (!superClass) return false;
25637
- if (isNodeOfType(superClass, "Identifier") && (superClass.name === "Component" || superClass.name === "PureComponent")) return true;
25638
- 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;
25639
- return false;
25640
- };
25641
26074
  const isReactCreateContext = (initializer) => {
25642
26075
  if (!initializer) return false;
25643
26076
  const expression = skipTsExpression(initializer);
@@ -25828,7 +26261,7 @@ const onlyExportComponents = defineRule({
25828
26261
  if (stripped.id) {
25829
26262
  const idNode = stripped.id;
25830
26263
  isExportedNodeIds.add(stripped);
25831
- if (isReactComponentName(idNode.name) && classExtendsReactComponent(stripped)) hasReactExport = true;
26264
+ if (isReactComponentName(idNode.name) && isEs6Component(stripped)) hasReactExport = true;
25832
26265
  else exports.push({
25833
26266
  kind: "non-component",
25834
26267
  reportNode: idNode
@@ -25888,7 +26321,7 @@ const onlyExportComponents = defineRule({
25888
26321
  exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state));
25889
26322
  } else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
25890
26323
  isExportedNodeIds.add(declaration);
25891
- 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" });
25892
26325
  else exports.push({
25893
26326
  kind: "non-component",
25894
26327
  reportNode: declaration.id
@@ -34681,6 +35114,47 @@ const serverAfterNonblocking = defineRule({
34681
35114
  }
34682
35115
  });
34683
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
34684
35158
  //#region src/plugin/rules/server/server-auth-actions.ts
34685
35159
  const isAsyncFunctionLikeNode = (node) => {
34686
35160
  if (!node) return false;
@@ -34723,9 +35197,13 @@ const isMemberCallAuthRelated = (receiverNode, methodName, genericMethodNames) =
34723
35197
  const getAuthCallName = (callExpression, allowedFunctionNames, genericMethodNames) => {
34724
35198
  const calleeNode = unwrapTypeWrappedCallee(callExpression.callee);
34725
35199
  if (!calleeNode) return null;
34726
- 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
+ }
34727
35204
  if (isNodeOfType(calleeNode, "MemberExpression") && isNodeOfType(calleeNode.property, "Identifier")) {
34728
35205
  const methodName = calleeNode.property.name;
35206
+ if (isAuthGuardName(methodName)) return methodName;
34729
35207
  if (!allowedFunctionNames.has(methodName)) return null;
34730
35208
  if (!isMemberCallAuthRelated(calleeNode.object, methodName, genericMethodNames)) return null;
34731
35209
  return methodName;
@@ -34947,6 +35425,7 @@ const serverFetchWithoutRevalidate = defineRule({
34947
35425
  CallExpression(node) {
34948
35426
  if (!isServerSideFile) return;
34949
35427
  if (!isFetchCall(node)) return;
35428
+ if (isMutatingFetchCall(node)) return;
34950
35429
  const optionsArg = node.arguments?.[1];
34951
35430
  if (optionsArg && objectExpressionHasNextRevalidate(optionsArg)) return;
34952
35431
  const urlArg = node.arguments?.[0];
@@ -35102,13 +35581,7 @@ const serverNoMutableModuleState = defineRule({
35102
35581
  const collectDeclaredNames = (declaration) => {
35103
35582
  const names = /* @__PURE__ */ new Set();
35104
35583
  if (!isNodeOfType(declaration, "VariableDeclaration")) return names;
35105
- for (const declarator of declaration.declarations ?? []) if (isNodeOfType(declarator.id, "Identifier")) names.add(declarator.id.name);
35106
- else if (isNodeOfType(declarator.id, "ObjectPattern")) {
35107
- for (const property of declarator.id.properties ?? []) if (isNodeOfType(property, "Property") && isNodeOfType(property.value, "Identifier")) names.add(property.value.name);
35108
- else if (isNodeOfType(property, "RestElement") && isNodeOfType(property.argument, "Identifier")) names.add(property.argument.name);
35109
- } else if (isNodeOfType(declarator.id, "ArrayPattern")) {
35110
- for (const element of declarator.id.elements ?? []) if (isNodeOfType(element, "Identifier")) names.add(element.name);
35111
- }
35584
+ for (const declarator of declaration.declarations ?? []) collectPatternNames(declarator.id, names);
35112
35585
  return names;
35113
35586
  };
35114
35587
  const declarationStartsWithAwait = (declaration) => {
@@ -35118,11 +35591,15 @@ const declarationStartsWithAwait = (declaration) => {
35118
35591
  };
35119
35592
  const declarationReadsAnyName = (declaration, names) => {
35120
35593
  if (names.size === 0) return false;
35594
+ if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
35121
35595
  let didRead = false;
35122
- walkAst(declaration, (child) => {
35123
- if (didRead) return;
35124
- if (isNodeOfType(child, "Identifier") && names.has(child.name)) didRead = true;
35125
- });
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
+ }
35126
35603
  return didRead;
35127
35604
  };
35128
35605
  const serverSequentialIndependentAwait = defineRule({
@@ -36382,7 +36859,7 @@ const urlPrefilledPrivilegedAction = defineRule({
36382
36859
  recommendation: "Require server-side validation and explicit confirmation for URL-sourced invite, role, permission, redirect, or sharing parameters.",
36383
36860
  scan: scanByPattern({
36384
36861
  shouldScan: (file) => isClientSourcePath(file.relativePath),
36385
- 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,
36386
36863
  message: "Client code reads sensitive action state from the URL, which can pre-fill invites, roles, redirects, or sharing flows with attacker values."
36387
36864
  })
36388
36865
  });
@@ -37144,6 +37621,17 @@ const reactDoctorRules = [
37144
37621
  category: "Performance"
37145
37622
  }
37146
37623
  },
37624
+ {
37625
+ key: "react-doctor/auth-token-in-web-storage",
37626
+ id: "auth-token-in-web-storage",
37627
+ source: "react-doctor",
37628
+ originallyExternal: false,
37629
+ rule: {
37630
+ ...authTokenInWebStorage,
37631
+ framework: "global",
37632
+ category: "Security"
37633
+ }
37634
+ },
37147
37635
  {
37148
37636
  key: "react-doctor/autocomplete-valid",
37149
37637
  id: "autocomplete-valid",
@@ -37360,6 +37848,18 @@ const reactDoctorRules = [
37360
37848
  requires: [...new Set(["react", ...noVagueButtonLabel.requires ?? []])]
37361
37849
  }
37362
37850
  },
37851
+ {
37852
+ key: "react-doctor/dialog-has-accessible-name",
37853
+ id: "dialog-has-accessible-name",
37854
+ source: "react-doctor",
37855
+ originallyExternal: false,
37856
+ rule: {
37857
+ ...dialogHasAccessibleName,
37858
+ framework: "global",
37859
+ category: "Accessibility",
37860
+ requires: [...new Set(["react", ...dialogHasAccessibleName.requires ?? []])]
37861
+ }
37862
+ },
37363
37863
  {
37364
37864
  key: "react-doctor/display-name",
37365
37865
  id: "display-name",
@@ -38519,6 +39019,18 @@ const reactDoctorRules = [
38519
39019
  requires: [...new Set(["react", ...noArrayIndexKey.requires ?? []])]
38520
39020
  }
38521
39021
  },
39022
+ {
39023
+ key: "react-doctor/no-async-effect-callback",
39024
+ id: "no-async-effect-callback",
39025
+ source: "react-doctor",
39026
+ originallyExternal: false,
39027
+ rule: {
39028
+ ...noAsyncEffectCallback,
39029
+ framework: "global",
39030
+ category: "Bugs",
39031
+ requires: [...new Set(["react", ...noAsyncEffectCallback.requires ?? []])]
39032
+ }
39033
+ },
38522
39034
  {
38523
39035
  key: "react-doctor/no-autofocus",
38524
39036
  id: "no-autofocus",
@@ -38542,6 +39054,18 @@ const reactDoctorRules = [
38542
39054
  category: "Performance"
38543
39055
  }
38544
39056
  },
39057
+ {
39058
+ key: "react-doctor/no-call-component-as-function",
39059
+ id: "no-call-component-as-function",
39060
+ source: "react-doctor",
39061
+ originallyExternal: false,
39062
+ rule: {
39063
+ ...noCallComponentAsFunction,
39064
+ framework: "global",
39065
+ category: "Bugs",
39066
+ requires: [...new Set(["react", ...noCallComponentAsFunction.requires ?? []])]
39067
+ }
39068
+ },
38545
39069
  {
38546
39070
  key: "react-doctor/no-cascading-set-state",
38547
39071
  id: "no-cascading-set-state",
@@ -38602,6 +39126,18 @@ const reactDoctorRules = [
38602
39126
  requires: [...new Set(["react", ...noCreateContextInRender.requires ?? []])]
38603
39127
  }
38604
39128
  },
39129
+ {
39130
+ key: "react-doctor/no-create-ref-in-function-component",
39131
+ id: "no-create-ref-in-function-component",
39132
+ source: "react-doctor",
39133
+ originallyExternal: false,
39134
+ rule: {
39135
+ ...noCreateRefInFunctionComponent,
39136
+ framework: "global",
39137
+ category: "Bugs",
39138
+ requires: [...new Set(["react", ...noCreateRefInFunctionComponent.requires ?? []])]
39139
+ }
39140
+ },
38605
39141
  {
38606
39142
  key: "react-doctor/no-create-store-in-render",
38607
39143
  id: "no-create-store-in-render",
@@ -38779,6 +39315,17 @@ const reactDoctorRules = [
38779
39315
  requires: [...new Set(["react", ...noDocumentStartViewTransition.requires ?? []])]
38780
39316
  }
38781
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
+ },
38782
39329
  {
38783
39330
  key: "react-doctor/no-dynamic-import-path",
38784
39331
  id: "no-dynamic-import-path",
@@ -38976,6 +39523,18 @@ const reactDoctorRules = [
38976
39523
  category: "Accessibility"
38977
39524
  }
38978
39525
  },
39526
+ {
39527
+ key: "react-doctor/no-img-lazy-with-high-fetchpriority",
39528
+ id: "no-img-lazy-with-high-fetchpriority",
39529
+ source: "react-doctor",
39530
+ originallyExternal: false,
39531
+ rule: {
39532
+ ...noImgLazyWithHighFetchpriority,
39533
+ framework: "global",
39534
+ category: "Performance",
39535
+ requires: [...new Set(["react", ...noImgLazyWithHighFetchpriority.requires ?? []])]
39536
+ }
39537
+ },
38979
39538
  {
38980
39539
  key: "react-doctor/no-initialize-state",
38981
39540
  id: "no-initialize-state",
@@ -39046,6 +39605,17 @@ const reactDoctorRules = [
39046
39605
  requires: [...new Set(["react", ...noIsMounted.requires ?? []])]
39047
39606
  }
39048
39607
  },
39608
+ {
39609
+ key: "react-doctor/no-json-parse-stringify-clone",
39610
+ id: "no-json-parse-stringify-clone",
39611
+ source: "react-doctor",
39612
+ originallyExternal: false,
39613
+ rule: {
39614
+ ...noJsonParseStringifyClone,
39615
+ framework: "global",
39616
+ category: "Performance"
39617
+ }
39618
+ },
39049
39619
  {
39050
39620
  key: "react-doctor/no-jsx-element-type",
39051
39621
  id: "no-jsx-element-type",
@@ -39565,6 +40135,18 @@ const reactDoctorRules = [
39565
40135
  requires: [...new Set(["react", ...noStaticElementInteractions.requires ?? []])]
39566
40136
  }
39567
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
+ },
39568
40150
  {
39569
40151
  key: "react-doctor/no-string-refs",
39570
40152
  id: "no-string-refs",
@@ -39577,6 +40159,17 @@ const reactDoctorRules = [
39577
40159
  requires: [...new Set(["react", ...noStringRefs.requires ?? []])]
39578
40160
  }
39579
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
+ },
39580
40173
  {
39581
40174
  key: "react-doctor/no-this-in-sfc",
39582
40175
  id: "no-this-in-sfc",
@@ -41685,32 +42278,6 @@ const computeUnconditionalSet = (cfg) => {
41685
42278
  }
41686
42279
  return unconditional;
41687
42280
  };
41688
- const computeDominatesExit = (cfg) => {
41689
- const reachableToExit = /* @__PURE__ */ new Set();
41690
- const queue = [cfg.exit];
41691
- while (queue.length > 0) {
41692
- const block = queue.shift();
41693
- if (reachableToExit.has(block)) continue;
41694
- reachableToExit.add(block);
41695
- for (const edge of block.predecessors) queue.push(edge.from);
41696
- }
41697
- const dominatesExit = /* @__PURE__ */ new Set();
41698
- const visit = (block) => {
41699
- if (block === cfg.exit) return true;
41700
- if (dominatesExit.has(block)) return true;
41701
- if (block.successors.length === 0) return false;
41702
- dominatesExit.add(block);
41703
- let allReach = true;
41704
- for (const edge of block.successors) if (!visit(edge.to)) {
41705
- allReach = false;
41706
- break;
41707
- }
41708
- if (!allReach) dominatesExit.delete(block);
41709
- return allReach;
41710
- };
41711
- for (const block of cfg.blocks) visit(block);
41712
- return dominatesExit;
41713
- };
41714
42281
  const analyzeControlFlow = (program) => {
41715
42282
  nextBlockId = 0;
41716
42283
  const functionCfgs = /* @__PURE__ */ new Map();
@@ -41718,8 +42285,7 @@ const analyzeControlFlow = (program) => {
41718
42285
  const cfg = buildFunctionCfg(functionNode, body);
41719
42286
  functionCfgs.set(functionNode, {
41720
42287
  cfg,
41721
- unconditionalSet: computeUnconditionalSet(cfg),
41722
- dominatesExitSet: computeDominatesExit(cfg)
42288
+ unconditionalSet: computeUnconditionalSet(cfg)
41723
42289
  });
41724
42290
  };
41725
42291
  if (isNodeOfType(program, "Program")) buildFor(program, {
@@ -41762,20 +42328,10 @@ const analyzeControlFlow = (program) => {
41762
42328
  if (!block) return true;
41763
42329
  return entry.unconditionalSet.has(block);
41764
42330
  };
41765
- const dominatesExit = (node) => {
41766
- const owner = enclosingFunction(node);
41767
- if (!owner) return true;
41768
- const entry = functionCfgs.get(owner);
41769
- if (!entry) return true;
41770
- const block = entry.cfg.blockOf(node);
41771
- if (!block) return true;
41772
- return entry.dominatesExitSet.has(block);
41773
- };
41774
42331
  return {
41775
42332
  cfgFor,
41776
42333
  enclosingFunction,
41777
- isUnconditionalFromEntry,
41778
- dominatesExit
42334
+ isUnconditionalFromEntry
41779
42335
  };
41780
42336
  };
41781
42337
  //#endregion
@@ -41800,8 +42356,7 @@ const buildFallbackScopes = () => ({
41800
42356
  const FALLBACK_CFG = {
41801
42357
  cfgFor: () => null,
41802
42358
  enclosingFunction: () => null,
41803
- isUnconditionalFromEntry: () => false,
41804
- dominatesExit: () => false
42359
+ isUnconditionalFromEntry: () => false
41805
42360
  };
41806
42361
  const wrapWithSemanticContext = (rule) => ({
41807
42362
  ...rule,