oxlint-plugin-react-doctor 0.7.5-dev.0f07133 → 0.7.5-dev.20cd922
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +611 -493
- 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 ?? []) {
|
|
@@ -23998,6 +23998,26 @@ const STATEFUL_HTML_DESCENDANT_TAGS = new Set([
|
|
|
23998
23998
|
"dialog",
|
|
23999
23999
|
"canvas"
|
|
24000
24000
|
]);
|
|
24001
|
+
const STATEFUL_HTML_ATTRIBUTE_NAMES = new Set([
|
|
24002
|
+
"autofocus",
|
|
24003
|
+
"contenteditable",
|
|
24004
|
+
"draggable",
|
|
24005
|
+
"tabindex"
|
|
24006
|
+
]);
|
|
24007
|
+
const isStaticallyFalseAttributeValue = (attribute) => {
|
|
24008
|
+
if (!isNodeOfType(attribute, "JSXAttribute") || !attribute.value) return false;
|
|
24009
|
+
const value = isNodeOfType(attribute.value, "JSXExpressionContainer") ? attribute.value.expression : attribute.value;
|
|
24010
|
+
return isNodeOfType(value, "Literal") && (value.value === false || value.value === "false");
|
|
24011
|
+
};
|
|
24012
|
+
const hasStatefulHtmlAttribute = (openingElement) => {
|
|
24013
|
+
if (!isNodeOfType(openingElement, "JSXOpeningElement")) return false;
|
|
24014
|
+
return openingElement.attributes.some((attribute) => {
|
|
24015
|
+
if (!isNodeOfType(attribute, "JSXAttribute") || !isNodeOfType(attribute.name, "JSXIdentifier")) return false;
|
|
24016
|
+
const attributeName = attribute.name.name.toLowerCase();
|
|
24017
|
+
if (!STATEFUL_HTML_ATTRIBUTE_NAMES.has(attributeName)) return false;
|
|
24018
|
+
return attributeName === "tabindex" || !isStaticallyFalseAttributeValue(attribute);
|
|
24019
|
+
});
|
|
24020
|
+
};
|
|
24001
24021
|
const STATEFUL_DESCENDANT_SCAN_BUDGET = 200;
|
|
24002
24022
|
const rootIdentifierNameOf = (expression) => {
|
|
24003
24023
|
let object = expression;
|
|
@@ -24015,7 +24035,8 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
|
|
|
24015
24035
|
budget -= 1;
|
|
24016
24036
|
const node = stack.pop();
|
|
24017
24037
|
if (isNodeOfType(node, "JSXElement")) {
|
|
24018
|
-
const
|
|
24038
|
+
const opening = node.openingElement;
|
|
24039
|
+
const name = opening.name;
|
|
24019
24040
|
if (name && isNodeOfType(name, "JSXIdentifier")) {
|
|
24020
24041
|
const tagName = name.name;
|
|
24021
24042
|
const firstChar = tagName.charCodeAt(0);
|
|
@@ -24023,6 +24044,7 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
|
|
|
24023
24044
|
if (STATEFUL_HTML_DESCENDANT_TAGS.has(tagName)) return true;
|
|
24024
24045
|
}
|
|
24025
24046
|
if (name && isNodeOfType(name, "JSXMemberExpression")) return true;
|
|
24047
|
+
if (hasStatefulHtmlAttribute(opening)) return true;
|
|
24026
24048
|
const children = node.children ?? [];
|
|
24027
24049
|
for (const child of children) stack.push(child);
|
|
24028
24050
|
continue;
|
|
@@ -24116,6 +24138,7 @@ const ARITHMETIC_KEY_OPERATORS = new Set([
|
|
|
24116
24138
|
"/",
|
|
24117
24139
|
"%"
|
|
24118
24140
|
]);
|
|
24141
|
+
const bindingMutationResultsByProgram = /* @__PURE__ */ new WeakMap();
|
|
24119
24142
|
const extractCandidateIdentifiers = (expression) => {
|
|
24120
24143
|
const node = stripParenExpression(expression);
|
|
24121
24144
|
if (isNodeOfType(node, "Identifier")) return [node];
|
|
@@ -24156,6 +24179,13 @@ const isArrayFromCall = (node) => {
|
|
|
24156
24179
|
const isBindingReassignedOrMutated = (referenceNode, bindingName) => {
|
|
24157
24180
|
const programRoot = findProgramRoot(referenceNode);
|
|
24158
24181
|
if (!programRoot) return false;
|
|
24182
|
+
let mutationResultsByBindingName = bindingMutationResultsByProgram.get(programRoot);
|
|
24183
|
+
if (!mutationResultsByBindingName) {
|
|
24184
|
+
mutationResultsByBindingName = /* @__PURE__ */ new Map();
|
|
24185
|
+
bindingMutationResultsByProgram.set(programRoot, mutationResultsByBindingName);
|
|
24186
|
+
}
|
|
24187
|
+
const cachedResult = mutationResultsByBindingName.get(bindingName);
|
|
24188
|
+
if (cachedResult !== void 0) return cachedResult;
|
|
24159
24189
|
let didFindWrite = false;
|
|
24160
24190
|
walkAst(programRoot, (child) => {
|
|
24161
24191
|
if (didFindWrite) return false;
|
|
@@ -24172,6 +24202,7 @@ const isBindingReassignedOrMutated = (referenceNode, bindingName) => {
|
|
|
24172
24202
|
return false;
|
|
24173
24203
|
}
|
|
24174
24204
|
});
|
|
24205
|
+
mutationResultsByBindingName.set(bindingName, didFindWrite);
|
|
24175
24206
|
return didFindWrite;
|
|
24176
24207
|
};
|
|
24177
24208
|
/**
|
|
@@ -24573,6 +24604,14 @@ const templateHasOuterMemberIdentity = (template, bindingFunction) => {
|
|
|
24573
24604
|
}
|
|
24574
24605
|
return false;
|
|
24575
24606
|
};
|
|
24607
|
+
const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
|
|
24608
|
+
const referencedItemNames = /* @__PURE__ */ new Set();
|
|
24609
|
+
for (const expression of template.expressions ?? []) {
|
|
24610
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
24611
|
+
if (isNodeOfType(unwrappedExpression, "Identifier") && itemNames.has(unwrappedExpression.name)) referencedItemNames.add(unwrappedExpression.name);
|
|
24612
|
+
}
|
|
24613
|
+
return referencedItemNames;
|
|
24614
|
+
};
|
|
24576
24615
|
const forLoopTestReadsDataLength = (test) => {
|
|
24577
24616
|
let didFindLengthRead = false;
|
|
24578
24617
|
walkAst(test, (child) => {
|
|
@@ -24709,9 +24748,11 @@ const noArrayIndexAsKey = defineRule({
|
|
|
24709
24748
|
} else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
|
|
24710
24749
|
const jsxElement = openingElement.parent;
|
|
24711
24750
|
if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
|
|
24751
|
+
const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
|
|
24752
|
+
const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET;
|
|
24712
24753
|
if (!containsStatefulDescendant(jsxElement, {
|
|
24713
|
-
memberRootNames:
|
|
24714
|
-
bareIdentifierNames: derivedNames
|
|
24754
|
+
memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET,
|
|
24755
|
+
bareIdentifierNames: primitiveItemNames.size > 0 ? new Set([...derivedNames, ...primitiveItemNames]) : derivedNames
|
|
24715
24756
|
})) return;
|
|
24716
24757
|
}
|
|
24717
24758
|
}
|
|
@@ -25272,10 +25313,10 @@ const declaresAnyDependency = (manifest) => DEPENDENCY_SECTION_NAMES.some((secti
|
|
|
25272
25313
|
const section = manifest[sectionName];
|
|
25273
25314
|
return typeof section === "object" && section !== null && Object.keys(section).length > 0;
|
|
25274
25315
|
});
|
|
25275
|
-
const declaresDependency = (manifest, dependencyName) => {
|
|
25276
|
-
|
|
25277
|
-
return
|
|
25278
|
-
};
|
|
25316
|
+
const declaresDependency = (manifest, dependencyName) => DEPENDENCY_SECTION_NAMES.some((sectionName) => {
|
|
25317
|
+
const section = manifest[sectionName];
|
|
25318
|
+
return typeof section === "object" && section !== null && Object.prototype.propertyIsEnumerable.call(section, dependencyName);
|
|
25319
|
+
});
|
|
25279
25320
|
const cachedPlatformByManifest = /* @__PURE__ */ new WeakMap();
|
|
25280
25321
|
const classifyPackagePlatform = (filename) => {
|
|
25281
25322
|
const manifest = readNearestPackageManifest(filename);
|
|
@@ -25522,25 +25563,23 @@ const isCallArgumentFunctionExpression = (node) => {
|
|
|
25522
25563
|
return parent.arguments.some((argumentNode) => argumentNode === node);
|
|
25523
25564
|
};
|
|
25524
25565
|
const isNestedRenderEvidenceBoundary = (node) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isCallArgumentFunctionExpression(node);
|
|
25525
|
-
const containsRenderOutput$1 = (
|
|
25526
|
-
|
|
25527
|
-
|
|
25528
|
-
|
|
25529
|
-
|
|
25530
|
-
|
|
25531
|
-
|
|
25532
|
-
|
|
25533
|
-
|
|
25534
|
-
|
|
25535
|
-
|
|
25536
|
-
}
|
|
25537
|
-
return false;
|
|
25566
|
+
const containsRenderOutput$1 = (rootNode, scopes) => {
|
|
25567
|
+
let hasRenderOutput = false;
|
|
25568
|
+
walkAst(rootNode, (node) => {
|
|
25569
|
+
if (hasRenderOutput) return false;
|
|
25570
|
+
if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
|
|
25571
|
+
if (node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS)) {
|
|
25572
|
+
hasRenderOutput = true;
|
|
25573
|
+
return false;
|
|
25574
|
+
}
|
|
25575
|
+
});
|
|
25576
|
+
return hasRenderOutput;
|
|
25538
25577
|
};
|
|
25539
25578
|
const renderOutputCache = /* @__PURE__ */ new WeakMap();
|
|
25540
25579
|
const functionContainsReactRenderOutput = (functionNode, scopes) => {
|
|
25541
25580
|
const cachedEntry = renderOutputCache.get(functionNode);
|
|
25542
25581
|
if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
|
|
25543
|
-
const hasRenderOutput = containsRenderOutput$1(functionNode,
|
|
25582
|
+
const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
|
|
25544
25583
|
renderOutputCache.set(functionNode, {
|
|
25545
25584
|
scopes,
|
|
25546
25585
|
hasRenderOutput
|
|
@@ -27346,7 +27385,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
27346
27385
|
let ancestor = setStateCall.parent;
|
|
27347
27386
|
while (ancestor) {
|
|
27348
27387
|
if (!lifecycleMember) {
|
|
27349
|
-
if (FUNCTION_NODE_TYPES$1.has(ancestor.type)) nestedFunctionCount += 1;
|
|
27388
|
+
if (FUNCTION_NODE_TYPES$1.has(ancestor.type) && (!isImmediatelyInvokedFunction(ancestor) || !("async" in ancestor) || ancestor.async === true)) nestedFunctionCount += 1;
|
|
27350
27389
|
if (isLifecycleMember(ancestor, lifecycleNames)) lifecycleMember = ancestor;
|
|
27351
27390
|
} else if (isEs5Component(ancestor) || isEs6Component(ancestor)) {
|
|
27352
27391
|
if (nestedFunctionCount > 1 && !options.disallowInNestedFunctions) return false;
|
|
@@ -27548,7 +27587,7 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
|
|
|
27548
27587
|
const body = lifecycleFunction.body;
|
|
27549
27588
|
if (!body) return derivedNames;
|
|
27550
27589
|
walkAst(body, (node) => {
|
|
27551
|
-
if (FUNCTION_NODE_TYPES.has(node.type)) return false;
|
|
27590
|
+
if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
|
|
27552
27591
|
if (!isNodeOfType(node, "VariableDeclarator")) return;
|
|
27553
27592
|
const init = node.init;
|
|
27554
27593
|
if (!init) return;
|
|
@@ -27558,6 +27597,67 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
|
|
|
27558
27597
|
return derivedNames;
|
|
27559
27598
|
};
|
|
27560
27599
|
const isStatefulOperand = (node, paramNames, derivedNames) => referencesAnyName(node, paramNames) || referencesAnyName(node, derivedNames) || containsThisStateOrProps(node);
|
|
27600
|
+
const getStaticMemberName = (node) => {
|
|
27601
|
+
if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
|
|
27602
|
+
return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
|
|
27603
|
+
};
|
|
27604
|
+
const getThisStateFieldName = (node) => {
|
|
27605
|
+
const unwrappedNode = stripParenExpression(node);
|
|
27606
|
+
if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
|
|
27607
|
+
const object = stripParenExpression(unwrappedNode.object);
|
|
27608
|
+
if (!isNodeOfType(object, "MemberExpression") || !isNodeOfType(stripParenExpression(object.object), "ThisExpression") || getStaticMemberName(object) !== "state") return null;
|
|
27609
|
+
return getStaticMemberName(unwrappedNode);
|
|
27610
|
+
};
|
|
27611
|
+
const collectLocalInitializers = (lifecycleFunction) => {
|
|
27612
|
+
const initializers = /* @__PURE__ */ new Map();
|
|
27613
|
+
const body = lifecycleFunction.body;
|
|
27614
|
+
if (!body) return initializers;
|
|
27615
|
+
walkAst(body, (node) => {
|
|
27616
|
+
if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
|
|
27617
|
+
if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.init) initializers.set(node.id.name, node.init);
|
|
27618
|
+
});
|
|
27619
|
+
return initializers;
|
|
27620
|
+
};
|
|
27621
|
+
const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
|
|
27622
|
+
if (readsPostMountValue(node)) return true;
|
|
27623
|
+
const referencedNames = /* @__PURE__ */ new Set();
|
|
27624
|
+
collectReferenceIdentifierNames(node, referencedNames);
|
|
27625
|
+
for (const referencedName of referencedNames) {
|
|
27626
|
+
if (visitedNames.has(referencedName)) continue;
|
|
27627
|
+
const initializer = localInitializers.get(referencedName);
|
|
27628
|
+
if (!initializer) continue;
|
|
27629
|
+
if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
|
|
27630
|
+
}
|
|
27631
|
+
return false;
|
|
27632
|
+
};
|
|
27633
|
+
const getSetStateFieldValue = (setStateCall, fieldName) => {
|
|
27634
|
+
if (!isNodeOfType(setStateCall, "CallExpression")) return null;
|
|
27635
|
+
const argument = setStateCall.arguments?.[0];
|
|
27636
|
+
if (!argument || !isNodeOfType(argument, "ObjectExpression")) return null;
|
|
27637
|
+
for (const property of argument.properties ?? []) {
|
|
27638
|
+
if (!isNodeOfType(property, "Property") || property.computed === true) continue;
|
|
27639
|
+
if ((isNodeOfType(property.key, "Identifier") && property.key.name || isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" && property.key.value || null) === fieldName) return property.value;
|
|
27640
|
+
}
|
|
27641
|
+
return null;
|
|
27642
|
+
};
|
|
27643
|
+
const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
|
|
27644
|
+
let qualifies = false;
|
|
27645
|
+
walkAst(test, (node) => {
|
|
27646
|
+
if (qualifies) return false;
|
|
27647
|
+
if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
|
|
27648
|
+
const leftFieldName = getThisStateFieldName(node.left);
|
|
27649
|
+
const rightFieldName = getThisStateFieldName(node.right);
|
|
27650
|
+
const fieldName = leftFieldName ?? rightFieldName;
|
|
27651
|
+
const comparedValue = leftFieldName ? node.right : node.left;
|
|
27652
|
+
if (!fieldName || !leftFieldName && !rightFieldName) return;
|
|
27653
|
+
const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
|
|
27654
|
+
if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
|
|
27655
|
+
if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
|
|
27656
|
+
qualifies = true;
|
|
27657
|
+
return false;
|
|
27658
|
+
});
|
|
27659
|
+
return qualifies;
|
|
27660
|
+
};
|
|
27561
27661
|
const isDiffGuardTest = (test, paramNames, derivedNames) => {
|
|
27562
27662
|
if (referencesAnyName(test, paramNames)) return true;
|
|
27563
27663
|
let qualifies = false;
|
|
@@ -27565,7 +27665,7 @@ const isDiffGuardTest = (test, paramNames, derivedNames) => {
|
|
|
27565
27665
|
if (qualifies) return false;
|
|
27566
27666
|
if (!isNodeOfType(node, "BinaryExpression")) return;
|
|
27567
27667
|
if (!EQUALITY_OPERATORS.has(node.operator)) return;
|
|
27568
|
-
if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames)) {
|
|
27668
|
+
if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames) && (referencesAnyName(node.left, derivedNames) || referencesAnyName(node.right, derivedNames))) {
|
|
27569
27669
|
qualifies = true;
|
|
27570
27670
|
return false;
|
|
27571
27671
|
}
|
|
@@ -27578,11 +27678,12 @@ const isInsideDiffGuard = (setStateCall) => {
|
|
|
27578
27678
|
const paramNames = /* @__PURE__ */ new Set();
|
|
27579
27679
|
for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
|
|
27580
27680
|
const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
|
|
27681
|
+
const localInitializers = collectLocalInitializers(lifecycleFunction);
|
|
27581
27682
|
let child = setStateCall;
|
|
27582
27683
|
let ancestor = setStateCall.parent;
|
|
27583
27684
|
while (ancestor && ancestor !== lifecycleFunction) {
|
|
27584
27685
|
const guardTest = isNodeOfType(ancestor, "IfStatement") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "ConditionalExpression") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right && ancestor.left || null;
|
|
27585
|
-
if (guardTest && isDiffGuardTest(guardTest, paramNames, derivedNames)) return true;
|
|
27686
|
+
if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers))) return true;
|
|
27586
27687
|
child = ancestor;
|
|
27587
27688
|
ancestor = ancestor.parent ?? null;
|
|
27588
27689
|
}
|
|
@@ -27763,7 +27864,16 @@ const producesOpaqueInstanceValue = (expression) => {
|
|
|
27763
27864
|
const collectSetterValueObservations = (componentBody, setterNames) => {
|
|
27764
27865
|
const plainFedSetterNames = /* @__PURE__ */ new Set();
|
|
27765
27866
|
const opaqueFedSetterNames = /* @__PURE__ */ new Set();
|
|
27867
|
+
const callbackRefSetterNames = /* @__PURE__ */ new Set();
|
|
27766
27868
|
walkComponentRespectingShadows(componentBody, /* @__PURE__ */ new Set(), (node, currentlyShadowed) => {
|
|
27869
|
+
if (isNodeOfType(node, "JSXAttribute")) {
|
|
27870
|
+
const attributeName = node.name;
|
|
27871
|
+
if (isNodeOfType(attributeName, "JSXIdentifier") && attributeName.name === "ref" && node.value && isNodeOfType(node.value, "JSXExpressionContainer")) {
|
|
27872
|
+
const expression = stripParenExpression(node.value.expression);
|
|
27873
|
+
if (isNodeOfType(expression, "Identifier") && setterNames.has(expression.name)) callbackRefSetterNames.add(expression.name);
|
|
27874
|
+
}
|
|
27875
|
+
return;
|
|
27876
|
+
}
|
|
27767
27877
|
if (!isNodeOfType(node, "CallExpression")) return;
|
|
27768
27878
|
if (!isNodeOfType(node.callee, "Identifier")) return;
|
|
27769
27879
|
const setterName = node.callee.name;
|
|
@@ -27780,21 +27890,10 @@ const collectSetterValueObservations = (componentBody, setterNames) => {
|
|
|
27780
27890
|
}, true);
|
|
27781
27891
|
return {
|
|
27782
27892
|
plainFedSetterNames,
|
|
27783
|
-
opaqueFedSetterNames
|
|
27893
|
+
opaqueFedSetterNames,
|
|
27894
|
+
callbackRefSetterNames
|
|
27784
27895
|
};
|
|
27785
27896
|
};
|
|
27786
|
-
const collectCallbackRefSetterNames = (componentBody) => {
|
|
27787
|
-
const callbackRefSetterNames = /* @__PURE__ */ new Set();
|
|
27788
|
-
walkAst(componentBody, (node) => {
|
|
27789
|
-
if (!isNodeOfType(node, "JSXAttribute")) return;
|
|
27790
|
-
const attributeName = node.name;
|
|
27791
|
-
if (isNodeOfType(attributeName, "JSXIdentifier") && attributeName.name === "ref" && node.value && isNodeOfType(node.value, "JSXExpressionContainer")) {
|
|
27792
|
-
const expression = stripParenExpression(node.value.expression);
|
|
27793
|
-
if (isNodeOfType(expression, "Identifier")) callbackRefSetterNames.add(expression.name);
|
|
27794
|
-
}
|
|
27795
|
-
});
|
|
27796
|
-
return callbackRefSetterNames;
|
|
27797
|
-
};
|
|
27798
27897
|
const collectFunctionLocalBindings = (functionNode) => {
|
|
27799
27898
|
const localBindings = /* @__PURE__ */ new Set();
|
|
27800
27899
|
if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return localBindings;
|
|
@@ -27806,8 +27905,8 @@ const collectFunctionLocalBindings = (functionNode) => {
|
|
|
27806
27905
|
return localBindings;
|
|
27807
27906
|
};
|
|
27808
27907
|
const collectBlockScopedBindings = (node) => {
|
|
27809
|
-
const blockBindings = /* @__PURE__ */ new Set();
|
|
27810
27908
|
if (isNodeOfType(node, "BlockStatement")) {
|
|
27909
|
+
const blockBindings = /* @__PURE__ */ new Set();
|
|
27811
27910
|
for (const statement of node.body ?? []) {
|
|
27812
27911
|
if (!isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
27813
27912
|
for (const declarator of statement.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
|
|
@@ -27815,17 +27914,22 @@ const collectBlockScopedBindings = (node) => {
|
|
|
27815
27914
|
return blockBindings;
|
|
27816
27915
|
}
|
|
27817
27916
|
if (isNodeOfType(node, "ForStatement") && isNodeOfType(node.init, "VariableDeclaration")) {
|
|
27917
|
+
const blockBindings = /* @__PURE__ */ new Set();
|
|
27818
27918
|
for (const declarator of node.init.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
|
|
27819
27919
|
return blockBindings;
|
|
27820
27920
|
}
|
|
27821
|
-
if ((isNodeOfType(node, "ForOfStatement") || isNodeOfType(node, "ForInStatement")) && isNodeOfType(node.left, "VariableDeclaration"))
|
|
27822
|
-
|
|
27921
|
+
if ((isNodeOfType(node, "ForOfStatement") || isNodeOfType(node, "ForInStatement")) && isNodeOfType(node.left, "VariableDeclaration")) {
|
|
27922
|
+
const blockBindings = /* @__PURE__ */ new Set();
|
|
27923
|
+
for (const declarator of node.left.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
|
|
27924
|
+
return blockBindings;
|
|
27925
|
+
}
|
|
27926
|
+
return null;
|
|
27823
27927
|
};
|
|
27824
27928
|
const walkComponentRespectingShadows = (node, shadowedStateNames, visit, isComponentBodyRoot = false) => {
|
|
27825
27929
|
if (!node || typeof node !== "object") return;
|
|
27826
27930
|
let nextShadowedStateNames = shadowedStateNames;
|
|
27827
|
-
const localBindings = isComponentBodyRoot ?
|
|
27828
|
-
if (localBindings.size > 0) {
|
|
27931
|
+
const localBindings = isComponentBodyRoot ? null : isFunctionLike$1(node) ? collectFunctionLocalBindings(node) : collectBlockScopedBindings(node);
|
|
27932
|
+
if (localBindings && localBindings.size > 0) {
|
|
27829
27933
|
const merged = new Set(shadowedStateNames);
|
|
27830
27934
|
for (const localName of localBindings) merged.add(localName);
|
|
27831
27935
|
nextShadowedStateNames = merged;
|
|
@@ -27851,11 +27955,10 @@ const noDirectStateMutation = defineRule({
|
|
|
27851
27955
|
const bindings = collectUseStateBindings(componentBody);
|
|
27852
27956
|
if (bindings.length === 0) return;
|
|
27853
27957
|
const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
|
|
27854
|
-
const callbackRefSetterNames = collectCallbackRefSetterNames(componentBody);
|
|
27855
27958
|
const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
|
|
27856
27959
|
const plainObjectStateValueNames = /* @__PURE__ */ new Set();
|
|
27857
27960
|
for (const binding of bindings) {
|
|
27858
|
-
if (callbackRefSetterNames.has(binding.setterName)) continue;
|
|
27961
|
+
if (setterValueObservations.callbackRefSetterNames.has(binding.setterName)) continue;
|
|
27859
27962
|
if (!isNodeOfType(binding.declarator.init, "CallExpression")) continue;
|
|
27860
27963
|
const initializerArgument = binding.declarator.init.arguments?.[0];
|
|
27861
27964
|
if (!initializerMarksPlainState(initializerArgument)) continue;
|
|
@@ -32573,25 +32676,60 @@ const resolveSettings$16 = (settings) => {
|
|
|
32573
32676
|
};
|
|
32574
32677
|
const isHocCall = (call, scopes) => {
|
|
32575
32678
|
if (!isNodeOfType(call, "CallExpression")) return false;
|
|
32576
|
-
|
|
32577
|
-
|
|
32679
|
+
if (isReactApiCall(call, REACT_HOC_NAMES, scopes, {
|
|
32680
|
+
allowGlobalReactNamespace: true,
|
|
32681
|
+
allowUnboundBareCalls: true
|
|
32682
|
+
})) return true;
|
|
32683
|
+
if (isReactHocMemberReference(call.callee, scopes)) return true;
|
|
32578
32684
|
if (!isNodeOfType(call.callee, "Identifier")) return false;
|
|
32579
32685
|
const symbol = scopes.symbolFor(call.callee);
|
|
32580
32686
|
if (!symbol) return false;
|
|
32581
|
-
return symbolMapsToHoc(symbol);
|
|
32687
|
+
return symbolMapsToHoc(symbol, scopes, /* @__PURE__ */ new Set());
|
|
32688
|
+
};
|
|
32689
|
+
const isReactImportEquals = (symbol) => {
|
|
32690
|
+
if (symbol.kind !== "ts-import-equals" || !isNodeOfType(symbol.declarationNode, "TSImportEqualsDeclaration")) return false;
|
|
32691
|
+
const moduleReference = symbol.declarationNode.moduleReference;
|
|
32692
|
+
return Boolean(isNodeOfType(moduleReference, "TSExternalModuleReference") && isNodeOfType(moduleReference.expression, "Literal") && typeof moduleReference.expression.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(moduleReference.expression.value));
|
|
32693
|
+
};
|
|
32694
|
+
const isRequireReactCall$1 = (node, scopes) => {
|
|
32695
|
+
if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "Identifier") || node.callee.name !== "require" || !scopes.isGlobalReference(node.callee)) return false;
|
|
32696
|
+
const moduleSpecifier = node.arguments[0];
|
|
32697
|
+
return Boolean(moduleSpecifier && isNodeOfType(moduleSpecifier, "Literal") && typeof moduleSpecifier.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(moduleSpecifier.value));
|
|
32698
|
+
};
|
|
32699
|
+
const isReactNamespaceExpression = (node, scopes) => {
|
|
32700
|
+
if (isRequireReactCall$1(node, scopes)) return true;
|
|
32701
|
+
if (!isNodeOfType(node, "Identifier")) return false;
|
|
32702
|
+
const symbol = scopes.symbolFor(node);
|
|
32703
|
+
if (!symbol) return node.name === "React" && scopes.isGlobalReference(node);
|
|
32704
|
+
if (symbol.initializer && isRequireReactCall$1(symbol.initializer, scopes)) return true;
|
|
32705
|
+
if (isReactImportEquals(symbol)) return true;
|
|
32706
|
+
return isReactNamespaceImport(node, scopes);
|
|
32707
|
+
};
|
|
32708
|
+
const isReactHocMemberReference = (node, scopes) => Boolean(isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.property, "Identifier") && REACT_HOC_NAMES.has(node.property.name) && isReactNamespaceExpression(node.object, scopes));
|
|
32709
|
+
const getDestructuredPropertyName$1 = (symbol) => {
|
|
32710
|
+
const property = symbol.bindingIdentifier.parent;
|
|
32711
|
+
if (!property || !isNodeOfType(property, "Property") || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
|
|
32712
|
+
if (isNodeOfType(property.key, "Identifier") && !property.computed) return property.key.name;
|
|
32713
|
+
if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
|
|
32714
|
+
return null;
|
|
32582
32715
|
};
|
|
32583
|
-
const symbolMapsToHoc = (symbol) => {
|
|
32584
|
-
if (
|
|
32585
|
-
|
|
32716
|
+
const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
32717
|
+
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
32718
|
+
visitedSymbolIds.add(symbol.id);
|
|
32719
|
+
if (symbol.kind === "import") {
|
|
32720
|
+
const importedName = getImportedName(symbol.declarationNode);
|
|
32721
|
+
return Boolean(isImportedFromReact(symbol) && importedName && REACT_HOC_NAMES.has(importedName));
|
|
32586
32722
|
}
|
|
32587
32723
|
const init = symbol.initializer;
|
|
32588
32724
|
if (!init) return false;
|
|
32589
|
-
|
|
32590
|
-
|
|
32591
|
-
|
|
32725
|
+
const destructuredPropertyName = getDestructuredPropertyName$1(symbol);
|
|
32726
|
+
if (destructuredPropertyName && REACT_HOC_NAMES.has(destructuredPropertyName) && isReactNamespaceExpression(init, scopes)) return true;
|
|
32727
|
+
if (isReactHocMemberReference(init, scopes)) return true;
|
|
32728
|
+
if (isNodeOfType(init, "Identifier")) {
|
|
32729
|
+
const initializedFromSymbol = scopes.symbolFor(init);
|
|
32730
|
+
if (initializedFromSymbol) return symbolMapsToHoc(initializedFromSymbol, scopes, visitedSymbolIds);
|
|
32731
|
+
return REACT_HOC_NAMES.has(init.name) && scopes.isGlobalReference(init);
|
|
32592
32732
|
}
|
|
32593
|
-
if (isNodeOfType(init, "Identifier") && REACT_HOC_NAMES.has(init.name)) return true;
|
|
32594
|
-
if (REACT_HOC_NAMES.has(symbol.name) && isNodeOfType(init, "Identifier") && init.name === "React") return true;
|
|
32595
32733
|
return false;
|
|
32596
32734
|
};
|
|
32597
32735
|
const isTrivialPassthroughChild = (child) => {
|
|
@@ -32649,26 +32787,14 @@ const isHocComponent = (call, scopes) => {
|
|
|
32649
32787
|
};
|
|
32650
32788
|
const containsJsx = (root) => {
|
|
32651
32789
|
let found = false;
|
|
32652
|
-
|
|
32653
|
-
if (found) return;
|
|
32790
|
+
walkAst(root, (node) => {
|
|
32791
|
+
if (found) return false;
|
|
32654
32792
|
if (node.type === "JSXElement" || node.type === "JSXFragment") {
|
|
32655
32793
|
found = true;
|
|
32656
|
-
return;
|
|
32657
|
-
}
|
|
32658
|
-
if (node !== root) {
|
|
32659
|
-
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "ClassDeclaration" || node.type === "ClassExpression") return;
|
|
32660
|
-
}
|
|
32661
|
-
const record = node;
|
|
32662
|
-
for (const key of Object.keys(record)) {
|
|
32663
|
-
if (key === "parent") continue;
|
|
32664
|
-
const child = record[key];
|
|
32665
|
-
if (Array.isArray(child)) {
|
|
32666
|
-
for (const item of child) if (isAstNode(item)) visit(item);
|
|
32667
|
-
} else if (isAstNode(child)) visit(child);
|
|
32668
|
-
if (found) return;
|
|
32794
|
+
return false;
|
|
32669
32795
|
}
|
|
32670
|
-
|
|
32671
|
-
|
|
32796
|
+
if (node !== root && (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "ClassDeclaration" || node.type === "ClassExpression")) return false;
|
|
32797
|
+
});
|
|
32672
32798
|
return found;
|
|
32673
32799
|
};
|
|
32674
32800
|
const expressionContainsJsx = (expression) => {
|
|
@@ -32692,7 +32818,7 @@ const unwrapTsCast = (expression) => {
|
|
|
32692
32818
|
while (current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSNonNullExpression") current = stripParenExpression(current.expression);
|
|
32693
32819
|
return current;
|
|
32694
32820
|
};
|
|
32695
|
-
const collectReExportedNames = (program) => {
|
|
32821
|
+
const collectReExportedNames = (program, scopes) => {
|
|
32696
32822
|
const names = /* @__PURE__ */ new Set();
|
|
32697
32823
|
if (!isNodeOfType(program, "Program")) return names;
|
|
32698
32824
|
for (const statement of program.body) {
|
|
@@ -32716,8 +32842,7 @@ const collectReExportedNames = (program) => {
|
|
|
32716
32842
|
if (!declarator.init) continue;
|
|
32717
32843
|
const init = unwrapTsCast(declarator.init);
|
|
32718
32844
|
if (isNodeOfType(init, "CallExpression")) {
|
|
32719
|
-
|
|
32720
|
-
if (calleeName && REACT_HOC_NAMES.has(calleeName)) {
|
|
32845
|
+
if (isHocCall(init, scopes)) {
|
|
32721
32846
|
const wrappedArg = init.arguments[0];
|
|
32722
32847
|
if (wrappedArg && isNodeOfType(wrappedArg, "Identifier")) names.add(wrappedArg.name);
|
|
32723
32848
|
}
|
|
@@ -32786,16 +32911,7 @@ const recordComponent = (context, name, reportNode, isStateless) => {
|
|
|
32786
32911
|
isStateless
|
|
32787
32912
|
});
|
|
32788
32913
|
};
|
|
32789
|
-
const walkChildren = (node, context) =>
|
|
32790
|
-
const record = node;
|
|
32791
|
-
for (const key of Object.keys(record)) {
|
|
32792
|
-
if (key === "parent") continue;
|
|
32793
|
-
const child = record[key];
|
|
32794
|
-
if (Array.isArray(child)) {
|
|
32795
|
-
for (const item of child) if (isAstNode(item)) walkComponentSearch(item, context);
|
|
32796
|
-
} else if (isAstNode(child)) walkComponentSearch(child, context);
|
|
32797
|
-
}
|
|
32798
|
-
};
|
|
32914
|
+
const walkChildren = (node, context) => forEachChildNode(node, context.visitChild);
|
|
32799
32915
|
const walkComponentSearch = (node, context) => {
|
|
32800
32916
|
if (isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) {
|
|
32801
32917
|
if (isEs6Component(node)) {
|
|
@@ -32897,12 +33013,13 @@ const noMultiComp = defineRule({
|
|
|
32897
33013
|
components: [],
|
|
32898
33014
|
componentDepth: 0,
|
|
32899
33015
|
currentVarName: null,
|
|
32900
|
-
scopes: context.scopes
|
|
33016
|
+
scopes: context.scopes,
|
|
33017
|
+
visitChild: (child) => walkComponentSearch(child, visitContext)
|
|
32901
33018
|
};
|
|
32902
33019
|
for (const statement of node.body) walkComponentSearch(statement, visitContext);
|
|
32903
33020
|
const flagged = settings.ignoreStateless ? visitContext.components.filter((component) => !component.isStateless) : visitContext.components;
|
|
32904
33021
|
if (flagged.length <= 2) return;
|
|
32905
|
-
const reExportedNames = collectReExportedNames(node);
|
|
33022
|
+
const reExportedNames = collectReExportedNames(node, context.scopes);
|
|
32906
33023
|
const exportedCount = flagged.filter((component) => isExportedDeclaration(component.reportNode, reExportedNames)).length;
|
|
32907
33024
|
if (exportedCount >= flagged.length || flagged.length >= 4 && exportedCount >= Math.floor(flagged.length * .7) || flagged.length >= 8 && exportedCount >= Math.floor(flagged.length * .5)) return;
|
|
32908
33025
|
const isSmallFeatureModule = exportedCount > 0 && exportedCount <= 2 && exportedCount < flagged.length;
|
|
@@ -35162,6 +35279,27 @@ const hasPreviousValueDep = (effectNode, depElements) => {
|
|
|
35162
35279
|
}
|
|
35163
35280
|
return false;
|
|
35164
35281
|
};
|
|
35282
|
+
const isStateLikeDependency = (analysis, element, isPropName) => {
|
|
35283
|
+
if (!isNodeOfType(element, "Identifier") || isPropName(element.name)) return false;
|
|
35284
|
+
if (!analysis) return true;
|
|
35285
|
+
const reference = getRef(analysis, element);
|
|
35286
|
+
if (!reference) return true;
|
|
35287
|
+
const upstreamReferences = getUpstreamRefs(analysis, reference);
|
|
35288
|
+
if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
|
|
35289
|
+
return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
|
|
35290
|
+
};
|
|
35291
|
+
const getRefHeldPropCallbackName = (callExpression, isPropName) => {
|
|
35292
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
35293
|
+
if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "current") return null;
|
|
35294
|
+
const receiver = stripParenExpression(callee.object);
|
|
35295
|
+
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
35296
|
+
const binding = findVariableInitializer(callExpression, receiver.name);
|
|
35297
|
+
if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
|
|
35298
|
+
if (getCalleeName$2(binding.initializer) !== "useRef") return null;
|
|
35299
|
+
const callbackArgument = binding.initializer.arguments?.[0];
|
|
35300
|
+
if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
|
|
35301
|
+
return isPropName(callbackArgument.name) ? callbackArgument.name : null;
|
|
35302
|
+
};
|
|
35165
35303
|
const noPropCallbackInEffect = defineRule({
|
|
35166
35304
|
id: "no-prop-callback-in-effect",
|
|
35167
35305
|
title: "Parent kept in sync with a callback effect",
|
|
@@ -35178,11 +35316,11 @@ const noPropCallbackInEffect = defineRule({
|
|
|
35178
35316
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
35179
35317
|
const depsNode = node.arguments[1];
|
|
35180
35318
|
if (!isNodeOfType(depsNode, "ArrayExpression") || !depsNode.elements?.length) return;
|
|
35181
|
-
const
|
|
35319
|
+
const analysis = getProgramAnalysis(node);
|
|
35320
|
+
const stateLikeDeps = (depsNode.elements ?? []).filter((element) => element && isStateLikeDependency(analysis, element, propStackTracker.isPropName));
|
|
35182
35321
|
if (stateLikeDeps.length === 0) return;
|
|
35183
35322
|
if (isRefLatchGuardedEffect(callback.body)) return;
|
|
35184
35323
|
if (hasPreviousValueDep(node, depsNode.elements ?? [])) return;
|
|
35185
|
-
const analysis = getProgramAnalysis(node);
|
|
35186
35324
|
if (analysis) {
|
|
35187
35325
|
const stateLikeDepRefs = [];
|
|
35188
35326
|
for (const element of stateLikeDeps) {
|
|
@@ -35194,9 +35332,9 @@ const noPropCallbackInEffect = defineRule({
|
|
|
35194
35332
|
const reportedNodes = /* @__PURE__ */ new Set();
|
|
35195
35333
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
35196
35334
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
35197
|
-
|
|
35198
|
-
const calleeName =
|
|
35199
|
-
if (!
|
|
35335
|
+
const directCallee = stripParenExpression(child.callee);
|
|
35336
|
+
const calleeName = isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName);
|
|
35337
|
+
if (!calleeName) return;
|
|
35200
35338
|
if (!isResultDiscardedCall(child)) return;
|
|
35201
35339
|
if (reportedNodes.has(child)) return;
|
|
35202
35340
|
reportedNodes.add(child);
|
|
@@ -39370,34 +39508,18 @@ const resolveSettings$8 = (settings) => {
|
|
|
39370
39508
|
};
|
|
39371
39509
|
const expressionContainsJsxOrCreateElement = (root) => {
|
|
39372
39510
|
let found = false;
|
|
39373
|
-
|
|
39374
|
-
if (found) return;
|
|
39511
|
+
walkAst(root, (node) => {
|
|
39512
|
+
if (found) return false;
|
|
39513
|
+
if (node !== root && NESTED_FUNCTION_TYPES.has(node.type)) return false;
|
|
39375
39514
|
if (node.type === "JSXElement" || node.type === "JSXFragment") {
|
|
39376
39515
|
found = true;
|
|
39377
|
-
return;
|
|
39516
|
+
return false;
|
|
39378
39517
|
}
|
|
39379
39518
|
if (isNodeOfType(node, "CallExpression") && isCreateElementCall(node)) {
|
|
39380
39519
|
found = true;
|
|
39381
|
-
return;
|
|
39382
|
-
}
|
|
39383
|
-
const record = node;
|
|
39384
|
-
for (const key of Object.keys(record)) {
|
|
39385
|
-
if (key === "parent") continue;
|
|
39386
|
-
const child = record[key];
|
|
39387
|
-
if (Array.isArray(child)) for (const item of child) {
|
|
39388
|
-
if (!isAstNode(item)) continue;
|
|
39389
|
-
if (NESTED_FUNCTION_TYPES.has(item.type)) continue;
|
|
39390
|
-
visit(item);
|
|
39391
|
-
if (found) return;
|
|
39392
|
-
}
|
|
39393
|
-
else if (isAstNode(child)) {
|
|
39394
|
-
if (NESTED_FUNCTION_TYPES.has(child.type)) continue;
|
|
39395
|
-
visit(child);
|
|
39396
|
-
}
|
|
39397
|
-
if (found) return;
|
|
39520
|
+
return false;
|
|
39398
39521
|
}
|
|
39399
|
-
};
|
|
39400
|
-
visit(root);
|
|
39522
|
+
});
|
|
39401
39523
|
return found;
|
|
39402
39524
|
};
|
|
39403
39525
|
const isReactClassComponent = (classNode) => {
|
|
@@ -40323,19 +40445,6 @@ const classifyExport = (name, reportNode, isFunction, initializer, state) => {
|
|
|
40323
40445
|
reportNode
|
|
40324
40446
|
};
|
|
40325
40447
|
};
|
|
40326
|
-
const collectRelevantNodes = (programRoot) => {
|
|
40327
|
-
const exportNodes = [];
|
|
40328
|
-
const componentCandidates = [];
|
|
40329
|
-
walkAst(programRoot, (child) => {
|
|
40330
|
-
const childType = child.type;
|
|
40331
|
-
if (childType === "ExportAllDeclaration" || childType === "ExportDefaultDeclaration" || childType === "ExportNamedDeclaration") exportNodes.push(child);
|
|
40332
|
-
else if (childType === "FunctionDeclaration" || childType === "VariableDeclarator" || childType === "ClassDeclaration") componentCandidates.push(child);
|
|
40333
|
-
});
|
|
40334
|
-
return {
|
|
40335
|
-
exportNodes,
|
|
40336
|
-
componentCandidates
|
|
40337
|
-
};
|
|
40338
|
-
};
|
|
40339
40448
|
const isEntryPointFile = (filename) => {
|
|
40340
40449
|
const lastSlash = Math.max(filename.lastIndexOf("/"), filename.lastIndexOf("\\"));
|
|
40341
40450
|
const basename = lastSlash === -1 ? filename : filename.slice(lastSlash + 1);
|
|
@@ -40374,197 +40483,212 @@ const onlyExportComponents = defineRule({
|
|
|
40374
40483
|
category: "Architecture",
|
|
40375
40484
|
create: (context) => {
|
|
40376
40485
|
const settings = resolveSettings$6(context.settings);
|
|
40377
|
-
|
|
40378
|
-
|
|
40379
|
-
|
|
40380
|
-
|
|
40381
|
-
|
|
40382
|
-
|
|
40383
|
-
|
|
40384
|
-
|
|
40385
|
-
|
|
40386
|
-
|
|
40387
|
-
|
|
40388
|
-
|
|
40389
|
-
|
|
40390
|
-
|
|
40391
|
-
|
|
40392
|
-
|
|
40393
|
-
|
|
40394
|
-
|
|
40395
|
-
|
|
40396
|
-
|
|
40397
|
-
|
|
40398
|
-
|
|
40399
|
-
|
|
40486
|
+
if (!isFileNameAllowed(normalizeFilename(context.filename ?? ""), settings.checkJS)) return {};
|
|
40487
|
+
const exportNodes = [];
|
|
40488
|
+
const componentCandidates = [];
|
|
40489
|
+
const pushExportNode = (node) => {
|
|
40490
|
+
exportNodes.push(node);
|
|
40491
|
+
};
|
|
40492
|
+
const pushComponentCandidate = (node) => {
|
|
40493
|
+
componentCandidates.push(node);
|
|
40494
|
+
};
|
|
40495
|
+
return {
|
|
40496
|
+
ExportAllDeclaration: pushExportNode,
|
|
40497
|
+
ExportDefaultDeclaration: pushExportNode,
|
|
40498
|
+
ExportNamedDeclaration: pushExportNode,
|
|
40499
|
+
FunctionDeclaration: pushComponentCandidate,
|
|
40500
|
+
VariableDeclarator: pushComponentCandidate,
|
|
40501
|
+
ClassDeclaration: pushComponentCandidate,
|
|
40502
|
+
"Program:exit"() {
|
|
40503
|
+
const localComponentNames = /* @__PURE__ */ new Set();
|
|
40504
|
+
const state = {
|
|
40505
|
+
customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
|
|
40506
|
+
allowExportNames: new Set(settings.allowExportNames),
|
|
40507
|
+
allowConstantExport: settings.allowConstantExport,
|
|
40508
|
+
localComponentNames,
|
|
40509
|
+
scopes: context.scopes
|
|
40510
|
+
};
|
|
40511
|
+
for (const child of componentCandidates) {
|
|
40512
|
+
if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
|
|
40513
|
+
if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) && functionContainsReactRenderOutput(child, context.scopes)) localComponentNames.add(child.id.name);
|
|
40514
|
+
}
|
|
40515
|
+
if (isNodeOfType(child, "ClassDeclaration") && child.id) {
|
|
40516
|
+
if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
|
|
40517
|
+
}
|
|
40518
|
+
if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
|
|
40519
|
+
const initializer = child.init;
|
|
40520
|
+
if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) {
|
|
40521
|
+
const expression = initializer ? skipTsExpression(initializer) : null;
|
|
40522
|
+
if (!(expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"))) || functionContainsReactRenderOutput(expression, context.scopes)) localComponentNames.add(child.id.name);
|
|
40523
|
+
}
|
|
40400
40524
|
}
|
|
40401
40525
|
}
|
|
40402
|
-
|
|
40403
|
-
|
|
40404
|
-
|
|
40405
|
-
|
|
40406
|
-
|
|
40407
|
-
|
|
40408
|
-
|
|
40409
|
-
|
|
40410
|
-
|
|
40411
|
-
|
|
40412
|
-
|
|
40413
|
-
|
|
40414
|
-
|
|
40415
|
-
|
|
40416
|
-
|
|
40417
|
-
|
|
40418
|
-
|
|
40419
|
-
|
|
40420
|
-
|
|
40421
|
-
|
|
40422
|
-
|
|
40423
|
-
|
|
40424
|
-
|
|
40425
|
-
|
|
40426
|
-
|
|
40427
|
-
|
|
40526
|
+
const exports = [];
|
|
40527
|
+
let hasReactExport = false;
|
|
40528
|
+
let hasAnyExports = false;
|
|
40529
|
+
const isExportedNodeIds = /* @__PURE__ */ new WeakSet();
|
|
40530
|
+
for (const child of exportNodes) {
|
|
40531
|
+
if (isNodeOfType(child, "ExportAllDeclaration")) {
|
|
40532
|
+
if (child.exportKind === "type") continue;
|
|
40533
|
+
hasAnyExports = true;
|
|
40534
|
+
context.report({
|
|
40535
|
+
node: child,
|
|
40536
|
+
message: EXPORT_ALL_MESSAGE
|
|
40537
|
+
});
|
|
40538
|
+
continue;
|
|
40539
|
+
}
|
|
40540
|
+
if (isNodeOfType(child, "ExportDefaultDeclaration")) {
|
|
40541
|
+
hasAnyExports = true;
|
|
40542
|
+
const declaration = child.declaration;
|
|
40543
|
+
const stripped = skipTsExpression(declaration);
|
|
40544
|
+
if (isNodeOfType(stripped, "FunctionDeclaration") || isNodeOfType(stripped, "FunctionExpression")) {
|
|
40545
|
+
if (stripped.id) {
|
|
40546
|
+
const idNode = stripped.id;
|
|
40547
|
+
isExportedNodeIds.add(stripped);
|
|
40548
|
+
exports.push(classifyExport(idNode.name, idNode, true, null, state));
|
|
40549
|
+
} else {
|
|
40550
|
+
context.report({
|
|
40551
|
+
node: stripped,
|
|
40552
|
+
message: ANONYMOUS_MESSAGE
|
|
40553
|
+
});
|
|
40554
|
+
hasReactExport = true;
|
|
40555
|
+
}
|
|
40556
|
+
continue;
|
|
40557
|
+
}
|
|
40558
|
+
if (isNodeOfType(stripped, "ClassDeclaration") || isNodeOfType(stripped, "ClassExpression")) {
|
|
40559
|
+
if (stripped.id) {
|
|
40560
|
+
const idNode = stripped.id;
|
|
40561
|
+
isExportedNodeIds.add(stripped);
|
|
40562
|
+
if (isReactComponentName(idNode.name) && isEs6Component(stripped)) hasReactExport = true;
|
|
40563
|
+
else exports.push({
|
|
40564
|
+
kind: "non-component",
|
|
40565
|
+
reportNode: idNode
|
|
40566
|
+
});
|
|
40567
|
+
} else context.report({
|
|
40428
40568
|
node: stripped,
|
|
40429
40569
|
message: ANONYMOUS_MESSAGE
|
|
40430
40570
|
});
|
|
40431
|
-
|
|
40571
|
+
continue;
|
|
40432
40572
|
}
|
|
40433
|
-
|
|
40434
|
-
|
|
40435
|
-
|
|
40436
|
-
|
|
40437
|
-
|
|
40438
|
-
|
|
40439
|
-
|
|
40440
|
-
|
|
40573
|
+
if (isNodeOfType(stripped, "Identifier")) {
|
|
40574
|
+
exports.push(classifyExport(stripped.name, stripped, false, null, state));
|
|
40575
|
+
continue;
|
|
40576
|
+
}
|
|
40577
|
+
if (isNodeOfType(stripped, "CallExpression")) {
|
|
40578
|
+
if (isRouteFactoryCall(stripped)) {
|
|
40579
|
+
hasReactExport = true;
|
|
40580
|
+
continue;
|
|
40581
|
+
}
|
|
40582
|
+
const isHoc = isHocCallee(stripped.callee, state);
|
|
40583
|
+
const firstArg = stripped.arguments[0];
|
|
40584
|
+
const firstArgIsValid = Boolean(firstArg) && (() => {
|
|
40585
|
+
if (!firstArg) return false;
|
|
40586
|
+
const expression = skipTsExpression(firstArg);
|
|
40587
|
+
if (isNodeOfType(expression, "Identifier")) return true;
|
|
40588
|
+
if (isNodeOfType(expression, "FunctionExpression") && expression.id) return true;
|
|
40589
|
+
if (isNodeOfType(expression, "CallExpression") && isHocCallee(expression.callee, state)) return true;
|
|
40590
|
+
return false;
|
|
40591
|
+
})();
|
|
40592
|
+
if (isHoc && firstArgIsValid) hasReactExport = true;
|
|
40593
|
+
else if (!isHoc && isConfigOnlyFactoryCall(stripped)) exports.push({
|
|
40441
40594
|
kind: "non-component",
|
|
40442
|
-
reportNode:
|
|
40595
|
+
reportNode: stripped
|
|
40596
|
+
});
|
|
40597
|
+
else context.report({
|
|
40598
|
+
node: stripped,
|
|
40599
|
+
message: ANONYMOUS_MESSAGE
|
|
40600
|
+
});
|
|
40601
|
+
continue;
|
|
40602
|
+
}
|
|
40603
|
+
if (isNodeOfType(stripped, "ObjectExpression")) {
|
|
40604
|
+
context.report({
|
|
40605
|
+
node: stripped,
|
|
40606
|
+
message: objectExpressionBundlesComponents(stripped, state) ? NAMESPACE_OBJECT_MESSAGE : ANONYMOUS_MESSAGE
|
|
40607
|
+
});
|
|
40608
|
+
continue;
|
|
40609
|
+
}
|
|
40610
|
+
if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "Literal")) {
|
|
40611
|
+
context.report({
|
|
40612
|
+
node: stripped,
|
|
40613
|
+
message: ANONYMOUS_MESSAGE
|
|
40443
40614
|
});
|
|
40444
|
-
} else context.report({
|
|
40445
|
-
node: stripped,
|
|
40446
|
-
message: ANONYMOUS_MESSAGE
|
|
40447
|
-
});
|
|
40448
|
-
continue;
|
|
40449
|
-
}
|
|
40450
|
-
if (isNodeOfType(stripped, "Identifier")) {
|
|
40451
|
-
exports.push(classifyExport(stripped.name, stripped, false, null, state));
|
|
40452
|
-
continue;
|
|
40453
|
-
}
|
|
40454
|
-
if (isNodeOfType(stripped, "CallExpression")) {
|
|
40455
|
-
if (isRouteFactoryCall(stripped)) {
|
|
40456
|
-
hasReactExport = true;
|
|
40457
40615
|
continue;
|
|
40458
40616
|
}
|
|
40459
|
-
const isHoc = isHocCallee(stripped.callee, state);
|
|
40460
|
-
const firstArg = stripped.arguments[0];
|
|
40461
|
-
const firstArgIsValid = Boolean(firstArg) && (() => {
|
|
40462
|
-
if (!firstArg) return false;
|
|
40463
|
-
const expression = skipTsExpression(firstArg);
|
|
40464
|
-
if (isNodeOfType(expression, "Identifier")) return true;
|
|
40465
|
-
if (isNodeOfType(expression, "FunctionExpression") && expression.id) return true;
|
|
40466
|
-
if (isNodeOfType(expression, "CallExpression") && isHocCallee(expression.callee, state)) return true;
|
|
40467
|
-
return false;
|
|
40468
|
-
})();
|
|
40469
|
-
if (isHoc && firstArgIsValid) hasReactExport = true;
|
|
40470
|
-
else if (!isHoc && isConfigOnlyFactoryCall(stripped)) exports.push({
|
|
40471
|
-
kind: "non-component",
|
|
40472
|
-
reportNode: stripped
|
|
40473
|
-
});
|
|
40474
|
-
else context.report({
|
|
40475
|
-
node: stripped,
|
|
40476
|
-
message: ANONYMOUS_MESSAGE
|
|
40477
|
-
});
|
|
40478
|
-
continue;
|
|
40479
|
-
}
|
|
40480
|
-
if (isNodeOfType(stripped, "ObjectExpression")) {
|
|
40481
|
-
context.report({
|
|
40482
|
-
node: stripped,
|
|
40483
|
-
message: objectExpressionBundlesComponents(stripped, state) ? NAMESPACE_OBJECT_MESSAGE : ANONYMOUS_MESSAGE
|
|
40484
|
-
});
|
|
40485
|
-
continue;
|
|
40486
|
-
}
|
|
40487
|
-
if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "Literal")) {
|
|
40488
40617
|
context.report({
|
|
40489
|
-
node:
|
|
40618
|
+
node: child,
|
|
40490
40619
|
message: ANONYMOUS_MESSAGE
|
|
40491
40620
|
});
|
|
40492
40621
|
continue;
|
|
40493
40622
|
}
|
|
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
|
-
});
|
|
40623
|
+
if (isNodeOfType(child, "ExportNamedDeclaration")) {
|
|
40624
|
+
if (child.exportKind === "type") continue;
|
|
40625
|
+
hasAnyExports = true;
|
|
40626
|
+
if (child.declaration) {
|
|
40627
|
+
const declaration = child.declaration;
|
|
40628
|
+
if (isNodeOfType(declaration, "FunctionDeclaration") && declaration.id) {
|
|
40629
|
+
isExportedNodeIds.add(declaration);
|
|
40630
|
+
exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state));
|
|
40631
|
+
} else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
|
|
40632
|
+
isExportedNodeIds.add(declaration);
|
|
40633
|
+
if (isReactComponentName(declaration.id.name) && isEs6Component(declaration)) exports.push({ kind: "react-component" });
|
|
40634
|
+
else exports.push({
|
|
40635
|
+
kind: "non-component",
|
|
40636
|
+
reportNode: declaration.id
|
|
40637
|
+
});
|
|
40638
|
+
} else if (isNodeOfType(declaration, "VariableDeclaration")) for (const declarator of declaration.declarations) {
|
|
40639
|
+
if (!isNodeOfType(declarator.id, "Identifier")) continue;
|
|
40640
|
+
isExportedNodeIds.add(declarator);
|
|
40641
|
+
const isFunction = canBeReactFunctionComponent(declarator.init ?? null, state);
|
|
40642
|
+
exports.push(classifyExport(declarator.id.name, declarator.id, isFunction, declarator.init, state));
|
|
40643
|
+
}
|
|
40644
|
+
else if (declaration.type === "TSEnumDeclaration" || declaration.type === "TSInterfaceDeclaration" || declaration.type === "TSTypeAliasDeclaration") {
|
|
40645
|
+
if (declaration.type === "TSEnumDeclaration") exports.push({
|
|
40646
|
+
kind: "non-component",
|
|
40647
|
+
reportNode: declaration
|
|
40648
|
+
});
|
|
40649
|
+
}
|
|
40526
40650
|
}
|
|
40527
|
-
|
|
40528
|
-
|
|
40529
|
-
|
|
40530
|
-
|
|
40531
|
-
|
|
40532
|
-
|
|
40533
|
-
|
|
40534
|
-
|
|
40535
|
-
|
|
40536
|
-
|
|
40537
|
-
|
|
40538
|
-
|
|
40539
|
-
|
|
40540
|
-
|
|
40541
|
-
|
|
40542
|
-
|
|
40543
|
-
|
|
40544
|
-
|
|
40545
|
-
|
|
40651
|
+
const isReExportFromSource = Boolean(child.source);
|
|
40652
|
+
for (const specifier of child.specifiers ?? []) {
|
|
40653
|
+
if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
|
|
40654
|
+
const exported = specifier.exported;
|
|
40655
|
+
const local = specifier.local;
|
|
40656
|
+
let exportedName = null;
|
|
40657
|
+
if (exported && isNodeOfType(exported, "Identifier")) exportedName = exported.name;
|
|
40658
|
+
const localName = local && isNodeOfType(local, "Identifier") ? local.name : null;
|
|
40659
|
+
const reportNode = specifier;
|
|
40660
|
+
let entry;
|
|
40661
|
+
if (exportedName === "default" && localName) entry = classifyExport(localName, reportNode, false, null, state);
|
|
40662
|
+
else if (exportedName) entry = classifyExport(exportedName, reportNode, false, null, state);
|
|
40663
|
+
else {
|
|
40664
|
+
entry = {
|
|
40665
|
+
kind: "non-component",
|
|
40666
|
+
reportNode
|
|
40667
|
+
};
|
|
40668
|
+
if (localName && isReactComponentName(localName)) exports.push({ kind: "react-component" });
|
|
40669
|
+
}
|
|
40670
|
+
if (isReExportFromSource && entry.kind !== "react-component") continue;
|
|
40671
|
+
exports.push(entry);
|
|
40546
40672
|
}
|
|
40547
|
-
if (isReExportFromSource && entry.kind !== "react-component") continue;
|
|
40548
|
-
exports.push(entry);
|
|
40549
40673
|
}
|
|
40550
40674
|
}
|
|
40551
|
-
|
|
40552
|
-
|
|
40553
|
-
for (const entry of exports) if (entry.kind === "namespace-object") context.report({
|
|
40554
|
-
node: entry.reportNode,
|
|
40555
|
-
message: NAMESPACE_OBJECT_MESSAGE
|
|
40556
|
-
});
|
|
40557
|
-
if (hasAnyExports && hasReactExport) for (const entry of exports) {
|
|
40558
|
-
if (entry.kind === "non-component") context.report({
|
|
40675
|
+
for (const entry of exports) if (entry.kind === "react-component") hasReactExport = true;
|
|
40676
|
+
for (const entry of exports) if (entry.kind === "namespace-object") context.report({
|
|
40559
40677
|
node: entry.reportNode,
|
|
40560
|
-
message:
|
|
40561
|
-
});
|
|
40562
|
-
if (entry.kind === "react-context") context.report({
|
|
40563
|
-
node: entry.reportNode,
|
|
40564
|
-
message: REACT_CONTEXT_MESSAGE
|
|
40678
|
+
message: NAMESPACE_OBJECT_MESSAGE
|
|
40565
40679
|
});
|
|
40680
|
+
if (hasAnyExports && hasReactExport) for (const entry of exports) {
|
|
40681
|
+
if (entry.kind === "non-component") context.report({
|
|
40682
|
+
node: entry.reportNode,
|
|
40683
|
+
message: NAMED_EXPORT_MESSAGE
|
|
40684
|
+
});
|
|
40685
|
+
if (entry.kind === "react-context") context.report({
|
|
40686
|
+
node: entry.reportNode,
|
|
40687
|
+
message: REACT_CONTEXT_MESSAGE
|
|
40688
|
+
});
|
|
40689
|
+
}
|
|
40566
40690
|
}
|
|
40567
|
-
}
|
|
40691
|
+
};
|
|
40568
40692
|
}
|
|
40569
40693
|
});
|
|
40570
40694
|
//#endregion
|
|
@@ -40666,7 +40790,12 @@ const postmessageOriginRisk = defineRule({
|
|
|
40666
40790
|
scan: (file) => {
|
|
40667
40791
|
if (!isProductionSourcePath(file.relativePath)) return [];
|
|
40668
40792
|
if (WORKER_FILE_PATH_PATTERN.test(file.relativePath)) return [];
|
|
40669
|
-
|
|
40793
|
+
if (!file.content.includes("addEventListener") && !file.content.includes("onmessage")) return [];
|
|
40794
|
+
const ast = parseSourceText({
|
|
40795
|
+
filename: file.absolutePath,
|
|
40796
|
+
sourceText: file.content,
|
|
40797
|
+
shouldAttachParentReferences: false
|
|
40798
|
+
});
|
|
40670
40799
|
if (ast === null) return [];
|
|
40671
40800
|
const findings = [];
|
|
40672
40801
|
walkAst(ast, (node) => {
|
|
@@ -43046,15 +43175,6 @@ const isStableHookWrapperArgument = (node) => {
|
|
|
43046
43175
|
const parent = node.parent;
|
|
43047
43176
|
return isNodeOfType(parent, "CallExpression") && isNodeOfType(parent.callee, "Identifier") && STABLE_HOOK_WRAPPERS.has(parent.callee.name) && Boolean(parent.arguments?.some((argument) => argument === node));
|
|
43048
43177
|
};
|
|
43049
|
-
const isImmediatelyInvokedFunction = (functionNode) => {
|
|
43050
|
-
let wrappedCallee = functionNode;
|
|
43051
|
-
let enclosing = functionNode.parent;
|
|
43052
|
-
while (enclosing && stripParenExpression(enclosing) === functionNode) {
|
|
43053
|
-
wrappedCallee = enclosing;
|
|
43054
|
-
enclosing = enclosing.parent ?? null;
|
|
43055
|
-
}
|
|
43056
|
-
return Boolean(enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee);
|
|
43057
|
-
};
|
|
43058
43178
|
const queryStableQueryClient = defineRule({
|
|
43059
43179
|
id: "query-stable-query-client",
|
|
43060
43180
|
title: "Unstable QueryClient in component",
|
|
@@ -59553,7 +59673,10 @@ const appendNode = (builder, block, node) => {
|
|
|
59553
59673
|
};
|
|
59554
59674
|
const mapDescendantsToBlock = (builder, node, block) => {
|
|
59555
59675
|
builder.nodeBlock.set(node, block);
|
|
59556
|
-
if (isFunctionLike$1(node))
|
|
59676
|
+
if (isFunctionLike$1(node)) {
|
|
59677
|
+
builder.nestedFunctions.push(node);
|
|
59678
|
+
return;
|
|
59679
|
+
}
|
|
59557
59680
|
const record = node;
|
|
59558
59681
|
for (const key of Object.keys(record)) {
|
|
59559
59682
|
if (key === "parent") continue;
|
|
@@ -59787,11 +59910,12 @@ const buildStatement = (builder, statement, current) => {
|
|
|
59787
59910
|
mapDescendantsToBlock(builder, statement, current);
|
|
59788
59911
|
return current;
|
|
59789
59912
|
};
|
|
59790
|
-
const buildFunctionCfg = (functionNode, body) => {
|
|
59913
|
+
const buildFunctionCfg = (functionNode, body, nestedFunctionSink) => {
|
|
59791
59914
|
const builder = {
|
|
59792
59915
|
blocks: [],
|
|
59793
59916
|
entry: null,
|
|
59794
59917
|
exit: null,
|
|
59918
|
+
nestedFunctions: nestedFunctionSink,
|
|
59795
59919
|
nodeBlock: /* @__PURE__ */ new Map(),
|
|
59796
59920
|
loopStack: [],
|
|
59797
59921
|
switchStack: [],
|
|
@@ -59809,13 +59933,12 @@ const buildFunctionCfg = (functionNode, body) => {
|
|
|
59809
59933
|
bodyEnd = entry;
|
|
59810
59934
|
}
|
|
59811
59935
|
addEdge(bodyEnd, exit, "uncond");
|
|
59812
|
-
const blockOf = (node) => builder.nodeBlock.get(node) ?? null;
|
|
59813
59936
|
return {
|
|
59814
59937
|
owner: functionNode,
|
|
59815
59938
|
entry,
|
|
59816
59939
|
exit,
|
|
59817
59940
|
blocks: builder.blocks,
|
|
59818
|
-
blockOf
|
|
59941
|
+
blockOf: (node) => builder.nodeBlock.get(node) ?? null
|
|
59819
59942
|
};
|
|
59820
59943
|
};
|
|
59821
59944
|
const computeUnconditionalSet = (cfg) => {
|
|
@@ -59852,8 +59975,9 @@ const computeUnconditionalSet = (cfg) => {
|
|
|
59852
59975
|
const analyzeControlFlow = (program) => {
|
|
59853
59976
|
nextBlockId = 0;
|
|
59854
59977
|
const functionCfgs = /* @__PURE__ */ new Map();
|
|
59978
|
+
const pendingFunctions = [];
|
|
59855
59979
|
const buildFor = (functionNode, body) => {
|
|
59856
|
-
const cfg = buildFunctionCfg(functionNode, body);
|
|
59980
|
+
const cfg = buildFunctionCfg(functionNode, body, pendingFunctions);
|
|
59857
59981
|
functionCfgs.set(functionNode, {
|
|
59858
59982
|
cfg,
|
|
59859
59983
|
unconditionalSet: computeUnconditionalSet(cfg)
|
|
@@ -59863,21 +59987,18 @@ const analyzeControlFlow = (program) => {
|
|
|
59863
59987
|
type: "BlockStatement",
|
|
59864
59988
|
body: program.body
|
|
59865
59989
|
});
|
|
59866
|
-
|
|
59867
|
-
|
|
59868
|
-
|
|
59869
|
-
|
|
59870
|
-
|
|
59871
|
-
|
|
59872
|
-
|
|
59873
|
-
|
|
59874
|
-
|
|
59875
|
-
|
|
59876
|
-
|
|
59877
|
-
} else if (isAstNode(child)) visit(child);
|
|
59878
|
-
}
|
|
59990
|
+
for (let functionIndex = 0; functionIndex < pendingFunctions.length; functionIndex += 1) {
|
|
59991
|
+
const functionNode = pendingFunctions[functionIndex];
|
|
59992
|
+
if (!isFunctionLike$1(functionNode) || !functionNode.body || functionCfgs.has(functionNode)) continue;
|
|
59993
|
+
buildFor(functionNode, functionNode.body);
|
|
59994
|
+
}
|
|
59995
|
+
const getFunctionEntry = (functionNode) => {
|
|
59996
|
+
const existingEntry = functionCfgs.get(functionNode);
|
|
59997
|
+
if (existingEntry) return existingEntry;
|
|
59998
|
+
if (!isFunctionLike$1(functionNode) || !functionNode.body) return null;
|
|
59999
|
+
buildFor(functionNode, functionNode.body);
|
|
60000
|
+
return functionCfgs.get(functionNode) ?? null;
|
|
59879
60001
|
};
|
|
59880
|
-
visit(program);
|
|
59881
60002
|
const enclosingFunction = (node) => {
|
|
59882
60003
|
let current = node;
|
|
59883
60004
|
while (current) {
|
|
@@ -59888,12 +60009,12 @@ const analyzeControlFlow = (program) => {
|
|
|
59888
60009
|
return null;
|
|
59889
60010
|
};
|
|
59890
60011
|
const cfgFor = (functionLike) => {
|
|
59891
|
-
return
|
|
60012
|
+
return getFunctionEntry(functionLike)?.cfg ?? null;
|
|
59892
60013
|
};
|
|
59893
60014
|
const isUnconditionalFromEntry = (node) => {
|
|
59894
60015
|
const owner = enclosingFunction(node);
|
|
59895
60016
|
if (!owner) return true;
|
|
59896
|
-
const entry =
|
|
60017
|
+
const entry = getFunctionEntry(owner);
|
|
59897
60018
|
if (!entry) return true;
|
|
59898
60019
|
const block = entry.cfg.blockOf(node);
|
|
59899
60020
|
if (!block) return true;
|
|
@@ -59967,17 +60088,14 @@ const wrapWithSemanticContext = (rule) => ({
|
|
|
59967
60088
|
}
|
|
59968
60089
|
};
|
|
59969
60090
|
const visitors = rule.create(enrichedContext);
|
|
59970
|
-
const
|
|
59971
|
-
|
|
59972
|
-
|
|
59973
|
-
|
|
59974
|
-
|
|
59975
|
-
|
|
59976
|
-
|
|
59977
|
-
|
|
59978
|
-
if (innerProgramHandler) innerProgramHandler(node);
|
|
59979
|
-
});
|
|
59980
|
-
return passthroughVisitors;
|
|
60091
|
+
const innerProgramHandler = visitors.Program;
|
|
60092
|
+
return {
|
|
60093
|
+
...visitors,
|
|
60094
|
+
Program: ((node) => {
|
|
60095
|
+
programRoot = node;
|
|
60096
|
+
if (innerProgramHandler) innerProgramHandler(node);
|
|
60097
|
+
})
|
|
60098
|
+
};
|
|
59981
60099
|
}
|
|
59982
60100
|
});
|
|
59983
60101
|
//#endregion
|