oxlint-plugin-react-doctor 0.7.5-dev.dbd4067 → 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 +630 -1052
  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;
@@ -5934,21 +5892,6 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
5934
5892
  }
5935
5893
  });
5936
5894
  //#endregion
5937
- //#region src/plugin/utils/get-static-property-key-name.ts
5938
- const getStaticPropertyKeyName = (node, options = {}) => {
5939
- if (!isNodeOfType(node, "Property")) return null;
5940
- if (node.computed) {
5941
- if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
5942
- return null;
5943
- }
5944
- if (isNodeOfType(node.key, "Identifier")) return node.key.name;
5945
- if (isNodeOfType(node.key, "Literal")) {
5946
- if (typeof node.key.value === "string") return node.key.value;
5947
- if (options.stringifyNonStringLiterals) return String(node.key.value);
5948
- }
5949
- return null;
5950
- };
5951
- //#endregion
5952
5895
  //#region src/plugin/utils/is-presentation-role.ts
5953
5896
  const isPresentationRole = (openingElement) => {
5954
5897
  const roleAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "role");
@@ -5993,21 +5936,6 @@ const isPureEventBlockerHandler = (attribute) => {
5993
5936
  return false;
5994
5937
  };
5995
5938
  //#endregion
5996
- //#region src/plugin/utils/resolve-const-identifier-alias.ts
5997
- const resolveConstIdentifierAlias = (identifier, scopes) => {
5998
- if (!isNodeOfType(identifier, "Identifier")) return null;
5999
- const visitedSymbolIds = /* @__PURE__ */ new Set();
6000
- let symbol = scopes.symbolFor(identifier);
6001
- while (symbol?.kind === "const") {
6002
- if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
6003
- visitedSymbolIds.add(symbol.id);
6004
- const initializer = stripParenExpression(symbol.initializer);
6005
- if (!isNodeOfType(initializer, "Identifier")) return symbol;
6006
- symbol = scopes.symbolFor(initializer);
6007
- }
6008
- return symbol;
6009
- };
6010
- //#endregion
6011
5939
  //#region src/plugin/rules/a11y/click-events-have-key-events.ts
6012
5940
  const MESSAGE$57 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
6013
5941
  const KEY_HANDLERS = [
@@ -6018,63 +5946,6 @@ const KEY_HANDLERS = [
6018
5946
  "onKeyDownCapture",
6019
5947
  "onKeyPressCapture"
6020
5948
  ];
6021
- const CLICK_HANDLERS = ["onClick", "onClickCapture"];
6022
- const TRANSPARENT_SPREAD_EVENT_NAMES = new Set([...CLICK_HANDLERS, ...KEY_HANDLERS].map((eventName) => eventName.toLowerCase()));
6023
- const CONSERVATIVE_SPREAD_PROP_NAMES = new Set([
6024
- "aria-hidden",
6025
- "onmouseenter",
6026
- "onmouseover",
6027
- "role"
6028
- ]);
6029
- const resolveSpreadObjectExpression = (expression, scopes) => {
6030
- const innerExpression = stripParenExpression(expression);
6031
- if (isNodeOfType(innerExpression, "ObjectExpression")) return innerExpression;
6032
- if (!isNodeOfType(innerExpression, "Identifier")) return null;
6033
- const symbol = resolveConstIdentifierAlias(innerExpression, scopes);
6034
- if (symbol?.kind !== "const" || !symbol.initializer) return null;
6035
- const initializer = stripParenExpression(symbol.initializer);
6036
- return isNodeOfType(initializer, "ObjectExpression") ? initializer : null;
6037
- };
6038
- const collectTransparentSpreadEventNames = (expression, scopes, eventValues, visitedObjectExpressions) => {
6039
- const objectExpression = resolveSpreadObjectExpression(expression, scopes);
6040
- if (!objectExpression || visitedObjectExpressions.has(objectExpression)) return false;
6041
- visitedObjectExpressions.add(objectExpression);
6042
- let isTransparent = true;
6043
- for (const property of objectExpression.properties) {
6044
- if (isNodeOfType(property, "SpreadElement")) {
6045
- if (!collectTransparentSpreadEventNames(property.argument, scopes, eventValues, visitedObjectExpressions)) {
6046
- isTransparent = false;
6047
- break;
6048
- }
6049
- continue;
6050
- }
6051
- if (!isNodeOfType(property, "Property")) {
6052
- isTransparent = false;
6053
- break;
6054
- }
6055
- const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
6056
- if (!propertyName) {
6057
- isTransparent = false;
6058
- break;
6059
- }
6060
- const normalizedPropertyName = propertyName.toLowerCase();
6061
- if (CONSERVATIVE_SPREAD_PROP_NAMES.has(normalizedPropertyName)) {
6062
- isTransparent = false;
6063
- break;
6064
- }
6065
- if (TRANSPARENT_SPREAD_EVENT_NAMES.has(normalizedPropertyName)) eventValues.set(normalizedPropertyName, property.value);
6066
- }
6067
- visitedObjectExpressions.delete(objectExpression);
6068
- return isTransparent;
6069
- };
6070
- const getTransparentSpreadEventValues = (attributes, scopes) => {
6071
- const eventValues = /* @__PURE__ */ new Map();
6072
- for (const attribute of attributes) {
6073
- if (!isNodeOfType(attribute, "JSXSpreadAttribute")) continue;
6074
- if (!collectTransparentSpreadEventNames(attribute.argument, scopes, eventValues, /* @__PURE__ */ new Set())) return null;
6075
- }
6076
- return eventValues;
6077
- };
6078
5949
  const FOCUSLESS_CONTAINER_TAGS = new Set([
6079
5950
  "tr",
6080
5951
  "td",
@@ -6122,10 +5993,7 @@ const isFocusForwardingFunctionBody = (body) => {
6122
5993
  };
6123
5994
  const resolveHandlerFunction = (attribute) => {
6124
5995
  if (!attribute.value || !isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
6125
- return resolveHandlerFunctionExpression(attribute.value.expression);
6126
- };
6127
- const resolveHandlerFunctionExpression = (handlerExpression) => {
6128
- let expression = handlerExpression;
5996
+ let expression = attribute.value.expression;
6129
5997
  if (isNodeOfType(expression, "Identifier")) {
6130
5998
  const binding = findVariableInitializer(expression, expression.name);
6131
5999
  if (!binding?.initializer) return null;
@@ -6224,22 +6092,18 @@ const clickEventsHaveKeyEvents = defineRule({
6224
6092
  if (!HTML_TAGS.has(tag)) return;
6225
6093
  if (tag === "label") return;
6226
6094
  if (!FOCUSLESS_CONTAINER_TAGS.has(tag) && isInteractiveElement(tag, node)) return;
6227
- const spreadEventValues = getTransparentSpreadEventValues(node.attributes, context.scopes);
6228
- if (!spreadEventValues) return;
6229
6095
  const onClick = hasJsxPropIgnoreCase(node.attributes, "onClick") ?? hasJsxPropIgnoreCase(node.attributes, "onClickCapture");
6230
- const spreadOnClickExpression = CLICK_HANDLERS.map((name) => spreadEventValues.get(name.toLowerCase())).find((expression) => expression !== void 0);
6231
- if (!onClick && !spreadOnClickExpression) return;
6232
- if (onClick && isPureEventBlockerHandler(onClick)) return;
6233
- if (onClick && isFocusForwardingHandler(onClick)) return;
6234
- const spreadHandlerFunction = spreadOnClickExpression ? resolveHandlerFunctionExpression(spreadOnClickExpression) : null;
6235
- if (spreadHandlerFunction && (isFocusForwardingFunctionBody(spreadHandlerFunction.body ?? null) || containsBackdropDismissComparison(spreadHandlerFunction.body ?? null))) return;
6096
+ if (!onClick) return;
6097
+ if (isPureEventBlockerHandler(onClick)) return;
6098
+ if (isFocusForwardingHandler(onClick)) return;
6099
+ if (hasJsxSpreadAttribute$1(node.attributes)) return;
6236
6100
  if (hasCompositeItemRole(node)) return;
6237
6101
  if (isHoverSelectionListItem(tag, node)) return;
6238
- if (onClick && isBackdropDismissHandler(onClick)) return;
6102
+ if (isBackdropDismissHandler(onClick)) return;
6239
6103
  if (containsKeyboardActivatableDescendant(node.parent)) return;
6240
6104
  if (isHiddenFromScreenReader(node, context.settings)) return;
6241
6105
  if (isPresentationRole(node)) return;
6242
- if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler) || spreadEventValues.has(handler.toLowerCase()))) return;
6106
+ if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
6243
6107
  context.report({
6244
6108
  node: node.name,
6245
6109
  message: MESSAGE$57
@@ -6965,10 +6829,11 @@ const getTemplateInterpolations = (valueTail) => {
6965
6829
  const interpolations = valueTail.slice(1, closingBacktickIndex).match(/\$\{[^}]*\}/g);
6966
6830
  return interpolations === null ? "" : interpolations.join(" ");
6967
6831
  };
6968
- const getIdentifierDeclarationInitializer = (identifier, sinkIndex, fileContent) => {
6969
- const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
6970
- if (declaration === null) return null;
6971
- 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);
6972
6837
  };
6973
6838
  const isPureStringLiteralConcat = (initializerText) => {
6974
6839
  if (!/^["']/.test(initializerText)) return false;
@@ -7038,153 +6903,6 @@ const isAllOperandsDomContentConcat = (valueExpression) => {
7038
6903
  return !HTML_TAINT_PATTERN.test(withoutCast.replace(DOM_CONTENT_SOURCE_VALUE_PATTERN, ""));
7039
6904
  });
7040
6905
  };
7041
- const findMatchingBraceIndex = (fileContent, openingBraceIndex) => {
7042
- let depth = 0;
7043
- let quote = null;
7044
- for (let index = openingBraceIndex; index < fileContent.length; index += 1) {
7045
- const character = fileContent[index];
7046
- if (quote !== null) {
7047
- if (character === quote && fileContent[index - 1] !== "\\") quote = null;
7048
- continue;
7049
- }
7050
- if (character === "\"" || character === "'" || character === "`") {
7051
- quote = character;
7052
- continue;
7053
- }
7054
- if (character === "{") depth += 1;
7055
- if (character === "}") {
7056
- depth -= 1;
7057
- if (depth === 0) return index;
7058
- }
7059
- }
7060
- return fileContent.length;
7061
- };
7062
- const findContainingBlockEndIndex = (fileContent, targetIndex) => {
7063
- const openingBraceIndexes = [];
7064
- let quote = null;
7065
- let isLineComment = false;
7066
- let isBlockComment = false;
7067
- for (let index = 0; index < targetIndex; index += 1) {
7068
- const character = fileContent[index];
7069
- const nextCharacter = fileContent[index + 1];
7070
- if (isLineComment) {
7071
- if (character === "\n") isLineComment = false;
7072
- continue;
7073
- }
7074
- if (isBlockComment) {
7075
- if (character === "*" && nextCharacter === "/") {
7076
- isBlockComment = false;
7077
- index += 1;
7078
- }
7079
- continue;
7080
- }
7081
- if (quote !== null) {
7082
- if (character === quote && fileContent[index - 1] !== "\\") quote = null;
7083
- continue;
7084
- }
7085
- if (character === "/" && nextCharacter === "/") {
7086
- isLineComment = true;
7087
- index += 1;
7088
- continue;
7089
- }
7090
- if (character === "/" && nextCharacter === "*") {
7091
- isBlockComment = true;
7092
- index += 1;
7093
- continue;
7094
- }
7095
- if (character === "\"" || character === "'" || character === "`") {
7096
- quote = character;
7097
- continue;
7098
- }
7099
- if (character === "{") openingBraceIndexes.push(index);
7100
- if (character === "}") openingBraceIndexes.pop();
7101
- }
7102
- const openingBraceIndex = openingBraceIndexes.at(-1);
7103
- return openingBraceIndex === void 0 ? fileContent.length : findMatchingBraceIndex(fileContent, openingBraceIndex);
7104
- };
7105
- const findVisibleIdentifierDeclaration = (identifier, sinkIndex, fileContent) => {
7106
- const initializerPattern = new RegExp(`(?:const|let|var)\\s+${escapeRegExp(identifier)}\\s*(?::[^=\\n]*)?=\\s*([^;\\n]+)`, "g");
7107
- let nearestDeclaration = null;
7108
- let nearestDeclarationIndex = -1;
7109
- for (const match of fileContent.matchAll(initializerPattern)) {
7110
- const declarationIndex = match.index;
7111
- if (declarationIndex === void 0 || declarationIndex >= sinkIndex || declarationIndex <= nearestDeclarationIndex || findContainingBlockEndIndex(fileContent, declarationIndex) < sinkIndex) continue;
7112
- nearestDeclarationIndex = declarationIndex;
7113
- const initializer = match[1];
7114
- if (initializer === void 0) continue;
7115
- nearestDeclaration = {
7116
- initializer,
7117
- initializerStartIndex: declarationIndex + match[0].length - initializer.length
7118
- };
7119
- }
7120
- return nearestDeclaration;
7121
- };
7122
- const findContainingFunctionParameterSource = (identifier, sinkIndex, fileContent) => {
7123
- const patterns = [/function\s+([\w$]+)\s*\(([^)]*)\)\s*\{/g, /(?:const|let|var)\s+([\w$]+)\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>\s*\{/g];
7124
- let closestSource = null;
7125
- let closestStartIndex = -1;
7126
- for (const pattern of patterns) for (const match of fileContent.matchAll(pattern)) {
7127
- const matchIndex = match.index;
7128
- if (matchIndex === void 0 || matchIndex >= sinkIndex || matchIndex < closestStartIndex) continue;
7129
- if (findMatchingBraceIndex(fileContent, matchIndex + match[0].lastIndexOf("{")) < sinkIndex) continue;
7130
- const parameterIndex = (match[2] ?? "").split(",").findIndex((parameter) => parameter.trim().match(/^(?:\.\.\.)?([\w$]+)/)?.[1] === identifier);
7131
- if (parameterIndex < 0) continue;
7132
- closestStartIndex = matchIndex;
7133
- closestSource = {
7134
- functionName: match[1] ?? "",
7135
- parameterIndex
7136
- };
7137
- }
7138
- return closestSource;
7139
- };
7140
- const splitTopLevelArguments = (argumentText) => {
7141
- const argumentsList = [];
7142
- let startIndex = 0;
7143
- let depth = 0;
7144
- let quote = null;
7145
- for (let index = 0; index < argumentText.length; index += 1) {
7146
- const character = argumentText[index];
7147
- if (quote !== null) {
7148
- if (character === quote && argumentText[index - 1] !== "\\") quote = null;
7149
- continue;
7150
- }
7151
- if (character === "\"" || character === "'" || character === "`") {
7152
- quote = character;
7153
- continue;
7154
- }
7155
- if (character === "(" || character === "[" || character === "{") depth += 1;
7156
- if (character === ")" || character === "]" || character === "}") depth -= 1;
7157
- if (character === "," && depth === 0) {
7158
- argumentsList.push(argumentText.slice(startIndex, index).trim());
7159
- startIndex = index + 1;
7160
- }
7161
- }
7162
- argumentsList.push(argumentText.slice(startIndex).trim());
7163
- return argumentsList;
7164
- };
7165
- const isHtmlTainted = (expression, fileContent, sinkIndex, visitedIdentifiers) => {
7166
- const trimmedExpression = expression.trim();
7167
- if (STRING_LITERAL_VALUE_PATTERN.test(`${trimmedExpression};`)) return false;
7168
- if (MODULE_CONSTANT_VALUE_PATTERN.test(`${trimmedExpression};`)) return false;
7169
- if (SANITIZER_PATTERN.test(trimmedExpression)) return false;
7170
- if (ENV_CONFIG_VALUE_PATTERN.test(trimmedExpression)) return false;
7171
- if (I18N_VALUE_PATTERN.test(trimmedExpression)) return false;
7172
- if (ESCAPING_SERIALIZER_CALL_PATTERN.test(trimmedExpression)) return false;
7173
- if (HTML_TAINT_PATTERN.test(trimmedExpression)) return true;
7174
- const identifier = trimmedExpression.match(/^([\w$]+)\s*(?:[;,})\n]|$)/)?.[1];
7175
- if (identifier === void 0 || visitedIdentifiers.has(identifier)) return false;
7176
- visitedIdentifiers.add(identifier);
7177
- const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
7178
- if (declaration !== null && isHtmlTainted(declaration.initializer, fileContent, sinkIndex, visitedIdentifiers)) return true;
7179
- const parameterSource = findContainingFunctionParameterSource(identifier, sinkIndex, fileContent);
7180
- if (parameterSource === null || parameterSource.functionName.length === 0) return false;
7181
- const callPattern = new RegExp(`\\b${escapeRegExp(parameterSource.functionName)}\\s*\\(([^)]*)\\)`, "g");
7182
- for (const callMatch of fileContent.matchAll(callPattern)) {
7183
- const argument = splitTopLevelArguments(callMatch[1] ?? "")[parameterSource.parameterIndex];
7184
- if (argument !== void 0 && isHtmlTainted(argument, fileContent, sinkIndex, new Set(visitedIdentifiers))) return true;
7185
- }
7186
- return false;
7187
- };
7188
6906
  const dangerousHtmlSink = defineRule({
7189
6907
  id: "dangerous-html-sink",
7190
6908
  title: "HTML injection sink with dynamic content",
@@ -7211,7 +6929,6 @@ const dangerousHtmlSink = defineRule({
7211
6929
  const valueTail = fullValueTail.slice(0, VALUE_EXPRESSION_MAX_CHARS);
7212
6930
  const terminatorIndex = valueTail.search(/[;}]/);
7213
6931
  const valueExpression = terminatorIndex >= 0 ? valueTail.slice(0, terminatorIndex + 1) : valueTail;
7214
- const sinkIndex = lines.slice(0, lineIndex).join("\n").length + (lineIndex > 0 ? 1 : 0) + line.search(DANGEROUS_HTML_PATTERN);
7215
6932
  if (STRING_LITERAL_VALUE_PATTERN.test(valueExpression)) continue;
7216
6933
  if (MODULE_CONSTANT_VALUE_PATTERN.test(valueExpression)) continue;
7217
6934
  if (DOM_CONTENT_SOURCE_VALUE_PATTERN.test(valueExpression) && !valueExpression.includes("+")) {
@@ -7223,7 +6940,7 @@ const dangerousHtmlSink = defineRule({
7223
6940
  let templateInterpolations = getTemplateInterpolations(longValueTail ?? fullValueTail);
7224
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;
7225
6942
  if (templateInterpolations === null && valueIdentifier !== void 0 && BARE_IDENTIFIER_VALUE_PATTERN.test(valueExpression)) {
7226
- const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier, sinkIndex, file.content);
6943
+ const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier, file.content);
7227
6944
  if (declarationInitializer !== null) {
7228
6945
  if (isPureStringLiteralConcat(declarationInitializer)) continue;
7229
6946
  templateInterpolations = getTemplateInterpolations(declarationInitializer);
@@ -7234,7 +6951,7 @@ const dangerousHtmlSink = defineRule({
7234
6951
  if (SANITIZER_PATTERN.test(judgedExpression)) continue;
7235
6952
  if (ENV_CONFIG_VALUE_PATTERN.test(judgedExpression)) continue;
7236
6953
  if (I18N_VALUE_PATTERN.test(judgedExpression)) continue;
7237
- if (!isHtmlTainted(judgedExpression, file.content, sinkIndex, /* @__PURE__ */ new Set())) continue;
6954
+ if (!HTML_TAINT_PATTERN.test(judgedExpression)) continue;
7238
6955
  if (ESCAPING_SERIALIZER_CALL_PATTERN.test(valueExpression)) continue;
7239
6956
  if (/highlighted/i.test(valueExpression)) continue;
7240
6957
  if (/highlight/i.test(valueExpression) && HIGHLIGHTER_LIBRARY_PATTERN.test(file.content)) continue;
@@ -7681,7 +7398,7 @@ const containsJsx$1 = (root) => {
7681
7398
  containsJsxCache.set(root, found);
7682
7399
  return found;
7683
7400
  };
7684
- const getStaticMemberName$2 = (node) => {
7401
+ const getStaticMemberName$1 = (node) => {
7685
7402
  if (!isNodeOfType(node, "MemberExpression")) return null;
7686
7403
  if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
7687
7404
  if (node.computed && isNodeOfType(node.property, "Literal") && typeof node.property.value === "string") return node.property.value;
@@ -7695,7 +7412,7 @@ const getAssignedName = (node) => {
7695
7412
  if (isNodeOfType(parent, "AssignmentExpression")) {
7696
7413
  const left = parent.left;
7697
7414
  if (isNodeOfType(left, "Identifier")) return left.name;
7698
- if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$2(left);
7415
+ if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$1(left);
7699
7416
  }
7700
7417
  return null;
7701
7418
  };
@@ -7703,20 +7420,20 @@ const isModuleExportsAssignment = (node) => {
7703
7420
  const parent = node.parent;
7704
7421
  if (!parent || !isNodeOfType(parent, "AssignmentExpression")) return false;
7705
7422
  const left = parent.left;
7706
- 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";
7707
7424
  };
7708
7425
  const isCreateClassLikeCall = (node) => {
7709
7426
  if (!isNodeOfType(node, "CallExpression")) return false;
7710
7427
  if (isEs5Component(node)) return true;
7711
7428
  const callee = node.callee;
7712
- if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$2(callee) === "createClass";
7429
+ if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$1(callee) === "createClass";
7713
7430
  return false;
7714
7431
  };
7715
- const isCreateContextCall$1 = (node) => {
7432
+ const isCreateContextCall = (node) => {
7716
7433
  if (!isNodeOfType(node, "CallExpression")) return false;
7717
7434
  const callee = node.callee;
7718
7435
  if (isNodeOfType(callee, "Identifier")) return callee.name === "createContext";
7719
- return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$2(callee) === "createContext";
7436
+ return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$1(callee) === "createContext";
7720
7437
  };
7721
7438
  const isNamedFunctionLike = (node) => (isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && Boolean(node.id?.name);
7722
7439
  const firstCallArgument = (node) => {
@@ -7774,7 +7491,7 @@ const memberExpressionPath = (node) => {
7774
7491
  if (isNodeOfType(node, "Identifier")) return [node.name];
7775
7492
  if (!isNodeOfType(node, "MemberExpression")) return [];
7776
7493
  const objectPath = memberExpressionPath(node.object);
7777
- const propertyName = getStaticMemberName$2(node);
7494
+ const propertyName = getStaticMemberName$1(node);
7778
7495
  return propertyName ? [...objectPath, propertyName] : objectPath;
7779
7496
  };
7780
7497
  const displayNameAssignmentIndexCache = /* @__PURE__ */ new WeakMap();
@@ -7784,7 +7501,7 @@ const getDisplayNameAssignmentIndex = (programRoot) => {
7784
7501
  const identifierTargets = /* @__PURE__ */ new Set();
7785
7502
  const memberPathSegments = /* @__PURE__ */ new Set();
7786
7503
  const visit = (node) => {
7787
- 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") {
7788
7505
  if (isNodeOfType(node.left.object, "Identifier")) identifierTargets.add(node.left.object.name);
7789
7506
  for (const segment of memberExpressionPath(node.left.object)) memberPathSegments.add(segment);
7790
7507
  }
@@ -7889,7 +7606,7 @@ const displayName = defineRule({
7889
7606
  }
7890
7607
  },
7891
7608
  CallExpression(node) {
7892
- if (settings.checkContextObjects && isReactVersionAtLeast$1(settings.reactVersion, 16, 3) && isCreateContextCall$1(node)) {
7609
+ if (settings.checkContextObjects && isReactVersionAtLeast$1(settings.reactVersion, 16, 3) && isCreateContextCall(node)) {
7893
7610
  const assignedName = getAssignedName(node);
7894
7611
  if (assignedName) {
7895
7612
  const programRoot = findProgramRoot(node);
@@ -7936,6 +7653,21 @@ const displayName = defineRule({
7936
7653
  }
7937
7654
  });
7938
7655
  //#endregion
7656
+ //#region src/plugin/utils/get-static-property-key-name.ts
7657
+ const getStaticPropertyKeyName = (node, options = {}) => {
7658
+ if (!isNodeOfType(node, "Property")) return null;
7659
+ if (node.computed) {
7660
+ if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
7661
+ return null;
7662
+ }
7663
+ if (isNodeOfType(node.key, "Identifier")) return node.key.name;
7664
+ if (isNodeOfType(node.key, "Literal")) {
7665
+ if (typeof node.key.value === "string") return node.key.value;
7666
+ if (options.stringifyNonStringLiterals) return String(node.key.value);
7667
+ }
7668
+ return null;
7669
+ };
7670
+ //#endregion
7939
7671
  //#region src/plugin/utils/read-static-boolean.ts
7940
7672
  const readStaticBoolean = (node) => {
7941
7673
  if (!node) return null;
@@ -7944,16 +7676,31 @@ const readStaticBoolean = (node) => {
7944
7676
  return unwrappedNode.value;
7945
7677
  };
7946
7678
  //#endregion
7679
+ //#region src/plugin/utils/resolve-const-identifier-alias.ts
7680
+ const resolveConstIdentifierAlias = (identifier, scopes) => {
7681
+ if (!isNodeOfType(identifier, "Identifier")) return null;
7682
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
7683
+ let symbol = scopes.symbolFor(identifier);
7684
+ while (symbol?.kind === "const") {
7685
+ if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
7686
+ visitedSymbolIds.add(symbol.id);
7687
+ const initializer = stripParenExpression(symbol.initializer);
7688
+ if (!isNodeOfType(initializer, "Identifier")) return symbol;
7689
+ symbol = scopes.symbolFor(initializer);
7690
+ }
7691
+ return symbol;
7692
+ };
7693
+ //#endregion
7947
7694
  //#region src/plugin/utils/is-react-api-call.ts
7948
7695
  const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? apiNames === apiName : apiNames.has(apiName);
7949
7696
  const isImportedFromReact = (symbol) => {
7950
7697
  if (symbol.kind !== "import") return false;
7951
7698
  const importDeclaration = symbol.declarationNode.parent;
7952
- 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");
7953
7700
  };
7954
- const isNamedReactApiImport = (identifier, apiNames, scopes, resolveAliases) => {
7701
+ const isNamedReactApiImport = (identifier, apiNames, scopes) => {
7955
7702
  if (!isNodeOfType(identifier, "Identifier")) return false;
7956
- const symbol = resolveAliases ? resolveConstIdentifierAlias(identifier, scopes) : scopes.symbolFor(identifier);
7703
+ const symbol = scopes.symbolFor(identifier);
7957
7704
  if (!symbol || !isImportedFromReact(symbol)) return false;
7958
7705
  const importedName = getImportedName(symbol.declarationNode);
7959
7706
  return Boolean(importedName && includesApiName(apiNames, importedName));
@@ -7967,7 +7714,7 @@ const isReactApiCall = (node, apiNames, scopes, options = {}) => {
7967
7714
  if (!isNodeOfType(node, "CallExpression")) return false;
7968
7715
  const callee = stripParenExpression(node.callee);
7969
7716
  if (isNodeOfType(callee, "Identifier")) {
7970
- if (isNamedReactApiImport(callee, apiNames, scopes, Boolean(options.resolveNamedAliases))) return true;
7717
+ if (isNamedReactApiImport(callee, apiNames, scopes)) return true;
7971
7718
  return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
7972
7719
  }
7973
7720
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || !includesApiName(apiNames, callee.property.name)) return false;
@@ -9899,11 +9646,6 @@ const walkParameterReferences = (pattern, state) => {
9899
9646
  const walk = (node, state) => {
9900
9647
  if (isFunctionLike$1(node)) {
9901
9648
  if (isNodeOfType(node, "FunctionDeclaration") && node.id) handleFunctionDeclaration(node, state);
9902
- const functionParams = node.params ?? [];
9903
- for (const param of functionParams) {
9904
- if (!("decorators" in param) || !Array.isArray(param.decorators)) continue;
9905
- for (const decorator of param.decorators) if (isAstNode(decorator)) walk(decorator, state);
9906
- }
9907
9649
  setNodeScope(node, state);
9908
9650
  const fnScope = pushScope(node.type === "ArrowFunctionExpression" ? "arrow-function" : "function", node, state);
9909
9651
  if ((isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && node.id) {
@@ -9916,6 +9658,7 @@ const walk = (node, state) => {
9916
9658
  });
9917
9659
  tagAsBinding(state, node.id);
9918
9660
  }
9661
+ const functionParams = node.params ?? [];
9919
9662
  handleFunctionParameters(functionParams, fnScope, state);
9920
9663
  for (const param of functionParams) walkParameterReferences(param, state);
9921
9664
  const body = node.body;
@@ -9925,9 +9668,6 @@ const walk = (node, state) => {
9925
9668
  }
9926
9669
  if (isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) {
9927
9670
  if (isNodeOfType(node, "ClassDeclaration") && node.id) handleClassDeclaration(node, state);
9928
- if (Array.isArray(node.decorators)) {
9929
- for (const decorator of node.decorators) if (isAstNode(decorator)) walk(decorator, state);
9930
- }
9931
9671
  const classScope = pushScope("class", node, state);
9932
9672
  setNodeScope(node, state);
9933
9673
  if (isNodeOfType(node, "ClassExpression") && node.id) {
@@ -10130,7 +9870,7 @@ const computeClosureCaptures = (functionNode, scopes) => {
10130
9870
  const functionScope = scopes.ownScopeFor(functionNode) ?? scopes.scopeFor(functionNode);
10131
9871
  const out = [];
10132
9872
  const seen = /* @__PURE__ */ new Set();
10133
- walkAst(functionNode, (node) => {
9873
+ const visit = (node) => {
10134
9874
  if (node !== functionNode && isFunctionLike$1(node)) {
10135
9875
  const innerCaptures = closureCaptures(node, scopes);
10136
9876
  for (const reference of innerCaptures) if (reference.resolvedSymbol && !isDescendantScope(reference.resolvedSymbol.scope, functionScope)) {
@@ -10139,7 +9879,7 @@ const computeClosureCaptures = (functionNode, scopes) => {
10139
9879
  seen.add(reference.id);
10140
9880
  }
10141
9881
  }
10142
- return false;
9882
+ return;
10143
9883
  }
10144
9884
  const reference = scopes.referenceFor(node);
10145
9885
  if (reference && reference.resolvedSymbol) {
@@ -10150,7 +9890,17 @@ const computeClosureCaptures = (functionNode, scopes) => {
10150
9890
  }
10151
9891
  }
10152
9892
  }
10153
- });
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);
10154
9904
  return out;
10155
9905
  };
10156
9906
  const capturesByAnalysis = /* @__PURE__ */ new WeakMap();
@@ -11176,11 +10926,6 @@ If the missing value is recreated every render, move it inside the hook or stabi
11176
10926
  return { CallExpression(node) {
11177
10927
  const hookName = getHookName(node.callee, context.scopes);
11178
10928
  if (!hookName || !isHookOfInterest(hookName, node.callee)) return;
11179
- if (HOOKS_REQUIRING_DEPS_MATCH.has(hookName) && !isReactApiCall(node, HOOKS_REQUIRING_DEPS_MATCH, context.scopes, {
11180
- allowGlobalReactNamespace: true,
11181
- allowUnboundBareCalls: true,
11182
- resolveNamedAliases: true
11183
- })) return;
11184
10929
  const callbackArgumentIndex = getCallbackArgumentIndex(hookName);
11185
10930
  const depsArgumentIndex = getDepsArgumentIndex(hookName);
11186
10931
  const callbackArgument = node.arguments[callbackArgumentIndex];
@@ -14475,11 +14220,7 @@ const jsIndexMaps = defineRule({
14475
14220
  //#region src/plugin/utils/are-expressions-structurally-equal.ts
14476
14221
  const areExpressionsStructurallyEqual = (a, b) => {
14477
14222
  if (!a || !b) return a === b;
14478
- const unwrappedA = stripParenExpression(a);
14479
- const unwrappedB = stripParenExpression(b);
14480
- if (unwrappedA !== a || unwrappedB !== b) return areExpressionsStructurallyEqual(unwrappedA, unwrappedB);
14481
14223
  if (a.type !== b.type) return false;
14482
- if (isNodeOfType(a, "ThisExpression")) return true;
14483
14224
  if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
14484
14225
  if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
14485
14226
  if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
@@ -19183,7 +18924,6 @@ const jwtInsecureVerification = defineRule({
19183
18924
  });
19184
18925
  //#endregion
19185
18926
  //#region src/plugin/rules/security-scan/key-lifecycle-risk.ts
19186
- const KEY_MATERIAL_MARKER_PATTERN = /PRIVATE KEY|SSH_PRIVATE_KEY|GPG_PRIVATE_KEY|DEPLOY_KEY|SIGNING_KEY/i;
19187
18927
  const keyLifecycleRisk = defineRule({
19188
18928
  id: "key-lifecycle-risk",
19189
18929
  title: "Long-lived key material in repository",
@@ -19191,7 +18931,7 @@ const keyLifecycleRisk = defineRule({
19191
18931
  committedFilesOnly: true,
19192
18932
  recommendation: "Remove private keys from source, rotate exposed credentials, prefer short-lived deploy credentials, and document revocation/expiry for release keys.",
19193
18933
  scan: scanByPattern({
19194
- 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),
19195
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,
19196
18936
  message: "Private or long-lived release key material appears in the repository."
19197
18937
  })
@@ -19897,21 +19637,15 @@ const localRpcNativeBridgeRisk = defineRule({
19897
19637
  message: "Code appears to bridge browser code to localhost/native capabilities with weak origin or update/install checks."
19898
19638
  })
19899
19639
  });
19900
- //#endregion
19901
- //#region src/plugin/rules/security-scan/mcp-tool-capability-risk.ts
19902
- const MCP_IMPORT_PATTERN = /\bfrom\s+["']@modelcontextprotocol\/sdk[^"']*["']|\bMcpServer\b|\bMcpAgent\b/;
19903
- const MCP_IMPORT_PREFILTER_PATTERN = /@modelcontextprotocol\/sdk|\bMcpServer\b|\bMcpAgent\b/;
19904
- const MCP_TOOL_SURFACE_PATTERN = /\bserver\.\s*tool\s*\(|\bregisterTool\s*\(|\bsetRequestHandler\s*\(\s*CallToolRequestSchema/;
19905
- const MCP_TOOL_SURFACE_PREFILTER_PATTERN = /\b(?:tool|registerTool|setRequestHandler)\b/;
19906
19640
  const mcpToolCapabilityRisk = defineRule({
19907
19641
  id: "mcp-tool-capability-risk",
19908
19642
  title: "MCP tool exposes dangerous capability",
19909
19643
  severity: "warn",
19910
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.",
19911
19645
  scan: scanByPattern({
19912
- 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),
19913
- pattern: MCP_TOOL_SURFACE_PATTERN,
19914
- 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],
19915
19649
  ignoreStringLiterals: true,
19916
19650
  message: "An MCP tool/resource/prompt handler appears to expose file, shell, network, or code-execution capability."
19917
19651
  })
@@ -21022,8 +20756,8 @@ const catchClauseRethrowsCaught = (handler) => {
21022
20756
  return didRethrow;
21023
20757
  };
21024
20758
  //#endregion
21025
- //#region src/plugin/utils/is-immediately-invoked-function.ts
21026
- const isImmediatelyInvokedFunction = (functionNode) => {
20759
+ //#region src/plugin/utils/find-guarding-try-statement.ts
20760
+ const isImmediatelyInvokedFunctionCallee = (functionNode) => {
21027
20761
  let wrappedCallee = functionNode;
21028
20762
  let enclosing = functionNode.parent;
21029
20763
  while (enclosing && stripParenExpression(enclosing) === functionNode) {
@@ -21032,13 +20766,11 @@ const isImmediatelyInvokedFunction = (functionNode) => {
21032
20766
  }
21033
20767
  return Boolean(enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee);
21034
20768
  };
21035
- //#endregion
21036
- //#region src/plugin/utils/find-guarding-try-statement.ts
21037
20769
  const findGuardingTryStatement = (node) => {
21038
20770
  let child = node;
21039
20771
  let ancestor = node.parent;
21040
20772
  while (ancestor) {
21041
- if (isFunctionLike$1(ancestor) && !isImmediatelyInvokedFunction(ancestor)) return null;
20773
+ if (isFunctionLike$1(ancestor) && !isImmediatelyInvokedFunctionCallee(ancestor)) return null;
21042
20774
  if (isNodeOfType(ancestor, "TryStatement") && ancestor.block === child && ancestor.handler && !catchClauseRethrowsCaught(ancestor.handler)) return ancestor;
21043
20775
  child = ancestor;
21044
20776
  ancestor = ancestor.parent ?? null;
@@ -21590,7 +21322,7 @@ const FILENAME_TO_LANG = {
21590
21322
  const resolveLang = (filename) => {
21591
21323
  return FILENAME_TO_LANG[path.extname(filename).toLowerCase()] ?? "tsx";
21592
21324
  };
21593
- const parseSourceText = ({ filename, sourceText, shouldAttachParentReferences = true }) => {
21325
+ const parseSourceText = (filename, sourceText) => {
21594
21326
  try {
21595
21327
  const result = parseSync(filename, sourceText, {
21596
21328
  astType: "ts",
@@ -21598,7 +21330,7 @@ const parseSourceText = ({ filename, sourceText, shouldAttachParentReferences =
21598
21330
  });
21599
21331
  if (result.errors.some((parseError) => parseError.severity === "Error")) return null;
21600
21332
  const parsedProgram = result.program;
21601
- if (shouldAttachParentReferences) attachParentReferences(parsedProgram);
21333
+ attachParentReferences(parsedProgram);
21602
21334
  return parsedProgram;
21603
21335
  } catch {
21604
21336
  return null;
@@ -21636,10 +21368,7 @@ const parseSourceFile = (absoluteFilePath) => {
21636
21368
  });
21637
21369
  return null;
21638
21370
  }
21639
- const parsedProgram = parseSourceText({
21640
- filename: absoluteFilePath,
21641
- sourceText
21642
- });
21371
+ const parsedProgram = parseSourceText(absoluteFilePath, sourceText);
21643
21372
  parseCache.set(absoluteFilePath, {
21644
21373
  mtimeMs: fileStat.mtimeMs,
21645
21374
  size: fileStat.size,
@@ -22171,8 +21900,6 @@ const DOM_QUERY_MEMBER_NAMES = new Set([
22171
21900
  ]);
22172
21901
  const LAYOUT_MEASUREMENT_MEMBER_NAMES = new Set([
22173
21902
  "current",
22174
- "textContent",
22175
- "innerText",
22176
21903
  "scrollWidth",
22177
21904
  "clientWidth",
22178
21905
  "offsetWidth",
@@ -22194,7 +21921,7 @@ const POST_MOUNT_GLOBAL_NAMES = new Set([
22194
21921
  "navigator"
22195
21922
  ]);
22196
21923
  const REF_FACTORY_CALLEE_NAMES = new Set(["useRef", "createRef"]);
22197
- 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");
22198
21925
  const isRefFactoryInitializer = (init) => {
22199
21926
  if (!init || !isNodeOfType(init, "CallExpression")) return false;
22200
21927
  const callee = init.callee;
@@ -22277,37 +22004,88 @@ const readsPostMountValue = (root) => {
22277
22004
  return found;
22278
22005
  };
22279
22006
  //#endregion
22280
- //#region src/plugin/rules/state-and-effects/utils/effect/get-ast-child-keys.ts
22281
- 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
+ };
22282
22019
  //#endregion
22283
22020
  //#region src/plugin/rules/state-and-effects/utils/effect/get-program-analysis.ts
22284
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
+ };
22285
22053
  const getProgramAnalysis = (anyNode) => {
22286
22054
  const programNode = findProgramRoot(anyNode);
22287
22055
  if (!programNode) return null;
22288
22056
  const cached = programToAnalysis.get(programNode);
22289
22057
  if (cached) return cached;
22290
- const analysis = {
22291
- programNode,
22292
- scopeManager: analyze(programNode, {
22058
+ const restorations = stripAndRecordParents(programNode);
22059
+ let scopeManager;
22060
+ try {
22061
+ scopeManager = analyze(programNode, {
22293
22062
  ecmaVersion: 2024,
22294
22063
  sourceType: "module",
22295
- childVisitorKeys: RUNTIME_VISITOR_KEYS,
22296
- fallback: getAstChildKeys
22297
- }),
22298
- scopeByNode: /* @__PURE__ */ new WeakMap(),
22299
- 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
22300
22073
  };
22301
22074
  programToAnalysis.set(programNode, analysis);
22302
22075
  return analysis;
22303
22076
  };
22304
- const getScopeForNode = (node, analysis) => {
22077
+ const scopeByNodeCache = /* @__PURE__ */ new WeakMap();
22078
+ const getScopeForNode = (node, manager) => {
22305
22079
  if (!node.range) return null;
22306
- const scopeByNode = analysis.scopeByNode;
22080
+ let scopeByNode = scopeByNodeCache.get(manager);
22081
+ if (!scopeByNode) {
22082
+ scopeByNode = /* @__PURE__ */ new WeakMap();
22083
+ scopeByNodeCache.set(manager, scopeByNode);
22084
+ }
22307
22085
  if (scopeByNode.has(node)) return scopeByNode.get(node) ?? null;
22308
22086
  let bestScope = null;
22309
22087
  let bestSize = Infinity;
22310
- for (const scope of analysis.scopeManager.scopes) {
22088
+ for (const scope of manager.scopes) {
22311
22089
  const block = scope.block;
22312
22090
  if (!block?.range) continue;
22313
22091
  if (node.range[0] < block.range[0] || node.range[1] > block.range[1]) continue;
@@ -22322,6 +22100,7 @@ const getScopeForNode = (node, analysis) => {
22322
22100
  };
22323
22101
  //#endregion
22324
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");
22325
22104
  const HOOK_NAME_PATTERN$2 = /^use[A-Z0-9]/;
22326
22105
  const isInsideCallbackArgumentOf = (identifier, initializer) => {
22327
22106
  if (!isNodeOfType(initializer, "CallExpression") && !isNodeOfType(initializer, "NewExpression")) return false;
@@ -22357,7 +22136,7 @@ const descend = (node, visit, visited = /* @__PURE__ */ new Set()) => {
22357
22136
  if (visited.has(node)) return;
22358
22137
  visit(node);
22359
22138
  visited.add(node);
22360
- const keys = getAstChildKeys(node);
22139
+ const keys = getChildKeys(node);
22361
22140
  const record = node;
22362
22141
  for (const key of keys) {
22363
22142
  const child = record[key];
@@ -22390,11 +22169,16 @@ const findDownstreamNodes = (topNode, type) => {
22390
22169
  });
22391
22170
  return nodes;
22392
22171
  };
22172
+ const refByIdentifierCache = /* @__PURE__ */ new WeakMap();
22393
22173
  const getRef = (analysis, identifier) => {
22394
- const refByIdentifier = analysis.referenceByIdentifier;
22174
+ let refByIdentifier = refByIdentifierCache.get(analysis);
22175
+ if (!refByIdentifier) {
22176
+ refByIdentifier = /* @__PURE__ */ new WeakMap();
22177
+ refByIdentifierCache.set(analysis, refByIdentifier);
22178
+ }
22395
22179
  if (refByIdentifier.has(identifier)) return refByIdentifier.get(identifier) ?? null;
22396
22180
  let resolvedReference = null;
22397
- const scope = getScopeForNode(identifier, analysis);
22181
+ const scope = getScopeForNode(identifier, analysis.scopeManager);
22398
22182
  if (scope) {
22399
22183
  for (const reference of scope.references) if (reference.identifier === identifier) {
22400
22184
  resolvedReference = reference;
@@ -22642,7 +22426,7 @@ const isReactFunctionalHOC = (analysis, node) => {
22642
22426
  const isWrappedSeparately = () => {
22643
22427
  if (!isNodeOfType(node.id, "Identifier")) return false;
22644
22428
  const bindingName = node.id.name;
22645
- const containingScope = getScopeForNode(node, analysis);
22429
+ const containingScope = getScopeForNode(node, analysis.scopeManager);
22646
22430
  if (!containingScope) return false;
22647
22431
  const variable = containingScope.variables.find((v) => v.name === bindingName);
22648
22432
  if (!variable) return false;
@@ -22873,14 +22657,11 @@ const hasCleanupReturn = (analysis, node, visited = /* @__PURE__ */ new WeakSet(
22873
22657
  if (isNodeOfType(node, "ReturnStatement") && node.argument != null) return isCleanupReturnArgument(analysis, node.argument);
22874
22658
  if (!isNodeOfType(node, "BlockStatement") && isFunctionLike$1(node)) return false;
22875
22659
  const record = node;
22876
- const childKeys = getAstChildKeys(node);
22877
- for (let keyIndex = 0; keyIndex < childKeys.length; keyIndex += 1) {
22878
- const value = record[childKeys[keyIndex]];
22879
- if (Array.isArray(value)) for (let itemIndex = 0; itemIndex < value.length; itemIndex += 1) {
22880
- const item = value[itemIndex];
22881
- if (isAstNode(item) && hasCleanupReturn(analysis, item, visited)) return true;
22882
- }
22883
- 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;
22884
22665
  }
22885
22666
  return false;
22886
22667
  };
@@ -23108,73 +22889,61 @@ const PURE_GLOBAL_CALLEE_NAMES = new Set([
23108
22889
  "Array",
23109
22890
  "BigInt",
23110
22891
  "Boolean",
23111
- "encodeURIComponent",
23112
22892
  "Number",
23113
22893
  "Object",
23114
22894
  "String",
23115
22895
  "parseFloat",
23116
- "parseInt",
23117
- "structuredClone"
23118
- ]);
23119
- const PURE_GLOBAL_CONSTRUCTOR_NAMES = new Set(["Date", "Set"]);
23120
- const PURE_HELPER_NAMESPACE_MEMBER_NAMES = new Map([
23121
- ["Array", new Set(["from"])],
23122
- ["JSON", new Set([
23123
- "isRawJSON",
23124
- "parse",
23125
- "rawJSON",
23126
- "stringify"
23127
- ])],
23128
- ["Math", new Set([
23129
- "abs",
23130
- "acos",
23131
- "acosh",
23132
- "asin",
23133
- "asinh",
23134
- "atan",
23135
- "atan2",
23136
- "atanh",
23137
- "cbrt",
23138
- "ceil",
23139
- "clz32",
23140
- "cos",
23141
- "cosh",
23142
- "exp",
23143
- "floor",
23144
- "fround",
23145
- "hypot",
23146
- "imul",
23147
- "log",
23148
- "log10",
23149
- "log1p",
23150
- "log2",
23151
- "max",
23152
- "min",
23153
- "pow",
23154
- "round",
23155
- "sign",
23156
- "sin",
23157
- "sinh",
23158
- "sqrt",
23159
- "tan",
23160
- "tanh",
23161
- "trunc"
23162
- ])],
23163
- ["Object", new Set(["assign"])]
22896
+ "parseInt"
23164
22897
  ]);
22898
+ const PURE_GLOBAL_NAMESPACE_NAMES = new Set(["JSON", "Math"]);
22899
+ const PURE_HELPER_NAMESPACE_MEMBER_NAMES = new Map([["JSON", new Set([
22900
+ "isRawJSON",
22901
+ "parse",
22902
+ "rawJSON",
22903
+ "stringify"
22904
+ ])], ["Math", new Set([
22905
+ "abs",
22906
+ "acos",
22907
+ "acosh",
22908
+ "asin",
22909
+ "asinh",
22910
+ "atan",
22911
+ "atan2",
22912
+ "atanh",
22913
+ "cbrt",
22914
+ "ceil",
22915
+ "clz32",
22916
+ "cos",
22917
+ "cosh",
22918
+ "exp",
22919
+ "floor",
22920
+ "fround",
22921
+ "hypot",
22922
+ "imul",
22923
+ "log",
22924
+ "log10",
22925
+ "log1p",
22926
+ "log2",
22927
+ "max",
22928
+ "min",
22929
+ "pow",
22930
+ "round",
22931
+ "sign",
22932
+ "sin",
22933
+ "sinh",
22934
+ "sqrt",
22935
+ "tan",
22936
+ "tanh",
22937
+ "trunc"
22938
+ ])]]);
23165
22939
  const PURE_MEMBER_TRANSFORM_NAMES = new Set([
23166
22940
  "concat",
23167
22941
  "filter",
23168
- "flatMap",
23169
22942
  "join",
23170
22943
  "map",
23171
- "reduce",
23172
- "replace",
23173
- "slice",
23174
22944
  "split",
23175
22945
  "toLowerCase",
23176
22946
  "toString",
23177
- "toSorted",
23178
22947
  "toUpperCase",
23179
22948
  "trim"
23180
22949
  ]);
@@ -23183,7 +22952,7 @@ const STORAGE_GLOBAL_NAMES$1 = new Set([
23183
22952
  "localStorage",
23184
22953
  "sessionStorage"
23185
22954
  ]);
23186
- const getStaticMemberName$1 = (node) => {
22955
+ const getStaticMemberName = (node) => {
23187
22956
  if (!isNodeOfType(node, "MemberExpression")) return null;
23188
22957
  if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
23189
22958
  if (node.computed && isNodeOfType(node.property, "Literal")) return typeof node.property.value === "string" ? node.property.value : null;
@@ -23198,7 +22967,7 @@ const getCallCalleeName$1 = (callExpression) => {
23198
22967
  if (!isNodeOfType(callExpression, "CallExpression")) return null;
23199
22968
  const callee = stripParenExpression(callExpression.callee);
23200
22969
  if (isNodeOfType(callee, "Identifier")) return callee.name;
23201
- return getStaticMemberName$1(callee);
22970
+ return getStaticMemberName(callee);
23202
22971
  };
23203
22972
  const getFunctionParameters = (functionNode) => isFunctionLike$1(functionNode) ? functionNode.params ?? [] : [];
23204
22973
  const getIdentifierBindingIdentity = (analysis, identifier) => {
@@ -23314,14 +23083,14 @@ const localUseEventPreservesCallback = (analysis, callee) => {
23314
23083
  if (!isNodeOfType(child, "CallExpression")) return;
23315
23084
  const forwardedCallee = stripParenExpression(child.callee);
23316
23085
  if (!isNodeOfType(forwardedCallee, "MemberExpression")) return;
23317
- if (getStaticMemberName$1(forwardedCallee) !== "current") return;
23086
+ if (getStaticMemberName(forwardedCallee) !== "current") return;
23318
23087
  if (!isNodeOfType(forwardedCallee.object, "Identifier")) return;
23319
23088
  const refReference = getRef(analysis, forwardedCallee.object);
23320
23089
  if (!callbackRefDeclarators.find((declarator) => refReference?.resolved?.defs.some((definition) => definition.node === declarator)) || !refReference?.resolved) return;
23321
23090
  if (!refReference.resolved.references.some((candidateReference) => {
23322
23091
  const memberExpression = candidateReference.identifier.parent;
23323
23092
  const assignmentExpression = memberExpression?.parent;
23324
- 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;
23325
23094
  return getIdentifierBindingIdentity(analysis, assignmentExpression.right) !== callbackBinding;
23326
23095
  })) forwardsCallback = true;
23327
23096
  });
@@ -23346,7 +23115,7 @@ const resolveWrappedCallable = (analysis, node) => {
23346
23115
  return callback && isFunctionLike$1(callback) ? callback : null;
23347
23116
  }
23348
23117
  if (!isNodeOfType(candidate, "MemberExpression")) return null;
23349
- if (getStaticMemberName$1(candidate) !== "current") return null;
23118
+ if (getStaticMemberName(candidate) !== "current") return null;
23350
23119
  if (!isNodeOfType(candidate.object, "Identifier")) return null;
23351
23120
  const reference = getRef(analysis, candidate.object);
23352
23121
  const declarator = reference?.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
@@ -23358,7 +23127,7 @@ const resolveWrappedCallable = (analysis, node) => {
23358
23127
  return Boolean(reference?.resolved?.references.some((candidateReference) => {
23359
23128
  const member = candidateReference.identifier.parent;
23360
23129
  const assignment = member?.parent;
23361
- 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);
23362
23131
  })) ? null : initializer;
23363
23132
  };
23364
23133
  const functionInvokesItself = (analysis, functionNode) => {
@@ -23397,7 +23166,7 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilena
23397
23166
  if (!isNodeOfType(child, "CallExpression")) return;
23398
23167
  const callee = stripParenExpression(child.callee);
23399
23168
  const calleeName = getCallCalleeName$1(child);
23400
- const memberName = getStaticMemberName$1(callee);
23169
+ const memberName = getStaticMemberName(callee);
23401
23170
  const isDeferringCall = calleeName !== null && DEFERRING_CALLEE_NAMES.has(calleeName) || memberName !== null && DEFERRING_MEMBER_NAMES.has(memberName);
23402
23171
  const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES$1.has(memberName) && isNodeOfType(callee, "MemberExpression");
23403
23172
  const enqueueFrame = (callableNode, argumentsForCallable, isDeferred, introducedBindings, allowWithoutInvocationEvidence = false) => {
@@ -23556,19 +23325,14 @@ const analyzeHelperExpression = (expression, environment, usedParameterIndices)
23556
23325
  const callbackSummary = analyzeHelperStatements(node.body.body ?? [], callbackEnvironment, usedParameterIndices, analyzeHelperExpression);
23557
23326
  return callbackSummary.isValid && !callbackSummary.canContinue;
23558
23327
  }
23559
- if (isNodeOfType(node, "NewExpression")) {
23560
- const callee = stripParenExpression(node.callee);
23561
- if (!isNodeOfType(callee, "Identifier") || !PURE_GLOBAL_CONSTRUCTOR_NAMES.has(callee.name) || environment.parameterIndices.has(callee.name) || environment.recursiveNames.has(callee.name) || environment.shadowedGlobalNames.has(callee.name)) return false;
23562
- return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
23563
- }
23564
23328
  if (isNodeOfType(node, "CallExpression")) {
23565
23329
  const callee = stripParenExpression(node.callee);
23566
23330
  const calleeRoot = getMemberRoot(callee);
23567
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);
23568
23332
  const namespaceName = isNodeOfType(calleeRoot, "Identifier") && !environment.parameterIndices.has(calleeRoot.name) && !environment.recursiveNames.has(calleeRoot.name) && !environment.shadowedGlobalNames.has(calleeRoot.name) ? calleeRoot.name : null;
23569
- const namespaceMemberName = getStaticMemberName$1(callee);
23333
+ const namespaceMemberName = getStaticMemberName(callee);
23570
23334
  const isPureNamespaceCall = isNodeOfType(callee, "MemberExpression") && namespaceName !== null && namespaceMemberName !== null && PURE_HELPER_NAMESPACE_MEMBER_NAMES.get(namespaceName)?.has(namespaceMemberName) === true;
23571
- 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) ?? "");
23572
23336
  if (!isPureGlobalCall && !isPureNamespaceCall && !isPureMemberTransform) return false;
23573
23337
  if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression") && !analyzeHelperExpression(callee.object, environment, usedParameterIndices)) return false;
23574
23338
  return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
@@ -23642,7 +23406,7 @@ const isLocallyConstructedObjectMember = (reference, memberExpression) => isNode
23642
23406
  return isNodeOfType(definitionNode, "VariableDeclarator") && Boolean(definitionNode.init) && (isNodeOfType(definitionNode.init, "ObjectExpression") || isNodeOfType(definitionNode.init, "ArrayExpression"));
23643
23407
  }) === true;
23644
23408
  const getUseRefDeclarator = (analysis, memberExpression) => {
23645
- 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;
23646
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;
23647
23411
  };
23648
23412
  const collectValueEvidence = (analysis, expression, frame, remainingCallFrames, visitedBindings = /* @__PURE__ */ new Set()) => {
@@ -23711,7 +23475,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23711
23475
  return collectValueEvidence(analysis, initializer.init, frame, remainingCallFrames, visitedBindings);
23712
23476
  }
23713
23477
  if (isNodeOfType(node, "MemberExpression")) {
23714
- if (getStaticMemberName$1(node) === "current" && isNodeOfType(node.object, "Identifier")) {
23478
+ if (getStaticMemberName(node) === "current" && isNodeOfType(node.object, "Identifier")) {
23715
23479
  const refBinding = getRef(analysis, node.object)?.resolved;
23716
23480
  const refDeclarator = useRefDeclarator;
23717
23481
  if (refBinding && refDeclarator && isNodeOfType(refDeclarator, "VariableDeclarator") && isNodeOfType(refDeclarator.init, "CallExpression")) {
@@ -23727,7 +23491,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23727
23491
  if (candidateReference.init) continue;
23728
23492
  const identifier = candidateReference.identifier;
23729
23493
  const member = identifier.parent;
23730
- if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName$1(member) !== "current") {
23494
+ if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName(member) !== "current") {
23731
23495
  evidence.hasUnknownSource = true;
23732
23496
  continue;
23733
23497
  }
@@ -23763,8 +23527,8 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23763
23527
  }
23764
23528
  const callee = stripParenExpression(node.callee);
23765
23529
  const calleeRoot = getMemberRoot(callee);
23766
- const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && getIdentifierBindingIdentity(analysis, callee) === null || isNodeOfType(callee, "MemberExpression") && isNodeOfType(calleeRoot, "Identifier") && getIdentifierBindingIdentity(analysis, calleeRoot) === null && PURE_HELPER_NAMESPACE_MEMBER_NAMES.get(calleeRoot.name)?.has(getStaticMemberName$1(callee) ?? "") === true;
23767
- const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName$1(callee) ?? "");
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;
23531
+ const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
23768
23532
  if (isPureGlobalCall || isPureMemberTransform) {
23769
23533
  if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression")) mergeEvidence(evidence, collectValueEvidence(analysis, callee.object, frame, remainingCallFrames, new Set(visitedBindings)));
23770
23534
  for (const argument of node.arguments ?? []) {
@@ -23816,15 +23580,6 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23816
23580
  }
23817
23581
  return evidence;
23818
23582
  }
23819
- if (isNodeOfType(node, "NewExpression")) {
23820
- const callee = stripParenExpression(node.callee);
23821
- if (!isNodeOfType(callee, "Identifier") || !PURE_GLOBAL_CONSTRUCTOR_NAMES.has(callee.name) || getIdentifierBindingIdentity(analysis, callee) !== null) {
23822
- evidence.hasUnknownSource = true;
23823
- return evidence;
23824
- }
23825
- for (const argument of node.arguments ?? []) mergeEvidence(evidence, collectValueEvidence(analysis, argument, frame, remainingCallFrames, new Set(visitedBindings)));
23826
- return evidence;
23827
- }
23828
23583
  if (isFunctionLike$1(node) || isNodeOfType(node, "AwaitExpression")) {
23829
23584
  evidence.hasUnknownSource = true;
23830
23585
  return evidence;
@@ -24243,26 +23998,6 @@ const STATEFUL_HTML_DESCENDANT_TAGS = new Set([
24243
23998
  "dialog",
24244
23999
  "canvas"
24245
24000
  ]);
24246
- const STATEFUL_HTML_ATTRIBUTE_NAMES = new Set([
24247
- "autofocus",
24248
- "contenteditable",
24249
- "draggable",
24250
- "tabindex"
24251
- ]);
24252
- const isStaticallyFalseAttributeValue = (attribute) => {
24253
- if (!isNodeOfType(attribute, "JSXAttribute") || !attribute.value) return false;
24254
- const value = isNodeOfType(attribute.value, "JSXExpressionContainer") ? attribute.value.expression : attribute.value;
24255
- return isNodeOfType(value, "Literal") && (value.value === false || value.value === "false");
24256
- };
24257
- const hasStatefulHtmlAttribute = (openingElement) => {
24258
- if (!isNodeOfType(openingElement, "JSXOpeningElement")) return false;
24259
- return openingElement.attributes.some((attribute) => {
24260
- if (!isNodeOfType(attribute, "JSXAttribute") || !isNodeOfType(attribute.name, "JSXIdentifier")) return false;
24261
- const attributeName = attribute.name.name.toLowerCase();
24262
- if (!STATEFUL_HTML_ATTRIBUTE_NAMES.has(attributeName)) return false;
24263
- return attributeName === "tabindex" || !isStaticallyFalseAttributeValue(attribute);
24264
- });
24265
- };
24266
24001
  const STATEFUL_DESCENDANT_SCAN_BUDGET = 200;
24267
24002
  const rootIdentifierNameOf = (expression) => {
24268
24003
  let object = expression;
@@ -24280,8 +24015,7 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
24280
24015
  budget -= 1;
24281
24016
  const node = stack.pop();
24282
24017
  if (isNodeOfType(node, "JSXElement")) {
24283
- const opening = node.openingElement;
24284
- const name = opening.name;
24018
+ const name = node.openingElement.name;
24285
24019
  if (name && isNodeOfType(name, "JSXIdentifier")) {
24286
24020
  const tagName = name.name;
24287
24021
  const firstChar = tagName.charCodeAt(0);
@@ -24289,7 +24023,6 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
24289
24023
  if (STATEFUL_HTML_DESCENDANT_TAGS.has(tagName)) return true;
24290
24024
  }
24291
24025
  if (name && isNodeOfType(name, "JSXMemberExpression")) return true;
24292
- if (hasStatefulHtmlAttribute(opening)) return true;
24293
24026
  const children = node.children ?? [];
24294
24027
  for (const child of children) stack.push(child);
24295
24028
  continue;
@@ -24383,7 +24116,6 @@ const ARITHMETIC_KEY_OPERATORS = new Set([
24383
24116
  "/",
24384
24117
  "%"
24385
24118
  ]);
24386
- const bindingMutationResultsByProgram = /* @__PURE__ */ new WeakMap();
24387
24119
  const extractCandidateIdentifiers = (expression) => {
24388
24120
  const node = stripParenExpression(expression);
24389
24121
  if (isNodeOfType(node, "Identifier")) return [node];
@@ -24424,13 +24156,6 @@ const isArrayFromCall = (node) => {
24424
24156
  const isBindingReassignedOrMutated = (referenceNode, bindingName) => {
24425
24157
  const programRoot = findProgramRoot(referenceNode);
24426
24158
  if (!programRoot) return false;
24427
- let mutationResultsByBindingName = bindingMutationResultsByProgram.get(programRoot);
24428
- if (!mutationResultsByBindingName) {
24429
- mutationResultsByBindingName = /* @__PURE__ */ new Map();
24430
- bindingMutationResultsByProgram.set(programRoot, mutationResultsByBindingName);
24431
- }
24432
- const cachedResult = mutationResultsByBindingName.get(bindingName);
24433
- if (cachedResult !== void 0) return cachedResult;
24434
24159
  let didFindWrite = false;
24435
24160
  walkAst(programRoot, (child) => {
24436
24161
  if (didFindWrite) return false;
@@ -24447,7 +24172,6 @@ const isBindingReassignedOrMutated = (referenceNode, bindingName) => {
24447
24172
  return false;
24448
24173
  }
24449
24174
  });
24450
- mutationResultsByBindingName.set(bindingName, didFindWrite);
24451
24175
  return didFindWrite;
24452
24176
  };
24453
24177
  /**
@@ -24849,14 +24573,6 @@ const templateHasOuterMemberIdentity = (template, bindingFunction) => {
24849
24573
  }
24850
24574
  return false;
24851
24575
  };
24852
- const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
24853
- const referencedItemNames = /* @__PURE__ */ new Set();
24854
- for (const expression of template.expressions ?? []) {
24855
- const unwrappedExpression = stripParenExpression(expression);
24856
- if (isNodeOfType(unwrappedExpression, "Identifier") && itemNames.has(unwrappedExpression.name)) referencedItemNames.add(unwrappedExpression.name);
24857
- }
24858
- return referencedItemNames;
24859
- };
24860
24576
  const forLoopTestReadsDataLength = (test) => {
24861
24577
  let didFindLengthRead = false;
24862
24578
  walkAst(test, (child) => {
@@ -24993,11 +24709,9 @@ const noArrayIndexAsKey = defineRule({
24993
24709
  } else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
24994
24710
  const jsxElement = openingElement.parent;
24995
24711
  if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
24996
- const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
24997
- const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET;
24998
24712
  if (!containsStatefulDescendant(jsxElement, {
24999
- memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET,
25000
- 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
25001
24715
  })) return;
25002
24716
  }
25003
24717
  }
@@ -25165,11 +24879,7 @@ const noAsyncEffectCallback = defineRule({
25165
24879
  severity: "warn",
25166
24880
  recommendation: "Don't make the effect callback `async`. Define an async function inside the effect and call it, then return a real cleanup function if you need one.",
25167
24881
  create: (context) => ({ CallExpression(node) {
25168
- if (!isReactApiCall(node, EFFECT_HOOK_NAMES$1, context.scopes, {
25169
- allowGlobalReactNamespace: true,
25170
- allowUnboundBareCalls: true,
25171
- resolveNamedAliases: true
25172
- })) return;
24882
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
25173
24883
  const callback = getEffectCallback(node);
25174
24884
  if (!callback) return;
25175
24885
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
@@ -25562,10 +25272,10 @@ const declaresAnyDependency = (manifest) => DEPENDENCY_SECTION_NAMES.some((secti
25562
25272
  const section = manifest[sectionName];
25563
25273
  return typeof section === "object" && section !== null && Object.keys(section).length > 0;
25564
25274
  });
25565
- const declaresDependency = (manifest, dependencyName) => DEPENDENCY_SECTION_NAMES.some((sectionName) => {
25566
- const section = manifest[sectionName];
25567
- return typeof section === "object" && section !== null && Object.prototype.propertyIsEnumerable.call(section, dependencyName);
25568
- });
25275
+ const declaresDependency = (manifest, dependencyName) => {
25276
+ for (const declaredName of iterateDependencyNames(manifest)) if (declaredName === dependencyName) return true;
25277
+ return false;
25278
+ };
25569
25279
  const cachedPlatformByManifest = /* @__PURE__ */ new WeakMap();
25570
25280
  const classifyPackagePlatform = (filename) => {
25571
25281
  const manifest = readNearestPackageManifest(filename);
@@ -25812,23 +25522,25 @@ const isCallArgumentFunctionExpression = (node) => {
25812
25522
  return parent.arguments.some((argumentNode) => argumentNode === node);
25813
25523
  };
25814
25524
  const isNestedRenderEvidenceBoundary = (node) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isCallArgumentFunctionExpression(node);
25815
- const containsRenderOutput$1 = (rootNode, scopes) => {
25816
- let hasRenderOutput = false;
25817
- walkAst(rootNode, (node) => {
25818
- if (hasRenderOutput) return false;
25819
- if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
25820
- if (node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS)) {
25821
- hasRenderOutput = true;
25822
- return false;
25823
- }
25824
- });
25825
- 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;
25826
25538
  };
25827
25539
  const renderOutputCache = /* @__PURE__ */ new WeakMap();
25828
25540
  const functionContainsReactRenderOutput = (functionNode, scopes) => {
25829
25541
  const cachedEntry = renderOutputCache.get(functionNode);
25830
25542
  if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
25831
- const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
25543
+ const hasRenderOutput = containsRenderOutput$1(functionNode, functionNode, scopes);
25832
25544
  renderOutputCache.set(functionNode, {
25833
25545
  scopes,
25834
25546
  hasRenderOutput
@@ -26367,19 +26079,6 @@ const noCloneElement = defineRule({
26367
26079
  } })
26368
26080
  });
26369
26081
  //#endregion
26370
- //#region src/plugin/utils/get-call-method-name.ts
26371
- /**
26372
- * Returns the static method name of a call's callee when it's a
26373
- * non-computed MemberExpression (`obj.method` → `"method"`), or
26374
- * `null` otherwise. Used by `no-pass-data-to-parent` and
26375
- * `no-pass-live-state-to-parent` to match the receiver-bound
26376
- * method-call shape against an allow/block list.
26377
- */
26378
- const getCallMethodName = (callee) => {
26379
- if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
26380
- return null;
26381
- };
26382
- //#endregion
26383
26082
  //#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
26384
26083
  const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
26385
26084
  const CONTEXT_MODULES = [
@@ -26387,35 +26086,23 @@ const CONTEXT_MODULES = [
26387
26086
  "use-context-selector",
26388
26087
  "react-tracked"
26389
26088
  ];
26390
- const getSupportedContextImportSource = (symbol) => {
26391
- if (symbol?.kind !== "import") return null;
26392
- const importDeclaration = symbol.declarationNode.parent;
26393
- if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration") || typeof importDeclaration.source.value !== "string" || !CONTEXT_MODULES.includes(importDeclaration.source.value)) return null;
26394
- return importDeclaration.source.value;
26395
- };
26396
- const isSupportedNamespaceSymbol = (symbol) => getSupportedContextImportSource(symbol) !== null && Boolean(symbol && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
26397
- const isDestructuredCreateContextBinding = (identifier, scopes) => {
26398
- if (!isNodeOfType(identifier, "Identifier")) return false;
26399
- const symbol = scopes.symbolFor(identifier);
26400
- if (symbol?.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "ObjectPattern")) return false;
26401
- const property = symbol.bindingIdentifier.parent;
26402
- if (!property || !isNodeOfType(property, "Property") || getStaticPropertyKeyName(property, { allowComputedString: true }) !== "createContext") return false;
26403
- const initializer = stripParenExpression(symbol.initializer);
26404
- return isNodeOfType(initializer, "Identifier") && isSupportedNamespaceSymbol(resolveConstIdentifierAlias(initializer, scopes));
26405
- };
26406
- const isCreateContextCall = (node, scopes) => {
26407
- const callee = stripParenExpression(node.callee);
26089
+ const isCreateContextCallee = (callee) => {
26408
26090
  if (isNodeOfType(callee, "Identifier")) {
26409
- if (isDestructuredCreateContextBinding(callee, scopes)) return true;
26410
- const symbol = resolveConstIdentifierAlias(callee, scopes);
26411
- return getSupportedContextImportSource(symbol) !== null && Boolean(symbol && getImportedName(symbol.declarationNode) === "createContext");
26091
+ const binding = getImportBindingForName(callee, callee.name);
26092
+ return binding !== null && binding.exportedName === "createContext" && CONTEXT_MODULES.includes(binding.source);
26412
26093
  }
26413
- if (!isNodeOfType(callee, "MemberExpression")) return false;
26414
- if ((getCallMethodName(callee) ?? (callee.computed && isNodeOfType(callee.property, "Literal") && typeof callee.property.value === "string" ? callee.property.value : null)) !== "createContext") return false;
26415
- const receiver = stripParenExpression(callee.object);
26416
- if (!isNodeOfType(receiver, "Identifier")) return false;
26417
- if (receiver.name === "React" && scopes.isGlobalReference(receiver)) return true;
26418
- return isSupportedNamespaceSymbol(resolveConstIdentifierAlias(receiver, scopes));
26094
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed) {
26095
+ const namespaceIdentifier = callee.object;
26096
+ const propertyIdentifier = callee.property;
26097
+ if (!isNodeOfType(namespaceIdentifier, "Identifier")) return false;
26098
+ if (!isNodeOfType(propertyIdentifier, "Identifier")) return false;
26099
+ if (propertyIdentifier.name !== "createContext") return false;
26100
+ const namespaceName = namespaceIdentifier.name;
26101
+ if (isCanonicalReactNamespaceName(namespaceName)) return true;
26102
+ const importSource = getImportSourceForName(namespaceIdentifier, namespaceName);
26103
+ return importSource !== null && CONTEXT_MODULES.includes(importSource);
26104
+ }
26105
+ return false;
26419
26106
  };
26420
26107
  const noCreateContextInRender = defineRule({
26421
26108
  id: "no-create-context-in-render",
@@ -26424,7 +26111,7 @@ const noCreateContextInRender = defineRule({
26424
26111
  category: "Correctness",
26425
26112
  recommendation: "Move `createContext(...)` outside the component, to the top level of the file, so it stays the same on every render.",
26426
26113
  create: (context) => ({ CallExpression(node) {
26427
- if (!isCreateContextCall(node, context.scopes)) return;
26114
+ if (!isCreateContextCallee(node.callee)) return;
26428
26115
  const componentOrHookName = enclosingComponentOrHookName(node);
26429
26116
  if (!componentOrHookName) return;
26430
26117
  context.report({
@@ -27659,7 +27346,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
27659
27346
  let ancestor = setStateCall.parent;
27660
27347
  while (ancestor) {
27661
27348
  if (!lifecycleMember) {
27662
- 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;
27663
27350
  if (isLifecycleMember(ancestor, lifecycleNames)) lifecycleMember = ancestor;
27664
27351
  } else if (isEs5Component(ancestor) || isEs6Component(ancestor)) {
27665
27352
  if (nestedFunctionCount > 1 && !options.disallowInNestedFunctions) return false;
@@ -27861,7 +27548,7 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
27861
27548
  const body = lifecycleFunction.body;
27862
27549
  if (!body) return derivedNames;
27863
27550
  walkAst(body, (node) => {
27864
- if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
27551
+ if (FUNCTION_NODE_TYPES.has(node.type)) return false;
27865
27552
  if (!isNodeOfType(node, "VariableDeclarator")) return;
27866
27553
  const init = node.init;
27867
27554
  if (!init) return;
@@ -27871,67 +27558,6 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
27871
27558
  return derivedNames;
27872
27559
  };
27873
27560
  const isStatefulOperand = (node, paramNames, derivedNames) => referencesAnyName(node, paramNames) || referencesAnyName(node, derivedNames) || containsThisStateOrProps(node);
27874
- const getStaticMemberName = (node) => {
27875
- if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
27876
- return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
27877
- };
27878
- const getThisStateFieldName = (node) => {
27879
- const unwrappedNode = stripParenExpression(node);
27880
- if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
27881
- const object = stripParenExpression(unwrappedNode.object);
27882
- if (!isNodeOfType(object, "MemberExpression") || !isNodeOfType(stripParenExpression(object.object), "ThisExpression") || getStaticMemberName(object) !== "state") return null;
27883
- return getStaticMemberName(unwrappedNode);
27884
- };
27885
- const collectLocalInitializers = (lifecycleFunction) => {
27886
- const initializers = /* @__PURE__ */ new Map();
27887
- const body = lifecycleFunction.body;
27888
- if (!body) return initializers;
27889
- walkAst(body, (node) => {
27890
- if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
27891
- if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.init) initializers.set(node.id.name, node.init);
27892
- });
27893
- return initializers;
27894
- };
27895
- const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
27896
- if (readsPostMountValue(node)) return true;
27897
- const referencedNames = /* @__PURE__ */ new Set();
27898
- collectReferenceIdentifierNames(node, referencedNames);
27899
- for (const referencedName of referencedNames) {
27900
- if (visitedNames.has(referencedName)) continue;
27901
- const initializer = localInitializers.get(referencedName);
27902
- if (!initializer) continue;
27903
- if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
27904
- }
27905
- return false;
27906
- };
27907
- const getSetStateFieldValue = (setStateCall, fieldName) => {
27908
- if (!isNodeOfType(setStateCall, "CallExpression")) return null;
27909
- const argument = setStateCall.arguments?.[0];
27910
- if (!argument || !isNodeOfType(argument, "ObjectExpression")) return null;
27911
- for (const property of argument.properties ?? []) {
27912
- if (!isNodeOfType(property, "Property") || property.computed === true) continue;
27913
- 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;
27914
- }
27915
- return null;
27916
- };
27917
- const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
27918
- let qualifies = false;
27919
- walkAst(test, (node) => {
27920
- if (qualifies) return false;
27921
- if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
27922
- const leftFieldName = getThisStateFieldName(node.left);
27923
- const rightFieldName = getThisStateFieldName(node.right);
27924
- const fieldName = leftFieldName ?? rightFieldName;
27925
- const comparedValue = leftFieldName ? node.right : node.left;
27926
- if (!fieldName || !leftFieldName && !rightFieldName) return;
27927
- const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
27928
- if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
27929
- if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
27930
- qualifies = true;
27931
- return false;
27932
- });
27933
- return qualifies;
27934
- };
27935
27561
  const isDiffGuardTest = (test, paramNames, derivedNames) => {
27936
27562
  if (referencesAnyName(test, paramNames)) return true;
27937
27563
  let qualifies = false;
@@ -27939,7 +27565,7 @@ const isDiffGuardTest = (test, paramNames, derivedNames) => {
27939
27565
  if (qualifies) return false;
27940
27566
  if (!isNodeOfType(node, "BinaryExpression")) return;
27941
27567
  if (!EQUALITY_OPERATORS.has(node.operator)) return;
27942
- 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)) {
27943
27569
  qualifies = true;
27944
27570
  return false;
27945
27571
  }
@@ -27952,12 +27578,11 @@ const isInsideDiffGuard = (setStateCall) => {
27952
27578
  const paramNames = /* @__PURE__ */ new Set();
27953
27579
  for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
27954
27580
  const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
27955
- const localInitializers = collectLocalInitializers(lifecycleFunction);
27956
27581
  let child = setStateCall;
27957
27582
  let ancestor = setStateCall.parent;
27958
27583
  while (ancestor && ancestor !== lifecycleFunction) {
27959
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;
27960
- if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers))) return true;
27585
+ if (guardTest && isDiffGuardTest(guardTest, paramNames, derivedNames)) return true;
27961
27586
  child = ancestor;
27962
27587
  ancestor = ancestor.parent ?? null;
27963
27588
  }
@@ -28138,16 +27763,7 @@ const producesOpaqueInstanceValue = (expression) => {
28138
27763
  const collectSetterValueObservations = (componentBody, setterNames) => {
28139
27764
  const plainFedSetterNames = /* @__PURE__ */ new Set();
28140
27765
  const opaqueFedSetterNames = /* @__PURE__ */ new Set();
28141
- const callbackRefSetterNames = /* @__PURE__ */ new Set();
28142
27766
  walkComponentRespectingShadows(componentBody, /* @__PURE__ */ new Set(), (node, currentlyShadowed) => {
28143
- if (isNodeOfType(node, "JSXAttribute")) {
28144
- const attributeName = node.name;
28145
- if (isNodeOfType(attributeName, "JSXIdentifier") && attributeName.name === "ref" && node.value && isNodeOfType(node.value, "JSXExpressionContainer")) {
28146
- const expression = stripParenExpression(node.value.expression);
28147
- if (isNodeOfType(expression, "Identifier") && setterNames.has(expression.name)) callbackRefSetterNames.add(expression.name);
28148
- }
28149
- return;
28150
- }
28151
27767
  if (!isNodeOfType(node, "CallExpression")) return;
28152
27768
  if (!isNodeOfType(node.callee, "Identifier")) return;
28153
27769
  const setterName = node.callee.name;
@@ -28164,10 +27780,21 @@ const collectSetterValueObservations = (componentBody, setterNames) => {
28164
27780
  }, true);
28165
27781
  return {
28166
27782
  plainFedSetterNames,
28167
- opaqueFedSetterNames,
28168
- callbackRefSetterNames
27783
+ opaqueFedSetterNames
28169
27784
  };
28170
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
+ };
28171
27798
  const collectFunctionLocalBindings = (functionNode) => {
28172
27799
  const localBindings = /* @__PURE__ */ new Set();
28173
27800
  if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return localBindings;
@@ -28179,8 +27806,8 @@ const collectFunctionLocalBindings = (functionNode) => {
28179
27806
  return localBindings;
28180
27807
  };
28181
27808
  const collectBlockScopedBindings = (node) => {
27809
+ const blockBindings = /* @__PURE__ */ new Set();
28182
27810
  if (isNodeOfType(node, "BlockStatement")) {
28183
- const blockBindings = /* @__PURE__ */ new Set();
28184
27811
  for (const statement of node.body ?? []) {
28185
27812
  if (!isNodeOfType(statement, "VariableDeclaration")) continue;
28186
27813
  for (const declarator of statement.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
@@ -28188,22 +27815,17 @@ const collectBlockScopedBindings = (node) => {
28188
27815
  return blockBindings;
28189
27816
  }
28190
27817
  if (isNodeOfType(node, "ForStatement") && isNodeOfType(node.init, "VariableDeclaration")) {
28191
- const blockBindings = /* @__PURE__ */ new Set();
28192
27818
  for (const declarator of node.init.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
28193
27819
  return blockBindings;
28194
27820
  }
28195
- if ((isNodeOfType(node, "ForOfStatement") || isNodeOfType(node, "ForInStatement")) && isNodeOfType(node.left, "VariableDeclaration")) {
28196
- const blockBindings = /* @__PURE__ */ new Set();
28197
- for (const declarator of node.left.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
28198
- return blockBindings;
28199
- }
28200
- 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;
28201
27823
  };
28202
27824
  const walkComponentRespectingShadows = (node, shadowedStateNames, visit, isComponentBodyRoot = false) => {
28203
27825
  if (!node || typeof node !== "object") return;
28204
27826
  let nextShadowedStateNames = shadowedStateNames;
28205
- const localBindings = isComponentBodyRoot ? null : isFunctionLike$1(node) ? collectFunctionLocalBindings(node) : collectBlockScopedBindings(node);
28206
- if (localBindings && localBindings.size > 0) {
27827
+ const localBindings = isComponentBodyRoot ? /* @__PURE__ */ new Set() : isFunctionLike$1(node) ? collectFunctionLocalBindings(node) : collectBlockScopedBindings(node);
27828
+ if (localBindings.size > 0) {
28207
27829
  const merged = new Set(shadowedStateNames);
28208
27830
  for (const localName of localBindings) merged.add(localName);
28209
27831
  nextShadowedStateNames = merged;
@@ -28229,10 +27851,11 @@ const noDirectStateMutation = defineRule({
28229
27851
  const bindings = collectUseStateBindings(componentBody);
28230
27852
  if (bindings.length === 0) return;
28231
27853
  const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
27854
+ const callbackRefSetterNames = collectCallbackRefSetterNames(componentBody);
28232
27855
  const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
28233
27856
  const plainObjectStateValueNames = /* @__PURE__ */ new Set();
28234
27857
  for (const binding of bindings) {
28235
- if (setterValueObservations.callbackRefSetterNames.has(binding.setterName)) continue;
27858
+ if (callbackRefSetterNames.has(binding.setterName)) continue;
28236
27859
  if (!isNodeOfType(binding.declarator.init, "CallExpression")) continue;
28237
27860
  const initializerArgument = binding.declarator.init.arguments?.[0];
28238
27861
  if (!initializerMarksPlainState(initializerArgument)) continue;
@@ -28374,31 +27997,20 @@ const noDocumentStartViewTransition = defineRule({
28374
27997
  } })
28375
27998
  });
28376
27999
  //#endregion
28377
- //#region src/plugin/utils/get-static-property-name.ts
28378
- const getStaticPropertyName = (memberExpression) => {
28379
- const property = memberExpression.property;
28380
- if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
28381
- if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
28382
- if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
28383
- return null;
28384
- };
28385
- //#endregion
28386
28000
  //#region src/plugin/rules/js-performance/no-document-write.ts
28387
28001
  const MESSAGE$24 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
28388
28002
  const WRITE_METHODS = new Set(["write", "writeln"]);
28389
- const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
28390
28003
  const noDocumentWrite = defineRule({
28391
28004
  id: "no-document-write",
28392
28005
  title: "document.write/writeln",
28393
28006
  severity: "warn",
28394
28007
  recommendation: "Don't use `document.write()`/`document.writeln()`. Append DOM nodes or set `innerHTML`/`textContent` on a specific element instead.",
28395
28008
  create: (context) => ({ CallExpression(node) {
28396
- const callee = stripParenExpression(node.callee);
28397
- if (!isNodeOfType(callee, "MemberExpression")) return;
28009
+ const callee = node.callee;
28010
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
28398
28011
  const receiver = stripParenExpression(callee.object);
28399
- if (!isNodeOfType(receiver, "Identifier") || !isGlobalDocumentReference(receiver, context)) return;
28400
- const methodName = getStaticPropertyName(callee);
28401
- if (methodName === null || !WRITE_METHODS.has(methodName)) return;
28012
+ if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "document") return;
28013
+ if (!isNodeOfType(callee.property, "Identifier") || !WRITE_METHODS.has(callee.property.name)) return;
28402
28014
  context.report({
28403
28015
  node,
28404
28016
  message: MESSAGE$24
@@ -29205,14 +28817,6 @@ const noEffectWithFreshDeps = defineRule({
29205
28817
  //#endregion
29206
28818
  //#region src/plugin/rules/security/no-eval.ts
29207
28819
  const SANDBOX_SURFACE_PATH_PATTERN = /(?:^|[/-])sandbox(?:$|[/-])|(?:^|\/)[\w.]+-sandbox(?:\/|\.[cm]?[jt]sx?$)/i;
29208
- const getExecutableGlobalName = (node, context) => {
29209
- const expression = stripParenExpression(node);
29210
- if (isNodeOfType(expression, "Identifier")) return context.scopes.isGlobalReference(expression) ? expression.name : null;
29211
- if (!isNodeOfType(expression, "MemberExpression")) return null;
29212
- const receiver = stripParenExpression(expression.object);
29213
- if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "globalThis" || !context.scopes.isGlobalReference(receiver)) return null;
29214
- return getStaticPropertyName(expression);
29215
- };
29216
28820
  const noEval = defineRule({
29217
28821
  id: "no-eval",
29218
28822
  title: "eval() runs untrusted code strings",
@@ -29222,21 +28826,20 @@ const noEval = defineRule({
29222
28826
  if (SANDBOX_SURFACE_PATH_PATTERN.test(normalizeFilename(context.filename ?? ""))) return {};
29223
28827
  return {
29224
28828
  CallExpression(node) {
29225
- const executableGlobalName = getExecutableGlobalName(node.callee, context);
29226
- if (executableGlobalName === "eval") {
28829
+ if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "eval") {
29227
28830
  context.report({
29228
28831
  node,
29229
28832
  message: "eval() is a code-injection vulnerability: it runs any string as code."
29230
28833
  });
29231
28834
  return;
29232
28835
  }
29233
- if ((executableGlobalName === "setTimeout" || executableGlobalName === "setInterval") && isNodeOfType(node.arguments?.[0], "Literal") && typeof node.arguments[0].value === "string") context.report({
28836
+ if (isNodeOfType(node.callee, "Identifier") && (node.callee.name === "setTimeout" || node.callee.name === "setInterval") && isNodeOfType(node.arguments?.[0], "Literal") && typeof node.arguments[0].value === "string") context.report({
29234
28837
  node,
29235
- message: `Passing a string to ${executableGlobalName}() is a code-injection vulnerability, since it runs that string as code.`
28838
+ message: `Passing a string to ${node.callee.name}() is a code-injection vulnerability, since it runs that string as code.`
29236
28839
  });
29237
28840
  },
29238
28841
  NewExpression(node) {
29239
- if (getExecutableGlobalName(node.callee, context) === "Function") {
28842
+ if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "Function") {
29240
28843
  const onlyArgument = node.arguments.length === 1 ? node.arguments[0] : void 0;
29241
28844
  if (isNodeOfType(onlyArgument, "Literal") && typeof onlyArgument.value === "string" && onlyArgument.value.trim() === "return this") return;
29242
28845
  context.report({
@@ -30495,11 +30098,7 @@ const noFetchInEffect = defineRule({
30495
30098
  severity: "warn",
30496
30099
  recommendation: "Use a data-fetching layer or Server Component so fetches do not race, double-fire, or leak from `useEffect`.",
30497
30100
  create: (context) => ({ CallExpression(node) {
30498
- if (!isReactApiCall(node, EFFECT_HOOK_NAMES$1, context.scopes, {
30499
- allowGlobalReactNamespace: true,
30500
- allowUnboundBareCalls: true,
30501
- resolveNamedAliases: true
30502
- })) return;
30101
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
30503
30102
  const callback = getEffectCallback(node);
30504
30103
  if (!callback) return;
30505
30104
  const analysisFunctions = collectEffectAnalysisFunctions(callback, context);
@@ -32974,60 +32573,25 @@ const resolveSettings$16 = (settings) => {
32974
32573
  };
32975
32574
  const isHocCall = (call, scopes) => {
32976
32575
  if (!isNodeOfType(call, "CallExpression")) return false;
32977
- if (isReactApiCall(call, REACT_HOC_NAMES, scopes, {
32978
- allowGlobalReactNamespace: true,
32979
- allowUnboundBareCalls: true
32980
- })) return true;
32981
- if (isReactHocMemberReference(call.callee, scopes)) return true;
32576
+ const calleeName = flattenCalleeName(call.callee);
32577
+ if (calleeName && REACT_HOC_NAMES.has(calleeName)) return true;
32982
32578
  if (!isNodeOfType(call.callee, "Identifier")) return false;
32983
32579
  const symbol = scopes.symbolFor(call.callee);
32984
32580
  if (!symbol) return false;
32985
- return symbolMapsToHoc(symbol, scopes, /* @__PURE__ */ new Set());
32986
- };
32987
- const isReactImportEquals = (symbol) => {
32988
- if (symbol.kind !== "ts-import-equals" || !isNodeOfType(symbol.declarationNode, "TSImportEqualsDeclaration")) return false;
32989
- const moduleReference = symbol.declarationNode.moduleReference;
32990
- 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);
32991
32582
  };
32992
- const isRequireReactCall$1 = (node, scopes) => {
32993
- if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier") || node.callee.name !== "require" || !scopes.isGlobalReference(node.callee)) return false;
32994
- const moduleSpecifier = node.arguments[0];
32995
- return Boolean(moduleSpecifier && isNodeOfType(moduleSpecifier, "Literal") && typeof moduleSpecifier.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(moduleSpecifier.value));
32996
- };
32997
- const isReactNamespaceExpression = (node, scopes) => {
32998
- if (isRequireReactCall$1(node, scopes)) return true;
32999
- if (!isNodeOfType(node, "Identifier")) return false;
33000
- const symbol = scopes.symbolFor(node);
33001
- if (!symbol) return node.name === "React" && scopes.isGlobalReference(node);
33002
- if (symbol.initializer && isRequireReactCall$1(symbol.initializer, scopes)) return true;
33003
- if (isReactImportEquals(symbol)) return true;
33004
- return isReactNamespaceImport(node, scopes);
33005
- };
33006
- 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));
33007
- const getDestructuredPropertyName$1 = (symbol) => {
33008
- const property = symbol.bindingIdentifier.parent;
33009
- if (!property || !isNodeOfType(property, "Property") || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
33010
- if (isNodeOfType(property.key, "Identifier") && !property.computed) return property.key.name;
33011
- if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
33012
- return null;
33013
- };
33014
- const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
33015
- if (visitedSymbolIds.has(symbol.id)) return false;
33016
- visitedSymbolIds.add(symbol.id);
33017
- if (symbol.kind === "import") {
33018
- const importedName = getImportedName(symbol.declarationNode);
33019
- 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;
33020
32586
  }
33021
32587
  const init = symbol.initializer;
33022
32588
  if (!init) return false;
33023
- const destructuredPropertyName = getDestructuredPropertyName$1(symbol);
33024
- if (destructuredPropertyName && REACT_HOC_NAMES.has(destructuredPropertyName) && isReactNamespaceExpression(init, scopes)) return true;
33025
- if (isReactHocMemberReference(init, scopes)) return true;
33026
- if (isNodeOfType(init, "Identifier")) {
33027
- const initializedFromSymbol = scopes.symbolFor(init);
33028
- if (initializedFromSymbol) return symbolMapsToHoc(initializedFromSymbol, scopes, visitedSymbolIds);
33029
- 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;
33030
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;
33031
32595
  return false;
33032
32596
  };
33033
32597
  const isTrivialPassthroughChild = (child) => {
@@ -33085,14 +32649,26 @@ const isHocComponent = (call, scopes) => {
33085
32649
  };
33086
32650
  const containsJsx = (root) => {
33087
32651
  let found = false;
33088
- walkAst(root, (node) => {
33089
- if (found) return false;
32652
+ const visit = (node) => {
32653
+ if (found) return;
33090
32654
  if (node.type === "JSXElement" || node.type === "JSXFragment") {
33091
32655
  found = true;
33092
- return false;
32656
+ return;
33093
32657
  }
33094
- if (node !== root && (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "ClassDeclaration" || node.type === "ClassExpression")) return false;
33095
- });
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);
33096
32672
  return found;
33097
32673
  };
33098
32674
  const expressionContainsJsx = (expression) => {
@@ -33116,7 +32692,7 @@ const unwrapTsCast = (expression) => {
33116
32692
  while (current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSNonNullExpression") current = stripParenExpression(current.expression);
33117
32693
  return current;
33118
32694
  };
33119
- const collectReExportedNames = (program, scopes) => {
32695
+ const collectReExportedNames = (program) => {
33120
32696
  const names = /* @__PURE__ */ new Set();
33121
32697
  if (!isNodeOfType(program, "Program")) return names;
33122
32698
  for (const statement of program.body) {
@@ -33140,7 +32716,8 @@ const collectReExportedNames = (program, scopes) => {
33140
32716
  if (!declarator.init) continue;
33141
32717
  const init = unwrapTsCast(declarator.init);
33142
32718
  if (isNodeOfType(init, "CallExpression")) {
33143
- if (isHocCall(init, scopes)) {
32719
+ const calleeName = flattenCalleeName(init.callee);
32720
+ if (calleeName && REACT_HOC_NAMES.has(calleeName)) {
33144
32721
  const wrappedArg = init.arguments[0];
33145
32722
  if (wrappedArg && isNodeOfType(wrappedArg, "Identifier")) names.add(wrappedArg.name);
33146
32723
  }
@@ -33209,7 +32786,16 @@ const recordComponent = (context, name, reportNode, isStateless) => {
33209
32786
  isStateless
33210
32787
  });
33211
32788
  };
33212
- 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
+ };
33213
32799
  const walkComponentSearch = (node, context) => {
33214
32800
  if (isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) {
33215
32801
  if (isEs6Component(node)) {
@@ -33311,13 +32897,12 @@ const noMultiComp = defineRule({
33311
32897
  components: [],
33312
32898
  componentDepth: 0,
33313
32899
  currentVarName: null,
33314
- scopes: context.scopes,
33315
- visitChild: (child) => walkComponentSearch(child, visitContext)
32900
+ scopes: context.scopes
33316
32901
  };
33317
32902
  for (const statement of node.body) walkComponentSearch(statement, visitContext);
33318
32903
  const flagged = settings.ignoreStateless ? visitContext.components.filter((component) => !component.isStateless) : visitContext.components;
33319
32904
  if (flagged.length <= 2) return;
33320
- const reExportedNames = collectReExportedNames(node, context.scopes);
32905
+ const reExportedNames = collectReExportedNames(node);
33321
32906
  const exportedCount = flagged.filter((component) => isExportedDeclaration(component.reportNode, reExportedNames)).length;
33322
32907
  if (exportedCount >= flagged.length || flagged.length >= 4 && exportedCount >= Math.floor(flagged.length * .7) || flagged.length >= 8 && exportedCount >= Math.floor(flagged.length * .5)) return;
33323
32908
  const isSmallFeatureModule = exportedCount > 0 && exportedCount <= 2 && exportedCount < flagged.length;
@@ -34709,6 +34294,19 @@ const DATA_SINK_METHOD_NAMES = new Set([
34709
34294
  "deserialize"
34710
34295
  ]);
34711
34296
  //#endregion
34297
+ //#region src/plugin/utils/get-call-method-name.ts
34298
+ /**
34299
+ * Returns the static method name of a call's callee when it's a
34300
+ * non-computed MemberExpression (`obj.method` → `"method"`), or
34301
+ * `null` otherwise. Used by `no-pass-data-to-parent` and
34302
+ * `no-pass-live-state-to-parent` to match the receiver-bound
34303
+ * method-call shape against an allow/block list.
34304
+ */
34305
+ const getCallMethodName = (callee) => {
34306
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
34307
+ return null;
34308
+ };
34309
+ //#endregion
34712
34310
  //#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
34713
34311
  const isUseStateIdentifier = (identifier) => {
34714
34312
  if (!isNodeOfType(identifier, "Identifier")) return false;
@@ -35334,30 +34932,6 @@ const guardsRenderShape = (comparison) => {
35334
34932
  }
35335
34933
  return false;
35336
34934
  };
35337
- const isPropsChildrenLength = (node, scopes) => {
35338
- const unwrappedNode = stripParenExpression(node);
35339
- return isNodeOfType(unwrappedNode, "MemberExpression") && !unwrappedNode.computed && isNodeOfType(unwrappedNode.property, "Identifier") && unwrappedNode.property.name === "length" && resolvesToPropsChildren(stripParenExpression(unwrappedNode.object), scopes);
35340
- };
35341
- const isLargeTextLengthComparison = (node, scopes) => {
35342
- const unwrappedNode = stripParenExpression(node);
35343
- if (!isNodeOfType(unwrappedNode, "BinaryExpression")) return false;
35344
- const leftIsLength = isPropsChildrenLength(unwrappedNode.left, scopes);
35345
- const rightIsLength = isPropsChildrenLength(unwrappedNode.right, scopes);
35346
- const thresholdNode = leftIsLength ? unwrappedNode.right : unwrappedNode.left;
35347
- if (!leftIsLength && !rightIsLength || !isNodeOfType(thresholdNode, "Literal")) return false;
35348
- if (typeof thresholdNode.value !== "number" || thresholdNode.value < 1e3) return false;
35349
- return leftIsLength ? unwrappedNode.operator === ">" || unwrappedNode.operator === ">=" : unwrappedNode.operator === "<" || unwrappedNode.operator === "<=";
35350
- };
35351
- const isLargeStringOptimizationGuard = (comparison, scopes) => {
35352
- let current = findTransparentExpressionRoot(comparison);
35353
- while (current.parent) {
35354
- const parent = current.parent;
35355
- if (!isNodeOfType(parent, "LogicalExpression") || parent.operator !== "&&") return false;
35356
- if (isLargeTextLengthComparison(parent.left === current ? parent.right : parent.left, scopes)) return true;
35357
- current = findTransparentExpressionRoot(parent);
35358
- }
35359
- return false;
35360
- };
35361
34935
  const noPolymorphicChildren = defineRule({
35362
34936
  id: "no-polymorphic-children",
35363
34937
  title: "Children type checked at runtime",
@@ -35371,7 +34945,6 @@ const noPolymorphicChildren = defineRule({
35371
34945
  const isStringLiteral = (operand) => isNodeOfType(operand, "Literal") && operand.value === "string";
35372
34946
  if (!isStringLiteral(node.left) && !isStringLiteral(node.right)) return;
35373
34947
  if (!guardsRenderShape(node)) return;
35374
- if (isLargeStringOptimizationGuard(node, context.scopes)) return;
35375
34948
  context.report({
35376
34949
  node,
35377
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."
@@ -35589,27 +35162,6 @@ const hasPreviousValueDep = (effectNode, depElements) => {
35589
35162
  }
35590
35163
  return false;
35591
35164
  };
35592
- const isStateLikeDependency = (analysis, element, isPropName) => {
35593
- if (!isNodeOfType(element, "Identifier") || isPropName(element.name)) return false;
35594
- if (!analysis) return true;
35595
- const reference = getRef(analysis, element);
35596
- if (!reference) return true;
35597
- const upstreamReferences = getUpstreamRefs(analysis, reference);
35598
- if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
35599
- return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
35600
- };
35601
- const getRefHeldPropCallbackName = (callExpression, isPropName) => {
35602
- const callee = stripParenExpression(callExpression.callee);
35603
- if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "current") return null;
35604
- const receiver = stripParenExpression(callee.object);
35605
- if (!isNodeOfType(receiver, "Identifier")) return null;
35606
- const binding = findVariableInitializer(callExpression, receiver.name);
35607
- if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
35608
- if (getCalleeName$2(binding.initializer) !== "useRef") return null;
35609
- const callbackArgument = binding.initializer.arguments?.[0];
35610
- if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
35611
- return isPropName(callbackArgument.name) ? callbackArgument.name : null;
35612
- };
35613
35165
  const noPropCallbackInEffect = defineRule({
35614
35166
  id: "no-prop-callback-in-effect",
35615
35167
  title: "Parent kept in sync with a callback effect",
@@ -35626,11 +35178,11 @@ const noPropCallbackInEffect = defineRule({
35626
35178
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
35627
35179
  const depsNode = node.arguments[1];
35628
35180
  if (!isNodeOfType(depsNode, "ArrayExpression") || !depsNode.elements?.length) return;
35629
- const analysis = getProgramAnalysis(node);
35630
- 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));
35631
35182
  if (stateLikeDeps.length === 0) return;
35632
35183
  if (isRefLatchGuardedEffect(callback.body)) return;
35633
35184
  if (hasPreviousValueDep(node, depsNode.elements ?? [])) return;
35185
+ const analysis = getProgramAnalysis(node);
35634
35186
  if (analysis) {
35635
35187
  const stateLikeDepRefs = [];
35636
35188
  for (const element of stateLikeDeps) {
@@ -35642,9 +35194,9 @@ const noPropCallbackInEffect = defineRule({
35642
35194
  const reportedNodes = /* @__PURE__ */ new Set();
35643
35195
  walkInsideStatementBlocks(callback.body, (child) => {
35644
35196
  if (!isNodeOfType(child, "CallExpression")) return;
35645
- const directCallee = stripParenExpression(child.callee);
35646
- const calleeName = isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName);
35647
- if (!calleeName) return;
35197
+ if (!isNodeOfType(child.callee, "Identifier")) return;
35198
+ const calleeName = child.callee.name;
35199
+ if (!propStackTracker.isPropName(calleeName)) return;
35648
35200
  if (!isResultDiscardedCall(child)) return;
35649
35201
  if (reportedNodes.has(child)) return;
35650
35202
  reportedNodes.add(child);
@@ -39818,18 +39370,34 @@ const resolveSettings$8 = (settings) => {
39818
39370
  };
39819
39371
  const expressionContainsJsxOrCreateElement = (root) => {
39820
39372
  let found = false;
39821
- walkAst(root, (node) => {
39822
- if (found) return false;
39823
- if (node !== root && NESTED_FUNCTION_TYPES.has(node.type)) return false;
39373
+ const visit = (node) => {
39374
+ if (found) return;
39824
39375
  if (node.type === "JSXElement" || node.type === "JSXFragment") {
39825
39376
  found = true;
39826
- return false;
39377
+ return;
39827
39378
  }
39828
39379
  if (isNodeOfType(node, "CallExpression") && isCreateElementCall(node)) {
39829
39380
  found = true;
39830
- return false;
39381
+ return;
39831
39382
  }
39832
- });
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);
39833
39401
  return found;
39834
39402
  };
39835
39403
  const isReactClassComponent = (classNode) => {
@@ -40755,6 +40323,19 @@ const classifyExport = (name, reportNode, isFunction, initializer, state) => {
40755
40323
  reportNode
40756
40324
  };
40757
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
+ };
40758
40339
  const isEntryPointFile = (filename) => {
40759
40340
  const lastSlash = Math.max(filename.lastIndexOf("/"), filename.lastIndexOf("\\"));
40760
40341
  const basename = lastSlash === -1 ? filename : filename.slice(lastSlash + 1);
@@ -40793,212 +40374,197 @@ const onlyExportComponents = defineRule({
40793
40374
  category: "Architecture",
40794
40375
  create: (context) => {
40795
40376
  const settings = resolveSettings$6(context.settings);
40796
- if (!isFileNameAllowed(normalizeFilename(context.filename ?? ""), settings.checkJS)) return {};
40797
- const exportNodes = [];
40798
- const componentCandidates = [];
40799
- const pushExportNode = (node) => {
40800
- exportNodes.push(node);
40801
- };
40802
- const pushComponentCandidate = (node) => {
40803
- componentCandidates.push(node);
40804
- };
40805
- return {
40806
- ExportAllDeclaration: pushExportNode,
40807
- ExportDefaultDeclaration: pushExportNode,
40808
- ExportNamedDeclaration: pushExportNode,
40809
- FunctionDeclaration: pushComponentCandidate,
40810
- VariableDeclarator: pushComponentCandidate,
40811
- ClassDeclaration: pushComponentCandidate,
40812
- "Program:exit"() {
40813
- const localComponentNames = /* @__PURE__ */ new Set();
40814
- const state = {
40815
- customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
40816
- allowExportNames: new Set(settings.allowExportNames),
40817
- allowConstantExport: settings.allowConstantExport,
40818
- localComponentNames,
40819
- scopes: context.scopes
40820
- };
40821
- for (const child of componentCandidates) {
40822
- if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
40823
- if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) && functionContainsReactRenderOutput(child, context.scopes)) localComponentNames.add(child.id.name);
40824
- }
40825
- if (isNodeOfType(child, "ClassDeclaration") && child.id) {
40826
- if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
40827
- }
40828
- if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
40829
- const initializer = child.init;
40830
- if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) {
40831
- const expression = initializer ? skipTsExpression(initializer) : null;
40832
- if (!(expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"))) || functionContainsReactRenderOutput(expression, context.scopes)) localComponentNames.add(child.id.name);
40833
- }
40834
- }
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);
40835
40391
  }
40836
- const exports = [];
40837
- let hasReactExport = false;
40838
- let hasAnyExports = false;
40839
- const isExportedNodeIds = /* @__PURE__ */ new WeakSet();
40840
- for (const child of exportNodes) {
40841
- if (isNodeOfType(child, "ExportAllDeclaration")) {
40842
- if (child.exportKind === "type") continue;
40843
- hasAnyExports = true;
40844
- context.report({
40845
- node: child,
40846
- message: EXPORT_ALL_MESSAGE
40847
- });
40848
- 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);
40849
40400
  }
40850
- if (isNodeOfType(child, "ExportDefaultDeclaration")) {
40851
- hasAnyExports = true;
40852
- const declaration = child.declaration;
40853
- const stripped = skipTsExpression(declaration);
40854
- if (isNodeOfType(stripped, "FunctionDeclaration") || isNodeOfType(stripped, "FunctionExpression")) {
40855
- if (stripped.id) {
40856
- const idNode = stripped.id;
40857
- isExportedNodeIds.add(stripped);
40858
- exports.push(classifyExport(idNode.name, idNode, true, null, state));
40859
- } else {
40860
- context.report({
40861
- node: stripped,
40862
- message: ANONYMOUS_MESSAGE
40863
- });
40864
- hasReactExport = true;
40865
- }
40866
- continue;
40867
- }
40868
- if (isNodeOfType(stripped, "ClassDeclaration") || isNodeOfType(stripped, "ClassExpression")) {
40869
- if (stripped.id) {
40870
- const idNode = stripped.id;
40871
- isExportedNodeIds.add(stripped);
40872
- if (isReactComponentName(idNode.name) && isEs6Component(stripped)) hasReactExport = true;
40873
- else exports.push({
40874
- kind: "non-component",
40875
- reportNode: idNode
40876
- });
40877
- } 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({
40878
40428
  node: stripped,
40879
40429
  message: ANONYMOUS_MESSAGE
40880
40430
  });
40881
- continue;
40882
- }
40883
- if (isNodeOfType(stripped, "Identifier")) {
40884
- exports.push(classifyExport(stripped.name, stripped, false, null, state));
40885
- continue;
40431
+ hasReactExport = true;
40886
40432
  }
40887
- if (isNodeOfType(stripped, "CallExpression")) {
40888
- if (isRouteFactoryCall(stripped)) {
40889
- hasReactExport = true;
40890
- continue;
40891
- }
40892
- const isHoc = isHocCallee(stripped.callee, state);
40893
- const firstArg = stripped.arguments[0];
40894
- const firstArgIsValid = Boolean(firstArg) && (() => {
40895
- if (!firstArg) return false;
40896
- const expression = skipTsExpression(firstArg);
40897
- if (isNodeOfType(expression, "Identifier")) return true;
40898
- if (isNodeOfType(expression, "FunctionExpression") && expression.id) return true;
40899
- if (isNodeOfType(expression, "CallExpression") && isHocCallee(expression.callee, state)) return true;
40900
- return false;
40901
- })();
40902
- if (isHoc && firstArgIsValid) hasReactExport = true;
40903
- 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({
40904
40441
  kind: "non-component",
40905
- reportNode: stripped
40906
- });
40907
- else context.report({
40908
- node: stripped,
40909
- message: ANONYMOUS_MESSAGE
40910
- });
40911
- continue;
40912
- }
40913
- if (isNodeOfType(stripped, "ObjectExpression")) {
40914
- context.report({
40915
- node: stripped,
40916
- message: objectExpressionBundlesComponents(stripped, state) ? NAMESPACE_OBJECT_MESSAGE : ANONYMOUS_MESSAGE
40917
- });
40918
- continue;
40919
- }
40920
- if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "Literal")) {
40921
- context.report({
40922
- node: stripped,
40923
- message: ANONYMOUS_MESSAGE
40442
+ reportNode: idNode
40924
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;
40925
40457
  continue;
40926
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")) {
40927
40488
  context.report({
40928
- node: child,
40489
+ node: stripped,
40929
40490
  message: ANONYMOUS_MESSAGE
40930
40491
  });
40931
40492
  continue;
40932
40493
  }
40933
- if (isNodeOfType(child, "ExportNamedDeclaration")) {
40934
- if (child.exportKind === "type") continue;
40935
- hasAnyExports = true;
40936
- if (child.declaration) {
40937
- const declaration = child.declaration;
40938
- if (isNodeOfType(declaration, "FunctionDeclaration") && declaration.id) {
40939
- isExportedNodeIds.add(declaration);
40940
- exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state));
40941
- } else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
40942
- isExportedNodeIds.add(declaration);
40943
- if (isReactComponentName(declaration.id.name) && isEs6Component(declaration)) exports.push({ kind: "react-component" });
40944
- else exports.push({
40945
- kind: "non-component",
40946
- reportNode: declaration.id
40947
- });
40948
- } else if (isNodeOfType(declaration, "VariableDeclaration")) for (const declarator of declaration.declarations) {
40949
- if (!isNodeOfType(declarator.id, "Identifier")) continue;
40950
- isExportedNodeIds.add(declarator);
40951
- const isFunction = canBeReactFunctionComponent(declarator.init ?? null, state);
40952
- exports.push(classifyExport(declarator.id.name, declarator.id, isFunction, declarator.init, state));
40953
- }
40954
- else if (declaration.type === "TSEnumDeclaration" || declaration.type === "TSInterfaceDeclaration" || declaration.type === "TSTypeAliasDeclaration") {
40955
- if (declaration.type === "TSEnumDeclaration") exports.push({
40956
- kind: "non-component",
40957
- reportNode: declaration
40958
- });
40959
- }
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));
40960
40520
  }
40961
- const isReExportFromSource = Boolean(child.source);
40962
- for (const specifier of child.specifiers ?? []) {
40963
- if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
40964
- const exported = specifier.exported;
40965
- const local = specifier.local;
40966
- let exportedName = null;
40967
- if (exported && isNodeOfType(exported, "Identifier")) exportedName = exported.name;
40968
- const localName = local && isNodeOfType(local, "Identifier") ? local.name : null;
40969
- const reportNode = specifier;
40970
- let entry;
40971
- if (exportedName === "default" && localName) entry = classifyExport(localName, reportNode, false, null, state);
40972
- else if (exportedName) entry = classifyExport(exportedName, reportNode, false, null, state);
40973
- else {
40974
- entry = {
40975
- kind: "non-component",
40976
- reportNode
40977
- };
40978
- if (localName && isReactComponentName(localName)) exports.push({ kind: "react-component" });
40979
- }
40980
- if (isReExportFromSource && entry.kind !== "react-component") continue;
40981
- 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" });
40982
40546
  }
40547
+ if (isReExportFromSource && entry.kind !== "react-component") continue;
40548
+ exports.push(entry);
40983
40549
  }
40984
40550
  }
40985
- for (const entry of exports) if (entry.kind === "react-component") hasReactExport = true;
40986
- 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({
40987
40559
  node: entry.reportNode,
40988
- 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
40989
40565
  });
40990
- if (hasAnyExports && hasReactExport) for (const entry of exports) {
40991
- if (entry.kind === "non-component") context.report({
40992
- node: entry.reportNode,
40993
- message: NAMED_EXPORT_MESSAGE
40994
- });
40995
- if (entry.kind === "react-context") context.report({
40996
- node: entry.reportNode,
40997
- message: REACT_CONTEXT_MESSAGE
40998
- });
40999
- }
41000
40566
  }
41001
- };
40567
+ } };
41002
40568
  }
41003
40569
  });
41004
40570
  //#endregion
@@ -41100,12 +40666,7 @@ const postmessageOriginRisk = defineRule({
41100
40666
  scan: (file) => {
41101
40667
  if (!isProductionSourcePath(file.relativePath)) return [];
41102
40668
  if (WORKER_FILE_PATH_PATTERN.test(file.relativePath)) return [];
41103
- if (!file.content.includes("addEventListener") && !file.content.includes("onmessage")) return [];
41104
- const ast = parseSourceText({
41105
- filename: file.absolutePath,
41106
- sourceText: file.content,
41107
- shouldAttachParentReferences: false
41108
- });
40669
+ const ast = parseSourceText(file.absolutePath, file.content);
41109
40670
  if (ast === null) return [];
41110
40671
  const findings = [];
41111
40672
  walkAst(ast, (node) => {
@@ -43485,6 +43046,15 @@ const isStableHookWrapperArgument = (node) => {
43485
43046
  const parent = node.parent;
43486
43047
  return isNodeOfType(parent, "CallExpression") && isNodeOfType(parent.callee, "Identifier") && STABLE_HOOK_WRAPPERS.has(parent.callee.name) && Boolean(parent.arguments?.some((argument) => argument === node));
43487
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
+ };
43488
43058
  const queryStableQueryClient = defineRule({
43489
43059
  id: "query-stable-query-client",
43490
43060
  title: "Unstable QueryClient in component",
@@ -54797,6 +54367,12 @@ const webhookSignatureRisk = defineRule({
54797
54367
  //#region src/plugin/rules/zod/utils/zod-ast.ts
54798
54368
  const ZOD_MODULE = "zod";
54799
54369
  const ZOD_MODULE_SOURCES = [ZOD_MODULE];
54370
+ const getStaticPropertyName = (member) => {
54371
+ const property = member.property;
54372
+ if (!member.computed && isNodeOfType(property, "Identifier")) return property.name;
54373
+ if (member.computed && isNodeOfType(property, "Literal") && typeof property.value === "string") return property.value;
54374
+ return null;
54375
+ };
54800
54376
  const importInfoCache = /* @__PURE__ */ new WeakMap();
54801
54377
  const getImportInfoForIdentifier = (identifier) => {
54802
54378
  if (importInfoCache.has(identifier)) return importInfoCache.get(identifier) ?? null;
@@ -59977,10 +59553,7 @@ const appendNode = (builder, block, node) => {
59977
59553
  };
59978
59554
  const mapDescendantsToBlock = (builder, node, block) => {
59979
59555
  builder.nodeBlock.set(node, block);
59980
- if (isFunctionLike$1(node)) {
59981
- builder.nestedFunctions.push(node);
59982
- return;
59983
- }
59556
+ if (isFunctionLike$1(node)) return;
59984
59557
  const record = node;
59985
59558
  for (const key of Object.keys(record)) {
59986
59559
  if (key === "parent") continue;
@@ -60214,12 +59787,11 @@ const buildStatement = (builder, statement, current) => {
60214
59787
  mapDescendantsToBlock(builder, statement, current);
60215
59788
  return current;
60216
59789
  };
60217
- const buildFunctionCfg = (functionNode, body, nestedFunctionSink) => {
59790
+ const buildFunctionCfg = (functionNode, body) => {
60218
59791
  const builder = {
60219
59792
  blocks: [],
60220
59793
  entry: null,
60221
59794
  exit: null,
60222
- nestedFunctions: nestedFunctionSink,
60223
59795
  nodeBlock: /* @__PURE__ */ new Map(),
60224
59796
  loopStack: [],
60225
59797
  switchStack: [],
@@ -60237,12 +59809,13 @@ const buildFunctionCfg = (functionNode, body, nestedFunctionSink) => {
60237
59809
  bodyEnd = entry;
60238
59810
  }
60239
59811
  addEdge(bodyEnd, exit, "uncond");
59812
+ const blockOf = (node) => builder.nodeBlock.get(node) ?? null;
60240
59813
  return {
60241
59814
  owner: functionNode,
60242
59815
  entry,
60243
59816
  exit,
60244
59817
  blocks: builder.blocks,
60245
- blockOf: (node) => builder.nodeBlock.get(node) ?? null
59818
+ blockOf
60246
59819
  };
60247
59820
  };
60248
59821
  const computeUnconditionalSet = (cfg) => {
@@ -60279,9 +59852,8 @@ const computeUnconditionalSet = (cfg) => {
60279
59852
  const analyzeControlFlow = (program) => {
60280
59853
  nextBlockId = 0;
60281
59854
  const functionCfgs = /* @__PURE__ */ new Map();
60282
- const pendingFunctions = [];
60283
59855
  const buildFor = (functionNode, body) => {
60284
- const cfg = buildFunctionCfg(functionNode, body, pendingFunctions);
59856
+ const cfg = buildFunctionCfg(functionNode, body);
60285
59857
  functionCfgs.set(functionNode, {
60286
59858
  cfg,
60287
59859
  unconditionalSet: computeUnconditionalSet(cfg)
@@ -60291,18 +59863,21 @@ const analyzeControlFlow = (program) => {
60291
59863
  type: "BlockStatement",
60292
59864
  body: program.body
60293
59865
  });
60294
- for (let functionIndex = 0; functionIndex < pendingFunctions.length; functionIndex += 1) {
60295
- const functionNode = pendingFunctions[functionIndex];
60296
- if (!isFunctionLike$1(functionNode) || !functionNode.body || functionCfgs.has(functionNode)) continue;
60297
- buildFor(functionNode, functionNode.body);
60298
- }
60299
- const getFunctionEntry = (functionNode) => {
60300
- const existingEntry = functionCfgs.get(functionNode);
60301
- if (existingEntry) return existingEntry;
60302
- if (!isFunctionLike$1(functionNode) || !functionNode.body) return null;
60303
- buildFor(functionNode, functionNode.body);
60304
- 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
+ }
60305
59879
  };
59880
+ visit(program);
60306
59881
  const enclosingFunction = (node) => {
60307
59882
  let current = node;
60308
59883
  while (current) {
@@ -60313,12 +59888,12 @@ const analyzeControlFlow = (program) => {
60313
59888
  return null;
60314
59889
  };
60315
59890
  const cfgFor = (functionLike) => {
60316
- return getFunctionEntry(functionLike)?.cfg ?? null;
59891
+ return functionCfgs.get(functionLike)?.cfg ?? null;
60317
59892
  };
60318
59893
  const isUnconditionalFromEntry = (node) => {
60319
59894
  const owner = enclosingFunction(node);
60320
59895
  if (!owner) return true;
60321
- const entry = getFunctionEntry(owner);
59896
+ const entry = functionCfgs.get(owner);
60322
59897
  if (!entry) return true;
60323
59898
  const block = entry.cfg.blockOf(node);
60324
59899
  if (!block) return true;
@@ -60392,14 +59967,17 @@ const wrapWithSemanticContext = (rule) => ({
60392
59967
  }
60393
59968
  };
60394
59969
  const visitors = rule.create(enrichedContext);
60395
- const innerProgramHandler = visitors.Program;
60396
- return {
60397
- ...visitors,
60398
- Program: ((node) => {
60399
- programRoot = node;
60400
- if (innerProgramHandler) innerProgramHandler(node);
60401
- })
60402
- };
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;
60403
59981
  }
60404
59982
  });
60405
59983
  //#endregion