oxlint-plugin-react-doctor 0.7.5-dev.d9676e2 → 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 +500 -793
- 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;
|
|
@@ -6871,10 +6829,11 @@ const getTemplateInterpolations = (valueTail) => {
|
|
|
6871
6829
|
const interpolations = valueTail.slice(1, closingBacktickIndex).match(/\$\{[^}]*\}/g);
|
|
6872
6830
|
return interpolations === null ? "" : interpolations.join(" ");
|
|
6873
6831
|
};
|
|
6874
|
-
const getIdentifierDeclarationInitializer = (identifier,
|
|
6875
|
-
const
|
|
6876
|
-
if (
|
|
6877
|
-
|
|
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);
|
|
6878
6837
|
};
|
|
6879
6838
|
const isPureStringLiteralConcat = (initializerText) => {
|
|
6880
6839
|
if (!/^["']/.test(initializerText)) return false;
|
|
@@ -6944,153 +6903,6 @@ const isAllOperandsDomContentConcat = (valueExpression) => {
|
|
|
6944
6903
|
return !HTML_TAINT_PATTERN.test(withoutCast.replace(DOM_CONTENT_SOURCE_VALUE_PATTERN, ""));
|
|
6945
6904
|
});
|
|
6946
6905
|
};
|
|
6947
|
-
const findMatchingBraceIndex = (fileContent, openingBraceIndex) => {
|
|
6948
|
-
let depth = 0;
|
|
6949
|
-
let quote = null;
|
|
6950
|
-
for (let index = openingBraceIndex; index < fileContent.length; index += 1) {
|
|
6951
|
-
const character = fileContent[index];
|
|
6952
|
-
if (quote !== null) {
|
|
6953
|
-
if (character === quote && fileContent[index - 1] !== "\\") quote = null;
|
|
6954
|
-
continue;
|
|
6955
|
-
}
|
|
6956
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
6957
|
-
quote = character;
|
|
6958
|
-
continue;
|
|
6959
|
-
}
|
|
6960
|
-
if (character === "{") depth += 1;
|
|
6961
|
-
if (character === "}") {
|
|
6962
|
-
depth -= 1;
|
|
6963
|
-
if (depth === 0) return index;
|
|
6964
|
-
}
|
|
6965
|
-
}
|
|
6966
|
-
return fileContent.length;
|
|
6967
|
-
};
|
|
6968
|
-
const findContainingBlockEndIndex = (fileContent, targetIndex) => {
|
|
6969
|
-
const openingBraceIndexes = [];
|
|
6970
|
-
let quote = null;
|
|
6971
|
-
let isLineComment = false;
|
|
6972
|
-
let isBlockComment = false;
|
|
6973
|
-
for (let index = 0; index < targetIndex; index += 1) {
|
|
6974
|
-
const character = fileContent[index];
|
|
6975
|
-
const nextCharacter = fileContent[index + 1];
|
|
6976
|
-
if (isLineComment) {
|
|
6977
|
-
if (character === "\n") isLineComment = false;
|
|
6978
|
-
continue;
|
|
6979
|
-
}
|
|
6980
|
-
if (isBlockComment) {
|
|
6981
|
-
if (character === "*" && nextCharacter === "/") {
|
|
6982
|
-
isBlockComment = false;
|
|
6983
|
-
index += 1;
|
|
6984
|
-
}
|
|
6985
|
-
continue;
|
|
6986
|
-
}
|
|
6987
|
-
if (quote !== null) {
|
|
6988
|
-
if (character === quote && fileContent[index - 1] !== "\\") quote = null;
|
|
6989
|
-
continue;
|
|
6990
|
-
}
|
|
6991
|
-
if (character === "/" && nextCharacter === "/") {
|
|
6992
|
-
isLineComment = true;
|
|
6993
|
-
index += 1;
|
|
6994
|
-
continue;
|
|
6995
|
-
}
|
|
6996
|
-
if (character === "/" && nextCharacter === "*") {
|
|
6997
|
-
isBlockComment = true;
|
|
6998
|
-
index += 1;
|
|
6999
|
-
continue;
|
|
7000
|
-
}
|
|
7001
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
7002
|
-
quote = character;
|
|
7003
|
-
continue;
|
|
7004
|
-
}
|
|
7005
|
-
if (character === "{") openingBraceIndexes.push(index);
|
|
7006
|
-
if (character === "}") openingBraceIndexes.pop();
|
|
7007
|
-
}
|
|
7008
|
-
const openingBraceIndex = openingBraceIndexes.at(-1);
|
|
7009
|
-
return openingBraceIndex === void 0 ? fileContent.length : findMatchingBraceIndex(fileContent, openingBraceIndex);
|
|
7010
|
-
};
|
|
7011
|
-
const findVisibleIdentifierDeclaration = (identifier, sinkIndex, fileContent) => {
|
|
7012
|
-
const initializerPattern = new RegExp(`(?:const|let|var)\\s+${escapeRegExp(identifier)}\\s*(?::[^=\\n]*)?=\\s*([^;\\n]+)`, "g");
|
|
7013
|
-
let nearestDeclaration = null;
|
|
7014
|
-
let nearestDeclarationIndex = -1;
|
|
7015
|
-
for (const match of fileContent.matchAll(initializerPattern)) {
|
|
7016
|
-
const declarationIndex = match.index;
|
|
7017
|
-
if (declarationIndex === void 0 || declarationIndex >= sinkIndex || declarationIndex <= nearestDeclarationIndex || findContainingBlockEndIndex(fileContent, declarationIndex) < sinkIndex) continue;
|
|
7018
|
-
nearestDeclarationIndex = declarationIndex;
|
|
7019
|
-
const initializer = match[1];
|
|
7020
|
-
if (initializer === void 0) continue;
|
|
7021
|
-
nearestDeclaration = {
|
|
7022
|
-
initializer,
|
|
7023
|
-
initializerStartIndex: declarationIndex + match[0].length - initializer.length
|
|
7024
|
-
};
|
|
7025
|
-
}
|
|
7026
|
-
return nearestDeclaration;
|
|
7027
|
-
};
|
|
7028
|
-
const findContainingFunctionParameterSource = (identifier, sinkIndex, fileContent) => {
|
|
7029
|
-
const patterns = [/function\s+([\w$]+)\s*\(([^)]*)\)\s*\{/g, /(?:const|let|var)\s+([\w$]+)\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>\s*\{/g];
|
|
7030
|
-
let closestSource = null;
|
|
7031
|
-
let closestStartIndex = -1;
|
|
7032
|
-
for (const pattern of patterns) for (const match of fileContent.matchAll(pattern)) {
|
|
7033
|
-
const matchIndex = match.index;
|
|
7034
|
-
if (matchIndex === void 0 || matchIndex >= sinkIndex || matchIndex < closestStartIndex) continue;
|
|
7035
|
-
if (findMatchingBraceIndex(fileContent, matchIndex + match[0].lastIndexOf("{")) < sinkIndex) continue;
|
|
7036
|
-
const parameterIndex = (match[2] ?? "").split(",").findIndex((parameter) => parameter.trim().match(/^(?:\.\.\.)?([\w$]+)/)?.[1] === identifier);
|
|
7037
|
-
if (parameterIndex < 0) continue;
|
|
7038
|
-
closestStartIndex = matchIndex;
|
|
7039
|
-
closestSource = {
|
|
7040
|
-
functionName: match[1] ?? "",
|
|
7041
|
-
parameterIndex
|
|
7042
|
-
};
|
|
7043
|
-
}
|
|
7044
|
-
return closestSource;
|
|
7045
|
-
};
|
|
7046
|
-
const splitTopLevelArguments = (argumentText) => {
|
|
7047
|
-
const argumentsList = [];
|
|
7048
|
-
let startIndex = 0;
|
|
7049
|
-
let depth = 0;
|
|
7050
|
-
let quote = null;
|
|
7051
|
-
for (let index = 0; index < argumentText.length; index += 1) {
|
|
7052
|
-
const character = argumentText[index];
|
|
7053
|
-
if (quote !== null) {
|
|
7054
|
-
if (character === quote && argumentText[index - 1] !== "\\") quote = null;
|
|
7055
|
-
continue;
|
|
7056
|
-
}
|
|
7057
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
7058
|
-
quote = character;
|
|
7059
|
-
continue;
|
|
7060
|
-
}
|
|
7061
|
-
if (character === "(" || character === "[" || character === "{") depth += 1;
|
|
7062
|
-
if (character === ")" || character === "]" || character === "}") depth -= 1;
|
|
7063
|
-
if (character === "," && depth === 0) {
|
|
7064
|
-
argumentsList.push(argumentText.slice(startIndex, index).trim());
|
|
7065
|
-
startIndex = index + 1;
|
|
7066
|
-
}
|
|
7067
|
-
}
|
|
7068
|
-
argumentsList.push(argumentText.slice(startIndex).trim());
|
|
7069
|
-
return argumentsList;
|
|
7070
|
-
};
|
|
7071
|
-
const isHtmlTainted = (expression, fileContent, sinkIndex, visitedIdentifiers) => {
|
|
7072
|
-
const trimmedExpression = expression.trim();
|
|
7073
|
-
if (STRING_LITERAL_VALUE_PATTERN.test(`${trimmedExpression};`)) return false;
|
|
7074
|
-
if (MODULE_CONSTANT_VALUE_PATTERN.test(`${trimmedExpression};`)) return false;
|
|
7075
|
-
if (SANITIZER_PATTERN.test(trimmedExpression)) return false;
|
|
7076
|
-
if (ENV_CONFIG_VALUE_PATTERN.test(trimmedExpression)) return false;
|
|
7077
|
-
if (I18N_VALUE_PATTERN.test(trimmedExpression)) return false;
|
|
7078
|
-
if (ESCAPING_SERIALIZER_CALL_PATTERN.test(trimmedExpression)) return false;
|
|
7079
|
-
if (HTML_TAINT_PATTERN.test(trimmedExpression)) return true;
|
|
7080
|
-
const identifier = trimmedExpression.match(/^([\w$]+)\s*(?:[;,})\n]|$)/)?.[1];
|
|
7081
|
-
if (identifier === void 0 || visitedIdentifiers.has(identifier)) return false;
|
|
7082
|
-
visitedIdentifiers.add(identifier);
|
|
7083
|
-
const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
|
|
7084
|
-
if (declaration !== null && isHtmlTainted(declaration.initializer, fileContent, sinkIndex, visitedIdentifiers)) return true;
|
|
7085
|
-
const parameterSource = findContainingFunctionParameterSource(identifier, sinkIndex, fileContent);
|
|
7086
|
-
if (parameterSource === null || parameterSource.functionName.length === 0) return false;
|
|
7087
|
-
const callPattern = new RegExp(`\\b${escapeRegExp(parameterSource.functionName)}\\s*\\(([^)]*)\\)`, "g");
|
|
7088
|
-
for (const callMatch of fileContent.matchAll(callPattern)) {
|
|
7089
|
-
const argument = splitTopLevelArguments(callMatch[1] ?? "")[parameterSource.parameterIndex];
|
|
7090
|
-
if (argument !== void 0 && isHtmlTainted(argument, fileContent, sinkIndex, new Set(visitedIdentifiers))) return true;
|
|
7091
|
-
}
|
|
7092
|
-
return false;
|
|
7093
|
-
};
|
|
7094
6906
|
const dangerousHtmlSink = defineRule({
|
|
7095
6907
|
id: "dangerous-html-sink",
|
|
7096
6908
|
title: "HTML injection sink with dynamic content",
|
|
@@ -7117,7 +6929,6 @@ const dangerousHtmlSink = defineRule({
|
|
|
7117
6929
|
const valueTail = fullValueTail.slice(0, VALUE_EXPRESSION_MAX_CHARS);
|
|
7118
6930
|
const terminatorIndex = valueTail.search(/[;}]/);
|
|
7119
6931
|
const valueExpression = terminatorIndex >= 0 ? valueTail.slice(0, terminatorIndex + 1) : valueTail;
|
|
7120
|
-
const sinkIndex = lines.slice(0, lineIndex).join("\n").length + (lineIndex > 0 ? 1 : 0) + line.search(DANGEROUS_HTML_PATTERN);
|
|
7121
6932
|
if (STRING_LITERAL_VALUE_PATTERN.test(valueExpression)) continue;
|
|
7122
6933
|
if (MODULE_CONSTANT_VALUE_PATTERN.test(valueExpression)) continue;
|
|
7123
6934
|
if (DOM_CONTENT_SOURCE_VALUE_PATTERN.test(valueExpression) && !valueExpression.includes("+")) {
|
|
@@ -7129,7 +6940,7 @@ const dangerousHtmlSink = defineRule({
|
|
|
7129
6940
|
let templateInterpolations = getTemplateInterpolations(longValueTail ?? fullValueTail);
|
|
7130
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;
|
|
7131
6942
|
if (templateInterpolations === null && valueIdentifier !== void 0 && BARE_IDENTIFIER_VALUE_PATTERN.test(valueExpression)) {
|
|
7132
|
-
const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier,
|
|
6943
|
+
const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier, file.content);
|
|
7133
6944
|
if (declarationInitializer !== null) {
|
|
7134
6945
|
if (isPureStringLiteralConcat(declarationInitializer)) continue;
|
|
7135
6946
|
templateInterpolations = getTemplateInterpolations(declarationInitializer);
|
|
@@ -7140,7 +6951,7 @@ const dangerousHtmlSink = defineRule({
|
|
|
7140
6951
|
if (SANITIZER_PATTERN.test(judgedExpression)) continue;
|
|
7141
6952
|
if (ENV_CONFIG_VALUE_PATTERN.test(judgedExpression)) continue;
|
|
7142
6953
|
if (I18N_VALUE_PATTERN.test(judgedExpression)) continue;
|
|
7143
|
-
if (!
|
|
6954
|
+
if (!HTML_TAINT_PATTERN.test(judgedExpression)) continue;
|
|
7144
6955
|
if (ESCAPING_SERIALIZER_CALL_PATTERN.test(valueExpression)) continue;
|
|
7145
6956
|
if (/highlighted/i.test(valueExpression)) continue;
|
|
7146
6957
|
if (/highlight/i.test(valueExpression) && HIGHLIGHTER_LIBRARY_PATTERN.test(file.content)) continue;
|
|
@@ -7587,7 +7398,7 @@ const containsJsx$1 = (root) => {
|
|
|
7587
7398
|
containsJsxCache.set(root, found);
|
|
7588
7399
|
return found;
|
|
7589
7400
|
};
|
|
7590
|
-
const getStaticMemberName$
|
|
7401
|
+
const getStaticMemberName$1 = (node) => {
|
|
7591
7402
|
if (!isNodeOfType(node, "MemberExpression")) return null;
|
|
7592
7403
|
if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
|
|
7593
7404
|
if (node.computed && isNodeOfType(node.property, "Literal") && typeof node.property.value === "string") return node.property.value;
|
|
@@ -7601,7 +7412,7 @@ const getAssignedName = (node) => {
|
|
|
7601
7412
|
if (isNodeOfType(parent, "AssignmentExpression")) {
|
|
7602
7413
|
const left = parent.left;
|
|
7603
7414
|
if (isNodeOfType(left, "Identifier")) return left.name;
|
|
7604
|
-
if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$
|
|
7415
|
+
if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$1(left);
|
|
7605
7416
|
}
|
|
7606
7417
|
return null;
|
|
7607
7418
|
};
|
|
@@ -7609,20 +7420,20 @@ const isModuleExportsAssignment = (node) => {
|
|
|
7609
7420
|
const parent = node.parent;
|
|
7610
7421
|
if (!parent || !isNodeOfType(parent, "AssignmentExpression")) return false;
|
|
7611
7422
|
const left = parent.left;
|
|
7612
|
-
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";
|
|
7613
7424
|
};
|
|
7614
7425
|
const isCreateClassLikeCall = (node) => {
|
|
7615
7426
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7616
7427
|
if (isEs5Component(node)) return true;
|
|
7617
7428
|
const callee = node.callee;
|
|
7618
|
-
if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$
|
|
7429
|
+
if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$1(callee) === "createClass";
|
|
7619
7430
|
return false;
|
|
7620
7431
|
};
|
|
7621
7432
|
const isCreateContextCall = (node) => {
|
|
7622
7433
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7623
7434
|
const callee = node.callee;
|
|
7624
7435
|
if (isNodeOfType(callee, "Identifier")) return callee.name === "createContext";
|
|
7625
|
-
return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$
|
|
7436
|
+
return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$1(callee) === "createContext";
|
|
7626
7437
|
};
|
|
7627
7438
|
const isNamedFunctionLike = (node) => (isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && Boolean(node.id?.name);
|
|
7628
7439
|
const firstCallArgument = (node) => {
|
|
@@ -7680,7 +7491,7 @@ const memberExpressionPath = (node) => {
|
|
|
7680
7491
|
if (isNodeOfType(node, "Identifier")) return [node.name];
|
|
7681
7492
|
if (!isNodeOfType(node, "MemberExpression")) return [];
|
|
7682
7493
|
const objectPath = memberExpressionPath(node.object);
|
|
7683
|
-
const propertyName = getStaticMemberName$
|
|
7494
|
+
const propertyName = getStaticMemberName$1(node);
|
|
7684
7495
|
return propertyName ? [...objectPath, propertyName] : objectPath;
|
|
7685
7496
|
};
|
|
7686
7497
|
const displayNameAssignmentIndexCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -7690,7 +7501,7 @@ const getDisplayNameAssignmentIndex = (programRoot) => {
|
|
|
7690
7501
|
const identifierTargets = /* @__PURE__ */ new Set();
|
|
7691
7502
|
const memberPathSegments = /* @__PURE__ */ new Set();
|
|
7692
7503
|
const visit = (node) => {
|
|
7693
|
-
if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName$
|
|
7504
|
+
if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName$1(node.left) === "displayName") {
|
|
7694
7505
|
if (isNodeOfType(node.left.object, "Identifier")) identifierTargets.add(node.left.object.name);
|
|
7695
7506
|
for (const segment of memberExpressionPath(node.left.object)) memberPathSegments.add(segment);
|
|
7696
7507
|
}
|
|
@@ -7885,7 +7696,7 @@ const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? ap
|
|
|
7885
7696
|
const isImportedFromReact = (symbol) => {
|
|
7886
7697
|
if (symbol.kind !== "import") return false;
|
|
7887
7698
|
const importDeclaration = symbol.declarationNode.parent;
|
|
7888
|
-
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") &&
|
|
7699
|
+
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react");
|
|
7889
7700
|
};
|
|
7890
7701
|
const isNamedReactApiImport = (identifier, apiNames, scopes) => {
|
|
7891
7702
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
@@ -9835,11 +9646,6 @@ const walkParameterReferences = (pattern, state) => {
|
|
|
9835
9646
|
const walk = (node, state) => {
|
|
9836
9647
|
if (isFunctionLike$1(node)) {
|
|
9837
9648
|
if (isNodeOfType(node, "FunctionDeclaration") && node.id) handleFunctionDeclaration(node, state);
|
|
9838
|
-
const functionParams = node.params ?? [];
|
|
9839
|
-
for (const param of functionParams) {
|
|
9840
|
-
if (!("decorators" in param) || !Array.isArray(param.decorators)) continue;
|
|
9841
|
-
for (const decorator of param.decorators) if (isAstNode(decorator)) walk(decorator, state);
|
|
9842
|
-
}
|
|
9843
9649
|
setNodeScope(node, state);
|
|
9844
9650
|
const fnScope = pushScope(node.type === "ArrowFunctionExpression" ? "arrow-function" : "function", node, state);
|
|
9845
9651
|
if ((isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && node.id) {
|
|
@@ -9852,6 +9658,7 @@ const walk = (node, state) => {
|
|
|
9852
9658
|
});
|
|
9853
9659
|
tagAsBinding(state, node.id);
|
|
9854
9660
|
}
|
|
9661
|
+
const functionParams = node.params ?? [];
|
|
9855
9662
|
handleFunctionParameters(functionParams, fnScope, state);
|
|
9856
9663
|
for (const param of functionParams) walkParameterReferences(param, state);
|
|
9857
9664
|
const body = node.body;
|
|
@@ -9861,9 +9668,6 @@ const walk = (node, state) => {
|
|
|
9861
9668
|
}
|
|
9862
9669
|
if (isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) {
|
|
9863
9670
|
if (isNodeOfType(node, "ClassDeclaration") && node.id) handleClassDeclaration(node, state);
|
|
9864
|
-
if (Array.isArray(node.decorators)) {
|
|
9865
|
-
for (const decorator of node.decorators) if (isAstNode(decorator)) walk(decorator, state);
|
|
9866
|
-
}
|
|
9867
9671
|
const classScope = pushScope("class", node, state);
|
|
9868
9672
|
setNodeScope(node, state);
|
|
9869
9673
|
if (isNodeOfType(node, "ClassExpression") && node.id) {
|
|
@@ -10066,7 +9870,7 @@ const computeClosureCaptures = (functionNode, scopes) => {
|
|
|
10066
9870
|
const functionScope = scopes.ownScopeFor(functionNode) ?? scopes.scopeFor(functionNode);
|
|
10067
9871
|
const out = [];
|
|
10068
9872
|
const seen = /* @__PURE__ */ new Set();
|
|
10069
|
-
|
|
9873
|
+
const visit = (node) => {
|
|
10070
9874
|
if (node !== functionNode && isFunctionLike$1(node)) {
|
|
10071
9875
|
const innerCaptures = closureCaptures(node, scopes);
|
|
10072
9876
|
for (const reference of innerCaptures) if (reference.resolvedSymbol && !isDescendantScope(reference.resolvedSymbol.scope, functionScope)) {
|
|
@@ -10075,7 +9879,7 @@ const computeClosureCaptures = (functionNode, scopes) => {
|
|
|
10075
9879
|
seen.add(reference.id);
|
|
10076
9880
|
}
|
|
10077
9881
|
}
|
|
10078
|
-
return
|
|
9882
|
+
return;
|
|
10079
9883
|
}
|
|
10080
9884
|
const reference = scopes.referenceFor(node);
|
|
10081
9885
|
if (reference && reference.resolvedSymbol) {
|
|
@@ -10086,7 +9890,17 @@ const computeClosureCaptures = (functionNode, scopes) => {
|
|
|
10086
9890
|
}
|
|
10087
9891
|
}
|
|
10088
9892
|
}
|
|
10089
|
-
|
|
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);
|
|
10090
9904
|
return out;
|
|
10091
9905
|
};
|
|
10092
9906
|
const capturesByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
@@ -14406,11 +14220,7 @@ const jsIndexMaps = defineRule({
|
|
|
14406
14220
|
//#region src/plugin/utils/are-expressions-structurally-equal.ts
|
|
14407
14221
|
const areExpressionsStructurallyEqual = (a, b) => {
|
|
14408
14222
|
if (!a || !b) return a === b;
|
|
14409
|
-
const unwrappedA = stripParenExpression(a);
|
|
14410
|
-
const unwrappedB = stripParenExpression(b);
|
|
14411
|
-
if (unwrappedA !== a || unwrappedB !== b) return areExpressionsStructurallyEqual(unwrappedA, unwrappedB);
|
|
14412
14223
|
if (a.type !== b.type) return false;
|
|
14413
|
-
if (isNodeOfType(a, "ThisExpression")) return true;
|
|
14414
14224
|
if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
|
|
14415
14225
|
if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
|
|
14416
14226
|
if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
|
|
@@ -19114,7 +18924,6 @@ const jwtInsecureVerification = defineRule({
|
|
|
19114
18924
|
});
|
|
19115
18925
|
//#endregion
|
|
19116
18926
|
//#region src/plugin/rules/security-scan/key-lifecycle-risk.ts
|
|
19117
|
-
const KEY_MATERIAL_MARKER_PATTERN = /PRIVATE KEY|SSH_PRIVATE_KEY|GPG_PRIVATE_KEY|DEPLOY_KEY|SIGNING_KEY/i;
|
|
19118
18927
|
const keyLifecycleRisk = defineRule({
|
|
19119
18928
|
id: "key-lifecycle-risk",
|
|
19120
18929
|
title: "Long-lived key material in repository",
|
|
@@ -19122,7 +18931,7 @@ const keyLifecycleRisk = defineRule({
|
|
|
19122
18931
|
committedFilesOnly: true,
|
|
19123
18932
|
recommendation: "Remove private keys from source, rotate exposed credentials, prefer short-lived deploy credentials, and document revocation/expiry for release keys.",
|
|
19124
18933
|
scan: scanByPattern({
|
|
19125
|
-
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),
|
|
19126
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,
|
|
19127
18936
|
message: "Private or long-lived release key material appears in the repository."
|
|
19128
18937
|
})
|
|
@@ -19828,21 +19637,15 @@ const localRpcNativeBridgeRisk = defineRule({
|
|
|
19828
19637
|
message: "Code appears to bridge browser code to localhost/native capabilities with weak origin or update/install checks."
|
|
19829
19638
|
})
|
|
19830
19639
|
});
|
|
19831
|
-
//#endregion
|
|
19832
|
-
//#region src/plugin/rules/security-scan/mcp-tool-capability-risk.ts
|
|
19833
|
-
const MCP_IMPORT_PATTERN = /\bfrom\s+["']@modelcontextprotocol\/sdk[^"']*["']|\bMcpServer\b|\bMcpAgent\b/;
|
|
19834
|
-
const MCP_IMPORT_PREFILTER_PATTERN = /@modelcontextprotocol\/sdk|\bMcpServer\b|\bMcpAgent\b/;
|
|
19835
|
-
const MCP_TOOL_SURFACE_PATTERN = /\bserver\.\s*tool\s*\(|\bregisterTool\s*\(|\bsetRequestHandler\s*\(\s*CallToolRequestSchema/;
|
|
19836
|
-
const MCP_TOOL_SURFACE_PREFILTER_PATTERN = /\b(?:tool|registerTool|setRequestHandler)\b/;
|
|
19837
19640
|
const mcpToolCapabilityRisk = defineRule({
|
|
19838
19641
|
id: "mcp-tool-capability-risk",
|
|
19839
19642
|
title: "MCP tool exposes dangerous capability",
|
|
19840
19643
|
severity: "warn",
|
|
19841
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.",
|
|
19842
19645
|
scan: scanByPattern({
|
|
19843
|
-
shouldScan: (file) => isProductionSourcePath(file.relativePath)
|
|
19844
|
-
pattern:
|
|
19845
|
-
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],
|
|
19846
19649
|
ignoreStringLiterals: true,
|
|
19847
19650
|
message: "An MCP tool/resource/prompt handler appears to expose file, shell, network, or code-execution capability."
|
|
19848
19651
|
})
|
|
@@ -20953,8 +20756,8 @@ const catchClauseRethrowsCaught = (handler) => {
|
|
|
20953
20756
|
return didRethrow;
|
|
20954
20757
|
};
|
|
20955
20758
|
//#endregion
|
|
20956
|
-
//#region src/plugin/utils/
|
|
20957
|
-
const
|
|
20759
|
+
//#region src/plugin/utils/find-guarding-try-statement.ts
|
|
20760
|
+
const isImmediatelyInvokedFunctionCallee = (functionNode) => {
|
|
20958
20761
|
let wrappedCallee = functionNode;
|
|
20959
20762
|
let enclosing = functionNode.parent;
|
|
20960
20763
|
while (enclosing && stripParenExpression(enclosing) === functionNode) {
|
|
@@ -20963,13 +20766,11 @@ const isImmediatelyInvokedFunction = (functionNode) => {
|
|
|
20963
20766
|
}
|
|
20964
20767
|
return Boolean(enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee);
|
|
20965
20768
|
};
|
|
20966
|
-
//#endregion
|
|
20967
|
-
//#region src/plugin/utils/find-guarding-try-statement.ts
|
|
20968
20769
|
const findGuardingTryStatement = (node) => {
|
|
20969
20770
|
let child = node;
|
|
20970
20771
|
let ancestor = node.parent;
|
|
20971
20772
|
while (ancestor) {
|
|
20972
|
-
if (isFunctionLike$1(ancestor) && !
|
|
20773
|
+
if (isFunctionLike$1(ancestor) && !isImmediatelyInvokedFunctionCallee(ancestor)) return null;
|
|
20973
20774
|
if (isNodeOfType(ancestor, "TryStatement") && ancestor.block === child && ancestor.handler && !catchClauseRethrowsCaught(ancestor.handler)) return ancestor;
|
|
20974
20775
|
child = ancestor;
|
|
20975
20776
|
ancestor = ancestor.parent ?? null;
|
|
@@ -21521,7 +21322,7 @@ const FILENAME_TO_LANG = {
|
|
|
21521
21322
|
const resolveLang = (filename) => {
|
|
21522
21323
|
return FILENAME_TO_LANG[path.extname(filename).toLowerCase()] ?? "tsx";
|
|
21523
21324
|
};
|
|
21524
|
-
const parseSourceText = (
|
|
21325
|
+
const parseSourceText = (filename, sourceText) => {
|
|
21525
21326
|
try {
|
|
21526
21327
|
const result = parseSync(filename, sourceText, {
|
|
21527
21328
|
astType: "ts",
|
|
@@ -21529,7 +21330,7 @@ const parseSourceText = ({ filename, sourceText, shouldAttachParentReferences =
|
|
|
21529
21330
|
});
|
|
21530
21331
|
if (result.errors.some((parseError) => parseError.severity === "Error")) return null;
|
|
21531
21332
|
const parsedProgram = result.program;
|
|
21532
|
-
|
|
21333
|
+
attachParentReferences(parsedProgram);
|
|
21533
21334
|
return parsedProgram;
|
|
21534
21335
|
} catch {
|
|
21535
21336
|
return null;
|
|
@@ -21567,10 +21368,7 @@ const parseSourceFile = (absoluteFilePath) => {
|
|
|
21567
21368
|
});
|
|
21568
21369
|
return null;
|
|
21569
21370
|
}
|
|
21570
|
-
const parsedProgram = parseSourceText(
|
|
21571
|
-
filename: absoluteFilePath,
|
|
21572
|
-
sourceText
|
|
21573
|
-
});
|
|
21371
|
+
const parsedProgram = parseSourceText(absoluteFilePath, sourceText);
|
|
21574
21372
|
parseCache.set(absoluteFilePath, {
|
|
21575
21373
|
mtimeMs: fileStat.mtimeMs,
|
|
21576
21374
|
size: fileStat.size,
|
|
@@ -22102,8 +21900,6 @@ const DOM_QUERY_MEMBER_NAMES = new Set([
|
|
|
22102
21900
|
]);
|
|
22103
21901
|
const LAYOUT_MEASUREMENT_MEMBER_NAMES = new Set([
|
|
22104
21902
|
"current",
|
|
22105
|
-
"textContent",
|
|
22106
|
-
"innerText",
|
|
22107
21903
|
"scrollWidth",
|
|
22108
21904
|
"clientWidth",
|
|
22109
21905
|
"offsetWidth",
|
|
@@ -22125,7 +21921,7 @@ const POST_MOUNT_GLOBAL_NAMES = new Set([
|
|
|
22125
21921
|
"navigator"
|
|
22126
21922
|
]);
|
|
22127
21923
|
const REF_FACTORY_CALLEE_NAMES = new Set(["useRef", "createRef"]);
|
|
22128
|
-
const hasRefLikeName = (name) => name === "ref" || name.endsWith("Ref") || name.endsWith("ref")
|
|
21924
|
+
const hasRefLikeName = (name) => name === "ref" || name.endsWith("Ref") || name.endsWith("ref");
|
|
22129
21925
|
const isRefFactoryInitializer = (init) => {
|
|
22130
21926
|
if (!init || !isNodeOfType(init, "CallExpression")) return false;
|
|
22131
21927
|
const callee = init.callee;
|
|
@@ -22208,37 +22004,88 @@ const readsPostMountValue = (root) => {
|
|
|
22208
22004
|
return found;
|
|
22209
22005
|
};
|
|
22210
22006
|
//#endregion
|
|
22211
|
-
//#region src/plugin/rules/state-and-effects/utils/effect/
|
|
22212
|
-
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
|
+
};
|
|
22213
22019
|
//#endregion
|
|
22214
22020
|
//#region src/plugin/rules/state-and-effects/utils/effect/get-program-analysis.ts
|
|
22215
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
|
+
};
|
|
22216
22053
|
const getProgramAnalysis = (anyNode) => {
|
|
22217
22054
|
const programNode = findProgramRoot(anyNode);
|
|
22218
22055
|
if (!programNode) return null;
|
|
22219
22056
|
const cached = programToAnalysis.get(programNode);
|
|
22220
22057
|
if (cached) return cached;
|
|
22221
|
-
const
|
|
22222
|
-
|
|
22223
|
-
|
|
22058
|
+
const restorations = stripAndRecordParents(programNode);
|
|
22059
|
+
let scopeManager;
|
|
22060
|
+
try {
|
|
22061
|
+
scopeManager = analyze(programNode, {
|
|
22224
22062
|
ecmaVersion: 2024,
|
|
22225
22063
|
sourceType: "module",
|
|
22226
|
-
childVisitorKeys:
|
|
22227
|
-
fallback:
|
|
22228
|
-
})
|
|
22229
|
-
|
|
22230
|
-
|
|
22064
|
+
childVisitorKeys: VISITOR_KEYS,
|
|
22065
|
+
fallback: "iteration"
|
|
22066
|
+
});
|
|
22067
|
+
} finally {
|
|
22068
|
+
restoreParents(restorations);
|
|
22069
|
+
}
|
|
22070
|
+
const analysis = {
|
|
22071
|
+
programNode,
|
|
22072
|
+
scopeManager
|
|
22231
22073
|
};
|
|
22232
22074
|
programToAnalysis.set(programNode, analysis);
|
|
22233
22075
|
return analysis;
|
|
22234
22076
|
};
|
|
22235
|
-
const
|
|
22077
|
+
const scopeByNodeCache = /* @__PURE__ */ new WeakMap();
|
|
22078
|
+
const getScopeForNode = (node, manager) => {
|
|
22236
22079
|
if (!node.range) return null;
|
|
22237
|
-
|
|
22080
|
+
let scopeByNode = scopeByNodeCache.get(manager);
|
|
22081
|
+
if (!scopeByNode) {
|
|
22082
|
+
scopeByNode = /* @__PURE__ */ new WeakMap();
|
|
22083
|
+
scopeByNodeCache.set(manager, scopeByNode);
|
|
22084
|
+
}
|
|
22238
22085
|
if (scopeByNode.has(node)) return scopeByNode.get(node) ?? null;
|
|
22239
22086
|
let bestScope = null;
|
|
22240
22087
|
let bestSize = Infinity;
|
|
22241
|
-
for (const scope of
|
|
22088
|
+
for (const scope of manager.scopes) {
|
|
22242
22089
|
const block = scope.block;
|
|
22243
22090
|
if (!block?.range) continue;
|
|
22244
22091
|
if (node.range[0] < block.range[0] || node.range[1] > block.range[1]) continue;
|
|
@@ -22253,6 +22100,7 @@ const getScopeForNode = (node, analysis) => {
|
|
|
22253
22100
|
};
|
|
22254
22101
|
//#endregion
|
|
22255
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");
|
|
22256
22104
|
const HOOK_NAME_PATTERN$2 = /^use[A-Z0-9]/;
|
|
22257
22105
|
const isInsideCallbackArgumentOf = (identifier, initializer) => {
|
|
22258
22106
|
if (!isNodeOfType(initializer, "CallExpression") && !isNodeOfType(initializer, "NewExpression")) return false;
|
|
@@ -22288,7 +22136,7 @@ const descend = (node, visit, visited = /* @__PURE__ */ new Set()) => {
|
|
|
22288
22136
|
if (visited.has(node)) return;
|
|
22289
22137
|
visit(node);
|
|
22290
22138
|
visited.add(node);
|
|
22291
|
-
const keys =
|
|
22139
|
+
const keys = getChildKeys(node);
|
|
22292
22140
|
const record = node;
|
|
22293
22141
|
for (const key of keys) {
|
|
22294
22142
|
const child = record[key];
|
|
@@ -22321,11 +22169,16 @@ const findDownstreamNodes = (topNode, type) => {
|
|
|
22321
22169
|
});
|
|
22322
22170
|
return nodes;
|
|
22323
22171
|
};
|
|
22172
|
+
const refByIdentifierCache = /* @__PURE__ */ new WeakMap();
|
|
22324
22173
|
const getRef = (analysis, identifier) => {
|
|
22325
|
-
|
|
22174
|
+
let refByIdentifier = refByIdentifierCache.get(analysis);
|
|
22175
|
+
if (!refByIdentifier) {
|
|
22176
|
+
refByIdentifier = /* @__PURE__ */ new WeakMap();
|
|
22177
|
+
refByIdentifierCache.set(analysis, refByIdentifier);
|
|
22178
|
+
}
|
|
22326
22179
|
if (refByIdentifier.has(identifier)) return refByIdentifier.get(identifier) ?? null;
|
|
22327
22180
|
let resolvedReference = null;
|
|
22328
|
-
const scope = getScopeForNode(identifier, analysis);
|
|
22181
|
+
const scope = getScopeForNode(identifier, analysis.scopeManager);
|
|
22329
22182
|
if (scope) {
|
|
22330
22183
|
for (const reference of scope.references) if (reference.identifier === identifier) {
|
|
22331
22184
|
resolvedReference = reference;
|
|
@@ -22573,7 +22426,7 @@ const isReactFunctionalHOC = (analysis, node) => {
|
|
|
22573
22426
|
const isWrappedSeparately = () => {
|
|
22574
22427
|
if (!isNodeOfType(node.id, "Identifier")) return false;
|
|
22575
22428
|
const bindingName = node.id.name;
|
|
22576
|
-
const containingScope = getScopeForNode(node, analysis);
|
|
22429
|
+
const containingScope = getScopeForNode(node, analysis.scopeManager);
|
|
22577
22430
|
if (!containingScope) return false;
|
|
22578
22431
|
const variable = containingScope.variables.find((v) => v.name === bindingName);
|
|
22579
22432
|
if (!variable) return false;
|
|
@@ -22804,14 +22657,11 @@ const hasCleanupReturn = (analysis, node, visited = /* @__PURE__ */ new WeakSet(
|
|
|
22804
22657
|
if (isNodeOfType(node, "ReturnStatement") && node.argument != null) return isCleanupReturnArgument(analysis, node.argument);
|
|
22805
22658
|
if (!isNodeOfType(node, "BlockStatement") && isFunctionLike$1(node)) return false;
|
|
22806
22659
|
const record = node;
|
|
22807
|
-
const
|
|
22808
|
-
|
|
22809
|
-
|
|
22810
|
-
|
|
22811
|
-
|
|
22812
|
-
if (isAstNode(item) && hasCleanupReturn(analysis, item, visited)) return true;
|
|
22813
|
-
}
|
|
22814
|
-
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;
|
|
22815
22665
|
}
|
|
22816
22666
|
return false;
|
|
22817
22667
|
};
|
|
@@ -23102,7 +22952,7 @@ const STORAGE_GLOBAL_NAMES$1 = new Set([
|
|
|
23102
22952
|
"localStorage",
|
|
23103
22953
|
"sessionStorage"
|
|
23104
22954
|
]);
|
|
23105
|
-
const getStaticMemberName
|
|
22955
|
+
const getStaticMemberName = (node) => {
|
|
23106
22956
|
if (!isNodeOfType(node, "MemberExpression")) return null;
|
|
23107
22957
|
if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
|
|
23108
22958
|
if (node.computed && isNodeOfType(node.property, "Literal")) return typeof node.property.value === "string" ? node.property.value : null;
|
|
@@ -23117,7 +22967,7 @@ const getCallCalleeName$1 = (callExpression) => {
|
|
|
23117
22967
|
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
23118
22968
|
const callee = stripParenExpression(callExpression.callee);
|
|
23119
22969
|
if (isNodeOfType(callee, "Identifier")) return callee.name;
|
|
23120
|
-
return getStaticMemberName
|
|
22970
|
+
return getStaticMemberName(callee);
|
|
23121
22971
|
};
|
|
23122
22972
|
const getFunctionParameters = (functionNode) => isFunctionLike$1(functionNode) ? functionNode.params ?? [] : [];
|
|
23123
22973
|
const getIdentifierBindingIdentity = (analysis, identifier) => {
|
|
@@ -23233,14 +23083,14 @@ const localUseEventPreservesCallback = (analysis, callee) => {
|
|
|
23233
23083
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
23234
23084
|
const forwardedCallee = stripParenExpression(child.callee);
|
|
23235
23085
|
if (!isNodeOfType(forwardedCallee, "MemberExpression")) return;
|
|
23236
|
-
if (getStaticMemberName
|
|
23086
|
+
if (getStaticMemberName(forwardedCallee) !== "current") return;
|
|
23237
23087
|
if (!isNodeOfType(forwardedCallee.object, "Identifier")) return;
|
|
23238
23088
|
const refReference = getRef(analysis, forwardedCallee.object);
|
|
23239
23089
|
if (!callbackRefDeclarators.find((declarator) => refReference?.resolved?.defs.some((definition) => definition.node === declarator)) || !refReference?.resolved) return;
|
|
23240
23090
|
if (!refReference.resolved.references.some((candidateReference) => {
|
|
23241
23091
|
const memberExpression = candidateReference.identifier.parent;
|
|
23242
23092
|
const assignmentExpression = memberExpression?.parent;
|
|
23243
|
-
if (!memberExpression || !isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName
|
|
23093
|
+
if (!memberExpression || !isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !assignmentExpression || !isNodeOfType(assignmentExpression, "AssignmentExpression") || assignmentExpression.left !== memberExpression) return false;
|
|
23244
23094
|
return getIdentifierBindingIdentity(analysis, assignmentExpression.right) !== callbackBinding;
|
|
23245
23095
|
})) forwardsCallback = true;
|
|
23246
23096
|
});
|
|
@@ -23265,7 +23115,7 @@ const resolveWrappedCallable = (analysis, node) => {
|
|
|
23265
23115
|
return callback && isFunctionLike$1(callback) ? callback : null;
|
|
23266
23116
|
}
|
|
23267
23117
|
if (!isNodeOfType(candidate, "MemberExpression")) return null;
|
|
23268
|
-
if (getStaticMemberName
|
|
23118
|
+
if (getStaticMemberName(candidate) !== "current") return null;
|
|
23269
23119
|
if (!isNodeOfType(candidate.object, "Identifier")) return null;
|
|
23270
23120
|
const reference = getRef(analysis, candidate.object);
|
|
23271
23121
|
const declarator = reference?.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
@@ -23277,7 +23127,7 @@ const resolveWrappedCallable = (analysis, node) => {
|
|
|
23277
23127
|
return Boolean(reference?.resolved?.references.some((candidateReference) => {
|
|
23278
23128
|
const member = candidateReference.identifier.parent;
|
|
23279
23129
|
const assignment = member?.parent;
|
|
23280
|
-
return Boolean(member && isNodeOfType(member, "MemberExpression") && getStaticMemberName
|
|
23130
|
+
return Boolean(member && isNodeOfType(member, "MemberExpression") && getStaticMemberName(member) === "current" && assignment && isNodeOfType(assignment, "AssignmentExpression") && assignment.left === member);
|
|
23281
23131
|
})) ? null : initializer;
|
|
23282
23132
|
};
|
|
23283
23133
|
const functionInvokesItself = (analysis, functionNode) => {
|
|
@@ -23316,7 +23166,7 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilena
|
|
|
23316
23166
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
23317
23167
|
const callee = stripParenExpression(child.callee);
|
|
23318
23168
|
const calleeName = getCallCalleeName$1(child);
|
|
23319
|
-
const memberName = getStaticMemberName
|
|
23169
|
+
const memberName = getStaticMemberName(callee);
|
|
23320
23170
|
const isDeferringCall = calleeName !== null && DEFERRING_CALLEE_NAMES.has(calleeName) || memberName !== null && DEFERRING_MEMBER_NAMES.has(memberName);
|
|
23321
23171
|
const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES$1.has(memberName) && isNodeOfType(callee, "MemberExpression");
|
|
23322
23172
|
const enqueueFrame = (callableNode, argumentsForCallable, isDeferred, introducedBindings, allowWithoutInvocationEvidence = false) => {
|
|
@@ -23480,9 +23330,9 @@ const analyzeHelperExpression = (expression, environment, usedParameterIndices)
|
|
|
23480
23330
|
const calleeRoot = getMemberRoot(callee);
|
|
23481
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);
|
|
23482
23332
|
const namespaceName = isNodeOfType(calleeRoot, "Identifier") && !environment.parameterIndices.has(calleeRoot.name) && !environment.recursiveNames.has(calleeRoot.name) && !environment.shadowedGlobalNames.has(calleeRoot.name) ? calleeRoot.name : null;
|
|
23483
|
-
const namespaceMemberName = getStaticMemberName
|
|
23333
|
+
const namespaceMemberName = getStaticMemberName(callee);
|
|
23484
23334
|
const isPureNamespaceCall = isNodeOfType(callee, "MemberExpression") && namespaceName !== null && namespaceMemberName !== null && PURE_HELPER_NAMESPACE_MEMBER_NAMES.get(namespaceName)?.has(namespaceMemberName) === true;
|
|
23485
|
-
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName
|
|
23335
|
+
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
|
|
23486
23336
|
if (!isPureGlobalCall && !isPureNamespaceCall && !isPureMemberTransform) return false;
|
|
23487
23337
|
if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression") && !analyzeHelperExpression(callee.object, environment, usedParameterIndices)) return false;
|
|
23488
23338
|
return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
|
|
@@ -23556,7 +23406,7 @@ const isLocallyConstructedObjectMember = (reference, memberExpression) => isNode
|
|
|
23556
23406
|
return isNodeOfType(definitionNode, "VariableDeclarator") && Boolean(definitionNode.init) && (isNodeOfType(definitionNode.init, "ObjectExpression") || isNodeOfType(definitionNode.init, "ArrayExpression"));
|
|
23557
23407
|
}) === true;
|
|
23558
23408
|
const getUseRefDeclarator = (analysis, memberExpression) => {
|
|
23559
|
-
if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName
|
|
23409
|
+
if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !isNodeOfType(memberExpression.object, "Identifier")) return null;
|
|
23560
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;
|
|
23561
23411
|
};
|
|
23562
23412
|
const collectValueEvidence = (analysis, expression, frame, remainingCallFrames, visitedBindings = /* @__PURE__ */ new Set()) => {
|
|
@@ -23625,7 +23475,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23625
23475
|
return collectValueEvidence(analysis, initializer.init, frame, remainingCallFrames, visitedBindings);
|
|
23626
23476
|
}
|
|
23627
23477
|
if (isNodeOfType(node, "MemberExpression")) {
|
|
23628
|
-
if (getStaticMemberName
|
|
23478
|
+
if (getStaticMemberName(node) === "current" && isNodeOfType(node.object, "Identifier")) {
|
|
23629
23479
|
const refBinding = getRef(analysis, node.object)?.resolved;
|
|
23630
23480
|
const refDeclarator = useRefDeclarator;
|
|
23631
23481
|
if (refBinding && refDeclarator && isNodeOfType(refDeclarator, "VariableDeclarator") && isNodeOfType(refDeclarator.init, "CallExpression")) {
|
|
@@ -23641,7 +23491,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23641
23491
|
if (candidateReference.init) continue;
|
|
23642
23492
|
const identifier = candidateReference.identifier;
|
|
23643
23493
|
const member = identifier.parent;
|
|
23644
|
-
if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName
|
|
23494
|
+
if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName(member) !== "current") {
|
|
23645
23495
|
evidence.hasUnknownSource = true;
|
|
23646
23496
|
continue;
|
|
23647
23497
|
}
|
|
@@ -23678,7 +23528,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23678
23528
|
const callee = stripParenExpression(node.callee);
|
|
23679
23529
|
const calleeRoot = getMemberRoot(callee);
|
|
23680
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;
|
|
23681
|
-
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName
|
|
23531
|
+
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
|
|
23682
23532
|
if (isPureGlobalCall || isPureMemberTransform) {
|
|
23683
23533
|
if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression")) mergeEvidence(evidence, collectValueEvidence(analysis, callee.object, frame, remainingCallFrames, new Set(visitedBindings)));
|
|
23684
23534
|
for (const argument of node.arguments ?? []) {
|
|
@@ -24148,26 +23998,6 @@ const STATEFUL_HTML_DESCENDANT_TAGS = new Set([
|
|
|
24148
23998
|
"dialog",
|
|
24149
23999
|
"canvas"
|
|
24150
24000
|
]);
|
|
24151
|
-
const STATEFUL_HTML_ATTRIBUTE_NAMES = new Set([
|
|
24152
|
-
"autofocus",
|
|
24153
|
-
"contenteditable",
|
|
24154
|
-
"draggable",
|
|
24155
|
-
"tabindex"
|
|
24156
|
-
]);
|
|
24157
|
-
const isStaticallyFalseAttributeValue = (attribute) => {
|
|
24158
|
-
if (!isNodeOfType(attribute, "JSXAttribute") || !attribute.value) return false;
|
|
24159
|
-
const value = isNodeOfType(attribute.value, "JSXExpressionContainer") ? attribute.value.expression : attribute.value;
|
|
24160
|
-
return isNodeOfType(value, "Literal") && (value.value === false || value.value === "false");
|
|
24161
|
-
};
|
|
24162
|
-
const hasStatefulHtmlAttribute = (openingElement) => {
|
|
24163
|
-
if (!isNodeOfType(openingElement, "JSXOpeningElement")) return false;
|
|
24164
|
-
return openingElement.attributes.some((attribute) => {
|
|
24165
|
-
if (!isNodeOfType(attribute, "JSXAttribute") || !isNodeOfType(attribute.name, "JSXIdentifier")) return false;
|
|
24166
|
-
const attributeName = attribute.name.name.toLowerCase();
|
|
24167
|
-
if (!STATEFUL_HTML_ATTRIBUTE_NAMES.has(attributeName)) return false;
|
|
24168
|
-
return attributeName === "tabindex" || !isStaticallyFalseAttributeValue(attribute);
|
|
24169
|
-
});
|
|
24170
|
-
};
|
|
24171
24001
|
const STATEFUL_DESCENDANT_SCAN_BUDGET = 200;
|
|
24172
24002
|
const rootIdentifierNameOf = (expression) => {
|
|
24173
24003
|
let object = expression;
|
|
@@ -24185,8 +24015,7 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
|
|
|
24185
24015
|
budget -= 1;
|
|
24186
24016
|
const node = stack.pop();
|
|
24187
24017
|
if (isNodeOfType(node, "JSXElement")) {
|
|
24188
|
-
const
|
|
24189
|
-
const name = opening.name;
|
|
24018
|
+
const name = node.openingElement.name;
|
|
24190
24019
|
if (name && isNodeOfType(name, "JSXIdentifier")) {
|
|
24191
24020
|
const tagName = name.name;
|
|
24192
24021
|
const firstChar = tagName.charCodeAt(0);
|
|
@@ -24194,7 +24023,6 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
|
|
|
24194
24023
|
if (STATEFUL_HTML_DESCENDANT_TAGS.has(tagName)) return true;
|
|
24195
24024
|
}
|
|
24196
24025
|
if (name && isNodeOfType(name, "JSXMemberExpression")) return true;
|
|
24197
|
-
if (hasStatefulHtmlAttribute(opening)) return true;
|
|
24198
24026
|
const children = node.children ?? [];
|
|
24199
24027
|
for (const child of children) stack.push(child);
|
|
24200
24028
|
continue;
|
|
@@ -24288,7 +24116,6 @@ const ARITHMETIC_KEY_OPERATORS = new Set([
|
|
|
24288
24116
|
"/",
|
|
24289
24117
|
"%"
|
|
24290
24118
|
]);
|
|
24291
|
-
const bindingMutationResultsByProgram = /* @__PURE__ */ new WeakMap();
|
|
24292
24119
|
const extractCandidateIdentifiers = (expression) => {
|
|
24293
24120
|
const node = stripParenExpression(expression);
|
|
24294
24121
|
if (isNodeOfType(node, "Identifier")) return [node];
|
|
@@ -24329,13 +24156,6 @@ const isArrayFromCall = (node) => {
|
|
|
24329
24156
|
const isBindingReassignedOrMutated = (referenceNode, bindingName) => {
|
|
24330
24157
|
const programRoot = findProgramRoot(referenceNode);
|
|
24331
24158
|
if (!programRoot) return false;
|
|
24332
|
-
let mutationResultsByBindingName = bindingMutationResultsByProgram.get(programRoot);
|
|
24333
|
-
if (!mutationResultsByBindingName) {
|
|
24334
|
-
mutationResultsByBindingName = /* @__PURE__ */ new Map();
|
|
24335
|
-
bindingMutationResultsByProgram.set(programRoot, mutationResultsByBindingName);
|
|
24336
|
-
}
|
|
24337
|
-
const cachedResult = mutationResultsByBindingName.get(bindingName);
|
|
24338
|
-
if (cachedResult !== void 0) return cachedResult;
|
|
24339
24159
|
let didFindWrite = false;
|
|
24340
24160
|
walkAst(programRoot, (child) => {
|
|
24341
24161
|
if (didFindWrite) return false;
|
|
@@ -24352,7 +24172,6 @@ const isBindingReassignedOrMutated = (referenceNode, bindingName) => {
|
|
|
24352
24172
|
return false;
|
|
24353
24173
|
}
|
|
24354
24174
|
});
|
|
24355
|
-
mutationResultsByBindingName.set(bindingName, didFindWrite);
|
|
24356
24175
|
return didFindWrite;
|
|
24357
24176
|
};
|
|
24358
24177
|
/**
|
|
@@ -24754,14 +24573,6 @@ const templateHasOuterMemberIdentity = (template, bindingFunction) => {
|
|
|
24754
24573
|
}
|
|
24755
24574
|
return false;
|
|
24756
24575
|
};
|
|
24757
|
-
const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
|
|
24758
|
-
const referencedItemNames = /* @__PURE__ */ new Set();
|
|
24759
|
-
for (const expression of template.expressions ?? []) {
|
|
24760
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
24761
|
-
if (isNodeOfType(unwrappedExpression, "Identifier") && itemNames.has(unwrappedExpression.name)) referencedItemNames.add(unwrappedExpression.name);
|
|
24762
|
-
}
|
|
24763
|
-
return referencedItemNames;
|
|
24764
|
-
};
|
|
24765
24576
|
const forLoopTestReadsDataLength = (test) => {
|
|
24766
24577
|
let didFindLengthRead = false;
|
|
24767
24578
|
walkAst(test, (child) => {
|
|
@@ -24898,11 +24709,9 @@ const noArrayIndexAsKey = defineRule({
|
|
|
24898
24709
|
} else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
|
|
24899
24710
|
const jsxElement = openingElement.parent;
|
|
24900
24711
|
if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
|
|
24901
|
-
const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
|
|
24902
|
-
const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET;
|
|
24903
24712
|
if (!containsStatefulDescendant(jsxElement, {
|
|
24904
|
-
memberRootNames:
|
|
24905
|
-
bareIdentifierNames:
|
|
24713
|
+
memberRootNames: INLINE_TEXT_LEAF_TAGS.has(elementName.name) ? itemNames : EMPTY_NAME_SET,
|
|
24714
|
+
bareIdentifierNames: derivedNames
|
|
24906
24715
|
})) return;
|
|
24907
24716
|
}
|
|
24908
24717
|
}
|
|
@@ -25463,10 +25272,10 @@ const declaresAnyDependency = (manifest) => DEPENDENCY_SECTION_NAMES.some((secti
|
|
|
25463
25272
|
const section = manifest[sectionName];
|
|
25464
25273
|
return typeof section === "object" && section !== null && Object.keys(section).length > 0;
|
|
25465
25274
|
});
|
|
25466
|
-
const declaresDependency = (manifest, dependencyName) =>
|
|
25467
|
-
const
|
|
25468
|
-
return
|
|
25469
|
-
}
|
|
25275
|
+
const declaresDependency = (manifest, dependencyName) => {
|
|
25276
|
+
for (const declaredName of iterateDependencyNames(manifest)) if (declaredName === dependencyName) return true;
|
|
25277
|
+
return false;
|
|
25278
|
+
};
|
|
25470
25279
|
const cachedPlatformByManifest = /* @__PURE__ */ new WeakMap();
|
|
25471
25280
|
const classifyPackagePlatform = (filename) => {
|
|
25472
25281
|
const manifest = readNearestPackageManifest(filename);
|
|
@@ -25713,23 +25522,25 @@ const isCallArgumentFunctionExpression = (node) => {
|
|
|
25713
25522
|
return parent.arguments.some((argumentNode) => argumentNode === node);
|
|
25714
25523
|
};
|
|
25715
25524
|
const isNestedRenderEvidenceBoundary = (node) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isCallArgumentFunctionExpression(node);
|
|
25716
|
-
const containsRenderOutput$1 = (rootNode, scopes) => {
|
|
25717
|
-
|
|
25718
|
-
|
|
25719
|
-
|
|
25720
|
-
|
|
25721
|
-
|
|
25722
|
-
|
|
25723
|
-
|
|
25724
|
-
|
|
25725
|
-
|
|
25726
|
-
|
|
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;
|
|
25727
25538
|
};
|
|
25728
25539
|
const renderOutputCache = /* @__PURE__ */ new WeakMap();
|
|
25729
25540
|
const functionContainsReactRenderOutput = (functionNode, scopes) => {
|
|
25730
25541
|
const cachedEntry = renderOutputCache.get(functionNode);
|
|
25731
25542
|
if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
|
|
25732
|
-
const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
|
|
25543
|
+
const hasRenderOutput = containsRenderOutput$1(functionNode, functionNode, scopes);
|
|
25733
25544
|
renderOutputCache.set(functionNode, {
|
|
25734
25545
|
scopes,
|
|
25735
25546
|
hasRenderOutput
|
|
@@ -27535,7 +27346,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
27535
27346
|
let ancestor = setStateCall.parent;
|
|
27536
27347
|
while (ancestor) {
|
|
27537
27348
|
if (!lifecycleMember) {
|
|
27538
|
-
if (FUNCTION_NODE_TYPES$1.has(ancestor.type)
|
|
27349
|
+
if (FUNCTION_NODE_TYPES$1.has(ancestor.type)) nestedFunctionCount += 1;
|
|
27539
27350
|
if (isLifecycleMember(ancestor, lifecycleNames)) lifecycleMember = ancestor;
|
|
27540
27351
|
} else if (isEs5Component(ancestor) || isEs6Component(ancestor)) {
|
|
27541
27352
|
if (nestedFunctionCount > 1 && !options.disallowInNestedFunctions) return false;
|
|
@@ -27737,7 +27548,7 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
|
|
|
27737
27548
|
const body = lifecycleFunction.body;
|
|
27738
27549
|
if (!body) return derivedNames;
|
|
27739
27550
|
walkAst(body, (node) => {
|
|
27740
|
-
if (FUNCTION_NODE_TYPES.has(node.type)
|
|
27551
|
+
if (FUNCTION_NODE_TYPES.has(node.type)) return false;
|
|
27741
27552
|
if (!isNodeOfType(node, "VariableDeclarator")) return;
|
|
27742
27553
|
const init = node.init;
|
|
27743
27554
|
if (!init) return;
|
|
@@ -27747,67 +27558,6 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
|
|
|
27747
27558
|
return derivedNames;
|
|
27748
27559
|
};
|
|
27749
27560
|
const isStatefulOperand = (node, paramNames, derivedNames) => referencesAnyName(node, paramNames) || referencesAnyName(node, derivedNames) || containsThisStateOrProps(node);
|
|
27750
|
-
const getStaticMemberName = (node) => {
|
|
27751
|
-
if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
|
|
27752
|
-
return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
|
|
27753
|
-
};
|
|
27754
|
-
const getThisStateFieldName = (node) => {
|
|
27755
|
-
const unwrappedNode = stripParenExpression(node);
|
|
27756
|
-
if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
|
|
27757
|
-
const object = stripParenExpression(unwrappedNode.object);
|
|
27758
|
-
if (!isNodeOfType(object, "MemberExpression") || !isNodeOfType(stripParenExpression(object.object), "ThisExpression") || getStaticMemberName(object) !== "state") return null;
|
|
27759
|
-
return getStaticMemberName(unwrappedNode);
|
|
27760
|
-
};
|
|
27761
|
-
const collectLocalInitializers = (lifecycleFunction) => {
|
|
27762
|
-
const initializers = /* @__PURE__ */ new Map();
|
|
27763
|
-
const body = lifecycleFunction.body;
|
|
27764
|
-
if (!body) return initializers;
|
|
27765
|
-
walkAst(body, (node) => {
|
|
27766
|
-
if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
|
|
27767
|
-
if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.init) initializers.set(node.id.name, node.init);
|
|
27768
|
-
});
|
|
27769
|
-
return initializers;
|
|
27770
|
-
};
|
|
27771
|
-
const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
|
|
27772
|
-
if (readsPostMountValue(node)) return true;
|
|
27773
|
-
const referencedNames = /* @__PURE__ */ new Set();
|
|
27774
|
-
collectReferenceIdentifierNames(node, referencedNames);
|
|
27775
|
-
for (const referencedName of referencedNames) {
|
|
27776
|
-
if (visitedNames.has(referencedName)) continue;
|
|
27777
|
-
const initializer = localInitializers.get(referencedName);
|
|
27778
|
-
if (!initializer) continue;
|
|
27779
|
-
if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
|
|
27780
|
-
}
|
|
27781
|
-
return false;
|
|
27782
|
-
};
|
|
27783
|
-
const getSetStateFieldValue = (setStateCall, fieldName) => {
|
|
27784
|
-
if (!isNodeOfType(setStateCall, "CallExpression")) return null;
|
|
27785
|
-
const argument = setStateCall.arguments?.[0];
|
|
27786
|
-
if (!argument || !isNodeOfType(argument, "ObjectExpression")) return null;
|
|
27787
|
-
for (const property of argument.properties ?? []) {
|
|
27788
|
-
if (!isNodeOfType(property, "Property") || property.computed === true) continue;
|
|
27789
|
-
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;
|
|
27790
|
-
}
|
|
27791
|
-
return null;
|
|
27792
|
-
};
|
|
27793
|
-
const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
|
|
27794
|
-
let qualifies = false;
|
|
27795
|
-
walkAst(test, (node) => {
|
|
27796
|
-
if (qualifies) return false;
|
|
27797
|
-
if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
|
|
27798
|
-
const leftFieldName = getThisStateFieldName(node.left);
|
|
27799
|
-
const rightFieldName = getThisStateFieldName(node.right);
|
|
27800
|
-
const fieldName = leftFieldName ?? rightFieldName;
|
|
27801
|
-
const comparedValue = leftFieldName ? node.right : node.left;
|
|
27802
|
-
if (!fieldName || !leftFieldName && !rightFieldName) return;
|
|
27803
|
-
const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
|
|
27804
|
-
if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
|
|
27805
|
-
if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
|
|
27806
|
-
qualifies = true;
|
|
27807
|
-
return false;
|
|
27808
|
-
});
|
|
27809
|
-
return qualifies;
|
|
27810
|
-
};
|
|
27811
27561
|
const isDiffGuardTest = (test, paramNames, derivedNames) => {
|
|
27812
27562
|
if (referencesAnyName(test, paramNames)) return true;
|
|
27813
27563
|
let qualifies = false;
|
|
@@ -27815,7 +27565,7 @@ const isDiffGuardTest = (test, paramNames, derivedNames) => {
|
|
|
27815
27565
|
if (qualifies) return false;
|
|
27816
27566
|
if (!isNodeOfType(node, "BinaryExpression")) return;
|
|
27817
27567
|
if (!EQUALITY_OPERATORS.has(node.operator)) return;
|
|
27818
|
-
if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames)
|
|
27568
|
+
if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames)) {
|
|
27819
27569
|
qualifies = true;
|
|
27820
27570
|
return false;
|
|
27821
27571
|
}
|
|
@@ -27828,12 +27578,11 @@ const isInsideDiffGuard = (setStateCall) => {
|
|
|
27828
27578
|
const paramNames = /* @__PURE__ */ new Set();
|
|
27829
27579
|
for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
|
|
27830
27580
|
const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
|
|
27831
|
-
const localInitializers = collectLocalInitializers(lifecycleFunction);
|
|
27832
27581
|
let child = setStateCall;
|
|
27833
27582
|
let ancestor = setStateCall.parent;
|
|
27834
27583
|
while (ancestor && ancestor !== lifecycleFunction) {
|
|
27835
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;
|
|
27836
|
-
if (guardTest &&
|
|
27585
|
+
if (guardTest && isDiffGuardTest(guardTest, paramNames, derivedNames)) return true;
|
|
27837
27586
|
child = ancestor;
|
|
27838
27587
|
ancestor = ancestor.parent ?? null;
|
|
27839
27588
|
}
|
|
@@ -28014,16 +27763,7 @@ const producesOpaqueInstanceValue = (expression) => {
|
|
|
28014
27763
|
const collectSetterValueObservations = (componentBody, setterNames) => {
|
|
28015
27764
|
const plainFedSetterNames = /* @__PURE__ */ new Set();
|
|
28016
27765
|
const opaqueFedSetterNames = /* @__PURE__ */ new Set();
|
|
28017
|
-
const callbackRefSetterNames = /* @__PURE__ */ new Set();
|
|
28018
27766
|
walkComponentRespectingShadows(componentBody, /* @__PURE__ */ new Set(), (node, currentlyShadowed) => {
|
|
28019
|
-
if (isNodeOfType(node, "JSXAttribute")) {
|
|
28020
|
-
const attributeName = node.name;
|
|
28021
|
-
if (isNodeOfType(attributeName, "JSXIdentifier") && attributeName.name === "ref" && node.value && isNodeOfType(node.value, "JSXExpressionContainer")) {
|
|
28022
|
-
const expression = stripParenExpression(node.value.expression);
|
|
28023
|
-
if (isNodeOfType(expression, "Identifier") && setterNames.has(expression.name)) callbackRefSetterNames.add(expression.name);
|
|
28024
|
-
}
|
|
28025
|
-
return;
|
|
28026
|
-
}
|
|
28027
27767
|
if (!isNodeOfType(node, "CallExpression")) return;
|
|
28028
27768
|
if (!isNodeOfType(node.callee, "Identifier")) return;
|
|
28029
27769
|
const setterName = node.callee.name;
|
|
@@ -28040,10 +27780,21 @@ const collectSetterValueObservations = (componentBody, setterNames) => {
|
|
|
28040
27780
|
}, true);
|
|
28041
27781
|
return {
|
|
28042
27782
|
plainFedSetterNames,
|
|
28043
|
-
opaqueFedSetterNames
|
|
28044
|
-
callbackRefSetterNames
|
|
27783
|
+
opaqueFedSetterNames
|
|
28045
27784
|
};
|
|
28046
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
|
+
};
|
|
28047
27798
|
const collectFunctionLocalBindings = (functionNode) => {
|
|
28048
27799
|
const localBindings = /* @__PURE__ */ new Set();
|
|
28049
27800
|
if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return localBindings;
|
|
@@ -28055,8 +27806,8 @@ const collectFunctionLocalBindings = (functionNode) => {
|
|
|
28055
27806
|
return localBindings;
|
|
28056
27807
|
};
|
|
28057
27808
|
const collectBlockScopedBindings = (node) => {
|
|
27809
|
+
const blockBindings = /* @__PURE__ */ new Set();
|
|
28058
27810
|
if (isNodeOfType(node, "BlockStatement")) {
|
|
28059
|
-
const blockBindings = /* @__PURE__ */ new Set();
|
|
28060
27811
|
for (const statement of node.body ?? []) {
|
|
28061
27812
|
if (!isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
28062
27813
|
for (const declarator of statement.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
|
|
@@ -28064,22 +27815,17 @@ const collectBlockScopedBindings = (node) => {
|
|
|
28064
27815
|
return blockBindings;
|
|
28065
27816
|
}
|
|
28066
27817
|
if (isNodeOfType(node, "ForStatement") && isNodeOfType(node.init, "VariableDeclaration")) {
|
|
28067
|
-
const blockBindings = /* @__PURE__ */ new Set();
|
|
28068
27818
|
for (const declarator of node.init.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
|
|
28069
27819
|
return blockBindings;
|
|
28070
27820
|
}
|
|
28071
|
-
if ((isNodeOfType(node, "ForOfStatement") || isNodeOfType(node, "ForInStatement")) && isNodeOfType(node.left, "VariableDeclaration"))
|
|
28072
|
-
|
|
28073
|
-
for (const declarator of node.left.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
|
|
28074
|
-
return blockBindings;
|
|
28075
|
-
}
|
|
28076
|
-
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;
|
|
28077
27823
|
};
|
|
28078
27824
|
const walkComponentRespectingShadows = (node, shadowedStateNames, visit, isComponentBodyRoot = false) => {
|
|
28079
27825
|
if (!node || typeof node !== "object") return;
|
|
28080
27826
|
let nextShadowedStateNames = shadowedStateNames;
|
|
28081
|
-
const localBindings = isComponentBodyRoot ?
|
|
28082
|
-
if (localBindings
|
|
27827
|
+
const localBindings = isComponentBodyRoot ? /* @__PURE__ */ new Set() : isFunctionLike$1(node) ? collectFunctionLocalBindings(node) : collectBlockScopedBindings(node);
|
|
27828
|
+
if (localBindings.size > 0) {
|
|
28083
27829
|
const merged = new Set(shadowedStateNames);
|
|
28084
27830
|
for (const localName of localBindings) merged.add(localName);
|
|
28085
27831
|
nextShadowedStateNames = merged;
|
|
@@ -28105,10 +27851,11 @@ const noDirectStateMutation = defineRule({
|
|
|
28105
27851
|
const bindings = collectUseStateBindings(componentBody);
|
|
28106
27852
|
if (bindings.length === 0) return;
|
|
28107
27853
|
const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
|
|
27854
|
+
const callbackRefSetterNames = collectCallbackRefSetterNames(componentBody);
|
|
28108
27855
|
const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
|
|
28109
27856
|
const plainObjectStateValueNames = /* @__PURE__ */ new Set();
|
|
28110
27857
|
for (const binding of bindings) {
|
|
28111
|
-
if (
|
|
27858
|
+
if (callbackRefSetterNames.has(binding.setterName)) continue;
|
|
28112
27859
|
if (!isNodeOfType(binding.declarator.init, "CallExpression")) continue;
|
|
28113
27860
|
const initializerArgument = binding.declarator.init.arguments?.[0];
|
|
28114
27861
|
if (!initializerMarksPlainState(initializerArgument)) continue;
|
|
@@ -32826,60 +32573,25 @@ const resolveSettings$16 = (settings) => {
|
|
|
32826
32573
|
};
|
|
32827
32574
|
const isHocCall = (call, scopes) => {
|
|
32828
32575
|
if (!isNodeOfType(call, "CallExpression")) return false;
|
|
32829
|
-
|
|
32830
|
-
|
|
32831
|
-
allowUnboundBareCalls: true
|
|
32832
|
-
})) return true;
|
|
32833
|
-
if (isReactHocMemberReference(call.callee, scopes)) return true;
|
|
32576
|
+
const calleeName = flattenCalleeName(call.callee);
|
|
32577
|
+
if (calleeName && REACT_HOC_NAMES.has(calleeName)) return true;
|
|
32834
32578
|
if (!isNodeOfType(call.callee, "Identifier")) return false;
|
|
32835
32579
|
const symbol = scopes.symbolFor(call.callee);
|
|
32836
32580
|
if (!symbol) return false;
|
|
32837
|
-
return symbolMapsToHoc(symbol
|
|
32838
|
-
};
|
|
32839
|
-
const isReactImportEquals = (symbol) => {
|
|
32840
|
-
if (symbol.kind !== "ts-import-equals" || !isNodeOfType(symbol.declarationNode, "TSImportEqualsDeclaration")) return false;
|
|
32841
|
-
const moduleReference = symbol.declarationNode.moduleReference;
|
|
32842
|
-
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);
|
|
32843
32582
|
};
|
|
32844
|
-
const
|
|
32845
|
-
if (
|
|
32846
|
-
|
|
32847
|
-
return Boolean(moduleSpecifier && isNodeOfType(moduleSpecifier, "Literal") && typeof moduleSpecifier.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(moduleSpecifier.value));
|
|
32848
|
-
};
|
|
32849
|
-
const isReactNamespaceExpression = (node, scopes) => {
|
|
32850
|
-
if (isRequireReactCall$1(node, scopes)) return true;
|
|
32851
|
-
if (!isNodeOfType(node, "Identifier")) return false;
|
|
32852
|
-
const symbol = scopes.symbolFor(node);
|
|
32853
|
-
if (!symbol) return node.name === "React" && scopes.isGlobalReference(node);
|
|
32854
|
-
if (symbol.initializer && isRequireReactCall$1(symbol.initializer, scopes)) return true;
|
|
32855
|
-
if (isReactImportEquals(symbol)) return true;
|
|
32856
|
-
return isReactNamespaceImport(node, scopes);
|
|
32857
|
-
};
|
|
32858
|
-
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));
|
|
32859
|
-
const getDestructuredPropertyName$1 = (symbol) => {
|
|
32860
|
-
const property = symbol.bindingIdentifier.parent;
|
|
32861
|
-
if (!property || !isNodeOfType(property, "Property") || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
|
|
32862
|
-
if (isNodeOfType(property.key, "Identifier") && !property.computed) return property.key.name;
|
|
32863
|
-
if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
|
|
32864
|
-
return null;
|
|
32865
|
-
};
|
|
32866
|
-
const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
32867
|
-
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
32868
|
-
visitedSymbolIds.add(symbol.id);
|
|
32869
|
-
if (symbol.kind === "import") {
|
|
32870
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
32871
|
-
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;
|
|
32872
32586
|
}
|
|
32873
32587
|
const init = symbol.initializer;
|
|
32874
32588
|
if (!init) return false;
|
|
32875
|
-
|
|
32876
|
-
|
|
32877
|
-
|
|
32878
|
-
if (isNodeOfType(init, "Identifier")) {
|
|
32879
|
-
const initializedFromSymbol = scopes.symbolFor(init);
|
|
32880
|
-
if (initializedFromSymbol) return symbolMapsToHoc(initializedFromSymbol, scopes, visitedSymbolIds);
|
|
32881
|
-
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;
|
|
32882
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;
|
|
32883
32595
|
return false;
|
|
32884
32596
|
};
|
|
32885
32597
|
const isTrivialPassthroughChild = (child) => {
|
|
@@ -32937,14 +32649,26 @@ const isHocComponent = (call, scopes) => {
|
|
|
32937
32649
|
};
|
|
32938
32650
|
const containsJsx = (root) => {
|
|
32939
32651
|
let found = false;
|
|
32940
|
-
|
|
32941
|
-
if (found) return
|
|
32652
|
+
const visit = (node) => {
|
|
32653
|
+
if (found) return;
|
|
32942
32654
|
if (node.type === "JSXElement" || node.type === "JSXFragment") {
|
|
32943
32655
|
found = true;
|
|
32944
|
-
return
|
|
32656
|
+
return;
|
|
32945
32657
|
}
|
|
32946
|
-
if (node !== root
|
|
32947
|
-
|
|
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);
|
|
32948
32672
|
return found;
|
|
32949
32673
|
};
|
|
32950
32674
|
const expressionContainsJsx = (expression) => {
|
|
@@ -32968,7 +32692,7 @@ const unwrapTsCast = (expression) => {
|
|
|
32968
32692
|
while (current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSNonNullExpression") current = stripParenExpression(current.expression);
|
|
32969
32693
|
return current;
|
|
32970
32694
|
};
|
|
32971
|
-
const collectReExportedNames = (program
|
|
32695
|
+
const collectReExportedNames = (program) => {
|
|
32972
32696
|
const names = /* @__PURE__ */ new Set();
|
|
32973
32697
|
if (!isNodeOfType(program, "Program")) return names;
|
|
32974
32698
|
for (const statement of program.body) {
|
|
@@ -32992,7 +32716,8 @@ const collectReExportedNames = (program, scopes) => {
|
|
|
32992
32716
|
if (!declarator.init) continue;
|
|
32993
32717
|
const init = unwrapTsCast(declarator.init);
|
|
32994
32718
|
if (isNodeOfType(init, "CallExpression")) {
|
|
32995
|
-
|
|
32719
|
+
const calleeName = flattenCalleeName(init.callee);
|
|
32720
|
+
if (calleeName && REACT_HOC_NAMES.has(calleeName)) {
|
|
32996
32721
|
const wrappedArg = init.arguments[0];
|
|
32997
32722
|
if (wrappedArg && isNodeOfType(wrappedArg, "Identifier")) names.add(wrappedArg.name);
|
|
32998
32723
|
}
|
|
@@ -33061,7 +32786,16 @@ const recordComponent = (context, name, reportNode, isStateless) => {
|
|
|
33061
32786
|
isStateless
|
|
33062
32787
|
});
|
|
33063
32788
|
};
|
|
33064
|
-
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
|
+
};
|
|
33065
32799
|
const walkComponentSearch = (node, context) => {
|
|
33066
32800
|
if (isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) {
|
|
33067
32801
|
if (isEs6Component(node)) {
|
|
@@ -33163,13 +32897,12 @@ const noMultiComp = defineRule({
|
|
|
33163
32897
|
components: [],
|
|
33164
32898
|
componentDepth: 0,
|
|
33165
32899
|
currentVarName: null,
|
|
33166
|
-
scopes: context.scopes
|
|
33167
|
-
visitChild: (child) => walkComponentSearch(child, visitContext)
|
|
32900
|
+
scopes: context.scopes
|
|
33168
32901
|
};
|
|
33169
32902
|
for (const statement of node.body) walkComponentSearch(statement, visitContext);
|
|
33170
32903
|
const flagged = settings.ignoreStateless ? visitContext.components.filter((component) => !component.isStateless) : visitContext.components;
|
|
33171
32904
|
if (flagged.length <= 2) return;
|
|
33172
|
-
const reExportedNames = collectReExportedNames(node
|
|
32905
|
+
const reExportedNames = collectReExportedNames(node);
|
|
33173
32906
|
const exportedCount = flagged.filter((component) => isExportedDeclaration(component.reportNode, reExportedNames)).length;
|
|
33174
32907
|
if (exportedCount >= flagged.length || flagged.length >= 4 && exportedCount >= Math.floor(flagged.length * .7) || flagged.length >= 8 && exportedCount >= Math.floor(flagged.length * .5)) return;
|
|
33175
32908
|
const isSmallFeatureModule = exportedCount > 0 && exportedCount <= 2 && exportedCount < flagged.length;
|
|
@@ -35199,30 +34932,6 @@ const guardsRenderShape = (comparison) => {
|
|
|
35199
34932
|
}
|
|
35200
34933
|
return false;
|
|
35201
34934
|
};
|
|
35202
|
-
const isPropsChildrenLength = (node, scopes) => {
|
|
35203
|
-
const unwrappedNode = stripParenExpression(node);
|
|
35204
|
-
return isNodeOfType(unwrappedNode, "MemberExpression") && !unwrappedNode.computed && isNodeOfType(unwrappedNode.property, "Identifier") && unwrappedNode.property.name === "length" && resolvesToPropsChildren(stripParenExpression(unwrappedNode.object), scopes);
|
|
35205
|
-
};
|
|
35206
|
-
const isLargeTextLengthComparison = (node, scopes) => {
|
|
35207
|
-
const unwrappedNode = stripParenExpression(node);
|
|
35208
|
-
if (!isNodeOfType(unwrappedNode, "BinaryExpression")) return false;
|
|
35209
|
-
const leftIsLength = isPropsChildrenLength(unwrappedNode.left, scopes);
|
|
35210
|
-
const rightIsLength = isPropsChildrenLength(unwrappedNode.right, scopes);
|
|
35211
|
-
const thresholdNode = leftIsLength ? unwrappedNode.right : unwrappedNode.left;
|
|
35212
|
-
if (!leftIsLength && !rightIsLength || !isNodeOfType(thresholdNode, "Literal")) return false;
|
|
35213
|
-
if (typeof thresholdNode.value !== "number" || thresholdNode.value < 1e3) return false;
|
|
35214
|
-
return leftIsLength ? unwrappedNode.operator === ">" || unwrappedNode.operator === ">=" : unwrappedNode.operator === "<" || unwrappedNode.operator === "<=";
|
|
35215
|
-
};
|
|
35216
|
-
const isLargeStringOptimizationGuard = (comparison, scopes) => {
|
|
35217
|
-
let current = findTransparentExpressionRoot(comparison);
|
|
35218
|
-
while (current.parent) {
|
|
35219
|
-
const parent = current.parent;
|
|
35220
|
-
if (!isNodeOfType(parent, "LogicalExpression") || parent.operator !== "&&") return false;
|
|
35221
|
-
if (isLargeTextLengthComparison(parent.left === current ? parent.right : parent.left, scopes)) return true;
|
|
35222
|
-
current = findTransparentExpressionRoot(parent);
|
|
35223
|
-
}
|
|
35224
|
-
return false;
|
|
35225
|
-
};
|
|
35226
34935
|
const noPolymorphicChildren = defineRule({
|
|
35227
34936
|
id: "no-polymorphic-children",
|
|
35228
34937
|
title: "Children type checked at runtime",
|
|
@@ -35236,7 +34945,6 @@ const noPolymorphicChildren = defineRule({
|
|
|
35236
34945
|
const isStringLiteral = (operand) => isNodeOfType(operand, "Literal") && operand.value === "string";
|
|
35237
34946
|
if (!isStringLiteral(node.left) && !isStringLiteral(node.right)) return;
|
|
35238
34947
|
if (!guardsRenderShape(node)) return;
|
|
35239
|
-
if (isLargeStringOptimizationGuard(node, context.scopes)) return;
|
|
35240
34948
|
context.report({
|
|
35241
34949
|
node,
|
|
35242
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."
|
|
@@ -35454,27 +35162,6 @@ const hasPreviousValueDep = (effectNode, depElements) => {
|
|
|
35454
35162
|
}
|
|
35455
35163
|
return false;
|
|
35456
35164
|
};
|
|
35457
|
-
const isStateLikeDependency = (analysis, element, isPropName) => {
|
|
35458
|
-
if (!isNodeOfType(element, "Identifier") || isPropName(element.name)) return false;
|
|
35459
|
-
if (!analysis) return true;
|
|
35460
|
-
const reference = getRef(analysis, element);
|
|
35461
|
-
if (!reference) return true;
|
|
35462
|
-
const upstreamReferences = getUpstreamRefs(analysis, reference);
|
|
35463
|
-
if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
|
|
35464
|
-
return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
|
|
35465
|
-
};
|
|
35466
|
-
const getRefHeldPropCallbackName = (callExpression, isPropName) => {
|
|
35467
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
35468
|
-
if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "current") return null;
|
|
35469
|
-
const receiver = stripParenExpression(callee.object);
|
|
35470
|
-
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
35471
|
-
const binding = findVariableInitializer(callExpression, receiver.name);
|
|
35472
|
-
if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
|
|
35473
|
-
if (getCalleeName$2(binding.initializer) !== "useRef") return null;
|
|
35474
|
-
const callbackArgument = binding.initializer.arguments?.[0];
|
|
35475
|
-
if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
|
|
35476
|
-
return isPropName(callbackArgument.name) ? callbackArgument.name : null;
|
|
35477
|
-
};
|
|
35478
35165
|
const noPropCallbackInEffect = defineRule({
|
|
35479
35166
|
id: "no-prop-callback-in-effect",
|
|
35480
35167
|
title: "Parent kept in sync with a callback effect",
|
|
@@ -35491,11 +35178,11 @@ const noPropCallbackInEffect = defineRule({
|
|
|
35491
35178
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
35492
35179
|
const depsNode = node.arguments[1];
|
|
35493
35180
|
if (!isNodeOfType(depsNode, "ArrayExpression") || !depsNode.elements?.length) return;
|
|
35494
|
-
const
|
|
35495
|
-
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));
|
|
35496
35182
|
if (stateLikeDeps.length === 0) return;
|
|
35497
35183
|
if (isRefLatchGuardedEffect(callback.body)) return;
|
|
35498
35184
|
if (hasPreviousValueDep(node, depsNode.elements ?? [])) return;
|
|
35185
|
+
const analysis = getProgramAnalysis(node);
|
|
35499
35186
|
if (analysis) {
|
|
35500
35187
|
const stateLikeDepRefs = [];
|
|
35501
35188
|
for (const element of stateLikeDeps) {
|
|
@@ -35507,9 +35194,9 @@ const noPropCallbackInEffect = defineRule({
|
|
|
35507
35194
|
const reportedNodes = /* @__PURE__ */ new Set();
|
|
35508
35195
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
35509
35196
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
35510
|
-
|
|
35511
|
-
const calleeName =
|
|
35512
|
-
if (!calleeName) return;
|
|
35197
|
+
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
35198
|
+
const calleeName = child.callee.name;
|
|
35199
|
+
if (!propStackTracker.isPropName(calleeName)) return;
|
|
35513
35200
|
if (!isResultDiscardedCall(child)) return;
|
|
35514
35201
|
if (reportedNodes.has(child)) return;
|
|
35515
35202
|
reportedNodes.add(child);
|
|
@@ -39683,18 +39370,34 @@ const resolveSettings$8 = (settings) => {
|
|
|
39683
39370
|
};
|
|
39684
39371
|
const expressionContainsJsxOrCreateElement = (root) => {
|
|
39685
39372
|
let found = false;
|
|
39686
|
-
|
|
39687
|
-
if (found) return
|
|
39688
|
-
if (node !== root && NESTED_FUNCTION_TYPES.has(node.type)) return false;
|
|
39373
|
+
const visit = (node) => {
|
|
39374
|
+
if (found) return;
|
|
39689
39375
|
if (node.type === "JSXElement" || node.type === "JSXFragment") {
|
|
39690
39376
|
found = true;
|
|
39691
|
-
return
|
|
39377
|
+
return;
|
|
39692
39378
|
}
|
|
39693
39379
|
if (isNodeOfType(node, "CallExpression") && isCreateElementCall(node)) {
|
|
39694
39380
|
found = true;
|
|
39695
|
-
return
|
|
39381
|
+
return;
|
|
39696
39382
|
}
|
|
39697
|
-
|
|
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);
|
|
39698
39401
|
return found;
|
|
39699
39402
|
};
|
|
39700
39403
|
const isReactClassComponent = (classNode) => {
|
|
@@ -40620,6 +40323,19 @@ const classifyExport = (name, reportNode, isFunction, initializer, state) => {
|
|
|
40620
40323
|
reportNode
|
|
40621
40324
|
};
|
|
40622
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
|
+
};
|
|
40623
40339
|
const isEntryPointFile = (filename) => {
|
|
40624
40340
|
const lastSlash = Math.max(filename.lastIndexOf("/"), filename.lastIndexOf("\\"));
|
|
40625
40341
|
const basename = lastSlash === -1 ? filename : filename.slice(lastSlash + 1);
|
|
@@ -40658,212 +40374,197 @@ const onlyExportComponents = defineRule({
|
|
|
40658
40374
|
category: "Architecture",
|
|
40659
40375
|
create: (context) => {
|
|
40660
40376
|
const settings = resolveSettings$6(context.settings);
|
|
40661
|
-
|
|
40662
|
-
|
|
40663
|
-
|
|
40664
|
-
|
|
40665
|
-
|
|
40666
|
-
|
|
40667
|
-
|
|
40668
|
-
|
|
40669
|
-
|
|
40670
|
-
|
|
40671
|
-
|
|
40672
|
-
|
|
40673
|
-
|
|
40674
|
-
|
|
40675
|
-
VariableDeclarator: pushComponentCandidate,
|
|
40676
|
-
ClassDeclaration: pushComponentCandidate,
|
|
40677
|
-
"Program:exit"() {
|
|
40678
|
-
const localComponentNames = /* @__PURE__ */ new Set();
|
|
40679
|
-
const state = {
|
|
40680
|
-
customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
|
|
40681
|
-
allowExportNames: new Set(settings.allowExportNames),
|
|
40682
|
-
allowConstantExport: settings.allowConstantExport,
|
|
40683
|
-
localComponentNames,
|
|
40684
|
-
scopes: context.scopes
|
|
40685
|
-
};
|
|
40686
|
-
for (const child of componentCandidates) {
|
|
40687
|
-
if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
|
|
40688
|
-
if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) && functionContainsReactRenderOutput(child, context.scopes)) localComponentNames.add(child.id.name);
|
|
40689
|
-
}
|
|
40690
|
-
if (isNodeOfType(child, "ClassDeclaration") && child.id) {
|
|
40691
|
-
if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
|
|
40692
|
-
}
|
|
40693
|
-
if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
|
|
40694
|
-
const initializer = child.init;
|
|
40695
|
-
if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) {
|
|
40696
|
-
const expression = initializer ? skipTsExpression(initializer) : null;
|
|
40697
|
-
if (!(expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"))) || functionContainsReactRenderOutput(expression, context.scopes)) localComponentNames.add(child.id.name);
|
|
40698
|
-
}
|
|
40699
|
-
}
|
|
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);
|
|
40700
40391
|
}
|
|
40701
|
-
|
|
40702
|
-
|
|
40703
|
-
|
|
40704
|
-
|
|
40705
|
-
|
|
40706
|
-
if (
|
|
40707
|
-
|
|
40708
|
-
|
|
40709
|
-
context.report({
|
|
40710
|
-
node: child,
|
|
40711
|
-
message: EXPORT_ALL_MESSAGE
|
|
40712
|
-
});
|
|
40713
|
-
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);
|
|
40714
40400
|
}
|
|
40715
|
-
|
|
40716
|
-
|
|
40717
|
-
|
|
40718
|
-
|
|
40719
|
-
|
|
40720
|
-
|
|
40721
|
-
|
|
40722
|
-
|
|
40723
|
-
|
|
40724
|
-
|
|
40725
|
-
|
|
40726
|
-
|
|
40727
|
-
|
|
40728
|
-
|
|
40729
|
-
|
|
40730
|
-
|
|
40731
|
-
|
|
40732
|
-
|
|
40733
|
-
|
|
40734
|
-
|
|
40735
|
-
|
|
40736
|
-
|
|
40737
|
-
|
|
40738
|
-
|
|
40739
|
-
|
|
40740
|
-
|
|
40741
|
-
|
|
40742
|
-
} 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({
|
|
40743
40428
|
node: stripped,
|
|
40744
40429
|
message: ANONYMOUS_MESSAGE
|
|
40745
40430
|
});
|
|
40746
|
-
|
|
40747
|
-
}
|
|
40748
|
-
if (isNodeOfType(stripped, "Identifier")) {
|
|
40749
|
-
exports.push(classifyExport(stripped.name, stripped, false, null, state));
|
|
40750
|
-
continue;
|
|
40431
|
+
hasReactExport = true;
|
|
40751
40432
|
}
|
|
40752
|
-
|
|
40753
|
-
|
|
40754
|
-
|
|
40755
|
-
|
|
40756
|
-
|
|
40757
|
-
|
|
40758
|
-
|
|
40759
|
-
|
|
40760
|
-
if (!firstArg) return false;
|
|
40761
|
-
const expression = skipTsExpression(firstArg);
|
|
40762
|
-
if (isNodeOfType(expression, "Identifier")) return true;
|
|
40763
|
-
if (isNodeOfType(expression, "FunctionExpression") && expression.id) return true;
|
|
40764
|
-
if (isNodeOfType(expression, "CallExpression") && isHocCallee(expression.callee, state)) return true;
|
|
40765
|
-
return false;
|
|
40766
|
-
})();
|
|
40767
|
-
if (isHoc && firstArgIsValid) hasReactExport = true;
|
|
40768
|
-
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({
|
|
40769
40441
|
kind: "non-component",
|
|
40770
|
-
reportNode:
|
|
40771
|
-
});
|
|
40772
|
-
else context.report({
|
|
40773
|
-
node: stripped,
|
|
40774
|
-
message: ANONYMOUS_MESSAGE
|
|
40775
|
-
});
|
|
40776
|
-
continue;
|
|
40777
|
-
}
|
|
40778
|
-
if (isNodeOfType(stripped, "ObjectExpression")) {
|
|
40779
|
-
context.report({
|
|
40780
|
-
node: stripped,
|
|
40781
|
-
message: objectExpressionBundlesComponents(stripped, state) ? NAMESPACE_OBJECT_MESSAGE : ANONYMOUS_MESSAGE
|
|
40782
|
-
});
|
|
40783
|
-
continue;
|
|
40784
|
-
}
|
|
40785
|
-
if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "Literal")) {
|
|
40786
|
-
context.report({
|
|
40787
|
-
node: stripped,
|
|
40788
|
-
message: ANONYMOUS_MESSAGE
|
|
40442
|
+
reportNode: idNode
|
|
40789
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;
|
|
40790
40457
|
continue;
|
|
40791
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")) {
|
|
40792
40488
|
context.report({
|
|
40793
|
-
node:
|
|
40489
|
+
node: stripped,
|
|
40794
40490
|
message: ANONYMOUS_MESSAGE
|
|
40795
40491
|
});
|
|
40796
40492
|
continue;
|
|
40797
40493
|
}
|
|
40798
|
-
|
|
40799
|
-
|
|
40800
|
-
|
|
40801
|
-
|
|
40802
|
-
|
|
40803
|
-
|
|
40804
|
-
|
|
40805
|
-
|
|
40806
|
-
|
|
40807
|
-
|
|
40808
|
-
|
|
40809
|
-
|
|
40810
|
-
|
|
40811
|
-
|
|
40812
|
-
|
|
40813
|
-
|
|
40814
|
-
|
|
40815
|
-
|
|
40816
|
-
|
|
40817
|
-
|
|
40818
|
-
}
|
|
40819
|
-
|
|
40820
|
-
|
|
40821
|
-
|
|
40822
|
-
|
|
40823
|
-
|
|
40824
|
-
}
|
|
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));
|
|
40825
40520
|
}
|
|
40826
|
-
|
|
40827
|
-
|
|
40828
|
-
|
|
40829
|
-
|
|
40830
|
-
|
|
40831
|
-
|
|
40832
|
-
|
|
40833
|
-
|
|
40834
|
-
|
|
40835
|
-
|
|
40836
|
-
|
|
40837
|
-
|
|
40838
|
-
|
|
40839
|
-
|
|
40840
|
-
|
|
40841
|
-
|
|
40842
|
-
|
|
40843
|
-
|
|
40844
|
-
|
|
40845
|
-
|
|
40846
|
-
|
|
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" });
|
|
40847
40546
|
}
|
|
40547
|
+
if (isReExportFromSource && entry.kind !== "react-component") continue;
|
|
40548
|
+
exports.push(entry);
|
|
40848
40549
|
}
|
|
40849
40550
|
}
|
|
40850
|
-
|
|
40851
|
-
|
|
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({
|
|
40852
40559
|
node: entry.reportNode,
|
|
40853
|
-
message:
|
|
40560
|
+
message: NAMED_EXPORT_MESSAGE
|
|
40561
|
+
});
|
|
40562
|
+
if (entry.kind === "react-context") context.report({
|
|
40563
|
+
node: entry.reportNode,
|
|
40564
|
+
message: REACT_CONTEXT_MESSAGE
|
|
40854
40565
|
});
|
|
40855
|
-
if (hasAnyExports && hasReactExport) for (const entry of exports) {
|
|
40856
|
-
if (entry.kind === "non-component") context.report({
|
|
40857
|
-
node: entry.reportNode,
|
|
40858
|
-
message: NAMED_EXPORT_MESSAGE
|
|
40859
|
-
});
|
|
40860
|
-
if (entry.kind === "react-context") context.report({
|
|
40861
|
-
node: entry.reportNode,
|
|
40862
|
-
message: REACT_CONTEXT_MESSAGE
|
|
40863
|
-
});
|
|
40864
|
-
}
|
|
40865
40566
|
}
|
|
40866
|
-
};
|
|
40567
|
+
} };
|
|
40867
40568
|
}
|
|
40868
40569
|
});
|
|
40869
40570
|
//#endregion
|
|
@@ -40965,12 +40666,7 @@ const postmessageOriginRisk = defineRule({
|
|
|
40965
40666
|
scan: (file) => {
|
|
40966
40667
|
if (!isProductionSourcePath(file.relativePath)) return [];
|
|
40967
40668
|
if (WORKER_FILE_PATH_PATTERN.test(file.relativePath)) return [];
|
|
40968
|
-
|
|
40969
|
-
const ast = parseSourceText({
|
|
40970
|
-
filename: file.absolutePath,
|
|
40971
|
-
sourceText: file.content,
|
|
40972
|
-
shouldAttachParentReferences: false
|
|
40973
|
-
});
|
|
40669
|
+
const ast = parseSourceText(file.absolutePath, file.content);
|
|
40974
40670
|
if (ast === null) return [];
|
|
40975
40671
|
const findings = [];
|
|
40976
40672
|
walkAst(ast, (node) => {
|
|
@@ -43350,6 +43046,15 @@ const isStableHookWrapperArgument = (node) => {
|
|
|
43350
43046
|
const parent = node.parent;
|
|
43351
43047
|
return isNodeOfType(parent, "CallExpression") && isNodeOfType(parent.callee, "Identifier") && STABLE_HOOK_WRAPPERS.has(parent.callee.name) && Boolean(parent.arguments?.some((argument) => argument === node));
|
|
43352
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
|
+
};
|
|
43353
43058
|
const queryStableQueryClient = defineRule({
|
|
43354
43059
|
id: "query-stable-query-client",
|
|
43355
43060
|
title: "Unstable QueryClient in component",
|
|
@@ -59848,10 +59553,7 @@ const appendNode = (builder, block, node) => {
|
|
|
59848
59553
|
};
|
|
59849
59554
|
const mapDescendantsToBlock = (builder, node, block) => {
|
|
59850
59555
|
builder.nodeBlock.set(node, block);
|
|
59851
|
-
if (isFunctionLike$1(node))
|
|
59852
|
-
builder.nestedFunctions.push(node);
|
|
59853
|
-
return;
|
|
59854
|
-
}
|
|
59556
|
+
if (isFunctionLike$1(node)) return;
|
|
59855
59557
|
const record = node;
|
|
59856
59558
|
for (const key of Object.keys(record)) {
|
|
59857
59559
|
if (key === "parent") continue;
|
|
@@ -60085,12 +59787,11 @@ const buildStatement = (builder, statement, current) => {
|
|
|
60085
59787
|
mapDescendantsToBlock(builder, statement, current);
|
|
60086
59788
|
return current;
|
|
60087
59789
|
};
|
|
60088
|
-
const buildFunctionCfg = (functionNode, body
|
|
59790
|
+
const buildFunctionCfg = (functionNode, body) => {
|
|
60089
59791
|
const builder = {
|
|
60090
59792
|
blocks: [],
|
|
60091
59793
|
entry: null,
|
|
60092
59794
|
exit: null,
|
|
60093
|
-
nestedFunctions: nestedFunctionSink,
|
|
60094
59795
|
nodeBlock: /* @__PURE__ */ new Map(),
|
|
60095
59796
|
loopStack: [],
|
|
60096
59797
|
switchStack: [],
|
|
@@ -60108,12 +59809,13 @@ const buildFunctionCfg = (functionNode, body, nestedFunctionSink) => {
|
|
|
60108
59809
|
bodyEnd = entry;
|
|
60109
59810
|
}
|
|
60110
59811
|
addEdge(bodyEnd, exit, "uncond");
|
|
59812
|
+
const blockOf = (node) => builder.nodeBlock.get(node) ?? null;
|
|
60111
59813
|
return {
|
|
60112
59814
|
owner: functionNode,
|
|
60113
59815
|
entry,
|
|
60114
59816
|
exit,
|
|
60115
59817
|
blocks: builder.blocks,
|
|
60116
|
-
blockOf
|
|
59818
|
+
blockOf
|
|
60117
59819
|
};
|
|
60118
59820
|
};
|
|
60119
59821
|
const computeUnconditionalSet = (cfg) => {
|
|
@@ -60150,9 +59852,8 @@ const computeUnconditionalSet = (cfg) => {
|
|
|
60150
59852
|
const analyzeControlFlow = (program) => {
|
|
60151
59853
|
nextBlockId = 0;
|
|
60152
59854
|
const functionCfgs = /* @__PURE__ */ new Map();
|
|
60153
|
-
const pendingFunctions = [];
|
|
60154
59855
|
const buildFor = (functionNode, body) => {
|
|
60155
|
-
const cfg = buildFunctionCfg(functionNode, body
|
|
59856
|
+
const cfg = buildFunctionCfg(functionNode, body);
|
|
60156
59857
|
functionCfgs.set(functionNode, {
|
|
60157
59858
|
cfg,
|
|
60158
59859
|
unconditionalSet: computeUnconditionalSet(cfg)
|
|
@@ -60162,18 +59863,21 @@ const analyzeControlFlow = (program) => {
|
|
|
60162
59863
|
type: "BlockStatement",
|
|
60163
59864
|
body: program.body
|
|
60164
59865
|
});
|
|
60165
|
-
|
|
60166
|
-
|
|
60167
|
-
|
|
60168
|
-
|
|
60169
|
-
|
|
60170
|
-
|
|
60171
|
-
const
|
|
60172
|
-
|
|
60173
|
-
|
|
60174
|
-
|
|
60175
|
-
|
|
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
|
+
}
|
|
60176
59879
|
};
|
|
59880
|
+
visit(program);
|
|
60177
59881
|
const enclosingFunction = (node) => {
|
|
60178
59882
|
let current = node;
|
|
60179
59883
|
while (current) {
|
|
@@ -60184,12 +59888,12 @@ const analyzeControlFlow = (program) => {
|
|
|
60184
59888
|
return null;
|
|
60185
59889
|
};
|
|
60186
59890
|
const cfgFor = (functionLike) => {
|
|
60187
|
-
return
|
|
59891
|
+
return functionCfgs.get(functionLike)?.cfg ?? null;
|
|
60188
59892
|
};
|
|
60189
59893
|
const isUnconditionalFromEntry = (node) => {
|
|
60190
59894
|
const owner = enclosingFunction(node);
|
|
60191
59895
|
if (!owner) return true;
|
|
60192
|
-
const entry =
|
|
59896
|
+
const entry = functionCfgs.get(owner);
|
|
60193
59897
|
if (!entry) return true;
|
|
60194
59898
|
const block = entry.cfg.blockOf(node);
|
|
60195
59899
|
if (!block) return true;
|
|
@@ -60263,14 +59967,17 @@ const wrapWithSemanticContext = (rule) => ({
|
|
|
60263
59967
|
}
|
|
60264
59968
|
};
|
|
60265
59969
|
const visitors = rule.create(enrichedContext);
|
|
60266
|
-
const
|
|
60267
|
-
|
|
60268
|
-
|
|
60269
|
-
|
|
60270
|
-
|
|
60271
|
-
|
|
60272
|
-
|
|
60273
|
-
|
|
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;
|
|
60274
59981
|
}
|
|
60275
59982
|
});
|
|
60276
59983
|
//#endregion
|