oxlint-plugin-react-doctor 0.7.5-dev.d9676e2 → 0.7.5

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 +500 -793
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,11 +1,14 @@
1
- import { KEYS } from "eslint-visitor-keys";
2
1
  import * as path from "node:path";
3
2
  import * as fs from "node:fs";
4
3
  import { readFileSync } from "node:fs";
5
- import { parseSync, visitorKeys } from "oxc-parser";
4
+ import { parseSync } from "oxc-parser";
6
5
  import { analyze } from "eslint-scope";
6
+ import * as eslintVisitorKeys from "eslint-visitor-keys";
7
+ //#region src/plugin/utils/has-type-property.ts
8
+ const hasTypeProperty = (value) => Boolean(value && typeof value === "object" && "type" in value);
9
+ //#endregion
7
10
  //#region src/plugin/utils/is-node-of-type.ts
8
- const isNodeOfType = (node, type) => node !== null && typeof node === "object" && "type" in node && node.type === type;
11
+ const isNodeOfType = (node, type) => Boolean(hasTypeProperty(node) && node.type === type);
9
12
  //#endregion
10
13
  //#region src/plugin/utils/non-react-jsx-dialect.ts
11
14
  const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
@@ -397,7 +400,6 @@ const isProductionFilePath = (relativePath, sourceFilePattern) => {
397
400
  //#endregion
398
401
  //#region src/plugin/rules/security-scan/utils/is-production-source-path.ts
399
402
  const isProductionSourcePath = (relativePath) => {
400
- if (/\.d\.[cm]?[jt]s$/i.test(relativePath)) return false;
401
403
  return isProductionFilePath(relativePath, SOURCE_FILE_PATTERN);
402
404
  };
403
405
  //#endregion
@@ -563,8 +565,7 @@ const REACT_RUNTIME_MODULE_SOURCES = new Set([
563
565
  "react",
564
566
  "react-dom",
565
567
  "preact/compat",
566
- "preact/hooks",
567
- "@wordpress/element"
568
+ "preact/hooks"
568
569
  ]);
569
570
  const REACT_ECOSYSTEM_PACKAGE_NAMES = new Set([
570
571
  "next",
@@ -801,55 +802,24 @@ const isHookCall$2 = (node, hookName) => {
801
802
  };
802
803
  //#endregion
803
804
  //#region src/plugin/utils/is-ast-node.ts
804
- const isAstNode = (value) => value !== null && typeof value === "object" && "type" in value && typeof value.type === "string";
805
- //#endregion
806
- //#region src/plugin/utils/runtime-visitor-keys.ts
807
- const RUNTIME_VISITOR_KEYS = {
808
- ...KEYS,
809
- ArrayPattern: ["decorators", ...KEYS.ArrayPattern],
810
- AssignmentPattern: ["decorators", ...KEYS.AssignmentPattern],
811
- ClassDeclaration: ["decorators", ...KEYS.ClassDeclaration],
812
- ClassExpression: ["decorators", ...KEYS.ClassExpression],
813
- Identifier: ["decorators", ...KEYS.Identifier],
814
- MethodDefinition: ["decorators", ...KEYS.MethodDefinition],
815
- ObjectPattern: ["decorators", ...KEYS.ObjectPattern],
816
- PropertyDefinition: ["decorators", ...KEYS.PropertyDefinition],
817
- RestElement: ["decorators", ...KEYS.RestElement]
805
+ const isAstNode = (value) => {
806
+ if (!hasTypeProperty(value)) return false;
807
+ return typeof value.type === "string";
818
808
  };
819
809
  //#endregion
820
810
  //#region src/plugin/utils/walk-ast.ts
821
- const forEachChildNode = (node, visit) => {
811
+ const walkAst = (node, visitor) => {
812
+ if (!node || typeof node !== "object") return;
813
+ if (visitor(node) === false) return;
822
814
  const nodeRecord = node;
823
- const childKeys = RUNTIME_VISITOR_KEYS[node.type];
824
- if (childKeys !== void 0) {
825
- for (let keyIndex = 0; keyIndex < childKeys.length; keyIndex += 1) {
826
- const child = nodeRecord[childKeys[keyIndex]];
827
- if (Array.isArray(child)) for (let itemIndex = 0; itemIndex < child.length; itemIndex += 1) {
828
- const item = child[itemIndex];
829
- if (isAstNode(item)) visit(item);
830
- }
831
- else if (isAstNode(child)) visit(child);
832
- }
833
- return;
834
- }
835
- for (const key in nodeRecord) {
836
- if (key === "parent" || !Object.hasOwn(nodeRecord, key)) continue;
815
+ for (const key of Object.keys(nodeRecord)) {
816
+ if (key === "parent") continue;
837
817
  const child = nodeRecord[key];
838
- if (Array.isArray(child)) for (let itemIndex = 0; itemIndex < child.length; itemIndex += 1) {
839
- const item = child[itemIndex];
840
- if (isAstNode(item)) visit(item);
841
- }
842
- else if (isAstNode(child)) visit(child);
818
+ if (Array.isArray(child)) {
819
+ for (const item of child) if (isAstNode(item)) walkAst(item, visitor);
820
+ } else if (isAstNode(child)) walkAst(child, visitor);
843
821
  }
844
822
  };
845
- const walkAst = (node, visitor) => {
846
- if (!node || typeof node !== "object") return;
847
- const visitNode = (current) => {
848
- if (visitor(current) === false) return;
849
- forEachChildNode(current, visitNode);
850
- };
851
- visitNode(node);
852
- };
853
823
  //#endregion
854
824
  //#region src/plugin/rules/state-and-effects/activity-wraps-effect-heavy-subtree.ts
855
825
  const ACTIVITY_IMPORTED_NAMES = new Set(["Activity", "unstable_Activity"]);
@@ -1292,7 +1262,7 @@ const REGEX_PRECEDING_KEYWORDS = new Set([
1292
1262
  "await",
1293
1263
  "throw"
1294
1264
  ]);
1295
- const isRegexLiteralStart = (content, characters, slashIndex) => {
1265
+ const isRegexLiteralStart = (characters, slashIndex) => {
1296
1266
  let cursor = slashIndex - 1;
1297
1267
  while (cursor >= 0 && WHITESPACE_PATTERN.test(characters[cursor])) cursor -= 1;
1298
1268
  if (cursor < 0) return true;
@@ -1302,7 +1272,7 @@ const isRegexLiteralStart = (content, characters, slashIndex) => {
1302
1272
  let wordStartIndex = cursor;
1303
1273
  while (wordStartIndex > 0 && IDENTIFIER_CHARACTER_PATTERN.test(characters[wordStartIndex - 1])) wordStartIndex -= 1;
1304
1274
  if (wordStartIndex > 0 && characters[wordStartIndex - 1] === ".") return false;
1305
- return REGEX_PRECEDING_KEYWORDS.has(content.slice(wordStartIndex, cursor + 1));
1275
+ return REGEX_PRECEDING_KEYWORDS.has(characters.slice(wordStartIndex, cursor + 1).join(""));
1306
1276
  }
1307
1277
  if (previousCharacter === "<") return false;
1308
1278
  if (previousCharacter === ">") return characterBefore === "=";
@@ -1343,15 +1313,13 @@ const quotedLiteralHasWhitespace = (content, openQuoteIndex, delimiter) => {
1343
1313
  return false;
1344
1314
  };
1345
1315
  const blankNonCodePreservingPositions = (content, blankStringContents) => {
1346
- let characters = null;
1316
+ const characters = content.split("");
1347
1317
  let stringDelimiter = null;
1348
1318
  let isBlankingString = false;
1349
1319
  const templateExpressionDepths = [];
1350
1320
  let index = 0;
1351
1321
  const blankUnlessNewline = (offset) => {
1352
- if (offset >= content.length || content[offset] === "\n") return;
1353
- characters ??= content.split("");
1354
- characters[offset] = " ";
1322
+ if (offset < content.length && content[offset] !== "\n") characters[offset] = " ";
1355
1323
  };
1356
1324
  while (index < content.length) {
1357
1325
  const character = content[index];
@@ -1398,7 +1366,6 @@ const blankNonCodePreservingPositions = (content, blankStringContents) => {
1398
1366
  continue;
1399
1367
  }
1400
1368
  if (character === "/" && nextCharacter === "/") {
1401
- characters ??= content.split("");
1402
1369
  while (index < content.length && content[index] !== "\n") {
1403
1370
  characters[index] = " ";
1404
1371
  index += 1;
@@ -1406,7 +1373,6 @@ const blankNonCodePreservingPositions = (content, blankStringContents) => {
1406
1373
  continue;
1407
1374
  }
1408
1375
  if (character === "/" && nextCharacter === "*") {
1409
- characters ??= content.split("");
1410
1376
  while (index < content.length) {
1411
1377
  if (content[index] === "*" && content[index + 1] === "/") {
1412
1378
  characters[index] = " ";
@@ -1420,7 +1386,7 @@ const blankNonCodePreservingPositions = (content, blankStringContents) => {
1420
1386
  continue;
1421
1387
  }
1422
1388
  if (character === "/") {
1423
- const regexEndIndex = isRegexLiteralStart(content, characters ?? content, index) ? findRegexLiteralEnd(content, index) : null;
1389
+ const regexEndIndex = isRegexLiteralStart(characters, index) ? findRegexLiteralEnd(content, index) : null;
1424
1390
  if (regexEndIndex !== null) {
1425
1391
  if (blankStringContents) for (let interiorIndex = index + 1; interiorIndex < regexEndIndex - 1; interiorIndex += 1) blankUnlessNewline(interiorIndex);
1426
1392
  index = regexEndIndex;
@@ -1438,9 +1404,9 @@ const blankNonCodePreservingPositions = (content, blankStringContents) => {
1438
1404
  }
1439
1405
  index += 1;
1440
1406
  }
1441
- return characters?.join("") ?? content;
1407
+ return characters.join("");
1442
1408
  };
1443
- const stripCommentsPreservingPositions = (content) => content.includes("//") || content.includes("/*") ? blankNonCodePreservingPositions(content, false) : content;
1409
+ const stripCommentsPreservingPositions = (content) => blankNonCodePreservingPositions(content, false);
1444
1410
  const stripCommentsAndStringLiteralsPreservingPositions = (content) => blankNonCodePreservingPositions(content, true);
1445
1411
  //#endregion
1446
1412
  //#region src/plugin/rules/security-scan/utils/scan-by-pattern.ts
@@ -1459,15 +1425,10 @@ const scanByPattern = ({ shouldScan, pattern, requireAll, suppressWhen, ignoreSt
1459
1425
  if (!shouldScan(file)) return [];
1460
1426
  const content = getScannableContent(file, ignoreStringLiterals);
1461
1427
  if (requireAll !== void 0 && !requireAll.every((gate) => gate.test(content))) return [];
1462
- let matchIndex = -1;
1463
- if (pattern instanceof RegExp) matchIndex = content.search(pattern);
1464
- else for (let patternIndex = 0; patternIndex < pattern.length; patternIndex += 1) {
1465
- matchIndex = content.search(pattern[patternIndex]);
1466
- if (matchIndex >= 0) break;
1467
- }
1468
- if (matchIndex < 0) return [];
1428
+ const matchedPattern = (pattern instanceof RegExp ? [pattern] : pattern).find((candidate) => candidate.test(content));
1429
+ if (matchedPattern === void 0) return [];
1469
1430
  if (suppressWhen !== void 0 && suppressWhen.test(content)) return [];
1470
- const { line, column } = getLocationAtIndex(content, matchIndex);
1431
+ const { line, column } = getMatchLocation(content, matchedPattern);
1471
1432
  return [{
1472
1433
  message,
1473
1434
  line,
@@ -1477,7 +1438,6 @@ const scanByPattern = ({ shouldScan, pattern, requireAll, suppressWhen, ignoreSt
1477
1438
  //#endregion
1478
1439
  //#region src/plugin/rules/security-scan/agent-tool-capability-risk.ts
1479
1440
  const AGENT_TOOL_DEFINITION_PATTERN = /\b(?:tool\s*\(\s*\{|createTool\s*\(|defineTool\s*\(|new\s+(?:DynamicTool|StructuredTool)\s*\()/;
1480
- const AGENT_TOOL_DEFINITION_PREFILTER_PATTERN = /\b(?:tool|createTool|defineTool|DynamicTool|StructuredTool)\b/;
1481
1441
  const AGENT_TOOL_CONTEXT_PATH_PATTERN = /(?:^|\/)(?:agents?|tools?|mcp)(?:\/|$)|(?:agent|tool|mcp)[^/]*\.[cm]?[jt]sx?$/i;
1482
1442
  const agentToolCapabilityRisk = defineRule({
1483
1443
  id: "agent-tool-capability-risk",
@@ -1485,7 +1445,7 @@ const agentToolCapabilityRisk = defineRule({
1485
1445
  severity: "warn",
1486
1446
  recommendation: "Treat tool inputs as prompt-injection controlled. Validate arguments, scope permissions per call, and avoid exposing shell/file/network primitives directly to agents.",
1487
1447
  scan: scanByPattern({
1488
- shouldScan: (file) => isProductionSourcePath(file.relativePath) && AGENT_TOOL_CONTEXT_PATH_PATTERN.test(file.relativePath) && AGENT_TOOL_DEFINITION_PREFILTER_PATTERN.test(file.content) && AGENT_TOOL_DANGEROUS_CAPABILITY_PATTERN.test(file.content),
1448
+ shouldScan: (file) => isProductionSourcePath(file.relativePath) && AGENT_TOOL_CONTEXT_PATH_PATTERN.test(file.relativePath),
1489
1449
  pattern: AGENT_TOOL_DEFINITION_PATTERN,
1490
1450
  requireAll: [AGENT_TOOL_DANGEROUS_CAPABILITY_PATTERN],
1491
1451
  ignoreStringLiterals: true,
@@ -5179,10 +5139,8 @@ const STORAGE_GLOBALS = new Set([
5179
5139
  const SENSITIVE_KEY_PATTERN = /token|jwt|secret|password|passwd|credential|api[-_]?key|bearer|private[-_]?key/i;
5180
5140
  const NON_AUTH_TOKEN_PATTERN = /csrf|xsrf|device|fcm|apns|push|design|tokeniz|syntax|css|theme|color/i;
5181
5141
  const STRONG_AUTH_KEY_PATTERN = /jwt|secret|password|passwd|credential|private[-_]?key|api[-_]?key|bearer|access[-_]?token|refresh[-_]?token|auth[-_]?token|id[-_]?token|session/i;
5182
- const PRODUCT_API_KEY_RECORDS_PATTERN = /(?:^|[._:-])(?:created|saved|integration|mailing)[-_]?api[-_]?keys$/i;
5183
5142
  const PRODUCT_API_KEY_COLLECTION_PATTERN = /[._:-](?:created|generated|saved)[-_]?api[-_]?keys$/i;
5184
5143
  const isAuthCredentialKey = (key) => {
5185
- if (PRODUCT_API_KEY_RECORDS_PATTERN.test(key)) return false;
5186
5144
  if (!SENSITIVE_KEY_PATTERN.test(key)) return false;
5187
5145
  if (PRODUCT_API_KEY_COLLECTION_PATTERN.test(key)) return false;
5188
5146
  if (NON_AUTH_TOKEN_PATTERN.test(key) && !STRONG_AUTH_KEY_PATTERN.test(key)) return false;
@@ -6871,10 +6829,11 @@ const getTemplateInterpolations = (valueTail) => {
6871
6829
  const interpolations = valueTail.slice(1, closingBacktickIndex).match(/\$\{[^}]*\}/g);
6872
6830
  return interpolations === null ? "" : interpolations.join(" ");
6873
6831
  };
6874
- const getIdentifierDeclarationInitializer = (identifier, sinkIndex, fileContent) => {
6875
- const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
6876
- if (declaration === null) return null;
6877
- return fileContent.slice(declaration.initializerStartIndex, declaration.initializerStartIndex + STATIC_TEMPLATE_MAX_CHARS);
6832
+ const getIdentifierDeclarationInitializer = (identifier, fileContent) => {
6833
+ const declarationMatch = new RegExp(`(?:const|let|var)\\s+${escapeRegExp(identifier)}\\s*(?::[^=\\n]{0,120})?=\\s*`).exec(fileContent);
6834
+ if (declarationMatch === null) return null;
6835
+ const initializerStartIndex = declarationMatch.index + declarationMatch[0].length;
6836
+ return fileContent.slice(initializerStartIndex, initializerStartIndex + STATIC_TEMPLATE_MAX_CHARS);
6878
6837
  };
6879
6838
  const isPureStringLiteralConcat = (initializerText) => {
6880
6839
  if (!/^["']/.test(initializerText)) return false;
@@ -6944,153 +6903,6 @@ const isAllOperandsDomContentConcat = (valueExpression) => {
6944
6903
  return !HTML_TAINT_PATTERN.test(withoutCast.replace(DOM_CONTENT_SOURCE_VALUE_PATTERN, ""));
6945
6904
  });
6946
6905
  };
6947
- const findMatchingBraceIndex = (fileContent, openingBraceIndex) => {
6948
- let depth = 0;
6949
- let quote = null;
6950
- for (let index = openingBraceIndex; index < fileContent.length; index += 1) {
6951
- const character = fileContent[index];
6952
- if (quote !== null) {
6953
- if (character === quote && fileContent[index - 1] !== "\\") quote = null;
6954
- continue;
6955
- }
6956
- if (character === "\"" || character === "'" || character === "`") {
6957
- quote = character;
6958
- continue;
6959
- }
6960
- if (character === "{") depth += 1;
6961
- if (character === "}") {
6962
- depth -= 1;
6963
- if (depth === 0) return index;
6964
- }
6965
- }
6966
- return fileContent.length;
6967
- };
6968
- const findContainingBlockEndIndex = (fileContent, targetIndex) => {
6969
- const openingBraceIndexes = [];
6970
- let quote = null;
6971
- let isLineComment = false;
6972
- let isBlockComment = false;
6973
- for (let index = 0; index < targetIndex; index += 1) {
6974
- const character = fileContent[index];
6975
- const nextCharacter = fileContent[index + 1];
6976
- if (isLineComment) {
6977
- if (character === "\n") isLineComment = false;
6978
- continue;
6979
- }
6980
- if (isBlockComment) {
6981
- if (character === "*" && nextCharacter === "/") {
6982
- isBlockComment = false;
6983
- index += 1;
6984
- }
6985
- continue;
6986
- }
6987
- if (quote !== null) {
6988
- if (character === quote && fileContent[index - 1] !== "\\") quote = null;
6989
- continue;
6990
- }
6991
- if (character === "/" && nextCharacter === "/") {
6992
- isLineComment = true;
6993
- index += 1;
6994
- continue;
6995
- }
6996
- if (character === "/" && nextCharacter === "*") {
6997
- isBlockComment = true;
6998
- index += 1;
6999
- continue;
7000
- }
7001
- if (character === "\"" || character === "'" || character === "`") {
7002
- quote = character;
7003
- continue;
7004
- }
7005
- if (character === "{") openingBraceIndexes.push(index);
7006
- if (character === "}") openingBraceIndexes.pop();
7007
- }
7008
- const openingBraceIndex = openingBraceIndexes.at(-1);
7009
- return openingBraceIndex === void 0 ? fileContent.length : findMatchingBraceIndex(fileContent, openingBraceIndex);
7010
- };
7011
- const findVisibleIdentifierDeclaration = (identifier, sinkIndex, fileContent) => {
7012
- const initializerPattern = new RegExp(`(?:const|let|var)\\s+${escapeRegExp(identifier)}\\s*(?::[^=\\n]*)?=\\s*([^;\\n]+)`, "g");
7013
- let nearestDeclaration = null;
7014
- let nearestDeclarationIndex = -1;
7015
- for (const match of fileContent.matchAll(initializerPattern)) {
7016
- const declarationIndex = match.index;
7017
- if (declarationIndex === void 0 || declarationIndex >= sinkIndex || declarationIndex <= nearestDeclarationIndex || findContainingBlockEndIndex(fileContent, declarationIndex) < sinkIndex) continue;
7018
- nearestDeclarationIndex = declarationIndex;
7019
- const initializer = match[1];
7020
- if (initializer === void 0) continue;
7021
- nearestDeclaration = {
7022
- initializer,
7023
- initializerStartIndex: declarationIndex + match[0].length - initializer.length
7024
- };
7025
- }
7026
- return nearestDeclaration;
7027
- };
7028
- const findContainingFunctionParameterSource = (identifier, sinkIndex, fileContent) => {
7029
- const patterns = [/function\s+([\w$]+)\s*\(([^)]*)\)\s*\{/g, /(?:const|let|var)\s+([\w$]+)\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>\s*\{/g];
7030
- let closestSource = null;
7031
- let closestStartIndex = -1;
7032
- for (const pattern of patterns) for (const match of fileContent.matchAll(pattern)) {
7033
- const matchIndex = match.index;
7034
- if (matchIndex === void 0 || matchIndex >= sinkIndex || matchIndex < closestStartIndex) continue;
7035
- if (findMatchingBraceIndex(fileContent, matchIndex + match[0].lastIndexOf("{")) < sinkIndex) continue;
7036
- const parameterIndex = (match[2] ?? "").split(",").findIndex((parameter) => parameter.trim().match(/^(?:\.\.\.)?([\w$]+)/)?.[1] === identifier);
7037
- if (parameterIndex < 0) continue;
7038
- closestStartIndex = matchIndex;
7039
- closestSource = {
7040
- functionName: match[1] ?? "",
7041
- parameterIndex
7042
- };
7043
- }
7044
- return closestSource;
7045
- };
7046
- const splitTopLevelArguments = (argumentText) => {
7047
- const argumentsList = [];
7048
- let startIndex = 0;
7049
- let depth = 0;
7050
- let quote = null;
7051
- for (let index = 0; index < argumentText.length; index += 1) {
7052
- const character = argumentText[index];
7053
- if (quote !== null) {
7054
- if (character === quote && argumentText[index - 1] !== "\\") quote = null;
7055
- continue;
7056
- }
7057
- if (character === "\"" || character === "'" || character === "`") {
7058
- quote = character;
7059
- continue;
7060
- }
7061
- if (character === "(" || character === "[" || character === "{") depth += 1;
7062
- if (character === ")" || character === "]" || character === "}") depth -= 1;
7063
- if (character === "," && depth === 0) {
7064
- argumentsList.push(argumentText.slice(startIndex, index).trim());
7065
- startIndex = index + 1;
7066
- }
7067
- }
7068
- argumentsList.push(argumentText.slice(startIndex).trim());
7069
- return argumentsList;
7070
- };
7071
- const isHtmlTainted = (expression, fileContent, sinkIndex, visitedIdentifiers) => {
7072
- const trimmedExpression = expression.trim();
7073
- if (STRING_LITERAL_VALUE_PATTERN.test(`${trimmedExpression};`)) return false;
7074
- if (MODULE_CONSTANT_VALUE_PATTERN.test(`${trimmedExpression};`)) return false;
7075
- if (SANITIZER_PATTERN.test(trimmedExpression)) return false;
7076
- if (ENV_CONFIG_VALUE_PATTERN.test(trimmedExpression)) return false;
7077
- if (I18N_VALUE_PATTERN.test(trimmedExpression)) return false;
7078
- if (ESCAPING_SERIALIZER_CALL_PATTERN.test(trimmedExpression)) return false;
7079
- if (HTML_TAINT_PATTERN.test(trimmedExpression)) return true;
7080
- const identifier = trimmedExpression.match(/^([\w$]+)\s*(?:[;,})\n]|$)/)?.[1];
7081
- if (identifier === void 0 || visitedIdentifiers.has(identifier)) return false;
7082
- visitedIdentifiers.add(identifier);
7083
- const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
7084
- if (declaration !== null && isHtmlTainted(declaration.initializer, fileContent, sinkIndex, visitedIdentifiers)) return true;
7085
- const parameterSource = findContainingFunctionParameterSource(identifier, sinkIndex, fileContent);
7086
- if (parameterSource === null || parameterSource.functionName.length === 0) return false;
7087
- const callPattern = new RegExp(`\\b${escapeRegExp(parameterSource.functionName)}\\s*\\(([^)]*)\\)`, "g");
7088
- for (const callMatch of fileContent.matchAll(callPattern)) {
7089
- const argument = splitTopLevelArguments(callMatch[1] ?? "")[parameterSource.parameterIndex];
7090
- if (argument !== void 0 && isHtmlTainted(argument, fileContent, sinkIndex, new Set(visitedIdentifiers))) return true;
7091
- }
7092
- return false;
7093
- };
7094
6906
  const dangerousHtmlSink = defineRule({
7095
6907
  id: "dangerous-html-sink",
7096
6908
  title: "HTML injection sink with dynamic content",
@@ -7117,7 +6929,6 @@ const dangerousHtmlSink = defineRule({
7117
6929
  const valueTail = fullValueTail.slice(0, VALUE_EXPRESSION_MAX_CHARS);
7118
6930
  const terminatorIndex = valueTail.search(/[;}]/);
7119
6931
  const valueExpression = terminatorIndex >= 0 ? valueTail.slice(0, terminatorIndex + 1) : valueTail;
7120
- const sinkIndex = lines.slice(0, lineIndex).join("\n").length + (lineIndex > 0 ? 1 : 0) + line.search(DANGEROUS_HTML_PATTERN);
7121
6932
  if (STRING_LITERAL_VALUE_PATTERN.test(valueExpression)) continue;
7122
6933
  if (MODULE_CONSTANT_VALUE_PATTERN.test(valueExpression)) continue;
7123
6934
  if (DOM_CONTENT_SOURCE_VALUE_PATTERN.test(valueExpression) && !valueExpression.includes("+")) {
@@ -7129,7 +6940,7 @@ const dangerousHtmlSink = defineRule({
7129
6940
  let templateInterpolations = getTemplateInterpolations(longValueTail ?? fullValueTail);
7130
6941
  const valueIdentifier = BARE_IDENTIFIER_VALUE_PATTERN.test(valueExpression) || MEMBER_OR_INDEX_ACCESS_VALUE_PATTERN.test(valueExpression) || IDENTIFIER_WITH_LITERAL_FALLBACK_PATTERN.test(valueExpression) ? valueExpression.match(/^[\w$]+/)?.[0] : void 0;
7131
6942
  if (templateInterpolations === null && valueIdentifier !== void 0 && BARE_IDENTIFIER_VALUE_PATTERN.test(valueExpression)) {
7132
- const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier, sinkIndex, file.content);
6943
+ const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier, file.content);
7133
6944
  if (declarationInitializer !== null) {
7134
6945
  if (isPureStringLiteralConcat(declarationInitializer)) continue;
7135
6946
  templateInterpolations = getTemplateInterpolations(declarationInitializer);
@@ -7140,7 +6951,7 @@ const dangerousHtmlSink = defineRule({
7140
6951
  if (SANITIZER_PATTERN.test(judgedExpression)) continue;
7141
6952
  if (ENV_CONFIG_VALUE_PATTERN.test(judgedExpression)) continue;
7142
6953
  if (I18N_VALUE_PATTERN.test(judgedExpression)) continue;
7143
- if (!isHtmlTainted(judgedExpression, file.content, sinkIndex, /* @__PURE__ */ new Set())) continue;
6954
+ if (!HTML_TAINT_PATTERN.test(judgedExpression)) continue;
7144
6955
  if (ESCAPING_SERIALIZER_CALL_PATTERN.test(valueExpression)) continue;
7145
6956
  if (/highlighted/i.test(valueExpression)) continue;
7146
6957
  if (/highlight/i.test(valueExpression) && HIGHLIGHTER_LIBRARY_PATTERN.test(file.content)) continue;
@@ -7587,7 +7398,7 @@ const containsJsx$1 = (root) => {
7587
7398
  containsJsxCache.set(root, found);
7588
7399
  return found;
7589
7400
  };
7590
- const getStaticMemberName$2 = (node) => {
7401
+ const getStaticMemberName$1 = (node) => {
7591
7402
  if (!isNodeOfType(node, "MemberExpression")) return null;
7592
7403
  if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
7593
7404
  if (node.computed && isNodeOfType(node.property, "Literal") && typeof node.property.value === "string") return node.property.value;
@@ -7601,7 +7412,7 @@ const getAssignedName = (node) => {
7601
7412
  if (isNodeOfType(parent, "AssignmentExpression")) {
7602
7413
  const left = parent.left;
7603
7414
  if (isNodeOfType(left, "Identifier")) return left.name;
7604
- if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$2(left);
7415
+ if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$1(left);
7605
7416
  }
7606
7417
  return null;
7607
7418
  };
@@ -7609,20 +7420,20 @@ const isModuleExportsAssignment = (node) => {
7609
7420
  const parent = node.parent;
7610
7421
  if (!parent || !isNodeOfType(parent, "AssignmentExpression")) return false;
7611
7422
  const left = parent.left;
7612
- return isNodeOfType(left, "MemberExpression") && isNodeOfType(left.object, "Identifier") && left.object.name === "module" && getStaticMemberName$2(left) === "exports";
7423
+ return isNodeOfType(left, "MemberExpression") && isNodeOfType(left.object, "Identifier") && left.object.name === "module" && getStaticMemberName$1(left) === "exports";
7613
7424
  };
7614
7425
  const isCreateClassLikeCall = (node) => {
7615
7426
  if (!isNodeOfType(node, "CallExpression")) return false;
7616
7427
  if (isEs5Component(node)) return true;
7617
7428
  const callee = node.callee;
7618
- if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$2(callee) === "createClass";
7429
+ if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$1(callee) === "createClass";
7619
7430
  return false;
7620
7431
  };
7621
7432
  const isCreateContextCall = (node) => {
7622
7433
  if (!isNodeOfType(node, "CallExpression")) return false;
7623
7434
  const callee = node.callee;
7624
7435
  if (isNodeOfType(callee, "Identifier")) return callee.name === "createContext";
7625
- return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$2(callee) === "createContext";
7436
+ return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$1(callee) === "createContext";
7626
7437
  };
7627
7438
  const isNamedFunctionLike = (node) => (isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && Boolean(node.id?.name);
7628
7439
  const firstCallArgument = (node) => {
@@ -7680,7 +7491,7 @@ const memberExpressionPath = (node) => {
7680
7491
  if (isNodeOfType(node, "Identifier")) return [node.name];
7681
7492
  if (!isNodeOfType(node, "MemberExpression")) return [];
7682
7493
  const objectPath = memberExpressionPath(node.object);
7683
- const propertyName = getStaticMemberName$2(node);
7494
+ const propertyName = getStaticMemberName$1(node);
7684
7495
  return propertyName ? [...objectPath, propertyName] : objectPath;
7685
7496
  };
7686
7497
  const displayNameAssignmentIndexCache = /* @__PURE__ */ new WeakMap();
@@ -7690,7 +7501,7 @@ const getDisplayNameAssignmentIndex = (programRoot) => {
7690
7501
  const identifierTargets = /* @__PURE__ */ new Set();
7691
7502
  const memberPathSegments = /* @__PURE__ */ new Set();
7692
7503
  const visit = (node) => {
7693
- if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName$2(node.left) === "displayName") {
7504
+ if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName$1(node.left) === "displayName") {
7694
7505
  if (isNodeOfType(node.left.object, "Identifier")) identifierTargets.add(node.left.object.name);
7695
7506
  for (const segment of memberExpressionPath(node.left.object)) memberPathSegments.add(segment);
7696
7507
  }
@@ -7885,7 +7696,7 @@ const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? ap
7885
7696
  const isImportedFromReact = (symbol) => {
7886
7697
  if (symbol.kind !== "import") return false;
7887
7698
  const importDeclaration = symbol.declarationNode.parent;
7888
- return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(importDeclaration.source.value));
7699
+ return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react");
7889
7700
  };
7890
7701
  const isNamedReactApiImport = (identifier, apiNames, scopes) => {
7891
7702
  if (!isNodeOfType(identifier, "Identifier")) return false;
@@ -9835,11 +9646,6 @@ const walkParameterReferences = (pattern, state) => {
9835
9646
  const walk = (node, state) => {
9836
9647
  if (isFunctionLike$1(node)) {
9837
9648
  if (isNodeOfType(node, "FunctionDeclaration") && node.id) handleFunctionDeclaration(node, state);
9838
- const functionParams = node.params ?? [];
9839
- for (const param of functionParams) {
9840
- if (!("decorators" in param) || !Array.isArray(param.decorators)) continue;
9841
- for (const decorator of param.decorators) if (isAstNode(decorator)) walk(decorator, state);
9842
- }
9843
9649
  setNodeScope(node, state);
9844
9650
  const fnScope = pushScope(node.type === "ArrowFunctionExpression" ? "arrow-function" : "function", node, state);
9845
9651
  if ((isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && node.id) {
@@ -9852,6 +9658,7 @@ const walk = (node, state) => {
9852
9658
  });
9853
9659
  tagAsBinding(state, node.id);
9854
9660
  }
9661
+ const functionParams = node.params ?? [];
9855
9662
  handleFunctionParameters(functionParams, fnScope, state);
9856
9663
  for (const param of functionParams) walkParameterReferences(param, state);
9857
9664
  const body = node.body;
@@ -9861,9 +9668,6 @@ const walk = (node, state) => {
9861
9668
  }
9862
9669
  if (isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) {
9863
9670
  if (isNodeOfType(node, "ClassDeclaration") && node.id) handleClassDeclaration(node, state);
9864
- if (Array.isArray(node.decorators)) {
9865
- for (const decorator of node.decorators) if (isAstNode(decorator)) walk(decorator, state);
9866
- }
9867
9671
  const classScope = pushScope("class", node, state);
9868
9672
  setNodeScope(node, state);
9869
9673
  if (isNodeOfType(node, "ClassExpression") && node.id) {
@@ -10066,7 +9870,7 @@ const computeClosureCaptures = (functionNode, scopes) => {
10066
9870
  const functionScope = scopes.ownScopeFor(functionNode) ?? scopes.scopeFor(functionNode);
10067
9871
  const out = [];
10068
9872
  const seen = /* @__PURE__ */ new Set();
10069
- walkAst(functionNode, (node) => {
9873
+ const visit = (node) => {
10070
9874
  if (node !== functionNode && isFunctionLike$1(node)) {
10071
9875
  const innerCaptures = closureCaptures(node, scopes);
10072
9876
  for (const reference of innerCaptures) if (reference.resolvedSymbol && !isDescendantScope(reference.resolvedSymbol.scope, functionScope)) {
@@ -10075,7 +9879,7 @@ const computeClosureCaptures = (functionNode, scopes) => {
10075
9879
  seen.add(reference.id);
10076
9880
  }
10077
9881
  }
10078
- return false;
9882
+ return;
10079
9883
  }
10080
9884
  const reference = scopes.referenceFor(node);
10081
9885
  if (reference && reference.resolvedSymbol) {
@@ -10086,7 +9890,17 @@ const computeClosureCaptures = (functionNode, scopes) => {
10086
9890
  }
10087
9891
  }
10088
9892
  }
10089
- });
9893
+ const record = node;
9894
+ for (const key of Object.keys(record)) {
9895
+ if (key === "parent") continue;
9896
+ if (TYPE_POSITION_CHILD_KEYS.has(key)) continue;
9897
+ const child = record[key];
9898
+ if (Array.isArray(child)) {
9899
+ for (const item of child) if (isAstNode(item)) visit(item);
9900
+ } else if (isAstNode(child)) visit(child);
9901
+ }
9902
+ };
9903
+ visit(functionNode);
10090
9904
  return out;
10091
9905
  };
10092
9906
  const capturesByAnalysis = /* @__PURE__ */ new WeakMap();
@@ -14406,11 +14220,7 @@ const jsIndexMaps = defineRule({
14406
14220
  //#region src/plugin/utils/are-expressions-structurally-equal.ts
14407
14221
  const areExpressionsStructurallyEqual = (a, b) => {
14408
14222
  if (!a || !b) return a === b;
14409
- const unwrappedA = stripParenExpression(a);
14410
- const unwrappedB = stripParenExpression(b);
14411
- if (unwrappedA !== a || unwrappedB !== b) return areExpressionsStructurallyEqual(unwrappedA, unwrappedB);
14412
14223
  if (a.type !== b.type) return false;
14413
- if (isNodeOfType(a, "ThisExpression")) return true;
14414
14224
  if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
14415
14225
  if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
14416
14226
  if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
@@ -19114,7 +18924,6 @@ const jwtInsecureVerification = defineRule({
19114
18924
  });
19115
18925
  //#endregion
19116
18926
  //#region src/plugin/rules/security-scan/key-lifecycle-risk.ts
19117
- const KEY_MATERIAL_MARKER_PATTERN = /PRIVATE KEY|SSH_PRIVATE_KEY|GPG_PRIVATE_KEY|DEPLOY_KEY|SIGNING_KEY/i;
19118
18927
  const keyLifecycleRisk = defineRule({
19119
18928
  id: "key-lifecycle-risk",
19120
18929
  title: "Long-lived key material in repository",
@@ -19122,7 +18931,7 @@ const keyLifecycleRisk = defineRule({
19122
18931
  committedFilesOnly: true,
19123
18932
  recommendation: "Remove private keys from source, rotate exposed credentials, prefer short-lived deploy credentials, and document revocation/expiry for release keys.",
19124
18933
  scan: scanByPattern({
19125
- shouldScan: (file) => !TEST_CONTEXT_PATTERN.test(file.relativePath) && !DOCUMENTATION_CONTEXT_PATTERN.test(file.relativePath) && KEY_MATERIAL_MARKER_PATTERN.test(file.content),
18934
+ shouldScan: (file) => !TEST_CONTEXT_PATTERN.test(file.relativePath) && !DOCUMENTATION_CONTEXT_PATTERN.test(file.relativePath),
19126
18935
  pattern: /(?<!(?:placeholder|example|sample|dummy|fake)[\s\S]{0,40})-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----(?:\s|\\r|\\n)*[A-Za-z0-9+/=][A-Za-z0-9+/=\s]{38,}(?![^-]{0,160}\.\.\.)|\b(?:SSH_PRIVATE_KEY|GPG_PRIVATE_KEY|DEPLOY_KEY|SIGNING_KEY)\b\s*[:=]\s*["'][^"'\n]{16,}["']/i,
19127
18936
  message: "Private or long-lived release key material appears in the repository."
19128
18937
  })
@@ -19828,21 +19637,15 @@ const localRpcNativeBridgeRisk = defineRule({
19828
19637
  message: "Code appears to bridge browser code to localhost/native capabilities with weak origin or update/install checks."
19829
19638
  })
19830
19639
  });
19831
- //#endregion
19832
- //#region src/plugin/rules/security-scan/mcp-tool-capability-risk.ts
19833
- const MCP_IMPORT_PATTERN = /\bfrom\s+["']@modelcontextprotocol\/sdk[^"']*["']|\bMcpServer\b|\bMcpAgent\b/;
19834
- const MCP_IMPORT_PREFILTER_PATTERN = /@modelcontextprotocol\/sdk|\bMcpServer\b|\bMcpAgent\b/;
19835
- const MCP_TOOL_SURFACE_PATTERN = /\bserver\.\s*tool\s*\(|\bregisterTool\s*\(|\bsetRequestHandler\s*\(\s*CallToolRequestSchema/;
19836
- const MCP_TOOL_SURFACE_PREFILTER_PATTERN = /\b(?:tool|registerTool|setRequestHandler)\b/;
19837
19640
  const mcpToolCapabilityRisk = defineRule({
19838
19641
  id: "mcp-tool-capability-risk",
19839
19642
  title: "MCP tool exposes dangerous capability",
19840
19643
  severity: "warn",
19841
19644
  recommendation: "MCP tool calls run with the connecting client's authority. Validate inputs, enforce per-tool authorization, and avoid raw filesystem/shell/network access where possible.",
19842
19645
  scan: scanByPattern({
19843
- shouldScan: (file) => isProductionSourcePath(file.relativePath) && MCP_IMPORT_PREFILTER_PATTERN.test(file.content) && MCP_TOOL_SURFACE_PREFILTER_PATTERN.test(file.content) && AGENT_TOOL_DANGEROUS_CAPABILITY_PATTERN.test(file.content),
19844
- pattern: MCP_TOOL_SURFACE_PATTERN,
19845
- requireAll: [MCP_IMPORT_PATTERN, AGENT_TOOL_DANGEROUS_CAPABILITY_PATTERN],
19646
+ shouldScan: (file) => isProductionSourcePath(file.relativePath),
19647
+ pattern: /\bserver\.\s*tool\s*\(|\bregisterTool\s*\(|\bsetRequestHandler\s*\(\s*CallToolRequestSchema/,
19648
+ requireAll: [/\bfrom\s+["']@modelcontextprotocol\/sdk[^"']*["']|\bMcpServer\b|\bMcpAgent\b/, AGENT_TOOL_DANGEROUS_CAPABILITY_PATTERN],
19846
19649
  ignoreStringLiterals: true,
19847
19650
  message: "An MCP tool/resource/prompt handler appears to expose file, shell, network, or code-execution capability."
19848
19651
  })
@@ -20953,8 +20756,8 @@ const catchClauseRethrowsCaught = (handler) => {
20953
20756
  return didRethrow;
20954
20757
  };
20955
20758
  //#endregion
20956
- //#region src/plugin/utils/is-immediately-invoked-function.ts
20957
- const isImmediatelyInvokedFunction = (functionNode) => {
20759
+ //#region src/plugin/utils/find-guarding-try-statement.ts
20760
+ const isImmediatelyInvokedFunctionCallee = (functionNode) => {
20958
20761
  let wrappedCallee = functionNode;
20959
20762
  let enclosing = functionNode.parent;
20960
20763
  while (enclosing && stripParenExpression(enclosing) === functionNode) {
@@ -20963,13 +20766,11 @@ const isImmediatelyInvokedFunction = (functionNode) => {
20963
20766
  }
20964
20767
  return Boolean(enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee);
20965
20768
  };
20966
- //#endregion
20967
- //#region src/plugin/utils/find-guarding-try-statement.ts
20968
20769
  const findGuardingTryStatement = (node) => {
20969
20770
  let child = node;
20970
20771
  let ancestor = node.parent;
20971
20772
  while (ancestor) {
20972
- if (isFunctionLike$1(ancestor) && !isImmediatelyInvokedFunction(ancestor)) return null;
20773
+ if (isFunctionLike$1(ancestor) && !isImmediatelyInvokedFunctionCallee(ancestor)) return null;
20973
20774
  if (isNodeOfType(ancestor, "TryStatement") && ancestor.block === child && ancestor.handler && !catchClauseRethrowsCaught(ancestor.handler)) return ancestor;
20974
20775
  child = ancestor;
20975
20776
  ancestor = ancestor.parent ?? null;
@@ -21521,7 +21322,7 @@ const FILENAME_TO_LANG = {
21521
21322
  const resolveLang = (filename) => {
21522
21323
  return FILENAME_TO_LANG[path.extname(filename).toLowerCase()] ?? "tsx";
21523
21324
  };
21524
- const parseSourceText = ({ filename, sourceText, shouldAttachParentReferences = true }) => {
21325
+ const parseSourceText = (filename, sourceText) => {
21525
21326
  try {
21526
21327
  const result = parseSync(filename, sourceText, {
21527
21328
  astType: "ts",
@@ -21529,7 +21330,7 @@ const parseSourceText = ({ filename, sourceText, shouldAttachParentReferences =
21529
21330
  });
21530
21331
  if (result.errors.some((parseError) => parseError.severity === "Error")) return null;
21531
21332
  const parsedProgram = result.program;
21532
- if (shouldAttachParentReferences) attachParentReferences(parsedProgram);
21333
+ attachParentReferences(parsedProgram);
21533
21334
  return parsedProgram;
21534
21335
  } catch {
21535
21336
  return null;
@@ -21567,10 +21368,7 @@ const parseSourceFile = (absoluteFilePath) => {
21567
21368
  });
21568
21369
  return null;
21569
21370
  }
21570
- const parsedProgram = parseSourceText({
21571
- filename: absoluteFilePath,
21572
- sourceText
21573
- });
21371
+ const parsedProgram = parseSourceText(absoluteFilePath, sourceText);
21574
21372
  parseCache.set(absoluteFilePath, {
21575
21373
  mtimeMs: fileStat.mtimeMs,
21576
21374
  size: fileStat.size,
@@ -22102,8 +21900,6 @@ const DOM_QUERY_MEMBER_NAMES = new Set([
22102
21900
  ]);
22103
21901
  const LAYOUT_MEASUREMENT_MEMBER_NAMES = new Set([
22104
21902
  "current",
22105
- "textContent",
22106
- "innerText",
22107
21903
  "scrollWidth",
22108
21904
  "clientWidth",
22109
21905
  "offsetWidth",
@@ -22125,7 +21921,7 @@ const POST_MOUNT_GLOBAL_NAMES = new Set([
22125
21921
  "navigator"
22126
21922
  ]);
22127
21923
  const REF_FACTORY_CALLEE_NAMES = new Set(["useRef", "createRef"]);
22128
- const hasRefLikeName = (name) => name === "ref" || name.endsWith("Ref") || name.endsWith("ref") || name.endsWith("Node") || name.endsWith("node") || name.endsWith("Element") || name.endsWith("element");
21924
+ const hasRefLikeName = (name) => name === "ref" || name.endsWith("Ref") || name.endsWith("ref");
22129
21925
  const isRefFactoryInitializer = (init) => {
22130
21926
  if (!init || !isNodeOfType(init, "CallExpression")) return false;
22131
21927
  const callee = init.callee;
@@ -22208,37 +22004,88 @@ const readsPostMountValue = (root) => {
22208
22004
  return found;
22209
22005
  };
22210
22006
  //#endregion
22211
- //#region src/plugin/rules/state-and-effects/utils/effect/get-ast-child-keys.ts
22212
- const getAstChildKeys = (node) => visitorKeys[node.type] ?? Object.keys(node).filter((key) => key !== "parent");
22007
+ //#region src/plugin/rules/state-and-effects/utils/effect/constants.ts
22008
+ const TYPESCRIPT_VISITOR_KEYS = {
22009
+ TSAsExpression: ["expression", "typeAnnotation"],
22010
+ TSNonNullExpression: ["expression"],
22011
+ TSSatisfiesExpression: ["expression", "typeAnnotation"],
22012
+ TSTypeAssertion: ["typeAnnotation", "expression"],
22013
+ TSInstantiationExpression: ["expression", "typeArguments"]
22014
+ };
22015
+ const VISITOR_KEYS = {
22016
+ ...eslintVisitorKeys.KEYS,
22017
+ ...TYPESCRIPT_VISITOR_KEYS
22018
+ };
22213
22019
  //#endregion
22214
22020
  //#region src/plugin/rules/state-and-effects/utils/effect/get-program-analysis.ts
22215
22021
  const programToAnalysis = /* @__PURE__ */ new WeakMap();
22022
+ const stripAndRecordParents = (root) => {
22023
+ const restorations = [];
22024
+ const seen = /* @__PURE__ */ new WeakSet();
22025
+ const visit = (value) => {
22026
+ if (!value || typeof value !== "object") return;
22027
+ if (seen.has(value)) return;
22028
+ seen.add(value);
22029
+ if (Array.isArray(value)) {
22030
+ for (const item of value) visit(item);
22031
+ return;
22032
+ }
22033
+ const record = value;
22034
+ if (!("type" in record)) return;
22035
+ if ("parent" in record) {
22036
+ restorations.push({
22037
+ node: record,
22038
+ originalParent: record.parent
22039
+ });
22040
+ record.parent = null;
22041
+ }
22042
+ for (const key of Object.keys(record)) {
22043
+ if (key === "parent") continue;
22044
+ visit(record[key]);
22045
+ }
22046
+ };
22047
+ visit(root);
22048
+ return restorations;
22049
+ };
22050
+ const restoreParents = (restorations) => {
22051
+ for (const restoration of restorations) restoration.node.parent = restoration.originalParent;
22052
+ };
22216
22053
  const getProgramAnalysis = (anyNode) => {
22217
22054
  const programNode = findProgramRoot(anyNode);
22218
22055
  if (!programNode) return null;
22219
22056
  const cached = programToAnalysis.get(programNode);
22220
22057
  if (cached) return cached;
22221
- const analysis = {
22222
- programNode,
22223
- scopeManager: analyze(programNode, {
22058
+ const restorations = stripAndRecordParents(programNode);
22059
+ let scopeManager;
22060
+ try {
22061
+ scopeManager = analyze(programNode, {
22224
22062
  ecmaVersion: 2024,
22225
22063
  sourceType: "module",
22226
- childVisitorKeys: RUNTIME_VISITOR_KEYS,
22227
- fallback: getAstChildKeys
22228
- }),
22229
- scopeByNode: /* @__PURE__ */ new WeakMap(),
22230
- referenceByIdentifier: /* @__PURE__ */ new WeakMap()
22064
+ childVisitorKeys: VISITOR_KEYS,
22065
+ fallback: "iteration"
22066
+ });
22067
+ } finally {
22068
+ restoreParents(restorations);
22069
+ }
22070
+ const analysis = {
22071
+ programNode,
22072
+ scopeManager
22231
22073
  };
22232
22074
  programToAnalysis.set(programNode, analysis);
22233
22075
  return analysis;
22234
22076
  };
22235
- const getScopeForNode = (node, analysis) => {
22077
+ const scopeByNodeCache = /* @__PURE__ */ new WeakMap();
22078
+ const getScopeForNode = (node, manager) => {
22236
22079
  if (!node.range) return null;
22237
- const scopeByNode = analysis.scopeByNode;
22080
+ let scopeByNode = scopeByNodeCache.get(manager);
22081
+ if (!scopeByNode) {
22082
+ scopeByNode = /* @__PURE__ */ new WeakMap();
22083
+ scopeByNodeCache.set(manager, scopeByNode);
22084
+ }
22238
22085
  if (scopeByNode.has(node)) return scopeByNode.get(node) ?? null;
22239
22086
  let bestScope = null;
22240
22087
  let bestSize = Infinity;
22241
- for (const scope of analysis.scopeManager.scopes) {
22088
+ for (const scope of manager.scopes) {
22242
22089
  const block = scope.block;
22243
22090
  if (!block?.range) continue;
22244
22091
  if (node.range[0] < block.range[0] || node.range[1] > block.range[1]) continue;
@@ -22253,6 +22100,7 @@ const getScopeForNode = (node, analysis) => {
22253
22100
  };
22254
22101
  //#endregion
22255
22102
  //#region src/plugin/rules/state-and-effects/utils/effect/ast.ts
22103
+ const getChildKeys = (node) => VISITOR_KEYS[node.type] ?? Object.keys(node).filter((key) => key !== "parent");
22256
22104
  const HOOK_NAME_PATTERN$2 = /^use[A-Z0-9]/;
22257
22105
  const isInsideCallbackArgumentOf = (identifier, initializer) => {
22258
22106
  if (!isNodeOfType(initializer, "CallExpression") && !isNodeOfType(initializer, "NewExpression")) return false;
@@ -22288,7 +22136,7 @@ const descend = (node, visit, visited = /* @__PURE__ */ new Set()) => {
22288
22136
  if (visited.has(node)) return;
22289
22137
  visit(node);
22290
22138
  visited.add(node);
22291
- const keys = getAstChildKeys(node);
22139
+ const keys = getChildKeys(node);
22292
22140
  const record = node;
22293
22141
  for (const key of keys) {
22294
22142
  const child = record[key];
@@ -22321,11 +22169,16 @@ const findDownstreamNodes = (topNode, type) => {
22321
22169
  });
22322
22170
  return nodes;
22323
22171
  };
22172
+ const refByIdentifierCache = /* @__PURE__ */ new WeakMap();
22324
22173
  const getRef = (analysis, identifier) => {
22325
- const refByIdentifier = analysis.referenceByIdentifier;
22174
+ let refByIdentifier = refByIdentifierCache.get(analysis);
22175
+ if (!refByIdentifier) {
22176
+ refByIdentifier = /* @__PURE__ */ new WeakMap();
22177
+ refByIdentifierCache.set(analysis, refByIdentifier);
22178
+ }
22326
22179
  if (refByIdentifier.has(identifier)) return refByIdentifier.get(identifier) ?? null;
22327
22180
  let resolvedReference = null;
22328
- const scope = getScopeForNode(identifier, analysis);
22181
+ const scope = getScopeForNode(identifier, analysis.scopeManager);
22329
22182
  if (scope) {
22330
22183
  for (const reference of scope.references) if (reference.identifier === identifier) {
22331
22184
  resolvedReference = reference;
@@ -22573,7 +22426,7 @@ const isReactFunctionalHOC = (analysis, node) => {
22573
22426
  const isWrappedSeparately = () => {
22574
22427
  if (!isNodeOfType(node.id, "Identifier")) return false;
22575
22428
  const bindingName = node.id.name;
22576
- const containingScope = getScopeForNode(node, analysis);
22429
+ const containingScope = getScopeForNode(node, analysis.scopeManager);
22577
22430
  if (!containingScope) return false;
22578
22431
  const variable = containingScope.variables.find((v) => v.name === bindingName);
22579
22432
  if (!variable) return false;
@@ -22804,14 +22657,11 @@ const hasCleanupReturn = (analysis, node, visited = /* @__PURE__ */ new WeakSet(
22804
22657
  if (isNodeOfType(node, "ReturnStatement") && node.argument != null) return isCleanupReturnArgument(analysis, node.argument);
22805
22658
  if (!isNodeOfType(node, "BlockStatement") && isFunctionLike$1(node)) return false;
22806
22659
  const record = node;
22807
- const childKeys = getAstChildKeys(node);
22808
- for (let keyIndex = 0; keyIndex < childKeys.length; keyIndex += 1) {
22809
- const value = record[childKeys[keyIndex]];
22810
- if (Array.isArray(value)) for (let itemIndex = 0; itemIndex < value.length; itemIndex += 1) {
22811
- const item = value[itemIndex];
22812
- if (isAstNode(item) && hasCleanupReturn(analysis, item, visited)) return true;
22813
- }
22814
- else if (isAstNode(value) && hasCleanupReturn(analysis, value, visited)) return true;
22660
+ for (const [key, value] of Object.entries(record)) {
22661
+ if (key === "parent") continue;
22662
+ if (Array.isArray(value)) {
22663
+ if (value.some((item) => isAstNode(item) && hasCleanupReturn(analysis, item, visited))) return true;
22664
+ } else if (isAstNode(value) && hasCleanupReturn(analysis, value, visited)) return true;
22815
22665
  }
22816
22666
  return false;
22817
22667
  };
@@ -23102,7 +22952,7 @@ const STORAGE_GLOBAL_NAMES$1 = new Set([
23102
22952
  "localStorage",
23103
22953
  "sessionStorage"
23104
22954
  ]);
23105
- const getStaticMemberName$1 = (node) => {
22955
+ const getStaticMemberName = (node) => {
23106
22956
  if (!isNodeOfType(node, "MemberExpression")) return null;
23107
22957
  if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
23108
22958
  if (node.computed && isNodeOfType(node.property, "Literal")) return typeof node.property.value === "string" ? node.property.value : null;
@@ -23117,7 +22967,7 @@ const getCallCalleeName$1 = (callExpression) => {
23117
22967
  if (!isNodeOfType(callExpression, "CallExpression")) return null;
23118
22968
  const callee = stripParenExpression(callExpression.callee);
23119
22969
  if (isNodeOfType(callee, "Identifier")) return callee.name;
23120
- return getStaticMemberName$1(callee);
22970
+ return getStaticMemberName(callee);
23121
22971
  };
23122
22972
  const getFunctionParameters = (functionNode) => isFunctionLike$1(functionNode) ? functionNode.params ?? [] : [];
23123
22973
  const getIdentifierBindingIdentity = (analysis, identifier) => {
@@ -23233,14 +23083,14 @@ const localUseEventPreservesCallback = (analysis, callee) => {
23233
23083
  if (!isNodeOfType(child, "CallExpression")) return;
23234
23084
  const forwardedCallee = stripParenExpression(child.callee);
23235
23085
  if (!isNodeOfType(forwardedCallee, "MemberExpression")) return;
23236
- if (getStaticMemberName$1(forwardedCallee) !== "current") return;
23086
+ if (getStaticMemberName(forwardedCallee) !== "current") return;
23237
23087
  if (!isNodeOfType(forwardedCallee.object, "Identifier")) return;
23238
23088
  const refReference = getRef(analysis, forwardedCallee.object);
23239
23089
  if (!callbackRefDeclarators.find((declarator) => refReference?.resolved?.defs.some((definition) => definition.node === declarator)) || !refReference?.resolved) return;
23240
23090
  if (!refReference.resolved.references.some((candidateReference) => {
23241
23091
  const memberExpression = candidateReference.identifier.parent;
23242
23092
  const assignmentExpression = memberExpression?.parent;
23243
- if (!memberExpression || !isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName$1(memberExpression) !== "current" || !assignmentExpression || !isNodeOfType(assignmentExpression, "AssignmentExpression") || assignmentExpression.left !== memberExpression) return false;
23093
+ if (!memberExpression || !isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !assignmentExpression || !isNodeOfType(assignmentExpression, "AssignmentExpression") || assignmentExpression.left !== memberExpression) return false;
23244
23094
  return getIdentifierBindingIdentity(analysis, assignmentExpression.right) !== callbackBinding;
23245
23095
  })) forwardsCallback = true;
23246
23096
  });
@@ -23265,7 +23115,7 @@ const resolveWrappedCallable = (analysis, node) => {
23265
23115
  return callback && isFunctionLike$1(callback) ? callback : null;
23266
23116
  }
23267
23117
  if (!isNodeOfType(candidate, "MemberExpression")) return null;
23268
- if (getStaticMemberName$1(candidate) !== "current") return null;
23118
+ if (getStaticMemberName(candidate) !== "current") return null;
23269
23119
  if (!isNodeOfType(candidate.object, "Identifier")) return null;
23270
23120
  const reference = getRef(analysis, candidate.object);
23271
23121
  const declarator = reference?.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
@@ -23277,7 +23127,7 @@ const resolveWrappedCallable = (analysis, node) => {
23277
23127
  return Boolean(reference?.resolved?.references.some((candidateReference) => {
23278
23128
  const member = candidateReference.identifier.parent;
23279
23129
  const assignment = member?.parent;
23280
- return Boolean(member && isNodeOfType(member, "MemberExpression") && getStaticMemberName$1(member) === "current" && assignment && isNodeOfType(assignment, "AssignmentExpression") && assignment.left === member);
23130
+ return Boolean(member && isNodeOfType(member, "MemberExpression") && getStaticMemberName(member) === "current" && assignment && isNodeOfType(assignment, "AssignmentExpression") && assignment.left === member);
23281
23131
  })) ? null : initializer;
23282
23132
  };
23283
23133
  const functionInvokesItself = (analysis, functionNode) => {
@@ -23316,7 +23166,7 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilena
23316
23166
  if (!isNodeOfType(child, "CallExpression")) return;
23317
23167
  const callee = stripParenExpression(child.callee);
23318
23168
  const calleeName = getCallCalleeName$1(child);
23319
- const memberName = getStaticMemberName$1(callee);
23169
+ const memberName = getStaticMemberName(callee);
23320
23170
  const isDeferringCall = calleeName !== null && DEFERRING_CALLEE_NAMES.has(calleeName) || memberName !== null && DEFERRING_MEMBER_NAMES.has(memberName);
23321
23171
  const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES$1.has(memberName) && isNodeOfType(callee, "MemberExpression");
23322
23172
  const enqueueFrame = (callableNode, argumentsForCallable, isDeferred, introducedBindings, allowWithoutInvocationEvidence = false) => {
@@ -23480,9 +23330,9 @@ const analyzeHelperExpression = (expression, environment, usedParameterIndices)
23480
23330
  const calleeRoot = getMemberRoot(callee);
23481
23331
  const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && !environment.parameterIndices.has(callee.name) && !environment.recursiveNames.has(callee.name) && !environment.shadowedGlobalNames.has(callee.name);
23482
23332
  const namespaceName = isNodeOfType(calleeRoot, "Identifier") && !environment.parameterIndices.has(calleeRoot.name) && !environment.recursiveNames.has(calleeRoot.name) && !environment.shadowedGlobalNames.has(calleeRoot.name) ? calleeRoot.name : null;
23483
- const namespaceMemberName = getStaticMemberName$1(callee);
23333
+ const namespaceMemberName = getStaticMemberName(callee);
23484
23334
  const isPureNamespaceCall = isNodeOfType(callee, "MemberExpression") && namespaceName !== null && namespaceMemberName !== null && PURE_HELPER_NAMESPACE_MEMBER_NAMES.get(namespaceName)?.has(namespaceMemberName) === true;
23485
- const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName$1(callee) ?? "");
23335
+ const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
23486
23336
  if (!isPureGlobalCall && !isPureNamespaceCall && !isPureMemberTransform) return false;
23487
23337
  if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression") && !analyzeHelperExpression(callee.object, environment, usedParameterIndices)) return false;
23488
23338
  return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
@@ -23556,7 +23406,7 @@ const isLocallyConstructedObjectMember = (reference, memberExpression) => isNode
23556
23406
  return isNodeOfType(definitionNode, "VariableDeclarator") && Boolean(definitionNode.init) && (isNodeOfType(definitionNode.init, "ObjectExpression") || isNodeOfType(definitionNode.init, "ArrayExpression"));
23557
23407
  }) === true;
23558
23408
  const getUseRefDeclarator = (analysis, memberExpression) => {
23559
- if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName$1(memberExpression) !== "current" || !isNodeOfType(memberExpression.object, "Identifier")) return null;
23409
+ if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !isNodeOfType(memberExpression.object, "Identifier")) return null;
23560
23410
  return getRef(analysis, memberExpression.object)?.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator") && isNodeOfType(definitionNode.init, "CallExpression") && getCallCalleeName$1(definitionNode.init) === "useRef") ?? null;
23561
23411
  };
23562
23412
  const collectValueEvidence = (analysis, expression, frame, remainingCallFrames, visitedBindings = /* @__PURE__ */ new Set()) => {
@@ -23625,7 +23475,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23625
23475
  return collectValueEvidence(analysis, initializer.init, frame, remainingCallFrames, visitedBindings);
23626
23476
  }
23627
23477
  if (isNodeOfType(node, "MemberExpression")) {
23628
- if (getStaticMemberName$1(node) === "current" && isNodeOfType(node.object, "Identifier")) {
23478
+ if (getStaticMemberName(node) === "current" && isNodeOfType(node.object, "Identifier")) {
23629
23479
  const refBinding = getRef(analysis, node.object)?.resolved;
23630
23480
  const refDeclarator = useRefDeclarator;
23631
23481
  if (refBinding && refDeclarator && isNodeOfType(refDeclarator, "VariableDeclarator") && isNodeOfType(refDeclarator.init, "CallExpression")) {
@@ -23641,7 +23491,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23641
23491
  if (candidateReference.init) continue;
23642
23492
  const identifier = candidateReference.identifier;
23643
23493
  const member = identifier.parent;
23644
- if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName$1(member) !== "current") {
23494
+ if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName(member) !== "current") {
23645
23495
  evidence.hasUnknownSource = true;
23646
23496
  continue;
23647
23497
  }
@@ -23678,7 +23528,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23678
23528
  const callee = stripParenExpression(node.callee);
23679
23529
  const calleeRoot = getMemberRoot(callee);
23680
23530
  const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && getIdentifierBindingIdentity(analysis, callee) === null || isNodeOfType(calleeRoot, "Identifier") && PURE_GLOBAL_NAMESPACE_NAMES.has(calleeRoot.name) && getIdentifierBindingIdentity(analysis, calleeRoot) === null;
23681
- const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName$1(callee) ?? "");
23531
+ const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
23682
23532
  if (isPureGlobalCall || isPureMemberTransform) {
23683
23533
  if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression")) mergeEvidence(evidence, collectValueEvidence(analysis, callee.object, frame, remainingCallFrames, new Set(visitedBindings)));
23684
23534
  for (const argument of node.arguments ?? []) {
@@ -24148,26 +23998,6 @@ const STATEFUL_HTML_DESCENDANT_TAGS = new Set([
24148
23998
  "dialog",
24149
23999
  "canvas"
24150
24000
  ]);
24151
- const STATEFUL_HTML_ATTRIBUTE_NAMES = new Set([
24152
- "autofocus",
24153
- "contenteditable",
24154
- "draggable",
24155
- "tabindex"
24156
- ]);
24157
- const isStaticallyFalseAttributeValue = (attribute) => {
24158
- if (!isNodeOfType(attribute, "JSXAttribute") || !attribute.value) return false;
24159
- const value = isNodeOfType(attribute.value, "JSXExpressionContainer") ? attribute.value.expression : attribute.value;
24160
- return isNodeOfType(value, "Literal") && (value.value === false || value.value === "false");
24161
- };
24162
- const hasStatefulHtmlAttribute = (openingElement) => {
24163
- if (!isNodeOfType(openingElement, "JSXOpeningElement")) return false;
24164
- return openingElement.attributes.some((attribute) => {
24165
- if (!isNodeOfType(attribute, "JSXAttribute") || !isNodeOfType(attribute.name, "JSXIdentifier")) return false;
24166
- const attributeName = attribute.name.name.toLowerCase();
24167
- if (!STATEFUL_HTML_ATTRIBUTE_NAMES.has(attributeName)) return false;
24168
- return attributeName === "tabindex" || !isStaticallyFalseAttributeValue(attribute);
24169
- });
24170
- };
24171
24001
  const STATEFUL_DESCENDANT_SCAN_BUDGET = 200;
24172
24002
  const rootIdentifierNameOf = (expression) => {
24173
24003
  let object = expression;
@@ -24185,8 +24015,7 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
24185
24015
  budget -= 1;
24186
24016
  const node = stack.pop();
24187
24017
  if (isNodeOfType(node, "JSXElement")) {
24188
- const opening = node.openingElement;
24189
- const name = opening.name;
24018
+ const name = node.openingElement.name;
24190
24019
  if (name && isNodeOfType(name, "JSXIdentifier")) {
24191
24020
  const tagName = name.name;
24192
24021
  const firstChar = tagName.charCodeAt(0);
@@ -24194,7 +24023,6 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
24194
24023
  if (STATEFUL_HTML_DESCENDANT_TAGS.has(tagName)) return true;
24195
24024
  }
24196
24025
  if (name && isNodeOfType(name, "JSXMemberExpression")) return true;
24197
- if (hasStatefulHtmlAttribute(opening)) return true;
24198
24026
  const children = node.children ?? [];
24199
24027
  for (const child of children) stack.push(child);
24200
24028
  continue;
@@ -24288,7 +24116,6 @@ const ARITHMETIC_KEY_OPERATORS = new Set([
24288
24116
  "/",
24289
24117
  "%"
24290
24118
  ]);
24291
- const bindingMutationResultsByProgram = /* @__PURE__ */ new WeakMap();
24292
24119
  const extractCandidateIdentifiers = (expression) => {
24293
24120
  const node = stripParenExpression(expression);
24294
24121
  if (isNodeOfType(node, "Identifier")) return [node];
@@ -24329,13 +24156,6 @@ const isArrayFromCall = (node) => {
24329
24156
  const isBindingReassignedOrMutated = (referenceNode, bindingName) => {
24330
24157
  const programRoot = findProgramRoot(referenceNode);
24331
24158
  if (!programRoot) return false;
24332
- let mutationResultsByBindingName = bindingMutationResultsByProgram.get(programRoot);
24333
- if (!mutationResultsByBindingName) {
24334
- mutationResultsByBindingName = /* @__PURE__ */ new Map();
24335
- bindingMutationResultsByProgram.set(programRoot, mutationResultsByBindingName);
24336
- }
24337
- const cachedResult = mutationResultsByBindingName.get(bindingName);
24338
- if (cachedResult !== void 0) return cachedResult;
24339
24159
  let didFindWrite = false;
24340
24160
  walkAst(programRoot, (child) => {
24341
24161
  if (didFindWrite) return false;
@@ -24352,7 +24172,6 @@ const isBindingReassignedOrMutated = (referenceNode, bindingName) => {
24352
24172
  return false;
24353
24173
  }
24354
24174
  });
24355
- mutationResultsByBindingName.set(bindingName, didFindWrite);
24356
24175
  return didFindWrite;
24357
24176
  };
24358
24177
  /**
@@ -24754,14 +24573,6 @@ const templateHasOuterMemberIdentity = (template, bindingFunction) => {
24754
24573
  }
24755
24574
  return false;
24756
24575
  };
24757
- const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
24758
- const referencedItemNames = /* @__PURE__ */ new Set();
24759
- for (const expression of template.expressions ?? []) {
24760
- const unwrappedExpression = stripParenExpression(expression);
24761
- if (isNodeOfType(unwrappedExpression, "Identifier") && itemNames.has(unwrappedExpression.name)) referencedItemNames.add(unwrappedExpression.name);
24762
- }
24763
- return referencedItemNames;
24764
- };
24765
24576
  const forLoopTestReadsDataLength = (test) => {
24766
24577
  let didFindLengthRead = false;
24767
24578
  walkAst(test, (child) => {
@@ -24898,11 +24709,9 @@ const noArrayIndexAsKey = defineRule({
24898
24709
  } else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
24899
24710
  const jsxElement = openingElement.parent;
24900
24711
  if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
24901
- const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
24902
- const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET;
24903
24712
  if (!containsStatefulDescendant(jsxElement, {
24904
- memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET,
24905
- bareIdentifierNames: primitiveItemNames.size > 0 ? new Set([...derivedNames, ...primitiveItemNames]) : derivedNames
24713
+ memberRootNames: INLINE_TEXT_LEAF_TAGS.has(elementName.name) ? itemNames : EMPTY_NAME_SET,
24714
+ bareIdentifierNames: derivedNames
24906
24715
  })) return;
24907
24716
  }
24908
24717
  }
@@ -25463,10 +25272,10 @@ const declaresAnyDependency = (manifest) => DEPENDENCY_SECTION_NAMES.some((secti
25463
25272
  const section = manifest[sectionName];
25464
25273
  return typeof section === "object" && section !== null && Object.keys(section).length > 0;
25465
25274
  });
25466
- const declaresDependency = (manifest, dependencyName) => DEPENDENCY_SECTION_NAMES.some((sectionName) => {
25467
- const section = manifest[sectionName];
25468
- return typeof section === "object" && section !== null && Object.prototype.propertyIsEnumerable.call(section, dependencyName);
25469
- });
25275
+ const declaresDependency = (manifest, dependencyName) => {
25276
+ for (const declaredName of iterateDependencyNames(manifest)) if (declaredName === dependencyName) return true;
25277
+ return false;
25278
+ };
25470
25279
  const cachedPlatformByManifest = /* @__PURE__ */ new WeakMap();
25471
25280
  const classifyPackagePlatform = (filename) => {
25472
25281
  const manifest = readNearestPackageManifest(filename);
@@ -25713,23 +25522,25 @@ const isCallArgumentFunctionExpression = (node) => {
25713
25522
  return parent.arguments.some((argumentNode) => argumentNode === node);
25714
25523
  };
25715
25524
  const isNestedRenderEvidenceBoundary = (node) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isCallArgumentFunctionExpression(node);
25716
- const containsRenderOutput$1 = (rootNode, scopes) => {
25717
- let hasRenderOutput = false;
25718
- walkAst(rootNode, (node) => {
25719
- if (hasRenderOutput) return false;
25720
- if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
25721
- if (node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS)) {
25722
- hasRenderOutput = true;
25723
- return false;
25724
- }
25725
- });
25726
- return hasRenderOutput;
25525
+ const containsRenderOutput$1 = (node, rootNode, scopes) => {
25526
+ if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
25527
+ if (node.type === "JSXElement" || node.type === "JSXFragment") return true;
25528
+ if (isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS)) return true;
25529
+ const nodeRecord = node;
25530
+ for (const key of Object.keys(nodeRecord)) {
25531
+ if (key === "parent") continue;
25532
+ const child = nodeRecord[key];
25533
+ if (Array.isArray(child)) {
25534
+ for (const innerChild of child) if (isAstNode(innerChild) && containsRenderOutput$1(innerChild, rootNode, scopes)) return true;
25535
+ } else if (isAstNode(child) && containsRenderOutput$1(child, rootNode, scopes)) return true;
25536
+ }
25537
+ return false;
25727
25538
  };
25728
25539
  const renderOutputCache = /* @__PURE__ */ new WeakMap();
25729
25540
  const functionContainsReactRenderOutput = (functionNode, scopes) => {
25730
25541
  const cachedEntry = renderOutputCache.get(functionNode);
25731
25542
  if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
25732
- const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
25543
+ const hasRenderOutput = containsRenderOutput$1(functionNode, functionNode, scopes);
25733
25544
  renderOutputCache.set(functionNode, {
25734
25545
  scopes,
25735
25546
  hasRenderOutput
@@ -27535,7 +27346,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
27535
27346
  let ancestor = setStateCall.parent;
27536
27347
  while (ancestor) {
27537
27348
  if (!lifecycleMember) {
27538
- if (FUNCTION_NODE_TYPES$1.has(ancestor.type) && (!isImmediatelyInvokedFunction(ancestor) || !("async" in ancestor) || ancestor.async === true)) nestedFunctionCount += 1;
27349
+ if (FUNCTION_NODE_TYPES$1.has(ancestor.type)) nestedFunctionCount += 1;
27539
27350
  if (isLifecycleMember(ancestor, lifecycleNames)) lifecycleMember = ancestor;
27540
27351
  } else if (isEs5Component(ancestor) || isEs6Component(ancestor)) {
27541
27352
  if (nestedFunctionCount > 1 && !options.disallowInNestedFunctions) return false;
@@ -27737,7 +27548,7 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
27737
27548
  const body = lifecycleFunction.body;
27738
27549
  if (!body) return derivedNames;
27739
27550
  walkAst(body, (node) => {
27740
- if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
27551
+ if (FUNCTION_NODE_TYPES.has(node.type)) return false;
27741
27552
  if (!isNodeOfType(node, "VariableDeclarator")) return;
27742
27553
  const init = node.init;
27743
27554
  if (!init) return;
@@ -27747,67 +27558,6 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
27747
27558
  return derivedNames;
27748
27559
  };
27749
27560
  const isStatefulOperand = (node, paramNames, derivedNames) => referencesAnyName(node, paramNames) || referencesAnyName(node, derivedNames) || containsThisStateOrProps(node);
27750
- const getStaticMemberName = (node) => {
27751
- if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
27752
- return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
27753
- };
27754
- const getThisStateFieldName = (node) => {
27755
- const unwrappedNode = stripParenExpression(node);
27756
- if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
27757
- const object = stripParenExpression(unwrappedNode.object);
27758
- if (!isNodeOfType(object, "MemberExpression") || !isNodeOfType(stripParenExpression(object.object), "ThisExpression") || getStaticMemberName(object) !== "state") return null;
27759
- return getStaticMemberName(unwrappedNode);
27760
- };
27761
- const collectLocalInitializers = (lifecycleFunction) => {
27762
- const initializers = /* @__PURE__ */ new Map();
27763
- const body = lifecycleFunction.body;
27764
- if (!body) return initializers;
27765
- walkAst(body, (node) => {
27766
- if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
27767
- if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.init) initializers.set(node.id.name, node.init);
27768
- });
27769
- return initializers;
27770
- };
27771
- const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
27772
- if (readsPostMountValue(node)) return true;
27773
- const referencedNames = /* @__PURE__ */ new Set();
27774
- collectReferenceIdentifierNames(node, referencedNames);
27775
- for (const referencedName of referencedNames) {
27776
- if (visitedNames.has(referencedName)) continue;
27777
- const initializer = localInitializers.get(referencedName);
27778
- if (!initializer) continue;
27779
- if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
27780
- }
27781
- return false;
27782
- };
27783
- const getSetStateFieldValue = (setStateCall, fieldName) => {
27784
- if (!isNodeOfType(setStateCall, "CallExpression")) return null;
27785
- const argument = setStateCall.arguments?.[0];
27786
- if (!argument || !isNodeOfType(argument, "ObjectExpression")) return null;
27787
- for (const property of argument.properties ?? []) {
27788
- if (!isNodeOfType(property, "Property") || property.computed === true) continue;
27789
- if ((isNodeOfType(property.key, "Identifier") && property.key.name || isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" && property.key.value || null) === fieldName) return property.value;
27790
- }
27791
- return null;
27792
- };
27793
- const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
27794
- let qualifies = false;
27795
- walkAst(test, (node) => {
27796
- if (qualifies) return false;
27797
- if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
27798
- const leftFieldName = getThisStateFieldName(node.left);
27799
- const rightFieldName = getThisStateFieldName(node.right);
27800
- const fieldName = leftFieldName ?? rightFieldName;
27801
- const comparedValue = leftFieldName ? node.right : node.left;
27802
- if (!fieldName || !leftFieldName && !rightFieldName) return;
27803
- const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
27804
- if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
27805
- if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
27806
- qualifies = true;
27807
- return false;
27808
- });
27809
- return qualifies;
27810
- };
27811
27561
  const isDiffGuardTest = (test, paramNames, derivedNames) => {
27812
27562
  if (referencesAnyName(test, paramNames)) return true;
27813
27563
  let qualifies = false;
@@ -27815,7 +27565,7 @@ const isDiffGuardTest = (test, paramNames, derivedNames) => {
27815
27565
  if (qualifies) return false;
27816
27566
  if (!isNodeOfType(node, "BinaryExpression")) return;
27817
27567
  if (!EQUALITY_OPERATORS.has(node.operator)) return;
27818
- if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames) && (referencesAnyName(node.left, derivedNames) || referencesAnyName(node.right, derivedNames))) {
27568
+ if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames)) {
27819
27569
  qualifies = true;
27820
27570
  return false;
27821
27571
  }
@@ -27828,12 +27578,11 @@ const isInsideDiffGuard = (setStateCall) => {
27828
27578
  const paramNames = /* @__PURE__ */ new Set();
27829
27579
  for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
27830
27580
  const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
27831
- const localInitializers = collectLocalInitializers(lifecycleFunction);
27832
27581
  let child = setStateCall;
27833
27582
  let ancestor = setStateCall.parent;
27834
27583
  while (ancestor && ancestor !== lifecycleFunction) {
27835
27584
  const guardTest = isNodeOfType(ancestor, "IfStatement") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "ConditionalExpression") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right && ancestor.left || null;
27836
- if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers))) return true;
27585
+ if (guardTest && isDiffGuardTest(guardTest, paramNames, derivedNames)) return true;
27837
27586
  child = ancestor;
27838
27587
  ancestor = ancestor.parent ?? null;
27839
27588
  }
@@ -28014,16 +27763,7 @@ const producesOpaqueInstanceValue = (expression) => {
28014
27763
  const collectSetterValueObservations = (componentBody, setterNames) => {
28015
27764
  const plainFedSetterNames = /* @__PURE__ */ new Set();
28016
27765
  const opaqueFedSetterNames = /* @__PURE__ */ new Set();
28017
- const callbackRefSetterNames = /* @__PURE__ */ new Set();
28018
27766
  walkComponentRespectingShadows(componentBody, /* @__PURE__ */ new Set(), (node, currentlyShadowed) => {
28019
- if (isNodeOfType(node, "JSXAttribute")) {
28020
- const attributeName = node.name;
28021
- if (isNodeOfType(attributeName, "JSXIdentifier") && attributeName.name === "ref" && node.value && isNodeOfType(node.value, "JSXExpressionContainer")) {
28022
- const expression = stripParenExpression(node.value.expression);
28023
- if (isNodeOfType(expression, "Identifier") && setterNames.has(expression.name)) callbackRefSetterNames.add(expression.name);
28024
- }
28025
- return;
28026
- }
28027
27767
  if (!isNodeOfType(node, "CallExpression")) return;
28028
27768
  if (!isNodeOfType(node.callee, "Identifier")) return;
28029
27769
  const setterName = node.callee.name;
@@ -28040,10 +27780,21 @@ const collectSetterValueObservations = (componentBody, setterNames) => {
28040
27780
  }, true);
28041
27781
  return {
28042
27782
  plainFedSetterNames,
28043
- opaqueFedSetterNames,
28044
- callbackRefSetterNames
27783
+ opaqueFedSetterNames
28045
27784
  };
28046
27785
  };
27786
+ const collectCallbackRefSetterNames = (componentBody) => {
27787
+ const callbackRefSetterNames = /* @__PURE__ */ new Set();
27788
+ walkAst(componentBody, (node) => {
27789
+ if (!isNodeOfType(node, "JSXAttribute")) return;
27790
+ const attributeName = node.name;
27791
+ if (isNodeOfType(attributeName, "JSXIdentifier") && attributeName.name === "ref" && node.value && isNodeOfType(node.value, "JSXExpressionContainer")) {
27792
+ const expression = stripParenExpression(node.value.expression);
27793
+ if (isNodeOfType(expression, "Identifier")) callbackRefSetterNames.add(expression.name);
27794
+ }
27795
+ });
27796
+ return callbackRefSetterNames;
27797
+ };
28047
27798
  const collectFunctionLocalBindings = (functionNode) => {
28048
27799
  const localBindings = /* @__PURE__ */ new Set();
28049
27800
  if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return localBindings;
@@ -28055,8 +27806,8 @@ const collectFunctionLocalBindings = (functionNode) => {
28055
27806
  return localBindings;
28056
27807
  };
28057
27808
  const collectBlockScopedBindings = (node) => {
27809
+ const blockBindings = /* @__PURE__ */ new Set();
28058
27810
  if (isNodeOfType(node, "BlockStatement")) {
28059
- const blockBindings = /* @__PURE__ */ new Set();
28060
27811
  for (const statement of node.body ?? []) {
28061
27812
  if (!isNodeOfType(statement, "VariableDeclaration")) continue;
28062
27813
  for (const declarator of statement.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
@@ -28064,22 +27815,17 @@ const collectBlockScopedBindings = (node) => {
28064
27815
  return blockBindings;
28065
27816
  }
28066
27817
  if (isNodeOfType(node, "ForStatement") && isNodeOfType(node.init, "VariableDeclaration")) {
28067
- const blockBindings = /* @__PURE__ */ new Set();
28068
27818
  for (const declarator of node.init.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
28069
27819
  return blockBindings;
28070
27820
  }
28071
- if ((isNodeOfType(node, "ForOfStatement") || isNodeOfType(node, "ForInStatement")) && isNodeOfType(node.left, "VariableDeclaration")) {
28072
- const blockBindings = /* @__PURE__ */ new Set();
28073
- for (const declarator of node.left.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
28074
- return blockBindings;
28075
- }
28076
- return null;
27821
+ if ((isNodeOfType(node, "ForOfStatement") || isNodeOfType(node, "ForInStatement")) && isNodeOfType(node.left, "VariableDeclaration")) for (const declarator of node.left.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
27822
+ return blockBindings;
28077
27823
  };
28078
27824
  const walkComponentRespectingShadows = (node, shadowedStateNames, visit, isComponentBodyRoot = false) => {
28079
27825
  if (!node || typeof node !== "object") return;
28080
27826
  let nextShadowedStateNames = shadowedStateNames;
28081
- const localBindings = isComponentBodyRoot ? null : isFunctionLike$1(node) ? collectFunctionLocalBindings(node) : collectBlockScopedBindings(node);
28082
- if (localBindings && localBindings.size > 0) {
27827
+ const localBindings = isComponentBodyRoot ? /* @__PURE__ */ new Set() : isFunctionLike$1(node) ? collectFunctionLocalBindings(node) : collectBlockScopedBindings(node);
27828
+ if (localBindings.size > 0) {
28083
27829
  const merged = new Set(shadowedStateNames);
28084
27830
  for (const localName of localBindings) merged.add(localName);
28085
27831
  nextShadowedStateNames = merged;
@@ -28105,10 +27851,11 @@ const noDirectStateMutation = defineRule({
28105
27851
  const bindings = collectUseStateBindings(componentBody);
28106
27852
  if (bindings.length === 0) return;
28107
27853
  const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
27854
+ const callbackRefSetterNames = collectCallbackRefSetterNames(componentBody);
28108
27855
  const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
28109
27856
  const plainObjectStateValueNames = /* @__PURE__ */ new Set();
28110
27857
  for (const binding of bindings) {
28111
- if (setterValueObservations.callbackRefSetterNames.has(binding.setterName)) continue;
27858
+ if (callbackRefSetterNames.has(binding.setterName)) continue;
28112
27859
  if (!isNodeOfType(binding.declarator.init, "CallExpression")) continue;
28113
27860
  const initializerArgument = binding.declarator.init.arguments?.[0];
28114
27861
  if (!initializerMarksPlainState(initializerArgument)) continue;
@@ -32826,60 +32573,25 @@ const resolveSettings$16 = (settings) => {
32826
32573
  };
32827
32574
  const isHocCall = (call, scopes) => {
32828
32575
  if (!isNodeOfType(call, "CallExpression")) return false;
32829
- if (isReactApiCall(call, REACT_HOC_NAMES, scopes, {
32830
- allowGlobalReactNamespace: true,
32831
- allowUnboundBareCalls: true
32832
- })) return true;
32833
- if (isReactHocMemberReference(call.callee, scopes)) return true;
32576
+ const calleeName = flattenCalleeName(call.callee);
32577
+ if (calleeName && REACT_HOC_NAMES.has(calleeName)) return true;
32834
32578
  if (!isNodeOfType(call.callee, "Identifier")) return false;
32835
32579
  const symbol = scopes.symbolFor(call.callee);
32836
32580
  if (!symbol) return false;
32837
- return symbolMapsToHoc(symbol, scopes, /* @__PURE__ */ new Set());
32838
- };
32839
- const isReactImportEquals = (symbol) => {
32840
- if (symbol.kind !== "ts-import-equals" || !isNodeOfType(symbol.declarationNode, "TSImportEqualsDeclaration")) return false;
32841
- const moduleReference = symbol.declarationNode.moduleReference;
32842
- return Boolean(isNodeOfType(moduleReference, "TSExternalModuleReference") && isNodeOfType(moduleReference.expression, "Literal") && typeof moduleReference.expression.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(moduleReference.expression.value));
32581
+ return symbolMapsToHoc(symbol);
32843
32582
  };
32844
- const isRequireReactCall$1 = (node, scopes) => {
32845
- if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier") || node.callee.name !== "require" || !scopes.isGlobalReference(node.callee)) return false;
32846
- const moduleSpecifier = node.arguments[0];
32847
- return Boolean(moduleSpecifier && isNodeOfType(moduleSpecifier, "Literal") && typeof moduleSpecifier.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(moduleSpecifier.value));
32848
- };
32849
- const isReactNamespaceExpression = (node, scopes) => {
32850
- if (isRequireReactCall$1(node, scopes)) return true;
32851
- if (!isNodeOfType(node, "Identifier")) return false;
32852
- const symbol = scopes.symbolFor(node);
32853
- if (!symbol) return node.name === "React" && scopes.isGlobalReference(node);
32854
- if (symbol.initializer && isRequireReactCall$1(symbol.initializer, scopes)) return true;
32855
- if (isReactImportEquals(symbol)) return true;
32856
- return isReactNamespaceImport(node, scopes);
32857
- };
32858
- const isReactHocMemberReference = (node, scopes) => Boolean(isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.property, "Identifier") && REACT_HOC_NAMES.has(node.property.name) && isReactNamespaceExpression(node.object, scopes));
32859
- const getDestructuredPropertyName$1 = (symbol) => {
32860
- const property = symbol.bindingIdentifier.parent;
32861
- if (!property || !isNodeOfType(property, "Property") || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
32862
- if (isNodeOfType(property.key, "Identifier") && !property.computed) return property.key.name;
32863
- if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
32864
- return null;
32865
- };
32866
- const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
32867
- if (visitedSymbolIds.has(symbol.id)) return false;
32868
- visitedSymbolIds.add(symbol.id);
32869
- if (symbol.kind === "import") {
32870
- const importedName = getImportedName(symbol.declarationNode);
32871
- return Boolean(isImportedFromReact(symbol) && importedName && REACT_HOC_NAMES.has(importedName));
32583
+ const symbolMapsToHoc = (symbol) => {
32584
+ if (REACT_HOC_NAMES.has(symbol.name)) {
32585
+ if (symbol.kind === "import") return true;
32872
32586
  }
32873
32587
  const init = symbol.initializer;
32874
32588
  if (!init) return false;
32875
- const destructuredPropertyName = getDestructuredPropertyName$1(symbol);
32876
- if (destructuredPropertyName && REACT_HOC_NAMES.has(destructuredPropertyName) && isReactNamespaceExpression(init, scopes)) return true;
32877
- if (isReactHocMemberReference(init, scopes)) return true;
32878
- if (isNodeOfType(init, "Identifier")) {
32879
- const initializedFromSymbol = scopes.symbolFor(init);
32880
- if (initializedFromSymbol) return symbolMapsToHoc(initializedFromSymbol, scopes, visitedSymbolIds);
32881
- return REACT_HOC_NAMES.has(init.name) && scopes.isGlobalReference(init);
32589
+ if (isNodeOfType(init, "MemberExpression")) {
32590
+ const flat = flattenCalleeName(init);
32591
+ if (flat && REACT_HOC_NAMES.has(flat)) return true;
32882
32592
  }
32593
+ if (isNodeOfType(init, "Identifier") && REACT_HOC_NAMES.has(init.name)) return true;
32594
+ if (REACT_HOC_NAMES.has(symbol.name) && isNodeOfType(init, "Identifier") && init.name === "React") return true;
32883
32595
  return false;
32884
32596
  };
32885
32597
  const isTrivialPassthroughChild = (child) => {
@@ -32937,14 +32649,26 @@ const isHocComponent = (call, scopes) => {
32937
32649
  };
32938
32650
  const containsJsx = (root) => {
32939
32651
  let found = false;
32940
- walkAst(root, (node) => {
32941
- if (found) return false;
32652
+ const visit = (node) => {
32653
+ if (found) return;
32942
32654
  if (node.type === "JSXElement" || node.type === "JSXFragment") {
32943
32655
  found = true;
32944
- return false;
32656
+ return;
32945
32657
  }
32946
- if (node !== root && (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "ClassDeclaration" || node.type === "ClassExpression")) return false;
32947
- });
32658
+ if (node !== root) {
32659
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "ClassDeclaration" || node.type === "ClassExpression") return;
32660
+ }
32661
+ const record = node;
32662
+ for (const key of Object.keys(record)) {
32663
+ if (key === "parent") continue;
32664
+ const child = record[key];
32665
+ if (Array.isArray(child)) {
32666
+ for (const item of child) if (isAstNode(item)) visit(item);
32667
+ } else if (isAstNode(child)) visit(child);
32668
+ if (found) return;
32669
+ }
32670
+ };
32671
+ visit(root);
32948
32672
  return found;
32949
32673
  };
32950
32674
  const expressionContainsJsx = (expression) => {
@@ -32968,7 +32692,7 @@ const unwrapTsCast = (expression) => {
32968
32692
  while (current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSNonNullExpression") current = stripParenExpression(current.expression);
32969
32693
  return current;
32970
32694
  };
32971
- const collectReExportedNames = (program, scopes) => {
32695
+ const collectReExportedNames = (program) => {
32972
32696
  const names = /* @__PURE__ */ new Set();
32973
32697
  if (!isNodeOfType(program, "Program")) return names;
32974
32698
  for (const statement of program.body) {
@@ -32992,7 +32716,8 @@ const collectReExportedNames = (program, scopes) => {
32992
32716
  if (!declarator.init) continue;
32993
32717
  const init = unwrapTsCast(declarator.init);
32994
32718
  if (isNodeOfType(init, "CallExpression")) {
32995
- if (isHocCall(init, scopes)) {
32719
+ const calleeName = flattenCalleeName(init.callee);
32720
+ if (calleeName && REACT_HOC_NAMES.has(calleeName)) {
32996
32721
  const wrappedArg = init.arguments[0];
32997
32722
  if (wrappedArg && isNodeOfType(wrappedArg, "Identifier")) names.add(wrappedArg.name);
32998
32723
  }
@@ -33061,7 +32786,16 @@ const recordComponent = (context, name, reportNode, isStateless) => {
33061
32786
  isStateless
33062
32787
  });
33063
32788
  };
33064
- const walkChildren = (node, context) => forEachChildNode(node, context.visitChild);
32789
+ const walkChildren = (node, context) => {
32790
+ const record = node;
32791
+ for (const key of Object.keys(record)) {
32792
+ if (key === "parent") continue;
32793
+ const child = record[key];
32794
+ if (Array.isArray(child)) {
32795
+ for (const item of child) if (isAstNode(item)) walkComponentSearch(item, context);
32796
+ } else if (isAstNode(child)) walkComponentSearch(child, context);
32797
+ }
32798
+ };
33065
32799
  const walkComponentSearch = (node, context) => {
33066
32800
  if (isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) {
33067
32801
  if (isEs6Component(node)) {
@@ -33163,13 +32897,12 @@ const noMultiComp = defineRule({
33163
32897
  components: [],
33164
32898
  componentDepth: 0,
33165
32899
  currentVarName: null,
33166
- scopes: context.scopes,
33167
- visitChild: (child) => walkComponentSearch(child, visitContext)
32900
+ scopes: context.scopes
33168
32901
  };
33169
32902
  for (const statement of node.body) walkComponentSearch(statement, visitContext);
33170
32903
  const flagged = settings.ignoreStateless ? visitContext.components.filter((component) => !component.isStateless) : visitContext.components;
33171
32904
  if (flagged.length <= 2) return;
33172
- const reExportedNames = collectReExportedNames(node, context.scopes);
32905
+ const reExportedNames = collectReExportedNames(node);
33173
32906
  const exportedCount = flagged.filter((component) => isExportedDeclaration(component.reportNode, reExportedNames)).length;
33174
32907
  if (exportedCount >= flagged.length || flagged.length >= 4 && exportedCount >= Math.floor(flagged.length * .7) || flagged.length >= 8 && exportedCount >= Math.floor(flagged.length * .5)) return;
33175
32908
  const isSmallFeatureModule = exportedCount > 0 && exportedCount <= 2 && exportedCount < flagged.length;
@@ -35199,30 +34932,6 @@ const guardsRenderShape = (comparison) => {
35199
34932
  }
35200
34933
  return false;
35201
34934
  };
35202
- const isPropsChildrenLength = (node, scopes) => {
35203
- const unwrappedNode = stripParenExpression(node);
35204
- return isNodeOfType(unwrappedNode, "MemberExpression") && !unwrappedNode.computed && isNodeOfType(unwrappedNode.property, "Identifier") && unwrappedNode.property.name === "length" && resolvesToPropsChildren(stripParenExpression(unwrappedNode.object), scopes);
35205
- };
35206
- const isLargeTextLengthComparison = (node, scopes) => {
35207
- const unwrappedNode = stripParenExpression(node);
35208
- if (!isNodeOfType(unwrappedNode, "BinaryExpression")) return false;
35209
- const leftIsLength = isPropsChildrenLength(unwrappedNode.left, scopes);
35210
- const rightIsLength = isPropsChildrenLength(unwrappedNode.right, scopes);
35211
- const thresholdNode = leftIsLength ? unwrappedNode.right : unwrappedNode.left;
35212
- if (!leftIsLength && !rightIsLength || !isNodeOfType(thresholdNode, "Literal")) return false;
35213
- if (typeof thresholdNode.value !== "number" || thresholdNode.value < 1e3) return false;
35214
- return leftIsLength ? unwrappedNode.operator === ">" || unwrappedNode.operator === ">=" : unwrappedNode.operator === "<" || unwrappedNode.operator === "<=";
35215
- };
35216
- const isLargeStringOptimizationGuard = (comparison, scopes) => {
35217
- let current = findTransparentExpressionRoot(comparison);
35218
- while (current.parent) {
35219
- const parent = current.parent;
35220
- if (!isNodeOfType(parent, "LogicalExpression") || parent.operator !== "&&") return false;
35221
- if (isLargeTextLengthComparison(parent.left === current ? parent.right : parent.left, scopes)) return true;
35222
- current = findTransparentExpressionRoot(parent);
35223
- }
35224
- return false;
35225
- };
35226
34935
  const noPolymorphicChildren = defineRule({
35227
34936
  id: "no-polymorphic-children",
35228
34937
  title: "Children type checked at runtime",
@@ -35236,7 +34945,6 @@ const noPolymorphicChildren = defineRule({
35236
34945
  const isStringLiteral = (operand) => isNodeOfType(operand, "Literal") && operand.value === "string";
35237
34946
  if (!isStringLiteral(node.left) && !isStringLiteral(node.right)) return;
35238
34947
  if (!guardsRenderShape(node)) return;
35239
- if (isLargeStringOptimizationGuard(node, context.scopes)) return;
35240
34948
  context.report({
35241
34949
  node,
35242
34950
  message: "Your users hit inconsistent behavior because `typeof children === \"string\"` makes this component switch on what callers pass, so add clear subcomponents like `<Button.Text>` instead."
@@ -35454,27 +35162,6 @@ const hasPreviousValueDep = (effectNode, depElements) => {
35454
35162
  }
35455
35163
  return false;
35456
35164
  };
35457
- const isStateLikeDependency = (analysis, element, isPropName) => {
35458
- if (!isNodeOfType(element, "Identifier") || isPropName(element.name)) return false;
35459
- if (!analysis) return true;
35460
- const reference = getRef(analysis, element);
35461
- if (!reference) return true;
35462
- const upstreamReferences = getUpstreamRefs(analysis, reference);
35463
- if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
35464
- return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
35465
- };
35466
- const getRefHeldPropCallbackName = (callExpression, isPropName) => {
35467
- const callee = stripParenExpression(callExpression.callee);
35468
- if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "current") return null;
35469
- const receiver = stripParenExpression(callee.object);
35470
- if (!isNodeOfType(receiver, "Identifier")) return null;
35471
- const binding = findVariableInitializer(callExpression, receiver.name);
35472
- if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
35473
- if (getCalleeName$2(binding.initializer) !== "useRef") return null;
35474
- const callbackArgument = binding.initializer.arguments?.[0];
35475
- if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
35476
- return isPropName(callbackArgument.name) ? callbackArgument.name : null;
35477
- };
35478
35165
  const noPropCallbackInEffect = defineRule({
35479
35166
  id: "no-prop-callback-in-effect",
35480
35167
  title: "Parent kept in sync with a callback effect",
@@ -35491,11 +35178,11 @@ const noPropCallbackInEffect = defineRule({
35491
35178
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
35492
35179
  const depsNode = node.arguments[1];
35493
35180
  if (!isNodeOfType(depsNode, "ArrayExpression") || !depsNode.elements?.length) return;
35494
- const analysis = getProgramAnalysis(node);
35495
- const stateLikeDeps = (depsNode.elements ?? []).filter((element) => element && isStateLikeDependency(analysis, element, propStackTracker.isPropName));
35181
+ const stateLikeDeps = (depsNode.elements ?? []).filter((element) => isNodeOfType(element, "Identifier") && !propStackTracker.isPropName(element.name));
35496
35182
  if (stateLikeDeps.length === 0) return;
35497
35183
  if (isRefLatchGuardedEffect(callback.body)) return;
35498
35184
  if (hasPreviousValueDep(node, depsNode.elements ?? [])) return;
35185
+ const analysis = getProgramAnalysis(node);
35499
35186
  if (analysis) {
35500
35187
  const stateLikeDepRefs = [];
35501
35188
  for (const element of stateLikeDeps) {
@@ -35507,9 +35194,9 @@ const noPropCallbackInEffect = defineRule({
35507
35194
  const reportedNodes = /* @__PURE__ */ new Set();
35508
35195
  walkInsideStatementBlocks(callback.body, (child) => {
35509
35196
  if (!isNodeOfType(child, "CallExpression")) return;
35510
- const directCallee = stripParenExpression(child.callee);
35511
- const calleeName = isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName);
35512
- if (!calleeName) return;
35197
+ if (!isNodeOfType(child.callee, "Identifier")) return;
35198
+ const calleeName = child.callee.name;
35199
+ if (!propStackTracker.isPropName(calleeName)) return;
35513
35200
  if (!isResultDiscardedCall(child)) return;
35514
35201
  if (reportedNodes.has(child)) return;
35515
35202
  reportedNodes.add(child);
@@ -39683,18 +39370,34 @@ const resolveSettings$8 = (settings) => {
39683
39370
  };
39684
39371
  const expressionContainsJsxOrCreateElement = (root) => {
39685
39372
  let found = false;
39686
- walkAst(root, (node) => {
39687
- if (found) return false;
39688
- if (node !== root && NESTED_FUNCTION_TYPES.has(node.type)) return false;
39373
+ const visit = (node) => {
39374
+ if (found) return;
39689
39375
  if (node.type === "JSXElement" || node.type === "JSXFragment") {
39690
39376
  found = true;
39691
- return false;
39377
+ return;
39692
39378
  }
39693
39379
  if (isNodeOfType(node, "CallExpression") && isCreateElementCall(node)) {
39694
39380
  found = true;
39695
- return false;
39381
+ return;
39696
39382
  }
39697
- });
39383
+ const record = node;
39384
+ for (const key of Object.keys(record)) {
39385
+ if (key === "parent") continue;
39386
+ const child = record[key];
39387
+ if (Array.isArray(child)) for (const item of child) {
39388
+ if (!isAstNode(item)) continue;
39389
+ if (NESTED_FUNCTION_TYPES.has(item.type)) continue;
39390
+ visit(item);
39391
+ if (found) return;
39392
+ }
39393
+ else if (isAstNode(child)) {
39394
+ if (NESTED_FUNCTION_TYPES.has(child.type)) continue;
39395
+ visit(child);
39396
+ }
39397
+ if (found) return;
39398
+ }
39399
+ };
39400
+ visit(root);
39698
39401
  return found;
39699
39402
  };
39700
39403
  const isReactClassComponent = (classNode) => {
@@ -40620,6 +40323,19 @@ const classifyExport = (name, reportNode, isFunction, initializer, state) => {
40620
40323
  reportNode
40621
40324
  };
40622
40325
  };
40326
+ const collectRelevantNodes = (programRoot) => {
40327
+ const exportNodes = [];
40328
+ const componentCandidates = [];
40329
+ walkAst(programRoot, (child) => {
40330
+ const childType = child.type;
40331
+ if (childType === "ExportAllDeclaration" || childType === "ExportDefaultDeclaration" || childType === "ExportNamedDeclaration") exportNodes.push(child);
40332
+ else if (childType === "FunctionDeclaration" || childType === "VariableDeclarator" || childType === "ClassDeclaration") componentCandidates.push(child);
40333
+ });
40334
+ return {
40335
+ exportNodes,
40336
+ componentCandidates
40337
+ };
40338
+ };
40623
40339
  const isEntryPointFile = (filename) => {
40624
40340
  const lastSlash = Math.max(filename.lastIndexOf("/"), filename.lastIndexOf("\\"));
40625
40341
  const basename = lastSlash === -1 ? filename : filename.slice(lastSlash + 1);
@@ -40658,212 +40374,197 @@ const onlyExportComponents = defineRule({
40658
40374
  category: "Architecture",
40659
40375
  create: (context) => {
40660
40376
  const settings = resolveSettings$6(context.settings);
40661
- if (!isFileNameAllowed(normalizeFilename(context.filename ?? ""), settings.checkJS)) return {};
40662
- const exportNodes = [];
40663
- const componentCandidates = [];
40664
- const pushExportNode = (node) => {
40665
- exportNodes.push(node);
40666
- };
40667
- const pushComponentCandidate = (node) => {
40668
- componentCandidates.push(node);
40669
- };
40670
- return {
40671
- ExportAllDeclaration: pushExportNode,
40672
- ExportDefaultDeclaration: pushExportNode,
40673
- ExportNamedDeclaration: pushExportNode,
40674
- FunctionDeclaration: pushComponentCandidate,
40675
- VariableDeclarator: pushComponentCandidate,
40676
- ClassDeclaration: pushComponentCandidate,
40677
- "Program:exit"() {
40678
- const localComponentNames = /* @__PURE__ */ new Set();
40679
- const state = {
40680
- customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
40681
- allowExportNames: new Set(settings.allowExportNames),
40682
- allowConstantExport: settings.allowConstantExport,
40683
- localComponentNames,
40684
- scopes: context.scopes
40685
- };
40686
- for (const child of componentCandidates) {
40687
- if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
40688
- if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) && functionContainsReactRenderOutput(child, context.scopes)) localComponentNames.add(child.id.name);
40689
- }
40690
- if (isNodeOfType(child, "ClassDeclaration") && child.id) {
40691
- if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
40692
- }
40693
- if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
40694
- const initializer = child.init;
40695
- if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) {
40696
- const expression = initializer ? skipTsExpression(initializer) : null;
40697
- if (!(expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"))) || functionContainsReactRenderOutput(expression, context.scopes)) localComponentNames.add(child.id.name);
40698
- }
40699
- }
40377
+ return { Program(node) {
40378
+ if (!isFileNameAllowed(normalizeFilename(context.filename ?? ""), settings.checkJS)) return;
40379
+ const { exportNodes, componentCandidates } = collectRelevantNodes(node);
40380
+ const localComponentNames = /* @__PURE__ */ new Set();
40381
+ const state = {
40382
+ customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
40383
+ allowExportNames: new Set(settings.allowExportNames),
40384
+ allowConstantExport: settings.allowConstantExport,
40385
+ localComponentNames,
40386
+ scopes: context.scopes
40387
+ };
40388
+ for (const child of componentCandidates) {
40389
+ if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
40390
+ if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) && functionContainsReactRenderOutput(child, context.scopes)) localComponentNames.add(child.id.name);
40700
40391
  }
40701
- const exports = [];
40702
- let hasReactExport = false;
40703
- let hasAnyExports = false;
40704
- const isExportedNodeIds = /* @__PURE__ */ new WeakSet();
40705
- for (const child of exportNodes) {
40706
- if (isNodeOfType(child, "ExportAllDeclaration")) {
40707
- if (child.exportKind === "type") continue;
40708
- hasAnyExports = true;
40709
- context.report({
40710
- node: child,
40711
- message: EXPORT_ALL_MESSAGE
40712
- });
40713
- continue;
40392
+ if (isNodeOfType(child, "ClassDeclaration") && child.id) {
40393
+ if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
40394
+ }
40395
+ if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
40396
+ const initializer = child.init;
40397
+ if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) {
40398
+ const expression = initializer ? skipTsExpression(initializer) : null;
40399
+ if (!(expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"))) || functionContainsReactRenderOutput(expression, context.scopes)) localComponentNames.add(child.id.name);
40714
40400
  }
40715
- if (isNodeOfType(child, "ExportDefaultDeclaration")) {
40716
- hasAnyExports = true;
40717
- const declaration = child.declaration;
40718
- const stripped = skipTsExpression(declaration);
40719
- if (isNodeOfType(stripped, "FunctionDeclaration") || isNodeOfType(stripped, "FunctionExpression")) {
40720
- if (stripped.id) {
40721
- const idNode = stripped.id;
40722
- isExportedNodeIds.add(stripped);
40723
- exports.push(classifyExport(idNode.name, idNode, true, null, state));
40724
- } else {
40725
- context.report({
40726
- node: stripped,
40727
- message: ANONYMOUS_MESSAGE
40728
- });
40729
- hasReactExport = true;
40730
- }
40731
- continue;
40732
- }
40733
- if (isNodeOfType(stripped, "ClassDeclaration") || isNodeOfType(stripped, "ClassExpression")) {
40734
- if (stripped.id) {
40735
- const idNode = stripped.id;
40736
- isExportedNodeIds.add(stripped);
40737
- if (isReactComponentName(idNode.name) && isEs6Component(stripped)) hasReactExport = true;
40738
- else exports.push({
40739
- kind: "non-component",
40740
- reportNode: idNode
40741
- });
40742
- } else context.report({
40401
+ }
40402
+ }
40403
+ const exports = [];
40404
+ let hasReactExport = false;
40405
+ let hasAnyExports = false;
40406
+ const isExportedNodeIds = /* @__PURE__ */ new WeakSet();
40407
+ for (const child of exportNodes) {
40408
+ if (isNodeOfType(child, "ExportAllDeclaration")) {
40409
+ if (child.exportKind === "type") continue;
40410
+ hasAnyExports = true;
40411
+ context.report({
40412
+ node: child,
40413
+ message: EXPORT_ALL_MESSAGE
40414
+ });
40415
+ continue;
40416
+ }
40417
+ if (isNodeOfType(child, "ExportDefaultDeclaration")) {
40418
+ hasAnyExports = true;
40419
+ const declaration = child.declaration;
40420
+ const stripped = skipTsExpression(declaration);
40421
+ if (isNodeOfType(stripped, "FunctionDeclaration") || isNodeOfType(stripped, "FunctionExpression")) {
40422
+ if (stripped.id) {
40423
+ const idNode = stripped.id;
40424
+ isExportedNodeIds.add(stripped);
40425
+ exports.push(classifyExport(idNode.name, idNode, true, null, state));
40426
+ } else {
40427
+ context.report({
40743
40428
  node: stripped,
40744
40429
  message: ANONYMOUS_MESSAGE
40745
40430
  });
40746
- continue;
40747
- }
40748
- if (isNodeOfType(stripped, "Identifier")) {
40749
- exports.push(classifyExport(stripped.name, stripped, false, null, state));
40750
- continue;
40431
+ hasReactExport = true;
40751
40432
  }
40752
- if (isNodeOfType(stripped, "CallExpression")) {
40753
- if (isRouteFactoryCall(stripped)) {
40754
- hasReactExport = true;
40755
- continue;
40756
- }
40757
- const isHoc = isHocCallee(stripped.callee, state);
40758
- const firstArg = stripped.arguments[0];
40759
- const firstArgIsValid = Boolean(firstArg) && (() => {
40760
- if (!firstArg) return false;
40761
- const expression = skipTsExpression(firstArg);
40762
- if (isNodeOfType(expression, "Identifier")) return true;
40763
- if (isNodeOfType(expression, "FunctionExpression") && expression.id) return true;
40764
- if (isNodeOfType(expression, "CallExpression") && isHocCallee(expression.callee, state)) return true;
40765
- return false;
40766
- })();
40767
- if (isHoc && firstArgIsValid) hasReactExport = true;
40768
- else if (!isHoc && isConfigOnlyFactoryCall(stripped)) exports.push({
40433
+ continue;
40434
+ }
40435
+ if (isNodeOfType(stripped, "ClassDeclaration") || isNodeOfType(stripped, "ClassExpression")) {
40436
+ if (stripped.id) {
40437
+ const idNode = stripped.id;
40438
+ isExportedNodeIds.add(stripped);
40439
+ if (isReactComponentName(idNode.name) && isEs6Component(stripped)) hasReactExport = true;
40440
+ else exports.push({
40769
40441
  kind: "non-component",
40770
- reportNode: stripped
40771
- });
40772
- else context.report({
40773
- node: stripped,
40774
- message: ANONYMOUS_MESSAGE
40775
- });
40776
- continue;
40777
- }
40778
- if (isNodeOfType(stripped, "ObjectExpression")) {
40779
- context.report({
40780
- node: stripped,
40781
- message: objectExpressionBundlesComponents(stripped, state) ? NAMESPACE_OBJECT_MESSAGE : ANONYMOUS_MESSAGE
40782
- });
40783
- continue;
40784
- }
40785
- if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "Literal")) {
40786
- context.report({
40787
- node: stripped,
40788
- message: ANONYMOUS_MESSAGE
40442
+ reportNode: idNode
40789
40443
  });
40444
+ } else context.report({
40445
+ node: stripped,
40446
+ message: ANONYMOUS_MESSAGE
40447
+ });
40448
+ continue;
40449
+ }
40450
+ if (isNodeOfType(stripped, "Identifier")) {
40451
+ exports.push(classifyExport(stripped.name, stripped, false, null, state));
40452
+ continue;
40453
+ }
40454
+ if (isNodeOfType(stripped, "CallExpression")) {
40455
+ if (isRouteFactoryCall(stripped)) {
40456
+ hasReactExport = true;
40790
40457
  continue;
40791
40458
  }
40459
+ const isHoc = isHocCallee(stripped.callee, state);
40460
+ const firstArg = stripped.arguments[0];
40461
+ const firstArgIsValid = Boolean(firstArg) && (() => {
40462
+ if (!firstArg) return false;
40463
+ const expression = skipTsExpression(firstArg);
40464
+ if (isNodeOfType(expression, "Identifier")) return true;
40465
+ if (isNodeOfType(expression, "FunctionExpression") && expression.id) return true;
40466
+ if (isNodeOfType(expression, "CallExpression") && isHocCallee(expression.callee, state)) return true;
40467
+ return false;
40468
+ })();
40469
+ if (isHoc && firstArgIsValid) hasReactExport = true;
40470
+ else if (!isHoc && isConfigOnlyFactoryCall(stripped)) exports.push({
40471
+ kind: "non-component",
40472
+ reportNode: stripped
40473
+ });
40474
+ else context.report({
40475
+ node: stripped,
40476
+ message: ANONYMOUS_MESSAGE
40477
+ });
40478
+ continue;
40479
+ }
40480
+ if (isNodeOfType(stripped, "ObjectExpression")) {
40481
+ context.report({
40482
+ node: stripped,
40483
+ message: objectExpressionBundlesComponents(stripped, state) ? NAMESPACE_OBJECT_MESSAGE : ANONYMOUS_MESSAGE
40484
+ });
40485
+ continue;
40486
+ }
40487
+ if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "Literal")) {
40792
40488
  context.report({
40793
- node: child,
40489
+ node: stripped,
40794
40490
  message: ANONYMOUS_MESSAGE
40795
40491
  });
40796
40492
  continue;
40797
40493
  }
40798
- if (isNodeOfType(child, "ExportNamedDeclaration")) {
40799
- if (child.exportKind === "type") continue;
40800
- hasAnyExports = true;
40801
- if (child.declaration) {
40802
- const declaration = child.declaration;
40803
- if (isNodeOfType(declaration, "FunctionDeclaration") && declaration.id) {
40804
- isExportedNodeIds.add(declaration);
40805
- exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state));
40806
- } else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
40807
- isExportedNodeIds.add(declaration);
40808
- if (isReactComponentName(declaration.id.name) && isEs6Component(declaration)) exports.push({ kind: "react-component" });
40809
- else exports.push({
40810
- kind: "non-component",
40811
- reportNode: declaration.id
40812
- });
40813
- } else if (isNodeOfType(declaration, "VariableDeclaration")) for (const declarator of declaration.declarations) {
40814
- if (!isNodeOfType(declarator.id, "Identifier")) continue;
40815
- isExportedNodeIds.add(declarator);
40816
- const isFunction = canBeReactFunctionComponent(declarator.init ?? null, state);
40817
- exports.push(classifyExport(declarator.id.name, declarator.id, isFunction, declarator.init, state));
40818
- }
40819
- else if (declaration.type === "TSEnumDeclaration" || declaration.type === "TSInterfaceDeclaration" || declaration.type === "TSTypeAliasDeclaration") {
40820
- if (declaration.type === "TSEnumDeclaration") exports.push({
40821
- kind: "non-component",
40822
- reportNode: declaration
40823
- });
40824
- }
40494
+ context.report({
40495
+ node: child,
40496
+ message: ANONYMOUS_MESSAGE
40497
+ });
40498
+ continue;
40499
+ }
40500
+ if (isNodeOfType(child, "ExportNamedDeclaration")) {
40501
+ if (child.exportKind === "type") continue;
40502
+ hasAnyExports = true;
40503
+ if (child.declaration) {
40504
+ const declaration = child.declaration;
40505
+ if (isNodeOfType(declaration, "FunctionDeclaration") && declaration.id) {
40506
+ isExportedNodeIds.add(declaration);
40507
+ exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state));
40508
+ } else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
40509
+ isExportedNodeIds.add(declaration);
40510
+ if (isReactComponentName(declaration.id.name) && isEs6Component(declaration)) exports.push({ kind: "react-component" });
40511
+ else exports.push({
40512
+ kind: "non-component",
40513
+ reportNode: declaration.id
40514
+ });
40515
+ } else if (isNodeOfType(declaration, "VariableDeclaration")) for (const declarator of declaration.declarations) {
40516
+ if (!isNodeOfType(declarator.id, "Identifier")) continue;
40517
+ isExportedNodeIds.add(declarator);
40518
+ const isFunction = canBeReactFunctionComponent(declarator.init ?? null, state);
40519
+ exports.push(classifyExport(declarator.id.name, declarator.id, isFunction, declarator.init, state));
40825
40520
  }
40826
- const isReExportFromSource = Boolean(child.source);
40827
- for (const specifier of child.specifiers ?? []) {
40828
- if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
40829
- const exported = specifier.exported;
40830
- const local = specifier.local;
40831
- let exportedName = null;
40832
- if (exported && isNodeOfType(exported, "Identifier")) exportedName = exported.name;
40833
- const localName = local && isNodeOfType(local, "Identifier") ? local.name : null;
40834
- const reportNode = specifier;
40835
- let entry;
40836
- if (exportedName === "default" && localName) entry = classifyExport(localName, reportNode, false, null, state);
40837
- else if (exportedName) entry = classifyExport(exportedName, reportNode, false, null, state);
40838
- else {
40839
- entry = {
40840
- kind: "non-component",
40841
- reportNode
40842
- };
40843
- if (localName && isReactComponentName(localName)) exports.push({ kind: "react-component" });
40844
- }
40845
- if (isReExportFromSource && entry.kind !== "react-component") continue;
40846
- exports.push(entry);
40521
+ else if (declaration.type === "TSEnumDeclaration" || declaration.type === "TSInterfaceDeclaration" || declaration.type === "TSTypeAliasDeclaration") {
40522
+ if (declaration.type === "TSEnumDeclaration") exports.push({
40523
+ kind: "non-component",
40524
+ reportNode: declaration
40525
+ });
40526
+ }
40527
+ }
40528
+ const isReExportFromSource = Boolean(child.source);
40529
+ for (const specifier of child.specifiers ?? []) {
40530
+ if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
40531
+ const exported = specifier.exported;
40532
+ const local = specifier.local;
40533
+ let exportedName = null;
40534
+ if (exported && isNodeOfType(exported, "Identifier")) exportedName = exported.name;
40535
+ const localName = local && isNodeOfType(local, "Identifier") ? local.name : null;
40536
+ const reportNode = specifier;
40537
+ let entry;
40538
+ if (exportedName === "default" && localName) entry = classifyExport(localName, reportNode, false, null, state);
40539
+ else if (exportedName) entry = classifyExport(exportedName, reportNode, false, null, state);
40540
+ else {
40541
+ entry = {
40542
+ kind: "non-component",
40543
+ reportNode
40544
+ };
40545
+ if (localName && isReactComponentName(localName)) exports.push({ kind: "react-component" });
40847
40546
  }
40547
+ if (isReExportFromSource && entry.kind !== "react-component") continue;
40548
+ exports.push(entry);
40848
40549
  }
40849
40550
  }
40850
- for (const entry of exports) if (entry.kind === "react-component") hasReactExport = true;
40851
- for (const entry of exports) if (entry.kind === "namespace-object") context.report({
40551
+ }
40552
+ for (const entry of exports) if (entry.kind === "react-component") hasReactExport = true;
40553
+ for (const entry of exports) if (entry.kind === "namespace-object") context.report({
40554
+ node: entry.reportNode,
40555
+ message: NAMESPACE_OBJECT_MESSAGE
40556
+ });
40557
+ if (hasAnyExports && hasReactExport) for (const entry of exports) {
40558
+ if (entry.kind === "non-component") context.report({
40852
40559
  node: entry.reportNode,
40853
- message: NAMESPACE_OBJECT_MESSAGE
40560
+ message: NAMED_EXPORT_MESSAGE
40561
+ });
40562
+ if (entry.kind === "react-context") context.report({
40563
+ node: entry.reportNode,
40564
+ message: REACT_CONTEXT_MESSAGE
40854
40565
  });
40855
- if (hasAnyExports && hasReactExport) for (const entry of exports) {
40856
- if (entry.kind === "non-component") context.report({
40857
- node: entry.reportNode,
40858
- message: NAMED_EXPORT_MESSAGE
40859
- });
40860
- if (entry.kind === "react-context") context.report({
40861
- node: entry.reportNode,
40862
- message: REACT_CONTEXT_MESSAGE
40863
- });
40864
- }
40865
40566
  }
40866
- };
40567
+ } };
40867
40568
  }
40868
40569
  });
40869
40570
  //#endregion
@@ -40965,12 +40666,7 @@ const postmessageOriginRisk = defineRule({
40965
40666
  scan: (file) => {
40966
40667
  if (!isProductionSourcePath(file.relativePath)) return [];
40967
40668
  if (WORKER_FILE_PATH_PATTERN.test(file.relativePath)) return [];
40968
- if (!file.content.includes("addEventListener") && !file.content.includes("onmessage")) return [];
40969
- const ast = parseSourceText({
40970
- filename: file.absolutePath,
40971
- sourceText: file.content,
40972
- shouldAttachParentReferences: false
40973
- });
40669
+ const ast = parseSourceText(file.absolutePath, file.content);
40974
40670
  if (ast === null) return [];
40975
40671
  const findings = [];
40976
40672
  walkAst(ast, (node) => {
@@ -43350,6 +43046,15 @@ const isStableHookWrapperArgument = (node) => {
43350
43046
  const parent = node.parent;
43351
43047
  return isNodeOfType(parent, "CallExpression") && isNodeOfType(parent.callee, "Identifier") && STABLE_HOOK_WRAPPERS.has(parent.callee.name) && Boolean(parent.arguments?.some((argument) => argument === node));
43352
43048
  };
43049
+ const isImmediatelyInvokedFunction = (functionNode) => {
43050
+ let wrappedCallee = functionNode;
43051
+ let enclosing = functionNode.parent;
43052
+ while (enclosing && stripParenExpression(enclosing) === functionNode) {
43053
+ wrappedCallee = enclosing;
43054
+ enclosing = enclosing.parent ?? null;
43055
+ }
43056
+ return Boolean(enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee);
43057
+ };
43353
43058
  const queryStableQueryClient = defineRule({
43354
43059
  id: "query-stable-query-client",
43355
43060
  title: "Unstable QueryClient in component",
@@ -59848,10 +59553,7 @@ const appendNode = (builder, block, node) => {
59848
59553
  };
59849
59554
  const mapDescendantsToBlock = (builder, node, block) => {
59850
59555
  builder.nodeBlock.set(node, block);
59851
- if (isFunctionLike$1(node)) {
59852
- builder.nestedFunctions.push(node);
59853
- return;
59854
- }
59556
+ if (isFunctionLike$1(node)) return;
59855
59557
  const record = node;
59856
59558
  for (const key of Object.keys(record)) {
59857
59559
  if (key === "parent") continue;
@@ -60085,12 +59787,11 @@ const buildStatement = (builder, statement, current) => {
60085
59787
  mapDescendantsToBlock(builder, statement, current);
60086
59788
  return current;
60087
59789
  };
60088
- const buildFunctionCfg = (functionNode, body, nestedFunctionSink) => {
59790
+ const buildFunctionCfg = (functionNode, body) => {
60089
59791
  const builder = {
60090
59792
  blocks: [],
60091
59793
  entry: null,
60092
59794
  exit: null,
60093
- nestedFunctions: nestedFunctionSink,
60094
59795
  nodeBlock: /* @__PURE__ */ new Map(),
60095
59796
  loopStack: [],
60096
59797
  switchStack: [],
@@ -60108,12 +59809,13 @@ const buildFunctionCfg = (functionNode, body, nestedFunctionSink) => {
60108
59809
  bodyEnd = entry;
60109
59810
  }
60110
59811
  addEdge(bodyEnd, exit, "uncond");
59812
+ const blockOf = (node) => builder.nodeBlock.get(node) ?? null;
60111
59813
  return {
60112
59814
  owner: functionNode,
60113
59815
  entry,
60114
59816
  exit,
60115
59817
  blocks: builder.blocks,
60116
- blockOf: (node) => builder.nodeBlock.get(node) ?? null
59818
+ blockOf
60117
59819
  };
60118
59820
  };
60119
59821
  const computeUnconditionalSet = (cfg) => {
@@ -60150,9 +59852,8 @@ const computeUnconditionalSet = (cfg) => {
60150
59852
  const analyzeControlFlow = (program) => {
60151
59853
  nextBlockId = 0;
60152
59854
  const functionCfgs = /* @__PURE__ */ new Map();
60153
- const pendingFunctions = [];
60154
59855
  const buildFor = (functionNode, body) => {
60155
- const cfg = buildFunctionCfg(functionNode, body, pendingFunctions);
59856
+ const cfg = buildFunctionCfg(functionNode, body);
60156
59857
  functionCfgs.set(functionNode, {
60157
59858
  cfg,
60158
59859
  unconditionalSet: computeUnconditionalSet(cfg)
@@ -60162,18 +59863,21 @@ const analyzeControlFlow = (program) => {
60162
59863
  type: "BlockStatement",
60163
59864
  body: program.body
60164
59865
  });
60165
- for (let functionIndex = 0; functionIndex < pendingFunctions.length; functionIndex += 1) {
60166
- const functionNode = pendingFunctions[functionIndex];
60167
- if (!isFunctionLike$1(functionNode) || !functionNode.body || functionCfgs.has(functionNode)) continue;
60168
- buildFor(functionNode, functionNode.body);
60169
- }
60170
- const getFunctionEntry = (functionNode) => {
60171
- const existingEntry = functionCfgs.get(functionNode);
60172
- if (existingEntry) return existingEntry;
60173
- if (!isFunctionLike$1(functionNode) || !functionNode.body) return null;
60174
- buildFor(functionNode, functionNode.body);
60175
- return functionCfgs.get(functionNode) ?? null;
59866
+ const visit = (node) => {
59867
+ if (isFunctionLike$1(node)) {
59868
+ const body = node.body;
59869
+ if (body) buildFor(node, body);
59870
+ }
59871
+ const record = node;
59872
+ for (const key of Object.keys(record)) {
59873
+ if (key === "parent") continue;
59874
+ const child = record[key];
59875
+ if (Array.isArray(child)) {
59876
+ for (const item of child) if (isAstNode(item)) visit(item);
59877
+ } else if (isAstNode(child)) visit(child);
59878
+ }
60176
59879
  };
59880
+ visit(program);
60177
59881
  const enclosingFunction = (node) => {
60178
59882
  let current = node;
60179
59883
  while (current) {
@@ -60184,12 +59888,12 @@ const analyzeControlFlow = (program) => {
60184
59888
  return null;
60185
59889
  };
60186
59890
  const cfgFor = (functionLike) => {
60187
- return getFunctionEntry(functionLike)?.cfg ?? null;
59891
+ return functionCfgs.get(functionLike)?.cfg ?? null;
60188
59892
  };
60189
59893
  const isUnconditionalFromEntry = (node) => {
60190
59894
  const owner = enclosingFunction(node);
60191
59895
  if (!owner) return true;
60192
- const entry = getFunctionEntry(owner);
59896
+ const entry = functionCfgs.get(owner);
60193
59897
  if (!entry) return true;
60194
59898
  const block = entry.cfg.blockOf(node);
60195
59899
  if (!block) return true;
@@ -60263,14 +59967,17 @@ const wrapWithSemanticContext = (rule) => ({
60263
59967
  }
60264
59968
  };
60265
59969
  const visitors = rule.create(enrichedContext);
60266
- const innerProgramHandler = visitors.Program;
60267
- return {
60268
- ...visitors,
60269
- Program: ((node) => {
60270
- programRoot = node;
60271
- if (innerProgramHandler) innerProgramHandler(node);
60272
- })
60273
- };
59970
+ const passthroughVisitors = {};
59971
+ for (const [nodeType, handler] of Object.entries(visitors)) {
59972
+ if (typeof handler !== "function") continue;
59973
+ passthroughVisitors[nodeType] = handler;
59974
+ }
59975
+ const innerProgramHandler = passthroughVisitors.Program;
59976
+ passthroughVisitors.Program = ((node) => {
59977
+ programRoot = node;
59978
+ if (innerProgramHandler) innerProgramHandler(node);
59979
+ });
59980
+ return passthroughVisitors;
60274
59981
  }
60275
59982
  });
60276
59983
  //#endregion