oxlint-plugin-react-doctor 0.7.5-dev.0f07133 → 0.7.5-dev.18e8717

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 +550 -485
  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 ?? []) {
@@ -24116,6 +24116,7 @@ const ARITHMETIC_KEY_OPERATORS = new Set([
24116
24116
  "/",
24117
24117
  "%"
24118
24118
  ]);
24119
+ const bindingMutationResultsByProgram = /* @__PURE__ */ new WeakMap();
24119
24120
  const extractCandidateIdentifiers = (expression) => {
24120
24121
  const node = stripParenExpression(expression);
24121
24122
  if (isNodeOfType(node, "Identifier")) return [node];
@@ -24156,6 +24157,13 @@ const isArrayFromCall = (node) => {
24156
24157
  const isBindingReassignedOrMutated = (referenceNode, bindingName) => {
24157
24158
  const programRoot = findProgramRoot(referenceNode);
24158
24159
  if (!programRoot) return false;
24160
+ let mutationResultsByBindingName = bindingMutationResultsByProgram.get(programRoot);
24161
+ if (!mutationResultsByBindingName) {
24162
+ mutationResultsByBindingName = /* @__PURE__ */ new Map();
24163
+ bindingMutationResultsByProgram.set(programRoot, mutationResultsByBindingName);
24164
+ }
24165
+ const cachedResult = mutationResultsByBindingName.get(bindingName);
24166
+ if (cachedResult !== void 0) return cachedResult;
24159
24167
  let didFindWrite = false;
24160
24168
  walkAst(programRoot, (child) => {
24161
24169
  if (didFindWrite) return false;
@@ -24172,6 +24180,7 @@ const isBindingReassignedOrMutated = (referenceNode, bindingName) => {
24172
24180
  return false;
24173
24181
  }
24174
24182
  });
24183
+ mutationResultsByBindingName.set(bindingName, didFindWrite);
24175
24184
  return didFindWrite;
24176
24185
  };
24177
24186
  /**
@@ -25272,10 +25281,10 @@ const declaresAnyDependency = (manifest) => DEPENDENCY_SECTION_NAMES.some((secti
25272
25281
  const section = manifest[sectionName];
25273
25282
  return typeof section === "object" && section !== null && Object.keys(section).length > 0;
25274
25283
  });
25275
- const declaresDependency = (manifest, dependencyName) => {
25276
- for (const declaredName of iterateDependencyNames(manifest)) if (declaredName === dependencyName) return true;
25277
- return false;
25278
- };
25284
+ const declaresDependency = (manifest, dependencyName) => DEPENDENCY_SECTION_NAMES.some((sectionName) => {
25285
+ const section = manifest[sectionName];
25286
+ return typeof section === "object" && section !== null && Object.prototype.propertyIsEnumerable.call(section, dependencyName);
25287
+ });
25279
25288
  const cachedPlatformByManifest = /* @__PURE__ */ new WeakMap();
25280
25289
  const classifyPackagePlatform = (filename) => {
25281
25290
  const manifest = readNearestPackageManifest(filename);
@@ -25522,25 +25531,23 @@ const isCallArgumentFunctionExpression = (node) => {
25522
25531
  return parent.arguments.some((argumentNode) => argumentNode === node);
25523
25532
  };
25524
25533
  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;
25534
+ const containsRenderOutput$1 = (rootNode, scopes) => {
25535
+ let hasRenderOutput = false;
25536
+ walkAst(rootNode, (node) => {
25537
+ if (hasRenderOutput) return false;
25538
+ if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
25539
+ if (node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS)) {
25540
+ hasRenderOutput = true;
25541
+ return false;
25542
+ }
25543
+ });
25544
+ return hasRenderOutput;
25538
25545
  };
25539
25546
  const renderOutputCache = /* @__PURE__ */ new WeakMap();
25540
25547
  const functionContainsReactRenderOutput = (functionNode, scopes) => {
25541
25548
  const cachedEntry = renderOutputCache.get(functionNode);
25542
25549
  if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
25543
- const hasRenderOutput = containsRenderOutput$1(functionNode, functionNode, scopes);
25550
+ const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
25544
25551
  renderOutputCache.set(functionNode, {
25545
25552
  scopes,
25546
25553
  hasRenderOutput
@@ -27346,7 +27353,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
27346
27353
  let ancestor = setStateCall.parent;
27347
27354
  while (ancestor) {
27348
27355
  if (!lifecycleMember) {
27349
- if (FUNCTION_NODE_TYPES$1.has(ancestor.type)) nestedFunctionCount += 1;
27356
+ if (FUNCTION_NODE_TYPES$1.has(ancestor.type) && (!isImmediatelyInvokedFunction(ancestor) || !("async" in ancestor) || ancestor.async === true)) nestedFunctionCount += 1;
27350
27357
  if (isLifecycleMember(ancestor, lifecycleNames)) lifecycleMember = ancestor;
27351
27358
  } else if (isEs5Component(ancestor) || isEs6Component(ancestor)) {
27352
27359
  if (nestedFunctionCount > 1 && !options.disallowInNestedFunctions) return false;
@@ -27548,7 +27555,7 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
27548
27555
  const body = lifecycleFunction.body;
27549
27556
  if (!body) return derivedNames;
27550
27557
  walkAst(body, (node) => {
27551
- if (FUNCTION_NODE_TYPES.has(node.type)) return false;
27558
+ if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
27552
27559
  if (!isNodeOfType(node, "VariableDeclarator")) return;
27553
27560
  const init = node.init;
27554
27561
  if (!init) return;
@@ -27558,6 +27565,67 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
27558
27565
  return derivedNames;
27559
27566
  };
27560
27567
  const isStatefulOperand = (node, paramNames, derivedNames) => referencesAnyName(node, paramNames) || referencesAnyName(node, derivedNames) || containsThisStateOrProps(node);
27568
+ const getStaticMemberName = (node) => {
27569
+ if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
27570
+ return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
27571
+ };
27572
+ const getThisStateFieldName = (node) => {
27573
+ const unwrappedNode = stripParenExpression(node);
27574
+ if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
27575
+ const object = stripParenExpression(unwrappedNode.object);
27576
+ if (!isNodeOfType(object, "MemberExpression") || !isNodeOfType(stripParenExpression(object.object), "ThisExpression") || getStaticMemberName(object) !== "state") return null;
27577
+ return getStaticMemberName(unwrappedNode);
27578
+ };
27579
+ const collectLocalInitializers = (lifecycleFunction) => {
27580
+ const initializers = /* @__PURE__ */ new Map();
27581
+ const body = lifecycleFunction.body;
27582
+ if (!body) return initializers;
27583
+ walkAst(body, (node) => {
27584
+ if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
27585
+ if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.init) initializers.set(node.id.name, node.init);
27586
+ });
27587
+ return initializers;
27588
+ };
27589
+ const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
27590
+ if (readsPostMountValue(node)) return true;
27591
+ const referencedNames = /* @__PURE__ */ new Set();
27592
+ collectReferenceIdentifierNames(node, referencedNames);
27593
+ for (const referencedName of referencedNames) {
27594
+ if (visitedNames.has(referencedName)) continue;
27595
+ const initializer = localInitializers.get(referencedName);
27596
+ if (!initializer) continue;
27597
+ if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
27598
+ }
27599
+ return false;
27600
+ };
27601
+ const getSetStateFieldValue = (setStateCall, fieldName) => {
27602
+ if (!isNodeOfType(setStateCall, "CallExpression")) return null;
27603
+ const argument = setStateCall.arguments?.[0];
27604
+ if (!argument || !isNodeOfType(argument, "ObjectExpression")) return null;
27605
+ for (const property of argument.properties ?? []) {
27606
+ if (!isNodeOfType(property, "Property") || property.computed === true) continue;
27607
+ 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;
27608
+ }
27609
+ return null;
27610
+ };
27611
+ const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
27612
+ let qualifies = false;
27613
+ walkAst(test, (node) => {
27614
+ if (qualifies) return false;
27615
+ if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
27616
+ const leftFieldName = getThisStateFieldName(node.left);
27617
+ const rightFieldName = getThisStateFieldName(node.right);
27618
+ const fieldName = leftFieldName ?? rightFieldName;
27619
+ const comparedValue = leftFieldName ? node.right : node.left;
27620
+ if (!fieldName || !leftFieldName && !rightFieldName) return;
27621
+ const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
27622
+ if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
27623
+ if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
27624
+ qualifies = true;
27625
+ return false;
27626
+ });
27627
+ return qualifies;
27628
+ };
27561
27629
  const isDiffGuardTest = (test, paramNames, derivedNames) => {
27562
27630
  if (referencesAnyName(test, paramNames)) return true;
27563
27631
  let qualifies = false;
@@ -27565,7 +27633,7 @@ const isDiffGuardTest = (test, paramNames, derivedNames) => {
27565
27633
  if (qualifies) return false;
27566
27634
  if (!isNodeOfType(node, "BinaryExpression")) return;
27567
27635
  if (!EQUALITY_OPERATORS.has(node.operator)) return;
27568
- if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames)) {
27636
+ if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames) && (referencesAnyName(node.left, derivedNames) || referencesAnyName(node.right, derivedNames))) {
27569
27637
  qualifies = true;
27570
27638
  return false;
27571
27639
  }
@@ -27578,11 +27646,12 @@ const isInsideDiffGuard = (setStateCall) => {
27578
27646
  const paramNames = /* @__PURE__ */ new Set();
27579
27647
  for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
27580
27648
  const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
27649
+ const localInitializers = collectLocalInitializers(lifecycleFunction);
27581
27650
  let child = setStateCall;
27582
27651
  let ancestor = setStateCall.parent;
27583
27652
  while (ancestor && ancestor !== lifecycleFunction) {
27584
27653
  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;
27654
+ if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers))) return true;
27586
27655
  child = ancestor;
27587
27656
  ancestor = ancestor.parent ?? null;
27588
27657
  }
@@ -27763,7 +27832,16 @@ const producesOpaqueInstanceValue = (expression) => {
27763
27832
  const collectSetterValueObservations = (componentBody, setterNames) => {
27764
27833
  const plainFedSetterNames = /* @__PURE__ */ new Set();
27765
27834
  const opaqueFedSetterNames = /* @__PURE__ */ new Set();
27835
+ const callbackRefSetterNames = /* @__PURE__ */ new Set();
27766
27836
  walkComponentRespectingShadows(componentBody, /* @__PURE__ */ new Set(), (node, currentlyShadowed) => {
27837
+ if (isNodeOfType(node, "JSXAttribute")) {
27838
+ const attributeName = node.name;
27839
+ if (isNodeOfType(attributeName, "JSXIdentifier") && attributeName.name === "ref" && node.value && isNodeOfType(node.value, "JSXExpressionContainer")) {
27840
+ const expression = stripParenExpression(node.value.expression);
27841
+ if (isNodeOfType(expression, "Identifier") && setterNames.has(expression.name)) callbackRefSetterNames.add(expression.name);
27842
+ }
27843
+ return;
27844
+ }
27767
27845
  if (!isNodeOfType(node, "CallExpression")) return;
27768
27846
  if (!isNodeOfType(node.callee, "Identifier")) return;
27769
27847
  const setterName = node.callee.name;
@@ -27780,21 +27858,10 @@ const collectSetterValueObservations = (componentBody, setterNames) => {
27780
27858
  }, true);
27781
27859
  return {
27782
27860
  plainFedSetterNames,
27783
- opaqueFedSetterNames
27861
+ opaqueFedSetterNames,
27862
+ callbackRefSetterNames
27784
27863
  };
27785
27864
  };
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
27865
  const collectFunctionLocalBindings = (functionNode) => {
27799
27866
  const localBindings = /* @__PURE__ */ new Set();
27800
27867
  if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return localBindings;
@@ -27806,8 +27873,8 @@ const collectFunctionLocalBindings = (functionNode) => {
27806
27873
  return localBindings;
27807
27874
  };
27808
27875
  const collectBlockScopedBindings = (node) => {
27809
- const blockBindings = /* @__PURE__ */ new Set();
27810
27876
  if (isNodeOfType(node, "BlockStatement")) {
27877
+ const blockBindings = /* @__PURE__ */ new Set();
27811
27878
  for (const statement of node.body ?? []) {
27812
27879
  if (!isNodeOfType(statement, "VariableDeclaration")) continue;
27813
27880
  for (const declarator of statement.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
@@ -27815,17 +27882,22 @@ const collectBlockScopedBindings = (node) => {
27815
27882
  return blockBindings;
27816
27883
  }
27817
27884
  if (isNodeOfType(node, "ForStatement") && isNodeOfType(node.init, "VariableDeclaration")) {
27885
+ const blockBindings = /* @__PURE__ */ new Set();
27818
27886
  for (const declarator of node.init.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
27819
27887
  return blockBindings;
27820
27888
  }
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;
27889
+ if ((isNodeOfType(node, "ForOfStatement") || isNodeOfType(node, "ForInStatement")) && isNodeOfType(node.left, "VariableDeclaration")) {
27890
+ const blockBindings = /* @__PURE__ */ new Set();
27891
+ for (const declarator of node.left.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
27892
+ return blockBindings;
27893
+ }
27894
+ return null;
27823
27895
  };
27824
27896
  const walkComponentRespectingShadows = (node, shadowedStateNames, visit, isComponentBodyRoot = false) => {
27825
27897
  if (!node || typeof node !== "object") return;
27826
27898
  let nextShadowedStateNames = shadowedStateNames;
27827
- const localBindings = isComponentBodyRoot ? /* @__PURE__ */ new Set() : isFunctionLike$1(node) ? collectFunctionLocalBindings(node) : collectBlockScopedBindings(node);
27828
- if (localBindings.size > 0) {
27899
+ const localBindings = isComponentBodyRoot ? null : isFunctionLike$1(node) ? collectFunctionLocalBindings(node) : collectBlockScopedBindings(node);
27900
+ if (localBindings && localBindings.size > 0) {
27829
27901
  const merged = new Set(shadowedStateNames);
27830
27902
  for (const localName of localBindings) merged.add(localName);
27831
27903
  nextShadowedStateNames = merged;
@@ -27851,11 +27923,10 @@ const noDirectStateMutation = defineRule({
27851
27923
  const bindings = collectUseStateBindings(componentBody);
27852
27924
  if (bindings.length === 0) return;
27853
27925
  const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
27854
- const callbackRefSetterNames = collectCallbackRefSetterNames(componentBody);
27855
27926
  const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
27856
27927
  const plainObjectStateValueNames = /* @__PURE__ */ new Set();
27857
27928
  for (const binding of bindings) {
27858
- if (callbackRefSetterNames.has(binding.setterName)) continue;
27929
+ if (setterValueObservations.callbackRefSetterNames.has(binding.setterName)) continue;
27859
27930
  if (!isNodeOfType(binding.declarator.init, "CallExpression")) continue;
27860
27931
  const initializerArgument = binding.declarator.init.arguments?.[0];
27861
27932
  if (!initializerMarksPlainState(initializerArgument)) continue;
@@ -32573,25 +32644,60 @@ const resolveSettings$16 = (settings) => {
32573
32644
  };
32574
32645
  const isHocCall = (call, scopes) => {
32575
32646
  if (!isNodeOfType(call, "CallExpression")) return false;
32576
- const calleeName = flattenCalleeName(call.callee);
32577
- if (calleeName && REACT_HOC_NAMES.has(calleeName)) return true;
32647
+ if (isReactApiCall(call, REACT_HOC_NAMES, scopes, {
32648
+ allowGlobalReactNamespace: true,
32649
+ allowUnboundBareCalls: true
32650
+ })) return true;
32651
+ if (isReactHocMemberReference(call.callee, scopes)) return true;
32578
32652
  if (!isNodeOfType(call.callee, "Identifier")) return false;
32579
32653
  const symbol = scopes.symbolFor(call.callee);
32580
32654
  if (!symbol) return false;
32581
- return symbolMapsToHoc(symbol);
32655
+ return symbolMapsToHoc(symbol, scopes, /* @__PURE__ */ new Set());
32656
+ };
32657
+ const isReactImportEquals = (symbol) => {
32658
+ if (symbol.kind !== "ts-import-equals" || !isNodeOfType(symbol.declarationNode, "TSImportEqualsDeclaration")) return false;
32659
+ const moduleReference = symbol.declarationNode.moduleReference;
32660
+ return Boolean(isNodeOfType(moduleReference, "TSExternalModuleReference") && isNodeOfType(moduleReference.expression, "Literal") && typeof moduleReference.expression.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(moduleReference.expression.value));
32661
+ };
32662
+ const isRequireReactCall$1 = (node, scopes) => {
32663
+ if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier") || node.callee.name !== "require" || !scopes.isGlobalReference(node.callee)) return false;
32664
+ const moduleSpecifier = node.arguments[0];
32665
+ return Boolean(moduleSpecifier && isNodeOfType(moduleSpecifier, "Literal") && typeof moduleSpecifier.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(moduleSpecifier.value));
32666
+ };
32667
+ const isReactNamespaceExpression = (node, scopes) => {
32668
+ if (isRequireReactCall$1(node, scopes)) return true;
32669
+ if (!isNodeOfType(node, "Identifier")) return false;
32670
+ const symbol = scopes.symbolFor(node);
32671
+ if (!symbol) return node.name === "React" && scopes.isGlobalReference(node);
32672
+ if (symbol.initializer && isRequireReactCall$1(symbol.initializer, scopes)) return true;
32673
+ if (isReactImportEquals(symbol)) return true;
32674
+ return isReactNamespaceImport(node, scopes);
32675
+ };
32676
+ 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));
32677
+ const getDestructuredPropertyName$1 = (symbol) => {
32678
+ const property = symbol.bindingIdentifier.parent;
32679
+ if (!property || !isNodeOfType(property, "Property") || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
32680
+ if (isNodeOfType(property.key, "Identifier") && !property.computed) return property.key.name;
32681
+ if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
32682
+ return null;
32582
32683
  };
32583
- const symbolMapsToHoc = (symbol) => {
32584
- if (REACT_HOC_NAMES.has(symbol.name)) {
32585
- if (symbol.kind === "import") return true;
32684
+ const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
32685
+ if (visitedSymbolIds.has(symbol.id)) return false;
32686
+ visitedSymbolIds.add(symbol.id);
32687
+ if (symbol.kind === "import") {
32688
+ const importedName = getImportedName(symbol.declarationNode);
32689
+ return Boolean(isImportedFromReact(symbol) && importedName && REACT_HOC_NAMES.has(importedName));
32586
32690
  }
32587
32691
  const init = symbol.initializer;
32588
32692
  if (!init) return false;
32589
- if (isNodeOfType(init, "MemberExpression")) {
32590
- const flat = flattenCalleeName(init);
32591
- if (flat && REACT_HOC_NAMES.has(flat)) return true;
32693
+ const destructuredPropertyName = getDestructuredPropertyName$1(symbol);
32694
+ if (destructuredPropertyName && REACT_HOC_NAMES.has(destructuredPropertyName) && isReactNamespaceExpression(init, scopes)) return true;
32695
+ if (isReactHocMemberReference(init, scopes)) return true;
32696
+ if (isNodeOfType(init, "Identifier")) {
32697
+ const initializedFromSymbol = scopes.symbolFor(init);
32698
+ if (initializedFromSymbol) return symbolMapsToHoc(initializedFromSymbol, scopes, visitedSymbolIds);
32699
+ return REACT_HOC_NAMES.has(init.name) && scopes.isGlobalReference(init);
32592
32700
  }
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
32701
  return false;
32596
32702
  };
32597
32703
  const isTrivialPassthroughChild = (child) => {
@@ -32649,26 +32755,14 @@ const isHocComponent = (call, scopes) => {
32649
32755
  };
32650
32756
  const containsJsx = (root) => {
32651
32757
  let found = false;
32652
- const visit = (node) => {
32653
- if (found) return;
32758
+ walkAst(root, (node) => {
32759
+ if (found) return false;
32654
32760
  if (node.type === "JSXElement" || node.type === "JSXFragment") {
32655
32761
  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;
32762
+ return false;
32669
32763
  }
32670
- };
32671
- visit(root);
32764
+ if (node !== root && (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "ClassDeclaration" || node.type === "ClassExpression")) return false;
32765
+ });
32672
32766
  return found;
32673
32767
  };
32674
32768
  const expressionContainsJsx = (expression) => {
@@ -32692,7 +32786,7 @@ const unwrapTsCast = (expression) => {
32692
32786
  while (current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSNonNullExpression") current = stripParenExpression(current.expression);
32693
32787
  return current;
32694
32788
  };
32695
- const collectReExportedNames = (program) => {
32789
+ const collectReExportedNames = (program, scopes) => {
32696
32790
  const names = /* @__PURE__ */ new Set();
32697
32791
  if (!isNodeOfType(program, "Program")) return names;
32698
32792
  for (const statement of program.body) {
@@ -32716,8 +32810,7 @@ const collectReExportedNames = (program) => {
32716
32810
  if (!declarator.init) continue;
32717
32811
  const init = unwrapTsCast(declarator.init);
32718
32812
  if (isNodeOfType(init, "CallExpression")) {
32719
- const calleeName = flattenCalleeName(init.callee);
32720
- if (calleeName && REACT_HOC_NAMES.has(calleeName)) {
32813
+ if (isHocCall(init, scopes)) {
32721
32814
  const wrappedArg = init.arguments[0];
32722
32815
  if (wrappedArg && isNodeOfType(wrappedArg, "Identifier")) names.add(wrappedArg.name);
32723
32816
  }
@@ -32786,16 +32879,7 @@ const recordComponent = (context, name, reportNode, isStateless) => {
32786
32879
  isStateless
32787
32880
  });
32788
32881
  };
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
- };
32882
+ const walkChildren = (node, context) => forEachChildNode(node, context.visitChild);
32799
32883
  const walkComponentSearch = (node, context) => {
32800
32884
  if (isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) {
32801
32885
  if (isEs6Component(node)) {
@@ -32897,12 +32981,13 @@ const noMultiComp = defineRule({
32897
32981
  components: [],
32898
32982
  componentDepth: 0,
32899
32983
  currentVarName: null,
32900
- scopes: context.scopes
32984
+ scopes: context.scopes,
32985
+ visitChild: (child) => walkComponentSearch(child, visitContext)
32901
32986
  };
32902
32987
  for (const statement of node.body) walkComponentSearch(statement, visitContext);
32903
32988
  const flagged = settings.ignoreStateless ? visitContext.components.filter((component) => !component.isStateless) : visitContext.components;
32904
32989
  if (flagged.length <= 2) return;
32905
- const reExportedNames = collectReExportedNames(node);
32990
+ const reExportedNames = collectReExportedNames(node, context.scopes);
32906
32991
  const exportedCount = flagged.filter((component) => isExportedDeclaration(component.reportNode, reExportedNames)).length;
32907
32992
  if (exportedCount >= flagged.length || flagged.length >= 4 && exportedCount >= Math.floor(flagged.length * .7) || flagged.length >= 8 && exportedCount >= Math.floor(flagged.length * .5)) return;
32908
32993
  const isSmallFeatureModule = exportedCount > 0 && exportedCount <= 2 && exportedCount < flagged.length;
@@ -39370,34 +39455,18 @@ const resolveSettings$8 = (settings) => {
39370
39455
  };
39371
39456
  const expressionContainsJsxOrCreateElement = (root) => {
39372
39457
  let found = false;
39373
- const visit = (node) => {
39374
- if (found) return;
39458
+ walkAst(root, (node) => {
39459
+ if (found) return false;
39460
+ if (node !== root && NESTED_FUNCTION_TYPES.has(node.type)) return false;
39375
39461
  if (node.type === "JSXElement" || node.type === "JSXFragment") {
39376
39462
  found = true;
39377
- return;
39463
+ return false;
39378
39464
  }
39379
39465
  if (isNodeOfType(node, "CallExpression") && isCreateElementCall(node)) {
39380
39466
  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;
39467
+ return false;
39398
39468
  }
39399
- };
39400
- visit(root);
39469
+ });
39401
39470
  return found;
39402
39471
  };
39403
39472
  const isReactClassComponent = (classNode) => {
@@ -40323,19 +40392,6 @@ const classifyExport = (name, reportNode, isFunction, initializer, state) => {
40323
40392
  reportNode
40324
40393
  };
40325
40394
  };
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
40395
  const isEntryPointFile = (filename) => {
40340
40396
  const lastSlash = Math.max(filename.lastIndexOf("/"), filename.lastIndexOf("\\"));
40341
40397
  const basename = lastSlash === -1 ? filename : filename.slice(lastSlash + 1);
@@ -40374,197 +40430,212 @@ const onlyExportComponents = defineRule({
40374
40430
  category: "Architecture",
40375
40431
  create: (context) => {
40376
40432
  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);
40433
+ if (!isFileNameAllowed(normalizeFilename(context.filename ?? ""), settings.checkJS)) return {};
40434
+ const exportNodes = [];
40435
+ const componentCandidates = [];
40436
+ const pushExportNode = (node) => {
40437
+ exportNodes.push(node);
40438
+ };
40439
+ const pushComponentCandidate = (node) => {
40440
+ componentCandidates.push(node);
40441
+ };
40442
+ return {
40443
+ ExportAllDeclaration: pushExportNode,
40444
+ ExportDefaultDeclaration: pushExportNode,
40445
+ ExportNamedDeclaration: pushExportNode,
40446
+ FunctionDeclaration: pushComponentCandidate,
40447
+ VariableDeclarator: pushComponentCandidate,
40448
+ ClassDeclaration: pushComponentCandidate,
40449
+ "Program:exit"() {
40450
+ const localComponentNames = /* @__PURE__ */ new Set();
40451
+ const state = {
40452
+ customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
40453
+ allowExportNames: new Set(settings.allowExportNames),
40454
+ allowConstantExport: settings.allowConstantExport,
40455
+ localComponentNames,
40456
+ scopes: context.scopes
40457
+ };
40458
+ for (const child of componentCandidates) {
40459
+ if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
40460
+ if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) && functionContainsReactRenderOutput(child, context.scopes)) localComponentNames.add(child.id.name);
40461
+ }
40462
+ if (isNodeOfType(child, "ClassDeclaration") && child.id) {
40463
+ if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
40464
+ }
40465
+ if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
40466
+ const initializer = child.init;
40467
+ if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) {
40468
+ const expression = initializer ? skipTsExpression(initializer) : null;
40469
+ if (!(expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"))) || functionContainsReactRenderOutput(expression, context.scopes)) localComponentNames.add(child.id.name);
40470
+ }
40400
40471
  }
40401
40472
  }
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({
40473
+ const exports = [];
40474
+ let hasReactExport = false;
40475
+ let hasAnyExports = false;
40476
+ const isExportedNodeIds = /* @__PURE__ */ new WeakSet();
40477
+ for (const child of exportNodes) {
40478
+ if (isNodeOfType(child, "ExportAllDeclaration")) {
40479
+ if (child.exportKind === "type") continue;
40480
+ hasAnyExports = true;
40481
+ context.report({
40482
+ node: child,
40483
+ message: EXPORT_ALL_MESSAGE
40484
+ });
40485
+ continue;
40486
+ }
40487
+ if (isNodeOfType(child, "ExportDefaultDeclaration")) {
40488
+ hasAnyExports = true;
40489
+ const declaration = child.declaration;
40490
+ const stripped = skipTsExpression(declaration);
40491
+ if (isNodeOfType(stripped, "FunctionDeclaration") || isNodeOfType(stripped, "FunctionExpression")) {
40492
+ if (stripped.id) {
40493
+ const idNode = stripped.id;
40494
+ isExportedNodeIds.add(stripped);
40495
+ exports.push(classifyExport(idNode.name, idNode, true, null, state));
40496
+ } else {
40497
+ context.report({
40498
+ node: stripped,
40499
+ message: ANONYMOUS_MESSAGE
40500
+ });
40501
+ hasReactExport = true;
40502
+ }
40503
+ continue;
40504
+ }
40505
+ if (isNodeOfType(stripped, "ClassDeclaration") || isNodeOfType(stripped, "ClassExpression")) {
40506
+ if (stripped.id) {
40507
+ const idNode = stripped.id;
40508
+ isExportedNodeIds.add(stripped);
40509
+ if (isReactComponentName(idNode.name) && isEs6Component(stripped)) hasReactExport = true;
40510
+ else exports.push({
40511
+ kind: "non-component",
40512
+ reportNode: idNode
40513
+ });
40514
+ } else context.report({
40428
40515
  node: stripped,
40429
40516
  message: ANONYMOUS_MESSAGE
40430
40517
  });
40431
- hasReactExport = true;
40518
+ continue;
40432
40519
  }
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({
40520
+ if (isNodeOfType(stripped, "Identifier")) {
40521
+ exports.push(classifyExport(stripped.name, stripped, false, null, state));
40522
+ continue;
40523
+ }
40524
+ if (isNodeOfType(stripped, "CallExpression")) {
40525
+ if (isRouteFactoryCall(stripped)) {
40526
+ hasReactExport = true;
40527
+ continue;
40528
+ }
40529
+ const isHoc = isHocCallee(stripped.callee, state);
40530
+ const firstArg = stripped.arguments[0];
40531
+ const firstArgIsValid = Boolean(firstArg) && (() => {
40532
+ if (!firstArg) return false;
40533
+ const expression = skipTsExpression(firstArg);
40534
+ if (isNodeOfType(expression, "Identifier")) return true;
40535
+ if (isNodeOfType(expression, "FunctionExpression") && expression.id) return true;
40536
+ if (isNodeOfType(expression, "CallExpression") && isHocCallee(expression.callee, state)) return true;
40537
+ return false;
40538
+ })();
40539
+ if (isHoc && firstArgIsValid) hasReactExport = true;
40540
+ else if (!isHoc && isConfigOnlyFactoryCall(stripped)) exports.push({
40441
40541
  kind: "non-component",
40442
- reportNode: idNode
40542
+ reportNode: stripped
40543
+ });
40544
+ else context.report({
40545
+ node: stripped,
40546
+ message: ANONYMOUS_MESSAGE
40547
+ });
40548
+ continue;
40549
+ }
40550
+ if (isNodeOfType(stripped, "ObjectExpression")) {
40551
+ context.report({
40552
+ node: stripped,
40553
+ message: objectExpressionBundlesComponents(stripped, state) ? NAMESPACE_OBJECT_MESSAGE : ANONYMOUS_MESSAGE
40554
+ });
40555
+ continue;
40556
+ }
40557
+ if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "Literal")) {
40558
+ context.report({
40559
+ node: stripped,
40560
+ message: ANONYMOUS_MESSAGE
40443
40561
  });
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
40562
  continue;
40458
40563
  }
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
40564
  context.report({
40489
- node: stripped,
40565
+ node: child,
40490
40566
  message: ANONYMOUS_MESSAGE
40491
40567
  });
40492
40568
  continue;
40493
40569
  }
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
- });
40570
+ if (isNodeOfType(child, "ExportNamedDeclaration")) {
40571
+ if (child.exportKind === "type") continue;
40572
+ hasAnyExports = true;
40573
+ if (child.declaration) {
40574
+ const declaration = child.declaration;
40575
+ if (isNodeOfType(declaration, "FunctionDeclaration") && declaration.id) {
40576
+ isExportedNodeIds.add(declaration);
40577
+ exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state));
40578
+ } else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
40579
+ isExportedNodeIds.add(declaration);
40580
+ if (isReactComponentName(declaration.id.name) && isEs6Component(declaration)) exports.push({ kind: "react-component" });
40581
+ else exports.push({
40582
+ kind: "non-component",
40583
+ reportNode: declaration.id
40584
+ });
40585
+ } else if (isNodeOfType(declaration, "VariableDeclaration")) for (const declarator of declaration.declarations) {
40586
+ if (!isNodeOfType(declarator.id, "Identifier")) continue;
40587
+ isExportedNodeIds.add(declarator);
40588
+ const isFunction = canBeReactFunctionComponent(declarator.init ?? null, state);
40589
+ exports.push(classifyExport(declarator.id.name, declarator.id, isFunction, declarator.init, state));
40590
+ }
40591
+ else if (declaration.type === "TSEnumDeclaration" || declaration.type === "TSInterfaceDeclaration" || declaration.type === "TSTypeAliasDeclaration") {
40592
+ if (declaration.type === "TSEnumDeclaration") exports.push({
40593
+ kind: "non-component",
40594
+ reportNode: declaration
40595
+ });
40596
+ }
40526
40597
  }
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" });
40598
+ const isReExportFromSource = Boolean(child.source);
40599
+ for (const specifier of child.specifiers ?? []) {
40600
+ if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
40601
+ const exported = specifier.exported;
40602
+ const local = specifier.local;
40603
+ let exportedName = null;
40604
+ if (exported && isNodeOfType(exported, "Identifier")) exportedName = exported.name;
40605
+ const localName = local && isNodeOfType(local, "Identifier") ? local.name : null;
40606
+ const reportNode = specifier;
40607
+ let entry;
40608
+ if (exportedName === "default" && localName) entry = classifyExport(localName, reportNode, false, null, state);
40609
+ else if (exportedName) entry = classifyExport(exportedName, reportNode, false, null, state);
40610
+ else {
40611
+ entry = {
40612
+ kind: "non-component",
40613
+ reportNode
40614
+ };
40615
+ if (localName && isReactComponentName(localName)) exports.push({ kind: "react-component" });
40616
+ }
40617
+ if (isReExportFromSource && entry.kind !== "react-component") continue;
40618
+ exports.push(entry);
40546
40619
  }
40547
- if (isReExportFromSource && entry.kind !== "react-component") continue;
40548
- exports.push(entry);
40549
40620
  }
40550
40621
  }
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({
40559
- node: entry.reportNode,
40560
- message: NAMED_EXPORT_MESSAGE
40561
- });
40562
- if (entry.kind === "react-context") context.report({
40622
+ for (const entry of exports) if (entry.kind === "react-component") hasReactExport = true;
40623
+ for (const entry of exports) if (entry.kind === "namespace-object") context.report({
40563
40624
  node: entry.reportNode,
40564
- message: REACT_CONTEXT_MESSAGE
40625
+ message: NAMESPACE_OBJECT_MESSAGE
40565
40626
  });
40627
+ if (hasAnyExports && hasReactExport) for (const entry of exports) {
40628
+ if (entry.kind === "non-component") context.report({
40629
+ node: entry.reportNode,
40630
+ message: NAMED_EXPORT_MESSAGE
40631
+ });
40632
+ if (entry.kind === "react-context") context.report({
40633
+ node: entry.reportNode,
40634
+ message: REACT_CONTEXT_MESSAGE
40635
+ });
40636
+ }
40566
40637
  }
40567
- } };
40638
+ };
40568
40639
  }
40569
40640
  });
40570
40641
  //#endregion
@@ -40666,7 +40737,12 @@ const postmessageOriginRisk = defineRule({
40666
40737
  scan: (file) => {
40667
40738
  if (!isProductionSourcePath(file.relativePath)) return [];
40668
40739
  if (WORKER_FILE_PATH_PATTERN.test(file.relativePath)) return [];
40669
- const ast = parseSourceText(file.absolutePath, file.content);
40740
+ if (!file.content.includes("addEventListener") && !file.content.includes("onmessage")) return [];
40741
+ const ast = parseSourceText({
40742
+ filename: file.absolutePath,
40743
+ sourceText: file.content,
40744
+ shouldAttachParentReferences: false
40745
+ });
40670
40746
  if (ast === null) return [];
40671
40747
  const findings = [];
40672
40748
  walkAst(ast, (node) => {
@@ -43046,15 +43122,6 @@ const isStableHookWrapperArgument = (node) => {
43046
43122
  const parent = node.parent;
43047
43123
  return isNodeOfType(parent, "CallExpression") && isNodeOfType(parent.callee, "Identifier") && STABLE_HOOK_WRAPPERS.has(parent.callee.name) && Boolean(parent.arguments?.some((argument) => argument === node));
43048
43124
  };
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
43125
  const queryStableQueryClient = defineRule({
43059
43126
  id: "query-stable-query-client",
43060
43127
  title: "Unstable QueryClient in component",
@@ -59553,7 +59620,10 @@ const appendNode = (builder, block, node) => {
59553
59620
  };
59554
59621
  const mapDescendantsToBlock = (builder, node, block) => {
59555
59622
  builder.nodeBlock.set(node, block);
59556
- if (isFunctionLike$1(node)) return;
59623
+ if (isFunctionLike$1(node)) {
59624
+ builder.nestedFunctions.push(node);
59625
+ return;
59626
+ }
59557
59627
  const record = node;
59558
59628
  for (const key of Object.keys(record)) {
59559
59629
  if (key === "parent") continue;
@@ -59787,11 +59857,12 @@ const buildStatement = (builder, statement, current) => {
59787
59857
  mapDescendantsToBlock(builder, statement, current);
59788
59858
  return current;
59789
59859
  };
59790
- const buildFunctionCfg = (functionNode, body) => {
59860
+ const buildFunctionCfg = (functionNode, body, nestedFunctionSink) => {
59791
59861
  const builder = {
59792
59862
  blocks: [],
59793
59863
  entry: null,
59794
59864
  exit: null,
59865
+ nestedFunctions: nestedFunctionSink,
59795
59866
  nodeBlock: /* @__PURE__ */ new Map(),
59796
59867
  loopStack: [],
59797
59868
  switchStack: [],
@@ -59809,13 +59880,12 @@ const buildFunctionCfg = (functionNode, body) => {
59809
59880
  bodyEnd = entry;
59810
59881
  }
59811
59882
  addEdge(bodyEnd, exit, "uncond");
59812
- const blockOf = (node) => builder.nodeBlock.get(node) ?? null;
59813
59883
  return {
59814
59884
  owner: functionNode,
59815
59885
  entry,
59816
59886
  exit,
59817
59887
  blocks: builder.blocks,
59818
- blockOf
59888
+ blockOf: (node) => builder.nodeBlock.get(node) ?? null
59819
59889
  };
59820
59890
  };
59821
59891
  const computeUnconditionalSet = (cfg) => {
@@ -59852,8 +59922,9 @@ const computeUnconditionalSet = (cfg) => {
59852
59922
  const analyzeControlFlow = (program) => {
59853
59923
  nextBlockId = 0;
59854
59924
  const functionCfgs = /* @__PURE__ */ new Map();
59925
+ const pendingFunctions = [];
59855
59926
  const buildFor = (functionNode, body) => {
59856
- const cfg = buildFunctionCfg(functionNode, body);
59927
+ const cfg = buildFunctionCfg(functionNode, body, pendingFunctions);
59857
59928
  functionCfgs.set(functionNode, {
59858
59929
  cfg,
59859
59930
  unconditionalSet: computeUnconditionalSet(cfg)
@@ -59863,21 +59934,18 @@ const analyzeControlFlow = (program) => {
59863
59934
  type: "BlockStatement",
59864
59935
  body: program.body
59865
59936
  });
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
- }
59937
+ for (let functionIndex = 0; functionIndex < pendingFunctions.length; functionIndex += 1) {
59938
+ const functionNode = pendingFunctions[functionIndex];
59939
+ if (!isFunctionLike$1(functionNode) || !functionNode.body || functionCfgs.has(functionNode)) continue;
59940
+ buildFor(functionNode, functionNode.body);
59941
+ }
59942
+ const getFunctionEntry = (functionNode) => {
59943
+ const existingEntry = functionCfgs.get(functionNode);
59944
+ if (existingEntry) return existingEntry;
59945
+ if (!isFunctionLike$1(functionNode) || !functionNode.body) return null;
59946
+ buildFor(functionNode, functionNode.body);
59947
+ return functionCfgs.get(functionNode) ?? null;
59879
59948
  };
59880
- visit(program);
59881
59949
  const enclosingFunction = (node) => {
59882
59950
  let current = node;
59883
59951
  while (current) {
@@ -59888,12 +59956,12 @@ const analyzeControlFlow = (program) => {
59888
59956
  return null;
59889
59957
  };
59890
59958
  const cfgFor = (functionLike) => {
59891
- return functionCfgs.get(functionLike)?.cfg ?? null;
59959
+ return getFunctionEntry(functionLike)?.cfg ?? null;
59892
59960
  };
59893
59961
  const isUnconditionalFromEntry = (node) => {
59894
59962
  const owner = enclosingFunction(node);
59895
59963
  if (!owner) return true;
59896
- const entry = functionCfgs.get(owner);
59964
+ const entry = getFunctionEntry(owner);
59897
59965
  if (!entry) return true;
59898
59966
  const block = entry.cfg.blockOf(node);
59899
59967
  if (!block) return true;
@@ -59967,17 +60035,14 @@ const wrapWithSemanticContext = (rule) => ({
59967
60035
  }
59968
60036
  };
59969
60037
  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;
60038
+ const innerProgramHandler = visitors.Program;
60039
+ return {
60040
+ ...visitors,
60041
+ Program: ((node) => {
60042
+ programRoot = node;
60043
+ if (innerProgramHandler) innerProgramHandler(node);
60044
+ })
60045
+ };
59981
60046
  }
59982
60047
  });
59983
60048
  //#endregion