oxlint-plugin-react-doctor 0.7.5-dev.0f07133 → 0.7.5-dev.20cd922

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 +611 -493
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,14 +1,11 @@
1
+ import { KEYS } from "eslint-visitor-keys";
1
2
  import * as path from "node:path";
2
3
  import * as fs from "node:fs";
3
4
  import { readFileSync } from "node:fs";
4
- import { parseSync } from "oxc-parser";
5
+ import { parseSync, visitorKeys } from "oxc-parser";
5
6
  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
10
7
  //#region src/plugin/utils/is-node-of-type.ts
11
- const isNodeOfType = (node, type) => Boolean(hasTypeProperty(node) && node.type === type);
8
+ const isNodeOfType = (node, type) => node !== null && typeof node === "object" && "type" in node && node.type === type;
12
9
  //#endregion
13
10
  //#region src/plugin/utils/non-react-jsx-dialect.ts
14
11
  const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
@@ -565,7 +562,8 @@ const REACT_RUNTIME_MODULE_SOURCES = new Set([
565
562
  "react",
566
563
  "react-dom",
567
564
  "preact/compat",
568
- "preact/hooks"
565
+ "preact/hooks",
566
+ "@wordpress/element"
569
567
  ]);
570
568
  const REACT_ECOSYSTEM_PACKAGE_NAMES = new Set([
571
569
  "next",
@@ -802,24 +800,55 @@ const isHookCall$2 = (node, hookName) => {
802
800
  };
803
801
  //#endregion
804
802
  //#region src/plugin/utils/is-ast-node.ts
805
- const isAstNode = (value) => {
806
- if (!hasTypeProperty(value)) return false;
807
- return typeof value.type === "string";
803
+ const isAstNode = (value) => value !== null && typeof value === "object" && "type" in value && typeof value.type === "string";
804
+ //#endregion
805
+ //#region src/plugin/utils/runtime-visitor-keys.ts
806
+ const RUNTIME_VISITOR_KEYS = {
807
+ ...KEYS,
808
+ ArrayPattern: ["decorators", ...KEYS.ArrayPattern],
809
+ AssignmentPattern: ["decorators", ...KEYS.AssignmentPattern],
810
+ ClassDeclaration: ["decorators", ...KEYS.ClassDeclaration],
811
+ ClassExpression: ["decorators", ...KEYS.ClassExpression],
812
+ Identifier: ["decorators", ...KEYS.Identifier],
813
+ MethodDefinition: ["decorators", ...KEYS.MethodDefinition],
814
+ ObjectPattern: ["decorators", ...KEYS.ObjectPattern],
815
+ PropertyDefinition: ["decorators", ...KEYS.PropertyDefinition],
816
+ RestElement: ["decorators", ...KEYS.RestElement]
808
817
  };
809
818
  //#endregion
810
819
  //#region src/plugin/utils/walk-ast.ts
811
- const walkAst = (node, visitor) => {
812
- if (!node || typeof node !== "object") return;
813
- if (visitor(node) === false) return;
820
+ const forEachChildNode = (node, visit) => {
814
821
  const nodeRecord = node;
815
- for (const key of Object.keys(nodeRecord)) {
816
- if (key === "parent") continue;
822
+ const childKeys = RUNTIME_VISITOR_KEYS[node.type];
823
+ if (childKeys !== void 0) {
824
+ for (let keyIndex = 0; keyIndex < childKeys.length; keyIndex += 1) {
825
+ const child = nodeRecord[childKeys[keyIndex]];
826
+ if (Array.isArray(child)) for (let itemIndex = 0; itemIndex < child.length; itemIndex += 1) {
827
+ const item = child[itemIndex];
828
+ if (isAstNode(item)) visit(item);
829
+ }
830
+ else if (isAstNode(child)) visit(child);
831
+ }
832
+ return;
833
+ }
834
+ for (const key in nodeRecord) {
835
+ if (key === "parent" || !Object.hasOwn(nodeRecord, key)) continue;
817
836
  const child = nodeRecord[key];
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);
837
+ if (Array.isArray(child)) for (let itemIndex = 0; itemIndex < child.length; itemIndex += 1) {
838
+ const item = child[itemIndex];
839
+ if (isAstNode(item)) visit(item);
840
+ }
841
+ else if (isAstNode(child)) visit(child);
821
842
  }
822
843
  };
844
+ const walkAst = (node, visitor) => {
845
+ if (!node || typeof node !== "object") return;
846
+ const visitNode = (current) => {
847
+ if (visitor(current) === false) return;
848
+ forEachChildNode(current, visitNode);
849
+ };
850
+ visitNode(node);
851
+ };
823
852
  //#endregion
824
853
  //#region src/plugin/rules/state-and-effects/activity-wraps-effect-heavy-subtree.ts
825
854
  const ACTIVITY_IMPORTED_NAMES = new Set(["Activity", "unstable_Activity"]);
@@ -1262,7 +1291,7 @@ const REGEX_PRECEDING_KEYWORDS = new Set([
1262
1291
  "await",
1263
1292
  "throw"
1264
1293
  ]);
1265
- const isRegexLiteralStart = (characters, slashIndex) => {
1294
+ const isRegexLiteralStart = (content, characters, slashIndex) => {
1266
1295
  let cursor = slashIndex - 1;
1267
1296
  while (cursor >= 0 && WHITESPACE_PATTERN.test(characters[cursor])) cursor -= 1;
1268
1297
  if (cursor < 0) return true;
@@ -1272,7 +1301,7 @@ const isRegexLiteralStart = (characters, slashIndex) => {
1272
1301
  let wordStartIndex = cursor;
1273
1302
  while (wordStartIndex > 0 && IDENTIFIER_CHARACTER_PATTERN.test(characters[wordStartIndex - 1])) wordStartIndex -= 1;
1274
1303
  if (wordStartIndex > 0 && characters[wordStartIndex - 1] === ".") return false;
1275
- return REGEX_PRECEDING_KEYWORDS.has(characters.slice(wordStartIndex, cursor + 1).join(""));
1304
+ return REGEX_PRECEDING_KEYWORDS.has(content.slice(wordStartIndex, cursor + 1));
1276
1305
  }
1277
1306
  if (previousCharacter === "<") return false;
1278
1307
  if (previousCharacter === ">") return characterBefore === "=";
@@ -1313,13 +1342,15 @@ const quotedLiteralHasWhitespace = (content, openQuoteIndex, delimiter) => {
1313
1342
  return false;
1314
1343
  };
1315
1344
  const blankNonCodePreservingPositions = (content, blankStringContents) => {
1316
- const characters = content.split("");
1345
+ let characters = null;
1317
1346
  let stringDelimiter = null;
1318
1347
  let isBlankingString = false;
1319
1348
  const templateExpressionDepths = [];
1320
1349
  let index = 0;
1321
1350
  const blankUnlessNewline = (offset) => {
1322
- if (offset < content.length && content[offset] !== "\n") characters[offset] = " ";
1351
+ if (offset >= content.length || content[offset] === "\n") return;
1352
+ characters ??= content.split("");
1353
+ characters[offset] = " ";
1323
1354
  };
1324
1355
  while (index < content.length) {
1325
1356
  const character = content[index];
@@ -1366,6 +1397,7 @@ const blankNonCodePreservingPositions = (content, blankStringContents) => {
1366
1397
  continue;
1367
1398
  }
1368
1399
  if (character === "/" && nextCharacter === "/") {
1400
+ characters ??= content.split("");
1369
1401
  while (index < content.length && content[index] !== "\n") {
1370
1402
  characters[index] = " ";
1371
1403
  index += 1;
@@ -1373,6 +1405,7 @@ const blankNonCodePreservingPositions = (content, blankStringContents) => {
1373
1405
  continue;
1374
1406
  }
1375
1407
  if (character === "/" && nextCharacter === "*") {
1408
+ characters ??= content.split("");
1376
1409
  while (index < content.length) {
1377
1410
  if (content[index] === "*" && content[index + 1] === "/") {
1378
1411
  characters[index] = " ";
@@ -1386,7 +1419,7 @@ const blankNonCodePreservingPositions = (content, blankStringContents) => {
1386
1419
  continue;
1387
1420
  }
1388
1421
  if (character === "/") {
1389
- const regexEndIndex = isRegexLiteralStart(characters, index) ? findRegexLiteralEnd(content, index) : null;
1422
+ const regexEndIndex = isRegexLiteralStart(content, characters ?? content, index) ? findRegexLiteralEnd(content, index) : null;
1390
1423
  if (regexEndIndex !== null) {
1391
1424
  if (blankStringContents) for (let interiorIndex = index + 1; interiorIndex < regexEndIndex - 1; interiorIndex += 1) blankUnlessNewline(interiorIndex);
1392
1425
  index = regexEndIndex;
@@ -1404,9 +1437,9 @@ const blankNonCodePreservingPositions = (content, blankStringContents) => {
1404
1437
  }
1405
1438
  index += 1;
1406
1439
  }
1407
- return characters.join("");
1440
+ return characters?.join("") ?? content;
1408
1441
  };
1409
- const stripCommentsPreservingPositions = (content) => blankNonCodePreservingPositions(content, false);
1442
+ const stripCommentsPreservingPositions = (content) => content.includes("//") || content.includes("/*") ? blankNonCodePreservingPositions(content, false) : content;
1410
1443
  const stripCommentsAndStringLiteralsPreservingPositions = (content) => blankNonCodePreservingPositions(content, true);
1411
1444
  //#endregion
1412
1445
  //#region src/plugin/rules/security-scan/utils/scan-by-pattern.ts
@@ -1425,10 +1458,15 @@ const scanByPattern = ({ shouldScan, pattern, requireAll, suppressWhen, ignoreSt
1425
1458
  if (!shouldScan(file)) return [];
1426
1459
  const content = getScannableContent(file, ignoreStringLiterals);
1427
1460
  if (requireAll !== void 0 && !requireAll.every((gate) => gate.test(content))) return [];
1428
- const matchedPattern = (pattern instanceof RegExp ? [pattern] : pattern).find((candidate) => candidate.test(content));
1429
- if (matchedPattern === void 0) return [];
1461
+ let matchIndex = -1;
1462
+ if (pattern instanceof RegExp) matchIndex = content.search(pattern);
1463
+ else for (let patternIndex = 0; patternIndex < pattern.length; patternIndex += 1) {
1464
+ matchIndex = content.search(pattern[patternIndex]);
1465
+ if (matchIndex >= 0) break;
1466
+ }
1467
+ if (matchIndex < 0) return [];
1430
1468
  if (suppressWhen !== void 0 && suppressWhen.test(content)) return [];
1431
- const { line, column } = getMatchLocation(content, matchedPattern);
1469
+ const { line, column } = getLocationAtIndex(content, matchIndex);
1432
1470
  return [{
1433
1471
  message,
1434
1472
  line,
@@ -1438,6 +1476,7 @@ const scanByPattern = ({ shouldScan, pattern, requireAll, suppressWhen, ignoreSt
1438
1476
  //#endregion
1439
1477
  //#region src/plugin/rules/security-scan/agent-tool-capability-risk.ts
1440
1478
  const AGENT_TOOL_DEFINITION_PATTERN = /\b(?:tool\s*\(\s*\{|createTool\s*\(|defineTool\s*\(|new\s+(?:DynamicTool|StructuredTool)\s*\()/;
1479
+ const AGENT_TOOL_DEFINITION_PREFILTER_PATTERN = /\b(?:tool|createTool|defineTool|DynamicTool|StructuredTool)\b/;
1441
1480
  const AGENT_TOOL_CONTEXT_PATH_PATTERN = /(?:^|\/)(?:agents?|tools?|mcp)(?:\/|$)|(?:agent|tool|mcp)[^/]*\.[cm]?[jt]sx?$/i;
1442
1481
  const agentToolCapabilityRisk = defineRule({
1443
1482
  id: "agent-tool-capability-risk",
@@ -1445,7 +1484,7 @@ const agentToolCapabilityRisk = defineRule({
1445
1484
  severity: "warn",
1446
1485
  recommendation: "Treat tool inputs as prompt-injection controlled. Validate arguments, scope permissions per call, and avoid exposing shell/file/network primitives directly to agents.",
1447
1486
  scan: scanByPattern({
1448
- shouldScan: (file) => isProductionSourcePath(file.relativePath) && AGENT_TOOL_CONTEXT_PATH_PATTERN.test(file.relativePath),
1487
+ 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),
1449
1488
  pattern: AGENT_TOOL_DEFINITION_PATTERN,
1450
1489
  requireAll: [AGENT_TOOL_DANGEROUS_CAPABILITY_PATTERN],
1451
1490
  ignoreStringLiterals: true,
@@ -7398,7 +7437,7 @@ const containsJsx$1 = (root) => {
7398
7437
  containsJsxCache.set(root, found);
7399
7438
  return found;
7400
7439
  };
7401
- const getStaticMemberName$1 = (node) => {
7440
+ const getStaticMemberName$2 = (node) => {
7402
7441
  if (!isNodeOfType(node, "MemberExpression")) return null;
7403
7442
  if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
7404
7443
  if (node.computed && isNodeOfType(node.property, "Literal") && typeof node.property.value === "string") return node.property.value;
@@ -7412,7 +7451,7 @@ const getAssignedName = (node) => {
7412
7451
  if (isNodeOfType(parent, "AssignmentExpression")) {
7413
7452
  const left = parent.left;
7414
7453
  if (isNodeOfType(left, "Identifier")) return left.name;
7415
- if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$1(left);
7454
+ if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$2(left);
7416
7455
  }
7417
7456
  return null;
7418
7457
  };
@@ -7420,20 +7459,20 @@ const isModuleExportsAssignment = (node) => {
7420
7459
  const parent = node.parent;
7421
7460
  if (!parent || !isNodeOfType(parent, "AssignmentExpression")) return false;
7422
7461
  const left = parent.left;
7423
- return isNodeOfType(left, "MemberExpression") && isNodeOfType(left.object, "Identifier") && left.object.name === "module" && getStaticMemberName$1(left) === "exports";
7462
+ return isNodeOfType(left, "MemberExpression") && isNodeOfType(left.object, "Identifier") && left.object.name === "module" && getStaticMemberName$2(left) === "exports";
7424
7463
  };
7425
7464
  const isCreateClassLikeCall = (node) => {
7426
7465
  if (!isNodeOfType(node, "CallExpression")) return false;
7427
7466
  if (isEs5Component(node)) return true;
7428
7467
  const callee = node.callee;
7429
- if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$1(callee) === "createClass";
7468
+ if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$2(callee) === "createClass";
7430
7469
  return false;
7431
7470
  };
7432
7471
  const isCreateContextCall = (node) => {
7433
7472
  if (!isNodeOfType(node, "CallExpression")) return false;
7434
7473
  const callee = node.callee;
7435
7474
  if (isNodeOfType(callee, "Identifier")) return callee.name === "createContext";
7436
- return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$1(callee) === "createContext";
7475
+ return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$2(callee) === "createContext";
7437
7476
  };
7438
7477
  const isNamedFunctionLike = (node) => (isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && Boolean(node.id?.name);
7439
7478
  const firstCallArgument = (node) => {
@@ -7491,7 +7530,7 @@ const memberExpressionPath = (node) => {
7491
7530
  if (isNodeOfType(node, "Identifier")) return [node.name];
7492
7531
  if (!isNodeOfType(node, "MemberExpression")) return [];
7493
7532
  const objectPath = memberExpressionPath(node.object);
7494
- const propertyName = getStaticMemberName$1(node);
7533
+ const propertyName = getStaticMemberName$2(node);
7495
7534
  return propertyName ? [...objectPath, propertyName] : objectPath;
7496
7535
  };
7497
7536
  const displayNameAssignmentIndexCache = /* @__PURE__ */ new WeakMap();
@@ -7501,7 +7540,7 @@ const getDisplayNameAssignmentIndex = (programRoot) => {
7501
7540
  const identifierTargets = /* @__PURE__ */ new Set();
7502
7541
  const memberPathSegments = /* @__PURE__ */ new Set();
7503
7542
  const visit = (node) => {
7504
- if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName$1(node.left) === "displayName") {
7543
+ if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName$2(node.left) === "displayName") {
7505
7544
  if (isNodeOfType(node.left.object, "Identifier")) identifierTargets.add(node.left.object.name);
7506
7545
  for (const segment of memberExpressionPath(node.left.object)) memberPathSegments.add(segment);
7507
7546
  }
@@ -7696,7 +7735,7 @@ const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? ap
7696
7735
  const isImportedFromReact = (symbol) => {
7697
7736
  if (symbol.kind !== "import") return false;
7698
7737
  const importDeclaration = symbol.declarationNode.parent;
7699
- return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react");
7738
+ return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(importDeclaration.source.value));
7700
7739
  };
7701
7740
  const isNamedReactApiImport = (identifier, apiNames, scopes) => {
7702
7741
  if (!isNodeOfType(identifier, "Identifier")) return false;
@@ -9646,6 +9685,11 @@ const walkParameterReferences = (pattern, state) => {
9646
9685
  const walk = (node, state) => {
9647
9686
  if (isFunctionLike$1(node)) {
9648
9687
  if (isNodeOfType(node, "FunctionDeclaration") && node.id) handleFunctionDeclaration(node, state);
9688
+ const functionParams = node.params ?? [];
9689
+ for (const param of functionParams) {
9690
+ if (!("decorators" in param) || !Array.isArray(param.decorators)) continue;
9691
+ for (const decorator of param.decorators) if (isAstNode(decorator)) walk(decorator, state);
9692
+ }
9649
9693
  setNodeScope(node, state);
9650
9694
  const fnScope = pushScope(node.type === "ArrowFunctionExpression" ? "arrow-function" : "function", node, state);
9651
9695
  if ((isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && node.id) {
@@ -9658,7 +9702,6 @@ const walk = (node, state) => {
9658
9702
  });
9659
9703
  tagAsBinding(state, node.id);
9660
9704
  }
9661
- const functionParams = node.params ?? [];
9662
9705
  handleFunctionParameters(functionParams, fnScope, state);
9663
9706
  for (const param of functionParams) walkParameterReferences(param, state);
9664
9707
  const body = node.body;
@@ -9668,6 +9711,9 @@ const walk = (node, state) => {
9668
9711
  }
9669
9712
  if (isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) {
9670
9713
  if (isNodeOfType(node, "ClassDeclaration") && node.id) handleClassDeclaration(node, state);
9714
+ if (Array.isArray(node.decorators)) {
9715
+ for (const decorator of node.decorators) if (isAstNode(decorator)) walk(decorator, state);
9716
+ }
9671
9717
  const classScope = pushScope("class", node, state);
9672
9718
  setNodeScope(node, state);
9673
9719
  if (isNodeOfType(node, "ClassExpression") && node.id) {
@@ -9870,7 +9916,7 @@ const computeClosureCaptures = (functionNode, scopes) => {
9870
9916
  const functionScope = scopes.ownScopeFor(functionNode) ?? scopes.scopeFor(functionNode);
9871
9917
  const out = [];
9872
9918
  const seen = /* @__PURE__ */ new Set();
9873
- const visit = (node) => {
9919
+ walkAst(functionNode, (node) => {
9874
9920
  if (node !== functionNode && isFunctionLike$1(node)) {
9875
9921
  const innerCaptures = closureCaptures(node, scopes);
9876
9922
  for (const reference of innerCaptures) if (reference.resolvedSymbol && !isDescendantScope(reference.resolvedSymbol.scope, functionScope)) {
@@ -9879,7 +9925,7 @@ const computeClosureCaptures = (functionNode, scopes) => {
9879
9925
  seen.add(reference.id);
9880
9926
  }
9881
9927
  }
9882
- return;
9928
+ return false;
9883
9929
  }
9884
9930
  const reference = scopes.referenceFor(node);
9885
9931
  if (reference && reference.resolvedSymbol) {
@@ -9890,17 +9936,7 @@ const computeClosureCaptures = (functionNode, scopes) => {
9890
9936
  }
9891
9937
  }
9892
9938
  }
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);
9939
+ });
9904
9940
  return out;
9905
9941
  };
9906
9942
  const capturesByAnalysis = /* @__PURE__ */ new WeakMap();
@@ -14220,7 +14256,11 @@ const jsIndexMaps = defineRule({
14220
14256
  //#region src/plugin/utils/are-expressions-structurally-equal.ts
14221
14257
  const areExpressionsStructurallyEqual = (a, b) => {
14222
14258
  if (!a || !b) return a === b;
14259
+ const unwrappedA = stripParenExpression(a);
14260
+ const unwrappedB = stripParenExpression(b);
14261
+ if (unwrappedA !== a || unwrappedB !== b) return areExpressionsStructurallyEqual(unwrappedA, unwrappedB);
14223
14262
  if (a.type !== b.type) return false;
14263
+ if (isNodeOfType(a, "ThisExpression")) return true;
14224
14264
  if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
14225
14265
  if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
14226
14266
  if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
@@ -18924,6 +18964,7 @@ const jwtInsecureVerification = defineRule({
18924
18964
  });
18925
18965
  //#endregion
18926
18966
  //#region src/plugin/rules/security-scan/key-lifecycle-risk.ts
18967
+ const KEY_MATERIAL_MARKER_PATTERN = /PRIVATE KEY|SSH_PRIVATE_KEY|GPG_PRIVATE_KEY|DEPLOY_KEY|SIGNING_KEY/i;
18927
18968
  const keyLifecycleRisk = defineRule({
18928
18969
  id: "key-lifecycle-risk",
18929
18970
  title: "Long-lived key material in repository",
@@ -18931,7 +18972,7 @@ const keyLifecycleRisk = defineRule({
18931
18972
  committedFilesOnly: true,
18932
18973
  recommendation: "Remove private keys from source, rotate exposed credentials, prefer short-lived deploy credentials, and document revocation/expiry for release keys.",
18933
18974
  scan: scanByPattern({
18934
- shouldScan: (file) => !TEST_CONTEXT_PATTERN.test(file.relativePath) && !DOCUMENTATION_CONTEXT_PATTERN.test(file.relativePath),
18975
+ shouldScan: (file) => !TEST_CONTEXT_PATTERN.test(file.relativePath) && !DOCUMENTATION_CONTEXT_PATTERN.test(file.relativePath) && KEY_MATERIAL_MARKER_PATTERN.test(file.content),
18935
18976
  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,
18936
18977
  message: "Private or long-lived release key material appears in the repository."
18937
18978
  })
@@ -19637,15 +19678,21 @@ const localRpcNativeBridgeRisk = defineRule({
19637
19678
  message: "Code appears to bridge browser code to localhost/native capabilities with weak origin or update/install checks."
19638
19679
  })
19639
19680
  });
19681
+ //#endregion
19682
+ //#region src/plugin/rules/security-scan/mcp-tool-capability-risk.ts
19683
+ const MCP_IMPORT_PATTERN = /\bfrom\s+["']@modelcontextprotocol\/sdk[^"']*["']|\bMcpServer\b|\bMcpAgent\b/;
19684
+ const MCP_IMPORT_PREFILTER_PATTERN = /@modelcontextprotocol\/sdk|\bMcpServer\b|\bMcpAgent\b/;
19685
+ const MCP_TOOL_SURFACE_PATTERN = /\bserver\.\s*tool\s*\(|\bregisterTool\s*\(|\bsetRequestHandler\s*\(\s*CallToolRequestSchema/;
19686
+ const MCP_TOOL_SURFACE_PREFILTER_PATTERN = /\b(?:tool|registerTool|setRequestHandler)\b/;
19640
19687
  const mcpToolCapabilityRisk = defineRule({
19641
19688
  id: "mcp-tool-capability-risk",
19642
19689
  title: "MCP tool exposes dangerous capability",
19643
19690
  severity: "warn",
19644
19691
  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.",
19645
19692
  scan: scanByPattern({
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],
19693
+ 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),
19694
+ pattern: MCP_TOOL_SURFACE_PATTERN,
19695
+ requireAll: [MCP_IMPORT_PATTERN, AGENT_TOOL_DANGEROUS_CAPABILITY_PATTERN],
19649
19696
  ignoreStringLiterals: true,
19650
19697
  message: "An MCP tool/resource/prompt handler appears to expose file, shell, network, or code-execution capability."
19651
19698
  })
@@ -20756,8 +20803,8 @@ const catchClauseRethrowsCaught = (handler) => {
20756
20803
  return didRethrow;
20757
20804
  };
20758
20805
  //#endregion
20759
- //#region src/plugin/utils/find-guarding-try-statement.ts
20760
- const isImmediatelyInvokedFunctionCallee = (functionNode) => {
20806
+ //#region src/plugin/utils/is-immediately-invoked-function.ts
20807
+ const isImmediatelyInvokedFunction = (functionNode) => {
20761
20808
  let wrappedCallee = functionNode;
20762
20809
  let enclosing = functionNode.parent;
20763
20810
  while (enclosing && stripParenExpression(enclosing) === functionNode) {
@@ -20766,11 +20813,13 @@ const isImmediatelyInvokedFunctionCallee = (functionNode) => {
20766
20813
  }
20767
20814
  return Boolean(enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee);
20768
20815
  };
20816
+ //#endregion
20817
+ //#region src/plugin/utils/find-guarding-try-statement.ts
20769
20818
  const findGuardingTryStatement = (node) => {
20770
20819
  let child = node;
20771
20820
  let ancestor = node.parent;
20772
20821
  while (ancestor) {
20773
- if (isFunctionLike$1(ancestor) && !isImmediatelyInvokedFunctionCallee(ancestor)) return null;
20822
+ if (isFunctionLike$1(ancestor) && !isImmediatelyInvokedFunction(ancestor)) return null;
20774
20823
  if (isNodeOfType(ancestor, "TryStatement") && ancestor.block === child && ancestor.handler && !catchClauseRethrowsCaught(ancestor.handler)) return ancestor;
20775
20824
  child = ancestor;
20776
20825
  ancestor = ancestor.parent ?? null;
@@ -21322,7 +21371,7 @@ const FILENAME_TO_LANG = {
21322
21371
  const resolveLang = (filename) => {
21323
21372
  return FILENAME_TO_LANG[path.extname(filename).toLowerCase()] ?? "tsx";
21324
21373
  };
21325
- const parseSourceText = (filename, sourceText) => {
21374
+ const parseSourceText = ({ filename, sourceText, shouldAttachParentReferences = true }) => {
21326
21375
  try {
21327
21376
  const result = parseSync(filename, sourceText, {
21328
21377
  astType: "ts",
@@ -21330,7 +21379,7 @@ const parseSourceText = (filename, sourceText) => {
21330
21379
  });
21331
21380
  if (result.errors.some((parseError) => parseError.severity === "Error")) return null;
21332
21381
  const parsedProgram = result.program;
21333
- attachParentReferences(parsedProgram);
21382
+ if (shouldAttachParentReferences) attachParentReferences(parsedProgram);
21334
21383
  return parsedProgram;
21335
21384
  } catch {
21336
21385
  return null;
@@ -21368,7 +21417,10 @@ const parseSourceFile = (absoluteFilePath) => {
21368
21417
  });
21369
21418
  return null;
21370
21419
  }
21371
- const parsedProgram = parseSourceText(absoluteFilePath, sourceText);
21420
+ const parsedProgram = parseSourceText({
21421
+ filename: absoluteFilePath,
21422
+ sourceText
21423
+ });
21372
21424
  parseCache.set(absoluteFilePath, {
21373
21425
  mtimeMs: fileStat.mtimeMs,
21374
21426
  size: fileStat.size,
@@ -21900,6 +21952,8 @@ const DOM_QUERY_MEMBER_NAMES = new Set([
21900
21952
  ]);
21901
21953
  const LAYOUT_MEASUREMENT_MEMBER_NAMES = new Set([
21902
21954
  "current",
21955
+ "textContent",
21956
+ "innerText",
21903
21957
  "scrollWidth",
21904
21958
  "clientWidth",
21905
21959
  "offsetWidth",
@@ -21921,7 +21975,7 @@ const POST_MOUNT_GLOBAL_NAMES = new Set([
21921
21975
  "navigator"
21922
21976
  ]);
21923
21977
  const REF_FACTORY_CALLEE_NAMES = new Set(["useRef", "createRef"]);
21924
- const hasRefLikeName = (name) => name === "ref" || name.endsWith("Ref") || name.endsWith("ref");
21978
+ const hasRefLikeName = (name) => name === "ref" || name.endsWith("Ref") || name.endsWith("ref") || name.endsWith("Node") || name.endsWith("node") || name.endsWith("Element") || name.endsWith("element");
21925
21979
  const isRefFactoryInitializer = (init) => {
21926
21980
  if (!init || !isNodeOfType(init, "CallExpression")) return false;
21927
21981
  const callee = init.callee;
@@ -22004,88 +22058,37 @@ const readsPostMountValue = (root) => {
22004
22058
  return found;
22005
22059
  };
22006
22060
  //#endregion
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
- };
22061
+ //#region src/plugin/rules/state-and-effects/utils/effect/get-ast-child-keys.ts
22062
+ const getAstChildKeys = (node) => visitorKeys[node.type] ?? Object.keys(node).filter((key) => key !== "parent");
22019
22063
  //#endregion
22020
22064
  //#region src/plugin/rules/state-and-effects/utils/effect/get-program-analysis.ts
22021
22065
  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
- };
22053
22066
  const getProgramAnalysis = (anyNode) => {
22054
22067
  const programNode = findProgramRoot(anyNode);
22055
22068
  if (!programNode) return null;
22056
22069
  const cached = programToAnalysis.get(programNode);
22057
22070
  if (cached) return cached;
22058
- const restorations = stripAndRecordParents(programNode);
22059
- let scopeManager;
22060
- try {
22061
- scopeManager = analyze(programNode, {
22062
- ecmaVersion: 2024,
22063
- sourceType: "module",
22064
- childVisitorKeys: VISITOR_KEYS,
22065
- fallback: "iteration"
22066
- });
22067
- } finally {
22068
- restoreParents(restorations);
22069
- }
22070
22071
  const analysis = {
22071
22072
  programNode,
22072
- scopeManager
22073
+ scopeManager: analyze(programNode, {
22074
+ ecmaVersion: 2024,
22075
+ sourceType: "module",
22076
+ childVisitorKeys: RUNTIME_VISITOR_KEYS,
22077
+ fallback: getAstChildKeys
22078
+ }),
22079
+ scopeByNode: /* @__PURE__ */ new WeakMap(),
22080
+ referenceByIdentifier: /* @__PURE__ */ new WeakMap()
22073
22081
  };
22074
22082
  programToAnalysis.set(programNode, analysis);
22075
22083
  return analysis;
22076
22084
  };
22077
- const scopeByNodeCache = /* @__PURE__ */ new WeakMap();
22078
- const getScopeForNode = (node, manager) => {
22085
+ const getScopeForNode = (node, analysis) => {
22079
22086
  if (!node.range) return null;
22080
- let scopeByNode = scopeByNodeCache.get(manager);
22081
- if (!scopeByNode) {
22082
- scopeByNode = /* @__PURE__ */ new WeakMap();
22083
- scopeByNodeCache.set(manager, scopeByNode);
22084
- }
22087
+ const scopeByNode = analysis.scopeByNode;
22085
22088
  if (scopeByNode.has(node)) return scopeByNode.get(node) ?? null;
22086
22089
  let bestScope = null;
22087
22090
  let bestSize = Infinity;
22088
- for (const scope of manager.scopes) {
22091
+ for (const scope of analysis.scopeManager.scopes) {
22089
22092
  const block = scope.block;
22090
22093
  if (!block?.range) continue;
22091
22094
  if (node.range[0] < block.range[0] || node.range[1] > block.range[1]) continue;
@@ -22100,7 +22103,6 @@ const getScopeForNode = (node, manager) => {
22100
22103
  };
22101
22104
  //#endregion
22102
22105
  //#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");
22104
22106
  const HOOK_NAME_PATTERN$2 = /^use[A-Z0-9]/;
22105
22107
  const isInsideCallbackArgumentOf = (identifier, initializer) => {
22106
22108
  if (!isNodeOfType(initializer, "CallExpression") && !isNodeOfType(initializer, "NewExpression")) return false;
@@ -22136,7 +22138,7 @@ const descend = (node, visit, visited = /* @__PURE__ */ new Set()) => {
22136
22138
  if (visited.has(node)) return;
22137
22139
  visit(node);
22138
22140
  visited.add(node);
22139
- const keys = getChildKeys(node);
22141
+ const keys = getAstChildKeys(node);
22140
22142
  const record = node;
22141
22143
  for (const key of keys) {
22142
22144
  const child = record[key];
@@ -22169,16 +22171,11 @@ const findDownstreamNodes = (topNode, type) => {
22169
22171
  });
22170
22172
  return nodes;
22171
22173
  };
22172
- const refByIdentifierCache = /* @__PURE__ */ new WeakMap();
22173
22174
  const getRef = (analysis, identifier) => {
22174
- let refByIdentifier = refByIdentifierCache.get(analysis);
22175
- if (!refByIdentifier) {
22176
- refByIdentifier = /* @__PURE__ */ new WeakMap();
22177
- refByIdentifierCache.set(analysis, refByIdentifier);
22178
- }
22175
+ const refByIdentifier = analysis.referenceByIdentifier;
22179
22176
  if (refByIdentifier.has(identifier)) return refByIdentifier.get(identifier) ?? null;
22180
22177
  let resolvedReference = null;
22181
- const scope = getScopeForNode(identifier, analysis.scopeManager);
22178
+ const scope = getScopeForNode(identifier, analysis);
22182
22179
  if (scope) {
22183
22180
  for (const reference of scope.references) if (reference.identifier === identifier) {
22184
22181
  resolvedReference = reference;
@@ -22426,7 +22423,7 @@ const isReactFunctionalHOC = (analysis, node) => {
22426
22423
  const isWrappedSeparately = () => {
22427
22424
  if (!isNodeOfType(node.id, "Identifier")) return false;
22428
22425
  const bindingName = node.id.name;
22429
- const containingScope = getScopeForNode(node, analysis.scopeManager);
22426
+ const containingScope = getScopeForNode(node, analysis);
22430
22427
  if (!containingScope) return false;
22431
22428
  const variable = containingScope.variables.find((v) => v.name === bindingName);
22432
22429
  if (!variable) return false;
@@ -22657,11 +22654,14 @@ const hasCleanupReturn = (analysis, node, visited = /* @__PURE__ */ new WeakSet(
22657
22654
  if (isNodeOfType(node, "ReturnStatement") && node.argument != null) return isCleanupReturnArgument(analysis, node.argument);
22658
22655
  if (!isNodeOfType(node, "BlockStatement") && isFunctionLike$1(node)) return false;
22659
22656
  const record = node;
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;
22657
+ const childKeys = getAstChildKeys(node);
22658
+ for (let keyIndex = 0; keyIndex < childKeys.length; keyIndex += 1) {
22659
+ const value = record[childKeys[keyIndex]];
22660
+ if (Array.isArray(value)) for (let itemIndex = 0; itemIndex < value.length; itemIndex += 1) {
22661
+ const item = value[itemIndex];
22662
+ if (isAstNode(item) && hasCleanupReturn(analysis, item, visited)) return true;
22663
+ }
22664
+ else if (isAstNode(value) && hasCleanupReturn(analysis, value, visited)) return true;
22665
22665
  }
22666
22666
  return false;
22667
22667
  };
@@ -22952,7 +22952,7 @@ const STORAGE_GLOBAL_NAMES$1 = new Set([
22952
22952
  "localStorage",
22953
22953
  "sessionStorage"
22954
22954
  ]);
22955
- const getStaticMemberName = (node) => {
22955
+ const getStaticMemberName$1 = (node) => {
22956
22956
  if (!isNodeOfType(node, "MemberExpression")) return null;
22957
22957
  if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
22958
22958
  if (node.computed && isNodeOfType(node.property, "Literal")) return typeof node.property.value === "string" ? node.property.value : null;
@@ -22967,7 +22967,7 @@ const getCallCalleeName$1 = (callExpression) => {
22967
22967
  if (!isNodeOfType(callExpression, "CallExpression")) return null;
22968
22968
  const callee = stripParenExpression(callExpression.callee);
22969
22969
  if (isNodeOfType(callee, "Identifier")) return callee.name;
22970
- return getStaticMemberName(callee);
22970
+ return getStaticMemberName$1(callee);
22971
22971
  };
22972
22972
  const getFunctionParameters = (functionNode) => isFunctionLike$1(functionNode) ? functionNode.params ?? [] : [];
22973
22973
  const getIdentifierBindingIdentity = (analysis, identifier) => {
@@ -23083,14 +23083,14 @@ const localUseEventPreservesCallback = (analysis, callee) => {
23083
23083
  if (!isNodeOfType(child, "CallExpression")) return;
23084
23084
  const forwardedCallee = stripParenExpression(child.callee);
23085
23085
  if (!isNodeOfType(forwardedCallee, "MemberExpression")) return;
23086
- if (getStaticMemberName(forwardedCallee) !== "current") return;
23086
+ if (getStaticMemberName$1(forwardedCallee) !== "current") return;
23087
23087
  if (!isNodeOfType(forwardedCallee.object, "Identifier")) return;
23088
23088
  const refReference = getRef(analysis, forwardedCallee.object);
23089
23089
  if (!callbackRefDeclarators.find((declarator) => refReference?.resolved?.defs.some((definition) => definition.node === declarator)) || !refReference?.resolved) return;
23090
23090
  if (!refReference.resolved.references.some((candidateReference) => {
23091
23091
  const memberExpression = candidateReference.identifier.parent;
23092
23092
  const assignmentExpression = memberExpression?.parent;
23093
- if (!memberExpression || !isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !assignmentExpression || !isNodeOfType(assignmentExpression, "AssignmentExpression") || assignmentExpression.left !== memberExpression) return false;
23093
+ if (!memberExpression || !isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName$1(memberExpression) !== "current" || !assignmentExpression || !isNodeOfType(assignmentExpression, "AssignmentExpression") || assignmentExpression.left !== memberExpression) return false;
23094
23094
  return getIdentifierBindingIdentity(analysis, assignmentExpression.right) !== callbackBinding;
23095
23095
  })) forwardsCallback = true;
23096
23096
  });
@@ -23115,7 +23115,7 @@ const resolveWrappedCallable = (analysis, node) => {
23115
23115
  return callback && isFunctionLike$1(callback) ? callback : null;
23116
23116
  }
23117
23117
  if (!isNodeOfType(candidate, "MemberExpression")) return null;
23118
- if (getStaticMemberName(candidate) !== "current") return null;
23118
+ if (getStaticMemberName$1(candidate) !== "current") return null;
23119
23119
  if (!isNodeOfType(candidate.object, "Identifier")) return null;
23120
23120
  const reference = getRef(analysis, candidate.object);
23121
23121
  const declarator = reference?.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
@@ -23127,7 +23127,7 @@ const resolveWrappedCallable = (analysis, node) => {
23127
23127
  return Boolean(reference?.resolved?.references.some((candidateReference) => {
23128
23128
  const member = candidateReference.identifier.parent;
23129
23129
  const assignment = member?.parent;
23130
- return Boolean(member && isNodeOfType(member, "MemberExpression") && getStaticMemberName(member) === "current" && assignment && isNodeOfType(assignment, "AssignmentExpression") && assignment.left === member);
23130
+ return Boolean(member && isNodeOfType(member, "MemberExpression") && getStaticMemberName$1(member) === "current" && assignment && isNodeOfType(assignment, "AssignmentExpression") && assignment.left === member);
23131
23131
  })) ? null : initializer;
23132
23132
  };
23133
23133
  const functionInvokesItself = (analysis, functionNode) => {
@@ -23166,7 +23166,7 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilena
23166
23166
  if (!isNodeOfType(child, "CallExpression")) return;
23167
23167
  const callee = stripParenExpression(child.callee);
23168
23168
  const calleeName = getCallCalleeName$1(child);
23169
- const memberName = getStaticMemberName(callee);
23169
+ const memberName = getStaticMemberName$1(callee);
23170
23170
  const isDeferringCall = calleeName !== null && DEFERRING_CALLEE_NAMES.has(calleeName) || memberName !== null && DEFERRING_MEMBER_NAMES.has(memberName);
23171
23171
  const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES$1.has(memberName) && isNodeOfType(callee, "MemberExpression");
23172
23172
  const enqueueFrame = (callableNode, argumentsForCallable, isDeferred, introducedBindings, allowWithoutInvocationEvidence = false) => {
@@ -23330,9 +23330,9 @@ const analyzeHelperExpression = (expression, environment, usedParameterIndices)
23330
23330
  const calleeRoot = getMemberRoot(callee);
23331
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);
23332
23332
  const namespaceName = isNodeOfType(calleeRoot, "Identifier") && !environment.parameterIndices.has(calleeRoot.name) && !environment.recursiveNames.has(calleeRoot.name) && !environment.shadowedGlobalNames.has(calleeRoot.name) ? calleeRoot.name : null;
23333
- const namespaceMemberName = getStaticMemberName(callee);
23333
+ const namespaceMemberName = getStaticMemberName$1(callee);
23334
23334
  const isPureNamespaceCall = isNodeOfType(callee, "MemberExpression") && namespaceName !== null && namespaceMemberName !== null && PURE_HELPER_NAMESPACE_MEMBER_NAMES.get(namespaceName)?.has(namespaceMemberName) === true;
23335
- const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
23335
+ const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName$1(callee) ?? "");
23336
23336
  if (!isPureGlobalCall && !isPureNamespaceCall && !isPureMemberTransform) return false;
23337
23337
  if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression") && !analyzeHelperExpression(callee.object, environment, usedParameterIndices)) return false;
23338
23338
  return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
@@ -23406,7 +23406,7 @@ const isLocallyConstructedObjectMember = (reference, memberExpression) => isNode
23406
23406
  return isNodeOfType(definitionNode, "VariableDeclarator") && Boolean(definitionNode.init) && (isNodeOfType(definitionNode.init, "ObjectExpression") || isNodeOfType(definitionNode.init, "ArrayExpression"));
23407
23407
  }) === true;
23408
23408
  const getUseRefDeclarator = (analysis, memberExpression) => {
23409
- if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !isNodeOfType(memberExpression.object, "Identifier")) return null;
23409
+ if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName$1(memberExpression) !== "current" || !isNodeOfType(memberExpression.object, "Identifier")) return null;
23410
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;
23411
23411
  };
23412
23412
  const collectValueEvidence = (analysis, expression, frame, remainingCallFrames, visitedBindings = /* @__PURE__ */ new Set()) => {
@@ -23475,7 +23475,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23475
23475
  return collectValueEvidence(analysis, initializer.init, frame, remainingCallFrames, visitedBindings);
23476
23476
  }
23477
23477
  if (isNodeOfType(node, "MemberExpression")) {
23478
- if (getStaticMemberName(node) === "current" && isNodeOfType(node.object, "Identifier")) {
23478
+ if (getStaticMemberName$1(node) === "current" && isNodeOfType(node.object, "Identifier")) {
23479
23479
  const refBinding = getRef(analysis, node.object)?.resolved;
23480
23480
  const refDeclarator = useRefDeclarator;
23481
23481
  if (refBinding && refDeclarator && isNodeOfType(refDeclarator, "VariableDeclarator") && isNodeOfType(refDeclarator.init, "CallExpression")) {
@@ -23491,7 +23491,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23491
23491
  if (candidateReference.init) continue;
23492
23492
  const identifier = candidateReference.identifier;
23493
23493
  const member = identifier.parent;
23494
- if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName(member) !== "current") {
23494
+ if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName$1(member) !== "current") {
23495
23495
  evidence.hasUnknownSource = true;
23496
23496
  continue;
23497
23497
  }
@@ -23528,7 +23528,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23528
23528
  const callee = stripParenExpression(node.callee);
23529
23529
  const calleeRoot = getMemberRoot(callee);
23530
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) ?? "");
23531
+ const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName$1(callee) ?? "");
23532
23532
  if (isPureGlobalCall || isPureMemberTransform) {
23533
23533
  if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression")) mergeEvidence(evidence, collectValueEvidence(analysis, callee.object, frame, remainingCallFrames, new Set(visitedBindings)));
23534
23534
  for (const argument of node.arguments ?? []) {
@@ -23998,6 +23998,26 @@ const STATEFUL_HTML_DESCENDANT_TAGS = new Set([
23998
23998
  "dialog",
23999
23999
  "canvas"
24000
24000
  ]);
24001
+ const STATEFUL_HTML_ATTRIBUTE_NAMES = new Set([
24002
+ "autofocus",
24003
+ "contenteditable",
24004
+ "draggable",
24005
+ "tabindex"
24006
+ ]);
24007
+ const isStaticallyFalseAttributeValue = (attribute) => {
24008
+ if (!isNodeOfType(attribute, "JSXAttribute") || !attribute.value) return false;
24009
+ const value = isNodeOfType(attribute.value, "JSXExpressionContainer") ? attribute.value.expression : attribute.value;
24010
+ return isNodeOfType(value, "Literal") && (value.value === false || value.value === "false");
24011
+ };
24012
+ const hasStatefulHtmlAttribute = (openingElement) => {
24013
+ if (!isNodeOfType(openingElement, "JSXOpeningElement")) return false;
24014
+ return openingElement.attributes.some((attribute) => {
24015
+ if (!isNodeOfType(attribute, "JSXAttribute") || !isNodeOfType(attribute.name, "JSXIdentifier")) return false;
24016
+ const attributeName = attribute.name.name.toLowerCase();
24017
+ if (!STATEFUL_HTML_ATTRIBUTE_NAMES.has(attributeName)) return false;
24018
+ return attributeName === "tabindex" || !isStaticallyFalseAttributeValue(attribute);
24019
+ });
24020
+ };
24001
24021
  const STATEFUL_DESCENDANT_SCAN_BUDGET = 200;
24002
24022
  const rootIdentifierNameOf = (expression) => {
24003
24023
  let object = expression;
@@ -24015,7 +24035,8 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
24015
24035
  budget -= 1;
24016
24036
  const node = stack.pop();
24017
24037
  if (isNodeOfType(node, "JSXElement")) {
24018
- const name = node.openingElement.name;
24038
+ const opening = node.openingElement;
24039
+ const name = opening.name;
24019
24040
  if (name && isNodeOfType(name, "JSXIdentifier")) {
24020
24041
  const tagName = name.name;
24021
24042
  const firstChar = tagName.charCodeAt(0);
@@ -24023,6 +24044,7 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
24023
24044
  if (STATEFUL_HTML_DESCENDANT_TAGS.has(tagName)) return true;
24024
24045
  }
24025
24046
  if (name && isNodeOfType(name, "JSXMemberExpression")) return true;
24047
+ if (hasStatefulHtmlAttribute(opening)) return true;
24026
24048
  const children = node.children ?? [];
24027
24049
  for (const child of children) stack.push(child);
24028
24050
  continue;
@@ -24116,6 +24138,7 @@ const ARITHMETIC_KEY_OPERATORS = new Set([
24116
24138
  "/",
24117
24139
  "%"
24118
24140
  ]);
24141
+ const bindingMutationResultsByProgram = /* @__PURE__ */ new WeakMap();
24119
24142
  const extractCandidateIdentifiers = (expression) => {
24120
24143
  const node = stripParenExpression(expression);
24121
24144
  if (isNodeOfType(node, "Identifier")) return [node];
@@ -24156,6 +24179,13 @@ const isArrayFromCall = (node) => {
24156
24179
  const isBindingReassignedOrMutated = (referenceNode, bindingName) => {
24157
24180
  const programRoot = findProgramRoot(referenceNode);
24158
24181
  if (!programRoot) return false;
24182
+ let mutationResultsByBindingName = bindingMutationResultsByProgram.get(programRoot);
24183
+ if (!mutationResultsByBindingName) {
24184
+ mutationResultsByBindingName = /* @__PURE__ */ new Map();
24185
+ bindingMutationResultsByProgram.set(programRoot, mutationResultsByBindingName);
24186
+ }
24187
+ const cachedResult = mutationResultsByBindingName.get(bindingName);
24188
+ if (cachedResult !== void 0) return cachedResult;
24159
24189
  let didFindWrite = false;
24160
24190
  walkAst(programRoot, (child) => {
24161
24191
  if (didFindWrite) return false;
@@ -24172,6 +24202,7 @@ const isBindingReassignedOrMutated = (referenceNode, bindingName) => {
24172
24202
  return false;
24173
24203
  }
24174
24204
  });
24205
+ mutationResultsByBindingName.set(bindingName, didFindWrite);
24175
24206
  return didFindWrite;
24176
24207
  };
24177
24208
  /**
@@ -24573,6 +24604,14 @@ const templateHasOuterMemberIdentity = (template, bindingFunction) => {
24573
24604
  }
24574
24605
  return false;
24575
24606
  };
24607
+ const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
24608
+ const referencedItemNames = /* @__PURE__ */ new Set();
24609
+ for (const expression of template.expressions ?? []) {
24610
+ const unwrappedExpression = stripParenExpression(expression);
24611
+ if (isNodeOfType(unwrappedExpression, "Identifier") && itemNames.has(unwrappedExpression.name)) referencedItemNames.add(unwrappedExpression.name);
24612
+ }
24613
+ return referencedItemNames;
24614
+ };
24576
24615
  const forLoopTestReadsDataLength = (test) => {
24577
24616
  let didFindLengthRead = false;
24578
24617
  walkAst(test, (child) => {
@@ -24709,9 +24748,11 @@ const noArrayIndexAsKey = defineRule({
24709
24748
  } else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
24710
24749
  const jsxElement = openingElement.parent;
24711
24750
  if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
24751
+ const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
24752
+ const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET;
24712
24753
  if (!containsStatefulDescendant(jsxElement, {
24713
- memberRootNames: INLINE_TEXT_LEAF_TAGS.has(elementName.name) ? itemNames : EMPTY_NAME_SET,
24714
- bareIdentifierNames: derivedNames
24754
+ memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET,
24755
+ bareIdentifierNames: primitiveItemNames.size > 0 ? new Set([...derivedNames, ...primitiveItemNames]) : derivedNames
24715
24756
  })) return;
24716
24757
  }
24717
24758
  }
@@ -25272,10 +25313,10 @@ const declaresAnyDependency = (manifest) => DEPENDENCY_SECTION_NAMES.some((secti
25272
25313
  const section = manifest[sectionName];
25273
25314
  return typeof section === "object" && section !== null && Object.keys(section).length > 0;
25274
25315
  });
25275
- const declaresDependency = (manifest, dependencyName) => {
25276
- for (const declaredName of iterateDependencyNames(manifest)) if (declaredName === dependencyName) return true;
25277
- return false;
25278
- };
25316
+ const declaresDependency = (manifest, dependencyName) => DEPENDENCY_SECTION_NAMES.some((sectionName) => {
25317
+ const section = manifest[sectionName];
25318
+ return typeof section === "object" && section !== null && Object.prototype.propertyIsEnumerable.call(section, dependencyName);
25319
+ });
25279
25320
  const cachedPlatformByManifest = /* @__PURE__ */ new WeakMap();
25280
25321
  const classifyPackagePlatform = (filename) => {
25281
25322
  const manifest = readNearestPackageManifest(filename);
@@ -25522,25 +25563,23 @@ const isCallArgumentFunctionExpression = (node) => {
25522
25563
  return parent.arguments.some((argumentNode) => argumentNode === node);
25523
25564
  };
25524
25565
  const isNestedRenderEvidenceBoundary = (node) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isCallArgumentFunctionExpression(node);
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;
25566
+ const containsRenderOutput$1 = (rootNode, scopes) => {
25567
+ let hasRenderOutput = false;
25568
+ walkAst(rootNode, (node) => {
25569
+ if (hasRenderOutput) return false;
25570
+ if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
25571
+ if (node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS)) {
25572
+ hasRenderOutput = true;
25573
+ return false;
25574
+ }
25575
+ });
25576
+ return hasRenderOutput;
25538
25577
  };
25539
25578
  const renderOutputCache = /* @__PURE__ */ new WeakMap();
25540
25579
  const functionContainsReactRenderOutput = (functionNode, scopes) => {
25541
25580
  const cachedEntry = renderOutputCache.get(functionNode);
25542
25581
  if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
25543
- const hasRenderOutput = containsRenderOutput$1(functionNode, functionNode, scopes);
25582
+ const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
25544
25583
  renderOutputCache.set(functionNode, {
25545
25584
  scopes,
25546
25585
  hasRenderOutput
@@ -27346,7 +27385,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
27346
27385
  let ancestor = setStateCall.parent;
27347
27386
  while (ancestor) {
27348
27387
  if (!lifecycleMember) {
27349
- if (FUNCTION_NODE_TYPES$1.has(ancestor.type)) nestedFunctionCount += 1;
27388
+ if (FUNCTION_NODE_TYPES$1.has(ancestor.type) && (!isImmediatelyInvokedFunction(ancestor) || !("async" in ancestor) || ancestor.async === true)) nestedFunctionCount += 1;
27350
27389
  if (isLifecycleMember(ancestor, lifecycleNames)) lifecycleMember = ancestor;
27351
27390
  } else if (isEs5Component(ancestor) || isEs6Component(ancestor)) {
27352
27391
  if (nestedFunctionCount > 1 && !options.disallowInNestedFunctions) return false;
@@ -27548,7 +27587,7 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
27548
27587
  const body = lifecycleFunction.body;
27549
27588
  if (!body) return derivedNames;
27550
27589
  walkAst(body, (node) => {
27551
- if (FUNCTION_NODE_TYPES.has(node.type)) return false;
27590
+ if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
27552
27591
  if (!isNodeOfType(node, "VariableDeclarator")) return;
27553
27592
  const init = node.init;
27554
27593
  if (!init) return;
@@ -27558,6 +27597,67 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
27558
27597
  return derivedNames;
27559
27598
  };
27560
27599
  const isStatefulOperand = (node, paramNames, derivedNames) => referencesAnyName(node, paramNames) || referencesAnyName(node, derivedNames) || containsThisStateOrProps(node);
27600
+ const getStaticMemberName = (node) => {
27601
+ if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
27602
+ return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
27603
+ };
27604
+ const getThisStateFieldName = (node) => {
27605
+ const unwrappedNode = stripParenExpression(node);
27606
+ if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
27607
+ const object = stripParenExpression(unwrappedNode.object);
27608
+ if (!isNodeOfType(object, "MemberExpression") || !isNodeOfType(stripParenExpression(object.object), "ThisExpression") || getStaticMemberName(object) !== "state") return null;
27609
+ return getStaticMemberName(unwrappedNode);
27610
+ };
27611
+ const collectLocalInitializers = (lifecycleFunction) => {
27612
+ const initializers = /* @__PURE__ */ new Map();
27613
+ const body = lifecycleFunction.body;
27614
+ if (!body) return initializers;
27615
+ walkAst(body, (node) => {
27616
+ if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
27617
+ if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.init) initializers.set(node.id.name, node.init);
27618
+ });
27619
+ return initializers;
27620
+ };
27621
+ const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
27622
+ if (readsPostMountValue(node)) return true;
27623
+ const referencedNames = /* @__PURE__ */ new Set();
27624
+ collectReferenceIdentifierNames(node, referencedNames);
27625
+ for (const referencedName of referencedNames) {
27626
+ if (visitedNames.has(referencedName)) continue;
27627
+ const initializer = localInitializers.get(referencedName);
27628
+ if (!initializer) continue;
27629
+ if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
27630
+ }
27631
+ return false;
27632
+ };
27633
+ const getSetStateFieldValue = (setStateCall, fieldName) => {
27634
+ if (!isNodeOfType(setStateCall, "CallExpression")) return null;
27635
+ const argument = setStateCall.arguments?.[0];
27636
+ if (!argument || !isNodeOfType(argument, "ObjectExpression")) return null;
27637
+ for (const property of argument.properties ?? []) {
27638
+ if (!isNodeOfType(property, "Property") || property.computed === true) continue;
27639
+ 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;
27640
+ }
27641
+ return null;
27642
+ };
27643
+ const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
27644
+ let qualifies = false;
27645
+ walkAst(test, (node) => {
27646
+ if (qualifies) return false;
27647
+ if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
27648
+ const leftFieldName = getThisStateFieldName(node.left);
27649
+ const rightFieldName = getThisStateFieldName(node.right);
27650
+ const fieldName = leftFieldName ?? rightFieldName;
27651
+ const comparedValue = leftFieldName ? node.right : node.left;
27652
+ if (!fieldName || !leftFieldName && !rightFieldName) return;
27653
+ const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
27654
+ if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
27655
+ if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
27656
+ qualifies = true;
27657
+ return false;
27658
+ });
27659
+ return qualifies;
27660
+ };
27561
27661
  const isDiffGuardTest = (test, paramNames, derivedNames) => {
27562
27662
  if (referencesAnyName(test, paramNames)) return true;
27563
27663
  let qualifies = false;
@@ -27565,7 +27665,7 @@ const isDiffGuardTest = (test, paramNames, derivedNames) => {
27565
27665
  if (qualifies) return false;
27566
27666
  if (!isNodeOfType(node, "BinaryExpression")) return;
27567
27667
  if (!EQUALITY_OPERATORS.has(node.operator)) return;
27568
- if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames)) {
27668
+ if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames) && (referencesAnyName(node.left, derivedNames) || referencesAnyName(node.right, derivedNames))) {
27569
27669
  qualifies = true;
27570
27670
  return false;
27571
27671
  }
@@ -27578,11 +27678,12 @@ const isInsideDiffGuard = (setStateCall) => {
27578
27678
  const paramNames = /* @__PURE__ */ new Set();
27579
27679
  for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
27580
27680
  const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
27681
+ const localInitializers = collectLocalInitializers(lifecycleFunction);
27581
27682
  let child = setStateCall;
27582
27683
  let ancestor = setStateCall.parent;
27583
27684
  while (ancestor && ancestor !== lifecycleFunction) {
27584
27685
  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;
27585
- if (guardTest && isDiffGuardTest(guardTest, paramNames, derivedNames)) return true;
27686
+ if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers))) return true;
27586
27687
  child = ancestor;
27587
27688
  ancestor = ancestor.parent ?? null;
27588
27689
  }
@@ -27763,7 +27864,16 @@ const producesOpaqueInstanceValue = (expression) => {
27763
27864
  const collectSetterValueObservations = (componentBody, setterNames) => {
27764
27865
  const plainFedSetterNames = /* @__PURE__ */ new Set();
27765
27866
  const opaqueFedSetterNames = /* @__PURE__ */ new Set();
27867
+ const callbackRefSetterNames = /* @__PURE__ */ new Set();
27766
27868
  walkComponentRespectingShadows(componentBody, /* @__PURE__ */ new Set(), (node, currentlyShadowed) => {
27869
+ if (isNodeOfType(node, "JSXAttribute")) {
27870
+ const attributeName = node.name;
27871
+ if (isNodeOfType(attributeName, "JSXIdentifier") && attributeName.name === "ref" && node.value && isNodeOfType(node.value, "JSXExpressionContainer")) {
27872
+ const expression = stripParenExpression(node.value.expression);
27873
+ if (isNodeOfType(expression, "Identifier") && setterNames.has(expression.name)) callbackRefSetterNames.add(expression.name);
27874
+ }
27875
+ return;
27876
+ }
27767
27877
  if (!isNodeOfType(node, "CallExpression")) return;
27768
27878
  if (!isNodeOfType(node.callee, "Identifier")) return;
27769
27879
  const setterName = node.callee.name;
@@ -27780,21 +27890,10 @@ const collectSetterValueObservations = (componentBody, setterNames) => {
27780
27890
  }, true);
27781
27891
  return {
27782
27892
  plainFedSetterNames,
27783
- opaqueFedSetterNames
27893
+ opaqueFedSetterNames,
27894
+ callbackRefSetterNames
27784
27895
  };
27785
27896
  };
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
- };
27798
27897
  const collectFunctionLocalBindings = (functionNode) => {
27799
27898
  const localBindings = /* @__PURE__ */ new Set();
27800
27899
  if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return localBindings;
@@ -27806,8 +27905,8 @@ const collectFunctionLocalBindings = (functionNode) => {
27806
27905
  return localBindings;
27807
27906
  };
27808
27907
  const collectBlockScopedBindings = (node) => {
27809
- const blockBindings = /* @__PURE__ */ new Set();
27810
27908
  if (isNodeOfType(node, "BlockStatement")) {
27909
+ const blockBindings = /* @__PURE__ */ new Set();
27811
27910
  for (const statement of node.body ?? []) {
27812
27911
  if (!isNodeOfType(statement, "VariableDeclaration")) continue;
27813
27912
  for (const declarator of statement.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
@@ -27815,17 +27914,22 @@ const collectBlockScopedBindings = (node) => {
27815
27914
  return blockBindings;
27816
27915
  }
27817
27916
  if (isNodeOfType(node, "ForStatement") && isNodeOfType(node.init, "VariableDeclaration")) {
27917
+ const blockBindings = /* @__PURE__ */ new Set();
27818
27918
  for (const declarator of node.init.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
27819
27919
  return blockBindings;
27820
27920
  }
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;
27921
+ if ((isNodeOfType(node, "ForOfStatement") || isNodeOfType(node, "ForInStatement")) && isNodeOfType(node.left, "VariableDeclaration")) {
27922
+ const blockBindings = /* @__PURE__ */ new Set();
27923
+ for (const declarator of node.left.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
27924
+ return blockBindings;
27925
+ }
27926
+ return null;
27823
27927
  };
27824
27928
  const walkComponentRespectingShadows = (node, shadowedStateNames, visit, isComponentBodyRoot = false) => {
27825
27929
  if (!node || typeof node !== "object") return;
27826
27930
  let nextShadowedStateNames = shadowedStateNames;
27827
- const localBindings = isComponentBodyRoot ? /* @__PURE__ */ new Set() : isFunctionLike$1(node) ? collectFunctionLocalBindings(node) : collectBlockScopedBindings(node);
27828
- if (localBindings.size > 0) {
27931
+ const localBindings = isComponentBodyRoot ? null : isFunctionLike$1(node) ? collectFunctionLocalBindings(node) : collectBlockScopedBindings(node);
27932
+ if (localBindings && localBindings.size > 0) {
27829
27933
  const merged = new Set(shadowedStateNames);
27830
27934
  for (const localName of localBindings) merged.add(localName);
27831
27935
  nextShadowedStateNames = merged;
@@ -27851,11 +27955,10 @@ const noDirectStateMutation = defineRule({
27851
27955
  const bindings = collectUseStateBindings(componentBody);
27852
27956
  if (bindings.length === 0) return;
27853
27957
  const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
27854
- const callbackRefSetterNames = collectCallbackRefSetterNames(componentBody);
27855
27958
  const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
27856
27959
  const plainObjectStateValueNames = /* @__PURE__ */ new Set();
27857
27960
  for (const binding of bindings) {
27858
- if (callbackRefSetterNames.has(binding.setterName)) continue;
27961
+ if (setterValueObservations.callbackRefSetterNames.has(binding.setterName)) continue;
27859
27962
  if (!isNodeOfType(binding.declarator.init, "CallExpression")) continue;
27860
27963
  const initializerArgument = binding.declarator.init.arguments?.[0];
27861
27964
  if (!initializerMarksPlainState(initializerArgument)) continue;
@@ -32573,25 +32676,60 @@ const resolveSettings$16 = (settings) => {
32573
32676
  };
32574
32677
  const isHocCall = (call, scopes) => {
32575
32678
  if (!isNodeOfType(call, "CallExpression")) return false;
32576
- const calleeName = flattenCalleeName(call.callee);
32577
- if (calleeName && REACT_HOC_NAMES.has(calleeName)) return true;
32679
+ if (isReactApiCall(call, REACT_HOC_NAMES, scopes, {
32680
+ allowGlobalReactNamespace: true,
32681
+ allowUnboundBareCalls: true
32682
+ })) return true;
32683
+ if (isReactHocMemberReference(call.callee, scopes)) return true;
32578
32684
  if (!isNodeOfType(call.callee, "Identifier")) return false;
32579
32685
  const symbol = scopes.symbolFor(call.callee);
32580
32686
  if (!symbol) return false;
32581
- return symbolMapsToHoc(symbol);
32687
+ return symbolMapsToHoc(symbol, scopes, /* @__PURE__ */ new Set());
32688
+ };
32689
+ const isReactImportEquals = (symbol) => {
32690
+ if (symbol.kind !== "ts-import-equals" || !isNodeOfType(symbol.declarationNode, "TSImportEqualsDeclaration")) return false;
32691
+ const moduleReference = symbol.declarationNode.moduleReference;
32692
+ return Boolean(isNodeOfType(moduleReference, "TSExternalModuleReference") && isNodeOfType(moduleReference.expression, "Literal") && typeof moduleReference.expression.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(moduleReference.expression.value));
32693
+ };
32694
+ const isRequireReactCall$1 = (node, scopes) => {
32695
+ if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier") || node.callee.name !== "require" || !scopes.isGlobalReference(node.callee)) return false;
32696
+ const moduleSpecifier = node.arguments[0];
32697
+ return Boolean(moduleSpecifier && isNodeOfType(moduleSpecifier, "Literal") && typeof moduleSpecifier.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(moduleSpecifier.value));
32698
+ };
32699
+ const isReactNamespaceExpression = (node, scopes) => {
32700
+ if (isRequireReactCall$1(node, scopes)) return true;
32701
+ if (!isNodeOfType(node, "Identifier")) return false;
32702
+ const symbol = scopes.symbolFor(node);
32703
+ if (!symbol) return node.name === "React" && scopes.isGlobalReference(node);
32704
+ if (symbol.initializer && isRequireReactCall$1(symbol.initializer, scopes)) return true;
32705
+ if (isReactImportEquals(symbol)) return true;
32706
+ return isReactNamespaceImport(node, scopes);
32707
+ };
32708
+ 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));
32709
+ const getDestructuredPropertyName$1 = (symbol) => {
32710
+ const property = symbol.bindingIdentifier.parent;
32711
+ if (!property || !isNodeOfType(property, "Property") || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
32712
+ if (isNodeOfType(property.key, "Identifier") && !property.computed) return property.key.name;
32713
+ if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
32714
+ return null;
32582
32715
  };
32583
- const symbolMapsToHoc = (symbol) => {
32584
- if (REACT_HOC_NAMES.has(symbol.name)) {
32585
- if (symbol.kind === "import") return true;
32716
+ const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
32717
+ if (visitedSymbolIds.has(symbol.id)) return false;
32718
+ visitedSymbolIds.add(symbol.id);
32719
+ if (symbol.kind === "import") {
32720
+ const importedName = getImportedName(symbol.declarationNode);
32721
+ return Boolean(isImportedFromReact(symbol) && importedName && REACT_HOC_NAMES.has(importedName));
32586
32722
  }
32587
32723
  const init = symbol.initializer;
32588
32724
  if (!init) return false;
32589
- if (isNodeOfType(init, "MemberExpression")) {
32590
- const flat = flattenCalleeName(init);
32591
- if (flat && REACT_HOC_NAMES.has(flat)) return true;
32725
+ const destructuredPropertyName = getDestructuredPropertyName$1(symbol);
32726
+ if (destructuredPropertyName && REACT_HOC_NAMES.has(destructuredPropertyName) && isReactNamespaceExpression(init, scopes)) return true;
32727
+ if (isReactHocMemberReference(init, scopes)) return true;
32728
+ if (isNodeOfType(init, "Identifier")) {
32729
+ const initializedFromSymbol = scopes.symbolFor(init);
32730
+ if (initializedFromSymbol) return symbolMapsToHoc(initializedFromSymbol, scopes, visitedSymbolIds);
32731
+ return REACT_HOC_NAMES.has(init.name) && scopes.isGlobalReference(init);
32592
32732
  }
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;
32595
32733
  return false;
32596
32734
  };
32597
32735
  const isTrivialPassthroughChild = (child) => {
@@ -32649,26 +32787,14 @@ const isHocComponent = (call, scopes) => {
32649
32787
  };
32650
32788
  const containsJsx = (root) => {
32651
32789
  let found = false;
32652
- const visit = (node) => {
32653
- if (found) return;
32790
+ walkAst(root, (node) => {
32791
+ if (found) return false;
32654
32792
  if (node.type === "JSXElement" || node.type === "JSXFragment") {
32655
32793
  found = true;
32656
- return;
32657
- }
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;
32794
+ return false;
32669
32795
  }
32670
- };
32671
- visit(root);
32796
+ if (node !== root && (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "ClassDeclaration" || node.type === "ClassExpression")) return false;
32797
+ });
32672
32798
  return found;
32673
32799
  };
32674
32800
  const expressionContainsJsx = (expression) => {
@@ -32692,7 +32818,7 @@ const unwrapTsCast = (expression) => {
32692
32818
  while (current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSNonNullExpression") current = stripParenExpression(current.expression);
32693
32819
  return current;
32694
32820
  };
32695
- const collectReExportedNames = (program) => {
32821
+ const collectReExportedNames = (program, scopes) => {
32696
32822
  const names = /* @__PURE__ */ new Set();
32697
32823
  if (!isNodeOfType(program, "Program")) return names;
32698
32824
  for (const statement of program.body) {
@@ -32716,8 +32842,7 @@ const collectReExportedNames = (program) => {
32716
32842
  if (!declarator.init) continue;
32717
32843
  const init = unwrapTsCast(declarator.init);
32718
32844
  if (isNodeOfType(init, "CallExpression")) {
32719
- const calleeName = flattenCalleeName(init.callee);
32720
- if (calleeName && REACT_HOC_NAMES.has(calleeName)) {
32845
+ if (isHocCall(init, scopes)) {
32721
32846
  const wrappedArg = init.arguments[0];
32722
32847
  if (wrappedArg && isNodeOfType(wrappedArg, "Identifier")) names.add(wrappedArg.name);
32723
32848
  }
@@ -32786,16 +32911,7 @@ const recordComponent = (context, name, reportNode, isStateless) => {
32786
32911
  isStateless
32787
32912
  });
32788
32913
  };
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
- };
32914
+ const walkChildren = (node, context) => forEachChildNode(node, context.visitChild);
32799
32915
  const walkComponentSearch = (node, context) => {
32800
32916
  if (isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) {
32801
32917
  if (isEs6Component(node)) {
@@ -32897,12 +33013,13 @@ const noMultiComp = defineRule({
32897
33013
  components: [],
32898
33014
  componentDepth: 0,
32899
33015
  currentVarName: null,
32900
- scopes: context.scopes
33016
+ scopes: context.scopes,
33017
+ visitChild: (child) => walkComponentSearch(child, visitContext)
32901
33018
  };
32902
33019
  for (const statement of node.body) walkComponentSearch(statement, visitContext);
32903
33020
  const flagged = settings.ignoreStateless ? visitContext.components.filter((component) => !component.isStateless) : visitContext.components;
32904
33021
  if (flagged.length <= 2) return;
32905
- const reExportedNames = collectReExportedNames(node);
33022
+ const reExportedNames = collectReExportedNames(node, context.scopes);
32906
33023
  const exportedCount = flagged.filter((component) => isExportedDeclaration(component.reportNode, reExportedNames)).length;
32907
33024
  if (exportedCount >= flagged.length || flagged.length >= 4 && exportedCount >= Math.floor(flagged.length * .7) || flagged.length >= 8 && exportedCount >= Math.floor(flagged.length * .5)) return;
32908
33025
  const isSmallFeatureModule = exportedCount > 0 && exportedCount <= 2 && exportedCount < flagged.length;
@@ -35162,6 +35279,27 @@ const hasPreviousValueDep = (effectNode, depElements) => {
35162
35279
  }
35163
35280
  return false;
35164
35281
  };
35282
+ const isStateLikeDependency = (analysis, element, isPropName) => {
35283
+ if (!isNodeOfType(element, "Identifier") || isPropName(element.name)) return false;
35284
+ if (!analysis) return true;
35285
+ const reference = getRef(analysis, element);
35286
+ if (!reference) return true;
35287
+ const upstreamReferences = getUpstreamRefs(analysis, reference);
35288
+ if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
35289
+ return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
35290
+ };
35291
+ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
35292
+ const callee = stripParenExpression(callExpression.callee);
35293
+ if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "current") return null;
35294
+ const receiver = stripParenExpression(callee.object);
35295
+ if (!isNodeOfType(receiver, "Identifier")) return null;
35296
+ const binding = findVariableInitializer(callExpression, receiver.name);
35297
+ if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
35298
+ if (getCalleeName$2(binding.initializer) !== "useRef") return null;
35299
+ const callbackArgument = binding.initializer.arguments?.[0];
35300
+ if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
35301
+ return isPropName(callbackArgument.name) ? callbackArgument.name : null;
35302
+ };
35165
35303
  const noPropCallbackInEffect = defineRule({
35166
35304
  id: "no-prop-callback-in-effect",
35167
35305
  title: "Parent kept in sync with a callback effect",
@@ -35178,11 +35316,11 @@ const noPropCallbackInEffect = defineRule({
35178
35316
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
35179
35317
  const depsNode = node.arguments[1];
35180
35318
  if (!isNodeOfType(depsNode, "ArrayExpression") || !depsNode.elements?.length) return;
35181
- const stateLikeDeps = (depsNode.elements ?? []).filter((element) => isNodeOfType(element, "Identifier") && !propStackTracker.isPropName(element.name));
35319
+ const analysis = getProgramAnalysis(node);
35320
+ const stateLikeDeps = (depsNode.elements ?? []).filter((element) => element && isStateLikeDependency(analysis, element, propStackTracker.isPropName));
35182
35321
  if (stateLikeDeps.length === 0) return;
35183
35322
  if (isRefLatchGuardedEffect(callback.body)) return;
35184
35323
  if (hasPreviousValueDep(node, depsNode.elements ?? [])) return;
35185
- const analysis = getProgramAnalysis(node);
35186
35324
  if (analysis) {
35187
35325
  const stateLikeDepRefs = [];
35188
35326
  for (const element of stateLikeDeps) {
@@ -35194,9 +35332,9 @@ const noPropCallbackInEffect = defineRule({
35194
35332
  const reportedNodes = /* @__PURE__ */ new Set();
35195
35333
  walkInsideStatementBlocks(callback.body, (child) => {
35196
35334
  if (!isNodeOfType(child, "CallExpression")) return;
35197
- if (!isNodeOfType(child.callee, "Identifier")) return;
35198
- const calleeName = child.callee.name;
35199
- if (!propStackTracker.isPropName(calleeName)) return;
35335
+ const directCallee = stripParenExpression(child.callee);
35336
+ const calleeName = isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName);
35337
+ if (!calleeName) return;
35200
35338
  if (!isResultDiscardedCall(child)) return;
35201
35339
  if (reportedNodes.has(child)) return;
35202
35340
  reportedNodes.add(child);
@@ -39370,34 +39508,18 @@ const resolveSettings$8 = (settings) => {
39370
39508
  };
39371
39509
  const expressionContainsJsxOrCreateElement = (root) => {
39372
39510
  let found = false;
39373
- const visit = (node) => {
39374
- if (found) return;
39511
+ walkAst(root, (node) => {
39512
+ if (found) return false;
39513
+ if (node !== root && NESTED_FUNCTION_TYPES.has(node.type)) return false;
39375
39514
  if (node.type === "JSXElement" || node.type === "JSXFragment") {
39376
39515
  found = true;
39377
- return;
39516
+ return false;
39378
39517
  }
39379
39518
  if (isNodeOfType(node, "CallExpression") && isCreateElementCall(node)) {
39380
39519
  found = true;
39381
- return;
39382
- }
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;
39520
+ return false;
39398
39521
  }
39399
- };
39400
- visit(root);
39522
+ });
39401
39523
  return found;
39402
39524
  };
39403
39525
  const isReactClassComponent = (classNode) => {
@@ -40323,19 +40445,6 @@ const classifyExport = (name, reportNode, isFunction, initializer, state) => {
40323
40445
  reportNode
40324
40446
  };
40325
40447
  };
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
- };
40339
40448
  const isEntryPointFile = (filename) => {
40340
40449
  const lastSlash = Math.max(filename.lastIndexOf("/"), filename.lastIndexOf("\\"));
40341
40450
  const basename = lastSlash === -1 ? filename : filename.slice(lastSlash + 1);
@@ -40374,197 +40483,212 @@ const onlyExportComponents = defineRule({
40374
40483
  category: "Architecture",
40375
40484
  create: (context) => {
40376
40485
  const settings = resolveSettings$6(context.settings);
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);
40391
- }
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);
40486
+ if (!isFileNameAllowed(normalizeFilename(context.filename ?? ""), settings.checkJS)) return {};
40487
+ const exportNodes = [];
40488
+ const componentCandidates = [];
40489
+ const pushExportNode = (node) => {
40490
+ exportNodes.push(node);
40491
+ };
40492
+ const pushComponentCandidate = (node) => {
40493
+ componentCandidates.push(node);
40494
+ };
40495
+ return {
40496
+ ExportAllDeclaration: pushExportNode,
40497
+ ExportDefaultDeclaration: pushExportNode,
40498
+ ExportNamedDeclaration: pushExportNode,
40499
+ FunctionDeclaration: pushComponentCandidate,
40500
+ VariableDeclarator: pushComponentCandidate,
40501
+ ClassDeclaration: pushComponentCandidate,
40502
+ "Program:exit"() {
40503
+ const localComponentNames = /* @__PURE__ */ new Set();
40504
+ const state = {
40505
+ customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
40506
+ allowExportNames: new Set(settings.allowExportNames),
40507
+ allowConstantExport: settings.allowConstantExport,
40508
+ localComponentNames,
40509
+ scopes: context.scopes
40510
+ };
40511
+ for (const child of componentCandidates) {
40512
+ if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
40513
+ if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) && functionContainsReactRenderOutput(child, context.scopes)) localComponentNames.add(child.id.name);
40514
+ }
40515
+ if (isNodeOfType(child, "ClassDeclaration") && child.id) {
40516
+ if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
40517
+ }
40518
+ if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
40519
+ const initializer = child.init;
40520
+ if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) {
40521
+ const expression = initializer ? skipTsExpression(initializer) : null;
40522
+ if (!(expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"))) || functionContainsReactRenderOutput(expression, context.scopes)) localComponentNames.add(child.id.name);
40523
+ }
40400
40524
  }
40401
40525
  }
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({
40526
+ const exports = [];
40527
+ let hasReactExport = false;
40528
+ let hasAnyExports = false;
40529
+ const isExportedNodeIds = /* @__PURE__ */ new WeakSet();
40530
+ for (const child of exportNodes) {
40531
+ if (isNodeOfType(child, "ExportAllDeclaration")) {
40532
+ if (child.exportKind === "type") continue;
40533
+ hasAnyExports = true;
40534
+ context.report({
40535
+ node: child,
40536
+ message: EXPORT_ALL_MESSAGE
40537
+ });
40538
+ continue;
40539
+ }
40540
+ if (isNodeOfType(child, "ExportDefaultDeclaration")) {
40541
+ hasAnyExports = true;
40542
+ const declaration = child.declaration;
40543
+ const stripped = skipTsExpression(declaration);
40544
+ if (isNodeOfType(stripped, "FunctionDeclaration") || isNodeOfType(stripped, "FunctionExpression")) {
40545
+ if (stripped.id) {
40546
+ const idNode = stripped.id;
40547
+ isExportedNodeIds.add(stripped);
40548
+ exports.push(classifyExport(idNode.name, idNode, true, null, state));
40549
+ } else {
40550
+ context.report({
40551
+ node: stripped,
40552
+ message: ANONYMOUS_MESSAGE
40553
+ });
40554
+ hasReactExport = true;
40555
+ }
40556
+ continue;
40557
+ }
40558
+ if (isNodeOfType(stripped, "ClassDeclaration") || isNodeOfType(stripped, "ClassExpression")) {
40559
+ if (stripped.id) {
40560
+ const idNode = stripped.id;
40561
+ isExportedNodeIds.add(stripped);
40562
+ if (isReactComponentName(idNode.name) && isEs6Component(stripped)) hasReactExport = true;
40563
+ else exports.push({
40564
+ kind: "non-component",
40565
+ reportNode: idNode
40566
+ });
40567
+ } else context.report({
40428
40568
  node: stripped,
40429
40569
  message: ANONYMOUS_MESSAGE
40430
40570
  });
40431
- hasReactExport = true;
40571
+ continue;
40432
40572
  }
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({
40573
+ if (isNodeOfType(stripped, "Identifier")) {
40574
+ exports.push(classifyExport(stripped.name, stripped, false, null, state));
40575
+ continue;
40576
+ }
40577
+ if (isNodeOfType(stripped, "CallExpression")) {
40578
+ if (isRouteFactoryCall(stripped)) {
40579
+ hasReactExport = true;
40580
+ continue;
40581
+ }
40582
+ const isHoc = isHocCallee(stripped.callee, state);
40583
+ const firstArg = stripped.arguments[0];
40584
+ const firstArgIsValid = Boolean(firstArg) && (() => {
40585
+ if (!firstArg) return false;
40586
+ const expression = skipTsExpression(firstArg);
40587
+ if (isNodeOfType(expression, "Identifier")) return true;
40588
+ if (isNodeOfType(expression, "FunctionExpression") && expression.id) return true;
40589
+ if (isNodeOfType(expression, "CallExpression") && isHocCallee(expression.callee, state)) return true;
40590
+ return false;
40591
+ })();
40592
+ if (isHoc && firstArgIsValid) hasReactExport = true;
40593
+ else if (!isHoc && isConfigOnlyFactoryCall(stripped)) exports.push({
40441
40594
  kind: "non-component",
40442
- reportNode: idNode
40595
+ reportNode: stripped
40596
+ });
40597
+ else context.report({
40598
+ node: stripped,
40599
+ message: ANONYMOUS_MESSAGE
40600
+ });
40601
+ continue;
40602
+ }
40603
+ if (isNodeOfType(stripped, "ObjectExpression")) {
40604
+ context.report({
40605
+ node: stripped,
40606
+ message: objectExpressionBundlesComponents(stripped, state) ? NAMESPACE_OBJECT_MESSAGE : ANONYMOUS_MESSAGE
40607
+ });
40608
+ continue;
40609
+ }
40610
+ if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "Literal")) {
40611
+ context.report({
40612
+ node: stripped,
40613
+ message: ANONYMOUS_MESSAGE
40443
40614
  });
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;
40457
40615
  continue;
40458
40616
  }
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")) {
40488
40617
  context.report({
40489
- node: stripped,
40618
+ node: child,
40490
40619
  message: ANONYMOUS_MESSAGE
40491
40620
  });
40492
40621
  continue;
40493
40622
  }
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));
40520
- }
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
- });
40623
+ if (isNodeOfType(child, "ExportNamedDeclaration")) {
40624
+ if (child.exportKind === "type") continue;
40625
+ hasAnyExports = true;
40626
+ if (child.declaration) {
40627
+ const declaration = child.declaration;
40628
+ if (isNodeOfType(declaration, "FunctionDeclaration") && declaration.id) {
40629
+ isExportedNodeIds.add(declaration);
40630
+ exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state));
40631
+ } else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
40632
+ isExportedNodeIds.add(declaration);
40633
+ if (isReactComponentName(declaration.id.name) && isEs6Component(declaration)) exports.push({ kind: "react-component" });
40634
+ else exports.push({
40635
+ kind: "non-component",
40636
+ reportNode: declaration.id
40637
+ });
40638
+ } else if (isNodeOfType(declaration, "VariableDeclaration")) for (const declarator of declaration.declarations) {
40639
+ if (!isNodeOfType(declarator.id, "Identifier")) continue;
40640
+ isExportedNodeIds.add(declarator);
40641
+ const isFunction = canBeReactFunctionComponent(declarator.init ?? null, state);
40642
+ exports.push(classifyExport(declarator.id.name, declarator.id, isFunction, declarator.init, state));
40643
+ }
40644
+ else if (declaration.type === "TSEnumDeclaration" || declaration.type === "TSInterfaceDeclaration" || declaration.type === "TSTypeAliasDeclaration") {
40645
+ if (declaration.type === "TSEnumDeclaration") exports.push({
40646
+ kind: "non-component",
40647
+ reportNode: declaration
40648
+ });
40649
+ }
40526
40650
  }
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" });
40651
+ const isReExportFromSource = Boolean(child.source);
40652
+ for (const specifier of child.specifiers ?? []) {
40653
+ if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
40654
+ const exported = specifier.exported;
40655
+ const local = specifier.local;
40656
+ let exportedName = null;
40657
+ if (exported && isNodeOfType(exported, "Identifier")) exportedName = exported.name;
40658
+ const localName = local && isNodeOfType(local, "Identifier") ? local.name : null;
40659
+ const reportNode = specifier;
40660
+ let entry;
40661
+ if (exportedName === "default" && localName) entry = classifyExport(localName, reportNode, false, null, state);
40662
+ else if (exportedName) entry = classifyExport(exportedName, reportNode, false, null, state);
40663
+ else {
40664
+ entry = {
40665
+ kind: "non-component",
40666
+ reportNode
40667
+ };
40668
+ if (localName && isReactComponentName(localName)) exports.push({ kind: "react-component" });
40669
+ }
40670
+ if (isReExportFromSource && entry.kind !== "react-component") continue;
40671
+ exports.push(entry);
40546
40672
  }
40547
- if (isReExportFromSource && entry.kind !== "react-component") continue;
40548
- exports.push(entry);
40549
40673
  }
40550
40674
  }
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({
40675
+ for (const entry of exports) if (entry.kind === "react-component") hasReactExport = true;
40676
+ for (const entry of exports) if (entry.kind === "namespace-object") context.report({
40559
40677
  node: entry.reportNode,
40560
- message: NAMED_EXPORT_MESSAGE
40561
- });
40562
- if (entry.kind === "react-context") context.report({
40563
- node: entry.reportNode,
40564
- message: REACT_CONTEXT_MESSAGE
40678
+ message: NAMESPACE_OBJECT_MESSAGE
40565
40679
  });
40680
+ if (hasAnyExports && hasReactExport) for (const entry of exports) {
40681
+ if (entry.kind === "non-component") context.report({
40682
+ node: entry.reportNode,
40683
+ message: NAMED_EXPORT_MESSAGE
40684
+ });
40685
+ if (entry.kind === "react-context") context.report({
40686
+ node: entry.reportNode,
40687
+ message: REACT_CONTEXT_MESSAGE
40688
+ });
40689
+ }
40566
40690
  }
40567
- } };
40691
+ };
40568
40692
  }
40569
40693
  });
40570
40694
  //#endregion
@@ -40666,7 +40790,12 @@ const postmessageOriginRisk = defineRule({
40666
40790
  scan: (file) => {
40667
40791
  if (!isProductionSourcePath(file.relativePath)) return [];
40668
40792
  if (WORKER_FILE_PATH_PATTERN.test(file.relativePath)) return [];
40669
- const ast = parseSourceText(file.absolutePath, file.content);
40793
+ if (!file.content.includes("addEventListener") && !file.content.includes("onmessage")) return [];
40794
+ const ast = parseSourceText({
40795
+ filename: file.absolutePath,
40796
+ sourceText: file.content,
40797
+ shouldAttachParentReferences: false
40798
+ });
40670
40799
  if (ast === null) return [];
40671
40800
  const findings = [];
40672
40801
  walkAst(ast, (node) => {
@@ -43046,15 +43175,6 @@ const isStableHookWrapperArgument = (node) => {
43046
43175
  const parent = node.parent;
43047
43176
  return isNodeOfType(parent, "CallExpression") && isNodeOfType(parent.callee, "Identifier") && STABLE_HOOK_WRAPPERS.has(parent.callee.name) && Boolean(parent.arguments?.some((argument) => argument === node));
43048
43177
  };
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
- };
43058
43178
  const queryStableQueryClient = defineRule({
43059
43179
  id: "query-stable-query-client",
43060
43180
  title: "Unstable QueryClient in component",
@@ -59553,7 +59673,10 @@ const appendNode = (builder, block, node) => {
59553
59673
  };
59554
59674
  const mapDescendantsToBlock = (builder, node, block) => {
59555
59675
  builder.nodeBlock.set(node, block);
59556
- if (isFunctionLike$1(node)) return;
59676
+ if (isFunctionLike$1(node)) {
59677
+ builder.nestedFunctions.push(node);
59678
+ return;
59679
+ }
59557
59680
  const record = node;
59558
59681
  for (const key of Object.keys(record)) {
59559
59682
  if (key === "parent") continue;
@@ -59787,11 +59910,12 @@ const buildStatement = (builder, statement, current) => {
59787
59910
  mapDescendantsToBlock(builder, statement, current);
59788
59911
  return current;
59789
59912
  };
59790
- const buildFunctionCfg = (functionNode, body) => {
59913
+ const buildFunctionCfg = (functionNode, body, nestedFunctionSink) => {
59791
59914
  const builder = {
59792
59915
  blocks: [],
59793
59916
  entry: null,
59794
59917
  exit: null,
59918
+ nestedFunctions: nestedFunctionSink,
59795
59919
  nodeBlock: /* @__PURE__ */ new Map(),
59796
59920
  loopStack: [],
59797
59921
  switchStack: [],
@@ -59809,13 +59933,12 @@ const buildFunctionCfg = (functionNode, body) => {
59809
59933
  bodyEnd = entry;
59810
59934
  }
59811
59935
  addEdge(bodyEnd, exit, "uncond");
59812
- const blockOf = (node) => builder.nodeBlock.get(node) ?? null;
59813
59936
  return {
59814
59937
  owner: functionNode,
59815
59938
  entry,
59816
59939
  exit,
59817
59940
  blocks: builder.blocks,
59818
- blockOf
59941
+ blockOf: (node) => builder.nodeBlock.get(node) ?? null
59819
59942
  };
59820
59943
  };
59821
59944
  const computeUnconditionalSet = (cfg) => {
@@ -59852,8 +59975,9 @@ const computeUnconditionalSet = (cfg) => {
59852
59975
  const analyzeControlFlow = (program) => {
59853
59976
  nextBlockId = 0;
59854
59977
  const functionCfgs = /* @__PURE__ */ new Map();
59978
+ const pendingFunctions = [];
59855
59979
  const buildFor = (functionNode, body) => {
59856
- const cfg = buildFunctionCfg(functionNode, body);
59980
+ const cfg = buildFunctionCfg(functionNode, body, pendingFunctions);
59857
59981
  functionCfgs.set(functionNode, {
59858
59982
  cfg,
59859
59983
  unconditionalSet: computeUnconditionalSet(cfg)
@@ -59863,21 +59987,18 @@ const analyzeControlFlow = (program) => {
59863
59987
  type: "BlockStatement",
59864
59988
  body: program.body
59865
59989
  });
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
- }
59990
+ for (let functionIndex = 0; functionIndex < pendingFunctions.length; functionIndex += 1) {
59991
+ const functionNode = pendingFunctions[functionIndex];
59992
+ if (!isFunctionLike$1(functionNode) || !functionNode.body || functionCfgs.has(functionNode)) continue;
59993
+ buildFor(functionNode, functionNode.body);
59994
+ }
59995
+ const getFunctionEntry = (functionNode) => {
59996
+ const existingEntry = functionCfgs.get(functionNode);
59997
+ if (existingEntry) return existingEntry;
59998
+ if (!isFunctionLike$1(functionNode) || !functionNode.body) return null;
59999
+ buildFor(functionNode, functionNode.body);
60000
+ return functionCfgs.get(functionNode) ?? null;
59879
60001
  };
59880
- visit(program);
59881
60002
  const enclosingFunction = (node) => {
59882
60003
  let current = node;
59883
60004
  while (current) {
@@ -59888,12 +60009,12 @@ const analyzeControlFlow = (program) => {
59888
60009
  return null;
59889
60010
  };
59890
60011
  const cfgFor = (functionLike) => {
59891
- return functionCfgs.get(functionLike)?.cfg ?? null;
60012
+ return getFunctionEntry(functionLike)?.cfg ?? null;
59892
60013
  };
59893
60014
  const isUnconditionalFromEntry = (node) => {
59894
60015
  const owner = enclosingFunction(node);
59895
60016
  if (!owner) return true;
59896
- const entry = functionCfgs.get(owner);
60017
+ const entry = getFunctionEntry(owner);
59897
60018
  if (!entry) return true;
59898
60019
  const block = entry.cfg.blockOf(node);
59899
60020
  if (!block) return true;
@@ -59967,17 +60088,14 @@ const wrapWithSemanticContext = (rule) => ({
59967
60088
  }
59968
60089
  };
59969
60090
  const visitors = rule.create(enrichedContext);
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;
60091
+ const innerProgramHandler = visitors.Program;
60092
+ return {
60093
+ ...visitors,
60094
+ Program: ((node) => {
60095
+ programRoot = node;
60096
+ if (innerProgramHandler) innerProgramHandler(node);
60097
+ })
60098
+ };
59981
60099
  }
59982
60100
  });
59983
60101
  //#endregion