oxlint-plugin-react-doctor 0.5.8-dev.7ef9f0e → 0.5.8-dev.80e3093

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 (2) hide show
  1. package/dist/index.js +64 -288
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1160,61 +1160,6 @@ const isFirebaseRulesPath = (relativePath) => /(?:^|\/)(?:firestore\.rules|stora
1160
1160
  //#endregion
1161
1161
  //#region src/plugin/rules/security-scan/utils/strip-comments-preserving-positions.ts
1162
1162
  const WHITESPACE_PATTERN = /\s/;
1163
- const IDENTIFIER_CHARACTER_PATTERN = /[\p{ID_Continue}$]/u;
1164
- const REGEX_PRECEDING_KEYWORDS = new Set([
1165
- "return",
1166
- "typeof",
1167
- "case",
1168
- "in",
1169
- "of",
1170
- "new",
1171
- "delete",
1172
- "void",
1173
- "instanceof",
1174
- "do",
1175
- "else",
1176
- "yield",
1177
- "await",
1178
- "throw"
1179
- ]);
1180
- const isRegexLiteralStart = (characters, slashIndex) => {
1181
- let cursor = slashIndex - 1;
1182
- while (cursor >= 0 && WHITESPACE_PATTERN.test(characters[cursor])) cursor -= 1;
1183
- if (cursor < 0) return true;
1184
- const previousCharacter = characters[cursor];
1185
- const characterBefore = cursor > 0 ? characters[cursor - 1] : "";
1186
- if (IDENTIFIER_CHARACTER_PATTERN.test(previousCharacter)) {
1187
- let wordStartIndex = cursor;
1188
- while (wordStartIndex > 0 && IDENTIFIER_CHARACTER_PATTERN.test(characters[wordStartIndex - 1])) wordStartIndex -= 1;
1189
- if (wordStartIndex > 0 && characters[wordStartIndex - 1] === ".") return false;
1190
- return REGEX_PRECEDING_KEYWORDS.has(characters.slice(wordStartIndex, cursor + 1).join(""));
1191
- }
1192
- if (previousCharacter === "<") return false;
1193
- if (previousCharacter === ">") return characterBefore === "=";
1194
- if (previousCharacter === "+" || previousCharacter === "-") return characterBefore !== previousCharacter;
1195
- if (previousCharacter === "!") return !(IDENTIFIER_CHARACTER_PATTERN.test(characterBefore) || characterBefore === ")" || characterBefore === "]");
1196
- return !(previousCharacter === ")" || previousCharacter === "]" || previousCharacter === "}" || previousCharacter === "\"" || previousCharacter === "'" || previousCharacter === "`");
1197
- };
1198
- const findRegexLiteralEnd = (content, slashIndex) => {
1199
- let cursor = slashIndex + 1;
1200
- let isInsideCharacterClass = false;
1201
- while (cursor < content.length) {
1202
- const character = content[cursor];
1203
- if (character === "\\") {
1204
- cursor += 2;
1205
- continue;
1206
- }
1207
- if (character === "\n") return null;
1208
- if (character === "[") isInsideCharacterClass = true;
1209
- else if (character === "]") isInsideCharacterClass = false;
1210
- else if (character === "/" && !isInsideCharacterClass) {
1211
- if (content[cursor + 1] === "/") return null;
1212
- return cursor + 1;
1213
- }
1214
- cursor += 1;
1215
- }
1216
- return null;
1217
- };
1218
1163
  const quotedLiteralHasWhitespace = (content, openQuoteIndex, delimiter) => {
1219
1164
  for (let cursor = openQuoteIndex + 1; cursor < content.length; cursor += 1) {
1220
1165
  const character = content[cursor];
@@ -1248,11 +1193,6 @@ const blankNonCodePreservingPositions = (content, blankStringContents) => {
1248
1193
  index += 2;
1249
1194
  continue;
1250
1195
  }
1251
- if (character === "\n" && stringDelimiter !== "`") {
1252
- stringDelimiter = null;
1253
- index += 1;
1254
- continue;
1255
- }
1256
1196
  if (character === stringDelimiter) {
1257
1197
  stringDelimiter = null;
1258
1198
  index += 1;
@@ -1300,14 +1240,6 @@ const blankNonCodePreservingPositions = (content, blankStringContents) => {
1300
1240
  }
1301
1241
  continue;
1302
1242
  }
1303
- if (character === "/") {
1304
- const regexEndIndex = isRegexLiteralStart(characters, index) ? findRegexLiteralEnd(content, index) : null;
1305
- if (regexEndIndex !== null) {
1306
- if (blankStringContents) for (let interiorIndex = index + 1; interiorIndex < regexEndIndex - 1; interiorIndex += 1) blankUnlessNewline(interiorIndex);
1307
- index = regexEndIndex;
1308
- continue;
1309
- }
1310
- }
1311
1243
  if (templateExpressionDepths.length > 0) {
1312
1244
  const innermost = templateExpressionDepths.length - 1;
1313
1245
  if (character === "{") templateExpressionDepths[innermost] += 1;
@@ -2086,7 +2018,7 @@ const anchorAmbiguousText = defineRule({
2086
2018
  });
2087
2019
  //#endregion
2088
2020
  //#region src/plugin/rules/a11y/anchor-has-content.ts
2089
- const MESSAGE$58 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
2021
+ const MESSAGE$59 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
2090
2022
  const anchorHasContent = defineRule({
2091
2023
  id: "anchor-has-content",
2092
2024
  title: "Anchor has no content",
@@ -2109,7 +2041,7 @@ const anchorHasContent = defineRule({
2109
2041
  ]) if (hasJsxPropIgnoreCase(opening.attributes, attribute)) return;
2110
2042
  context.report({
2111
2043
  node: opening.name,
2112
- message: MESSAGE$58
2044
+ message: MESSAGE$59
2113
2045
  });
2114
2046
  } };
2115
2047
  }
@@ -2495,7 +2427,7 @@ const parseJsxValue = (value) => {
2495
2427
  };
2496
2428
  //#endregion
2497
2429
  //#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
2498
- const MESSAGE$57 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
2430
+ const MESSAGE$58 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
2499
2431
  const ariaActivedescendantHasTabindex = defineRule({
2500
2432
  id: "aria-activedescendant-has-tabindex",
2501
2433
  title: "aria-activedescendant missing tabindex",
@@ -2513,14 +2445,14 @@ const ariaActivedescendantHasTabindex = defineRule({
2513
2445
  if (tabIndexValue === null || tabIndexValue >= -1) return;
2514
2446
  context.report({
2515
2447
  node: node.name,
2516
- message: MESSAGE$57
2448
+ message: MESSAGE$58
2517
2449
  });
2518
2450
  return;
2519
2451
  }
2520
2452
  if (isInteractiveElement(tag, node)) return;
2521
2453
  context.report({
2522
2454
  node: node.name,
2523
- message: MESSAGE$57
2455
+ message: MESSAGE$58
2524
2456
  });
2525
2457
  } })
2526
2458
  });
@@ -4625,7 +4557,7 @@ const asyncParallel = defineRule({
4625
4557
  });
4626
4558
  //#endregion
4627
4559
  //#region src/plugin/rules/security/auth-token-in-web-storage.ts
4628
- const MESSAGE$56 = "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.";
4560
+ 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.";
4629
4561
  const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
4630
4562
  const STORAGE_GLOBALS = new Set([
4631
4563
  "window",
@@ -4666,7 +4598,7 @@ const authTokenInWebStorage = defineRule({
4666
4598
  if (!isAuthCredentialKey(keyArgument.value)) return;
4667
4599
  context.report({
4668
4600
  node,
4669
- message: MESSAGE$56
4601
+ message: MESSAGE$57
4670
4602
  });
4671
4603
  },
4672
4604
  AssignmentExpression(node) {
@@ -4677,7 +4609,7 @@ const authTokenInWebStorage = defineRule({
4677
4609
  if (!propertyName || !isAuthCredentialKey(propertyName)) return;
4678
4610
  context.report({
4679
4611
  node: target,
4680
- message: MESSAGE$56
4612
+ message: MESSAGE$57
4681
4613
  });
4682
4614
  }
4683
4615
  }))
@@ -5167,7 +5099,7 @@ const isPureEventBlockerHandler = (attribute) => {
5167
5099
  };
5168
5100
  //#endregion
5169
5101
  //#region src/plugin/rules/a11y/click-events-have-key-events.ts
5170
- const MESSAGE$55 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
5102
+ const MESSAGE$56 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
5171
5103
  const KEY_HANDLERS = [
5172
5104
  "onKeyUp",
5173
5105
  "onKeyDown",
@@ -5195,7 +5127,7 @@ const clickEventsHaveKeyEvents = defineRule({
5195
5127
  if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
5196
5128
  context.report({
5197
5129
  node: node.name,
5198
- message: MESSAGE$55
5130
+ message: MESSAGE$56
5199
5131
  });
5200
5132
  } };
5201
5133
  }
@@ -5412,7 +5344,7 @@ const getClassNameLiteral = (classAttribute) => {
5412
5344
  };
5413
5345
  //#endregion
5414
5346
  //#region src/plugin/rules/a11y/control-has-associated-label.ts
5415
- const MESSAGE$54 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
5347
+ 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`.";
5416
5348
  const DISPLAY_NONE_CLASS_TOKEN = "hidden";
5417
5349
  const isDisplayNoneClassToken = (token) => token.toLowerCase() === DISPLAY_NONE_CLASS_TOKEN;
5418
5350
  const collectStaticTemplateClassTokens = (templateLiteral) => {
@@ -5607,7 +5539,7 @@ const controlHasAssociatedLabel = defineRule({
5607
5539
  for (const child of node.children) if (checkChildForLabel(child, 1, checkContext)) return;
5608
5540
  context.report({
5609
5541
  node: opening,
5610
- message: MESSAGE$54
5542
+ message: MESSAGE$55
5611
5543
  });
5612
5544
  } };
5613
5545
  }
@@ -5630,7 +5562,7 @@ const corsCookieTrustRisk = defineRule({
5630
5562
  const DANGEROUS_HTML_PATTERN = /dangerouslySetInnerHTML|\.(?:inner|outer)HTML\s*[+]?=(?!=)|\.insertAdjacentHTML\s*\(|\bdocument\.write(?:ln)?\s*\(|\.(?:createContextualFragment|setHTMLUnsafe)\s*\(/;
5631
5563
  const HTML_VALUE_START_PATTERN = /(?:__html\s*:|\.(?:inner|outer)HTML\s*[+]?=(?!=)|\.insertAdjacentHTML\s*\(\s*[^,]*,|\bdocument\.write(?:ln)?\s*\(|\.(?:createContextualFragment|setHTMLUnsafe)\s*\()\s*([\s\S]*)/;
5632
5564
  const HTML_TAINT_PATTERN = /searchParams|query|params|request|req\.|response\.|result\.|data\.|await|fetch|props\.|children|content|html|body|text|message|\blocation\b|document\.cookie|\breferrer\b|\blocalStorage\b|\bsessionStorage\b|URLSearchParams|window\.name/i;
5633
- const STRING_LITERAL_VALUE_PATTERN = /^(?:"(?:\\.|[^"\\\n])*"|'(?:\\.|[^'\\\n])*'|`[^`$]*`)\s*(?:\/\/[^\n]*)?\s*(?:[;,})\n]|$)/;
5565
+ const STRING_LITERAL_VALUE_PATTERN = /^(?:["'][^"']*["']|`[^`$]*`)\s*(?:\/\/[^\n]*)?\s*(?:[;,})\n]|$)/;
5634
5566
  const MODULE_CONSTANT_VALUE_PATTERN = /^[A-Z][A-Z0-9_]*\s*(?:\/\/[^\n]*)?\s*(?:[;,})\n]|$)/;
5635
5567
  const DOM_CONTENT_SOURCE_VALUE_PATTERN = /^[\w$]+(?:\??\.[\w$]+)*\??\.(?:inner|outer)HTML\b/;
5636
5568
  const SANITIZER_PATTERN = /\b(?:DOMPurify|sanitize\w*|purify|(?:escape|encode)[A-Za-z]*(?:Html|HTML|Entit\w*)|insane|xss)\b|(?<!un)safe|(?<!un)saniti[sz]/i;
@@ -6062,7 +5994,7 @@ const noVagueButtonLabel = defineRule({
6062
5994
  });
6063
5995
  //#endregion
6064
5996
  //#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
6065
- const MESSAGE$53 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
5997
+ 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.";
6066
5998
  const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
6067
5999
  const NAME_PROVIDING_ATTRIBUTES = [
6068
6000
  "aria-label",
@@ -6085,7 +6017,7 @@ const dialogHasAccessibleName = defineRule({
6085
6017
  if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
6086
6018
  context.report({
6087
6019
  node: node.name,
6088
- message: MESSAGE$53
6020
+ message: MESSAGE$54
6089
6021
  });
6090
6022
  } })
6091
6023
  });
@@ -6124,7 +6056,7 @@ const isEs6Component = (node) => {
6124
6056
  };
6125
6057
  //#endregion
6126
6058
  //#region src/plugin/rules/react-builtins/display-name.ts
6127
- const MESSAGE$52 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
6059
+ const MESSAGE$53 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
6128
6060
  const DEFAULT_ADDITIONAL_HOCS = [
6129
6061
  "observer",
6130
6062
  "lazy",
@@ -6327,7 +6259,7 @@ const displayName = defineRule({
6327
6259
  const reportAt = (node) => {
6328
6260
  context.report({
6329
6261
  node,
6330
- message: MESSAGE$52
6262
+ message: MESSAGE$53
6331
6263
  });
6332
6264
  };
6333
6265
  return {
@@ -8491,7 +8423,7 @@ const forbidElements = defineRule({
8491
8423
  });
8492
8424
  //#endregion
8493
8425
  //#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
8494
- const MESSAGE$51 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
8426
+ const MESSAGE$52 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
8495
8427
  const forwardRefUsesRef = defineRule({
8496
8428
  id: "forward-ref-uses-ref",
8497
8429
  title: "forwardRef without ref parameter",
@@ -8511,7 +8443,7 @@ const forwardRefUsesRef = defineRule({
8511
8443
  if (isNodeOfType(onlyParam, "RestElement")) return;
8512
8444
  context.report({
8513
8445
  node: inner,
8514
- message: MESSAGE$51
8446
+ message: MESSAGE$52
8515
8447
  });
8516
8448
  } })
8517
8449
  });
@@ -8548,7 +8480,7 @@ const gitProviderUrlInjectionRisk = defineRule({
8548
8480
  });
8549
8481
  //#endregion
8550
8482
  //#region src/plugin/rules/a11y/heading-has-content.ts
8551
- const MESSAGE$50 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
8483
+ 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`.";
8552
8484
  const DEFAULT_HEADING_TAGS = [
8553
8485
  "h1",
8554
8486
  "h2",
@@ -8582,7 +8514,7 @@ const headingHasContent = defineRule({
8582
8514
  for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
8583
8515
  context.report({
8584
8516
  node,
8585
- message: MESSAGE$50
8517
+ message: MESSAGE$51
8586
8518
  });
8587
8519
  } };
8588
8520
  }
@@ -8720,7 +8652,7 @@ const hooksNoNanInDeps = defineRule({
8720
8652
  });
8721
8653
  //#endregion
8722
8654
  //#region src/plugin/rules/a11y/html-has-lang.ts
8723
- const MESSAGE$49 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
8655
+ const MESSAGE$50 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
8724
8656
  const resolveSettings$38 = (settings) => {
8725
8657
  const reactDoctor = settings?.["react-doctor"];
8726
8658
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
@@ -8767,13 +8699,13 @@ const htmlHasLang = defineRule({
8767
8699
  if (!lang) {
8768
8700
  context.report({
8769
8701
  node: node.name,
8770
- message: MESSAGE$49
8702
+ message: MESSAGE$50
8771
8703
  });
8772
8704
  return;
8773
8705
  }
8774
8706
  if (evaluateLang(lang.value) === "empty") context.report({
8775
8707
  node: lang,
8776
- message: MESSAGE$49
8708
+ message: MESSAGE$50
8777
8709
  });
8778
8710
  } };
8779
8711
  }
@@ -8993,7 +8925,7 @@ const htmlNoNestedInteractive = defineRule({
8993
8925
  });
8994
8926
  //#endregion
8995
8927
  //#region src/plugin/rules/a11y/iframe-has-title.ts
8996
- const MESSAGE$48 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
8928
+ const MESSAGE$49 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
8997
8929
  const evaluateTitleValue = (value) => {
8998
8930
  if (!value) return "missing";
8999
8931
  if (isNodeOfType(value, "Literal")) {
@@ -9033,14 +8965,14 @@ const iframeHasTitle = defineRule({
9033
8965
  if (!titleAttr) {
9034
8966
  if (hasSpread || tag === "iframe") context.report({
9035
8967
  node: node.name,
9036
- message: MESSAGE$48
8968
+ message: MESSAGE$49
9037
8969
  });
9038
8970
  return;
9039
8971
  }
9040
8972
  const verdict = evaluateTitleValue(titleAttr.value);
9041
8973
  if (verdict === "missing" || verdict === "empty") context.report({
9042
8974
  node: titleAttr,
9043
- message: MESSAGE$48
8975
+ message: MESSAGE$49
9044
8976
  });
9045
8977
  } })
9046
8978
  });
@@ -9155,7 +9087,7 @@ const iframeMissingSandbox = defineRule({
9155
9087
  });
9156
9088
  //#endregion
9157
9089
  //#region src/plugin/rules/a11y/img-redundant-alt.ts
9158
- const MESSAGE$47 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
9090
+ const MESSAGE$48 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
9159
9091
  const DEFAULT_COMPONENTS = ["img"];
9160
9092
  const DEFAULT_REDUNDANT_WORDS = [
9161
9093
  "image",
@@ -9220,7 +9152,7 @@ const imgRedundantAlt = defineRule({
9220
9152
  if (!altAttribute) return;
9221
9153
  if (altValueRedundant(altAttribute, settings.words)) context.report({
9222
9154
  node: altAttribute,
9223
- message: MESSAGE$47
9155
+ message: MESSAGE$48
9224
9156
  });
9225
9157
  } };
9226
9158
  }
@@ -9259,7 +9191,7 @@ const DEPRECATED_CIPHER_API_PATTERN = /(?<!cipher\.)\bcreate(?:Cipher|Decipher)\
9259
9191
  const WEAK_CIPHER_ALGORITHM_PATTERN = /\bcreate(?:Cipher|Decipher)iv\s*\(\s*["'](?:des|des3|des-?ede3?|rc4|rc2|bf|blowfish)\b/i;
9260
9192
  const WEAK_CIPHER_NAME_PATTERN = /\b(?:DES|RC4|Blowfish)\b/;
9261
9193
  const CIPHER_CONTEXT_PATTERN = /\b(?:cipher|decipher|encrypt|decrypt|crypto)\b/i;
9262
- const UNSAFE_SIGNATURE_COMPARISON_PATTERN = /[A-Za-z_$][\w$.]{0,100}signature[\w$]*(?:\([^)]*\))?\s*(?:===?|!==?)\s*[A-Za-z_$][\w$.]*(?:\([^)]*\))?|[A-Za-z_$][\w$.]{0,100}(?:\([^)]*\))?\s*(?:===?|!==?)\s*[A-Za-z_$][\w$.]{0,100}signature[\w$]*(?:\([^)]*\))?/i;
9194
+ const UNSAFE_SIGNATURE_COMPARISON_PATTERN = /[A-Za-z_$][\w$.]*signature[\w$]*(?:\([^)]*\))?\s*(?:===?|!==?)\s*[A-Za-z_$][\w$.]*(?:\([^)]*\))?|[A-Za-z_$][\w$.]*(?:\([^)]*\))?\s*(?:===?|!==?)\s*[A-Za-z_$][\w$.]*signature[\w$]*(?:\([^)]*\))?/i;
9263
9195
  const ENUM_MEMBER_COMPARAND_PATTERN = /(?:===?|!==?)\s*[A-Z](?:[a-z]|[A-Z0-9_]*\b(?!\s*[.(]))|^[A-Z](?:[a-z]|[A-Z0-9_]*\b(?!\s*[.(]))[\w$.]*(?:\([^)]*\))?\s*(?:===?|!==?)/;
9264
9196
  const SIGNATURE_METADATA_IDENTIFIER_PATTERN = /signature(?:Method|Type|Status|Algorithm|Kind|Mode|Version)\b/i;
9265
9197
  const BOOLEAN_COMPARAND_PATTERN = /(?:===?|!==?)\s*(?:true|false|null|undefined)\b/;
@@ -9299,18 +9231,17 @@ const insecureCryptoRisk = defineRule({
9299
9231
  if (!isProductionSourcePath(file.relativePath)) return [];
9300
9232
  if (DEMO_CONTEXT_PATTERN.test(file.relativePath)) return [];
9301
9233
  if (PROTOCOL_MANDATED_HASH_CONTEXT_PATTERN.test(file.relativePath)) return [];
9302
- const content = getScannableContent(file);
9303
- let matchIndex = findMatchIndexNearContext(content, WEAK_HASH_PATTERN, SECURITY_CONTEXT_PATTERN, PROTOCOL_MANDATED_HASH_CONTEXT_PATTERN);
9304
- if (matchIndex < 0) matchIndex = content.search(WEAK_CIPHER_ALGORITHM_PATTERN);
9305
- if (matchIndex < 0) matchIndex = content.search(DEPRECATED_CIPHER_API_PATTERN);
9306
- if (matchIndex < 0 && CIPHER_CONTEXT_PATTERN.test(content)) matchIndex = content.search(WEAK_CIPHER_NAME_PATTERN);
9307
- if (matchIndex < 0 && !TIMING_SAFE_COMPARISON_PATTERN.test(content) && !CLIENT_COMPONENT_FILE_PATTERN.test(file.relativePath)) {
9308
- const comparisonMatch = UNSAFE_SIGNATURE_COMPARISON_PATTERN.exec(content);
9234
+ let matchIndex = findMatchIndexNearContext(file.content, WEAK_HASH_PATTERN, SECURITY_CONTEXT_PATTERN, PROTOCOL_MANDATED_HASH_CONTEXT_PATTERN);
9235
+ if (matchIndex < 0) matchIndex = file.content.search(WEAK_CIPHER_ALGORITHM_PATTERN);
9236
+ if (matchIndex < 0) matchIndex = file.content.search(DEPRECATED_CIPHER_API_PATTERN);
9237
+ if (matchIndex < 0 && CIPHER_CONTEXT_PATTERN.test(file.content)) matchIndex = file.content.search(WEAK_CIPHER_NAME_PATTERN);
9238
+ if (matchIndex < 0 && !TIMING_SAFE_COMPARISON_PATTERN.test(file.content) && !CLIENT_COMPONENT_FILE_PATTERN.test(file.relativePath)) {
9239
+ const comparisonMatch = UNSAFE_SIGNATURE_COMPARISON_PATTERN.exec(file.content);
9309
9240
  if (comparisonMatch !== null && !ENUM_MEMBER_COMPARAND_PATTERN.test(comparisonMatch[0]) && !SIGNATURE_METADATA_IDENTIFIER_PATTERN.test(comparisonMatch[0]) && !BOOLEAN_COMPARAND_PATTERN.test(comparisonMatch[0])) matchIndex = comparisonMatch.index;
9310
9241
  }
9311
- if (matchIndex < 0) matchIndex = findRandomCallIndexWithSameLineContext(content, MATH_RANDOM_CALL_PATTERN, SECURITY_RANDOM_CONTEXT_PATTERN, UI_NONCE_CONTEXT_PATTERN);
9242
+ if (matchIndex < 0) matchIndex = findRandomCallIndexWithSameLineContext(file.content, MATH_RANDOM_CALL_PATTERN, SECURITY_RANDOM_CONTEXT_PATTERN, UI_NONCE_CONTEXT_PATTERN);
9312
9243
  if (matchIndex < 0) return [];
9313
- const location = getLocationAtIndex(content, matchIndex);
9244
+ const location = getLocationAtIndex(file.content, matchIndex);
9314
9245
  return [{
9315
9246
  message: "Code uses weak hashes, deprecated ciphers, timing-unsafe comparisons, or Math.random in a security-shaped context.",
9316
9247
  line: location.line,
@@ -12200,7 +12131,7 @@ const jsxMaxDepth = defineRule({
12200
12131
  });
12201
12132
  //#endregion
12202
12133
  //#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
12203
- const MESSAGE$46 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
12134
+ const MESSAGE$47 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
12204
12135
  const LITERAL_TEXT_TAGS = new Set([
12205
12136
  "code",
12206
12137
  "pre",
@@ -12248,7 +12179,7 @@ const jsxNoCommentTextnodes = defineRule({
12248
12179
  if (isInsideLiteralTextTag(node)) return;
12249
12180
  context.report({
12250
12181
  node,
12251
- message: MESSAGE$46
12182
+ message: MESSAGE$47
12252
12183
  });
12253
12184
  } })
12254
12185
  });
@@ -12279,7 +12210,7 @@ const isInsideFunctionScope = (node) => {
12279
12210
  };
12280
12211
  //#endregion
12281
12212
  //#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
12282
- const MESSAGE$45 = "Every reader of this context redraws on each render because you build its `value` inline.";
12213
+ const MESSAGE$46 = "Every reader of this context redraws on each render because you build its `value` inline.";
12283
12214
  const CONTEXT_MODULES$1 = [
12284
12215
  "react",
12285
12216
  "use-context-selector",
@@ -12377,7 +12308,7 @@ const jsxNoConstructedContextValues = defineRule({
12377
12308
  if (!isConstructedValue(innerExpression)) continue;
12378
12309
  context.report({
12379
12310
  node: attribute,
12380
- message: MESSAGE$45
12311
+ message: MESSAGE$46
12381
12312
  });
12382
12313
  }
12383
12314
  }
@@ -12461,8 +12392,7 @@ const isJsxAttributeOnIntrinsicHtmlElement = (attribute) => {
12461
12392
  };
12462
12393
  //#endregion
12463
12394
  //#region src/plugin/rules/react-builtins/jsx-no-jsx-as-prop.ts
12464
- const MESSAGE_MEMOISED = "This child redraws every render because the prop gets brand new JSX each time.";
12465
- const MESSAGE_UNKNOWN = "If this child is memoized, it still redraws every render because the prop gets brand new JSX each time.";
12395
+ const MESSAGE$45 = "This child redraws every render because the prop gets brand new JSX each time.";
12466
12396
  const KNOWN_SLOT_PROP_NAMES = new Set([
12467
12397
  "icon",
12468
12398
  "Icon",
@@ -12725,8 +12655,7 @@ const jsxNoJsxAsProp = defineRule({
12725
12655
  if (isJsxAttributeOnIntrinsicHtmlElement(node)) return;
12726
12656
  const parentJsxOpening = node.parent;
12727
12657
  const openingName = parentJsxOpening && isNodeOfType(parentJsxOpening, "JSXOpeningElement") ? parentJsxOpening.name : null;
12728
- const memoStatus = memoStatusForJsxOpeningName(memoRegistry, openingName);
12729
- if (memoStatus === "not-memoised") return;
12658
+ if (memoStatusForJsxOpeningName(memoRegistry, openingName) === "not-memoised") return;
12730
12659
  if (isNodeOfType(node.name, "JSXIdentifier") && isSlotPropName(node.name.name)) return;
12731
12660
  if (!isInsideFunctionScope(node)) return;
12732
12661
  const value = node.value;
@@ -12737,7 +12666,7 @@ const jsxNoJsxAsProp = defineRule({
12737
12666
  if (!isJsxProducingExpression(expressionNode) && !followsRenderLocalJsxBinding(expressionNode, node)) return;
12738
12667
  context.report({
12739
12668
  node,
12740
- message: memoStatus === "memoised" ? MESSAGE_MEMOISED : MESSAGE_UNKNOWN
12669
+ message: MESSAGE$45
12741
12670
  });
12742
12671
  }
12743
12672
  };
@@ -18017,108 +17946,19 @@ const isArrayFromLengthObjectCall = (node) => {
18017
17946
  }
18018
17947
  return false;
18019
17948
  };
18020
- const TYPE_RESOLUTION_DEPTH_LIMIT = 4;
18021
- const isStringKeywordAnnotation = (typeAnnotation) => Boolean(typeAnnotation && isNodeOfType(typeAnnotation, "TSTypeAnnotation") && isNodeOfType(typeAnnotation.typeAnnotation, "TSStringKeyword"));
18022
- const findSameFileTypeDeclaration = (referenceNode, typeName) => {
18023
- const programRoot = findProgramRoot(referenceNode);
18024
- if (!programRoot || !isNodeOfType(programRoot, "Program")) return null;
18025
- for (const statement of programRoot.body) {
18026
- const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
18027
- if (!declaration) continue;
18028
- if ((isNodeOfType(declaration, "TSInterfaceDeclaration") || isNodeOfType(declaration, "TSTypeAliasDeclaration")) && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === typeName) return declaration;
18029
- }
18030
- return null;
18031
- };
18032
- const typeDeclaresStringProperty = (typeNode, propertyName, referenceNode, depth) => {
18033
- if (depth > TYPE_RESOLUTION_DEPTH_LIMIT) return false;
18034
- let members = null;
18035
- if (isNodeOfType(typeNode, "TSTypeLiteral")) members = typeNode.members;
18036
- else if (isNodeOfType(typeNode, "TSInterfaceDeclaration")) members = typeNode.body.body;
18037
- if (members) {
18038
- for (const member of members) {
18039
- if (!isNodeOfType(member, "TSPropertySignature")) continue;
18040
- if (!isNodeOfType(member.key, "Identifier") || member.key.name !== propertyName) continue;
18041
- return isStringKeywordAnnotation(member.typeAnnotation);
18042
- }
18043
- return false;
18044
- }
18045
- if (isNodeOfType(typeNode, "TSTypeAliasDeclaration")) return typeDeclaresStringProperty(typeNode.typeAnnotation, propertyName, referenceNode, depth + 1);
18046
- if (isNodeOfType(typeNode, "TSTypeReference") && isNodeOfType(typeNode.typeName, "Identifier")) {
18047
- const declaration = findSameFileTypeDeclaration(referenceNode, typeNode.typeName.name);
18048
- if (!declaration) return false;
18049
- return typeDeclaresStringProperty(declaration, propertyName, referenceNode, depth + 1);
18050
- }
18051
- return false;
18052
- };
18053
- const isDestructuredFromStringTypedPattern = (bindingIdentifier) => {
18054
- const property = bindingIdentifier.parent;
18055
- if (!property || !isNodeOfType(property, "Property")) return false;
18056
- if (!isNodeOfType(property.key, "Identifier")) return false;
18057
- const pattern = property.parent;
18058
- if (!pattern || !isNodeOfType(pattern, "ObjectPattern")) return false;
18059
- const typeAnnotation = pattern.typeAnnotation;
18060
- if (!typeAnnotation || !isNodeOfType(typeAnnotation, "TSTypeAnnotation")) return false;
18061
- return typeDeclaresStringProperty(typeAnnotation.typeAnnotation, property.key.name, bindingIdentifier, 0);
18062
- };
18063
- const isProvablyStringValued = (expression, depth) => {
18064
- if (depth > TYPE_RESOLUTION_DEPTH_LIMIT) return false;
18065
- const node = stripParenExpression(expression);
18066
- if (isNodeOfType(node, "Literal")) return typeof node.value === "string";
18067
- if (isNodeOfType(node, "TemplateLiteral")) return true;
18068
- if (isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "String") return true;
18069
- if (isNodeOfType(node, "Identifier")) {
18070
- const binding = findVariableInitializer(node, node.name);
18071
- if (!binding) return false;
18072
- if (binding.initializer && isProvablyStringValued(binding.initializer, depth + 1)) return true;
18073
- if (isNodeOfType(binding.bindingIdentifier, "Identifier") && isStringKeywordAnnotation(binding.bindingIdentifier.typeAnnotation)) return true;
18074
- return isDestructuredFromStringTypedPattern(binding.bindingIdentifier);
18075
- }
18076
- return false;
18077
- };
18078
- const hasProvablyStringFirstArgument = (callNode) => {
18079
- if (!isNodeOfType(callNode, "CallExpression")) return false;
18080
- const source = callNode.arguments?.[0];
18081
- return Boolean(source && isProvablyStringValued(source, 0));
18082
- };
18083
- /**
18084
- * `[...str]`, `str.split(...)`, and `Array.from(str)` slice ONE string
18085
- * into positional fragments (characters, lines, tokens). Fragment
18086
- * position IS the entry's stable identity — nothing reorders, filters,
18087
- * or carries per-item state — so an index key is correct there.
18088
- * `.split(...)` needs no string proof (only strings have `.split`);
18089
- * the spread / `Array.from` forms do, because both are equally common
18090
- * on arrays.
18091
- */
18092
- const isStringDerivedReceiver = (receiver, depth = 0) => {
18093
- const node = stripParenExpression(receiver);
18094
- if (isNodeOfType(node, "Identifier")) {
18095
- if (depth >= TYPE_RESOLUTION_DEPTH_LIMIT) return false;
18096
- const binding = findVariableInitializer(node, node.name);
18097
- if (!binding?.initializer) return false;
18098
- return isStringDerivedReceiver(binding.initializer, depth + 1);
18099
- }
18100
- if (isNodeOfType(node, "ArrayExpression") && node.elements?.length === 1) {
18101
- const only = node.elements[0];
18102
- if (only && isNodeOfType(only, "SpreadElement")) return isProvablyStringValued(only.argument, 0);
18103
- }
18104
- if (isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "split") return true;
18105
- return isArrayFromCall(node) && hasProvablyStringFirstArgument(node);
18106
- };
18107
- const isInsideIteratorMapMatching = (node, matchesMethodReceiver, matchesArrayFromCall) => {
17949
+ const isInsideStaticPlaceholderMap = (node) => {
18108
17950
  let current = node;
18109
17951
  while (current.parent) {
18110
17952
  const parent = current.parent;
18111
17953
  if (isFunctionLike$1(current) && isNodeOfType(parent, "CallExpression") && parent.arguments.includes(current)) {
18112
17954
  const callee = parent.callee;
18113
- if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && (callee.property.name === "map" || callee.property.name === "flatMap" || callee.property.name === "forEach")) return matchesMethodReceiver(callee.object);
18114
- if (isArrayFromCall(parent) && parent.arguments.length >= 2 && parent.arguments[1] === current) return matchesArrayFromCall(parent);
17955
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && (callee.property.name === "map" || callee.property.name === "flatMap" || callee.property.name === "forEach")) return isStaticPlaceholderReceiver(callee.object);
17956
+ if (isArrayFromCall(parent) && parent.arguments.length >= 2 && parent.arguments[1] === current) return isArrayFromLengthObjectCall(parent);
18115
17957
  }
18116
17958
  current = parent;
18117
17959
  }
18118
17960
  return false;
18119
17961
  };
18120
- const isInsideStringDerivedMap = (node) => isInsideIteratorMapMatching(node, isStringDerivedReceiver, hasProvablyStringFirstArgument);
18121
- const isInsideStaticPlaceholderMap = (node) => isInsideIteratorMapMatching(node, isStaticPlaceholderReceiver, isArrayFromLengthObjectCall);
18122
17962
  /**
18123
17963
  * Walk up from a JSXAttribute node looking for the enclosing iterator
18124
17964
  * callback (`.map(cb)`, `.flatMap(cb)`, `.forEach(cb)`, `Array.from(_, cb)`)
@@ -18174,7 +18014,6 @@ const noArrayIndexAsKey = defineRule({
18174
18014
  const indexName = extractIndexName(node.value.expression);
18175
18015
  if (!indexName) return;
18176
18016
  if (isInsideStaticPlaceholderMap(node)) return;
18177
- if (isInsideStringDerivedMap(node)) return;
18178
18017
  if (isCompositeKeyWithIteratorIdentity(node.value.expression, node)) return;
18179
18018
  const openingElement = node.parent;
18180
18019
  if (openingElement && isNodeOfType(openingElement, "JSXOpeningElement")) {
@@ -20012,25 +19851,6 @@ const countSetterCallSites = (ref) => {
20012
19851
  }
20013
19852
  return count;
20014
19853
  };
20015
- const collectUpdaterParameterReads = (analysis, updaterFn) => {
20016
- const reads = [];
20017
- for (const ref of getDownstreamRefs(analysis, updaterFn.body)) if (ref.resolved?.defs.some((def) => def.type === "Parameter" && def.node === updaterFn)) reads.push(ref.identifier);
20018
- return reads;
20019
- };
20020
- const readsParameterOnlyViaObjectSpread = (parameterReads) => parameterReads.every((read) => {
20021
- const spread = read.parent;
20022
- if (!spread || !isNodeOfType(spread, "SpreadElement")) return false;
20023
- const container = spread.parent;
20024
- return Boolean(container && isNodeOfType(container, "ObjectExpression"));
20025
- });
20026
- const isAccumulatingFunctionalUpdater = (analysis, callExpr) => {
20027
- if (!isNodeOfType(callExpr, "CallExpression")) return false;
20028
- const firstArgument = callExpr.arguments?.[0];
20029
- if (!firstArgument || !isFunctionLike$1(firstArgument)) return false;
20030
- const parameterReads = collectUpdaterParameterReads(analysis, firstArgument);
20031
- if (parameterReads.length === 0) return false;
20032
- return !readsParameterOnlyViaObjectSpread(parameterReads);
20033
- };
20034
19854
  const getStateNameForUseStateDecl = (useStateNode) => {
20035
19855
  if (!useStateNode || !isNodeOfType(useStateNode, "VariableDeclarator")) return null;
20036
19856
  if (!isNodeOfType(useStateNode.id, "ArrayPattern")) return null;
@@ -20065,7 +19885,6 @@ const noDerivedState = defineRule({
20065
19885
  const depsUpstreamRefs = depsRefs.flatMap((depRef) => getUpstreamRefs(analysis, depRef));
20066
19886
  if (isInitialOnlySetterCall(callExpr)) continue;
20067
19887
  if (isControlledPropMirror(node, callExpr)) continue;
20068
- if (isAccumulatingFunctionalUpdater(analysis, callExpr)) continue;
20069
19888
  const isSomeArgsInternal = argsUpstreamRefs.some((argRef) => isState(analysis, argRef) || isProp(analysis, argRef));
20070
19889
  const isValueAlwaysInSync = argsUpstreamRefs.length > 0 && argsUpstreamRefs.every((argRef) => depsUpstreamRefs.some((depRef) => argRef.resolved === depRef.resolved)) && countSetterCallSites(ref) === 1;
20071
19890
  if (isSomeArgsInternal) context.report({
@@ -31055,67 +30874,35 @@ const preferUseSyncExternalStore = defineRule({
31055
30874
  });
31056
30875
  //#endregion
31057
30876
  //#region src/plugin/rules/state-and-effects/prefer-use-reducer.ts
31058
- const getSetterNameFromStatement = (statement, setterNames) => {
31059
- let expression = null;
31060
- if (isNodeOfType(statement, "ExpressionStatement")) expression = statement.expression;
31061
- else if (isNodeOfType(statement, "ReturnStatement")) expression = statement.argument;
31062
- if (!expression) return null;
31063
- const call = stripParenExpression(expression);
31064
- if (!isNodeOfType(call, "CallExpression")) return null;
31065
- if (!isNodeOfType(call.callee, "Identifier")) return null;
31066
- return setterNames.has(call.callee.name) ? call.callee.name : null;
31067
- };
31068
- const findLargestCoUpdatedSetterGroup = (componentBody, setterNames) => {
31069
- let largestGroupSize = 0;
31070
- const visit = (node, isInsideNestedFunction) => {
31071
- if (isNodeOfType(node, "BlockStatement") && isInsideNestedFunction) {
31072
- const groupSetterNames = /* @__PURE__ */ new Set();
31073
- for (const statement of node.body ?? []) {
31074
- const setterName = getSetterNameFromStatement(statement, setterNames);
31075
- if (setterName) groupSetterNames.add(setterName);
31076
- }
31077
- largestGroupSize = Math.max(largestGroupSize, groupSetterNames.size);
31078
- }
31079
- const enteringNestedFunction = isInsideNestedFunction || isFunctionLike$1(node);
31080
- const record = node;
31081
- for (const key of Object.keys(record)) {
31082
- if (key === "parent" || key === "type") continue;
31083
- const child = record[key];
31084
- if (Array.isArray(child)) {
31085
- for (const item of child) if (isAstNode(item)) visit(item, enteringNestedFunction);
31086
- } else if (isAstNode(child)) visit(child, enteringNestedFunction);
31087
- }
31088
- };
31089
- visit(componentBody, false);
31090
- return largestGroupSize;
31091
- };
31092
30877
  const preferUseReducer = defineRule({
31093
30878
  id: "prefer-useReducer",
31094
30879
  title: "Many related useState calls",
31095
30880
  tags: ["test-noise"],
31096
30881
  severity: "warn",
31097
- recommendation: "Group state that always changes together into `useReducer` so one dispatched action describes the whole transition instead of a list of setter calls.",
30882
+ recommendation: "Group related state in `useReducer` so one logical update does not fan out into separate renders.",
31098
30883
  create: (context) => {
31099
- const reportCoUpdatedUseState = (body, componentName) => {
30884
+ const reportExcessiveUseState = (body, componentName) => {
31100
30885
  if (!isNodeOfType(body, "BlockStatement")) return;
31101
- const setterNames = new Set(collectUseStateBindings(body).map((binding) => binding.setterName));
31102
- if (setterNames.size < 5) return;
31103
- const coUpdatedCount = findLargestCoUpdatedSetterGroup(body, setterNames);
31104
- if (coUpdatedCount >= 5) context.report({
30886
+ let useStateCount = 0;
30887
+ for (const statement of body.body ?? []) {
30888
+ if (!isNodeOfType(statement, "VariableDeclaration")) continue;
30889
+ for (const declarator of statement.declarations ?? []) if (declarator.init && isHookCall$1(declarator.init, "useState")) useStateCount++;
30890
+ }
30891
+ if (useStateCount >= 5) context.report({
31105
30892
  node: body,
31106
- message: `"${componentName}" updates ${coUpdatedCount} separate useState values in one place state that changes together is easier to keep consistent as a single useReducer action.`
30893
+ message: `${useStateCount} useState calls in "${componentName}" can each trigger a separate render.`
31107
30894
  });
31108
30895
  };
31109
30896
  return {
31110
30897
  FunctionDeclaration(node) {
31111
30898
  if (!node.id?.name || !isUppercaseName(node.id.name)) return;
31112
- reportCoUpdatedUseState(node.body, node.id.name);
30899
+ reportExcessiveUseState(node.body, node.id.name);
31113
30900
  },
31114
30901
  VariableDeclarator(node) {
31115
30902
  if (!isComponentAssignment(node)) return;
31116
30903
  if (!isNodeOfType(node.init, "ArrowFunctionExpression") && !isNodeOfType(node.init, "FunctionExpression")) return;
31117
30904
  if (!isNodeOfType(node.id, "Identifier")) return;
31118
- reportCoUpdatedUseState(node.init.body, node.id.name);
30905
+ reportExcessiveUseState(node.init.body, node.id.name);
31119
30906
  }
31120
30907
  };
31121
30908
  }
@@ -34382,17 +34169,6 @@ const collectReturnedJsxRoots = (functionNode) => {
34382
34169
  return roots;
34383
34170
  };
34384
34171
  const isChildrenForwardingJsxChild = (child, bindings) => isNodeOfType(child, "JSXExpressionContainer") && isChildrenValueExpression(child.expression, bindings);
34385
- const fragmentChildrenOrNull = (child) => {
34386
- if (isNodeOfType(child, "JSXFragment")) return child.children ?? [];
34387
- if (isNodeOfType(child, "JSXElement") && isJsxFragmentElement(child.openingElement)) return child.children ?? [];
34388
- return null;
34389
- };
34390
- const isChildrenForwardingJsxChildThroughFragments = (child, bindings) => {
34391
- if (isChildrenForwardingJsxChild(child, bindings)) return true;
34392
- const fragmentChildren = fragmentChildrenOrNull(child);
34393
- if (fragmentChildren === null) return false;
34394
- return fragmentChildren.some((innerChild) => isChildrenForwardingJsxChildThroughFragments(innerChild, bindings));
34395
- };
34396
34172
  const isChildrenForwardingAttribute = (attribute, bindings) => {
34397
34173
  if (isNodeOfType(attribute, "JSXSpreadAttribute")) return isPropsObjectExpression(attribute.argument, bindings);
34398
34174
  return isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && attribute.name.name === "children" && isNodeOfType(attribute.value, "JSXExpressionContainer") && isChildrenValueExpression(attribute.value.expression, bindings);
@@ -34404,7 +34180,7 @@ const jsxRootForwardsChildrenIntoText = (jsxRoot, bindings, isTextHandlingElemen
34404
34180
  if (!isNodeOfType(node, "JSXElement")) return void 0;
34405
34181
  const elementName = resolveJsxElementName(node.openingElement);
34406
34182
  if (!elementName || !isTextHandlingElement(elementName)) return;
34407
- didForwardIntoText = (node.children ?? []).some((child) => isChildrenForwardingJsxChildThroughFragments(child, bindings)) || (node.openingElement.attributes ?? []).some((attribute) => isChildrenForwardingAttribute(attribute, bindings));
34183
+ didForwardIntoText = (node.children ?? []).some((child) => isChildrenForwardingJsxChild(child, bindings)) || (node.openingElement.attributes ?? []).some((attribute) => isChildrenForwardingAttribute(attribute, bindings));
34408
34184
  });
34409
34185
  return didForwardIntoText;
34410
34186
  };
@@ -34422,7 +34198,7 @@ const jsxRootForwardsChildren = (jsxRoot, bindings, isTextHandlingElement, count
34422
34198
  return;
34423
34199
  }
34424
34200
  }
34425
- if (countsAsForwardTarget(node) && (node.children ?? []).some((child) => isChildrenForwardingJsxChildThroughFragments(child, bindings))) didForward = true;
34201
+ if (countsAsForwardTarget(node) && (node.children ?? []).some((child) => isChildrenForwardingJsxChild(child, bindings))) didForward = true;
34426
34202
  });
34427
34203
  return didForward;
34428
34204
  };
@@ -34500,13 +34276,14 @@ const recordWrapperFromDeclaration = (componentName, definitionNode, isTextHandl
34500
34276
  if (kind === "text") wrappers.add(componentName);
34501
34277
  else if (kind === "nonText") nonTextWrappers.add(componentName);
34502
34278
  };
34279
+ const MAX_TRANSITIVE_WRAPPER_PASSES = 3;
34503
34280
  const collectTextWrapperComponents = (programNode, isTextHandlingRoot, isNonTextHostRoot) => {
34504
34281
  const wrappers = /* @__PURE__ */ new Set();
34505
34282
  const nonTextWrappers = /* @__PURE__ */ new Set();
34506
34283
  const isTextHandlingElement = (elementName) => isTextHandlingRoot(elementName) || wrappers.has(elementName);
34507
34284
  const isNonTextHostElement = (elementName) => isNonTextHostRoot(elementName) || nonTextWrappers.has(elementName);
34508
34285
  const recordDeclaration = (componentName, definitionNode) => recordWrapperFromDeclaration(componentName, definitionNode, isTextHandlingElement, isNonTextHostElement, wrappers, nonTextWrappers);
34509
- while (true) {
34286
+ for (let pass = 0; pass < MAX_TRANSITIVE_WRAPPER_PASSES; pass += 1) {
34510
34287
  const wrappersSizeBeforePass = wrappers.size;
34511
34288
  const nonTextSizeBeforePass = nonTextWrappers.size;
34512
34289
  walkAst(programNode, (node) => {
@@ -39188,7 +38965,7 @@ const isAuthGuardName = (calleeName) => {
39188
38965
  //#endregion
39189
38966
  //#region src/plugin/utils/is-non-privileged-server-action.ts
39190
38967
  const NON_DATA_EFFECT_FUNCTION_NAMES = new Set([...CACHE_REVALIDATION_FUNCTION_NAMES, ...NEXTJS_NAVIGATION_FUNCTIONS]);
39191
- const isCacheOrNavigationCall = (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && NON_DATA_EFFECT_FUNCTION_NAMES.has(node.callee.name) && getImportSourceForName(node, node.callee.name) !== null;
38968
+ const isCacheOrNavigationCall = (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && NON_DATA_EFFECT_FUNCTION_NAMES.has(node.callee.name);
39192
38969
  const unwrapExpression = (node) => {
39193
38970
  let current = node;
39194
38971
  while (current) {
@@ -39213,7 +38990,6 @@ const isDataExposingValue = (node) => {
39213
38990
  const isLiteralOnlyExpression = (node) => {
39214
38991
  if (!node) return false;
39215
38992
  if (isNodeOfType(node, "Literal")) return true;
39216
- if (isNodeOfType(node, "Identifier")) return node.name === "undefined";
39217
38993
  if (isNodeOfType(node, "TemplateLiteral")) return (node.expressions ?? []).every(isLiteralOnlyExpression);
39218
38994
  if (isNodeOfType(node, "UnaryExpression")) return isLiteralOnlyExpression(node.argument);
39219
38995
  if (isNodeOfType(node, "ArrayExpression")) return (node.elements ?? []).every((element) => element === null || !isNodeOfType(element, "SpreadElement") && isLiteralOnlyExpression(element));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.5.8-dev.7ef9f0e",
3
+ "version": "0.5.8-dev.80e3093",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",