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.
- package/dist/index.js +550 -485
- 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) =>
|
|
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
|
-
|
|
807
|
-
|
|
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
|
|
812
|
-
if (!node || typeof node !== "object") return;
|
|
813
|
-
if (visitor(node) === false) return;
|
|
820
|
+
const forEachChildNode = (node, visit) => {
|
|
814
821
|
const nodeRecord = node;
|
|
815
|
-
|
|
816
|
-
|
|
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
|
-
|
|
820
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1429
|
-
if (
|
|
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 } =
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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 === "
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
19648
|
-
requireAll: [
|
|
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/
|
|
20760
|
-
const
|
|
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) && !
|
|
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(
|
|
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/
|
|
22008
|
-
const
|
|
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
|
|
22078
|
-
const getScopeForNode = (node, manager) => {
|
|
22085
|
+
const getScopeForNode = (node, analysis) => {
|
|
22079
22086
|
if (!node.range) return null;
|
|
22080
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
22661
|
-
|
|
22662
|
-
|
|
22663
|
-
|
|
22664
|
-
|
|
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
|
-
|
|
25277
|
-
return
|
|
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 = (
|
|
25526
|
-
|
|
25527
|
-
|
|
25528
|
-
|
|
25529
|
-
|
|
25530
|
-
|
|
25531
|
-
|
|
25532
|
-
|
|
25533
|
-
|
|
25534
|
-
|
|
25535
|
-
|
|
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,
|
|
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"))
|
|
27822
|
-
|
|
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 ?
|
|
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
|
-
|
|
32577
|
-
|
|
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 (
|
|
32585
|
-
|
|
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
|
-
|
|
32590
|
-
|
|
32591
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
40378
|
-
|
|
40379
|
-
|
|
40380
|
-
|
|
40381
|
-
|
|
40382
|
-
|
|
40383
|
-
|
|
40384
|
-
|
|
40385
|
-
|
|
40386
|
-
|
|
40387
|
-
|
|
40388
|
-
|
|
40389
|
-
|
|
40390
|
-
|
|
40391
|
-
|
|
40392
|
-
|
|
40393
|
-
|
|
40394
|
-
|
|
40395
|
-
|
|
40396
|
-
|
|
40397
|
-
|
|
40398
|
-
|
|
40399
|
-
|
|
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
|
-
|
|
40404
|
-
|
|
40405
|
-
|
|
40406
|
-
|
|
40407
|
-
|
|
40408
|
-
|
|
40409
|
-
|
|
40410
|
-
|
|
40411
|
-
|
|
40412
|
-
|
|
40413
|
-
|
|
40414
|
-
|
|
40415
|
-
|
|
40416
|
-
|
|
40417
|
-
|
|
40418
|
-
|
|
40419
|
-
|
|
40420
|
-
|
|
40421
|
-
|
|
40422
|
-
|
|
40423
|
-
|
|
40424
|
-
|
|
40425
|
-
|
|
40426
|
-
|
|
40427
|
-
|
|
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
|
-
|
|
40518
|
+
continue;
|
|
40432
40519
|
}
|
|
40433
|
-
|
|
40434
|
-
|
|
40435
|
-
|
|
40436
|
-
|
|
40437
|
-
|
|
40438
|
-
|
|
40439
|
-
|
|
40440
|
-
|
|
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:
|
|
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:
|
|
40565
|
+
node: child,
|
|
40490
40566
|
message: ANONYMOUS_MESSAGE
|
|
40491
40567
|
});
|
|
40492
40568
|
continue;
|
|
40493
40569
|
}
|
|
40494
|
-
|
|
40495
|
-
|
|
40496
|
-
|
|
40497
|
-
|
|
40498
|
-
|
|
40499
|
-
|
|
40500
|
-
|
|
40501
|
-
|
|
40502
|
-
|
|
40503
|
-
|
|
40504
|
-
|
|
40505
|
-
|
|
40506
|
-
|
|
40507
|
-
|
|
40508
|
-
|
|
40509
|
-
|
|
40510
|
-
|
|
40511
|
-
|
|
40512
|
-
|
|
40513
|
-
|
|
40514
|
-
}
|
|
40515
|
-
|
|
40516
|
-
|
|
40517
|
-
|
|
40518
|
-
|
|
40519
|
-
|
|
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
|
-
|
|
40529
|
-
|
|
40530
|
-
|
|
40531
|
-
|
|
40532
|
-
|
|
40533
|
-
|
|
40534
|
-
|
|
40535
|
-
|
|
40536
|
-
|
|
40537
|
-
|
|
40538
|
-
|
|
40539
|
-
|
|
40540
|
-
|
|
40541
|
-
|
|
40542
|
-
|
|
40543
|
-
|
|
40544
|
-
|
|
40545
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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))
|
|
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
|
-
|
|
59867
|
-
|
|
59868
|
-
|
|
59869
|
-
|
|
59870
|
-
|
|
59871
|
-
|
|
59872
|
-
|
|
59873
|
-
|
|
59874
|
-
|
|
59875
|
-
|
|
59876
|
-
|
|
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
|
|
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 =
|
|
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
|
|
59971
|
-
|
|
59972
|
-
|
|
59973
|
-
|
|
59974
|
-
|
|
59975
|
-
|
|
59976
|
-
|
|
59977
|
-
|
|
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
|