oxlint-plugin-react-doctor 0.7.5-dev.dbd4067 → 0.7.5
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 +630 -1052
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { KEYS } from "eslint-visitor-keys";
|
|
2
1
|
import * as path from "node:path";
|
|
3
2
|
import * as fs from "node:fs";
|
|
4
3
|
import { readFileSync } from "node:fs";
|
|
5
|
-
import { parseSync
|
|
4
|
+
import { parseSync } from "oxc-parser";
|
|
6
5
|
import { analyze } from "eslint-scope";
|
|
6
|
+
import * as eslintVisitorKeys from "eslint-visitor-keys";
|
|
7
|
+
//#region src/plugin/utils/has-type-property.ts
|
|
8
|
+
const hasTypeProperty = (value) => Boolean(value && typeof value === "object" && "type" in value);
|
|
9
|
+
//#endregion
|
|
7
10
|
//#region src/plugin/utils/is-node-of-type.ts
|
|
8
|
-
const isNodeOfType = (node, type) => node
|
|
11
|
+
const isNodeOfType = (node, type) => Boolean(hasTypeProperty(node) && node.type === type);
|
|
9
12
|
//#endregion
|
|
10
13
|
//#region src/plugin/utils/non-react-jsx-dialect.ts
|
|
11
14
|
const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
|
|
@@ -397,7 +400,6 @@ const isProductionFilePath = (relativePath, sourceFilePattern) => {
|
|
|
397
400
|
//#endregion
|
|
398
401
|
//#region src/plugin/rules/security-scan/utils/is-production-source-path.ts
|
|
399
402
|
const isProductionSourcePath = (relativePath) => {
|
|
400
|
-
if (/\.d\.[cm]?[jt]s$/i.test(relativePath)) return false;
|
|
401
403
|
return isProductionFilePath(relativePath, SOURCE_FILE_PATTERN);
|
|
402
404
|
};
|
|
403
405
|
//#endregion
|
|
@@ -563,8 +565,7 @@ const REACT_RUNTIME_MODULE_SOURCES = new Set([
|
|
|
563
565
|
"react",
|
|
564
566
|
"react-dom",
|
|
565
567
|
"preact/compat",
|
|
566
|
-
"preact/hooks"
|
|
567
|
-
"@wordpress/element"
|
|
568
|
+
"preact/hooks"
|
|
568
569
|
]);
|
|
569
570
|
const REACT_ECOSYSTEM_PACKAGE_NAMES = new Set([
|
|
570
571
|
"next",
|
|
@@ -801,55 +802,24 @@ const isHookCall$2 = (node, hookName) => {
|
|
|
801
802
|
};
|
|
802
803
|
//#endregion
|
|
803
804
|
//#region src/plugin/utils/is-ast-node.ts
|
|
804
|
-
const isAstNode = (value) =>
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
const RUNTIME_VISITOR_KEYS = {
|
|
808
|
-
...KEYS,
|
|
809
|
-
ArrayPattern: ["decorators", ...KEYS.ArrayPattern],
|
|
810
|
-
AssignmentPattern: ["decorators", ...KEYS.AssignmentPattern],
|
|
811
|
-
ClassDeclaration: ["decorators", ...KEYS.ClassDeclaration],
|
|
812
|
-
ClassExpression: ["decorators", ...KEYS.ClassExpression],
|
|
813
|
-
Identifier: ["decorators", ...KEYS.Identifier],
|
|
814
|
-
MethodDefinition: ["decorators", ...KEYS.MethodDefinition],
|
|
815
|
-
ObjectPattern: ["decorators", ...KEYS.ObjectPattern],
|
|
816
|
-
PropertyDefinition: ["decorators", ...KEYS.PropertyDefinition],
|
|
817
|
-
RestElement: ["decorators", ...KEYS.RestElement]
|
|
805
|
+
const isAstNode = (value) => {
|
|
806
|
+
if (!hasTypeProperty(value)) return false;
|
|
807
|
+
return typeof value.type === "string";
|
|
818
808
|
};
|
|
819
809
|
//#endregion
|
|
820
810
|
//#region src/plugin/utils/walk-ast.ts
|
|
821
|
-
const
|
|
811
|
+
const walkAst = (node, visitor) => {
|
|
812
|
+
if (!node || typeof node !== "object") return;
|
|
813
|
+
if (visitor(node) === false) return;
|
|
822
814
|
const nodeRecord = node;
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
for (let keyIndex = 0; keyIndex < childKeys.length; keyIndex += 1) {
|
|
826
|
-
const child = nodeRecord[childKeys[keyIndex]];
|
|
827
|
-
if (Array.isArray(child)) for (let itemIndex = 0; itemIndex < child.length; itemIndex += 1) {
|
|
828
|
-
const item = child[itemIndex];
|
|
829
|
-
if (isAstNode(item)) visit(item);
|
|
830
|
-
}
|
|
831
|
-
else if (isAstNode(child)) visit(child);
|
|
832
|
-
}
|
|
833
|
-
return;
|
|
834
|
-
}
|
|
835
|
-
for (const key in nodeRecord) {
|
|
836
|
-
if (key === "parent" || !Object.hasOwn(nodeRecord, key)) continue;
|
|
815
|
+
for (const key of Object.keys(nodeRecord)) {
|
|
816
|
+
if (key === "parent") continue;
|
|
837
817
|
const child = nodeRecord[key];
|
|
838
|
-
if (Array.isArray(child))
|
|
839
|
-
const item
|
|
840
|
-
|
|
841
|
-
}
|
|
842
|
-
else if (isAstNode(child)) visit(child);
|
|
818
|
+
if (Array.isArray(child)) {
|
|
819
|
+
for (const item of child) if (isAstNode(item)) walkAst(item, visitor);
|
|
820
|
+
} else if (isAstNode(child)) walkAst(child, visitor);
|
|
843
821
|
}
|
|
844
822
|
};
|
|
845
|
-
const walkAst = (node, visitor) => {
|
|
846
|
-
if (!node || typeof node !== "object") return;
|
|
847
|
-
const visitNode = (current) => {
|
|
848
|
-
if (visitor(current) === false) return;
|
|
849
|
-
forEachChildNode(current, visitNode);
|
|
850
|
-
};
|
|
851
|
-
visitNode(node);
|
|
852
|
-
};
|
|
853
823
|
//#endregion
|
|
854
824
|
//#region src/plugin/rules/state-and-effects/activity-wraps-effect-heavy-subtree.ts
|
|
855
825
|
const ACTIVITY_IMPORTED_NAMES = new Set(["Activity", "unstable_Activity"]);
|
|
@@ -1292,7 +1262,7 @@ const REGEX_PRECEDING_KEYWORDS = new Set([
|
|
|
1292
1262
|
"await",
|
|
1293
1263
|
"throw"
|
|
1294
1264
|
]);
|
|
1295
|
-
const isRegexLiteralStart = (
|
|
1265
|
+
const isRegexLiteralStart = (characters, slashIndex) => {
|
|
1296
1266
|
let cursor = slashIndex - 1;
|
|
1297
1267
|
while (cursor >= 0 && WHITESPACE_PATTERN.test(characters[cursor])) cursor -= 1;
|
|
1298
1268
|
if (cursor < 0) return true;
|
|
@@ -1302,7 +1272,7 @@ const isRegexLiteralStart = (content, characters, slashIndex) => {
|
|
|
1302
1272
|
let wordStartIndex = cursor;
|
|
1303
1273
|
while (wordStartIndex > 0 && IDENTIFIER_CHARACTER_PATTERN.test(characters[wordStartIndex - 1])) wordStartIndex -= 1;
|
|
1304
1274
|
if (wordStartIndex > 0 && characters[wordStartIndex - 1] === ".") return false;
|
|
1305
|
-
return REGEX_PRECEDING_KEYWORDS.has(
|
|
1275
|
+
return REGEX_PRECEDING_KEYWORDS.has(characters.slice(wordStartIndex, cursor + 1).join(""));
|
|
1306
1276
|
}
|
|
1307
1277
|
if (previousCharacter === "<") return false;
|
|
1308
1278
|
if (previousCharacter === ">") return characterBefore === "=";
|
|
@@ -1343,15 +1313,13 @@ const quotedLiteralHasWhitespace = (content, openQuoteIndex, delimiter) => {
|
|
|
1343
1313
|
return false;
|
|
1344
1314
|
};
|
|
1345
1315
|
const blankNonCodePreservingPositions = (content, blankStringContents) => {
|
|
1346
|
-
|
|
1316
|
+
const characters = content.split("");
|
|
1347
1317
|
let stringDelimiter = null;
|
|
1348
1318
|
let isBlankingString = false;
|
|
1349
1319
|
const templateExpressionDepths = [];
|
|
1350
1320
|
let index = 0;
|
|
1351
1321
|
const blankUnlessNewline = (offset) => {
|
|
1352
|
-
if (offset
|
|
1353
|
-
characters ??= content.split("");
|
|
1354
|
-
characters[offset] = " ";
|
|
1322
|
+
if (offset < content.length && content[offset] !== "\n") characters[offset] = " ";
|
|
1355
1323
|
};
|
|
1356
1324
|
while (index < content.length) {
|
|
1357
1325
|
const character = content[index];
|
|
@@ -1398,7 +1366,6 @@ const blankNonCodePreservingPositions = (content, blankStringContents) => {
|
|
|
1398
1366
|
continue;
|
|
1399
1367
|
}
|
|
1400
1368
|
if (character === "/" && nextCharacter === "/") {
|
|
1401
|
-
characters ??= content.split("");
|
|
1402
1369
|
while (index < content.length && content[index] !== "\n") {
|
|
1403
1370
|
characters[index] = " ";
|
|
1404
1371
|
index += 1;
|
|
@@ -1406,7 +1373,6 @@ const blankNonCodePreservingPositions = (content, blankStringContents) => {
|
|
|
1406
1373
|
continue;
|
|
1407
1374
|
}
|
|
1408
1375
|
if (character === "/" && nextCharacter === "*") {
|
|
1409
|
-
characters ??= content.split("");
|
|
1410
1376
|
while (index < content.length) {
|
|
1411
1377
|
if (content[index] === "*" && content[index + 1] === "/") {
|
|
1412
1378
|
characters[index] = " ";
|
|
@@ -1420,7 +1386,7 @@ const blankNonCodePreservingPositions = (content, blankStringContents) => {
|
|
|
1420
1386
|
continue;
|
|
1421
1387
|
}
|
|
1422
1388
|
if (character === "/") {
|
|
1423
|
-
const regexEndIndex = isRegexLiteralStart(
|
|
1389
|
+
const regexEndIndex = isRegexLiteralStart(characters, index) ? findRegexLiteralEnd(content, index) : null;
|
|
1424
1390
|
if (regexEndIndex !== null) {
|
|
1425
1391
|
if (blankStringContents) for (let interiorIndex = index + 1; interiorIndex < regexEndIndex - 1; interiorIndex += 1) blankUnlessNewline(interiorIndex);
|
|
1426
1392
|
index = regexEndIndex;
|
|
@@ -1438,9 +1404,9 @@ const blankNonCodePreservingPositions = (content, blankStringContents) => {
|
|
|
1438
1404
|
}
|
|
1439
1405
|
index += 1;
|
|
1440
1406
|
}
|
|
1441
|
-
return characters
|
|
1407
|
+
return characters.join("");
|
|
1442
1408
|
};
|
|
1443
|
-
const stripCommentsPreservingPositions = (content) =>
|
|
1409
|
+
const stripCommentsPreservingPositions = (content) => blankNonCodePreservingPositions(content, false);
|
|
1444
1410
|
const stripCommentsAndStringLiteralsPreservingPositions = (content) => blankNonCodePreservingPositions(content, true);
|
|
1445
1411
|
//#endregion
|
|
1446
1412
|
//#region src/plugin/rules/security-scan/utils/scan-by-pattern.ts
|
|
@@ -1459,15 +1425,10 @@ const scanByPattern = ({ shouldScan, pattern, requireAll, suppressWhen, ignoreSt
|
|
|
1459
1425
|
if (!shouldScan(file)) return [];
|
|
1460
1426
|
const content = getScannableContent(file, ignoreStringLiterals);
|
|
1461
1427
|
if (requireAll !== void 0 && !requireAll.every((gate) => gate.test(content))) return [];
|
|
1462
|
-
|
|
1463
|
-
if (
|
|
1464
|
-
else for (let patternIndex = 0; patternIndex < pattern.length; patternIndex += 1) {
|
|
1465
|
-
matchIndex = content.search(pattern[patternIndex]);
|
|
1466
|
-
if (matchIndex >= 0) break;
|
|
1467
|
-
}
|
|
1468
|
-
if (matchIndex < 0) return [];
|
|
1428
|
+
const matchedPattern = (pattern instanceof RegExp ? [pattern] : pattern).find((candidate) => candidate.test(content));
|
|
1429
|
+
if (matchedPattern === void 0) return [];
|
|
1469
1430
|
if (suppressWhen !== void 0 && suppressWhen.test(content)) return [];
|
|
1470
|
-
const { line, column } =
|
|
1431
|
+
const { line, column } = getMatchLocation(content, matchedPattern);
|
|
1471
1432
|
return [{
|
|
1472
1433
|
message,
|
|
1473
1434
|
line,
|
|
@@ -1477,7 +1438,6 @@ const scanByPattern = ({ shouldScan, pattern, requireAll, suppressWhen, ignoreSt
|
|
|
1477
1438
|
//#endregion
|
|
1478
1439
|
//#region src/plugin/rules/security-scan/agent-tool-capability-risk.ts
|
|
1479
1440
|
const AGENT_TOOL_DEFINITION_PATTERN = /\b(?:tool\s*\(\s*\{|createTool\s*\(|defineTool\s*\(|new\s+(?:DynamicTool|StructuredTool)\s*\()/;
|
|
1480
|
-
const AGENT_TOOL_DEFINITION_PREFILTER_PATTERN = /\b(?:tool|createTool|defineTool|DynamicTool|StructuredTool)\b/;
|
|
1481
1441
|
const AGENT_TOOL_CONTEXT_PATH_PATTERN = /(?:^|\/)(?:agents?|tools?|mcp)(?:\/|$)|(?:agent|tool|mcp)[^/]*\.[cm]?[jt]sx?$/i;
|
|
1482
1442
|
const agentToolCapabilityRisk = defineRule({
|
|
1483
1443
|
id: "agent-tool-capability-risk",
|
|
@@ -1485,7 +1445,7 @@ const agentToolCapabilityRisk = defineRule({
|
|
|
1485
1445
|
severity: "warn",
|
|
1486
1446
|
recommendation: "Treat tool inputs as prompt-injection controlled. Validate arguments, scope permissions per call, and avoid exposing shell/file/network primitives directly to agents.",
|
|
1487
1447
|
scan: scanByPattern({
|
|
1488
|
-
shouldScan: (file) => isProductionSourcePath(file.relativePath) && AGENT_TOOL_CONTEXT_PATH_PATTERN.test(file.relativePath)
|
|
1448
|
+
shouldScan: (file) => isProductionSourcePath(file.relativePath) && AGENT_TOOL_CONTEXT_PATH_PATTERN.test(file.relativePath),
|
|
1489
1449
|
pattern: AGENT_TOOL_DEFINITION_PATTERN,
|
|
1490
1450
|
requireAll: [AGENT_TOOL_DANGEROUS_CAPABILITY_PATTERN],
|
|
1491
1451
|
ignoreStringLiterals: true,
|
|
@@ -5179,10 +5139,8 @@ const STORAGE_GLOBALS = new Set([
|
|
|
5179
5139
|
const SENSITIVE_KEY_PATTERN = /token|jwt|secret|password|passwd|credential|api[-_]?key|bearer|private[-_]?key/i;
|
|
5180
5140
|
const NON_AUTH_TOKEN_PATTERN = /csrf|xsrf|device|fcm|apns|push|design|tokeniz|syntax|css|theme|color/i;
|
|
5181
5141
|
const STRONG_AUTH_KEY_PATTERN = /jwt|secret|password|passwd|credential|private[-_]?key|api[-_]?key|bearer|access[-_]?token|refresh[-_]?token|auth[-_]?token|id[-_]?token|session/i;
|
|
5182
|
-
const PRODUCT_API_KEY_RECORDS_PATTERN = /(?:^|[._:-])(?:created|saved|integration|mailing)[-_]?api[-_]?keys$/i;
|
|
5183
5142
|
const PRODUCT_API_KEY_COLLECTION_PATTERN = /[._:-](?:created|generated|saved)[-_]?api[-_]?keys$/i;
|
|
5184
5143
|
const isAuthCredentialKey = (key) => {
|
|
5185
|
-
if (PRODUCT_API_KEY_RECORDS_PATTERN.test(key)) return false;
|
|
5186
5144
|
if (!SENSITIVE_KEY_PATTERN.test(key)) return false;
|
|
5187
5145
|
if (PRODUCT_API_KEY_COLLECTION_PATTERN.test(key)) return false;
|
|
5188
5146
|
if (NON_AUTH_TOKEN_PATTERN.test(key) && !STRONG_AUTH_KEY_PATTERN.test(key)) return false;
|
|
@@ -5934,21 +5892,6 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
5934
5892
|
}
|
|
5935
5893
|
});
|
|
5936
5894
|
//#endregion
|
|
5937
|
-
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
5938
|
-
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
5939
|
-
if (!isNodeOfType(node, "Property")) return null;
|
|
5940
|
-
if (node.computed) {
|
|
5941
|
-
if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
|
|
5942
|
-
return null;
|
|
5943
|
-
}
|
|
5944
|
-
if (isNodeOfType(node.key, "Identifier")) return node.key.name;
|
|
5945
|
-
if (isNodeOfType(node.key, "Literal")) {
|
|
5946
|
-
if (typeof node.key.value === "string") return node.key.value;
|
|
5947
|
-
if (options.stringifyNonStringLiterals) return String(node.key.value);
|
|
5948
|
-
}
|
|
5949
|
-
return null;
|
|
5950
|
-
};
|
|
5951
|
-
//#endregion
|
|
5952
5895
|
//#region src/plugin/utils/is-presentation-role.ts
|
|
5953
5896
|
const isPresentationRole = (openingElement) => {
|
|
5954
5897
|
const roleAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "role");
|
|
@@ -5993,21 +5936,6 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
5993
5936
|
return false;
|
|
5994
5937
|
};
|
|
5995
5938
|
//#endregion
|
|
5996
|
-
//#region src/plugin/utils/resolve-const-identifier-alias.ts
|
|
5997
|
-
const resolveConstIdentifierAlias = (identifier, scopes) => {
|
|
5998
|
-
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
5999
|
-
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
6000
|
-
let symbol = scopes.symbolFor(identifier);
|
|
6001
|
-
while (symbol?.kind === "const") {
|
|
6002
|
-
if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
6003
|
-
visitedSymbolIds.add(symbol.id);
|
|
6004
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
6005
|
-
if (!isNodeOfType(initializer, "Identifier")) return symbol;
|
|
6006
|
-
symbol = scopes.symbolFor(initializer);
|
|
6007
|
-
}
|
|
6008
|
-
return symbol;
|
|
6009
|
-
};
|
|
6010
|
-
//#endregion
|
|
6011
5939
|
//#region src/plugin/rules/a11y/click-events-have-key-events.ts
|
|
6012
5940
|
const MESSAGE$57 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
|
|
6013
5941
|
const KEY_HANDLERS = [
|
|
@@ -6018,63 +5946,6 @@ const KEY_HANDLERS = [
|
|
|
6018
5946
|
"onKeyDownCapture",
|
|
6019
5947
|
"onKeyPressCapture"
|
|
6020
5948
|
];
|
|
6021
|
-
const CLICK_HANDLERS = ["onClick", "onClickCapture"];
|
|
6022
|
-
const TRANSPARENT_SPREAD_EVENT_NAMES = new Set([...CLICK_HANDLERS, ...KEY_HANDLERS].map((eventName) => eventName.toLowerCase()));
|
|
6023
|
-
const CONSERVATIVE_SPREAD_PROP_NAMES = new Set([
|
|
6024
|
-
"aria-hidden",
|
|
6025
|
-
"onmouseenter",
|
|
6026
|
-
"onmouseover",
|
|
6027
|
-
"role"
|
|
6028
|
-
]);
|
|
6029
|
-
const resolveSpreadObjectExpression = (expression, scopes) => {
|
|
6030
|
-
const innerExpression = stripParenExpression(expression);
|
|
6031
|
-
if (isNodeOfType(innerExpression, "ObjectExpression")) return innerExpression;
|
|
6032
|
-
if (!isNodeOfType(innerExpression, "Identifier")) return null;
|
|
6033
|
-
const symbol = resolveConstIdentifierAlias(innerExpression, scopes);
|
|
6034
|
-
if (symbol?.kind !== "const" || !symbol.initializer) return null;
|
|
6035
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
6036
|
-
return isNodeOfType(initializer, "ObjectExpression") ? initializer : null;
|
|
6037
|
-
};
|
|
6038
|
-
const collectTransparentSpreadEventNames = (expression, scopes, eventValues, visitedObjectExpressions) => {
|
|
6039
|
-
const objectExpression = resolveSpreadObjectExpression(expression, scopes);
|
|
6040
|
-
if (!objectExpression || visitedObjectExpressions.has(objectExpression)) return false;
|
|
6041
|
-
visitedObjectExpressions.add(objectExpression);
|
|
6042
|
-
let isTransparent = true;
|
|
6043
|
-
for (const property of objectExpression.properties) {
|
|
6044
|
-
if (isNodeOfType(property, "SpreadElement")) {
|
|
6045
|
-
if (!collectTransparentSpreadEventNames(property.argument, scopes, eventValues, visitedObjectExpressions)) {
|
|
6046
|
-
isTransparent = false;
|
|
6047
|
-
break;
|
|
6048
|
-
}
|
|
6049
|
-
continue;
|
|
6050
|
-
}
|
|
6051
|
-
if (!isNodeOfType(property, "Property")) {
|
|
6052
|
-
isTransparent = false;
|
|
6053
|
-
break;
|
|
6054
|
-
}
|
|
6055
|
-
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
6056
|
-
if (!propertyName) {
|
|
6057
|
-
isTransparent = false;
|
|
6058
|
-
break;
|
|
6059
|
-
}
|
|
6060
|
-
const normalizedPropertyName = propertyName.toLowerCase();
|
|
6061
|
-
if (CONSERVATIVE_SPREAD_PROP_NAMES.has(normalizedPropertyName)) {
|
|
6062
|
-
isTransparent = false;
|
|
6063
|
-
break;
|
|
6064
|
-
}
|
|
6065
|
-
if (TRANSPARENT_SPREAD_EVENT_NAMES.has(normalizedPropertyName)) eventValues.set(normalizedPropertyName, property.value);
|
|
6066
|
-
}
|
|
6067
|
-
visitedObjectExpressions.delete(objectExpression);
|
|
6068
|
-
return isTransparent;
|
|
6069
|
-
};
|
|
6070
|
-
const getTransparentSpreadEventValues = (attributes, scopes) => {
|
|
6071
|
-
const eventValues = /* @__PURE__ */ new Map();
|
|
6072
|
-
for (const attribute of attributes) {
|
|
6073
|
-
if (!isNodeOfType(attribute, "JSXSpreadAttribute")) continue;
|
|
6074
|
-
if (!collectTransparentSpreadEventNames(attribute.argument, scopes, eventValues, /* @__PURE__ */ new Set())) return null;
|
|
6075
|
-
}
|
|
6076
|
-
return eventValues;
|
|
6077
|
-
};
|
|
6078
5949
|
const FOCUSLESS_CONTAINER_TAGS = new Set([
|
|
6079
5950
|
"tr",
|
|
6080
5951
|
"td",
|
|
@@ -6122,10 +5993,7 @@ const isFocusForwardingFunctionBody = (body) => {
|
|
|
6122
5993
|
};
|
|
6123
5994
|
const resolveHandlerFunction = (attribute) => {
|
|
6124
5995
|
if (!attribute.value || !isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
|
|
6125
|
-
|
|
6126
|
-
};
|
|
6127
|
-
const resolveHandlerFunctionExpression = (handlerExpression) => {
|
|
6128
|
-
let expression = handlerExpression;
|
|
5996
|
+
let expression = attribute.value.expression;
|
|
6129
5997
|
if (isNodeOfType(expression, "Identifier")) {
|
|
6130
5998
|
const binding = findVariableInitializer(expression, expression.name);
|
|
6131
5999
|
if (!binding?.initializer) return null;
|
|
@@ -6224,22 +6092,18 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
6224
6092
|
if (!HTML_TAGS.has(tag)) return;
|
|
6225
6093
|
if (tag === "label") return;
|
|
6226
6094
|
if (!FOCUSLESS_CONTAINER_TAGS.has(tag) && isInteractiveElement(tag, node)) return;
|
|
6227
|
-
const spreadEventValues = getTransparentSpreadEventValues(node.attributes, context.scopes);
|
|
6228
|
-
if (!spreadEventValues) return;
|
|
6229
6095
|
const onClick = hasJsxPropIgnoreCase(node.attributes, "onClick") ?? hasJsxPropIgnoreCase(node.attributes, "onClickCapture");
|
|
6230
|
-
|
|
6231
|
-
if (
|
|
6232
|
-
if (
|
|
6233
|
-
if (
|
|
6234
|
-
const spreadHandlerFunction = spreadOnClickExpression ? resolveHandlerFunctionExpression(spreadOnClickExpression) : null;
|
|
6235
|
-
if (spreadHandlerFunction && (isFocusForwardingFunctionBody(spreadHandlerFunction.body ?? null) || containsBackdropDismissComparison(spreadHandlerFunction.body ?? null))) return;
|
|
6096
|
+
if (!onClick) return;
|
|
6097
|
+
if (isPureEventBlockerHandler(onClick)) return;
|
|
6098
|
+
if (isFocusForwardingHandler(onClick)) return;
|
|
6099
|
+
if (hasJsxSpreadAttribute$1(node.attributes)) return;
|
|
6236
6100
|
if (hasCompositeItemRole(node)) return;
|
|
6237
6101
|
if (isHoverSelectionListItem(tag, node)) return;
|
|
6238
|
-
if (
|
|
6102
|
+
if (isBackdropDismissHandler(onClick)) return;
|
|
6239
6103
|
if (containsKeyboardActivatableDescendant(node.parent)) return;
|
|
6240
6104
|
if (isHiddenFromScreenReader(node, context.settings)) return;
|
|
6241
6105
|
if (isPresentationRole(node)) return;
|
|
6242
|
-
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler)
|
|
6106
|
+
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
|
|
6243
6107
|
context.report({
|
|
6244
6108
|
node: node.name,
|
|
6245
6109
|
message: MESSAGE$57
|
|
@@ -6965,10 +6829,11 @@ const getTemplateInterpolations = (valueTail) => {
|
|
|
6965
6829
|
const interpolations = valueTail.slice(1, closingBacktickIndex).match(/\$\{[^}]*\}/g);
|
|
6966
6830
|
return interpolations === null ? "" : interpolations.join(" ");
|
|
6967
6831
|
};
|
|
6968
|
-
const getIdentifierDeclarationInitializer = (identifier,
|
|
6969
|
-
const
|
|
6970
|
-
if (
|
|
6971
|
-
|
|
6832
|
+
const getIdentifierDeclarationInitializer = (identifier, fileContent) => {
|
|
6833
|
+
const declarationMatch = new RegExp(`(?:const|let|var)\\s+${escapeRegExp(identifier)}\\s*(?::[^=\\n]{0,120})?=\\s*`).exec(fileContent);
|
|
6834
|
+
if (declarationMatch === null) return null;
|
|
6835
|
+
const initializerStartIndex = declarationMatch.index + declarationMatch[0].length;
|
|
6836
|
+
return fileContent.slice(initializerStartIndex, initializerStartIndex + STATIC_TEMPLATE_MAX_CHARS);
|
|
6972
6837
|
};
|
|
6973
6838
|
const isPureStringLiteralConcat = (initializerText) => {
|
|
6974
6839
|
if (!/^["']/.test(initializerText)) return false;
|
|
@@ -7038,153 +6903,6 @@ const isAllOperandsDomContentConcat = (valueExpression) => {
|
|
|
7038
6903
|
return !HTML_TAINT_PATTERN.test(withoutCast.replace(DOM_CONTENT_SOURCE_VALUE_PATTERN, ""));
|
|
7039
6904
|
});
|
|
7040
6905
|
};
|
|
7041
|
-
const findMatchingBraceIndex = (fileContent, openingBraceIndex) => {
|
|
7042
|
-
let depth = 0;
|
|
7043
|
-
let quote = null;
|
|
7044
|
-
for (let index = openingBraceIndex; index < fileContent.length; index += 1) {
|
|
7045
|
-
const character = fileContent[index];
|
|
7046
|
-
if (quote !== null) {
|
|
7047
|
-
if (character === quote && fileContent[index - 1] !== "\\") quote = null;
|
|
7048
|
-
continue;
|
|
7049
|
-
}
|
|
7050
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
7051
|
-
quote = character;
|
|
7052
|
-
continue;
|
|
7053
|
-
}
|
|
7054
|
-
if (character === "{") depth += 1;
|
|
7055
|
-
if (character === "}") {
|
|
7056
|
-
depth -= 1;
|
|
7057
|
-
if (depth === 0) return index;
|
|
7058
|
-
}
|
|
7059
|
-
}
|
|
7060
|
-
return fileContent.length;
|
|
7061
|
-
};
|
|
7062
|
-
const findContainingBlockEndIndex = (fileContent, targetIndex) => {
|
|
7063
|
-
const openingBraceIndexes = [];
|
|
7064
|
-
let quote = null;
|
|
7065
|
-
let isLineComment = false;
|
|
7066
|
-
let isBlockComment = false;
|
|
7067
|
-
for (let index = 0; index < targetIndex; index += 1) {
|
|
7068
|
-
const character = fileContent[index];
|
|
7069
|
-
const nextCharacter = fileContent[index + 1];
|
|
7070
|
-
if (isLineComment) {
|
|
7071
|
-
if (character === "\n") isLineComment = false;
|
|
7072
|
-
continue;
|
|
7073
|
-
}
|
|
7074
|
-
if (isBlockComment) {
|
|
7075
|
-
if (character === "*" && nextCharacter === "/") {
|
|
7076
|
-
isBlockComment = false;
|
|
7077
|
-
index += 1;
|
|
7078
|
-
}
|
|
7079
|
-
continue;
|
|
7080
|
-
}
|
|
7081
|
-
if (quote !== null) {
|
|
7082
|
-
if (character === quote && fileContent[index - 1] !== "\\") quote = null;
|
|
7083
|
-
continue;
|
|
7084
|
-
}
|
|
7085
|
-
if (character === "/" && nextCharacter === "/") {
|
|
7086
|
-
isLineComment = true;
|
|
7087
|
-
index += 1;
|
|
7088
|
-
continue;
|
|
7089
|
-
}
|
|
7090
|
-
if (character === "/" && nextCharacter === "*") {
|
|
7091
|
-
isBlockComment = true;
|
|
7092
|
-
index += 1;
|
|
7093
|
-
continue;
|
|
7094
|
-
}
|
|
7095
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
7096
|
-
quote = character;
|
|
7097
|
-
continue;
|
|
7098
|
-
}
|
|
7099
|
-
if (character === "{") openingBraceIndexes.push(index);
|
|
7100
|
-
if (character === "}") openingBraceIndexes.pop();
|
|
7101
|
-
}
|
|
7102
|
-
const openingBraceIndex = openingBraceIndexes.at(-1);
|
|
7103
|
-
return openingBraceIndex === void 0 ? fileContent.length : findMatchingBraceIndex(fileContent, openingBraceIndex);
|
|
7104
|
-
};
|
|
7105
|
-
const findVisibleIdentifierDeclaration = (identifier, sinkIndex, fileContent) => {
|
|
7106
|
-
const initializerPattern = new RegExp(`(?:const|let|var)\\s+${escapeRegExp(identifier)}\\s*(?::[^=\\n]*)?=\\s*([^;\\n]+)`, "g");
|
|
7107
|
-
let nearestDeclaration = null;
|
|
7108
|
-
let nearestDeclarationIndex = -1;
|
|
7109
|
-
for (const match of fileContent.matchAll(initializerPattern)) {
|
|
7110
|
-
const declarationIndex = match.index;
|
|
7111
|
-
if (declarationIndex === void 0 || declarationIndex >= sinkIndex || declarationIndex <= nearestDeclarationIndex || findContainingBlockEndIndex(fileContent, declarationIndex) < sinkIndex) continue;
|
|
7112
|
-
nearestDeclarationIndex = declarationIndex;
|
|
7113
|
-
const initializer = match[1];
|
|
7114
|
-
if (initializer === void 0) continue;
|
|
7115
|
-
nearestDeclaration = {
|
|
7116
|
-
initializer,
|
|
7117
|
-
initializerStartIndex: declarationIndex + match[0].length - initializer.length
|
|
7118
|
-
};
|
|
7119
|
-
}
|
|
7120
|
-
return nearestDeclaration;
|
|
7121
|
-
};
|
|
7122
|
-
const findContainingFunctionParameterSource = (identifier, sinkIndex, fileContent) => {
|
|
7123
|
-
const patterns = [/function\s+([\w$]+)\s*\(([^)]*)\)\s*\{/g, /(?:const|let|var)\s+([\w$]+)\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>\s*\{/g];
|
|
7124
|
-
let closestSource = null;
|
|
7125
|
-
let closestStartIndex = -1;
|
|
7126
|
-
for (const pattern of patterns) for (const match of fileContent.matchAll(pattern)) {
|
|
7127
|
-
const matchIndex = match.index;
|
|
7128
|
-
if (matchIndex === void 0 || matchIndex >= sinkIndex || matchIndex < closestStartIndex) continue;
|
|
7129
|
-
if (findMatchingBraceIndex(fileContent, matchIndex + match[0].lastIndexOf("{")) < sinkIndex) continue;
|
|
7130
|
-
const parameterIndex = (match[2] ?? "").split(",").findIndex((parameter) => parameter.trim().match(/^(?:\.\.\.)?([\w$]+)/)?.[1] === identifier);
|
|
7131
|
-
if (parameterIndex < 0) continue;
|
|
7132
|
-
closestStartIndex = matchIndex;
|
|
7133
|
-
closestSource = {
|
|
7134
|
-
functionName: match[1] ?? "",
|
|
7135
|
-
parameterIndex
|
|
7136
|
-
};
|
|
7137
|
-
}
|
|
7138
|
-
return closestSource;
|
|
7139
|
-
};
|
|
7140
|
-
const splitTopLevelArguments = (argumentText) => {
|
|
7141
|
-
const argumentsList = [];
|
|
7142
|
-
let startIndex = 0;
|
|
7143
|
-
let depth = 0;
|
|
7144
|
-
let quote = null;
|
|
7145
|
-
for (let index = 0; index < argumentText.length; index += 1) {
|
|
7146
|
-
const character = argumentText[index];
|
|
7147
|
-
if (quote !== null) {
|
|
7148
|
-
if (character === quote && argumentText[index - 1] !== "\\") quote = null;
|
|
7149
|
-
continue;
|
|
7150
|
-
}
|
|
7151
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
7152
|
-
quote = character;
|
|
7153
|
-
continue;
|
|
7154
|
-
}
|
|
7155
|
-
if (character === "(" || character === "[" || character === "{") depth += 1;
|
|
7156
|
-
if (character === ")" || character === "]" || character === "}") depth -= 1;
|
|
7157
|
-
if (character === "," && depth === 0) {
|
|
7158
|
-
argumentsList.push(argumentText.slice(startIndex, index).trim());
|
|
7159
|
-
startIndex = index + 1;
|
|
7160
|
-
}
|
|
7161
|
-
}
|
|
7162
|
-
argumentsList.push(argumentText.slice(startIndex).trim());
|
|
7163
|
-
return argumentsList;
|
|
7164
|
-
};
|
|
7165
|
-
const isHtmlTainted = (expression, fileContent, sinkIndex, visitedIdentifiers) => {
|
|
7166
|
-
const trimmedExpression = expression.trim();
|
|
7167
|
-
if (STRING_LITERAL_VALUE_PATTERN.test(`${trimmedExpression};`)) return false;
|
|
7168
|
-
if (MODULE_CONSTANT_VALUE_PATTERN.test(`${trimmedExpression};`)) return false;
|
|
7169
|
-
if (SANITIZER_PATTERN.test(trimmedExpression)) return false;
|
|
7170
|
-
if (ENV_CONFIG_VALUE_PATTERN.test(trimmedExpression)) return false;
|
|
7171
|
-
if (I18N_VALUE_PATTERN.test(trimmedExpression)) return false;
|
|
7172
|
-
if (ESCAPING_SERIALIZER_CALL_PATTERN.test(trimmedExpression)) return false;
|
|
7173
|
-
if (HTML_TAINT_PATTERN.test(trimmedExpression)) return true;
|
|
7174
|
-
const identifier = trimmedExpression.match(/^([\w$]+)\s*(?:[;,})\n]|$)/)?.[1];
|
|
7175
|
-
if (identifier === void 0 || visitedIdentifiers.has(identifier)) return false;
|
|
7176
|
-
visitedIdentifiers.add(identifier);
|
|
7177
|
-
const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
|
|
7178
|
-
if (declaration !== null && isHtmlTainted(declaration.initializer, fileContent, sinkIndex, visitedIdentifiers)) return true;
|
|
7179
|
-
const parameterSource = findContainingFunctionParameterSource(identifier, sinkIndex, fileContent);
|
|
7180
|
-
if (parameterSource === null || parameterSource.functionName.length === 0) return false;
|
|
7181
|
-
const callPattern = new RegExp(`\\b${escapeRegExp(parameterSource.functionName)}\\s*\\(([^)]*)\\)`, "g");
|
|
7182
|
-
for (const callMatch of fileContent.matchAll(callPattern)) {
|
|
7183
|
-
const argument = splitTopLevelArguments(callMatch[1] ?? "")[parameterSource.parameterIndex];
|
|
7184
|
-
if (argument !== void 0 && isHtmlTainted(argument, fileContent, sinkIndex, new Set(visitedIdentifiers))) return true;
|
|
7185
|
-
}
|
|
7186
|
-
return false;
|
|
7187
|
-
};
|
|
7188
6906
|
const dangerousHtmlSink = defineRule({
|
|
7189
6907
|
id: "dangerous-html-sink",
|
|
7190
6908
|
title: "HTML injection sink with dynamic content",
|
|
@@ -7211,7 +6929,6 @@ const dangerousHtmlSink = defineRule({
|
|
|
7211
6929
|
const valueTail = fullValueTail.slice(0, VALUE_EXPRESSION_MAX_CHARS);
|
|
7212
6930
|
const terminatorIndex = valueTail.search(/[;}]/);
|
|
7213
6931
|
const valueExpression = terminatorIndex >= 0 ? valueTail.slice(0, terminatorIndex + 1) : valueTail;
|
|
7214
|
-
const sinkIndex = lines.slice(0, lineIndex).join("\n").length + (lineIndex > 0 ? 1 : 0) + line.search(DANGEROUS_HTML_PATTERN);
|
|
7215
6932
|
if (STRING_LITERAL_VALUE_PATTERN.test(valueExpression)) continue;
|
|
7216
6933
|
if (MODULE_CONSTANT_VALUE_PATTERN.test(valueExpression)) continue;
|
|
7217
6934
|
if (DOM_CONTENT_SOURCE_VALUE_PATTERN.test(valueExpression) && !valueExpression.includes("+")) {
|
|
@@ -7223,7 +6940,7 @@ const dangerousHtmlSink = defineRule({
|
|
|
7223
6940
|
let templateInterpolations = getTemplateInterpolations(longValueTail ?? fullValueTail);
|
|
7224
6941
|
const valueIdentifier = BARE_IDENTIFIER_VALUE_PATTERN.test(valueExpression) || MEMBER_OR_INDEX_ACCESS_VALUE_PATTERN.test(valueExpression) || IDENTIFIER_WITH_LITERAL_FALLBACK_PATTERN.test(valueExpression) ? valueExpression.match(/^[\w$]+/)?.[0] : void 0;
|
|
7225
6942
|
if (templateInterpolations === null && valueIdentifier !== void 0 && BARE_IDENTIFIER_VALUE_PATTERN.test(valueExpression)) {
|
|
7226
|
-
const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier,
|
|
6943
|
+
const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier, file.content);
|
|
7227
6944
|
if (declarationInitializer !== null) {
|
|
7228
6945
|
if (isPureStringLiteralConcat(declarationInitializer)) continue;
|
|
7229
6946
|
templateInterpolations = getTemplateInterpolations(declarationInitializer);
|
|
@@ -7234,7 +6951,7 @@ const dangerousHtmlSink = defineRule({
|
|
|
7234
6951
|
if (SANITIZER_PATTERN.test(judgedExpression)) continue;
|
|
7235
6952
|
if (ENV_CONFIG_VALUE_PATTERN.test(judgedExpression)) continue;
|
|
7236
6953
|
if (I18N_VALUE_PATTERN.test(judgedExpression)) continue;
|
|
7237
|
-
if (!
|
|
6954
|
+
if (!HTML_TAINT_PATTERN.test(judgedExpression)) continue;
|
|
7238
6955
|
if (ESCAPING_SERIALIZER_CALL_PATTERN.test(valueExpression)) continue;
|
|
7239
6956
|
if (/highlighted/i.test(valueExpression)) continue;
|
|
7240
6957
|
if (/highlight/i.test(valueExpression) && HIGHLIGHTER_LIBRARY_PATTERN.test(file.content)) continue;
|
|
@@ -7681,7 +7398,7 @@ const containsJsx$1 = (root) => {
|
|
|
7681
7398
|
containsJsxCache.set(root, found);
|
|
7682
7399
|
return found;
|
|
7683
7400
|
};
|
|
7684
|
-
const getStaticMemberName$
|
|
7401
|
+
const getStaticMemberName$1 = (node) => {
|
|
7685
7402
|
if (!isNodeOfType(node, "MemberExpression")) return null;
|
|
7686
7403
|
if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
|
|
7687
7404
|
if (node.computed && isNodeOfType(node.property, "Literal") && typeof node.property.value === "string") return node.property.value;
|
|
@@ -7695,7 +7412,7 @@ const getAssignedName = (node) => {
|
|
|
7695
7412
|
if (isNodeOfType(parent, "AssignmentExpression")) {
|
|
7696
7413
|
const left = parent.left;
|
|
7697
7414
|
if (isNodeOfType(left, "Identifier")) return left.name;
|
|
7698
|
-
if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$
|
|
7415
|
+
if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$1(left);
|
|
7699
7416
|
}
|
|
7700
7417
|
return null;
|
|
7701
7418
|
};
|
|
@@ -7703,20 +7420,20 @@ const isModuleExportsAssignment = (node) => {
|
|
|
7703
7420
|
const parent = node.parent;
|
|
7704
7421
|
if (!parent || !isNodeOfType(parent, "AssignmentExpression")) return false;
|
|
7705
7422
|
const left = parent.left;
|
|
7706
|
-
return isNodeOfType(left, "MemberExpression") && isNodeOfType(left.object, "Identifier") && left.object.name === "module" && getStaticMemberName$
|
|
7423
|
+
return isNodeOfType(left, "MemberExpression") && isNodeOfType(left.object, "Identifier") && left.object.name === "module" && getStaticMemberName$1(left) === "exports";
|
|
7707
7424
|
};
|
|
7708
7425
|
const isCreateClassLikeCall = (node) => {
|
|
7709
7426
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7710
7427
|
if (isEs5Component(node)) return true;
|
|
7711
7428
|
const callee = node.callee;
|
|
7712
|
-
if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$
|
|
7429
|
+
if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$1(callee) === "createClass";
|
|
7713
7430
|
return false;
|
|
7714
7431
|
};
|
|
7715
|
-
const isCreateContextCall
|
|
7432
|
+
const isCreateContextCall = (node) => {
|
|
7716
7433
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7717
7434
|
const callee = node.callee;
|
|
7718
7435
|
if (isNodeOfType(callee, "Identifier")) return callee.name === "createContext";
|
|
7719
|
-
return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$
|
|
7436
|
+
return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$1(callee) === "createContext";
|
|
7720
7437
|
};
|
|
7721
7438
|
const isNamedFunctionLike = (node) => (isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && Boolean(node.id?.name);
|
|
7722
7439
|
const firstCallArgument = (node) => {
|
|
@@ -7774,7 +7491,7 @@ const memberExpressionPath = (node) => {
|
|
|
7774
7491
|
if (isNodeOfType(node, "Identifier")) return [node.name];
|
|
7775
7492
|
if (!isNodeOfType(node, "MemberExpression")) return [];
|
|
7776
7493
|
const objectPath = memberExpressionPath(node.object);
|
|
7777
|
-
const propertyName = getStaticMemberName$
|
|
7494
|
+
const propertyName = getStaticMemberName$1(node);
|
|
7778
7495
|
return propertyName ? [...objectPath, propertyName] : objectPath;
|
|
7779
7496
|
};
|
|
7780
7497
|
const displayNameAssignmentIndexCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -7784,7 +7501,7 @@ const getDisplayNameAssignmentIndex = (programRoot) => {
|
|
|
7784
7501
|
const identifierTargets = /* @__PURE__ */ new Set();
|
|
7785
7502
|
const memberPathSegments = /* @__PURE__ */ new Set();
|
|
7786
7503
|
const visit = (node) => {
|
|
7787
|
-
if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName$
|
|
7504
|
+
if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName$1(node.left) === "displayName") {
|
|
7788
7505
|
if (isNodeOfType(node.left.object, "Identifier")) identifierTargets.add(node.left.object.name);
|
|
7789
7506
|
for (const segment of memberExpressionPath(node.left.object)) memberPathSegments.add(segment);
|
|
7790
7507
|
}
|
|
@@ -7889,7 +7606,7 @@ const displayName = defineRule({
|
|
|
7889
7606
|
}
|
|
7890
7607
|
},
|
|
7891
7608
|
CallExpression(node) {
|
|
7892
|
-
if (settings.checkContextObjects && isReactVersionAtLeast$1(settings.reactVersion, 16, 3) && isCreateContextCall
|
|
7609
|
+
if (settings.checkContextObjects && isReactVersionAtLeast$1(settings.reactVersion, 16, 3) && isCreateContextCall(node)) {
|
|
7893
7610
|
const assignedName = getAssignedName(node);
|
|
7894
7611
|
if (assignedName) {
|
|
7895
7612
|
const programRoot = findProgramRoot(node);
|
|
@@ -7936,6 +7653,21 @@ const displayName = defineRule({
|
|
|
7936
7653
|
}
|
|
7937
7654
|
});
|
|
7938
7655
|
//#endregion
|
|
7656
|
+
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
7657
|
+
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
7658
|
+
if (!isNodeOfType(node, "Property")) return null;
|
|
7659
|
+
if (node.computed) {
|
|
7660
|
+
if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
|
|
7661
|
+
return null;
|
|
7662
|
+
}
|
|
7663
|
+
if (isNodeOfType(node.key, "Identifier")) return node.key.name;
|
|
7664
|
+
if (isNodeOfType(node.key, "Literal")) {
|
|
7665
|
+
if (typeof node.key.value === "string") return node.key.value;
|
|
7666
|
+
if (options.stringifyNonStringLiterals) return String(node.key.value);
|
|
7667
|
+
}
|
|
7668
|
+
return null;
|
|
7669
|
+
};
|
|
7670
|
+
//#endregion
|
|
7939
7671
|
//#region src/plugin/utils/read-static-boolean.ts
|
|
7940
7672
|
const readStaticBoolean = (node) => {
|
|
7941
7673
|
if (!node) return null;
|
|
@@ -7944,16 +7676,31 @@ const readStaticBoolean = (node) => {
|
|
|
7944
7676
|
return unwrappedNode.value;
|
|
7945
7677
|
};
|
|
7946
7678
|
//#endregion
|
|
7679
|
+
//#region src/plugin/utils/resolve-const-identifier-alias.ts
|
|
7680
|
+
const resolveConstIdentifierAlias = (identifier, scopes) => {
|
|
7681
|
+
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
7682
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
7683
|
+
let symbol = scopes.symbolFor(identifier);
|
|
7684
|
+
while (symbol?.kind === "const") {
|
|
7685
|
+
if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
7686
|
+
visitedSymbolIds.add(symbol.id);
|
|
7687
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
7688
|
+
if (!isNodeOfType(initializer, "Identifier")) return symbol;
|
|
7689
|
+
symbol = scopes.symbolFor(initializer);
|
|
7690
|
+
}
|
|
7691
|
+
return symbol;
|
|
7692
|
+
};
|
|
7693
|
+
//#endregion
|
|
7947
7694
|
//#region src/plugin/utils/is-react-api-call.ts
|
|
7948
7695
|
const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? apiNames === apiName : apiNames.has(apiName);
|
|
7949
7696
|
const isImportedFromReact = (symbol) => {
|
|
7950
7697
|
if (symbol.kind !== "import") return false;
|
|
7951
7698
|
const importDeclaration = symbol.declarationNode.parent;
|
|
7952
|
-
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") &&
|
|
7699
|
+
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react");
|
|
7953
7700
|
};
|
|
7954
|
-
const isNamedReactApiImport = (identifier, apiNames, scopes
|
|
7701
|
+
const isNamedReactApiImport = (identifier, apiNames, scopes) => {
|
|
7955
7702
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
7956
|
-
const symbol =
|
|
7703
|
+
const symbol = scopes.symbolFor(identifier);
|
|
7957
7704
|
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
7958
7705
|
const importedName = getImportedName(symbol.declarationNode);
|
|
7959
7706
|
return Boolean(importedName && includesApiName(apiNames, importedName));
|
|
@@ -7967,7 +7714,7 @@ const isReactApiCall = (node, apiNames, scopes, options = {}) => {
|
|
|
7967
7714
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7968
7715
|
const callee = stripParenExpression(node.callee);
|
|
7969
7716
|
if (isNodeOfType(callee, "Identifier")) {
|
|
7970
|
-
if (isNamedReactApiImport(callee, apiNames, scopes
|
|
7717
|
+
if (isNamedReactApiImport(callee, apiNames, scopes)) return true;
|
|
7971
7718
|
return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
|
|
7972
7719
|
}
|
|
7973
7720
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || !includesApiName(apiNames, callee.property.name)) return false;
|
|
@@ -9899,11 +9646,6 @@ const walkParameterReferences = (pattern, state) => {
|
|
|
9899
9646
|
const walk = (node, state) => {
|
|
9900
9647
|
if (isFunctionLike$1(node)) {
|
|
9901
9648
|
if (isNodeOfType(node, "FunctionDeclaration") && node.id) handleFunctionDeclaration(node, state);
|
|
9902
|
-
const functionParams = node.params ?? [];
|
|
9903
|
-
for (const param of functionParams) {
|
|
9904
|
-
if (!("decorators" in param) || !Array.isArray(param.decorators)) continue;
|
|
9905
|
-
for (const decorator of param.decorators) if (isAstNode(decorator)) walk(decorator, state);
|
|
9906
|
-
}
|
|
9907
9649
|
setNodeScope(node, state);
|
|
9908
9650
|
const fnScope = pushScope(node.type === "ArrowFunctionExpression" ? "arrow-function" : "function", node, state);
|
|
9909
9651
|
if ((isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && node.id) {
|
|
@@ -9916,6 +9658,7 @@ const walk = (node, state) => {
|
|
|
9916
9658
|
});
|
|
9917
9659
|
tagAsBinding(state, node.id);
|
|
9918
9660
|
}
|
|
9661
|
+
const functionParams = node.params ?? [];
|
|
9919
9662
|
handleFunctionParameters(functionParams, fnScope, state);
|
|
9920
9663
|
for (const param of functionParams) walkParameterReferences(param, state);
|
|
9921
9664
|
const body = node.body;
|
|
@@ -9925,9 +9668,6 @@ const walk = (node, state) => {
|
|
|
9925
9668
|
}
|
|
9926
9669
|
if (isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) {
|
|
9927
9670
|
if (isNodeOfType(node, "ClassDeclaration") && node.id) handleClassDeclaration(node, state);
|
|
9928
|
-
if (Array.isArray(node.decorators)) {
|
|
9929
|
-
for (const decorator of node.decorators) if (isAstNode(decorator)) walk(decorator, state);
|
|
9930
|
-
}
|
|
9931
9671
|
const classScope = pushScope("class", node, state);
|
|
9932
9672
|
setNodeScope(node, state);
|
|
9933
9673
|
if (isNodeOfType(node, "ClassExpression") && node.id) {
|
|
@@ -10130,7 +9870,7 @@ const computeClosureCaptures = (functionNode, scopes) => {
|
|
|
10130
9870
|
const functionScope = scopes.ownScopeFor(functionNode) ?? scopes.scopeFor(functionNode);
|
|
10131
9871
|
const out = [];
|
|
10132
9872
|
const seen = /* @__PURE__ */ new Set();
|
|
10133
|
-
|
|
9873
|
+
const visit = (node) => {
|
|
10134
9874
|
if (node !== functionNode && isFunctionLike$1(node)) {
|
|
10135
9875
|
const innerCaptures = closureCaptures(node, scopes);
|
|
10136
9876
|
for (const reference of innerCaptures) if (reference.resolvedSymbol && !isDescendantScope(reference.resolvedSymbol.scope, functionScope)) {
|
|
@@ -10139,7 +9879,7 @@ const computeClosureCaptures = (functionNode, scopes) => {
|
|
|
10139
9879
|
seen.add(reference.id);
|
|
10140
9880
|
}
|
|
10141
9881
|
}
|
|
10142
|
-
return
|
|
9882
|
+
return;
|
|
10143
9883
|
}
|
|
10144
9884
|
const reference = scopes.referenceFor(node);
|
|
10145
9885
|
if (reference && reference.resolvedSymbol) {
|
|
@@ -10150,7 +9890,17 @@ const computeClosureCaptures = (functionNode, scopes) => {
|
|
|
10150
9890
|
}
|
|
10151
9891
|
}
|
|
10152
9892
|
}
|
|
10153
|
-
|
|
9893
|
+
const record = node;
|
|
9894
|
+
for (const key of Object.keys(record)) {
|
|
9895
|
+
if (key === "parent") continue;
|
|
9896
|
+
if (TYPE_POSITION_CHILD_KEYS.has(key)) continue;
|
|
9897
|
+
const child = record[key];
|
|
9898
|
+
if (Array.isArray(child)) {
|
|
9899
|
+
for (const item of child) if (isAstNode(item)) visit(item);
|
|
9900
|
+
} else if (isAstNode(child)) visit(child);
|
|
9901
|
+
}
|
|
9902
|
+
};
|
|
9903
|
+
visit(functionNode);
|
|
10154
9904
|
return out;
|
|
10155
9905
|
};
|
|
10156
9906
|
const capturesByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
@@ -11176,11 +10926,6 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
11176
10926
|
return { CallExpression(node) {
|
|
11177
10927
|
const hookName = getHookName(node.callee, context.scopes);
|
|
11178
10928
|
if (!hookName || !isHookOfInterest(hookName, node.callee)) return;
|
|
11179
|
-
if (HOOKS_REQUIRING_DEPS_MATCH.has(hookName) && !isReactApiCall(node, HOOKS_REQUIRING_DEPS_MATCH, context.scopes, {
|
|
11180
|
-
allowGlobalReactNamespace: true,
|
|
11181
|
-
allowUnboundBareCalls: true,
|
|
11182
|
-
resolveNamedAliases: true
|
|
11183
|
-
})) return;
|
|
11184
10929
|
const callbackArgumentIndex = getCallbackArgumentIndex(hookName);
|
|
11185
10930
|
const depsArgumentIndex = getDepsArgumentIndex(hookName);
|
|
11186
10931
|
const callbackArgument = node.arguments[callbackArgumentIndex];
|
|
@@ -14475,11 +14220,7 @@ const jsIndexMaps = defineRule({
|
|
|
14475
14220
|
//#region src/plugin/utils/are-expressions-structurally-equal.ts
|
|
14476
14221
|
const areExpressionsStructurallyEqual = (a, b) => {
|
|
14477
14222
|
if (!a || !b) return a === b;
|
|
14478
|
-
const unwrappedA = stripParenExpression(a);
|
|
14479
|
-
const unwrappedB = stripParenExpression(b);
|
|
14480
|
-
if (unwrappedA !== a || unwrappedB !== b) return areExpressionsStructurallyEqual(unwrappedA, unwrappedB);
|
|
14481
14223
|
if (a.type !== b.type) return false;
|
|
14482
|
-
if (isNodeOfType(a, "ThisExpression")) return true;
|
|
14483
14224
|
if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
|
|
14484
14225
|
if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
|
|
14485
14226
|
if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
|
|
@@ -19183,7 +18924,6 @@ const jwtInsecureVerification = defineRule({
|
|
|
19183
18924
|
});
|
|
19184
18925
|
//#endregion
|
|
19185
18926
|
//#region src/plugin/rules/security-scan/key-lifecycle-risk.ts
|
|
19186
|
-
const KEY_MATERIAL_MARKER_PATTERN = /PRIVATE KEY|SSH_PRIVATE_KEY|GPG_PRIVATE_KEY|DEPLOY_KEY|SIGNING_KEY/i;
|
|
19187
18927
|
const keyLifecycleRisk = defineRule({
|
|
19188
18928
|
id: "key-lifecycle-risk",
|
|
19189
18929
|
title: "Long-lived key material in repository",
|
|
@@ -19191,7 +18931,7 @@ const keyLifecycleRisk = defineRule({
|
|
|
19191
18931
|
committedFilesOnly: true,
|
|
19192
18932
|
recommendation: "Remove private keys from source, rotate exposed credentials, prefer short-lived deploy credentials, and document revocation/expiry for release keys.",
|
|
19193
18933
|
scan: scanByPattern({
|
|
19194
|
-
shouldScan: (file) => !TEST_CONTEXT_PATTERN.test(file.relativePath) && !DOCUMENTATION_CONTEXT_PATTERN.test(file.relativePath)
|
|
18934
|
+
shouldScan: (file) => !TEST_CONTEXT_PATTERN.test(file.relativePath) && !DOCUMENTATION_CONTEXT_PATTERN.test(file.relativePath),
|
|
19195
18935
|
pattern: /(?<!(?:placeholder|example|sample|dummy|fake)[\s\S]{0,40})-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----(?:\s|\\r|\\n)*[A-Za-z0-9+/=][A-Za-z0-9+/=\s]{38,}(?![^-]{0,160}\.\.\.)|\b(?:SSH_PRIVATE_KEY|GPG_PRIVATE_KEY|DEPLOY_KEY|SIGNING_KEY)\b\s*[:=]\s*["'][^"'\n]{16,}["']/i,
|
|
19196
18936
|
message: "Private or long-lived release key material appears in the repository."
|
|
19197
18937
|
})
|
|
@@ -19897,21 +19637,15 @@ const localRpcNativeBridgeRisk = defineRule({
|
|
|
19897
19637
|
message: "Code appears to bridge browser code to localhost/native capabilities with weak origin or update/install checks."
|
|
19898
19638
|
})
|
|
19899
19639
|
});
|
|
19900
|
-
//#endregion
|
|
19901
|
-
//#region src/plugin/rules/security-scan/mcp-tool-capability-risk.ts
|
|
19902
|
-
const MCP_IMPORT_PATTERN = /\bfrom\s+["']@modelcontextprotocol\/sdk[^"']*["']|\bMcpServer\b|\bMcpAgent\b/;
|
|
19903
|
-
const MCP_IMPORT_PREFILTER_PATTERN = /@modelcontextprotocol\/sdk|\bMcpServer\b|\bMcpAgent\b/;
|
|
19904
|
-
const MCP_TOOL_SURFACE_PATTERN = /\bserver\.\s*tool\s*\(|\bregisterTool\s*\(|\bsetRequestHandler\s*\(\s*CallToolRequestSchema/;
|
|
19905
|
-
const MCP_TOOL_SURFACE_PREFILTER_PATTERN = /\b(?:tool|registerTool|setRequestHandler)\b/;
|
|
19906
19640
|
const mcpToolCapabilityRisk = defineRule({
|
|
19907
19641
|
id: "mcp-tool-capability-risk",
|
|
19908
19642
|
title: "MCP tool exposes dangerous capability",
|
|
19909
19643
|
severity: "warn",
|
|
19910
19644
|
recommendation: "MCP tool calls run with the connecting client's authority. Validate inputs, enforce per-tool authorization, and avoid raw filesystem/shell/network access where possible.",
|
|
19911
19645
|
scan: scanByPattern({
|
|
19912
|
-
shouldScan: (file) => isProductionSourcePath(file.relativePath)
|
|
19913
|
-
pattern:
|
|
19914
|
-
requireAll: [
|
|
19646
|
+
shouldScan: (file) => isProductionSourcePath(file.relativePath),
|
|
19647
|
+
pattern: /\bserver\.\s*tool\s*\(|\bregisterTool\s*\(|\bsetRequestHandler\s*\(\s*CallToolRequestSchema/,
|
|
19648
|
+
requireAll: [/\bfrom\s+["']@modelcontextprotocol\/sdk[^"']*["']|\bMcpServer\b|\bMcpAgent\b/, AGENT_TOOL_DANGEROUS_CAPABILITY_PATTERN],
|
|
19915
19649
|
ignoreStringLiterals: true,
|
|
19916
19650
|
message: "An MCP tool/resource/prompt handler appears to expose file, shell, network, or code-execution capability."
|
|
19917
19651
|
})
|
|
@@ -21022,8 +20756,8 @@ const catchClauseRethrowsCaught = (handler) => {
|
|
|
21022
20756
|
return didRethrow;
|
|
21023
20757
|
};
|
|
21024
20758
|
//#endregion
|
|
21025
|
-
//#region src/plugin/utils/
|
|
21026
|
-
const
|
|
20759
|
+
//#region src/plugin/utils/find-guarding-try-statement.ts
|
|
20760
|
+
const isImmediatelyInvokedFunctionCallee = (functionNode) => {
|
|
21027
20761
|
let wrappedCallee = functionNode;
|
|
21028
20762
|
let enclosing = functionNode.parent;
|
|
21029
20763
|
while (enclosing && stripParenExpression(enclosing) === functionNode) {
|
|
@@ -21032,13 +20766,11 @@ const isImmediatelyInvokedFunction = (functionNode) => {
|
|
|
21032
20766
|
}
|
|
21033
20767
|
return Boolean(enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee);
|
|
21034
20768
|
};
|
|
21035
|
-
//#endregion
|
|
21036
|
-
//#region src/plugin/utils/find-guarding-try-statement.ts
|
|
21037
20769
|
const findGuardingTryStatement = (node) => {
|
|
21038
20770
|
let child = node;
|
|
21039
20771
|
let ancestor = node.parent;
|
|
21040
20772
|
while (ancestor) {
|
|
21041
|
-
if (isFunctionLike$1(ancestor) && !
|
|
20773
|
+
if (isFunctionLike$1(ancestor) && !isImmediatelyInvokedFunctionCallee(ancestor)) return null;
|
|
21042
20774
|
if (isNodeOfType(ancestor, "TryStatement") && ancestor.block === child && ancestor.handler && !catchClauseRethrowsCaught(ancestor.handler)) return ancestor;
|
|
21043
20775
|
child = ancestor;
|
|
21044
20776
|
ancestor = ancestor.parent ?? null;
|
|
@@ -21590,7 +21322,7 @@ const FILENAME_TO_LANG = {
|
|
|
21590
21322
|
const resolveLang = (filename) => {
|
|
21591
21323
|
return FILENAME_TO_LANG[path.extname(filename).toLowerCase()] ?? "tsx";
|
|
21592
21324
|
};
|
|
21593
|
-
const parseSourceText = (
|
|
21325
|
+
const parseSourceText = (filename, sourceText) => {
|
|
21594
21326
|
try {
|
|
21595
21327
|
const result = parseSync(filename, sourceText, {
|
|
21596
21328
|
astType: "ts",
|
|
@@ -21598,7 +21330,7 @@ const parseSourceText = ({ filename, sourceText, shouldAttachParentReferences =
|
|
|
21598
21330
|
});
|
|
21599
21331
|
if (result.errors.some((parseError) => parseError.severity === "Error")) return null;
|
|
21600
21332
|
const parsedProgram = result.program;
|
|
21601
|
-
|
|
21333
|
+
attachParentReferences(parsedProgram);
|
|
21602
21334
|
return parsedProgram;
|
|
21603
21335
|
} catch {
|
|
21604
21336
|
return null;
|
|
@@ -21636,10 +21368,7 @@ const parseSourceFile = (absoluteFilePath) => {
|
|
|
21636
21368
|
});
|
|
21637
21369
|
return null;
|
|
21638
21370
|
}
|
|
21639
|
-
const parsedProgram = parseSourceText(
|
|
21640
|
-
filename: absoluteFilePath,
|
|
21641
|
-
sourceText
|
|
21642
|
-
});
|
|
21371
|
+
const parsedProgram = parseSourceText(absoluteFilePath, sourceText);
|
|
21643
21372
|
parseCache.set(absoluteFilePath, {
|
|
21644
21373
|
mtimeMs: fileStat.mtimeMs,
|
|
21645
21374
|
size: fileStat.size,
|
|
@@ -22171,8 +21900,6 @@ const DOM_QUERY_MEMBER_NAMES = new Set([
|
|
|
22171
21900
|
]);
|
|
22172
21901
|
const LAYOUT_MEASUREMENT_MEMBER_NAMES = new Set([
|
|
22173
21902
|
"current",
|
|
22174
|
-
"textContent",
|
|
22175
|
-
"innerText",
|
|
22176
21903
|
"scrollWidth",
|
|
22177
21904
|
"clientWidth",
|
|
22178
21905
|
"offsetWidth",
|
|
@@ -22194,7 +21921,7 @@ const POST_MOUNT_GLOBAL_NAMES = new Set([
|
|
|
22194
21921
|
"navigator"
|
|
22195
21922
|
]);
|
|
22196
21923
|
const REF_FACTORY_CALLEE_NAMES = new Set(["useRef", "createRef"]);
|
|
22197
|
-
const hasRefLikeName = (name) => name === "ref" || name.endsWith("Ref") || name.endsWith("ref")
|
|
21924
|
+
const hasRefLikeName = (name) => name === "ref" || name.endsWith("Ref") || name.endsWith("ref");
|
|
22198
21925
|
const isRefFactoryInitializer = (init) => {
|
|
22199
21926
|
if (!init || !isNodeOfType(init, "CallExpression")) return false;
|
|
22200
21927
|
const callee = init.callee;
|
|
@@ -22277,37 +22004,88 @@ const readsPostMountValue = (root) => {
|
|
|
22277
22004
|
return found;
|
|
22278
22005
|
};
|
|
22279
22006
|
//#endregion
|
|
22280
|
-
//#region src/plugin/rules/state-and-effects/utils/effect/
|
|
22281
|
-
const
|
|
22007
|
+
//#region src/plugin/rules/state-and-effects/utils/effect/constants.ts
|
|
22008
|
+
const TYPESCRIPT_VISITOR_KEYS = {
|
|
22009
|
+
TSAsExpression: ["expression", "typeAnnotation"],
|
|
22010
|
+
TSNonNullExpression: ["expression"],
|
|
22011
|
+
TSSatisfiesExpression: ["expression", "typeAnnotation"],
|
|
22012
|
+
TSTypeAssertion: ["typeAnnotation", "expression"],
|
|
22013
|
+
TSInstantiationExpression: ["expression", "typeArguments"]
|
|
22014
|
+
};
|
|
22015
|
+
const VISITOR_KEYS = {
|
|
22016
|
+
...eslintVisitorKeys.KEYS,
|
|
22017
|
+
...TYPESCRIPT_VISITOR_KEYS
|
|
22018
|
+
};
|
|
22282
22019
|
//#endregion
|
|
22283
22020
|
//#region src/plugin/rules/state-and-effects/utils/effect/get-program-analysis.ts
|
|
22284
22021
|
const programToAnalysis = /* @__PURE__ */ new WeakMap();
|
|
22022
|
+
const stripAndRecordParents = (root) => {
|
|
22023
|
+
const restorations = [];
|
|
22024
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
22025
|
+
const visit = (value) => {
|
|
22026
|
+
if (!value || typeof value !== "object") return;
|
|
22027
|
+
if (seen.has(value)) return;
|
|
22028
|
+
seen.add(value);
|
|
22029
|
+
if (Array.isArray(value)) {
|
|
22030
|
+
for (const item of value) visit(item);
|
|
22031
|
+
return;
|
|
22032
|
+
}
|
|
22033
|
+
const record = value;
|
|
22034
|
+
if (!("type" in record)) return;
|
|
22035
|
+
if ("parent" in record) {
|
|
22036
|
+
restorations.push({
|
|
22037
|
+
node: record,
|
|
22038
|
+
originalParent: record.parent
|
|
22039
|
+
});
|
|
22040
|
+
record.parent = null;
|
|
22041
|
+
}
|
|
22042
|
+
for (const key of Object.keys(record)) {
|
|
22043
|
+
if (key === "parent") continue;
|
|
22044
|
+
visit(record[key]);
|
|
22045
|
+
}
|
|
22046
|
+
};
|
|
22047
|
+
visit(root);
|
|
22048
|
+
return restorations;
|
|
22049
|
+
};
|
|
22050
|
+
const restoreParents = (restorations) => {
|
|
22051
|
+
for (const restoration of restorations) restoration.node.parent = restoration.originalParent;
|
|
22052
|
+
};
|
|
22285
22053
|
const getProgramAnalysis = (anyNode) => {
|
|
22286
22054
|
const programNode = findProgramRoot(anyNode);
|
|
22287
22055
|
if (!programNode) return null;
|
|
22288
22056
|
const cached = programToAnalysis.get(programNode);
|
|
22289
22057
|
if (cached) return cached;
|
|
22290
|
-
const
|
|
22291
|
-
|
|
22292
|
-
|
|
22058
|
+
const restorations = stripAndRecordParents(programNode);
|
|
22059
|
+
let scopeManager;
|
|
22060
|
+
try {
|
|
22061
|
+
scopeManager = analyze(programNode, {
|
|
22293
22062
|
ecmaVersion: 2024,
|
|
22294
22063
|
sourceType: "module",
|
|
22295
|
-
childVisitorKeys:
|
|
22296
|
-
fallback:
|
|
22297
|
-
})
|
|
22298
|
-
|
|
22299
|
-
|
|
22064
|
+
childVisitorKeys: VISITOR_KEYS,
|
|
22065
|
+
fallback: "iteration"
|
|
22066
|
+
});
|
|
22067
|
+
} finally {
|
|
22068
|
+
restoreParents(restorations);
|
|
22069
|
+
}
|
|
22070
|
+
const analysis = {
|
|
22071
|
+
programNode,
|
|
22072
|
+
scopeManager
|
|
22300
22073
|
};
|
|
22301
22074
|
programToAnalysis.set(programNode, analysis);
|
|
22302
22075
|
return analysis;
|
|
22303
22076
|
};
|
|
22304
|
-
const
|
|
22077
|
+
const scopeByNodeCache = /* @__PURE__ */ new WeakMap();
|
|
22078
|
+
const getScopeForNode = (node, manager) => {
|
|
22305
22079
|
if (!node.range) return null;
|
|
22306
|
-
|
|
22080
|
+
let scopeByNode = scopeByNodeCache.get(manager);
|
|
22081
|
+
if (!scopeByNode) {
|
|
22082
|
+
scopeByNode = /* @__PURE__ */ new WeakMap();
|
|
22083
|
+
scopeByNodeCache.set(manager, scopeByNode);
|
|
22084
|
+
}
|
|
22307
22085
|
if (scopeByNode.has(node)) return scopeByNode.get(node) ?? null;
|
|
22308
22086
|
let bestScope = null;
|
|
22309
22087
|
let bestSize = Infinity;
|
|
22310
|
-
for (const scope of
|
|
22088
|
+
for (const scope of manager.scopes) {
|
|
22311
22089
|
const block = scope.block;
|
|
22312
22090
|
if (!block?.range) continue;
|
|
22313
22091
|
if (node.range[0] < block.range[0] || node.range[1] > block.range[1]) continue;
|
|
@@ -22322,6 +22100,7 @@ const getScopeForNode = (node, analysis) => {
|
|
|
22322
22100
|
};
|
|
22323
22101
|
//#endregion
|
|
22324
22102
|
//#region src/plugin/rules/state-and-effects/utils/effect/ast.ts
|
|
22103
|
+
const getChildKeys = (node) => VISITOR_KEYS[node.type] ?? Object.keys(node).filter((key) => key !== "parent");
|
|
22325
22104
|
const HOOK_NAME_PATTERN$2 = /^use[A-Z0-9]/;
|
|
22326
22105
|
const isInsideCallbackArgumentOf = (identifier, initializer) => {
|
|
22327
22106
|
if (!isNodeOfType(initializer, "CallExpression") && !isNodeOfType(initializer, "NewExpression")) return false;
|
|
@@ -22357,7 +22136,7 @@ const descend = (node, visit, visited = /* @__PURE__ */ new Set()) => {
|
|
|
22357
22136
|
if (visited.has(node)) return;
|
|
22358
22137
|
visit(node);
|
|
22359
22138
|
visited.add(node);
|
|
22360
|
-
const keys =
|
|
22139
|
+
const keys = getChildKeys(node);
|
|
22361
22140
|
const record = node;
|
|
22362
22141
|
for (const key of keys) {
|
|
22363
22142
|
const child = record[key];
|
|
@@ -22390,11 +22169,16 @@ const findDownstreamNodes = (topNode, type) => {
|
|
|
22390
22169
|
});
|
|
22391
22170
|
return nodes;
|
|
22392
22171
|
};
|
|
22172
|
+
const refByIdentifierCache = /* @__PURE__ */ new WeakMap();
|
|
22393
22173
|
const getRef = (analysis, identifier) => {
|
|
22394
|
-
|
|
22174
|
+
let refByIdentifier = refByIdentifierCache.get(analysis);
|
|
22175
|
+
if (!refByIdentifier) {
|
|
22176
|
+
refByIdentifier = /* @__PURE__ */ new WeakMap();
|
|
22177
|
+
refByIdentifierCache.set(analysis, refByIdentifier);
|
|
22178
|
+
}
|
|
22395
22179
|
if (refByIdentifier.has(identifier)) return refByIdentifier.get(identifier) ?? null;
|
|
22396
22180
|
let resolvedReference = null;
|
|
22397
|
-
const scope = getScopeForNode(identifier, analysis);
|
|
22181
|
+
const scope = getScopeForNode(identifier, analysis.scopeManager);
|
|
22398
22182
|
if (scope) {
|
|
22399
22183
|
for (const reference of scope.references) if (reference.identifier === identifier) {
|
|
22400
22184
|
resolvedReference = reference;
|
|
@@ -22642,7 +22426,7 @@ const isReactFunctionalHOC = (analysis, node) => {
|
|
|
22642
22426
|
const isWrappedSeparately = () => {
|
|
22643
22427
|
if (!isNodeOfType(node.id, "Identifier")) return false;
|
|
22644
22428
|
const bindingName = node.id.name;
|
|
22645
|
-
const containingScope = getScopeForNode(node, analysis);
|
|
22429
|
+
const containingScope = getScopeForNode(node, analysis.scopeManager);
|
|
22646
22430
|
if (!containingScope) return false;
|
|
22647
22431
|
const variable = containingScope.variables.find((v) => v.name === bindingName);
|
|
22648
22432
|
if (!variable) return false;
|
|
@@ -22873,14 +22657,11 @@ const hasCleanupReturn = (analysis, node, visited = /* @__PURE__ */ new WeakSet(
|
|
|
22873
22657
|
if (isNodeOfType(node, "ReturnStatement") && node.argument != null) return isCleanupReturnArgument(analysis, node.argument);
|
|
22874
22658
|
if (!isNodeOfType(node, "BlockStatement") && isFunctionLike$1(node)) return false;
|
|
22875
22659
|
const record = node;
|
|
22876
|
-
const
|
|
22877
|
-
|
|
22878
|
-
|
|
22879
|
-
|
|
22880
|
-
|
|
22881
|
-
if (isAstNode(item) && hasCleanupReturn(analysis, item, visited)) return true;
|
|
22882
|
-
}
|
|
22883
|
-
else if (isAstNode(value) && hasCleanupReturn(analysis, value, visited)) return true;
|
|
22660
|
+
for (const [key, value] of Object.entries(record)) {
|
|
22661
|
+
if (key === "parent") continue;
|
|
22662
|
+
if (Array.isArray(value)) {
|
|
22663
|
+
if (value.some((item) => isAstNode(item) && hasCleanupReturn(analysis, item, visited))) return true;
|
|
22664
|
+
} else if (isAstNode(value) && hasCleanupReturn(analysis, value, visited)) return true;
|
|
22884
22665
|
}
|
|
22885
22666
|
return false;
|
|
22886
22667
|
};
|
|
@@ -23108,73 +22889,61 @@ const PURE_GLOBAL_CALLEE_NAMES = new Set([
|
|
|
23108
22889
|
"Array",
|
|
23109
22890
|
"BigInt",
|
|
23110
22891
|
"Boolean",
|
|
23111
|
-
"encodeURIComponent",
|
|
23112
22892
|
"Number",
|
|
23113
22893
|
"Object",
|
|
23114
22894
|
"String",
|
|
23115
22895
|
"parseFloat",
|
|
23116
|
-
"parseInt"
|
|
23117
|
-
"structuredClone"
|
|
23118
|
-
]);
|
|
23119
|
-
const PURE_GLOBAL_CONSTRUCTOR_NAMES = new Set(["Date", "Set"]);
|
|
23120
|
-
const PURE_HELPER_NAMESPACE_MEMBER_NAMES = new Map([
|
|
23121
|
-
["Array", new Set(["from"])],
|
|
23122
|
-
["JSON", new Set([
|
|
23123
|
-
"isRawJSON",
|
|
23124
|
-
"parse",
|
|
23125
|
-
"rawJSON",
|
|
23126
|
-
"stringify"
|
|
23127
|
-
])],
|
|
23128
|
-
["Math", new Set([
|
|
23129
|
-
"abs",
|
|
23130
|
-
"acos",
|
|
23131
|
-
"acosh",
|
|
23132
|
-
"asin",
|
|
23133
|
-
"asinh",
|
|
23134
|
-
"atan",
|
|
23135
|
-
"atan2",
|
|
23136
|
-
"atanh",
|
|
23137
|
-
"cbrt",
|
|
23138
|
-
"ceil",
|
|
23139
|
-
"clz32",
|
|
23140
|
-
"cos",
|
|
23141
|
-
"cosh",
|
|
23142
|
-
"exp",
|
|
23143
|
-
"floor",
|
|
23144
|
-
"fround",
|
|
23145
|
-
"hypot",
|
|
23146
|
-
"imul",
|
|
23147
|
-
"log",
|
|
23148
|
-
"log10",
|
|
23149
|
-
"log1p",
|
|
23150
|
-
"log2",
|
|
23151
|
-
"max",
|
|
23152
|
-
"min",
|
|
23153
|
-
"pow",
|
|
23154
|
-
"round",
|
|
23155
|
-
"sign",
|
|
23156
|
-
"sin",
|
|
23157
|
-
"sinh",
|
|
23158
|
-
"sqrt",
|
|
23159
|
-
"tan",
|
|
23160
|
-
"tanh",
|
|
23161
|
-
"trunc"
|
|
23162
|
-
])],
|
|
23163
|
-
["Object", new Set(["assign"])]
|
|
22896
|
+
"parseInt"
|
|
23164
22897
|
]);
|
|
22898
|
+
const PURE_GLOBAL_NAMESPACE_NAMES = new Set(["JSON", "Math"]);
|
|
22899
|
+
const PURE_HELPER_NAMESPACE_MEMBER_NAMES = new Map([["JSON", new Set([
|
|
22900
|
+
"isRawJSON",
|
|
22901
|
+
"parse",
|
|
22902
|
+
"rawJSON",
|
|
22903
|
+
"stringify"
|
|
22904
|
+
])], ["Math", new Set([
|
|
22905
|
+
"abs",
|
|
22906
|
+
"acos",
|
|
22907
|
+
"acosh",
|
|
22908
|
+
"asin",
|
|
22909
|
+
"asinh",
|
|
22910
|
+
"atan",
|
|
22911
|
+
"atan2",
|
|
22912
|
+
"atanh",
|
|
22913
|
+
"cbrt",
|
|
22914
|
+
"ceil",
|
|
22915
|
+
"clz32",
|
|
22916
|
+
"cos",
|
|
22917
|
+
"cosh",
|
|
22918
|
+
"exp",
|
|
22919
|
+
"floor",
|
|
22920
|
+
"fround",
|
|
22921
|
+
"hypot",
|
|
22922
|
+
"imul",
|
|
22923
|
+
"log",
|
|
22924
|
+
"log10",
|
|
22925
|
+
"log1p",
|
|
22926
|
+
"log2",
|
|
22927
|
+
"max",
|
|
22928
|
+
"min",
|
|
22929
|
+
"pow",
|
|
22930
|
+
"round",
|
|
22931
|
+
"sign",
|
|
22932
|
+
"sin",
|
|
22933
|
+
"sinh",
|
|
22934
|
+
"sqrt",
|
|
22935
|
+
"tan",
|
|
22936
|
+
"tanh",
|
|
22937
|
+
"trunc"
|
|
22938
|
+
])]]);
|
|
23165
22939
|
const PURE_MEMBER_TRANSFORM_NAMES = new Set([
|
|
23166
22940
|
"concat",
|
|
23167
22941
|
"filter",
|
|
23168
|
-
"flatMap",
|
|
23169
22942
|
"join",
|
|
23170
22943
|
"map",
|
|
23171
|
-
"reduce",
|
|
23172
|
-
"replace",
|
|
23173
|
-
"slice",
|
|
23174
22944
|
"split",
|
|
23175
22945
|
"toLowerCase",
|
|
23176
22946
|
"toString",
|
|
23177
|
-
"toSorted",
|
|
23178
22947
|
"toUpperCase",
|
|
23179
22948
|
"trim"
|
|
23180
22949
|
]);
|
|
@@ -23183,7 +22952,7 @@ const STORAGE_GLOBAL_NAMES$1 = new Set([
|
|
|
23183
22952
|
"localStorage",
|
|
23184
22953
|
"sessionStorage"
|
|
23185
22954
|
]);
|
|
23186
|
-
const getStaticMemberName
|
|
22955
|
+
const getStaticMemberName = (node) => {
|
|
23187
22956
|
if (!isNodeOfType(node, "MemberExpression")) return null;
|
|
23188
22957
|
if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
|
|
23189
22958
|
if (node.computed && isNodeOfType(node.property, "Literal")) return typeof node.property.value === "string" ? node.property.value : null;
|
|
@@ -23198,7 +22967,7 @@ const getCallCalleeName$1 = (callExpression) => {
|
|
|
23198
22967
|
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
23199
22968
|
const callee = stripParenExpression(callExpression.callee);
|
|
23200
22969
|
if (isNodeOfType(callee, "Identifier")) return callee.name;
|
|
23201
|
-
return getStaticMemberName
|
|
22970
|
+
return getStaticMemberName(callee);
|
|
23202
22971
|
};
|
|
23203
22972
|
const getFunctionParameters = (functionNode) => isFunctionLike$1(functionNode) ? functionNode.params ?? [] : [];
|
|
23204
22973
|
const getIdentifierBindingIdentity = (analysis, identifier) => {
|
|
@@ -23314,14 +23083,14 @@ const localUseEventPreservesCallback = (analysis, callee) => {
|
|
|
23314
23083
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
23315
23084
|
const forwardedCallee = stripParenExpression(child.callee);
|
|
23316
23085
|
if (!isNodeOfType(forwardedCallee, "MemberExpression")) return;
|
|
23317
|
-
if (getStaticMemberName
|
|
23086
|
+
if (getStaticMemberName(forwardedCallee) !== "current") return;
|
|
23318
23087
|
if (!isNodeOfType(forwardedCallee.object, "Identifier")) return;
|
|
23319
23088
|
const refReference = getRef(analysis, forwardedCallee.object);
|
|
23320
23089
|
if (!callbackRefDeclarators.find((declarator) => refReference?.resolved?.defs.some((definition) => definition.node === declarator)) || !refReference?.resolved) return;
|
|
23321
23090
|
if (!refReference.resolved.references.some((candidateReference) => {
|
|
23322
23091
|
const memberExpression = candidateReference.identifier.parent;
|
|
23323
23092
|
const assignmentExpression = memberExpression?.parent;
|
|
23324
|
-
if (!memberExpression || !isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName
|
|
23093
|
+
if (!memberExpression || !isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !assignmentExpression || !isNodeOfType(assignmentExpression, "AssignmentExpression") || assignmentExpression.left !== memberExpression) return false;
|
|
23325
23094
|
return getIdentifierBindingIdentity(analysis, assignmentExpression.right) !== callbackBinding;
|
|
23326
23095
|
})) forwardsCallback = true;
|
|
23327
23096
|
});
|
|
@@ -23346,7 +23115,7 @@ const resolveWrappedCallable = (analysis, node) => {
|
|
|
23346
23115
|
return callback && isFunctionLike$1(callback) ? callback : null;
|
|
23347
23116
|
}
|
|
23348
23117
|
if (!isNodeOfType(candidate, "MemberExpression")) return null;
|
|
23349
|
-
if (getStaticMemberName
|
|
23118
|
+
if (getStaticMemberName(candidate) !== "current") return null;
|
|
23350
23119
|
if (!isNodeOfType(candidate.object, "Identifier")) return null;
|
|
23351
23120
|
const reference = getRef(analysis, candidate.object);
|
|
23352
23121
|
const declarator = reference?.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
@@ -23358,7 +23127,7 @@ const resolveWrappedCallable = (analysis, node) => {
|
|
|
23358
23127
|
return Boolean(reference?.resolved?.references.some((candidateReference) => {
|
|
23359
23128
|
const member = candidateReference.identifier.parent;
|
|
23360
23129
|
const assignment = member?.parent;
|
|
23361
|
-
return Boolean(member && isNodeOfType(member, "MemberExpression") && getStaticMemberName
|
|
23130
|
+
return Boolean(member && isNodeOfType(member, "MemberExpression") && getStaticMemberName(member) === "current" && assignment && isNodeOfType(assignment, "AssignmentExpression") && assignment.left === member);
|
|
23362
23131
|
})) ? null : initializer;
|
|
23363
23132
|
};
|
|
23364
23133
|
const functionInvokesItself = (analysis, functionNode) => {
|
|
@@ -23397,7 +23166,7 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilena
|
|
|
23397
23166
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
23398
23167
|
const callee = stripParenExpression(child.callee);
|
|
23399
23168
|
const calleeName = getCallCalleeName$1(child);
|
|
23400
|
-
const memberName = getStaticMemberName
|
|
23169
|
+
const memberName = getStaticMemberName(callee);
|
|
23401
23170
|
const isDeferringCall = calleeName !== null && DEFERRING_CALLEE_NAMES.has(calleeName) || memberName !== null && DEFERRING_MEMBER_NAMES.has(memberName);
|
|
23402
23171
|
const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES$1.has(memberName) && isNodeOfType(callee, "MemberExpression");
|
|
23403
23172
|
const enqueueFrame = (callableNode, argumentsForCallable, isDeferred, introducedBindings, allowWithoutInvocationEvidence = false) => {
|
|
@@ -23556,19 +23325,14 @@ const analyzeHelperExpression = (expression, environment, usedParameterIndices)
|
|
|
23556
23325
|
const callbackSummary = analyzeHelperStatements(node.body.body ?? [], callbackEnvironment, usedParameterIndices, analyzeHelperExpression);
|
|
23557
23326
|
return callbackSummary.isValid && !callbackSummary.canContinue;
|
|
23558
23327
|
}
|
|
23559
|
-
if (isNodeOfType(node, "NewExpression")) {
|
|
23560
|
-
const callee = stripParenExpression(node.callee);
|
|
23561
|
-
if (!isNodeOfType(callee, "Identifier") || !PURE_GLOBAL_CONSTRUCTOR_NAMES.has(callee.name) || environment.parameterIndices.has(callee.name) || environment.recursiveNames.has(callee.name) || environment.shadowedGlobalNames.has(callee.name)) return false;
|
|
23562
|
-
return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
|
|
23563
|
-
}
|
|
23564
23328
|
if (isNodeOfType(node, "CallExpression")) {
|
|
23565
23329
|
const callee = stripParenExpression(node.callee);
|
|
23566
23330
|
const calleeRoot = getMemberRoot(callee);
|
|
23567
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);
|
|
23568
23332
|
const namespaceName = isNodeOfType(calleeRoot, "Identifier") && !environment.parameterIndices.has(calleeRoot.name) && !environment.recursiveNames.has(calleeRoot.name) && !environment.shadowedGlobalNames.has(calleeRoot.name) ? calleeRoot.name : null;
|
|
23569
|
-
const namespaceMemberName = getStaticMemberName
|
|
23333
|
+
const namespaceMemberName = getStaticMemberName(callee);
|
|
23570
23334
|
const isPureNamespaceCall = isNodeOfType(callee, "MemberExpression") && namespaceName !== null && namespaceMemberName !== null && PURE_HELPER_NAMESPACE_MEMBER_NAMES.get(namespaceName)?.has(namespaceMemberName) === true;
|
|
23571
|
-
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName
|
|
23335
|
+
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
|
|
23572
23336
|
if (!isPureGlobalCall && !isPureNamespaceCall && !isPureMemberTransform) return false;
|
|
23573
23337
|
if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression") && !analyzeHelperExpression(callee.object, environment, usedParameterIndices)) return false;
|
|
23574
23338
|
return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
|
|
@@ -23642,7 +23406,7 @@ const isLocallyConstructedObjectMember = (reference, memberExpression) => isNode
|
|
|
23642
23406
|
return isNodeOfType(definitionNode, "VariableDeclarator") && Boolean(definitionNode.init) && (isNodeOfType(definitionNode.init, "ObjectExpression") || isNodeOfType(definitionNode.init, "ArrayExpression"));
|
|
23643
23407
|
}) === true;
|
|
23644
23408
|
const getUseRefDeclarator = (analysis, memberExpression) => {
|
|
23645
|
-
if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName
|
|
23409
|
+
if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !isNodeOfType(memberExpression.object, "Identifier")) return null;
|
|
23646
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;
|
|
23647
23411
|
};
|
|
23648
23412
|
const collectValueEvidence = (analysis, expression, frame, remainingCallFrames, visitedBindings = /* @__PURE__ */ new Set()) => {
|
|
@@ -23711,7 +23475,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23711
23475
|
return collectValueEvidence(analysis, initializer.init, frame, remainingCallFrames, visitedBindings);
|
|
23712
23476
|
}
|
|
23713
23477
|
if (isNodeOfType(node, "MemberExpression")) {
|
|
23714
|
-
if (getStaticMemberName
|
|
23478
|
+
if (getStaticMemberName(node) === "current" && isNodeOfType(node.object, "Identifier")) {
|
|
23715
23479
|
const refBinding = getRef(analysis, node.object)?.resolved;
|
|
23716
23480
|
const refDeclarator = useRefDeclarator;
|
|
23717
23481
|
if (refBinding && refDeclarator && isNodeOfType(refDeclarator, "VariableDeclarator") && isNodeOfType(refDeclarator.init, "CallExpression")) {
|
|
@@ -23727,7 +23491,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23727
23491
|
if (candidateReference.init) continue;
|
|
23728
23492
|
const identifier = candidateReference.identifier;
|
|
23729
23493
|
const member = identifier.parent;
|
|
23730
|
-
if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName
|
|
23494
|
+
if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName(member) !== "current") {
|
|
23731
23495
|
evidence.hasUnknownSource = true;
|
|
23732
23496
|
continue;
|
|
23733
23497
|
}
|
|
@@ -23763,8 +23527,8 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23763
23527
|
}
|
|
23764
23528
|
const callee = stripParenExpression(node.callee);
|
|
23765
23529
|
const calleeRoot = getMemberRoot(callee);
|
|
23766
|
-
const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && getIdentifierBindingIdentity(analysis, callee) === null || isNodeOfType(
|
|
23767
|
-
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName
|
|
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) ?? "");
|
|
23768
23532
|
if (isPureGlobalCall || isPureMemberTransform) {
|
|
23769
23533
|
if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression")) mergeEvidence(evidence, collectValueEvidence(analysis, callee.object, frame, remainingCallFrames, new Set(visitedBindings)));
|
|
23770
23534
|
for (const argument of node.arguments ?? []) {
|
|
@@ -23816,15 +23580,6 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23816
23580
|
}
|
|
23817
23581
|
return evidence;
|
|
23818
23582
|
}
|
|
23819
|
-
if (isNodeOfType(node, "NewExpression")) {
|
|
23820
|
-
const callee = stripParenExpression(node.callee);
|
|
23821
|
-
if (!isNodeOfType(callee, "Identifier") || !PURE_GLOBAL_CONSTRUCTOR_NAMES.has(callee.name) || getIdentifierBindingIdentity(analysis, callee) !== null) {
|
|
23822
|
-
evidence.hasUnknownSource = true;
|
|
23823
|
-
return evidence;
|
|
23824
|
-
}
|
|
23825
|
-
for (const argument of node.arguments ?? []) mergeEvidence(evidence, collectValueEvidence(analysis, argument, frame, remainingCallFrames, new Set(visitedBindings)));
|
|
23826
|
-
return evidence;
|
|
23827
|
-
}
|
|
23828
23583
|
if (isFunctionLike$1(node) || isNodeOfType(node, "AwaitExpression")) {
|
|
23829
23584
|
evidence.hasUnknownSource = true;
|
|
23830
23585
|
return evidence;
|
|
@@ -24243,26 +23998,6 @@ const STATEFUL_HTML_DESCENDANT_TAGS = new Set([
|
|
|
24243
23998
|
"dialog",
|
|
24244
23999
|
"canvas"
|
|
24245
24000
|
]);
|
|
24246
|
-
const STATEFUL_HTML_ATTRIBUTE_NAMES = new Set([
|
|
24247
|
-
"autofocus",
|
|
24248
|
-
"contenteditable",
|
|
24249
|
-
"draggable",
|
|
24250
|
-
"tabindex"
|
|
24251
|
-
]);
|
|
24252
|
-
const isStaticallyFalseAttributeValue = (attribute) => {
|
|
24253
|
-
if (!isNodeOfType(attribute, "JSXAttribute") || !attribute.value) return false;
|
|
24254
|
-
const value = isNodeOfType(attribute.value, "JSXExpressionContainer") ? attribute.value.expression : attribute.value;
|
|
24255
|
-
return isNodeOfType(value, "Literal") && (value.value === false || value.value === "false");
|
|
24256
|
-
};
|
|
24257
|
-
const hasStatefulHtmlAttribute = (openingElement) => {
|
|
24258
|
-
if (!isNodeOfType(openingElement, "JSXOpeningElement")) return false;
|
|
24259
|
-
return openingElement.attributes.some((attribute) => {
|
|
24260
|
-
if (!isNodeOfType(attribute, "JSXAttribute") || !isNodeOfType(attribute.name, "JSXIdentifier")) return false;
|
|
24261
|
-
const attributeName = attribute.name.name.toLowerCase();
|
|
24262
|
-
if (!STATEFUL_HTML_ATTRIBUTE_NAMES.has(attributeName)) return false;
|
|
24263
|
-
return attributeName === "tabindex" || !isStaticallyFalseAttributeValue(attribute);
|
|
24264
|
-
});
|
|
24265
|
-
};
|
|
24266
24001
|
const STATEFUL_DESCENDANT_SCAN_BUDGET = 200;
|
|
24267
24002
|
const rootIdentifierNameOf = (expression) => {
|
|
24268
24003
|
let object = expression;
|
|
@@ -24280,8 +24015,7 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
|
|
|
24280
24015
|
budget -= 1;
|
|
24281
24016
|
const node = stack.pop();
|
|
24282
24017
|
if (isNodeOfType(node, "JSXElement")) {
|
|
24283
|
-
const
|
|
24284
|
-
const name = opening.name;
|
|
24018
|
+
const name = node.openingElement.name;
|
|
24285
24019
|
if (name && isNodeOfType(name, "JSXIdentifier")) {
|
|
24286
24020
|
const tagName = name.name;
|
|
24287
24021
|
const firstChar = tagName.charCodeAt(0);
|
|
@@ -24289,7 +24023,6 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
|
|
|
24289
24023
|
if (STATEFUL_HTML_DESCENDANT_TAGS.has(tagName)) return true;
|
|
24290
24024
|
}
|
|
24291
24025
|
if (name && isNodeOfType(name, "JSXMemberExpression")) return true;
|
|
24292
|
-
if (hasStatefulHtmlAttribute(opening)) return true;
|
|
24293
24026
|
const children = node.children ?? [];
|
|
24294
24027
|
for (const child of children) stack.push(child);
|
|
24295
24028
|
continue;
|
|
@@ -24383,7 +24116,6 @@ const ARITHMETIC_KEY_OPERATORS = new Set([
|
|
|
24383
24116
|
"/",
|
|
24384
24117
|
"%"
|
|
24385
24118
|
]);
|
|
24386
|
-
const bindingMutationResultsByProgram = /* @__PURE__ */ new WeakMap();
|
|
24387
24119
|
const extractCandidateIdentifiers = (expression) => {
|
|
24388
24120
|
const node = stripParenExpression(expression);
|
|
24389
24121
|
if (isNodeOfType(node, "Identifier")) return [node];
|
|
@@ -24424,13 +24156,6 @@ const isArrayFromCall = (node) => {
|
|
|
24424
24156
|
const isBindingReassignedOrMutated = (referenceNode, bindingName) => {
|
|
24425
24157
|
const programRoot = findProgramRoot(referenceNode);
|
|
24426
24158
|
if (!programRoot) return false;
|
|
24427
|
-
let mutationResultsByBindingName = bindingMutationResultsByProgram.get(programRoot);
|
|
24428
|
-
if (!mutationResultsByBindingName) {
|
|
24429
|
-
mutationResultsByBindingName = /* @__PURE__ */ new Map();
|
|
24430
|
-
bindingMutationResultsByProgram.set(programRoot, mutationResultsByBindingName);
|
|
24431
|
-
}
|
|
24432
|
-
const cachedResult = mutationResultsByBindingName.get(bindingName);
|
|
24433
|
-
if (cachedResult !== void 0) return cachedResult;
|
|
24434
24159
|
let didFindWrite = false;
|
|
24435
24160
|
walkAst(programRoot, (child) => {
|
|
24436
24161
|
if (didFindWrite) return false;
|
|
@@ -24447,7 +24172,6 @@ const isBindingReassignedOrMutated = (referenceNode, bindingName) => {
|
|
|
24447
24172
|
return false;
|
|
24448
24173
|
}
|
|
24449
24174
|
});
|
|
24450
|
-
mutationResultsByBindingName.set(bindingName, didFindWrite);
|
|
24451
24175
|
return didFindWrite;
|
|
24452
24176
|
};
|
|
24453
24177
|
/**
|
|
@@ -24849,14 +24573,6 @@ const templateHasOuterMemberIdentity = (template, bindingFunction) => {
|
|
|
24849
24573
|
}
|
|
24850
24574
|
return false;
|
|
24851
24575
|
};
|
|
24852
|
-
const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
|
|
24853
|
-
const referencedItemNames = /* @__PURE__ */ new Set();
|
|
24854
|
-
for (const expression of template.expressions ?? []) {
|
|
24855
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
24856
|
-
if (isNodeOfType(unwrappedExpression, "Identifier") && itemNames.has(unwrappedExpression.name)) referencedItemNames.add(unwrappedExpression.name);
|
|
24857
|
-
}
|
|
24858
|
-
return referencedItemNames;
|
|
24859
|
-
};
|
|
24860
24576
|
const forLoopTestReadsDataLength = (test) => {
|
|
24861
24577
|
let didFindLengthRead = false;
|
|
24862
24578
|
walkAst(test, (child) => {
|
|
@@ -24993,11 +24709,9 @@ const noArrayIndexAsKey = defineRule({
|
|
|
24993
24709
|
} else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
|
|
24994
24710
|
const jsxElement = openingElement.parent;
|
|
24995
24711
|
if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
|
|
24996
|
-
const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
|
|
24997
|
-
const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET;
|
|
24998
24712
|
if (!containsStatefulDescendant(jsxElement, {
|
|
24999
|
-
memberRootNames:
|
|
25000
|
-
bareIdentifierNames:
|
|
24713
|
+
memberRootNames: INLINE_TEXT_LEAF_TAGS.has(elementName.name) ? itemNames : EMPTY_NAME_SET,
|
|
24714
|
+
bareIdentifierNames: derivedNames
|
|
25001
24715
|
})) return;
|
|
25002
24716
|
}
|
|
25003
24717
|
}
|
|
@@ -25165,11 +24879,7 @@ const noAsyncEffectCallback = defineRule({
|
|
|
25165
24879
|
severity: "warn",
|
|
25166
24880
|
recommendation: "Don't make the effect callback `async`. Define an async function inside the effect and call it, then return a real cleanup function if you need one.",
|
|
25167
24881
|
create: (context) => ({ CallExpression(node) {
|
|
25168
|
-
if (!
|
|
25169
|
-
allowGlobalReactNamespace: true,
|
|
25170
|
-
allowUnboundBareCalls: true,
|
|
25171
|
-
resolveNamedAliases: true
|
|
25172
|
-
})) return;
|
|
24882
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
25173
24883
|
const callback = getEffectCallback(node);
|
|
25174
24884
|
if (!callback) return;
|
|
25175
24885
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
@@ -25562,10 +25272,10 @@ const declaresAnyDependency = (manifest) => DEPENDENCY_SECTION_NAMES.some((secti
|
|
|
25562
25272
|
const section = manifest[sectionName];
|
|
25563
25273
|
return typeof section === "object" && section !== null && Object.keys(section).length > 0;
|
|
25564
25274
|
});
|
|
25565
|
-
const declaresDependency = (manifest, dependencyName) =>
|
|
25566
|
-
const
|
|
25567
|
-
return
|
|
25568
|
-
}
|
|
25275
|
+
const declaresDependency = (manifest, dependencyName) => {
|
|
25276
|
+
for (const declaredName of iterateDependencyNames(manifest)) if (declaredName === dependencyName) return true;
|
|
25277
|
+
return false;
|
|
25278
|
+
};
|
|
25569
25279
|
const cachedPlatformByManifest = /* @__PURE__ */ new WeakMap();
|
|
25570
25280
|
const classifyPackagePlatform = (filename) => {
|
|
25571
25281
|
const manifest = readNearestPackageManifest(filename);
|
|
@@ -25812,23 +25522,25 @@ const isCallArgumentFunctionExpression = (node) => {
|
|
|
25812
25522
|
return parent.arguments.some((argumentNode) => argumentNode === node);
|
|
25813
25523
|
};
|
|
25814
25524
|
const isNestedRenderEvidenceBoundary = (node) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isCallArgumentFunctionExpression(node);
|
|
25815
|
-
const containsRenderOutput$1 = (rootNode, scopes) => {
|
|
25816
|
-
|
|
25817
|
-
|
|
25818
|
-
|
|
25819
|
-
|
|
25820
|
-
|
|
25821
|
-
|
|
25822
|
-
|
|
25823
|
-
|
|
25824
|
-
|
|
25825
|
-
|
|
25525
|
+
const containsRenderOutput$1 = (node, rootNode, scopes) => {
|
|
25526
|
+
if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
|
|
25527
|
+
if (node.type === "JSXElement" || node.type === "JSXFragment") return true;
|
|
25528
|
+
if (isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS)) return true;
|
|
25529
|
+
const nodeRecord = node;
|
|
25530
|
+
for (const key of Object.keys(nodeRecord)) {
|
|
25531
|
+
if (key === "parent") continue;
|
|
25532
|
+
const child = nodeRecord[key];
|
|
25533
|
+
if (Array.isArray(child)) {
|
|
25534
|
+
for (const innerChild of child) if (isAstNode(innerChild) && containsRenderOutput$1(innerChild, rootNode, scopes)) return true;
|
|
25535
|
+
} else if (isAstNode(child) && containsRenderOutput$1(child, rootNode, scopes)) return true;
|
|
25536
|
+
}
|
|
25537
|
+
return false;
|
|
25826
25538
|
};
|
|
25827
25539
|
const renderOutputCache = /* @__PURE__ */ new WeakMap();
|
|
25828
25540
|
const functionContainsReactRenderOutput = (functionNode, scopes) => {
|
|
25829
25541
|
const cachedEntry = renderOutputCache.get(functionNode);
|
|
25830
25542
|
if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
|
|
25831
|
-
const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
|
|
25543
|
+
const hasRenderOutput = containsRenderOutput$1(functionNode, functionNode, scopes);
|
|
25832
25544
|
renderOutputCache.set(functionNode, {
|
|
25833
25545
|
scopes,
|
|
25834
25546
|
hasRenderOutput
|
|
@@ -26367,19 +26079,6 @@ const noCloneElement = defineRule({
|
|
|
26367
26079
|
} })
|
|
26368
26080
|
});
|
|
26369
26081
|
//#endregion
|
|
26370
|
-
//#region src/plugin/utils/get-call-method-name.ts
|
|
26371
|
-
/**
|
|
26372
|
-
* Returns the static method name of a call's callee when it's a
|
|
26373
|
-
* non-computed MemberExpression (`obj.method` → `"method"`), or
|
|
26374
|
-
* `null` otherwise. Used by `no-pass-data-to-parent` and
|
|
26375
|
-
* `no-pass-live-state-to-parent` to match the receiver-bound
|
|
26376
|
-
* method-call shape against an allow/block list.
|
|
26377
|
-
*/
|
|
26378
|
-
const getCallMethodName = (callee) => {
|
|
26379
|
-
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
|
|
26380
|
-
return null;
|
|
26381
|
-
};
|
|
26382
|
-
//#endregion
|
|
26383
26082
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
26384
26083
|
const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
26385
26084
|
const CONTEXT_MODULES = [
|
|
@@ -26387,35 +26086,23 @@ const CONTEXT_MODULES = [
|
|
|
26387
26086
|
"use-context-selector",
|
|
26388
26087
|
"react-tracked"
|
|
26389
26088
|
];
|
|
26390
|
-
const
|
|
26391
|
-
if (symbol?.kind !== "import") return null;
|
|
26392
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
26393
|
-
if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration") || typeof importDeclaration.source.value !== "string" || !CONTEXT_MODULES.includes(importDeclaration.source.value)) return null;
|
|
26394
|
-
return importDeclaration.source.value;
|
|
26395
|
-
};
|
|
26396
|
-
const isSupportedNamespaceSymbol = (symbol) => getSupportedContextImportSource(symbol) !== null && Boolean(symbol && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
|
|
26397
|
-
const isDestructuredCreateContextBinding = (identifier, scopes) => {
|
|
26398
|
-
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
26399
|
-
const symbol = scopes.symbolFor(identifier);
|
|
26400
|
-
if (symbol?.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "ObjectPattern")) return false;
|
|
26401
|
-
const property = symbol.bindingIdentifier.parent;
|
|
26402
|
-
if (!property || !isNodeOfType(property, "Property") || getStaticPropertyKeyName(property, { allowComputedString: true }) !== "createContext") return false;
|
|
26403
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
26404
|
-
return isNodeOfType(initializer, "Identifier") && isSupportedNamespaceSymbol(resolveConstIdentifierAlias(initializer, scopes));
|
|
26405
|
-
};
|
|
26406
|
-
const isCreateContextCall = (node, scopes) => {
|
|
26407
|
-
const callee = stripParenExpression(node.callee);
|
|
26089
|
+
const isCreateContextCallee = (callee) => {
|
|
26408
26090
|
if (isNodeOfType(callee, "Identifier")) {
|
|
26409
|
-
|
|
26410
|
-
|
|
26411
|
-
return getSupportedContextImportSource(symbol) !== null && Boolean(symbol && getImportedName(symbol.declarationNode) === "createContext");
|
|
26091
|
+
const binding = getImportBindingForName(callee, callee.name);
|
|
26092
|
+
return binding !== null && binding.exportedName === "createContext" && CONTEXT_MODULES.includes(binding.source);
|
|
26412
26093
|
}
|
|
26413
|
-
if (
|
|
26414
|
-
|
|
26415
|
-
|
|
26416
|
-
|
|
26417
|
-
|
|
26418
|
-
|
|
26094
|
+
if (isNodeOfType(callee, "MemberExpression") && !callee.computed) {
|
|
26095
|
+
const namespaceIdentifier = callee.object;
|
|
26096
|
+
const propertyIdentifier = callee.property;
|
|
26097
|
+
if (!isNodeOfType(namespaceIdentifier, "Identifier")) return false;
|
|
26098
|
+
if (!isNodeOfType(propertyIdentifier, "Identifier")) return false;
|
|
26099
|
+
if (propertyIdentifier.name !== "createContext") return false;
|
|
26100
|
+
const namespaceName = namespaceIdentifier.name;
|
|
26101
|
+
if (isCanonicalReactNamespaceName(namespaceName)) return true;
|
|
26102
|
+
const importSource = getImportSourceForName(namespaceIdentifier, namespaceName);
|
|
26103
|
+
return importSource !== null && CONTEXT_MODULES.includes(importSource);
|
|
26104
|
+
}
|
|
26105
|
+
return false;
|
|
26419
26106
|
};
|
|
26420
26107
|
const noCreateContextInRender = defineRule({
|
|
26421
26108
|
id: "no-create-context-in-render",
|
|
@@ -26424,7 +26111,7 @@ const noCreateContextInRender = defineRule({
|
|
|
26424
26111
|
category: "Correctness",
|
|
26425
26112
|
recommendation: "Move `createContext(...)` outside the component, to the top level of the file, so it stays the same on every render.",
|
|
26426
26113
|
create: (context) => ({ CallExpression(node) {
|
|
26427
|
-
if (!
|
|
26114
|
+
if (!isCreateContextCallee(node.callee)) return;
|
|
26428
26115
|
const componentOrHookName = enclosingComponentOrHookName(node);
|
|
26429
26116
|
if (!componentOrHookName) return;
|
|
26430
26117
|
context.report({
|
|
@@ -27659,7 +27346,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
27659
27346
|
let ancestor = setStateCall.parent;
|
|
27660
27347
|
while (ancestor) {
|
|
27661
27348
|
if (!lifecycleMember) {
|
|
27662
|
-
if (FUNCTION_NODE_TYPES$1.has(ancestor.type)
|
|
27349
|
+
if (FUNCTION_NODE_TYPES$1.has(ancestor.type)) nestedFunctionCount += 1;
|
|
27663
27350
|
if (isLifecycleMember(ancestor, lifecycleNames)) lifecycleMember = ancestor;
|
|
27664
27351
|
} else if (isEs5Component(ancestor) || isEs6Component(ancestor)) {
|
|
27665
27352
|
if (nestedFunctionCount > 1 && !options.disallowInNestedFunctions) return false;
|
|
@@ -27861,7 +27548,7 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
|
|
|
27861
27548
|
const body = lifecycleFunction.body;
|
|
27862
27549
|
if (!body) return derivedNames;
|
|
27863
27550
|
walkAst(body, (node) => {
|
|
27864
|
-
if (FUNCTION_NODE_TYPES.has(node.type)
|
|
27551
|
+
if (FUNCTION_NODE_TYPES.has(node.type)) return false;
|
|
27865
27552
|
if (!isNodeOfType(node, "VariableDeclarator")) return;
|
|
27866
27553
|
const init = node.init;
|
|
27867
27554
|
if (!init) return;
|
|
@@ -27871,67 +27558,6 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
|
|
|
27871
27558
|
return derivedNames;
|
|
27872
27559
|
};
|
|
27873
27560
|
const isStatefulOperand = (node, paramNames, derivedNames) => referencesAnyName(node, paramNames) || referencesAnyName(node, derivedNames) || containsThisStateOrProps(node);
|
|
27874
|
-
const getStaticMemberName = (node) => {
|
|
27875
|
-
if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
|
|
27876
|
-
return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
|
|
27877
|
-
};
|
|
27878
|
-
const getThisStateFieldName = (node) => {
|
|
27879
|
-
const unwrappedNode = stripParenExpression(node);
|
|
27880
|
-
if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
|
|
27881
|
-
const object = stripParenExpression(unwrappedNode.object);
|
|
27882
|
-
if (!isNodeOfType(object, "MemberExpression") || !isNodeOfType(stripParenExpression(object.object), "ThisExpression") || getStaticMemberName(object) !== "state") return null;
|
|
27883
|
-
return getStaticMemberName(unwrappedNode);
|
|
27884
|
-
};
|
|
27885
|
-
const collectLocalInitializers = (lifecycleFunction) => {
|
|
27886
|
-
const initializers = /* @__PURE__ */ new Map();
|
|
27887
|
-
const body = lifecycleFunction.body;
|
|
27888
|
-
if (!body) return initializers;
|
|
27889
|
-
walkAst(body, (node) => {
|
|
27890
|
-
if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
|
|
27891
|
-
if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.init) initializers.set(node.id.name, node.init);
|
|
27892
|
-
});
|
|
27893
|
-
return initializers;
|
|
27894
|
-
};
|
|
27895
|
-
const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
|
|
27896
|
-
if (readsPostMountValue(node)) return true;
|
|
27897
|
-
const referencedNames = /* @__PURE__ */ new Set();
|
|
27898
|
-
collectReferenceIdentifierNames(node, referencedNames);
|
|
27899
|
-
for (const referencedName of referencedNames) {
|
|
27900
|
-
if (visitedNames.has(referencedName)) continue;
|
|
27901
|
-
const initializer = localInitializers.get(referencedName);
|
|
27902
|
-
if (!initializer) continue;
|
|
27903
|
-
if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
|
|
27904
|
-
}
|
|
27905
|
-
return false;
|
|
27906
|
-
};
|
|
27907
|
-
const getSetStateFieldValue = (setStateCall, fieldName) => {
|
|
27908
|
-
if (!isNodeOfType(setStateCall, "CallExpression")) return null;
|
|
27909
|
-
const argument = setStateCall.arguments?.[0];
|
|
27910
|
-
if (!argument || !isNodeOfType(argument, "ObjectExpression")) return null;
|
|
27911
|
-
for (const property of argument.properties ?? []) {
|
|
27912
|
-
if (!isNodeOfType(property, "Property") || property.computed === true) continue;
|
|
27913
|
-
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;
|
|
27914
|
-
}
|
|
27915
|
-
return null;
|
|
27916
|
-
};
|
|
27917
|
-
const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
|
|
27918
|
-
let qualifies = false;
|
|
27919
|
-
walkAst(test, (node) => {
|
|
27920
|
-
if (qualifies) return false;
|
|
27921
|
-
if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
|
|
27922
|
-
const leftFieldName = getThisStateFieldName(node.left);
|
|
27923
|
-
const rightFieldName = getThisStateFieldName(node.right);
|
|
27924
|
-
const fieldName = leftFieldName ?? rightFieldName;
|
|
27925
|
-
const comparedValue = leftFieldName ? node.right : node.left;
|
|
27926
|
-
if (!fieldName || !leftFieldName && !rightFieldName) return;
|
|
27927
|
-
const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
|
|
27928
|
-
if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
|
|
27929
|
-
if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
|
|
27930
|
-
qualifies = true;
|
|
27931
|
-
return false;
|
|
27932
|
-
});
|
|
27933
|
-
return qualifies;
|
|
27934
|
-
};
|
|
27935
27561
|
const isDiffGuardTest = (test, paramNames, derivedNames) => {
|
|
27936
27562
|
if (referencesAnyName(test, paramNames)) return true;
|
|
27937
27563
|
let qualifies = false;
|
|
@@ -27939,7 +27565,7 @@ const isDiffGuardTest = (test, paramNames, derivedNames) => {
|
|
|
27939
27565
|
if (qualifies) return false;
|
|
27940
27566
|
if (!isNodeOfType(node, "BinaryExpression")) return;
|
|
27941
27567
|
if (!EQUALITY_OPERATORS.has(node.operator)) return;
|
|
27942
|
-
if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames)
|
|
27568
|
+
if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames)) {
|
|
27943
27569
|
qualifies = true;
|
|
27944
27570
|
return false;
|
|
27945
27571
|
}
|
|
@@ -27952,12 +27578,11 @@ const isInsideDiffGuard = (setStateCall) => {
|
|
|
27952
27578
|
const paramNames = /* @__PURE__ */ new Set();
|
|
27953
27579
|
for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
|
|
27954
27580
|
const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
|
|
27955
|
-
const localInitializers = collectLocalInitializers(lifecycleFunction);
|
|
27956
27581
|
let child = setStateCall;
|
|
27957
27582
|
let ancestor = setStateCall.parent;
|
|
27958
27583
|
while (ancestor && ancestor !== lifecycleFunction) {
|
|
27959
27584
|
const guardTest = isNodeOfType(ancestor, "IfStatement") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "ConditionalExpression") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right && ancestor.left || null;
|
|
27960
|
-
if (guardTest &&
|
|
27585
|
+
if (guardTest && isDiffGuardTest(guardTest, paramNames, derivedNames)) return true;
|
|
27961
27586
|
child = ancestor;
|
|
27962
27587
|
ancestor = ancestor.parent ?? null;
|
|
27963
27588
|
}
|
|
@@ -28138,16 +27763,7 @@ const producesOpaqueInstanceValue = (expression) => {
|
|
|
28138
27763
|
const collectSetterValueObservations = (componentBody, setterNames) => {
|
|
28139
27764
|
const plainFedSetterNames = /* @__PURE__ */ new Set();
|
|
28140
27765
|
const opaqueFedSetterNames = /* @__PURE__ */ new Set();
|
|
28141
|
-
const callbackRefSetterNames = /* @__PURE__ */ new Set();
|
|
28142
27766
|
walkComponentRespectingShadows(componentBody, /* @__PURE__ */ new Set(), (node, currentlyShadowed) => {
|
|
28143
|
-
if (isNodeOfType(node, "JSXAttribute")) {
|
|
28144
|
-
const attributeName = node.name;
|
|
28145
|
-
if (isNodeOfType(attributeName, "JSXIdentifier") && attributeName.name === "ref" && node.value && isNodeOfType(node.value, "JSXExpressionContainer")) {
|
|
28146
|
-
const expression = stripParenExpression(node.value.expression);
|
|
28147
|
-
if (isNodeOfType(expression, "Identifier") && setterNames.has(expression.name)) callbackRefSetterNames.add(expression.name);
|
|
28148
|
-
}
|
|
28149
|
-
return;
|
|
28150
|
-
}
|
|
28151
27767
|
if (!isNodeOfType(node, "CallExpression")) return;
|
|
28152
27768
|
if (!isNodeOfType(node.callee, "Identifier")) return;
|
|
28153
27769
|
const setterName = node.callee.name;
|
|
@@ -28164,10 +27780,21 @@ const collectSetterValueObservations = (componentBody, setterNames) => {
|
|
|
28164
27780
|
}, true);
|
|
28165
27781
|
return {
|
|
28166
27782
|
plainFedSetterNames,
|
|
28167
|
-
opaqueFedSetterNames
|
|
28168
|
-
callbackRefSetterNames
|
|
27783
|
+
opaqueFedSetterNames
|
|
28169
27784
|
};
|
|
28170
27785
|
};
|
|
27786
|
+
const collectCallbackRefSetterNames = (componentBody) => {
|
|
27787
|
+
const callbackRefSetterNames = /* @__PURE__ */ new Set();
|
|
27788
|
+
walkAst(componentBody, (node) => {
|
|
27789
|
+
if (!isNodeOfType(node, "JSXAttribute")) return;
|
|
27790
|
+
const attributeName = node.name;
|
|
27791
|
+
if (isNodeOfType(attributeName, "JSXIdentifier") && attributeName.name === "ref" && node.value && isNodeOfType(node.value, "JSXExpressionContainer")) {
|
|
27792
|
+
const expression = stripParenExpression(node.value.expression);
|
|
27793
|
+
if (isNodeOfType(expression, "Identifier")) callbackRefSetterNames.add(expression.name);
|
|
27794
|
+
}
|
|
27795
|
+
});
|
|
27796
|
+
return callbackRefSetterNames;
|
|
27797
|
+
};
|
|
28171
27798
|
const collectFunctionLocalBindings = (functionNode) => {
|
|
28172
27799
|
const localBindings = /* @__PURE__ */ new Set();
|
|
28173
27800
|
if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return localBindings;
|
|
@@ -28179,8 +27806,8 @@ const collectFunctionLocalBindings = (functionNode) => {
|
|
|
28179
27806
|
return localBindings;
|
|
28180
27807
|
};
|
|
28181
27808
|
const collectBlockScopedBindings = (node) => {
|
|
27809
|
+
const blockBindings = /* @__PURE__ */ new Set();
|
|
28182
27810
|
if (isNodeOfType(node, "BlockStatement")) {
|
|
28183
|
-
const blockBindings = /* @__PURE__ */ new Set();
|
|
28184
27811
|
for (const statement of node.body ?? []) {
|
|
28185
27812
|
if (!isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
28186
27813
|
for (const declarator of statement.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
|
|
@@ -28188,22 +27815,17 @@ const collectBlockScopedBindings = (node) => {
|
|
|
28188
27815
|
return blockBindings;
|
|
28189
27816
|
}
|
|
28190
27817
|
if (isNodeOfType(node, "ForStatement") && isNodeOfType(node.init, "VariableDeclaration")) {
|
|
28191
|
-
const blockBindings = /* @__PURE__ */ new Set();
|
|
28192
27818
|
for (const declarator of node.init.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
|
|
28193
27819
|
return blockBindings;
|
|
28194
27820
|
}
|
|
28195
|
-
if ((isNodeOfType(node, "ForOfStatement") || isNodeOfType(node, "ForInStatement")) && isNodeOfType(node.left, "VariableDeclaration"))
|
|
28196
|
-
|
|
28197
|
-
for (const declarator of node.left.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
|
|
28198
|
-
return blockBindings;
|
|
28199
|
-
}
|
|
28200
|
-
return null;
|
|
27821
|
+
if ((isNodeOfType(node, "ForOfStatement") || isNodeOfType(node, "ForInStatement")) && isNodeOfType(node.left, "VariableDeclaration")) for (const declarator of node.left.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
|
|
27822
|
+
return blockBindings;
|
|
28201
27823
|
};
|
|
28202
27824
|
const walkComponentRespectingShadows = (node, shadowedStateNames, visit, isComponentBodyRoot = false) => {
|
|
28203
27825
|
if (!node || typeof node !== "object") return;
|
|
28204
27826
|
let nextShadowedStateNames = shadowedStateNames;
|
|
28205
|
-
const localBindings = isComponentBodyRoot ?
|
|
28206
|
-
if (localBindings
|
|
27827
|
+
const localBindings = isComponentBodyRoot ? /* @__PURE__ */ new Set() : isFunctionLike$1(node) ? collectFunctionLocalBindings(node) : collectBlockScopedBindings(node);
|
|
27828
|
+
if (localBindings.size > 0) {
|
|
28207
27829
|
const merged = new Set(shadowedStateNames);
|
|
28208
27830
|
for (const localName of localBindings) merged.add(localName);
|
|
28209
27831
|
nextShadowedStateNames = merged;
|
|
@@ -28229,10 +27851,11 @@ const noDirectStateMutation = defineRule({
|
|
|
28229
27851
|
const bindings = collectUseStateBindings(componentBody);
|
|
28230
27852
|
if (bindings.length === 0) return;
|
|
28231
27853
|
const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
|
|
27854
|
+
const callbackRefSetterNames = collectCallbackRefSetterNames(componentBody);
|
|
28232
27855
|
const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
|
|
28233
27856
|
const plainObjectStateValueNames = /* @__PURE__ */ new Set();
|
|
28234
27857
|
for (const binding of bindings) {
|
|
28235
|
-
if (
|
|
27858
|
+
if (callbackRefSetterNames.has(binding.setterName)) continue;
|
|
28236
27859
|
if (!isNodeOfType(binding.declarator.init, "CallExpression")) continue;
|
|
28237
27860
|
const initializerArgument = binding.declarator.init.arguments?.[0];
|
|
28238
27861
|
if (!initializerMarksPlainState(initializerArgument)) continue;
|
|
@@ -28374,31 +27997,20 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
28374
27997
|
} })
|
|
28375
27998
|
});
|
|
28376
27999
|
//#endregion
|
|
28377
|
-
//#region src/plugin/utils/get-static-property-name.ts
|
|
28378
|
-
const getStaticPropertyName = (memberExpression) => {
|
|
28379
|
-
const property = memberExpression.property;
|
|
28380
|
-
if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
28381
|
-
if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
|
|
28382
|
-
if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
|
|
28383
|
-
return null;
|
|
28384
|
-
};
|
|
28385
|
-
//#endregion
|
|
28386
28000
|
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
28387
28001
|
const MESSAGE$24 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
|
|
28388
28002
|
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
28389
|
-
const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
|
|
28390
28003
|
const noDocumentWrite = defineRule({
|
|
28391
28004
|
id: "no-document-write",
|
|
28392
28005
|
title: "document.write/writeln",
|
|
28393
28006
|
severity: "warn",
|
|
28394
28007
|
recommendation: "Don't use `document.write()`/`document.writeln()`. Append DOM nodes or set `innerHTML`/`textContent` on a specific element instead.",
|
|
28395
28008
|
create: (context) => ({ CallExpression(node) {
|
|
28396
|
-
const callee =
|
|
28397
|
-
if (!isNodeOfType(callee, "MemberExpression")) return;
|
|
28009
|
+
const callee = node.callee;
|
|
28010
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
|
|
28398
28011
|
const receiver = stripParenExpression(callee.object);
|
|
28399
|
-
if (!isNodeOfType(receiver, "Identifier") ||
|
|
28400
|
-
|
|
28401
|
-
if (methodName === null || !WRITE_METHODS.has(methodName)) return;
|
|
28012
|
+
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "document") return;
|
|
28013
|
+
if (!isNodeOfType(callee.property, "Identifier") || !WRITE_METHODS.has(callee.property.name)) return;
|
|
28402
28014
|
context.report({
|
|
28403
28015
|
node,
|
|
28404
28016
|
message: MESSAGE$24
|
|
@@ -29205,14 +28817,6 @@ const noEffectWithFreshDeps = defineRule({
|
|
|
29205
28817
|
//#endregion
|
|
29206
28818
|
//#region src/plugin/rules/security/no-eval.ts
|
|
29207
28819
|
const SANDBOX_SURFACE_PATH_PATTERN = /(?:^|[/-])sandbox(?:$|[/-])|(?:^|\/)[\w.]+-sandbox(?:\/|\.[cm]?[jt]sx?$)/i;
|
|
29208
|
-
const getExecutableGlobalName = (node, context) => {
|
|
29209
|
-
const expression = stripParenExpression(node);
|
|
29210
|
-
if (isNodeOfType(expression, "Identifier")) return context.scopes.isGlobalReference(expression) ? expression.name : null;
|
|
29211
|
-
if (!isNodeOfType(expression, "MemberExpression")) return null;
|
|
29212
|
-
const receiver = stripParenExpression(expression.object);
|
|
29213
|
-
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "globalThis" || !context.scopes.isGlobalReference(receiver)) return null;
|
|
29214
|
-
return getStaticPropertyName(expression);
|
|
29215
|
-
};
|
|
29216
28820
|
const noEval = defineRule({
|
|
29217
28821
|
id: "no-eval",
|
|
29218
28822
|
title: "eval() runs untrusted code strings",
|
|
@@ -29222,21 +28826,20 @@ const noEval = defineRule({
|
|
|
29222
28826
|
if (SANDBOX_SURFACE_PATH_PATTERN.test(normalizeFilename(context.filename ?? ""))) return {};
|
|
29223
28827
|
return {
|
|
29224
28828
|
CallExpression(node) {
|
|
29225
|
-
|
|
29226
|
-
if (executableGlobalName === "eval") {
|
|
28829
|
+
if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "eval") {
|
|
29227
28830
|
context.report({
|
|
29228
28831
|
node,
|
|
29229
28832
|
message: "eval() is a code-injection vulnerability: it runs any string as code."
|
|
29230
28833
|
});
|
|
29231
28834
|
return;
|
|
29232
28835
|
}
|
|
29233
|
-
if ((
|
|
28836
|
+
if (isNodeOfType(node.callee, "Identifier") && (node.callee.name === "setTimeout" || node.callee.name === "setInterval") && isNodeOfType(node.arguments?.[0], "Literal") && typeof node.arguments[0].value === "string") context.report({
|
|
29234
28837
|
node,
|
|
29235
|
-
message: `Passing a string to ${
|
|
28838
|
+
message: `Passing a string to ${node.callee.name}() is a code-injection vulnerability, since it runs that string as code.`
|
|
29236
28839
|
});
|
|
29237
28840
|
},
|
|
29238
28841
|
NewExpression(node) {
|
|
29239
|
-
if (
|
|
28842
|
+
if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "Function") {
|
|
29240
28843
|
const onlyArgument = node.arguments.length === 1 ? node.arguments[0] : void 0;
|
|
29241
28844
|
if (isNodeOfType(onlyArgument, "Literal") && typeof onlyArgument.value === "string" && onlyArgument.value.trim() === "return this") return;
|
|
29242
28845
|
context.report({
|
|
@@ -30495,11 +30098,7 @@ const noFetchInEffect = defineRule({
|
|
|
30495
30098
|
severity: "warn",
|
|
30496
30099
|
recommendation: "Use a data-fetching layer or Server Component so fetches do not race, double-fire, or leak from `useEffect`.",
|
|
30497
30100
|
create: (context) => ({ CallExpression(node) {
|
|
30498
|
-
if (!
|
|
30499
|
-
allowGlobalReactNamespace: true,
|
|
30500
|
-
allowUnboundBareCalls: true,
|
|
30501
|
-
resolveNamedAliases: true
|
|
30502
|
-
})) return;
|
|
30101
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
30503
30102
|
const callback = getEffectCallback(node);
|
|
30504
30103
|
if (!callback) return;
|
|
30505
30104
|
const analysisFunctions = collectEffectAnalysisFunctions(callback, context);
|
|
@@ -32974,60 +32573,25 @@ const resolveSettings$16 = (settings) => {
|
|
|
32974
32573
|
};
|
|
32975
32574
|
const isHocCall = (call, scopes) => {
|
|
32976
32575
|
if (!isNodeOfType(call, "CallExpression")) return false;
|
|
32977
|
-
|
|
32978
|
-
|
|
32979
|
-
allowUnboundBareCalls: true
|
|
32980
|
-
})) return true;
|
|
32981
|
-
if (isReactHocMemberReference(call.callee, scopes)) return true;
|
|
32576
|
+
const calleeName = flattenCalleeName(call.callee);
|
|
32577
|
+
if (calleeName && REACT_HOC_NAMES.has(calleeName)) return true;
|
|
32982
32578
|
if (!isNodeOfType(call.callee, "Identifier")) return false;
|
|
32983
32579
|
const symbol = scopes.symbolFor(call.callee);
|
|
32984
32580
|
if (!symbol) return false;
|
|
32985
|
-
return symbolMapsToHoc(symbol
|
|
32986
|
-
};
|
|
32987
|
-
const isReactImportEquals = (symbol) => {
|
|
32988
|
-
if (symbol.kind !== "ts-import-equals" || !isNodeOfType(symbol.declarationNode, "TSImportEqualsDeclaration")) return false;
|
|
32989
|
-
const moduleReference = symbol.declarationNode.moduleReference;
|
|
32990
|
-
return Boolean(isNodeOfType(moduleReference, "TSExternalModuleReference") && isNodeOfType(moduleReference.expression, "Literal") && typeof moduleReference.expression.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(moduleReference.expression.value));
|
|
32581
|
+
return symbolMapsToHoc(symbol);
|
|
32991
32582
|
};
|
|
32992
|
-
const
|
|
32993
|
-
if (
|
|
32994
|
-
|
|
32995
|
-
return Boolean(moduleSpecifier && isNodeOfType(moduleSpecifier, "Literal") && typeof moduleSpecifier.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(moduleSpecifier.value));
|
|
32996
|
-
};
|
|
32997
|
-
const isReactNamespaceExpression = (node, scopes) => {
|
|
32998
|
-
if (isRequireReactCall$1(node, scopes)) return true;
|
|
32999
|
-
if (!isNodeOfType(node, "Identifier")) return false;
|
|
33000
|
-
const symbol = scopes.symbolFor(node);
|
|
33001
|
-
if (!symbol) return node.name === "React" && scopes.isGlobalReference(node);
|
|
33002
|
-
if (symbol.initializer && isRequireReactCall$1(symbol.initializer, scopes)) return true;
|
|
33003
|
-
if (isReactImportEquals(symbol)) return true;
|
|
33004
|
-
return isReactNamespaceImport(node, scopes);
|
|
33005
|
-
};
|
|
33006
|
-
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));
|
|
33007
|
-
const getDestructuredPropertyName$1 = (symbol) => {
|
|
33008
|
-
const property = symbol.bindingIdentifier.parent;
|
|
33009
|
-
if (!property || !isNodeOfType(property, "Property") || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
|
|
33010
|
-
if (isNodeOfType(property.key, "Identifier") && !property.computed) return property.key.name;
|
|
33011
|
-
if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
|
|
33012
|
-
return null;
|
|
33013
|
-
};
|
|
33014
|
-
const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
33015
|
-
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
33016
|
-
visitedSymbolIds.add(symbol.id);
|
|
33017
|
-
if (symbol.kind === "import") {
|
|
33018
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
33019
|
-
return Boolean(isImportedFromReact(symbol) && importedName && REACT_HOC_NAMES.has(importedName));
|
|
32583
|
+
const symbolMapsToHoc = (symbol) => {
|
|
32584
|
+
if (REACT_HOC_NAMES.has(symbol.name)) {
|
|
32585
|
+
if (symbol.kind === "import") return true;
|
|
33020
32586
|
}
|
|
33021
32587
|
const init = symbol.initializer;
|
|
33022
32588
|
if (!init) return false;
|
|
33023
|
-
|
|
33024
|
-
|
|
33025
|
-
|
|
33026
|
-
if (isNodeOfType(init, "Identifier")) {
|
|
33027
|
-
const initializedFromSymbol = scopes.symbolFor(init);
|
|
33028
|
-
if (initializedFromSymbol) return symbolMapsToHoc(initializedFromSymbol, scopes, visitedSymbolIds);
|
|
33029
|
-
return REACT_HOC_NAMES.has(init.name) && scopes.isGlobalReference(init);
|
|
32589
|
+
if (isNodeOfType(init, "MemberExpression")) {
|
|
32590
|
+
const flat = flattenCalleeName(init);
|
|
32591
|
+
if (flat && REACT_HOC_NAMES.has(flat)) return true;
|
|
33030
32592
|
}
|
|
32593
|
+
if (isNodeOfType(init, "Identifier") && REACT_HOC_NAMES.has(init.name)) return true;
|
|
32594
|
+
if (REACT_HOC_NAMES.has(symbol.name) && isNodeOfType(init, "Identifier") && init.name === "React") return true;
|
|
33031
32595
|
return false;
|
|
33032
32596
|
};
|
|
33033
32597
|
const isTrivialPassthroughChild = (child) => {
|
|
@@ -33085,14 +32649,26 @@ const isHocComponent = (call, scopes) => {
|
|
|
33085
32649
|
};
|
|
33086
32650
|
const containsJsx = (root) => {
|
|
33087
32651
|
let found = false;
|
|
33088
|
-
|
|
33089
|
-
if (found) return
|
|
32652
|
+
const visit = (node) => {
|
|
32653
|
+
if (found) return;
|
|
33090
32654
|
if (node.type === "JSXElement" || node.type === "JSXFragment") {
|
|
33091
32655
|
found = true;
|
|
33092
|
-
return
|
|
32656
|
+
return;
|
|
33093
32657
|
}
|
|
33094
|
-
if (node !== root
|
|
33095
|
-
|
|
32658
|
+
if (node !== root) {
|
|
32659
|
+
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression" || node.type === "ClassDeclaration" || node.type === "ClassExpression") return;
|
|
32660
|
+
}
|
|
32661
|
+
const record = node;
|
|
32662
|
+
for (const key of Object.keys(record)) {
|
|
32663
|
+
if (key === "parent") continue;
|
|
32664
|
+
const child = record[key];
|
|
32665
|
+
if (Array.isArray(child)) {
|
|
32666
|
+
for (const item of child) if (isAstNode(item)) visit(item);
|
|
32667
|
+
} else if (isAstNode(child)) visit(child);
|
|
32668
|
+
if (found) return;
|
|
32669
|
+
}
|
|
32670
|
+
};
|
|
32671
|
+
visit(root);
|
|
33096
32672
|
return found;
|
|
33097
32673
|
};
|
|
33098
32674
|
const expressionContainsJsx = (expression) => {
|
|
@@ -33116,7 +32692,7 @@ const unwrapTsCast = (expression) => {
|
|
|
33116
32692
|
while (current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSNonNullExpression") current = stripParenExpression(current.expression);
|
|
33117
32693
|
return current;
|
|
33118
32694
|
};
|
|
33119
|
-
const collectReExportedNames = (program
|
|
32695
|
+
const collectReExportedNames = (program) => {
|
|
33120
32696
|
const names = /* @__PURE__ */ new Set();
|
|
33121
32697
|
if (!isNodeOfType(program, "Program")) return names;
|
|
33122
32698
|
for (const statement of program.body) {
|
|
@@ -33140,7 +32716,8 @@ const collectReExportedNames = (program, scopes) => {
|
|
|
33140
32716
|
if (!declarator.init) continue;
|
|
33141
32717
|
const init = unwrapTsCast(declarator.init);
|
|
33142
32718
|
if (isNodeOfType(init, "CallExpression")) {
|
|
33143
|
-
|
|
32719
|
+
const calleeName = flattenCalleeName(init.callee);
|
|
32720
|
+
if (calleeName && REACT_HOC_NAMES.has(calleeName)) {
|
|
33144
32721
|
const wrappedArg = init.arguments[0];
|
|
33145
32722
|
if (wrappedArg && isNodeOfType(wrappedArg, "Identifier")) names.add(wrappedArg.name);
|
|
33146
32723
|
}
|
|
@@ -33209,7 +32786,16 @@ const recordComponent = (context, name, reportNode, isStateless) => {
|
|
|
33209
32786
|
isStateless
|
|
33210
32787
|
});
|
|
33211
32788
|
};
|
|
33212
|
-
const walkChildren = (node, context) =>
|
|
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
|
+
};
|
|
33213
32799
|
const walkComponentSearch = (node, context) => {
|
|
33214
32800
|
if (isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) {
|
|
33215
32801
|
if (isEs6Component(node)) {
|
|
@@ -33311,13 +32897,12 @@ const noMultiComp = defineRule({
|
|
|
33311
32897
|
components: [],
|
|
33312
32898
|
componentDepth: 0,
|
|
33313
32899
|
currentVarName: null,
|
|
33314
|
-
scopes: context.scopes
|
|
33315
|
-
visitChild: (child) => walkComponentSearch(child, visitContext)
|
|
32900
|
+
scopes: context.scopes
|
|
33316
32901
|
};
|
|
33317
32902
|
for (const statement of node.body) walkComponentSearch(statement, visitContext);
|
|
33318
32903
|
const flagged = settings.ignoreStateless ? visitContext.components.filter((component) => !component.isStateless) : visitContext.components;
|
|
33319
32904
|
if (flagged.length <= 2) return;
|
|
33320
|
-
const reExportedNames = collectReExportedNames(node
|
|
32905
|
+
const reExportedNames = collectReExportedNames(node);
|
|
33321
32906
|
const exportedCount = flagged.filter((component) => isExportedDeclaration(component.reportNode, reExportedNames)).length;
|
|
33322
32907
|
if (exportedCount >= flagged.length || flagged.length >= 4 && exportedCount >= Math.floor(flagged.length * .7) || flagged.length >= 8 && exportedCount >= Math.floor(flagged.length * .5)) return;
|
|
33323
32908
|
const isSmallFeatureModule = exportedCount > 0 && exportedCount <= 2 && exportedCount < flagged.length;
|
|
@@ -34709,6 +34294,19 @@ const DATA_SINK_METHOD_NAMES = new Set([
|
|
|
34709
34294
|
"deserialize"
|
|
34710
34295
|
]);
|
|
34711
34296
|
//#endregion
|
|
34297
|
+
//#region src/plugin/utils/get-call-method-name.ts
|
|
34298
|
+
/**
|
|
34299
|
+
* Returns the static method name of a call's callee when it's a
|
|
34300
|
+
* non-computed MemberExpression (`obj.method` → `"method"`), or
|
|
34301
|
+
* `null` otherwise. Used by `no-pass-data-to-parent` and
|
|
34302
|
+
* `no-pass-live-state-to-parent` to match the receiver-bound
|
|
34303
|
+
* method-call shape against an allow/block list.
|
|
34304
|
+
*/
|
|
34305
|
+
const getCallMethodName = (callee) => {
|
|
34306
|
+
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
|
|
34307
|
+
return null;
|
|
34308
|
+
};
|
|
34309
|
+
//#endregion
|
|
34712
34310
|
//#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
|
|
34713
34311
|
const isUseStateIdentifier = (identifier) => {
|
|
34714
34312
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
@@ -35334,30 +34932,6 @@ const guardsRenderShape = (comparison) => {
|
|
|
35334
34932
|
}
|
|
35335
34933
|
return false;
|
|
35336
34934
|
};
|
|
35337
|
-
const isPropsChildrenLength = (node, scopes) => {
|
|
35338
|
-
const unwrappedNode = stripParenExpression(node);
|
|
35339
|
-
return isNodeOfType(unwrappedNode, "MemberExpression") && !unwrappedNode.computed && isNodeOfType(unwrappedNode.property, "Identifier") && unwrappedNode.property.name === "length" && resolvesToPropsChildren(stripParenExpression(unwrappedNode.object), scopes);
|
|
35340
|
-
};
|
|
35341
|
-
const isLargeTextLengthComparison = (node, scopes) => {
|
|
35342
|
-
const unwrappedNode = stripParenExpression(node);
|
|
35343
|
-
if (!isNodeOfType(unwrappedNode, "BinaryExpression")) return false;
|
|
35344
|
-
const leftIsLength = isPropsChildrenLength(unwrappedNode.left, scopes);
|
|
35345
|
-
const rightIsLength = isPropsChildrenLength(unwrappedNode.right, scopes);
|
|
35346
|
-
const thresholdNode = leftIsLength ? unwrappedNode.right : unwrappedNode.left;
|
|
35347
|
-
if (!leftIsLength && !rightIsLength || !isNodeOfType(thresholdNode, "Literal")) return false;
|
|
35348
|
-
if (typeof thresholdNode.value !== "number" || thresholdNode.value < 1e3) return false;
|
|
35349
|
-
return leftIsLength ? unwrappedNode.operator === ">" || unwrappedNode.operator === ">=" : unwrappedNode.operator === "<" || unwrappedNode.operator === "<=";
|
|
35350
|
-
};
|
|
35351
|
-
const isLargeStringOptimizationGuard = (comparison, scopes) => {
|
|
35352
|
-
let current = findTransparentExpressionRoot(comparison);
|
|
35353
|
-
while (current.parent) {
|
|
35354
|
-
const parent = current.parent;
|
|
35355
|
-
if (!isNodeOfType(parent, "LogicalExpression") || parent.operator !== "&&") return false;
|
|
35356
|
-
if (isLargeTextLengthComparison(parent.left === current ? parent.right : parent.left, scopes)) return true;
|
|
35357
|
-
current = findTransparentExpressionRoot(parent);
|
|
35358
|
-
}
|
|
35359
|
-
return false;
|
|
35360
|
-
};
|
|
35361
34935
|
const noPolymorphicChildren = defineRule({
|
|
35362
34936
|
id: "no-polymorphic-children",
|
|
35363
34937
|
title: "Children type checked at runtime",
|
|
@@ -35371,7 +34945,6 @@ const noPolymorphicChildren = defineRule({
|
|
|
35371
34945
|
const isStringLiteral = (operand) => isNodeOfType(operand, "Literal") && operand.value === "string";
|
|
35372
34946
|
if (!isStringLiteral(node.left) && !isStringLiteral(node.right)) return;
|
|
35373
34947
|
if (!guardsRenderShape(node)) return;
|
|
35374
|
-
if (isLargeStringOptimizationGuard(node, context.scopes)) return;
|
|
35375
34948
|
context.report({
|
|
35376
34949
|
node,
|
|
35377
34950
|
message: "Your users hit inconsistent behavior because `typeof children === \"string\"` makes this component switch on what callers pass, so add clear subcomponents like `<Button.Text>` instead."
|
|
@@ -35589,27 +35162,6 @@ const hasPreviousValueDep = (effectNode, depElements) => {
|
|
|
35589
35162
|
}
|
|
35590
35163
|
return false;
|
|
35591
35164
|
};
|
|
35592
|
-
const isStateLikeDependency = (analysis, element, isPropName) => {
|
|
35593
|
-
if (!isNodeOfType(element, "Identifier") || isPropName(element.name)) return false;
|
|
35594
|
-
if (!analysis) return true;
|
|
35595
|
-
const reference = getRef(analysis, element);
|
|
35596
|
-
if (!reference) return true;
|
|
35597
|
-
const upstreamReferences = getUpstreamRefs(analysis, reference);
|
|
35598
|
-
if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
|
|
35599
|
-
return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
|
|
35600
|
-
};
|
|
35601
|
-
const getRefHeldPropCallbackName = (callExpression, isPropName) => {
|
|
35602
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
35603
|
-
if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "current") return null;
|
|
35604
|
-
const receiver = stripParenExpression(callee.object);
|
|
35605
|
-
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
35606
|
-
const binding = findVariableInitializer(callExpression, receiver.name);
|
|
35607
|
-
if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
|
|
35608
|
-
if (getCalleeName$2(binding.initializer) !== "useRef") return null;
|
|
35609
|
-
const callbackArgument = binding.initializer.arguments?.[0];
|
|
35610
|
-
if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
|
|
35611
|
-
return isPropName(callbackArgument.name) ? callbackArgument.name : null;
|
|
35612
|
-
};
|
|
35613
35165
|
const noPropCallbackInEffect = defineRule({
|
|
35614
35166
|
id: "no-prop-callback-in-effect",
|
|
35615
35167
|
title: "Parent kept in sync with a callback effect",
|
|
@@ -35626,11 +35178,11 @@ const noPropCallbackInEffect = defineRule({
|
|
|
35626
35178
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
35627
35179
|
const depsNode = node.arguments[1];
|
|
35628
35180
|
if (!isNodeOfType(depsNode, "ArrayExpression") || !depsNode.elements?.length) return;
|
|
35629
|
-
const
|
|
35630
|
-
const stateLikeDeps = (depsNode.elements ?? []).filter((element) => element && isStateLikeDependency(analysis, element, propStackTracker.isPropName));
|
|
35181
|
+
const stateLikeDeps = (depsNode.elements ?? []).filter((element) => isNodeOfType(element, "Identifier") && !propStackTracker.isPropName(element.name));
|
|
35631
35182
|
if (stateLikeDeps.length === 0) return;
|
|
35632
35183
|
if (isRefLatchGuardedEffect(callback.body)) return;
|
|
35633
35184
|
if (hasPreviousValueDep(node, depsNode.elements ?? [])) return;
|
|
35185
|
+
const analysis = getProgramAnalysis(node);
|
|
35634
35186
|
if (analysis) {
|
|
35635
35187
|
const stateLikeDepRefs = [];
|
|
35636
35188
|
for (const element of stateLikeDeps) {
|
|
@@ -35642,9 +35194,9 @@ const noPropCallbackInEffect = defineRule({
|
|
|
35642
35194
|
const reportedNodes = /* @__PURE__ */ new Set();
|
|
35643
35195
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
35644
35196
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
35645
|
-
|
|
35646
|
-
const calleeName =
|
|
35647
|
-
if (!calleeName) return;
|
|
35197
|
+
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
35198
|
+
const calleeName = child.callee.name;
|
|
35199
|
+
if (!propStackTracker.isPropName(calleeName)) return;
|
|
35648
35200
|
if (!isResultDiscardedCall(child)) return;
|
|
35649
35201
|
if (reportedNodes.has(child)) return;
|
|
35650
35202
|
reportedNodes.add(child);
|
|
@@ -39818,18 +39370,34 @@ const resolveSettings$8 = (settings) => {
|
|
|
39818
39370
|
};
|
|
39819
39371
|
const expressionContainsJsxOrCreateElement = (root) => {
|
|
39820
39372
|
let found = false;
|
|
39821
|
-
|
|
39822
|
-
if (found) return
|
|
39823
|
-
if (node !== root && NESTED_FUNCTION_TYPES.has(node.type)) return false;
|
|
39373
|
+
const visit = (node) => {
|
|
39374
|
+
if (found) return;
|
|
39824
39375
|
if (node.type === "JSXElement" || node.type === "JSXFragment") {
|
|
39825
39376
|
found = true;
|
|
39826
|
-
return
|
|
39377
|
+
return;
|
|
39827
39378
|
}
|
|
39828
39379
|
if (isNodeOfType(node, "CallExpression") && isCreateElementCall(node)) {
|
|
39829
39380
|
found = true;
|
|
39830
|
-
return
|
|
39381
|
+
return;
|
|
39831
39382
|
}
|
|
39832
|
-
|
|
39383
|
+
const record = node;
|
|
39384
|
+
for (const key of Object.keys(record)) {
|
|
39385
|
+
if (key === "parent") continue;
|
|
39386
|
+
const child = record[key];
|
|
39387
|
+
if (Array.isArray(child)) for (const item of child) {
|
|
39388
|
+
if (!isAstNode(item)) continue;
|
|
39389
|
+
if (NESTED_FUNCTION_TYPES.has(item.type)) continue;
|
|
39390
|
+
visit(item);
|
|
39391
|
+
if (found) return;
|
|
39392
|
+
}
|
|
39393
|
+
else if (isAstNode(child)) {
|
|
39394
|
+
if (NESTED_FUNCTION_TYPES.has(child.type)) continue;
|
|
39395
|
+
visit(child);
|
|
39396
|
+
}
|
|
39397
|
+
if (found) return;
|
|
39398
|
+
}
|
|
39399
|
+
};
|
|
39400
|
+
visit(root);
|
|
39833
39401
|
return found;
|
|
39834
39402
|
};
|
|
39835
39403
|
const isReactClassComponent = (classNode) => {
|
|
@@ -40755,6 +40323,19 @@ const classifyExport = (name, reportNode, isFunction, initializer, state) => {
|
|
|
40755
40323
|
reportNode
|
|
40756
40324
|
};
|
|
40757
40325
|
};
|
|
40326
|
+
const collectRelevantNodes = (programRoot) => {
|
|
40327
|
+
const exportNodes = [];
|
|
40328
|
+
const componentCandidates = [];
|
|
40329
|
+
walkAst(programRoot, (child) => {
|
|
40330
|
+
const childType = child.type;
|
|
40331
|
+
if (childType === "ExportAllDeclaration" || childType === "ExportDefaultDeclaration" || childType === "ExportNamedDeclaration") exportNodes.push(child);
|
|
40332
|
+
else if (childType === "FunctionDeclaration" || childType === "VariableDeclarator" || childType === "ClassDeclaration") componentCandidates.push(child);
|
|
40333
|
+
});
|
|
40334
|
+
return {
|
|
40335
|
+
exportNodes,
|
|
40336
|
+
componentCandidates
|
|
40337
|
+
};
|
|
40338
|
+
};
|
|
40758
40339
|
const isEntryPointFile = (filename) => {
|
|
40759
40340
|
const lastSlash = Math.max(filename.lastIndexOf("/"), filename.lastIndexOf("\\"));
|
|
40760
40341
|
const basename = lastSlash === -1 ? filename : filename.slice(lastSlash + 1);
|
|
@@ -40793,212 +40374,197 @@ const onlyExportComponents = defineRule({
|
|
|
40793
40374
|
category: "Architecture",
|
|
40794
40375
|
create: (context) => {
|
|
40795
40376
|
const settings = resolveSettings$6(context.settings);
|
|
40796
|
-
|
|
40797
|
-
|
|
40798
|
-
|
|
40799
|
-
|
|
40800
|
-
|
|
40801
|
-
|
|
40802
|
-
|
|
40803
|
-
|
|
40804
|
-
|
|
40805
|
-
|
|
40806
|
-
|
|
40807
|
-
|
|
40808
|
-
|
|
40809
|
-
|
|
40810
|
-
VariableDeclarator: pushComponentCandidate,
|
|
40811
|
-
ClassDeclaration: pushComponentCandidate,
|
|
40812
|
-
"Program:exit"() {
|
|
40813
|
-
const localComponentNames = /* @__PURE__ */ new Set();
|
|
40814
|
-
const state = {
|
|
40815
|
-
customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
|
|
40816
|
-
allowExportNames: new Set(settings.allowExportNames),
|
|
40817
|
-
allowConstantExport: settings.allowConstantExport,
|
|
40818
|
-
localComponentNames,
|
|
40819
|
-
scopes: context.scopes
|
|
40820
|
-
};
|
|
40821
|
-
for (const child of componentCandidates) {
|
|
40822
|
-
if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
|
|
40823
|
-
if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) && functionContainsReactRenderOutput(child, context.scopes)) localComponentNames.add(child.id.name);
|
|
40824
|
-
}
|
|
40825
|
-
if (isNodeOfType(child, "ClassDeclaration") && child.id) {
|
|
40826
|
-
if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
|
|
40827
|
-
}
|
|
40828
|
-
if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
|
|
40829
|
-
const initializer = child.init;
|
|
40830
|
-
if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) {
|
|
40831
|
-
const expression = initializer ? skipTsExpression(initializer) : null;
|
|
40832
|
-
if (!(expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"))) || functionContainsReactRenderOutput(expression, context.scopes)) localComponentNames.add(child.id.name);
|
|
40833
|
-
}
|
|
40834
|
-
}
|
|
40377
|
+
return { Program(node) {
|
|
40378
|
+
if (!isFileNameAllowed(normalizeFilename(context.filename ?? ""), settings.checkJS)) return;
|
|
40379
|
+
const { exportNodes, componentCandidates } = collectRelevantNodes(node);
|
|
40380
|
+
const localComponentNames = /* @__PURE__ */ new Set();
|
|
40381
|
+
const state = {
|
|
40382
|
+
customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
|
|
40383
|
+
allowExportNames: new Set(settings.allowExportNames),
|
|
40384
|
+
allowConstantExport: settings.allowConstantExport,
|
|
40385
|
+
localComponentNames,
|
|
40386
|
+
scopes: context.scopes
|
|
40387
|
+
};
|
|
40388
|
+
for (const child of componentCandidates) {
|
|
40389
|
+
if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
|
|
40390
|
+
if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) && functionContainsReactRenderOutput(child, context.scopes)) localComponentNames.add(child.id.name);
|
|
40835
40391
|
}
|
|
40836
|
-
|
|
40837
|
-
|
|
40838
|
-
|
|
40839
|
-
|
|
40840
|
-
|
|
40841
|
-
if (
|
|
40842
|
-
|
|
40843
|
-
|
|
40844
|
-
context.report({
|
|
40845
|
-
node: child,
|
|
40846
|
-
message: EXPORT_ALL_MESSAGE
|
|
40847
|
-
});
|
|
40848
|
-
continue;
|
|
40392
|
+
if (isNodeOfType(child, "ClassDeclaration") && child.id) {
|
|
40393
|
+
if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
|
|
40394
|
+
}
|
|
40395
|
+
if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
|
|
40396
|
+
const initializer = child.init;
|
|
40397
|
+
if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) {
|
|
40398
|
+
const expression = initializer ? skipTsExpression(initializer) : null;
|
|
40399
|
+
if (!(expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"))) || functionContainsReactRenderOutput(expression, context.scopes)) localComponentNames.add(child.id.name);
|
|
40849
40400
|
}
|
|
40850
|
-
|
|
40851
|
-
|
|
40852
|
-
|
|
40853
|
-
|
|
40854
|
-
|
|
40855
|
-
|
|
40856
|
-
|
|
40857
|
-
|
|
40858
|
-
|
|
40859
|
-
|
|
40860
|
-
|
|
40861
|
-
|
|
40862
|
-
|
|
40863
|
-
|
|
40864
|
-
|
|
40865
|
-
|
|
40866
|
-
|
|
40867
|
-
|
|
40868
|
-
|
|
40869
|
-
|
|
40870
|
-
|
|
40871
|
-
|
|
40872
|
-
|
|
40873
|
-
|
|
40874
|
-
|
|
40875
|
-
|
|
40876
|
-
|
|
40877
|
-
} else context.report({
|
|
40401
|
+
}
|
|
40402
|
+
}
|
|
40403
|
+
const exports = [];
|
|
40404
|
+
let hasReactExport = false;
|
|
40405
|
+
let hasAnyExports = false;
|
|
40406
|
+
const isExportedNodeIds = /* @__PURE__ */ new WeakSet();
|
|
40407
|
+
for (const child of exportNodes) {
|
|
40408
|
+
if (isNodeOfType(child, "ExportAllDeclaration")) {
|
|
40409
|
+
if (child.exportKind === "type") continue;
|
|
40410
|
+
hasAnyExports = true;
|
|
40411
|
+
context.report({
|
|
40412
|
+
node: child,
|
|
40413
|
+
message: EXPORT_ALL_MESSAGE
|
|
40414
|
+
});
|
|
40415
|
+
continue;
|
|
40416
|
+
}
|
|
40417
|
+
if (isNodeOfType(child, "ExportDefaultDeclaration")) {
|
|
40418
|
+
hasAnyExports = true;
|
|
40419
|
+
const declaration = child.declaration;
|
|
40420
|
+
const stripped = skipTsExpression(declaration);
|
|
40421
|
+
if (isNodeOfType(stripped, "FunctionDeclaration") || isNodeOfType(stripped, "FunctionExpression")) {
|
|
40422
|
+
if (stripped.id) {
|
|
40423
|
+
const idNode = stripped.id;
|
|
40424
|
+
isExportedNodeIds.add(stripped);
|
|
40425
|
+
exports.push(classifyExport(idNode.name, idNode, true, null, state));
|
|
40426
|
+
} else {
|
|
40427
|
+
context.report({
|
|
40878
40428
|
node: stripped,
|
|
40879
40429
|
message: ANONYMOUS_MESSAGE
|
|
40880
40430
|
});
|
|
40881
|
-
|
|
40882
|
-
}
|
|
40883
|
-
if (isNodeOfType(stripped, "Identifier")) {
|
|
40884
|
-
exports.push(classifyExport(stripped.name, stripped, false, null, state));
|
|
40885
|
-
continue;
|
|
40431
|
+
hasReactExport = true;
|
|
40886
40432
|
}
|
|
40887
|
-
|
|
40888
|
-
|
|
40889
|
-
|
|
40890
|
-
|
|
40891
|
-
|
|
40892
|
-
|
|
40893
|
-
|
|
40894
|
-
|
|
40895
|
-
if (!firstArg) return false;
|
|
40896
|
-
const expression = skipTsExpression(firstArg);
|
|
40897
|
-
if (isNodeOfType(expression, "Identifier")) return true;
|
|
40898
|
-
if (isNodeOfType(expression, "FunctionExpression") && expression.id) return true;
|
|
40899
|
-
if (isNodeOfType(expression, "CallExpression") && isHocCallee(expression.callee, state)) return true;
|
|
40900
|
-
return false;
|
|
40901
|
-
})();
|
|
40902
|
-
if (isHoc && firstArgIsValid) hasReactExport = true;
|
|
40903
|
-
else if (!isHoc && isConfigOnlyFactoryCall(stripped)) exports.push({
|
|
40433
|
+
continue;
|
|
40434
|
+
}
|
|
40435
|
+
if (isNodeOfType(stripped, "ClassDeclaration") || isNodeOfType(stripped, "ClassExpression")) {
|
|
40436
|
+
if (stripped.id) {
|
|
40437
|
+
const idNode = stripped.id;
|
|
40438
|
+
isExportedNodeIds.add(stripped);
|
|
40439
|
+
if (isReactComponentName(idNode.name) && isEs6Component(stripped)) hasReactExport = true;
|
|
40440
|
+
else exports.push({
|
|
40904
40441
|
kind: "non-component",
|
|
40905
|
-
reportNode:
|
|
40906
|
-
});
|
|
40907
|
-
else context.report({
|
|
40908
|
-
node: stripped,
|
|
40909
|
-
message: ANONYMOUS_MESSAGE
|
|
40910
|
-
});
|
|
40911
|
-
continue;
|
|
40912
|
-
}
|
|
40913
|
-
if (isNodeOfType(stripped, "ObjectExpression")) {
|
|
40914
|
-
context.report({
|
|
40915
|
-
node: stripped,
|
|
40916
|
-
message: objectExpressionBundlesComponents(stripped, state) ? NAMESPACE_OBJECT_MESSAGE : ANONYMOUS_MESSAGE
|
|
40917
|
-
});
|
|
40918
|
-
continue;
|
|
40919
|
-
}
|
|
40920
|
-
if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "Literal")) {
|
|
40921
|
-
context.report({
|
|
40922
|
-
node: stripped,
|
|
40923
|
-
message: ANONYMOUS_MESSAGE
|
|
40442
|
+
reportNode: idNode
|
|
40924
40443
|
});
|
|
40444
|
+
} else context.report({
|
|
40445
|
+
node: stripped,
|
|
40446
|
+
message: ANONYMOUS_MESSAGE
|
|
40447
|
+
});
|
|
40448
|
+
continue;
|
|
40449
|
+
}
|
|
40450
|
+
if (isNodeOfType(stripped, "Identifier")) {
|
|
40451
|
+
exports.push(classifyExport(stripped.name, stripped, false, null, state));
|
|
40452
|
+
continue;
|
|
40453
|
+
}
|
|
40454
|
+
if (isNodeOfType(stripped, "CallExpression")) {
|
|
40455
|
+
if (isRouteFactoryCall(stripped)) {
|
|
40456
|
+
hasReactExport = true;
|
|
40925
40457
|
continue;
|
|
40926
40458
|
}
|
|
40459
|
+
const isHoc = isHocCallee(stripped.callee, state);
|
|
40460
|
+
const firstArg = stripped.arguments[0];
|
|
40461
|
+
const firstArgIsValid = Boolean(firstArg) && (() => {
|
|
40462
|
+
if (!firstArg) return false;
|
|
40463
|
+
const expression = skipTsExpression(firstArg);
|
|
40464
|
+
if (isNodeOfType(expression, "Identifier")) return true;
|
|
40465
|
+
if (isNodeOfType(expression, "FunctionExpression") && expression.id) return true;
|
|
40466
|
+
if (isNodeOfType(expression, "CallExpression") && isHocCallee(expression.callee, state)) return true;
|
|
40467
|
+
return false;
|
|
40468
|
+
})();
|
|
40469
|
+
if (isHoc && firstArgIsValid) hasReactExport = true;
|
|
40470
|
+
else if (!isHoc && isConfigOnlyFactoryCall(stripped)) exports.push({
|
|
40471
|
+
kind: "non-component",
|
|
40472
|
+
reportNode: stripped
|
|
40473
|
+
});
|
|
40474
|
+
else context.report({
|
|
40475
|
+
node: stripped,
|
|
40476
|
+
message: ANONYMOUS_MESSAGE
|
|
40477
|
+
});
|
|
40478
|
+
continue;
|
|
40479
|
+
}
|
|
40480
|
+
if (isNodeOfType(stripped, "ObjectExpression")) {
|
|
40481
|
+
context.report({
|
|
40482
|
+
node: stripped,
|
|
40483
|
+
message: objectExpressionBundlesComponents(stripped, state) ? NAMESPACE_OBJECT_MESSAGE : ANONYMOUS_MESSAGE
|
|
40484
|
+
});
|
|
40485
|
+
continue;
|
|
40486
|
+
}
|
|
40487
|
+
if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "Literal")) {
|
|
40927
40488
|
context.report({
|
|
40928
|
-
node:
|
|
40489
|
+
node: stripped,
|
|
40929
40490
|
message: ANONYMOUS_MESSAGE
|
|
40930
40491
|
});
|
|
40931
40492
|
continue;
|
|
40932
40493
|
}
|
|
40933
|
-
|
|
40934
|
-
|
|
40935
|
-
|
|
40936
|
-
|
|
40937
|
-
|
|
40938
|
-
|
|
40939
|
-
|
|
40940
|
-
|
|
40941
|
-
|
|
40942
|
-
|
|
40943
|
-
|
|
40944
|
-
|
|
40945
|
-
|
|
40946
|
-
|
|
40947
|
-
|
|
40948
|
-
|
|
40949
|
-
|
|
40950
|
-
|
|
40951
|
-
|
|
40952
|
-
|
|
40953
|
-
}
|
|
40954
|
-
|
|
40955
|
-
|
|
40956
|
-
|
|
40957
|
-
|
|
40958
|
-
|
|
40959
|
-
}
|
|
40494
|
+
context.report({
|
|
40495
|
+
node: child,
|
|
40496
|
+
message: ANONYMOUS_MESSAGE
|
|
40497
|
+
});
|
|
40498
|
+
continue;
|
|
40499
|
+
}
|
|
40500
|
+
if (isNodeOfType(child, "ExportNamedDeclaration")) {
|
|
40501
|
+
if (child.exportKind === "type") continue;
|
|
40502
|
+
hasAnyExports = true;
|
|
40503
|
+
if (child.declaration) {
|
|
40504
|
+
const declaration = child.declaration;
|
|
40505
|
+
if (isNodeOfType(declaration, "FunctionDeclaration") && declaration.id) {
|
|
40506
|
+
isExportedNodeIds.add(declaration);
|
|
40507
|
+
exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state));
|
|
40508
|
+
} else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
|
|
40509
|
+
isExportedNodeIds.add(declaration);
|
|
40510
|
+
if (isReactComponentName(declaration.id.name) && isEs6Component(declaration)) exports.push({ kind: "react-component" });
|
|
40511
|
+
else exports.push({
|
|
40512
|
+
kind: "non-component",
|
|
40513
|
+
reportNode: declaration.id
|
|
40514
|
+
});
|
|
40515
|
+
} else if (isNodeOfType(declaration, "VariableDeclaration")) for (const declarator of declaration.declarations) {
|
|
40516
|
+
if (!isNodeOfType(declarator.id, "Identifier")) continue;
|
|
40517
|
+
isExportedNodeIds.add(declarator);
|
|
40518
|
+
const isFunction = canBeReactFunctionComponent(declarator.init ?? null, state);
|
|
40519
|
+
exports.push(classifyExport(declarator.id.name, declarator.id, isFunction, declarator.init, state));
|
|
40960
40520
|
}
|
|
40961
|
-
|
|
40962
|
-
|
|
40963
|
-
|
|
40964
|
-
|
|
40965
|
-
|
|
40966
|
-
|
|
40967
|
-
|
|
40968
|
-
|
|
40969
|
-
|
|
40970
|
-
|
|
40971
|
-
|
|
40972
|
-
|
|
40973
|
-
|
|
40974
|
-
|
|
40975
|
-
|
|
40976
|
-
|
|
40977
|
-
|
|
40978
|
-
|
|
40979
|
-
|
|
40980
|
-
|
|
40981
|
-
|
|
40521
|
+
else if (declaration.type === "TSEnumDeclaration" || declaration.type === "TSInterfaceDeclaration" || declaration.type === "TSTypeAliasDeclaration") {
|
|
40522
|
+
if (declaration.type === "TSEnumDeclaration") exports.push({
|
|
40523
|
+
kind: "non-component",
|
|
40524
|
+
reportNode: declaration
|
|
40525
|
+
});
|
|
40526
|
+
}
|
|
40527
|
+
}
|
|
40528
|
+
const isReExportFromSource = Boolean(child.source);
|
|
40529
|
+
for (const specifier of child.specifiers ?? []) {
|
|
40530
|
+
if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
|
|
40531
|
+
const exported = specifier.exported;
|
|
40532
|
+
const local = specifier.local;
|
|
40533
|
+
let exportedName = null;
|
|
40534
|
+
if (exported && isNodeOfType(exported, "Identifier")) exportedName = exported.name;
|
|
40535
|
+
const localName = local && isNodeOfType(local, "Identifier") ? local.name : null;
|
|
40536
|
+
const reportNode = specifier;
|
|
40537
|
+
let entry;
|
|
40538
|
+
if (exportedName === "default" && localName) entry = classifyExport(localName, reportNode, false, null, state);
|
|
40539
|
+
else if (exportedName) entry = classifyExport(exportedName, reportNode, false, null, state);
|
|
40540
|
+
else {
|
|
40541
|
+
entry = {
|
|
40542
|
+
kind: "non-component",
|
|
40543
|
+
reportNode
|
|
40544
|
+
};
|
|
40545
|
+
if (localName && isReactComponentName(localName)) exports.push({ kind: "react-component" });
|
|
40982
40546
|
}
|
|
40547
|
+
if (isReExportFromSource && entry.kind !== "react-component") continue;
|
|
40548
|
+
exports.push(entry);
|
|
40983
40549
|
}
|
|
40984
40550
|
}
|
|
40985
|
-
|
|
40986
|
-
|
|
40551
|
+
}
|
|
40552
|
+
for (const entry of exports) if (entry.kind === "react-component") hasReactExport = true;
|
|
40553
|
+
for (const entry of exports) if (entry.kind === "namespace-object") context.report({
|
|
40554
|
+
node: entry.reportNode,
|
|
40555
|
+
message: NAMESPACE_OBJECT_MESSAGE
|
|
40556
|
+
});
|
|
40557
|
+
if (hasAnyExports && hasReactExport) for (const entry of exports) {
|
|
40558
|
+
if (entry.kind === "non-component") context.report({
|
|
40987
40559
|
node: entry.reportNode,
|
|
40988
|
-
message:
|
|
40560
|
+
message: NAMED_EXPORT_MESSAGE
|
|
40561
|
+
});
|
|
40562
|
+
if (entry.kind === "react-context") context.report({
|
|
40563
|
+
node: entry.reportNode,
|
|
40564
|
+
message: REACT_CONTEXT_MESSAGE
|
|
40989
40565
|
});
|
|
40990
|
-
if (hasAnyExports && hasReactExport) for (const entry of exports) {
|
|
40991
|
-
if (entry.kind === "non-component") context.report({
|
|
40992
|
-
node: entry.reportNode,
|
|
40993
|
-
message: NAMED_EXPORT_MESSAGE
|
|
40994
|
-
});
|
|
40995
|
-
if (entry.kind === "react-context") context.report({
|
|
40996
|
-
node: entry.reportNode,
|
|
40997
|
-
message: REACT_CONTEXT_MESSAGE
|
|
40998
|
-
});
|
|
40999
|
-
}
|
|
41000
40566
|
}
|
|
41001
|
-
};
|
|
40567
|
+
} };
|
|
41002
40568
|
}
|
|
41003
40569
|
});
|
|
41004
40570
|
//#endregion
|
|
@@ -41100,12 +40666,7 @@ const postmessageOriginRisk = defineRule({
|
|
|
41100
40666
|
scan: (file) => {
|
|
41101
40667
|
if (!isProductionSourcePath(file.relativePath)) return [];
|
|
41102
40668
|
if (WORKER_FILE_PATH_PATTERN.test(file.relativePath)) return [];
|
|
41103
|
-
|
|
41104
|
-
const ast = parseSourceText({
|
|
41105
|
-
filename: file.absolutePath,
|
|
41106
|
-
sourceText: file.content,
|
|
41107
|
-
shouldAttachParentReferences: false
|
|
41108
|
-
});
|
|
40669
|
+
const ast = parseSourceText(file.absolutePath, file.content);
|
|
41109
40670
|
if (ast === null) return [];
|
|
41110
40671
|
const findings = [];
|
|
41111
40672
|
walkAst(ast, (node) => {
|
|
@@ -43485,6 +43046,15 @@ const isStableHookWrapperArgument = (node) => {
|
|
|
43485
43046
|
const parent = node.parent;
|
|
43486
43047
|
return isNodeOfType(parent, "CallExpression") && isNodeOfType(parent.callee, "Identifier") && STABLE_HOOK_WRAPPERS.has(parent.callee.name) && Boolean(parent.arguments?.some((argument) => argument === node));
|
|
43487
43048
|
};
|
|
43049
|
+
const isImmediatelyInvokedFunction = (functionNode) => {
|
|
43050
|
+
let wrappedCallee = functionNode;
|
|
43051
|
+
let enclosing = functionNode.parent;
|
|
43052
|
+
while (enclosing && stripParenExpression(enclosing) === functionNode) {
|
|
43053
|
+
wrappedCallee = enclosing;
|
|
43054
|
+
enclosing = enclosing.parent ?? null;
|
|
43055
|
+
}
|
|
43056
|
+
return Boolean(enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee);
|
|
43057
|
+
};
|
|
43488
43058
|
const queryStableQueryClient = defineRule({
|
|
43489
43059
|
id: "query-stable-query-client",
|
|
43490
43060
|
title: "Unstable QueryClient in component",
|
|
@@ -54797,6 +54367,12 @@ const webhookSignatureRisk = defineRule({
|
|
|
54797
54367
|
//#region src/plugin/rules/zod/utils/zod-ast.ts
|
|
54798
54368
|
const ZOD_MODULE = "zod";
|
|
54799
54369
|
const ZOD_MODULE_SOURCES = [ZOD_MODULE];
|
|
54370
|
+
const getStaticPropertyName = (member) => {
|
|
54371
|
+
const property = member.property;
|
|
54372
|
+
if (!member.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
54373
|
+
if (member.computed && isNodeOfType(property, "Literal") && typeof property.value === "string") return property.value;
|
|
54374
|
+
return null;
|
|
54375
|
+
};
|
|
54800
54376
|
const importInfoCache = /* @__PURE__ */ new WeakMap();
|
|
54801
54377
|
const getImportInfoForIdentifier = (identifier) => {
|
|
54802
54378
|
if (importInfoCache.has(identifier)) return importInfoCache.get(identifier) ?? null;
|
|
@@ -59977,10 +59553,7 @@ const appendNode = (builder, block, node) => {
|
|
|
59977
59553
|
};
|
|
59978
59554
|
const mapDescendantsToBlock = (builder, node, block) => {
|
|
59979
59555
|
builder.nodeBlock.set(node, block);
|
|
59980
|
-
if (isFunctionLike$1(node))
|
|
59981
|
-
builder.nestedFunctions.push(node);
|
|
59982
|
-
return;
|
|
59983
|
-
}
|
|
59556
|
+
if (isFunctionLike$1(node)) return;
|
|
59984
59557
|
const record = node;
|
|
59985
59558
|
for (const key of Object.keys(record)) {
|
|
59986
59559
|
if (key === "parent") continue;
|
|
@@ -60214,12 +59787,11 @@ const buildStatement = (builder, statement, current) => {
|
|
|
60214
59787
|
mapDescendantsToBlock(builder, statement, current);
|
|
60215
59788
|
return current;
|
|
60216
59789
|
};
|
|
60217
|
-
const buildFunctionCfg = (functionNode, body
|
|
59790
|
+
const buildFunctionCfg = (functionNode, body) => {
|
|
60218
59791
|
const builder = {
|
|
60219
59792
|
blocks: [],
|
|
60220
59793
|
entry: null,
|
|
60221
59794
|
exit: null,
|
|
60222
|
-
nestedFunctions: nestedFunctionSink,
|
|
60223
59795
|
nodeBlock: /* @__PURE__ */ new Map(),
|
|
60224
59796
|
loopStack: [],
|
|
60225
59797
|
switchStack: [],
|
|
@@ -60237,12 +59809,13 @@ const buildFunctionCfg = (functionNode, body, nestedFunctionSink) => {
|
|
|
60237
59809
|
bodyEnd = entry;
|
|
60238
59810
|
}
|
|
60239
59811
|
addEdge(bodyEnd, exit, "uncond");
|
|
59812
|
+
const blockOf = (node) => builder.nodeBlock.get(node) ?? null;
|
|
60240
59813
|
return {
|
|
60241
59814
|
owner: functionNode,
|
|
60242
59815
|
entry,
|
|
60243
59816
|
exit,
|
|
60244
59817
|
blocks: builder.blocks,
|
|
60245
|
-
blockOf
|
|
59818
|
+
blockOf
|
|
60246
59819
|
};
|
|
60247
59820
|
};
|
|
60248
59821
|
const computeUnconditionalSet = (cfg) => {
|
|
@@ -60279,9 +59852,8 @@ const computeUnconditionalSet = (cfg) => {
|
|
|
60279
59852
|
const analyzeControlFlow = (program) => {
|
|
60280
59853
|
nextBlockId = 0;
|
|
60281
59854
|
const functionCfgs = /* @__PURE__ */ new Map();
|
|
60282
|
-
const pendingFunctions = [];
|
|
60283
59855
|
const buildFor = (functionNode, body) => {
|
|
60284
|
-
const cfg = buildFunctionCfg(functionNode, body
|
|
59856
|
+
const cfg = buildFunctionCfg(functionNode, body);
|
|
60285
59857
|
functionCfgs.set(functionNode, {
|
|
60286
59858
|
cfg,
|
|
60287
59859
|
unconditionalSet: computeUnconditionalSet(cfg)
|
|
@@ -60291,18 +59863,21 @@ const analyzeControlFlow = (program) => {
|
|
|
60291
59863
|
type: "BlockStatement",
|
|
60292
59864
|
body: program.body
|
|
60293
59865
|
});
|
|
60294
|
-
|
|
60295
|
-
|
|
60296
|
-
|
|
60297
|
-
|
|
60298
|
-
|
|
60299
|
-
|
|
60300
|
-
const
|
|
60301
|
-
|
|
60302
|
-
|
|
60303
|
-
|
|
60304
|
-
|
|
59866
|
+
const visit = (node) => {
|
|
59867
|
+
if (isFunctionLike$1(node)) {
|
|
59868
|
+
const body = node.body;
|
|
59869
|
+
if (body) buildFor(node, body);
|
|
59870
|
+
}
|
|
59871
|
+
const record = node;
|
|
59872
|
+
for (const key of Object.keys(record)) {
|
|
59873
|
+
if (key === "parent") continue;
|
|
59874
|
+
const child = record[key];
|
|
59875
|
+
if (Array.isArray(child)) {
|
|
59876
|
+
for (const item of child) if (isAstNode(item)) visit(item);
|
|
59877
|
+
} else if (isAstNode(child)) visit(child);
|
|
59878
|
+
}
|
|
60305
59879
|
};
|
|
59880
|
+
visit(program);
|
|
60306
59881
|
const enclosingFunction = (node) => {
|
|
60307
59882
|
let current = node;
|
|
60308
59883
|
while (current) {
|
|
@@ -60313,12 +59888,12 @@ const analyzeControlFlow = (program) => {
|
|
|
60313
59888
|
return null;
|
|
60314
59889
|
};
|
|
60315
59890
|
const cfgFor = (functionLike) => {
|
|
60316
|
-
return
|
|
59891
|
+
return functionCfgs.get(functionLike)?.cfg ?? null;
|
|
60317
59892
|
};
|
|
60318
59893
|
const isUnconditionalFromEntry = (node) => {
|
|
60319
59894
|
const owner = enclosingFunction(node);
|
|
60320
59895
|
if (!owner) return true;
|
|
60321
|
-
const entry =
|
|
59896
|
+
const entry = functionCfgs.get(owner);
|
|
60322
59897
|
if (!entry) return true;
|
|
60323
59898
|
const block = entry.cfg.blockOf(node);
|
|
60324
59899
|
if (!block) return true;
|
|
@@ -60392,14 +59967,17 @@ const wrapWithSemanticContext = (rule) => ({
|
|
|
60392
59967
|
}
|
|
60393
59968
|
};
|
|
60394
59969
|
const visitors = rule.create(enrichedContext);
|
|
60395
|
-
const
|
|
60396
|
-
|
|
60397
|
-
|
|
60398
|
-
|
|
60399
|
-
|
|
60400
|
-
|
|
60401
|
-
|
|
60402
|
-
|
|
59970
|
+
const passthroughVisitors = {};
|
|
59971
|
+
for (const [nodeType, handler] of Object.entries(visitors)) {
|
|
59972
|
+
if (typeof handler !== "function") continue;
|
|
59973
|
+
passthroughVisitors[nodeType] = handler;
|
|
59974
|
+
}
|
|
59975
|
+
const innerProgramHandler = passthroughVisitors.Program;
|
|
59976
|
+
passthroughVisitors.Program = ((node) => {
|
|
59977
|
+
programRoot = node;
|
|
59978
|
+
if (innerProgramHandler) innerProgramHandler(node);
|
|
59979
|
+
});
|
|
59980
|
+
return passthroughVisitors;
|
|
60403
59981
|
}
|
|
60404
59982
|
});
|
|
60405
59983
|
//#endregion
|