oxlint-plugin-react-doctor 0.7.5-dev.037bd56 → 0.7.5-dev.0f07133

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 +631 -1162
  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;
@@ -9070,30 +8817,6 @@ const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, conte
9070
8817
  }
9071
8818
  return matchingBlocks.size > 0;
9072
8819
  };
9073
- const doMatchingNodesCoverEveryPathFromFunctionEntry = (owner, matchingNodes, context) => {
9074
- const functionCfg = context.cfg.cfgFor(owner);
9075
- if (!functionCfg) return false;
9076
- const matchingBlocks = new Set(matchingNodes.flatMap((matchingNode) => {
9077
- if (context.cfg.enclosingFunction(matchingNode) !== owner) return [];
9078
- const matchingBlock = functionCfg.blockOf(matchingNode);
9079
- return matchingBlock ? [matchingBlock] : [];
9080
- }));
9081
- if (matchingBlocks.size === 0) return false;
9082
- const visitedBlocks = new Set([functionCfg.entry]);
9083
- const pendingBlocks = [functionCfg.entry];
9084
- while (pendingBlocks.length > 0) {
9085
- const currentBlock = pendingBlocks.pop();
9086
- if (!currentBlock) break;
9087
- if (matchingBlocks.has(currentBlock)) continue;
9088
- for (const edge of currentBlock.successors) {
9089
- if (edge.to === functionCfg.exit) return false;
9090
- if (visitedBlocks.has(edge.to)) continue;
9091
- visitedBlocks.add(edge.to);
9092
- pendingBlocks.push(edge.to);
9093
- }
9094
- }
9095
- return true;
9096
- };
9097
8820
  const removeSynchronouslyReleasedUsages = (callback, usages, context) => {
9098
8821
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
9099
8822
  if (!isNodeOfType(callback.body, "BlockStatement")) return usages;
@@ -9294,83 +9017,6 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
9294
9017
  });
9295
9018
  return didCleanupFunctionMatch;
9296
9019
  };
9297
- const doesBoundCleanupReleaseUsage = (expression, usage, context) => {
9298
- const callExpression = stripParenExpression(expression);
9299
- if (!isNodeOfType(callExpression, "CallExpression")) return false;
9300
- const bindCallee = stripParenExpression(callExpression.callee);
9301
- if (!isNodeOfType(bindCallee, "MemberExpression") || bindCallee.computed || !isNodeOfType(bindCallee.property, "Identifier") || bindCallee.property.name !== "bind") return false;
9302
- const releaseMember = stripParenExpression(bindCallee.object);
9303
- if (!isNodeOfType(releaseMember, "MemberExpression") || releaseMember.computed || !isNodeOfType(releaseMember.property, "Identifier")) return false;
9304
- const releaseReceiverKey = resolveExpressionKey$1(releaseMember.object, context);
9305
- if (releaseReceiverKey === null || releaseReceiverKey !== resolveExpressionKey$1(callExpression.arguments?.[0], context)) return false;
9306
- const releaseVerbName = releaseMember.property.name;
9307
- if (usage.kind === "socket") return usage.handleKey === releaseReceiverKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
9308
- return usage.kind === "subscribe" && usage.handleKey === releaseReceiverKey && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub" || releaseVerbName === "close" || releaseVerbName === "unwatch" || releaseVerbName === "unlisten" || BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(releaseVerbName));
9309
- };
9310
- const callbackReturnsCleanupForUsage = (callback, usage, context) => {
9311
- if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
9312
- const doesReturnedValueReleaseUsage = (returnedValue) => {
9313
- if (doesBoundCleanupReleaseUsage(returnedValue, usage, context)) return true;
9314
- const cleanupFunction = resolveStableValue(returnedValue, context);
9315
- if (cleanupFunction && doesBoundCleanupReleaseUsage(cleanupFunction, usage, context)) return true;
9316
- return Boolean(cleanupFunction && isFunctionLike$1(cleanupFunction) && doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context));
9317
- };
9318
- if (!isNodeOfType(callback.body, "BlockStatement")) return doesReturnedValueReleaseUsage(stripParenExpression(callback.body));
9319
- const matchingCleanupReturns = [];
9320
- walkInsideStatementBlocks(callback.body, (child) => {
9321
- if (isNodeOfType(child, "ReturnStatement") && child.argument && doesReturnedValueReleaseUsage(stripParenExpression(child.argument))) matchingCleanupReturns.push(child);
9322
- });
9323
- return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
9324
- };
9325
- const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
9326
- if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
9327
- const functionCfg = context.cfg.cfgFor(callback);
9328
- const usageBlock = functionCfg?.blockOf(usage.node);
9329
- const usageStart = getRangeStart(usage.node);
9330
- if (!functionCfg || !usageBlock || usageStart === null) return false;
9331
- const findHandleGuard = (releaseCall) => {
9332
- if (usage.handleKey === null) return null;
9333
- let ancestor = releaseCall.parent;
9334
- while (ancestor && ancestor !== callback.body) {
9335
- if (isNodeOfType(ancestor, "IfStatement")) return ancestor.alternate === null && resolveExpressionKey$1(ancestor.test, context) === usage.handleKey && getRangeStart(ancestor) !== null && (getRangeStart(ancestor) ?? usageStart) < usageStart ? ancestor : null;
9336
- ancestor = ancestor.parent;
9337
- }
9338
- return null;
9339
- };
9340
- const matchingReleaseAnchors = [];
9341
- walkInsideStatementBlocks(callback.body, (child) => {
9342
- if (!isNodeOfType(child, "CallExpression")) return;
9343
- const releaseStart = getRangeStart(child);
9344
- const handleGuard = findHandleGuard(child);
9345
- if (releaseStart === null || releaseStart >= usageStart || functionCfg.blockOf(child) !== usageBlock && !handleGuard) return;
9346
- if (doesReleaseCallMatchUsage(child, usage, context)) {
9347
- matchingReleaseAnchors.push(handleGuard ?? child);
9348
- return;
9349
- }
9350
- const helperFunction = resolveStableValue(child.callee, context);
9351
- if (helperFunction && isFunctionLike$1(helperFunction) && doesCleanupFunctionReleaseUsage(helperFunction, usage, context)) matchingReleaseAnchors.push(handleGuard ?? child);
9352
- });
9353
- return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
9354
- };
9355
- const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
9356
- const componentFunction = findEnclosingFunction(callback);
9357
- if (!componentFunction || !isNodeOfType(componentFunction, "ArrowFunctionExpression") && !isNodeOfType(componentFunction, "FunctionExpression") && !isNodeOfType(componentFunction, "FunctionDeclaration")) return false;
9358
- let didFindUnmountCleanup = false;
9359
- walkAst(componentFunction.body, (child) => {
9360
- if (didFindUnmountCleanup) return false;
9361
- if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction(child) !== componentFunction) return;
9362
- if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
9363
- const dependencyList = child.arguments?.[1];
9364
- if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
9365
- const cleanupCallback = getEffectCallback(child);
9366
- if (cleanupCallback && cleanupCallback !== callback && callbackReturnsCleanupForUsage(cleanupCallback, usage, context)) {
9367
- didFindUnmountCleanup = true;
9368
- return false;
9369
- }
9370
- });
9371
- return didFindUnmountCleanup;
9372
- };
9373
- const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
9374
9020
  const effectHasCleanupForUsage = (callback, usage, context) => {
9375
9021
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
9376
9022
  if (usage.kind === "subscribe" && findEnclosingFunction(usage.node) === callback && doesResourceResultEscape(usage.node, true) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
@@ -9383,10 +9029,6 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
9383
9029
  if (returnStart !== null && usageStart !== null && returnStart < usageStart) return;
9384
9030
  const returnedValue = child.argument ? stripParenExpression(child.argument) : null;
9385
9031
  if (!returnedValue) return;
9386
- if (doesBoundCleanupReleaseUsage(returnedValue, usage, context)) {
9387
- matchingCleanupReturns.push(child);
9388
- return;
9389
- }
9390
9032
  if (usage.kind === "subscribe" && (returnedValue === usage.node || getRangeStart(returnedValue) !== null && getRangeStart(returnedValue) === getRangeStart(usage.node)) && isCleanupReturningSubscribeLikeCallExpression(returnedValue)) {
9391
9033
  matchingCleanupReturns.push(child);
9392
9034
  return;
@@ -9402,17 +9044,13 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
9402
9044
  if (!context.scopes.symbolFor(returnedValue)?.initializer) return;
9403
9045
  }
9404
9046
  const cleanupFunction = resolveStableValue(returnedValue, context);
9405
- if (cleanupFunction && doesBoundCleanupReleaseUsage(cleanupFunction, usage, context)) {
9406
- matchingCleanupReturns.push(child);
9407
- return;
9408
- }
9409
9047
  if (!cleanupFunction || !isFunctionLike$1(cleanupFunction)) return;
9410
9048
  if (doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context)) matchingCleanupReturns.push(child);
9411
9049
  });
9412
9050
  return doMatchingNodesCoverEveryPathAfterUsage(resolveCleanupPathAnchor(usage.node, callback, context), matchingCleanupReturns, context);
9413
9051
  };
9414
9052
  const findFirstUsageWithoutCleanup = (callback, usages, context) => {
9415
- for (const usage of usages) if (!effectHasCleanupForUsage(callback, usage, context) && !hasSplitLifecycleCleanup(callback, usage, context)) return usage;
9053
+ for (const usage of usages) if (!effectHasCleanupForUsage(callback, usage, context)) return usage;
9416
9054
  return null;
9417
9055
  };
9418
9056
  const isLocalAbortControllerExpression = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
@@ -10008,11 +9646,6 @@ const walkParameterReferences = (pattern, state) => {
10008
9646
  const walk = (node, state) => {
10009
9647
  if (isFunctionLike$1(node)) {
10010
9648
  if (isNodeOfType(node, "FunctionDeclaration") && node.id) handleFunctionDeclaration(node, state);
10011
- const functionParams = node.params ?? [];
10012
- for (const param of functionParams) {
10013
- if (!("decorators" in param) || !Array.isArray(param.decorators)) continue;
10014
- for (const decorator of param.decorators) if (isAstNode(decorator)) walk(decorator, state);
10015
- }
10016
9649
  setNodeScope(node, state);
10017
9650
  const fnScope = pushScope(node.type === "ArrowFunctionExpression" ? "arrow-function" : "function", node, state);
10018
9651
  if ((isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && node.id) {
@@ -10025,6 +9658,7 @@ const walk = (node, state) => {
10025
9658
  });
10026
9659
  tagAsBinding(state, node.id);
10027
9660
  }
9661
+ const functionParams = node.params ?? [];
10028
9662
  handleFunctionParameters(functionParams, fnScope, state);
10029
9663
  for (const param of functionParams) walkParameterReferences(param, state);
10030
9664
  const body = node.body;
@@ -10034,9 +9668,6 @@ const walk = (node, state) => {
10034
9668
  }
10035
9669
  if (isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) {
10036
9670
  if (isNodeOfType(node, "ClassDeclaration") && node.id) handleClassDeclaration(node, state);
10037
- if (Array.isArray(node.decorators)) {
10038
- for (const decorator of node.decorators) if (isAstNode(decorator)) walk(decorator, state);
10039
- }
10040
9671
  const classScope = pushScope("class", node, state);
10041
9672
  setNodeScope(node, state);
10042
9673
  if (isNodeOfType(node, "ClassExpression") && node.id) {
@@ -10239,7 +9870,7 @@ const computeClosureCaptures = (functionNode, scopes) => {
10239
9870
  const functionScope = scopes.ownScopeFor(functionNode) ?? scopes.scopeFor(functionNode);
10240
9871
  const out = [];
10241
9872
  const seen = /* @__PURE__ */ new Set();
10242
- walkAst(functionNode, (node) => {
9873
+ const visit = (node) => {
10243
9874
  if (node !== functionNode && isFunctionLike$1(node)) {
10244
9875
  const innerCaptures = closureCaptures(node, scopes);
10245
9876
  for (const reference of innerCaptures) if (reference.resolvedSymbol && !isDescendantScope(reference.resolvedSymbol.scope, functionScope)) {
@@ -10248,7 +9879,7 @@ const computeClosureCaptures = (functionNode, scopes) => {
10248
9879
  seen.add(reference.id);
10249
9880
  }
10250
9881
  }
10251
- return false;
9882
+ return;
10252
9883
  }
10253
9884
  const reference = scopes.referenceFor(node);
10254
9885
  if (reference && reference.resolvedSymbol) {
@@ -10259,7 +9890,17 @@ const computeClosureCaptures = (functionNode, scopes) => {
10259
9890
  }
10260
9891
  }
10261
9892
  }
10262
- });
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);
10263
9904
  return out;
10264
9905
  };
10265
9906
  const capturesByAnalysis = /* @__PURE__ */ new WeakMap();
@@ -11285,11 +10926,6 @@ If the missing value is recreated every render, move it inside the hook or stabi
11285
10926
  return { CallExpression(node) {
11286
10927
  const hookName = getHookName(node.callee, context.scopes);
11287
10928
  if (!hookName || !isHookOfInterest(hookName, node.callee)) return;
11288
- if (HOOKS_REQUIRING_DEPS_MATCH.has(hookName) && !isReactApiCall(node, HOOKS_REQUIRING_DEPS_MATCH, context.scopes, {
11289
- allowGlobalReactNamespace: true,
11290
- allowUnboundBareCalls: true,
11291
- resolveNamedAliases: true
11292
- })) return;
11293
10929
  const callbackArgumentIndex = getCallbackArgumentIndex(hookName);
11294
10930
  const depsArgumentIndex = getDepsArgumentIndex(hookName);
11295
10931
  const callbackArgument = node.arguments[callbackArgumentIndex];
@@ -14584,11 +14220,7 @@ const jsIndexMaps = defineRule({
14584
14220
  //#region src/plugin/utils/are-expressions-structurally-equal.ts
14585
14221
  const areExpressionsStructurallyEqual = (a, b) => {
14586
14222
  if (!a || !b) return a === b;
14587
- const unwrappedA = stripParenExpression(a);
14588
- const unwrappedB = stripParenExpression(b);
14589
- if (unwrappedA !== a || unwrappedB !== b) return areExpressionsStructurallyEqual(unwrappedA, unwrappedB);
14590
14223
  if (a.type !== b.type) return false;
14591
- if (isNodeOfType(a, "ThisExpression")) return true;
14592
14224
  if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
14593
14225
  if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
14594
14226
  if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
@@ -19292,7 +18924,6 @@ const jwtInsecureVerification = defineRule({
19292
18924
  });
19293
18925
  //#endregion
19294
18926
  //#region src/plugin/rules/security-scan/key-lifecycle-risk.ts
19295
- const KEY_MATERIAL_MARKER_PATTERN = /PRIVATE KEY|SSH_PRIVATE_KEY|GPG_PRIVATE_KEY|DEPLOY_KEY|SIGNING_KEY/i;
19296
18927
  const keyLifecycleRisk = defineRule({
19297
18928
  id: "key-lifecycle-risk",
19298
18929
  title: "Long-lived key material in repository",
@@ -19300,7 +18931,7 @@ const keyLifecycleRisk = defineRule({
19300
18931
  committedFilesOnly: true,
19301
18932
  recommendation: "Remove private keys from source, rotate exposed credentials, prefer short-lived deploy credentials, and document revocation/expiry for release keys.",
19302
18933
  scan: scanByPattern({
19303
- 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),
19304
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,
19305
18936
  message: "Private or long-lived release key material appears in the repository."
19306
18937
  })
@@ -20006,21 +19637,15 @@ const localRpcNativeBridgeRisk = defineRule({
20006
19637
  message: "Code appears to bridge browser code to localhost/native capabilities with weak origin or update/install checks."
20007
19638
  })
20008
19639
  });
20009
- //#endregion
20010
- //#region src/plugin/rules/security-scan/mcp-tool-capability-risk.ts
20011
- const MCP_IMPORT_PATTERN = /\bfrom\s+["']@modelcontextprotocol\/sdk[^"']*["']|\bMcpServer\b|\bMcpAgent\b/;
20012
- const MCP_IMPORT_PREFILTER_PATTERN = /@modelcontextprotocol\/sdk|\bMcpServer\b|\bMcpAgent\b/;
20013
- const MCP_TOOL_SURFACE_PATTERN = /\bserver\.\s*tool\s*\(|\bregisterTool\s*\(|\bsetRequestHandler\s*\(\s*CallToolRequestSchema/;
20014
- const MCP_TOOL_SURFACE_PREFILTER_PATTERN = /\b(?:tool|registerTool|setRequestHandler)\b/;
20015
19640
  const mcpToolCapabilityRisk = defineRule({
20016
19641
  id: "mcp-tool-capability-risk",
20017
19642
  title: "MCP tool exposes dangerous capability",
20018
19643
  severity: "warn",
20019
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.",
20020
19645
  scan: scanByPattern({
20021
- 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),
20022
- pattern: MCP_TOOL_SURFACE_PATTERN,
20023
- 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],
20024
19649
  ignoreStringLiterals: true,
20025
19650
  message: "An MCP tool/resource/prompt handler appears to expose file, shell, network, or code-execution capability."
20026
19651
  })
@@ -21131,8 +20756,8 @@ const catchClauseRethrowsCaught = (handler) => {
21131
20756
  return didRethrow;
21132
20757
  };
21133
20758
  //#endregion
21134
- //#region src/plugin/utils/is-immediately-invoked-function.ts
21135
- const isImmediatelyInvokedFunction = (functionNode) => {
20759
+ //#region src/plugin/utils/find-guarding-try-statement.ts
20760
+ const isImmediatelyInvokedFunctionCallee = (functionNode) => {
21136
20761
  let wrappedCallee = functionNode;
21137
20762
  let enclosing = functionNode.parent;
21138
20763
  while (enclosing && stripParenExpression(enclosing) === functionNode) {
@@ -21141,13 +20766,11 @@ const isImmediatelyInvokedFunction = (functionNode) => {
21141
20766
  }
21142
20767
  return Boolean(enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee);
21143
20768
  };
21144
- //#endregion
21145
- //#region src/plugin/utils/find-guarding-try-statement.ts
21146
20769
  const findGuardingTryStatement = (node) => {
21147
20770
  let child = node;
21148
20771
  let ancestor = node.parent;
21149
20772
  while (ancestor) {
21150
- if (isFunctionLike$1(ancestor) && !isImmediatelyInvokedFunction(ancestor)) return null;
20773
+ if (isFunctionLike$1(ancestor) && !isImmediatelyInvokedFunctionCallee(ancestor)) return null;
21151
20774
  if (isNodeOfType(ancestor, "TryStatement") && ancestor.block === child && ancestor.handler && !catchClauseRethrowsCaught(ancestor.handler)) return ancestor;
21152
20775
  child = ancestor;
21153
20776
  ancestor = ancestor.parent ?? null;
@@ -21699,7 +21322,7 @@ const FILENAME_TO_LANG = {
21699
21322
  const resolveLang = (filename) => {
21700
21323
  return FILENAME_TO_LANG[path.extname(filename).toLowerCase()] ?? "tsx";
21701
21324
  };
21702
- const parseSourceText = ({ filename, sourceText, shouldAttachParentReferences = true }) => {
21325
+ const parseSourceText = (filename, sourceText) => {
21703
21326
  try {
21704
21327
  const result = parseSync(filename, sourceText, {
21705
21328
  astType: "ts",
@@ -21707,7 +21330,7 @@ const parseSourceText = ({ filename, sourceText, shouldAttachParentReferences =
21707
21330
  });
21708
21331
  if (result.errors.some((parseError) => parseError.severity === "Error")) return null;
21709
21332
  const parsedProgram = result.program;
21710
- if (shouldAttachParentReferences) attachParentReferences(parsedProgram);
21333
+ attachParentReferences(parsedProgram);
21711
21334
  return parsedProgram;
21712
21335
  } catch {
21713
21336
  return null;
@@ -21745,10 +21368,7 @@ const parseSourceFile = (absoluteFilePath) => {
21745
21368
  });
21746
21369
  return null;
21747
21370
  }
21748
- const parsedProgram = parseSourceText({
21749
- filename: absoluteFilePath,
21750
- sourceText
21751
- });
21371
+ const parsedProgram = parseSourceText(absoluteFilePath, sourceText);
21752
21372
  parseCache.set(absoluteFilePath, {
21753
21373
  mtimeMs: fileStat.mtimeMs,
21754
21374
  size: fileStat.size,
@@ -22280,8 +21900,6 @@ const DOM_QUERY_MEMBER_NAMES = new Set([
22280
21900
  ]);
22281
21901
  const LAYOUT_MEASUREMENT_MEMBER_NAMES = new Set([
22282
21902
  "current",
22283
- "textContent",
22284
- "innerText",
22285
21903
  "scrollWidth",
22286
21904
  "clientWidth",
22287
21905
  "offsetWidth",
@@ -22303,7 +21921,7 @@ const POST_MOUNT_GLOBAL_NAMES = new Set([
22303
21921
  "navigator"
22304
21922
  ]);
22305
21923
  const REF_FACTORY_CALLEE_NAMES = new Set(["useRef", "createRef"]);
22306
- 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");
22307
21925
  const isRefFactoryInitializer = (init) => {
22308
21926
  if (!init || !isNodeOfType(init, "CallExpression")) return false;
22309
21927
  const callee = init.callee;
@@ -22386,37 +22004,88 @@ const readsPostMountValue = (root) => {
22386
22004
  return found;
22387
22005
  };
22388
22006
  //#endregion
22389
- //#region src/plugin/rules/state-and-effects/utils/effect/get-ast-child-keys.ts
22390
- 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
+ };
22391
22019
  //#endregion
22392
22020
  //#region src/plugin/rules/state-and-effects/utils/effect/get-program-analysis.ts
22393
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
+ };
22394
22053
  const getProgramAnalysis = (anyNode) => {
22395
22054
  const programNode = findProgramRoot(anyNode);
22396
22055
  if (!programNode) return null;
22397
22056
  const cached = programToAnalysis.get(programNode);
22398
22057
  if (cached) return cached;
22399
- const analysis = {
22400
- programNode,
22401
- scopeManager: analyze(programNode, {
22058
+ const restorations = stripAndRecordParents(programNode);
22059
+ let scopeManager;
22060
+ try {
22061
+ scopeManager = analyze(programNode, {
22402
22062
  ecmaVersion: 2024,
22403
22063
  sourceType: "module",
22404
- childVisitorKeys: RUNTIME_VISITOR_KEYS,
22405
- fallback: getAstChildKeys
22406
- }),
22407
- scopeByNode: /* @__PURE__ */ new WeakMap(),
22408
- 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
22409
22073
  };
22410
22074
  programToAnalysis.set(programNode, analysis);
22411
22075
  return analysis;
22412
22076
  };
22413
- const getScopeForNode = (node, analysis) => {
22077
+ const scopeByNodeCache = /* @__PURE__ */ new WeakMap();
22078
+ const getScopeForNode = (node, manager) => {
22414
22079
  if (!node.range) return null;
22415
- const scopeByNode = analysis.scopeByNode;
22080
+ let scopeByNode = scopeByNodeCache.get(manager);
22081
+ if (!scopeByNode) {
22082
+ scopeByNode = /* @__PURE__ */ new WeakMap();
22083
+ scopeByNodeCache.set(manager, scopeByNode);
22084
+ }
22416
22085
  if (scopeByNode.has(node)) return scopeByNode.get(node) ?? null;
22417
22086
  let bestScope = null;
22418
22087
  let bestSize = Infinity;
22419
- for (const scope of analysis.scopeManager.scopes) {
22088
+ for (const scope of manager.scopes) {
22420
22089
  const block = scope.block;
22421
22090
  if (!block?.range) continue;
22422
22091
  if (node.range[0] < block.range[0] || node.range[1] > block.range[1]) continue;
@@ -22431,6 +22100,7 @@ const getScopeForNode = (node, analysis) => {
22431
22100
  };
22432
22101
  //#endregion
22433
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");
22434
22104
  const HOOK_NAME_PATTERN$2 = /^use[A-Z0-9]/;
22435
22105
  const isInsideCallbackArgumentOf = (identifier, initializer) => {
22436
22106
  if (!isNodeOfType(initializer, "CallExpression") && !isNodeOfType(initializer, "NewExpression")) return false;
@@ -22466,7 +22136,7 @@ const descend = (node, visit, visited = /* @__PURE__ */ new Set()) => {
22466
22136
  if (visited.has(node)) return;
22467
22137
  visit(node);
22468
22138
  visited.add(node);
22469
- const keys = getAstChildKeys(node);
22139
+ const keys = getChildKeys(node);
22470
22140
  const record = node;
22471
22141
  for (const key of keys) {
22472
22142
  const child = record[key];
@@ -22499,11 +22169,16 @@ const findDownstreamNodes = (topNode, type) => {
22499
22169
  });
22500
22170
  return nodes;
22501
22171
  };
22172
+ const refByIdentifierCache = /* @__PURE__ */ new WeakMap();
22502
22173
  const getRef = (analysis, identifier) => {
22503
- const refByIdentifier = analysis.referenceByIdentifier;
22174
+ let refByIdentifier = refByIdentifierCache.get(analysis);
22175
+ if (!refByIdentifier) {
22176
+ refByIdentifier = /* @__PURE__ */ new WeakMap();
22177
+ refByIdentifierCache.set(analysis, refByIdentifier);
22178
+ }
22504
22179
  if (refByIdentifier.has(identifier)) return refByIdentifier.get(identifier) ?? null;
22505
22180
  let resolvedReference = null;
22506
- const scope = getScopeForNode(identifier, analysis);
22181
+ const scope = getScopeForNode(identifier, analysis.scopeManager);
22507
22182
  if (scope) {
22508
22183
  for (const reference of scope.references) if (reference.identifier === identifier) {
22509
22184
  resolvedReference = reference;
@@ -22751,7 +22426,7 @@ const isReactFunctionalHOC = (analysis, node) => {
22751
22426
  const isWrappedSeparately = () => {
22752
22427
  if (!isNodeOfType(node.id, "Identifier")) return false;
22753
22428
  const bindingName = node.id.name;
22754
- const containingScope = getScopeForNode(node, analysis);
22429
+ const containingScope = getScopeForNode(node, analysis.scopeManager);
22755
22430
  if (!containingScope) return false;
22756
22431
  const variable = containingScope.variables.find((v) => v.name === bindingName);
22757
22432
  if (!variable) return false;
@@ -22982,14 +22657,11 @@ const hasCleanupReturn = (analysis, node, visited = /* @__PURE__ */ new WeakSet(
22982
22657
  if (isNodeOfType(node, "ReturnStatement") && node.argument != null) return isCleanupReturnArgument(analysis, node.argument);
22983
22658
  if (!isNodeOfType(node, "BlockStatement") && isFunctionLike$1(node)) return false;
22984
22659
  const record = node;
22985
- const childKeys = getAstChildKeys(node);
22986
- for (let keyIndex = 0; keyIndex < childKeys.length; keyIndex += 1) {
22987
- const value = record[childKeys[keyIndex]];
22988
- if (Array.isArray(value)) for (let itemIndex = 0; itemIndex < value.length; itemIndex += 1) {
22989
- const item = value[itemIndex];
22990
- if (isAstNode(item) && hasCleanupReturn(analysis, item, visited)) return true;
22991
- }
22992
- 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;
22993
22665
  }
22994
22666
  return false;
22995
22667
  };
@@ -23217,73 +22889,61 @@ const PURE_GLOBAL_CALLEE_NAMES = new Set([
23217
22889
  "Array",
23218
22890
  "BigInt",
23219
22891
  "Boolean",
23220
- "encodeURIComponent",
23221
22892
  "Number",
23222
22893
  "Object",
23223
22894
  "String",
23224
22895
  "parseFloat",
23225
- "parseInt",
23226
- "structuredClone"
23227
- ]);
23228
- const PURE_GLOBAL_CONSTRUCTOR_NAMES = new Set(["Date", "Set"]);
23229
- const PURE_HELPER_NAMESPACE_MEMBER_NAMES = new Map([
23230
- ["Array", new Set(["from"])],
23231
- ["JSON", new Set([
23232
- "isRawJSON",
23233
- "parse",
23234
- "rawJSON",
23235
- "stringify"
23236
- ])],
23237
- ["Math", new Set([
23238
- "abs",
23239
- "acos",
23240
- "acosh",
23241
- "asin",
23242
- "asinh",
23243
- "atan",
23244
- "atan2",
23245
- "atanh",
23246
- "cbrt",
23247
- "ceil",
23248
- "clz32",
23249
- "cos",
23250
- "cosh",
23251
- "exp",
23252
- "floor",
23253
- "fround",
23254
- "hypot",
23255
- "imul",
23256
- "log",
23257
- "log10",
23258
- "log1p",
23259
- "log2",
23260
- "max",
23261
- "min",
23262
- "pow",
23263
- "round",
23264
- "sign",
23265
- "sin",
23266
- "sinh",
23267
- "sqrt",
23268
- "tan",
23269
- "tanh",
23270
- "trunc"
23271
- ])],
23272
- ["Object", new Set(["assign"])]
22896
+ "parseInt"
23273
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
+ ])]]);
23274
22939
  const PURE_MEMBER_TRANSFORM_NAMES = new Set([
23275
22940
  "concat",
23276
22941
  "filter",
23277
- "flatMap",
23278
22942
  "join",
23279
22943
  "map",
23280
- "reduce",
23281
- "replace",
23282
- "slice",
23283
22944
  "split",
23284
22945
  "toLowerCase",
23285
22946
  "toString",
23286
- "toSorted",
23287
22947
  "toUpperCase",
23288
22948
  "trim"
23289
22949
  ]);
@@ -23292,7 +22952,7 @@ const STORAGE_GLOBAL_NAMES$1 = new Set([
23292
22952
  "localStorage",
23293
22953
  "sessionStorage"
23294
22954
  ]);
23295
- const getStaticMemberName$1 = (node) => {
22955
+ const getStaticMemberName = (node) => {
23296
22956
  if (!isNodeOfType(node, "MemberExpression")) return null;
23297
22957
  if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
23298
22958
  if (node.computed && isNodeOfType(node.property, "Literal")) return typeof node.property.value === "string" ? node.property.value : null;
@@ -23307,7 +22967,7 @@ const getCallCalleeName$1 = (callExpression) => {
23307
22967
  if (!isNodeOfType(callExpression, "CallExpression")) return null;
23308
22968
  const callee = stripParenExpression(callExpression.callee);
23309
22969
  if (isNodeOfType(callee, "Identifier")) return callee.name;
23310
- return getStaticMemberName$1(callee);
22970
+ return getStaticMemberName(callee);
23311
22971
  };
23312
22972
  const getFunctionParameters = (functionNode) => isFunctionLike$1(functionNode) ? functionNode.params ?? [] : [];
23313
22973
  const getIdentifierBindingIdentity = (analysis, identifier) => {
@@ -23423,14 +23083,14 @@ const localUseEventPreservesCallback = (analysis, callee) => {
23423
23083
  if (!isNodeOfType(child, "CallExpression")) return;
23424
23084
  const forwardedCallee = stripParenExpression(child.callee);
23425
23085
  if (!isNodeOfType(forwardedCallee, "MemberExpression")) return;
23426
- if (getStaticMemberName$1(forwardedCallee) !== "current") return;
23086
+ if (getStaticMemberName(forwardedCallee) !== "current") return;
23427
23087
  if (!isNodeOfType(forwardedCallee.object, "Identifier")) return;
23428
23088
  const refReference = getRef(analysis, forwardedCallee.object);
23429
23089
  if (!callbackRefDeclarators.find((declarator) => refReference?.resolved?.defs.some((definition) => definition.node === declarator)) || !refReference?.resolved) return;
23430
23090
  if (!refReference.resolved.references.some((candidateReference) => {
23431
23091
  const memberExpression = candidateReference.identifier.parent;
23432
23092
  const assignmentExpression = memberExpression?.parent;
23433
- 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;
23434
23094
  return getIdentifierBindingIdentity(analysis, assignmentExpression.right) !== callbackBinding;
23435
23095
  })) forwardsCallback = true;
23436
23096
  });
@@ -23455,7 +23115,7 @@ const resolveWrappedCallable = (analysis, node) => {
23455
23115
  return callback && isFunctionLike$1(callback) ? callback : null;
23456
23116
  }
23457
23117
  if (!isNodeOfType(candidate, "MemberExpression")) return null;
23458
- if (getStaticMemberName$1(candidate) !== "current") return null;
23118
+ if (getStaticMemberName(candidate) !== "current") return null;
23459
23119
  if (!isNodeOfType(candidate.object, "Identifier")) return null;
23460
23120
  const reference = getRef(analysis, candidate.object);
23461
23121
  const declarator = reference?.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
@@ -23467,7 +23127,7 @@ const resolveWrappedCallable = (analysis, node) => {
23467
23127
  return Boolean(reference?.resolved?.references.some((candidateReference) => {
23468
23128
  const member = candidateReference.identifier.parent;
23469
23129
  const assignment = member?.parent;
23470
- 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);
23471
23131
  })) ? null : initializer;
23472
23132
  };
23473
23133
  const functionInvokesItself = (analysis, functionNode) => {
@@ -23506,7 +23166,7 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilena
23506
23166
  if (!isNodeOfType(child, "CallExpression")) return;
23507
23167
  const callee = stripParenExpression(child.callee);
23508
23168
  const calleeName = getCallCalleeName$1(child);
23509
- const memberName = getStaticMemberName$1(callee);
23169
+ const memberName = getStaticMemberName(callee);
23510
23170
  const isDeferringCall = calleeName !== null && DEFERRING_CALLEE_NAMES.has(calleeName) || memberName !== null && DEFERRING_MEMBER_NAMES.has(memberName);
23511
23171
  const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES$1.has(memberName) && isNodeOfType(callee, "MemberExpression");
23512
23172
  const enqueueFrame = (callableNode, argumentsForCallable, isDeferred, introducedBindings, allowWithoutInvocationEvidence = false) => {
@@ -23665,19 +23325,14 @@ const analyzeHelperExpression = (expression, environment, usedParameterIndices)
23665
23325
  const callbackSummary = analyzeHelperStatements(node.body.body ?? [], callbackEnvironment, usedParameterIndices, analyzeHelperExpression);
23666
23326
  return callbackSummary.isValid && !callbackSummary.canContinue;
23667
23327
  }
23668
- if (isNodeOfType(node, "NewExpression")) {
23669
- const callee = stripParenExpression(node.callee);
23670
- 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;
23671
- return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
23672
- }
23673
23328
  if (isNodeOfType(node, "CallExpression")) {
23674
23329
  const callee = stripParenExpression(node.callee);
23675
23330
  const calleeRoot = getMemberRoot(callee);
23676
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);
23677
23332
  const namespaceName = isNodeOfType(calleeRoot, "Identifier") && !environment.parameterIndices.has(calleeRoot.name) && !environment.recursiveNames.has(calleeRoot.name) && !environment.shadowedGlobalNames.has(calleeRoot.name) ? calleeRoot.name : null;
23678
- const namespaceMemberName = getStaticMemberName$1(callee);
23333
+ const namespaceMemberName = getStaticMemberName(callee);
23679
23334
  const isPureNamespaceCall = isNodeOfType(callee, "MemberExpression") && namespaceName !== null && namespaceMemberName !== null && PURE_HELPER_NAMESPACE_MEMBER_NAMES.get(namespaceName)?.has(namespaceMemberName) === true;
23680
- 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) ?? "");
23681
23336
  if (!isPureGlobalCall && !isPureNamespaceCall && !isPureMemberTransform) return false;
23682
23337
  if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression") && !analyzeHelperExpression(callee.object, environment, usedParameterIndices)) return false;
23683
23338
  return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
@@ -23751,7 +23406,7 @@ const isLocallyConstructedObjectMember = (reference, memberExpression) => isNode
23751
23406
  return isNodeOfType(definitionNode, "VariableDeclarator") && Boolean(definitionNode.init) && (isNodeOfType(definitionNode.init, "ObjectExpression") || isNodeOfType(definitionNode.init, "ArrayExpression"));
23752
23407
  }) === true;
23753
23408
  const getUseRefDeclarator = (analysis, memberExpression) => {
23754
- 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;
23755
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;
23756
23411
  };
23757
23412
  const collectValueEvidence = (analysis, expression, frame, remainingCallFrames, visitedBindings = /* @__PURE__ */ new Set()) => {
@@ -23820,7 +23475,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23820
23475
  return collectValueEvidence(analysis, initializer.init, frame, remainingCallFrames, visitedBindings);
23821
23476
  }
23822
23477
  if (isNodeOfType(node, "MemberExpression")) {
23823
- if (getStaticMemberName$1(node) === "current" && isNodeOfType(node.object, "Identifier")) {
23478
+ if (getStaticMemberName(node) === "current" && isNodeOfType(node.object, "Identifier")) {
23824
23479
  const refBinding = getRef(analysis, node.object)?.resolved;
23825
23480
  const refDeclarator = useRefDeclarator;
23826
23481
  if (refBinding && refDeclarator && isNodeOfType(refDeclarator, "VariableDeclarator") && isNodeOfType(refDeclarator.init, "CallExpression")) {
@@ -23836,7 +23491,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23836
23491
  if (candidateReference.init) continue;
23837
23492
  const identifier = candidateReference.identifier;
23838
23493
  const member = identifier.parent;
23839
- if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName$1(member) !== "current") {
23494
+ if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName(member) !== "current") {
23840
23495
  evidence.hasUnknownSource = true;
23841
23496
  continue;
23842
23497
  }
@@ -23872,8 +23527,8 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23872
23527
  }
23873
23528
  const callee = stripParenExpression(node.callee);
23874
23529
  const calleeRoot = getMemberRoot(callee);
23875
- 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;
23876
- 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) ?? "");
23877
23532
  if (isPureGlobalCall || isPureMemberTransform) {
23878
23533
  if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression")) mergeEvidence(evidence, collectValueEvidence(analysis, callee.object, frame, remainingCallFrames, new Set(visitedBindings)));
23879
23534
  for (const argument of node.arguments ?? []) {
@@ -23925,15 +23580,6 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23925
23580
  }
23926
23581
  return evidence;
23927
23582
  }
23928
- if (isNodeOfType(node, "NewExpression")) {
23929
- const callee = stripParenExpression(node.callee);
23930
- if (!isNodeOfType(callee, "Identifier") || !PURE_GLOBAL_CONSTRUCTOR_NAMES.has(callee.name) || getIdentifierBindingIdentity(analysis, callee) !== null) {
23931
- evidence.hasUnknownSource = true;
23932
- return evidence;
23933
- }
23934
- for (const argument of node.arguments ?? []) mergeEvidence(evidence, collectValueEvidence(analysis, argument, frame, remainingCallFrames, new Set(visitedBindings)));
23935
- return evidence;
23936
- }
23937
23583
  if (isFunctionLike$1(node) || isNodeOfType(node, "AwaitExpression")) {
23938
23584
  evidence.hasUnknownSource = true;
23939
23585
  return evidence;
@@ -24352,26 +23998,6 @@ const STATEFUL_HTML_DESCENDANT_TAGS = new Set([
24352
23998
  "dialog",
24353
23999
  "canvas"
24354
24000
  ]);
24355
- const STATEFUL_HTML_ATTRIBUTE_NAMES = new Set([
24356
- "autofocus",
24357
- "contenteditable",
24358
- "draggable",
24359
- "tabindex"
24360
- ]);
24361
- const isStaticallyFalseAttributeValue = (attribute) => {
24362
- if (!isNodeOfType(attribute, "JSXAttribute") || !attribute.value) return false;
24363
- const value = isNodeOfType(attribute.value, "JSXExpressionContainer") ? attribute.value.expression : attribute.value;
24364
- return isNodeOfType(value, "Literal") && (value.value === false || value.value === "false");
24365
- };
24366
- const hasStatefulHtmlAttribute = (openingElement) => {
24367
- if (!isNodeOfType(openingElement, "JSXOpeningElement")) return false;
24368
- return openingElement.attributes.some((attribute) => {
24369
- if (!isNodeOfType(attribute, "JSXAttribute") || !isNodeOfType(attribute.name, "JSXIdentifier")) return false;
24370
- const attributeName = attribute.name.name.toLowerCase();
24371
- if (!STATEFUL_HTML_ATTRIBUTE_NAMES.has(attributeName)) return false;
24372
- return attributeName === "tabindex" || !isStaticallyFalseAttributeValue(attribute);
24373
- });
24374
- };
24375
24001
  const STATEFUL_DESCENDANT_SCAN_BUDGET = 200;
24376
24002
  const rootIdentifierNameOf = (expression) => {
24377
24003
  let object = expression;
@@ -24389,8 +24015,7 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
24389
24015
  budget -= 1;
24390
24016
  const node = stack.pop();
24391
24017
  if (isNodeOfType(node, "JSXElement")) {
24392
- const opening = node.openingElement;
24393
- const name = opening.name;
24018
+ const name = node.openingElement.name;
24394
24019
  if (name && isNodeOfType(name, "JSXIdentifier")) {
24395
24020
  const tagName = name.name;
24396
24021
  const firstChar = tagName.charCodeAt(0);
@@ -24398,7 +24023,6 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
24398
24023
  if (STATEFUL_HTML_DESCENDANT_TAGS.has(tagName)) return true;
24399
24024
  }
24400
24025
  if (name && isNodeOfType(name, "JSXMemberExpression")) return true;
24401
- if (hasStatefulHtmlAttribute(opening)) return true;
24402
24026
  const children = node.children ?? [];
24403
24027
  for (const child of children) stack.push(child);
24404
24028
  continue;
@@ -24492,7 +24116,6 @@ const ARITHMETIC_KEY_OPERATORS = new Set([
24492
24116
  "/",
24493
24117
  "%"
24494
24118
  ]);
24495
- const bindingMutationResultsByProgram = /* @__PURE__ */ new WeakMap();
24496
24119
  const extractCandidateIdentifiers = (expression) => {
24497
24120
  const node = stripParenExpression(expression);
24498
24121
  if (isNodeOfType(node, "Identifier")) return [node];
@@ -24533,13 +24156,6 @@ const isArrayFromCall = (node) => {
24533
24156
  const isBindingReassignedOrMutated = (referenceNode, bindingName) => {
24534
24157
  const programRoot = findProgramRoot(referenceNode);
24535
24158
  if (!programRoot) return false;
24536
- let mutationResultsByBindingName = bindingMutationResultsByProgram.get(programRoot);
24537
- if (!mutationResultsByBindingName) {
24538
- mutationResultsByBindingName = /* @__PURE__ */ new Map();
24539
- bindingMutationResultsByProgram.set(programRoot, mutationResultsByBindingName);
24540
- }
24541
- const cachedResult = mutationResultsByBindingName.get(bindingName);
24542
- if (cachedResult !== void 0) return cachedResult;
24543
24159
  let didFindWrite = false;
24544
24160
  walkAst(programRoot, (child) => {
24545
24161
  if (didFindWrite) return false;
@@ -24556,7 +24172,6 @@ const isBindingReassignedOrMutated = (referenceNode, bindingName) => {
24556
24172
  return false;
24557
24173
  }
24558
24174
  });
24559
- mutationResultsByBindingName.set(bindingName, didFindWrite);
24560
24175
  return didFindWrite;
24561
24176
  };
24562
24177
  /**
@@ -24958,14 +24573,6 @@ const templateHasOuterMemberIdentity = (template, bindingFunction) => {
24958
24573
  }
24959
24574
  return false;
24960
24575
  };
24961
- const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
24962
- const referencedItemNames = /* @__PURE__ */ new Set();
24963
- for (const expression of template.expressions ?? []) {
24964
- const unwrappedExpression = stripParenExpression(expression);
24965
- if (isNodeOfType(unwrappedExpression, "Identifier") && itemNames.has(unwrappedExpression.name)) referencedItemNames.add(unwrappedExpression.name);
24966
- }
24967
- return referencedItemNames;
24968
- };
24969
24576
  const forLoopTestReadsDataLength = (test) => {
24970
24577
  let didFindLengthRead = false;
24971
24578
  walkAst(test, (child) => {
@@ -25102,11 +24709,9 @@ const noArrayIndexAsKey = defineRule({
25102
24709
  } else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
25103
24710
  const jsxElement = openingElement.parent;
25104
24711
  if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
25105
- const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
25106
- const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET;
25107
24712
  if (!containsStatefulDescendant(jsxElement, {
25108
- memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET,
25109
- 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
25110
24715
  })) return;
25111
24716
  }
25112
24717
  }
@@ -25274,11 +24879,7 @@ const noAsyncEffectCallback = defineRule({
25274
24879
  severity: "warn",
25275
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.",
25276
24881
  create: (context) => ({ CallExpression(node) {
25277
- if (!isReactApiCall(node, EFFECT_HOOK_NAMES$1, context.scopes, {
25278
- allowGlobalReactNamespace: true,
25279
- allowUnboundBareCalls: true,
25280
- resolveNamedAliases: true
25281
- })) return;
24882
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
25282
24883
  const callback = getEffectCallback(node);
25283
24884
  if (!callback) return;
25284
24885
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
@@ -25671,10 +25272,10 @@ const declaresAnyDependency = (manifest) => DEPENDENCY_SECTION_NAMES.some((secti
25671
25272
  const section = manifest[sectionName];
25672
25273
  return typeof section === "object" && section !== null && Object.keys(section).length > 0;
25673
25274
  });
25674
- const declaresDependency = (manifest, dependencyName) => DEPENDENCY_SECTION_NAMES.some((sectionName) => {
25675
- const section = manifest[sectionName];
25676
- return typeof section === "object" && section !== null && Object.prototype.propertyIsEnumerable.call(section, dependencyName);
25677
- });
25275
+ const declaresDependency = (manifest, dependencyName) => {
25276
+ for (const declaredName of iterateDependencyNames(manifest)) if (declaredName === dependencyName) return true;
25277
+ return false;
25278
+ };
25678
25279
  const cachedPlatformByManifest = /* @__PURE__ */ new WeakMap();
25679
25280
  const classifyPackagePlatform = (filename) => {
25680
25281
  const manifest = readNearestPackageManifest(filename);
@@ -25921,23 +25522,25 @@ const isCallArgumentFunctionExpression = (node) => {
25921
25522
  return parent.arguments.some((argumentNode) => argumentNode === node);
25922
25523
  };
25923
25524
  const isNestedRenderEvidenceBoundary = (node) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isCallArgumentFunctionExpression(node);
25924
- const containsRenderOutput$1 = (rootNode, scopes) => {
25925
- let hasRenderOutput = false;
25926
- walkAst(rootNode, (node) => {
25927
- if (hasRenderOutput) return false;
25928
- if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
25929
- if (node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS)) {
25930
- hasRenderOutput = true;
25931
- return false;
25932
- }
25933
- });
25934
- 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;
25935
25538
  };
25936
25539
  const renderOutputCache = /* @__PURE__ */ new WeakMap();
25937
25540
  const functionContainsReactRenderOutput = (functionNode, scopes) => {
25938
25541
  const cachedEntry = renderOutputCache.get(functionNode);
25939
25542
  if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
25940
- const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
25543
+ const hasRenderOutput = containsRenderOutput$1(functionNode, functionNode, scopes);
25941
25544
  renderOutputCache.set(functionNode, {
25942
25545
  scopes,
25943
25546
  hasRenderOutput
@@ -26476,19 +26079,6 @@ const noCloneElement = defineRule({
26476
26079
  } })
26477
26080
  });
26478
26081
  //#endregion
26479
- //#region src/plugin/utils/get-call-method-name.ts
26480
- /**
26481
- * Returns the static method name of a call's callee when it's a
26482
- * non-computed MemberExpression (`obj.method` → `"method"`), or
26483
- * `null` otherwise. Used by `no-pass-data-to-parent` and
26484
- * `no-pass-live-state-to-parent` to match the receiver-bound
26485
- * method-call shape against an allow/block list.
26486
- */
26487
- const getCallMethodName = (callee) => {
26488
- if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
26489
- return null;
26490
- };
26491
- //#endregion
26492
26082
  //#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
26493
26083
  const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
26494
26084
  const CONTEXT_MODULES = [
@@ -26496,35 +26086,23 @@ const CONTEXT_MODULES = [
26496
26086
  "use-context-selector",
26497
26087
  "react-tracked"
26498
26088
  ];
26499
- const getSupportedContextImportSource = (symbol) => {
26500
- if (symbol?.kind !== "import") return null;
26501
- const importDeclaration = symbol.declarationNode.parent;
26502
- if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration") || typeof importDeclaration.source.value !== "string" || !CONTEXT_MODULES.includes(importDeclaration.source.value)) return null;
26503
- return importDeclaration.source.value;
26504
- };
26505
- const isSupportedNamespaceSymbol = (symbol) => getSupportedContextImportSource(symbol) !== null && Boolean(symbol && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
26506
- const isDestructuredCreateContextBinding = (identifier, scopes) => {
26507
- if (!isNodeOfType(identifier, "Identifier")) return false;
26508
- const symbol = scopes.symbolFor(identifier);
26509
- if (symbol?.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "ObjectPattern")) return false;
26510
- const property = symbol.bindingIdentifier.parent;
26511
- if (!property || !isNodeOfType(property, "Property") || getStaticPropertyKeyName(property, { allowComputedString: true }) !== "createContext") return false;
26512
- const initializer = stripParenExpression(symbol.initializer);
26513
- return isNodeOfType(initializer, "Identifier") && isSupportedNamespaceSymbol(resolveConstIdentifierAlias(initializer, scopes));
26514
- };
26515
- const isCreateContextCall = (node, scopes) => {
26516
- const callee = stripParenExpression(node.callee);
26089
+ const isCreateContextCallee = (callee) => {
26517
26090
  if (isNodeOfType(callee, "Identifier")) {
26518
- if (isDestructuredCreateContextBinding(callee, scopes)) return true;
26519
- const symbol = resolveConstIdentifierAlias(callee, scopes);
26520
- 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);
26521
26093
  }
26522
- if (!isNodeOfType(callee, "MemberExpression")) return false;
26523
- if ((getCallMethodName(callee) ?? (callee.computed && isNodeOfType(callee.property, "Literal") && typeof callee.property.value === "string" ? callee.property.value : null)) !== "createContext") return false;
26524
- const receiver = stripParenExpression(callee.object);
26525
- if (!isNodeOfType(receiver, "Identifier")) return false;
26526
- if (receiver.name === "React" && scopes.isGlobalReference(receiver)) return true;
26527
- 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;
26528
26106
  };
26529
26107
  const noCreateContextInRender = defineRule({
26530
26108
  id: "no-create-context-in-render",
@@ -26533,7 +26111,7 @@ const noCreateContextInRender = defineRule({
26533
26111
  category: "Correctness",
26534
26112
  recommendation: "Move `createContext(...)` outside the component, to the top level of the file, so it stays the same on every render.",
26535
26113
  create: (context) => ({ CallExpression(node) {
26536
- if (!isCreateContextCall(node, context.scopes)) return;
26114
+ if (!isCreateContextCallee(node.callee)) return;
26537
26115
  const componentOrHookName = enclosingComponentOrHookName(node);
26538
26116
  if (!componentOrHookName) return;
26539
26117
  context.report({
@@ -27768,7 +27346,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
27768
27346
  let ancestor = setStateCall.parent;
27769
27347
  while (ancestor) {
27770
27348
  if (!lifecycleMember) {
27771
- 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;
27772
27350
  if (isLifecycleMember(ancestor, lifecycleNames)) lifecycleMember = ancestor;
27773
27351
  } else if (isEs5Component(ancestor) || isEs6Component(ancestor)) {
27774
27352
  if (nestedFunctionCount > 1 && !options.disallowInNestedFunctions) return false;
@@ -27970,7 +27548,7 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
27970
27548
  const body = lifecycleFunction.body;
27971
27549
  if (!body) return derivedNames;
27972
27550
  walkAst(body, (node) => {
27973
- if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
27551
+ if (FUNCTION_NODE_TYPES.has(node.type)) return false;
27974
27552
  if (!isNodeOfType(node, "VariableDeclarator")) return;
27975
27553
  const init = node.init;
27976
27554
  if (!init) return;
@@ -27980,67 +27558,6 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
27980
27558
  return derivedNames;
27981
27559
  };
27982
27560
  const isStatefulOperand = (node, paramNames, derivedNames) => referencesAnyName(node, paramNames) || referencesAnyName(node, derivedNames) || containsThisStateOrProps(node);
27983
- const getStaticMemberName = (node) => {
27984
- if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
27985
- return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
27986
- };
27987
- const getThisStateFieldName = (node) => {
27988
- const unwrappedNode = stripParenExpression(node);
27989
- if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
27990
- const object = stripParenExpression(unwrappedNode.object);
27991
- if (!isNodeOfType(object, "MemberExpression") || !isNodeOfType(stripParenExpression(object.object), "ThisExpression") || getStaticMemberName(object) !== "state") return null;
27992
- return getStaticMemberName(unwrappedNode);
27993
- };
27994
- const collectLocalInitializers = (lifecycleFunction) => {
27995
- const initializers = /* @__PURE__ */ new Map();
27996
- const body = lifecycleFunction.body;
27997
- if (!body) return initializers;
27998
- walkAst(body, (node) => {
27999
- if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
28000
- if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.init) initializers.set(node.id.name, node.init);
28001
- });
28002
- return initializers;
28003
- };
28004
- const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
28005
- if (readsPostMountValue(node)) return true;
28006
- const referencedNames = /* @__PURE__ */ new Set();
28007
- collectReferenceIdentifierNames(node, referencedNames);
28008
- for (const referencedName of referencedNames) {
28009
- if (visitedNames.has(referencedName)) continue;
28010
- const initializer = localInitializers.get(referencedName);
28011
- if (!initializer) continue;
28012
- if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
28013
- }
28014
- return false;
28015
- };
28016
- const getSetStateFieldValue = (setStateCall, fieldName) => {
28017
- if (!isNodeOfType(setStateCall, "CallExpression")) return null;
28018
- const argument = setStateCall.arguments?.[0];
28019
- if (!argument || !isNodeOfType(argument, "ObjectExpression")) return null;
28020
- for (const property of argument.properties ?? []) {
28021
- if (!isNodeOfType(property, "Property") || property.computed === true) continue;
28022
- 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;
28023
- }
28024
- return null;
28025
- };
28026
- const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
28027
- let qualifies = false;
28028
- walkAst(test, (node) => {
28029
- if (qualifies) return false;
28030
- if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
28031
- const leftFieldName = getThisStateFieldName(node.left);
28032
- const rightFieldName = getThisStateFieldName(node.right);
28033
- const fieldName = leftFieldName ?? rightFieldName;
28034
- const comparedValue = leftFieldName ? node.right : node.left;
28035
- if (!fieldName || !leftFieldName && !rightFieldName) return;
28036
- const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
28037
- if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
28038
- if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
28039
- qualifies = true;
28040
- return false;
28041
- });
28042
- return qualifies;
28043
- };
28044
27561
  const isDiffGuardTest = (test, paramNames, derivedNames) => {
28045
27562
  if (referencesAnyName(test, paramNames)) return true;
28046
27563
  let qualifies = false;
@@ -28048,7 +27565,7 @@ const isDiffGuardTest = (test, paramNames, derivedNames) => {
28048
27565
  if (qualifies) return false;
28049
27566
  if (!isNodeOfType(node, "BinaryExpression")) return;
28050
27567
  if (!EQUALITY_OPERATORS.has(node.operator)) return;
28051
- 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)) {
28052
27569
  qualifies = true;
28053
27570
  return false;
28054
27571
  }
@@ -28061,12 +27578,11 @@ const isInsideDiffGuard = (setStateCall) => {
28061
27578
  const paramNames = /* @__PURE__ */ new Set();
28062
27579
  for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
28063
27580
  const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
28064
- const localInitializers = collectLocalInitializers(lifecycleFunction);
28065
27581
  let child = setStateCall;
28066
27582
  let ancestor = setStateCall.parent;
28067
27583
  while (ancestor && ancestor !== lifecycleFunction) {
28068
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;
28069
- if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers))) return true;
27585
+ if (guardTest && isDiffGuardTest(guardTest, paramNames, derivedNames)) return true;
28070
27586
  child = ancestor;
28071
27587
  ancestor = ancestor.parent ?? null;
28072
27588
  }
@@ -28247,16 +27763,7 @@ const producesOpaqueInstanceValue = (expression) => {
28247
27763
  const collectSetterValueObservations = (componentBody, setterNames) => {
28248
27764
  const plainFedSetterNames = /* @__PURE__ */ new Set();
28249
27765
  const opaqueFedSetterNames = /* @__PURE__ */ new Set();
28250
- const callbackRefSetterNames = /* @__PURE__ */ new Set();
28251
27766
  walkComponentRespectingShadows(componentBody, /* @__PURE__ */ new Set(), (node, currentlyShadowed) => {
28252
- if (isNodeOfType(node, "JSXAttribute")) {
28253
- const attributeName = node.name;
28254
- if (isNodeOfType(attributeName, "JSXIdentifier") && attributeName.name === "ref" && node.value && isNodeOfType(node.value, "JSXExpressionContainer")) {
28255
- const expression = stripParenExpression(node.value.expression);
28256
- if (isNodeOfType(expression, "Identifier") && setterNames.has(expression.name)) callbackRefSetterNames.add(expression.name);
28257
- }
28258
- return;
28259
- }
28260
27767
  if (!isNodeOfType(node, "CallExpression")) return;
28261
27768
  if (!isNodeOfType(node.callee, "Identifier")) return;
28262
27769
  const setterName = node.callee.name;
@@ -28273,10 +27780,21 @@ const collectSetterValueObservations = (componentBody, setterNames) => {
28273
27780
  }, true);
28274
27781
  return {
28275
27782
  plainFedSetterNames,
28276
- opaqueFedSetterNames,
28277
- callbackRefSetterNames
27783
+ opaqueFedSetterNames
28278
27784
  };
28279
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
+ };
28280
27798
  const collectFunctionLocalBindings = (functionNode) => {
28281
27799
  const localBindings = /* @__PURE__ */ new Set();
28282
27800
  if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return localBindings;
@@ -28288,8 +27806,8 @@ const collectFunctionLocalBindings = (functionNode) => {
28288
27806
  return localBindings;
28289
27807
  };
28290
27808
  const collectBlockScopedBindings = (node) => {
27809
+ const blockBindings = /* @__PURE__ */ new Set();
28291
27810
  if (isNodeOfType(node, "BlockStatement")) {
28292
- const blockBindings = /* @__PURE__ */ new Set();
28293
27811
  for (const statement of node.body ?? []) {
28294
27812
  if (!isNodeOfType(statement, "VariableDeclaration")) continue;
28295
27813
  for (const declarator of statement.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
@@ -28297,22 +27815,17 @@ const collectBlockScopedBindings = (node) => {
28297
27815
  return blockBindings;
28298
27816
  }
28299
27817
  if (isNodeOfType(node, "ForStatement") && isNodeOfType(node.init, "VariableDeclaration")) {
28300
- const blockBindings = /* @__PURE__ */ new Set();
28301
27818
  for (const declarator of node.init.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
28302
27819
  return blockBindings;
28303
27820
  }
28304
- if ((isNodeOfType(node, "ForOfStatement") || isNodeOfType(node, "ForInStatement")) && isNodeOfType(node.left, "VariableDeclaration")) {
28305
- const blockBindings = /* @__PURE__ */ new Set();
28306
- for (const declarator of node.left.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
28307
- return blockBindings;
28308
- }
28309
- 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;
28310
27823
  };
28311
27824
  const walkComponentRespectingShadows = (node, shadowedStateNames, visit, isComponentBodyRoot = false) => {
28312
27825
  if (!node || typeof node !== "object") return;
28313
27826
  let nextShadowedStateNames = shadowedStateNames;
28314
- const localBindings = isComponentBodyRoot ? null : isFunctionLike$1(node) ? collectFunctionLocalBindings(node) : collectBlockScopedBindings(node);
28315
- if (localBindings && localBindings.size > 0) {
27827
+ const localBindings = isComponentBodyRoot ? /* @__PURE__ */ new Set() : isFunctionLike$1(node) ? collectFunctionLocalBindings(node) : collectBlockScopedBindings(node);
27828
+ if (localBindings.size > 0) {
28316
27829
  const merged = new Set(shadowedStateNames);
28317
27830
  for (const localName of localBindings) merged.add(localName);
28318
27831
  nextShadowedStateNames = merged;
@@ -28338,10 +27851,11 @@ const noDirectStateMutation = defineRule({
28338
27851
  const bindings = collectUseStateBindings(componentBody);
28339
27852
  if (bindings.length === 0) return;
28340
27853
  const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
27854
+ const callbackRefSetterNames = collectCallbackRefSetterNames(componentBody);
28341
27855
  const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
28342
27856
  const plainObjectStateValueNames = /* @__PURE__ */ new Set();
28343
27857
  for (const binding of bindings) {
28344
- if (setterValueObservations.callbackRefSetterNames.has(binding.setterName)) continue;
27858
+ if (callbackRefSetterNames.has(binding.setterName)) continue;
28345
27859
  if (!isNodeOfType(binding.declarator.init, "CallExpression")) continue;
28346
27860
  const initializerArgument = binding.declarator.init.arguments?.[0];
28347
27861
  if (!initializerMarksPlainState(initializerArgument)) continue;
@@ -28483,31 +27997,20 @@ const noDocumentStartViewTransition = defineRule({
28483
27997
  } })
28484
27998
  });
28485
27999
  //#endregion
28486
- //#region src/plugin/utils/get-static-property-name.ts
28487
- const getStaticPropertyName = (memberExpression) => {
28488
- const property = memberExpression.property;
28489
- if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
28490
- if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
28491
- if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
28492
- return null;
28493
- };
28494
- //#endregion
28495
28000
  //#region src/plugin/rules/js-performance/no-document-write.ts
28496
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.";
28497
28002
  const WRITE_METHODS = new Set(["write", "writeln"]);
28498
- const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
28499
28003
  const noDocumentWrite = defineRule({
28500
28004
  id: "no-document-write",
28501
28005
  title: "document.write/writeln",
28502
28006
  severity: "warn",
28503
28007
  recommendation: "Don't use `document.write()`/`document.writeln()`. Append DOM nodes or set `innerHTML`/`textContent` on a specific element instead.",
28504
28008
  create: (context) => ({ CallExpression(node) {
28505
- const callee = stripParenExpression(node.callee);
28506
- if (!isNodeOfType(callee, "MemberExpression")) return;
28009
+ const callee = node.callee;
28010
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
28507
28011
  const receiver = stripParenExpression(callee.object);
28508
- if (!isNodeOfType(receiver, "Identifier") || !isGlobalDocumentReference(receiver, context)) return;
28509
- const methodName = getStaticPropertyName(callee);
28510
- 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;
28511
28014
  context.report({
28512
28015
  node,
28513
28016
  message: MESSAGE$24
@@ -29314,14 +28817,6 @@ const noEffectWithFreshDeps = defineRule({
29314
28817
  //#endregion
29315
28818
  //#region src/plugin/rules/security/no-eval.ts
29316
28819
  const SANDBOX_SURFACE_PATH_PATTERN = /(?:^|[/-])sandbox(?:$|[/-])|(?:^|\/)[\w.]+-sandbox(?:\/|\.[cm]?[jt]sx?$)/i;
29317
- const getExecutableGlobalName = (node, context) => {
29318
- const expression = stripParenExpression(node);
29319
- if (isNodeOfType(expression, "Identifier")) return context.scopes.isGlobalReference(expression) ? expression.name : null;
29320
- if (!isNodeOfType(expression, "MemberExpression")) return null;
29321
- const receiver = stripParenExpression(expression.object);
29322
- if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "globalThis" || !context.scopes.isGlobalReference(receiver)) return null;
29323
- return getStaticPropertyName(expression);
29324
- };
29325
28820
  const noEval = defineRule({
29326
28821
  id: "no-eval",
29327
28822
  title: "eval() runs untrusted code strings",
@@ -29331,21 +28826,20 @@ const noEval = defineRule({
29331
28826
  if (SANDBOX_SURFACE_PATH_PATTERN.test(normalizeFilename(context.filename ?? ""))) return {};
29332
28827
  return {
29333
28828
  CallExpression(node) {
29334
- const executableGlobalName = getExecutableGlobalName(node.callee, context);
29335
- if (executableGlobalName === "eval") {
28829
+ if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "eval") {
29336
28830
  context.report({
29337
28831
  node,
29338
28832
  message: "eval() is a code-injection vulnerability: it runs any string as code."
29339
28833
  });
29340
28834
  return;
29341
28835
  }
29342
- 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({
29343
28837
  node,
29344
- 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.`
29345
28839
  });
29346
28840
  },
29347
28841
  NewExpression(node) {
29348
- if (getExecutableGlobalName(node.callee, context) === "Function") {
28842
+ if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "Function") {
29349
28843
  const onlyArgument = node.arguments.length === 1 ? node.arguments[0] : void 0;
29350
28844
  if (isNodeOfType(onlyArgument, "Literal") && typeof onlyArgument.value === "string" && onlyArgument.value.trim() === "return this") return;
29351
28845
  context.report({
@@ -30604,11 +30098,7 @@ const noFetchInEffect = defineRule({
30604
30098
  severity: "warn",
30605
30099
  recommendation: "Use a data-fetching layer or Server Component so fetches do not race, double-fire, or leak from `useEffect`.",
30606
30100
  create: (context) => ({ CallExpression(node) {
30607
- if (!isReactApiCall(node, EFFECT_HOOK_NAMES$1, context.scopes, {
30608
- allowGlobalReactNamespace: true,
30609
- allowUnboundBareCalls: true,
30610
- resolveNamedAliases: true
30611
- })) return;
30101
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
30612
30102
  const callback = getEffectCallback(node);
30613
30103
  if (!callback) return;
30614
30104
  const analysisFunctions = collectEffectAnalysisFunctions(callback, context);
@@ -33083,60 +32573,25 @@ const resolveSettings$16 = (settings) => {
33083
32573
  };
33084
32574
  const isHocCall = (call, scopes) => {
33085
32575
  if (!isNodeOfType(call, "CallExpression")) return false;
33086
- if (isReactApiCall(call, REACT_HOC_NAMES, scopes, {
33087
- allowGlobalReactNamespace: true,
33088
- allowUnboundBareCalls: true
33089
- })) return true;
33090
- if (isReactHocMemberReference(call.callee, scopes)) return true;
32576
+ const calleeName = flattenCalleeName(call.callee);
32577
+ if (calleeName && REACT_HOC_NAMES.has(calleeName)) return true;
33091
32578
  if (!isNodeOfType(call.callee, "Identifier")) return false;
33092
32579
  const symbol = scopes.symbolFor(call.callee);
33093
32580
  if (!symbol) return false;
33094
- return symbolMapsToHoc(symbol, scopes, /* @__PURE__ */ new Set());
33095
- };
33096
- const isReactImportEquals = (symbol) => {
33097
- if (symbol.kind !== "ts-import-equals" || !isNodeOfType(symbol.declarationNode, "TSImportEqualsDeclaration")) return false;
33098
- const moduleReference = symbol.declarationNode.moduleReference;
33099
- 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);
33100
32582
  };
33101
- const isRequireReactCall$1 = (node, scopes) => {
33102
- if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier") || node.callee.name !== "require" || !scopes.isGlobalReference(node.callee)) return false;
33103
- const moduleSpecifier = node.arguments[0];
33104
- return Boolean(moduleSpecifier && isNodeOfType(moduleSpecifier, "Literal") && typeof moduleSpecifier.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(moduleSpecifier.value));
33105
- };
33106
- const isReactNamespaceExpression = (node, scopes) => {
33107
- if (isRequireReactCall$1(node, scopes)) return true;
33108
- if (!isNodeOfType(node, "Identifier")) return false;
33109
- const symbol = scopes.symbolFor(node);
33110
- if (!symbol) return node.name === "React" && scopes.isGlobalReference(node);
33111
- if (symbol.initializer && isRequireReactCall$1(symbol.initializer, scopes)) return true;
33112
- if (isReactImportEquals(symbol)) return true;
33113
- return isReactNamespaceImport(node, scopes);
33114
- };
33115
- 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));
33116
- const getDestructuredPropertyName$1 = (symbol) => {
33117
- const property = symbol.bindingIdentifier.parent;
33118
- if (!property || !isNodeOfType(property, "Property") || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
33119
- if (isNodeOfType(property.key, "Identifier") && !property.computed) return property.key.name;
33120
- if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
33121
- return null;
33122
- };
33123
- const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
33124
- if (visitedSymbolIds.has(symbol.id)) return false;
33125
- visitedSymbolIds.add(symbol.id);
33126
- if (symbol.kind === "import") {
33127
- const importedName = getImportedName(symbol.declarationNode);
33128
- 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;
33129
32586
  }
33130
32587
  const init = symbol.initializer;
33131
32588
  if (!init) return false;
33132
- const destructuredPropertyName = getDestructuredPropertyName$1(symbol);
33133
- if (destructuredPropertyName && REACT_HOC_NAMES.has(destructuredPropertyName) && isReactNamespaceExpression(init, scopes)) return true;
33134
- if (isReactHocMemberReference(init, scopes)) return true;
33135
- if (isNodeOfType(init, "Identifier")) {
33136
- const initializedFromSymbol = scopes.symbolFor(init);
33137
- if (initializedFromSymbol) return symbolMapsToHoc(initializedFromSymbol, scopes, visitedSymbolIds);
33138
- 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;
33139
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;
33140
32595
  return false;
33141
32596
  };
33142
32597
  const isTrivialPassthroughChild = (child) => {
@@ -33194,14 +32649,26 @@ const isHocComponent = (call, scopes) => {
33194
32649
  };
33195
32650
  const containsJsx = (root) => {
33196
32651
  let found = false;
33197
- walkAst(root, (node) => {
33198
- if (found) return false;
32652
+ const visit = (node) => {
32653
+ if (found) return;
33199
32654
  if (node.type === "JSXElement" || node.type === "JSXFragment") {
33200
32655
  found = true;
33201
- return false;
32656
+ return;
33202
32657
  }
33203
- if (node !== root && (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "ClassDeclaration" || node.type === "ClassExpression")) return false;
33204
- });
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);
33205
32672
  return found;
33206
32673
  };
33207
32674
  const expressionContainsJsx = (expression) => {
@@ -33225,7 +32692,7 @@ const unwrapTsCast = (expression) => {
33225
32692
  while (current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSNonNullExpression") current = stripParenExpression(current.expression);
33226
32693
  return current;
33227
32694
  };
33228
- const collectReExportedNames = (program, scopes) => {
32695
+ const collectReExportedNames = (program) => {
33229
32696
  const names = /* @__PURE__ */ new Set();
33230
32697
  if (!isNodeOfType(program, "Program")) return names;
33231
32698
  for (const statement of program.body) {
@@ -33249,7 +32716,8 @@ const collectReExportedNames = (program, scopes) => {
33249
32716
  if (!declarator.init) continue;
33250
32717
  const init = unwrapTsCast(declarator.init);
33251
32718
  if (isNodeOfType(init, "CallExpression")) {
33252
- if (isHocCall(init, scopes)) {
32719
+ const calleeName = flattenCalleeName(init.callee);
32720
+ if (calleeName && REACT_HOC_NAMES.has(calleeName)) {
33253
32721
  const wrappedArg = init.arguments[0];
33254
32722
  if (wrappedArg && isNodeOfType(wrappedArg, "Identifier")) names.add(wrappedArg.name);
33255
32723
  }
@@ -33318,7 +32786,16 @@ const recordComponent = (context, name, reportNode, isStateless) => {
33318
32786
  isStateless
33319
32787
  });
33320
32788
  };
33321
- 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
+ };
33322
32799
  const walkComponentSearch = (node, context) => {
33323
32800
  if (isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) {
33324
32801
  if (isEs6Component(node)) {
@@ -33420,13 +32897,12 @@ const noMultiComp = defineRule({
33420
32897
  components: [],
33421
32898
  componentDepth: 0,
33422
32899
  currentVarName: null,
33423
- scopes: context.scopes,
33424
- visitChild: (child) => walkComponentSearch(child, visitContext)
32900
+ scopes: context.scopes
33425
32901
  };
33426
32902
  for (const statement of node.body) walkComponentSearch(statement, visitContext);
33427
32903
  const flagged = settings.ignoreStateless ? visitContext.components.filter((component) => !component.isStateless) : visitContext.components;
33428
32904
  if (flagged.length <= 2) return;
33429
- const reExportedNames = collectReExportedNames(node, context.scopes);
32905
+ const reExportedNames = collectReExportedNames(node);
33430
32906
  const exportedCount = flagged.filter((component) => isExportedDeclaration(component.reportNode, reExportedNames)).length;
33431
32907
  if (exportedCount >= flagged.length || flagged.length >= 4 && exportedCount >= Math.floor(flagged.length * .7) || flagged.length >= 8 && exportedCount >= Math.floor(flagged.length * .5)) return;
33432
32908
  const isSmallFeatureModule = exportedCount > 0 && exportedCount <= 2 && exportedCount < flagged.length;
@@ -34818,6 +34294,19 @@ const DATA_SINK_METHOD_NAMES = new Set([
34818
34294
  "deserialize"
34819
34295
  ]);
34820
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
34821
34310
  //#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
34822
34311
  const isUseStateIdentifier = (identifier) => {
34823
34312
  if (!isNodeOfType(identifier, "Identifier")) return false;
@@ -35443,30 +34932,6 @@ const guardsRenderShape = (comparison) => {
35443
34932
  }
35444
34933
  return false;
35445
34934
  };
35446
- const isPropsChildrenLength = (node, scopes) => {
35447
- const unwrappedNode = stripParenExpression(node);
35448
- return isNodeOfType(unwrappedNode, "MemberExpression") && !unwrappedNode.computed && isNodeOfType(unwrappedNode.property, "Identifier") && unwrappedNode.property.name === "length" && resolvesToPropsChildren(stripParenExpression(unwrappedNode.object), scopes);
35449
- };
35450
- const isLargeTextLengthComparison = (node, scopes) => {
35451
- const unwrappedNode = stripParenExpression(node);
35452
- if (!isNodeOfType(unwrappedNode, "BinaryExpression")) return false;
35453
- const leftIsLength = isPropsChildrenLength(unwrappedNode.left, scopes);
35454
- const rightIsLength = isPropsChildrenLength(unwrappedNode.right, scopes);
35455
- const thresholdNode = leftIsLength ? unwrappedNode.right : unwrappedNode.left;
35456
- if (!leftIsLength && !rightIsLength || !isNodeOfType(thresholdNode, "Literal")) return false;
35457
- if (typeof thresholdNode.value !== "number" || thresholdNode.value < 1e3) return false;
35458
- return leftIsLength ? unwrappedNode.operator === ">" || unwrappedNode.operator === ">=" : unwrappedNode.operator === "<" || unwrappedNode.operator === "<=";
35459
- };
35460
- const isLargeStringOptimizationGuard = (comparison, scopes) => {
35461
- let current = findTransparentExpressionRoot(comparison);
35462
- while (current.parent) {
35463
- const parent = current.parent;
35464
- if (!isNodeOfType(parent, "LogicalExpression") || parent.operator !== "&&") return false;
35465
- if (isLargeTextLengthComparison(parent.left === current ? parent.right : parent.left, scopes)) return true;
35466
- current = findTransparentExpressionRoot(parent);
35467
- }
35468
- return false;
35469
- };
35470
34935
  const noPolymorphicChildren = defineRule({
35471
34936
  id: "no-polymorphic-children",
35472
34937
  title: "Children type checked at runtime",
@@ -35480,7 +34945,6 @@ const noPolymorphicChildren = defineRule({
35480
34945
  const isStringLiteral = (operand) => isNodeOfType(operand, "Literal") && operand.value === "string";
35481
34946
  if (!isStringLiteral(node.left) && !isStringLiteral(node.right)) return;
35482
34947
  if (!guardsRenderShape(node)) return;
35483
- if (isLargeStringOptimizationGuard(node, context.scopes)) return;
35484
34948
  context.report({
35485
34949
  node,
35486
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."
@@ -35698,27 +35162,6 @@ const hasPreviousValueDep = (effectNode, depElements) => {
35698
35162
  }
35699
35163
  return false;
35700
35164
  };
35701
- const isStateLikeDependency = (analysis, element, isPropName) => {
35702
- if (!isNodeOfType(element, "Identifier") || isPropName(element.name)) return false;
35703
- if (!analysis) return true;
35704
- const reference = getRef(analysis, element);
35705
- if (!reference) return true;
35706
- const upstreamReferences = getUpstreamRefs(analysis, reference);
35707
- if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
35708
- return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
35709
- };
35710
- const getRefHeldPropCallbackName = (callExpression, isPropName) => {
35711
- const callee = stripParenExpression(callExpression.callee);
35712
- if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "current") return null;
35713
- const receiver = stripParenExpression(callee.object);
35714
- if (!isNodeOfType(receiver, "Identifier")) return null;
35715
- const binding = findVariableInitializer(callExpression, receiver.name);
35716
- if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
35717
- if (getCalleeName$2(binding.initializer) !== "useRef") return null;
35718
- const callbackArgument = binding.initializer.arguments?.[0];
35719
- if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
35720
- return isPropName(callbackArgument.name) ? callbackArgument.name : null;
35721
- };
35722
35165
  const noPropCallbackInEffect = defineRule({
35723
35166
  id: "no-prop-callback-in-effect",
35724
35167
  title: "Parent kept in sync with a callback effect",
@@ -35735,11 +35178,11 @@ const noPropCallbackInEffect = defineRule({
35735
35178
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
35736
35179
  const depsNode = node.arguments[1];
35737
35180
  if (!isNodeOfType(depsNode, "ArrayExpression") || !depsNode.elements?.length) return;
35738
- const analysis = getProgramAnalysis(node);
35739
- 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));
35740
35182
  if (stateLikeDeps.length === 0) return;
35741
35183
  if (isRefLatchGuardedEffect(callback.body)) return;
35742
35184
  if (hasPreviousValueDep(node, depsNode.elements ?? [])) return;
35185
+ const analysis = getProgramAnalysis(node);
35743
35186
  if (analysis) {
35744
35187
  const stateLikeDepRefs = [];
35745
35188
  for (const element of stateLikeDeps) {
@@ -35751,9 +35194,9 @@ const noPropCallbackInEffect = defineRule({
35751
35194
  const reportedNodes = /* @__PURE__ */ new Set();
35752
35195
  walkInsideStatementBlocks(callback.body, (child) => {
35753
35196
  if (!isNodeOfType(child, "CallExpression")) return;
35754
- const directCallee = stripParenExpression(child.callee);
35755
- const calleeName = isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName);
35756
- if (!calleeName) return;
35197
+ if (!isNodeOfType(child.callee, "Identifier")) return;
35198
+ const calleeName = child.callee.name;
35199
+ if (!propStackTracker.isPropName(calleeName)) return;
35757
35200
  if (!isResultDiscardedCall(child)) return;
35758
35201
  if (reportedNodes.has(child)) return;
35759
35202
  reportedNodes.add(child);
@@ -39927,18 +39370,34 @@ const resolveSettings$8 = (settings) => {
39927
39370
  };
39928
39371
  const expressionContainsJsxOrCreateElement = (root) => {
39929
39372
  let found = false;
39930
- walkAst(root, (node) => {
39931
- if (found) return false;
39932
- if (node !== root && NESTED_FUNCTION_TYPES.has(node.type)) return false;
39373
+ const visit = (node) => {
39374
+ if (found) return;
39933
39375
  if (node.type === "JSXElement" || node.type === "JSXFragment") {
39934
39376
  found = true;
39935
- return false;
39377
+ return;
39936
39378
  }
39937
39379
  if (isNodeOfType(node, "CallExpression") && isCreateElementCall(node)) {
39938
39380
  found = true;
39939
- return false;
39381
+ return;
39940
39382
  }
39941
- });
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);
39942
39401
  return found;
39943
39402
  };
39944
39403
  const isReactClassComponent = (classNode) => {
@@ -40864,6 +40323,19 @@ const classifyExport = (name, reportNode, isFunction, initializer, state) => {
40864
40323
  reportNode
40865
40324
  };
40866
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
+ };
40867
40339
  const isEntryPointFile = (filename) => {
40868
40340
  const lastSlash = Math.max(filename.lastIndexOf("/"), filename.lastIndexOf("\\"));
40869
40341
  const basename = lastSlash === -1 ? filename : filename.slice(lastSlash + 1);
@@ -40902,212 +40374,197 @@ const onlyExportComponents = defineRule({
40902
40374
  category: "Architecture",
40903
40375
  create: (context) => {
40904
40376
  const settings = resolveSettings$6(context.settings);
40905
- if (!isFileNameAllowed(normalizeFilename(context.filename ?? ""), settings.checkJS)) return {};
40906
- const exportNodes = [];
40907
- const componentCandidates = [];
40908
- const pushExportNode = (node) => {
40909
- exportNodes.push(node);
40910
- };
40911
- const pushComponentCandidate = (node) => {
40912
- componentCandidates.push(node);
40913
- };
40914
- return {
40915
- ExportAllDeclaration: pushExportNode,
40916
- ExportDefaultDeclaration: pushExportNode,
40917
- ExportNamedDeclaration: pushExportNode,
40918
- FunctionDeclaration: pushComponentCandidate,
40919
- VariableDeclarator: pushComponentCandidate,
40920
- ClassDeclaration: pushComponentCandidate,
40921
- "Program:exit"() {
40922
- const localComponentNames = /* @__PURE__ */ new Set();
40923
- const state = {
40924
- customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
40925
- allowExportNames: new Set(settings.allowExportNames),
40926
- allowConstantExport: settings.allowConstantExport,
40927
- localComponentNames,
40928
- scopes: context.scopes
40929
- };
40930
- for (const child of componentCandidates) {
40931
- if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
40932
- if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) && functionContainsReactRenderOutput(child, context.scopes)) localComponentNames.add(child.id.name);
40933
- }
40934
- if (isNodeOfType(child, "ClassDeclaration") && child.id) {
40935
- if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
40936
- }
40937
- if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
40938
- const initializer = child.init;
40939
- if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) {
40940
- const expression = initializer ? skipTsExpression(initializer) : null;
40941
- if (!(expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"))) || functionContainsReactRenderOutput(expression, context.scopes)) localComponentNames.add(child.id.name);
40942
- }
40943
- }
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);
40944
40391
  }
40945
- const exports = [];
40946
- let hasReactExport = false;
40947
- let hasAnyExports = false;
40948
- const isExportedNodeIds = /* @__PURE__ */ new WeakSet();
40949
- for (const child of exportNodes) {
40950
- if (isNodeOfType(child, "ExportAllDeclaration")) {
40951
- if (child.exportKind === "type") continue;
40952
- hasAnyExports = true;
40953
- context.report({
40954
- node: child,
40955
- message: EXPORT_ALL_MESSAGE
40956
- });
40957
- 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);
40958
40400
  }
40959
- if (isNodeOfType(child, "ExportDefaultDeclaration")) {
40960
- hasAnyExports = true;
40961
- const declaration = child.declaration;
40962
- const stripped = skipTsExpression(declaration);
40963
- if (isNodeOfType(stripped, "FunctionDeclaration") || isNodeOfType(stripped, "FunctionExpression")) {
40964
- if (stripped.id) {
40965
- const idNode = stripped.id;
40966
- isExportedNodeIds.add(stripped);
40967
- exports.push(classifyExport(idNode.name, idNode, true, null, state));
40968
- } else {
40969
- context.report({
40970
- node: stripped,
40971
- message: ANONYMOUS_MESSAGE
40972
- });
40973
- hasReactExport = true;
40974
- }
40975
- continue;
40976
- }
40977
- if (isNodeOfType(stripped, "ClassDeclaration") || isNodeOfType(stripped, "ClassExpression")) {
40978
- if (stripped.id) {
40979
- const idNode = stripped.id;
40980
- isExportedNodeIds.add(stripped);
40981
- if (isReactComponentName(idNode.name) && isEs6Component(stripped)) hasReactExport = true;
40982
- else exports.push({
40983
- kind: "non-component",
40984
- reportNode: idNode
40985
- });
40986
- } 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({
40987
40428
  node: stripped,
40988
40429
  message: ANONYMOUS_MESSAGE
40989
40430
  });
40990
- continue;
40991
- }
40992
- if (isNodeOfType(stripped, "Identifier")) {
40993
- exports.push(classifyExport(stripped.name, stripped, false, null, state));
40994
- continue;
40431
+ hasReactExport = true;
40995
40432
  }
40996
- if (isNodeOfType(stripped, "CallExpression")) {
40997
- if (isRouteFactoryCall(stripped)) {
40998
- hasReactExport = true;
40999
- continue;
41000
- }
41001
- const isHoc = isHocCallee(stripped.callee, state);
41002
- const firstArg = stripped.arguments[0];
41003
- const firstArgIsValid = Boolean(firstArg) && (() => {
41004
- if (!firstArg) return false;
41005
- const expression = skipTsExpression(firstArg);
41006
- if (isNodeOfType(expression, "Identifier")) return true;
41007
- if (isNodeOfType(expression, "FunctionExpression") && expression.id) return true;
41008
- if (isNodeOfType(expression, "CallExpression") && isHocCallee(expression.callee, state)) return true;
41009
- return false;
41010
- })();
41011
- if (isHoc && firstArgIsValid) hasReactExport = true;
41012
- 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({
41013
40441
  kind: "non-component",
41014
- reportNode: stripped
41015
- });
41016
- else context.report({
41017
- node: stripped,
41018
- message: ANONYMOUS_MESSAGE
41019
- });
41020
- continue;
41021
- }
41022
- if (isNodeOfType(stripped, "ObjectExpression")) {
41023
- context.report({
41024
- node: stripped,
41025
- message: objectExpressionBundlesComponents(stripped, state) ? NAMESPACE_OBJECT_MESSAGE : ANONYMOUS_MESSAGE
41026
- });
41027
- continue;
41028
- }
41029
- if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "Literal")) {
41030
- context.report({
41031
- node: stripped,
41032
- message: ANONYMOUS_MESSAGE
40442
+ reportNode: idNode
41033
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;
41034
40457
  continue;
41035
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")) {
41036
40488
  context.report({
41037
- node: child,
40489
+ node: stripped,
41038
40490
  message: ANONYMOUS_MESSAGE
41039
40491
  });
41040
40492
  continue;
41041
40493
  }
41042
- if (isNodeOfType(child, "ExportNamedDeclaration")) {
41043
- if (child.exportKind === "type") continue;
41044
- hasAnyExports = true;
41045
- if (child.declaration) {
41046
- const declaration = child.declaration;
41047
- if (isNodeOfType(declaration, "FunctionDeclaration") && declaration.id) {
41048
- isExportedNodeIds.add(declaration);
41049
- exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state));
41050
- } else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
41051
- isExportedNodeIds.add(declaration);
41052
- if (isReactComponentName(declaration.id.name) && isEs6Component(declaration)) exports.push({ kind: "react-component" });
41053
- else exports.push({
41054
- kind: "non-component",
41055
- reportNode: declaration.id
41056
- });
41057
- } else if (isNodeOfType(declaration, "VariableDeclaration")) for (const declarator of declaration.declarations) {
41058
- if (!isNodeOfType(declarator.id, "Identifier")) continue;
41059
- isExportedNodeIds.add(declarator);
41060
- const isFunction = canBeReactFunctionComponent(declarator.init ?? null, state);
41061
- exports.push(classifyExport(declarator.id.name, declarator.id, isFunction, declarator.init, state));
41062
- }
41063
- else if (declaration.type === "TSEnumDeclaration" || declaration.type === "TSInterfaceDeclaration" || declaration.type === "TSTypeAliasDeclaration") {
41064
- if (declaration.type === "TSEnumDeclaration") exports.push({
41065
- kind: "non-component",
41066
- reportNode: declaration
41067
- });
41068
- }
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));
41069
40520
  }
41070
- const isReExportFromSource = Boolean(child.source);
41071
- for (const specifier of child.specifiers ?? []) {
41072
- if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
41073
- const exported = specifier.exported;
41074
- const local = specifier.local;
41075
- let exportedName = null;
41076
- if (exported && isNodeOfType(exported, "Identifier")) exportedName = exported.name;
41077
- const localName = local && isNodeOfType(local, "Identifier") ? local.name : null;
41078
- const reportNode = specifier;
41079
- let entry;
41080
- if (exportedName === "default" && localName) entry = classifyExport(localName, reportNode, false, null, state);
41081
- else if (exportedName) entry = classifyExport(exportedName, reportNode, false, null, state);
41082
- else {
41083
- entry = {
41084
- kind: "non-component",
41085
- reportNode
41086
- };
41087
- if (localName && isReactComponentName(localName)) exports.push({ kind: "react-component" });
41088
- }
41089
- if (isReExportFromSource && entry.kind !== "react-component") continue;
41090
- 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" });
41091
40546
  }
40547
+ if (isReExportFromSource && entry.kind !== "react-component") continue;
40548
+ exports.push(entry);
41092
40549
  }
41093
40550
  }
41094
- for (const entry of exports) if (entry.kind === "react-component") hasReactExport = true;
41095
- 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({
41096
40559
  node: entry.reportNode,
41097
- 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
41098
40565
  });
41099
- if (hasAnyExports && hasReactExport) for (const entry of exports) {
41100
- if (entry.kind === "non-component") context.report({
41101
- node: entry.reportNode,
41102
- message: NAMED_EXPORT_MESSAGE
41103
- });
41104
- if (entry.kind === "react-context") context.report({
41105
- node: entry.reportNode,
41106
- message: REACT_CONTEXT_MESSAGE
41107
- });
41108
- }
41109
40566
  }
41110
- };
40567
+ } };
41111
40568
  }
41112
40569
  });
41113
40570
  //#endregion
@@ -41209,12 +40666,7 @@ const postmessageOriginRisk = defineRule({
41209
40666
  scan: (file) => {
41210
40667
  if (!isProductionSourcePath(file.relativePath)) return [];
41211
40668
  if (WORKER_FILE_PATH_PATTERN.test(file.relativePath)) return [];
41212
- if (!file.content.includes("addEventListener") && !file.content.includes("onmessage")) return [];
41213
- const ast = parseSourceText({
41214
- filename: file.absolutePath,
41215
- sourceText: file.content,
41216
- shouldAttachParentReferences: false
41217
- });
40669
+ const ast = parseSourceText(file.absolutePath, file.content);
41218
40670
  if (ast === null) return [];
41219
40671
  const findings = [];
41220
40672
  walkAst(ast, (node) => {
@@ -43594,6 +43046,15 @@ const isStableHookWrapperArgument = (node) => {
43594
43046
  const parent = node.parent;
43595
43047
  return isNodeOfType(parent, "CallExpression") && isNodeOfType(parent.callee, "Identifier") && STABLE_HOOK_WRAPPERS.has(parent.callee.name) && Boolean(parent.arguments?.some((argument) => argument === node));
43596
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
+ };
43597
43058
  const queryStableQueryClient = defineRule({
43598
43059
  id: "query-stable-query-client",
43599
43060
  title: "Unstable QueryClient in component",
@@ -54906,6 +54367,12 @@ const webhookSignatureRisk = defineRule({
54906
54367
  //#region src/plugin/rules/zod/utils/zod-ast.ts
54907
54368
  const ZOD_MODULE = "zod";
54908
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
+ };
54909
54376
  const importInfoCache = /* @__PURE__ */ new WeakMap();
54910
54377
  const getImportInfoForIdentifier = (identifier) => {
54911
54378
  if (importInfoCache.has(identifier)) return importInfoCache.get(identifier) ?? null;
@@ -60086,10 +59553,7 @@ const appendNode = (builder, block, node) => {
60086
59553
  };
60087
59554
  const mapDescendantsToBlock = (builder, node, block) => {
60088
59555
  builder.nodeBlock.set(node, block);
60089
- if (isFunctionLike$1(node)) {
60090
- builder.nestedFunctions.push(node);
60091
- return;
60092
- }
59556
+ if (isFunctionLike$1(node)) return;
60093
59557
  const record = node;
60094
59558
  for (const key of Object.keys(record)) {
60095
59559
  if (key === "parent") continue;
@@ -60323,12 +59787,11 @@ const buildStatement = (builder, statement, current) => {
60323
59787
  mapDescendantsToBlock(builder, statement, current);
60324
59788
  return current;
60325
59789
  };
60326
- const buildFunctionCfg = (functionNode, body, nestedFunctionSink) => {
59790
+ const buildFunctionCfg = (functionNode, body) => {
60327
59791
  const builder = {
60328
59792
  blocks: [],
60329
59793
  entry: null,
60330
59794
  exit: null,
60331
- nestedFunctions: nestedFunctionSink,
60332
59795
  nodeBlock: /* @__PURE__ */ new Map(),
60333
59796
  loopStack: [],
60334
59797
  switchStack: [],
@@ -60346,12 +59809,13 @@ const buildFunctionCfg = (functionNode, body, nestedFunctionSink) => {
60346
59809
  bodyEnd = entry;
60347
59810
  }
60348
59811
  addEdge(bodyEnd, exit, "uncond");
59812
+ const blockOf = (node) => builder.nodeBlock.get(node) ?? null;
60349
59813
  return {
60350
59814
  owner: functionNode,
60351
59815
  entry,
60352
59816
  exit,
60353
59817
  blocks: builder.blocks,
60354
- blockOf: (node) => builder.nodeBlock.get(node) ?? null
59818
+ blockOf
60355
59819
  };
60356
59820
  };
60357
59821
  const computeUnconditionalSet = (cfg) => {
@@ -60388,9 +59852,8 @@ const computeUnconditionalSet = (cfg) => {
60388
59852
  const analyzeControlFlow = (program) => {
60389
59853
  nextBlockId = 0;
60390
59854
  const functionCfgs = /* @__PURE__ */ new Map();
60391
- const pendingFunctions = [];
60392
59855
  const buildFor = (functionNode, body) => {
60393
- const cfg = buildFunctionCfg(functionNode, body, pendingFunctions);
59856
+ const cfg = buildFunctionCfg(functionNode, body);
60394
59857
  functionCfgs.set(functionNode, {
60395
59858
  cfg,
60396
59859
  unconditionalSet: computeUnconditionalSet(cfg)
@@ -60400,18 +59863,21 @@ const analyzeControlFlow = (program) => {
60400
59863
  type: "BlockStatement",
60401
59864
  body: program.body
60402
59865
  });
60403
- for (let functionIndex = 0; functionIndex < pendingFunctions.length; functionIndex += 1) {
60404
- const functionNode = pendingFunctions[functionIndex];
60405
- if (!isFunctionLike$1(functionNode) || !functionNode.body || functionCfgs.has(functionNode)) continue;
60406
- buildFor(functionNode, functionNode.body);
60407
- }
60408
- const getFunctionEntry = (functionNode) => {
60409
- const existingEntry = functionCfgs.get(functionNode);
60410
- if (existingEntry) return existingEntry;
60411
- if (!isFunctionLike$1(functionNode) || !functionNode.body) return null;
60412
- buildFor(functionNode, functionNode.body);
60413
- 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
+ }
60414
59879
  };
59880
+ visit(program);
60415
59881
  const enclosingFunction = (node) => {
60416
59882
  let current = node;
60417
59883
  while (current) {
@@ -60422,12 +59888,12 @@ const analyzeControlFlow = (program) => {
60422
59888
  return null;
60423
59889
  };
60424
59890
  const cfgFor = (functionLike) => {
60425
- return getFunctionEntry(functionLike)?.cfg ?? null;
59891
+ return functionCfgs.get(functionLike)?.cfg ?? null;
60426
59892
  };
60427
59893
  const isUnconditionalFromEntry = (node) => {
60428
59894
  const owner = enclosingFunction(node);
60429
59895
  if (!owner) return true;
60430
- const entry = getFunctionEntry(owner);
59896
+ const entry = functionCfgs.get(owner);
60431
59897
  if (!entry) return true;
60432
59898
  const block = entry.cfg.blockOf(node);
60433
59899
  if (!block) return true;
@@ -60501,14 +59967,17 @@ const wrapWithSemanticContext = (rule) => ({
60501
59967
  }
60502
59968
  };
60503
59969
  const visitors = rule.create(enrichedContext);
60504
- const innerProgramHandler = visitors.Program;
60505
- return {
60506
- ...visitors,
60507
- Program: ((node) => {
60508
- programRoot = node;
60509
- if (innerProgramHandler) innerProgramHandler(node);
60510
- })
60511
- };
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;
60512
59981
  }
60513
59982
  });
60514
59983
  //#endregion