oxlint-plugin-react-doctor 0.7.5-dev.037bd56 → 0.7.5-dev.0f07133
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 +631 -1162
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { KEYS } from "eslint-visitor-keys";
|
|
2
1
|
import * as path from "node:path";
|
|
3
2
|
import * as fs from "node:fs";
|
|
4
3
|
import { readFileSync } from "node:fs";
|
|
5
|
-
import { parseSync
|
|
4
|
+
import { parseSync } from "oxc-parser";
|
|
6
5
|
import { analyze } from "eslint-scope";
|
|
6
|
+
import * as eslintVisitorKeys from "eslint-visitor-keys";
|
|
7
|
+
//#region src/plugin/utils/has-type-property.ts
|
|
8
|
+
const hasTypeProperty = (value) => Boolean(value && typeof value === "object" && "type" in value);
|
|
9
|
+
//#endregion
|
|
7
10
|
//#region src/plugin/utils/is-node-of-type.ts
|
|
8
|
-
const isNodeOfType = (node, type) => node
|
|
11
|
+
const isNodeOfType = (node, type) => Boolean(hasTypeProperty(node) && node.type === type);
|
|
9
12
|
//#endregion
|
|
10
13
|
//#region src/plugin/utils/non-react-jsx-dialect.ts
|
|
11
14
|
const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
|
|
@@ -397,7 +400,6 @@ const isProductionFilePath = (relativePath, sourceFilePattern) => {
|
|
|
397
400
|
//#endregion
|
|
398
401
|
//#region src/plugin/rules/security-scan/utils/is-production-source-path.ts
|
|
399
402
|
const isProductionSourcePath = (relativePath) => {
|
|
400
|
-
if (/\.d\.[cm]?[jt]s$/i.test(relativePath)) return false;
|
|
401
403
|
return isProductionFilePath(relativePath, SOURCE_FILE_PATTERN);
|
|
402
404
|
};
|
|
403
405
|
//#endregion
|
|
@@ -563,8 +565,7 @@ const REACT_RUNTIME_MODULE_SOURCES = new Set([
|
|
|
563
565
|
"react",
|
|
564
566
|
"react-dom",
|
|
565
567
|
"preact/compat",
|
|
566
|
-
"preact/hooks"
|
|
567
|
-
"@wordpress/element"
|
|
568
|
+
"preact/hooks"
|
|
568
569
|
]);
|
|
569
570
|
const REACT_ECOSYSTEM_PACKAGE_NAMES = new Set([
|
|
570
571
|
"next",
|
|
@@ -801,55 +802,24 @@ const isHookCall$2 = (node, hookName) => {
|
|
|
801
802
|
};
|
|
802
803
|
//#endregion
|
|
803
804
|
//#region src/plugin/utils/is-ast-node.ts
|
|
804
|
-
const isAstNode = (value) =>
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
const RUNTIME_VISITOR_KEYS = {
|
|
808
|
-
...KEYS,
|
|
809
|
-
ArrayPattern: ["decorators", ...KEYS.ArrayPattern],
|
|
810
|
-
AssignmentPattern: ["decorators", ...KEYS.AssignmentPattern],
|
|
811
|
-
ClassDeclaration: ["decorators", ...KEYS.ClassDeclaration],
|
|
812
|
-
ClassExpression: ["decorators", ...KEYS.ClassExpression],
|
|
813
|
-
Identifier: ["decorators", ...KEYS.Identifier],
|
|
814
|
-
MethodDefinition: ["decorators", ...KEYS.MethodDefinition],
|
|
815
|
-
ObjectPattern: ["decorators", ...KEYS.ObjectPattern],
|
|
816
|
-
PropertyDefinition: ["decorators", ...KEYS.PropertyDefinition],
|
|
817
|
-
RestElement: ["decorators", ...KEYS.RestElement]
|
|
805
|
+
const isAstNode = (value) => {
|
|
806
|
+
if (!hasTypeProperty(value)) return false;
|
|
807
|
+
return typeof value.type === "string";
|
|
818
808
|
};
|
|
819
809
|
//#endregion
|
|
820
810
|
//#region src/plugin/utils/walk-ast.ts
|
|
821
|
-
const
|
|
811
|
+
const walkAst = (node, visitor) => {
|
|
812
|
+
if (!node || typeof node !== "object") return;
|
|
813
|
+
if (visitor(node) === false) return;
|
|
822
814
|
const nodeRecord = node;
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
for (let keyIndex = 0; keyIndex < childKeys.length; keyIndex += 1) {
|
|
826
|
-
const child = nodeRecord[childKeys[keyIndex]];
|
|
827
|
-
if (Array.isArray(child)) for (let itemIndex = 0; itemIndex < child.length; itemIndex += 1) {
|
|
828
|
-
const item = child[itemIndex];
|
|
829
|
-
if (isAstNode(item)) visit(item);
|
|
830
|
-
}
|
|
831
|
-
else if (isAstNode(child)) visit(child);
|
|
832
|
-
}
|
|
833
|
-
return;
|
|
834
|
-
}
|
|
835
|
-
for (const key in nodeRecord) {
|
|
836
|
-
if (key === "parent" || !Object.hasOwn(nodeRecord, key)) continue;
|
|
815
|
+
for (const key of Object.keys(nodeRecord)) {
|
|
816
|
+
if (key === "parent") continue;
|
|
837
817
|
const child = nodeRecord[key];
|
|
838
|
-
if (Array.isArray(child))
|
|
839
|
-
const item
|
|
840
|
-
|
|
841
|
-
}
|
|
842
|
-
else if (isAstNode(child)) visit(child);
|
|
818
|
+
if (Array.isArray(child)) {
|
|
819
|
+
for (const item of child) if (isAstNode(item)) walkAst(item, visitor);
|
|
820
|
+
} else if (isAstNode(child)) walkAst(child, visitor);
|
|
843
821
|
}
|
|
844
822
|
};
|
|
845
|
-
const walkAst = (node, visitor) => {
|
|
846
|
-
if (!node || typeof node !== "object") return;
|
|
847
|
-
const visitNode = (current) => {
|
|
848
|
-
if (visitor(current) === false) return;
|
|
849
|
-
forEachChildNode(current, visitNode);
|
|
850
|
-
};
|
|
851
|
-
visitNode(node);
|
|
852
|
-
};
|
|
853
823
|
//#endregion
|
|
854
824
|
//#region src/plugin/rules/state-and-effects/activity-wraps-effect-heavy-subtree.ts
|
|
855
825
|
const ACTIVITY_IMPORTED_NAMES = new Set(["Activity", "unstable_Activity"]);
|
|
@@ -1292,7 +1262,7 @@ const REGEX_PRECEDING_KEYWORDS = new Set([
|
|
|
1292
1262
|
"await",
|
|
1293
1263
|
"throw"
|
|
1294
1264
|
]);
|
|
1295
|
-
const isRegexLiteralStart = (
|
|
1265
|
+
const isRegexLiteralStart = (characters, slashIndex) => {
|
|
1296
1266
|
let cursor = slashIndex - 1;
|
|
1297
1267
|
while (cursor >= 0 && WHITESPACE_PATTERN.test(characters[cursor])) cursor -= 1;
|
|
1298
1268
|
if (cursor < 0) return true;
|
|
@@ -1302,7 +1272,7 @@ const isRegexLiteralStart = (content, characters, slashIndex) => {
|
|
|
1302
1272
|
let wordStartIndex = cursor;
|
|
1303
1273
|
while (wordStartIndex > 0 && IDENTIFIER_CHARACTER_PATTERN.test(characters[wordStartIndex - 1])) wordStartIndex -= 1;
|
|
1304
1274
|
if (wordStartIndex > 0 && characters[wordStartIndex - 1] === ".") return false;
|
|
1305
|
-
return REGEX_PRECEDING_KEYWORDS.has(
|
|
1275
|
+
return REGEX_PRECEDING_KEYWORDS.has(characters.slice(wordStartIndex, cursor + 1).join(""));
|
|
1306
1276
|
}
|
|
1307
1277
|
if (previousCharacter === "<") return false;
|
|
1308
1278
|
if (previousCharacter === ">") return characterBefore === "=";
|
|
@@ -1343,15 +1313,13 @@ const quotedLiteralHasWhitespace = (content, openQuoteIndex, delimiter) => {
|
|
|
1343
1313
|
return false;
|
|
1344
1314
|
};
|
|
1345
1315
|
const blankNonCodePreservingPositions = (content, blankStringContents) => {
|
|
1346
|
-
|
|
1316
|
+
const characters = content.split("");
|
|
1347
1317
|
let stringDelimiter = null;
|
|
1348
1318
|
let isBlankingString = false;
|
|
1349
1319
|
const templateExpressionDepths = [];
|
|
1350
1320
|
let index = 0;
|
|
1351
1321
|
const blankUnlessNewline = (offset) => {
|
|
1352
|
-
if (offset
|
|
1353
|
-
characters ??= content.split("");
|
|
1354
|
-
characters[offset] = " ";
|
|
1322
|
+
if (offset < content.length && content[offset] !== "\n") characters[offset] = " ";
|
|
1355
1323
|
};
|
|
1356
1324
|
while (index < content.length) {
|
|
1357
1325
|
const character = content[index];
|
|
@@ -1398,7 +1366,6 @@ const blankNonCodePreservingPositions = (content, blankStringContents) => {
|
|
|
1398
1366
|
continue;
|
|
1399
1367
|
}
|
|
1400
1368
|
if (character === "/" && nextCharacter === "/") {
|
|
1401
|
-
characters ??= content.split("");
|
|
1402
1369
|
while (index < content.length && content[index] !== "\n") {
|
|
1403
1370
|
characters[index] = " ";
|
|
1404
1371
|
index += 1;
|
|
@@ -1406,7 +1373,6 @@ const blankNonCodePreservingPositions = (content, blankStringContents) => {
|
|
|
1406
1373
|
continue;
|
|
1407
1374
|
}
|
|
1408
1375
|
if (character === "/" && nextCharacter === "*") {
|
|
1409
|
-
characters ??= content.split("");
|
|
1410
1376
|
while (index < content.length) {
|
|
1411
1377
|
if (content[index] === "*" && content[index + 1] === "/") {
|
|
1412
1378
|
characters[index] = " ";
|
|
@@ -1420,7 +1386,7 @@ const blankNonCodePreservingPositions = (content, blankStringContents) => {
|
|
|
1420
1386
|
continue;
|
|
1421
1387
|
}
|
|
1422
1388
|
if (character === "/") {
|
|
1423
|
-
const regexEndIndex = isRegexLiteralStart(
|
|
1389
|
+
const regexEndIndex = isRegexLiteralStart(characters, index) ? findRegexLiteralEnd(content, index) : null;
|
|
1424
1390
|
if (regexEndIndex !== null) {
|
|
1425
1391
|
if (blankStringContents) for (let interiorIndex = index + 1; interiorIndex < regexEndIndex - 1; interiorIndex += 1) blankUnlessNewline(interiorIndex);
|
|
1426
1392
|
index = regexEndIndex;
|
|
@@ -1438,9 +1404,9 @@ const blankNonCodePreservingPositions = (content, blankStringContents) => {
|
|
|
1438
1404
|
}
|
|
1439
1405
|
index += 1;
|
|
1440
1406
|
}
|
|
1441
|
-
return characters
|
|
1407
|
+
return characters.join("");
|
|
1442
1408
|
};
|
|
1443
|
-
const stripCommentsPreservingPositions = (content) =>
|
|
1409
|
+
const stripCommentsPreservingPositions = (content) => blankNonCodePreservingPositions(content, false);
|
|
1444
1410
|
const stripCommentsAndStringLiteralsPreservingPositions = (content) => blankNonCodePreservingPositions(content, true);
|
|
1445
1411
|
//#endregion
|
|
1446
1412
|
//#region src/plugin/rules/security-scan/utils/scan-by-pattern.ts
|
|
@@ -1459,15 +1425,10 @@ const scanByPattern = ({ shouldScan, pattern, requireAll, suppressWhen, ignoreSt
|
|
|
1459
1425
|
if (!shouldScan(file)) return [];
|
|
1460
1426
|
const content = getScannableContent(file, ignoreStringLiterals);
|
|
1461
1427
|
if (requireAll !== void 0 && !requireAll.every((gate) => gate.test(content))) return [];
|
|
1462
|
-
|
|
1463
|
-
if (
|
|
1464
|
-
else for (let patternIndex = 0; patternIndex < pattern.length; patternIndex += 1) {
|
|
1465
|
-
matchIndex = content.search(pattern[patternIndex]);
|
|
1466
|
-
if (matchIndex >= 0) break;
|
|
1467
|
-
}
|
|
1468
|
-
if (matchIndex < 0) return [];
|
|
1428
|
+
const matchedPattern = (pattern instanceof RegExp ? [pattern] : pattern).find((candidate) => candidate.test(content));
|
|
1429
|
+
if (matchedPattern === void 0) return [];
|
|
1469
1430
|
if (suppressWhen !== void 0 && suppressWhen.test(content)) return [];
|
|
1470
|
-
const { line, column } =
|
|
1431
|
+
const { line, column } = getMatchLocation(content, matchedPattern);
|
|
1471
1432
|
return [{
|
|
1472
1433
|
message,
|
|
1473
1434
|
line,
|
|
@@ -1477,7 +1438,6 @@ const scanByPattern = ({ shouldScan, pattern, requireAll, suppressWhen, ignoreSt
|
|
|
1477
1438
|
//#endregion
|
|
1478
1439
|
//#region src/plugin/rules/security-scan/agent-tool-capability-risk.ts
|
|
1479
1440
|
const AGENT_TOOL_DEFINITION_PATTERN = /\b(?:tool\s*\(\s*\{|createTool\s*\(|defineTool\s*\(|new\s+(?:DynamicTool|StructuredTool)\s*\()/;
|
|
1480
|
-
const AGENT_TOOL_DEFINITION_PREFILTER_PATTERN = /\b(?:tool|createTool|defineTool|DynamicTool|StructuredTool)\b/;
|
|
1481
1441
|
const AGENT_TOOL_CONTEXT_PATH_PATTERN = /(?:^|\/)(?:agents?|tools?|mcp)(?:\/|$)|(?:agent|tool|mcp)[^/]*\.[cm]?[jt]sx?$/i;
|
|
1482
1442
|
const agentToolCapabilityRisk = defineRule({
|
|
1483
1443
|
id: "agent-tool-capability-risk",
|
|
@@ -1485,7 +1445,7 @@ const agentToolCapabilityRisk = defineRule({
|
|
|
1485
1445
|
severity: "warn",
|
|
1486
1446
|
recommendation: "Treat tool inputs as prompt-injection controlled. Validate arguments, scope permissions per call, and avoid exposing shell/file/network primitives directly to agents.",
|
|
1487
1447
|
scan: scanByPattern({
|
|
1488
|
-
shouldScan: (file) => isProductionSourcePath(file.relativePath) && AGENT_TOOL_CONTEXT_PATH_PATTERN.test(file.relativePath)
|
|
1448
|
+
shouldScan: (file) => isProductionSourcePath(file.relativePath) && AGENT_TOOL_CONTEXT_PATH_PATTERN.test(file.relativePath),
|
|
1489
1449
|
pattern: AGENT_TOOL_DEFINITION_PATTERN,
|
|
1490
1450
|
requireAll: [AGENT_TOOL_DANGEROUS_CAPABILITY_PATTERN],
|
|
1491
1451
|
ignoreStringLiterals: true,
|
|
@@ -5179,10 +5139,8 @@ const STORAGE_GLOBALS = new Set([
|
|
|
5179
5139
|
const SENSITIVE_KEY_PATTERN = /token|jwt|secret|password|passwd|credential|api[-_]?key|bearer|private[-_]?key/i;
|
|
5180
5140
|
const NON_AUTH_TOKEN_PATTERN = /csrf|xsrf|device|fcm|apns|push|design|tokeniz|syntax|css|theme|color/i;
|
|
5181
5141
|
const STRONG_AUTH_KEY_PATTERN = /jwt|secret|password|passwd|credential|private[-_]?key|api[-_]?key|bearer|access[-_]?token|refresh[-_]?token|auth[-_]?token|id[-_]?token|session/i;
|
|
5182
|
-
const PRODUCT_API_KEY_RECORDS_PATTERN = /(?:^|[._:-])(?:created|saved|integration|mailing)[-_]?api[-_]?keys$/i;
|
|
5183
5142
|
const PRODUCT_API_KEY_COLLECTION_PATTERN = /[._:-](?:created|generated|saved)[-_]?api[-_]?keys$/i;
|
|
5184
5143
|
const isAuthCredentialKey = (key) => {
|
|
5185
|
-
if (PRODUCT_API_KEY_RECORDS_PATTERN.test(key)) return false;
|
|
5186
5144
|
if (!SENSITIVE_KEY_PATTERN.test(key)) return false;
|
|
5187
5145
|
if (PRODUCT_API_KEY_COLLECTION_PATTERN.test(key)) return false;
|
|
5188
5146
|
if (NON_AUTH_TOKEN_PATTERN.test(key) && !STRONG_AUTH_KEY_PATTERN.test(key)) return false;
|
|
@@ -5934,21 +5892,6 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
5934
5892
|
}
|
|
5935
5893
|
});
|
|
5936
5894
|
//#endregion
|
|
5937
|
-
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
5938
|
-
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
5939
|
-
if (!isNodeOfType(node, "Property")) return null;
|
|
5940
|
-
if (node.computed) {
|
|
5941
|
-
if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
|
|
5942
|
-
return null;
|
|
5943
|
-
}
|
|
5944
|
-
if (isNodeOfType(node.key, "Identifier")) return node.key.name;
|
|
5945
|
-
if (isNodeOfType(node.key, "Literal")) {
|
|
5946
|
-
if (typeof node.key.value === "string") return node.key.value;
|
|
5947
|
-
if (options.stringifyNonStringLiterals) return String(node.key.value);
|
|
5948
|
-
}
|
|
5949
|
-
return null;
|
|
5950
|
-
};
|
|
5951
|
-
//#endregion
|
|
5952
5895
|
//#region src/plugin/utils/is-presentation-role.ts
|
|
5953
5896
|
const isPresentationRole = (openingElement) => {
|
|
5954
5897
|
const roleAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "role");
|
|
@@ -5993,21 +5936,6 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
5993
5936
|
return false;
|
|
5994
5937
|
};
|
|
5995
5938
|
//#endregion
|
|
5996
|
-
//#region src/plugin/utils/resolve-const-identifier-alias.ts
|
|
5997
|
-
const resolveConstIdentifierAlias = (identifier, scopes) => {
|
|
5998
|
-
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
5999
|
-
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
6000
|
-
let symbol = scopes.symbolFor(identifier);
|
|
6001
|
-
while (symbol?.kind === "const") {
|
|
6002
|
-
if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
6003
|
-
visitedSymbolIds.add(symbol.id);
|
|
6004
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
6005
|
-
if (!isNodeOfType(initializer, "Identifier")) return symbol;
|
|
6006
|
-
symbol = scopes.symbolFor(initializer);
|
|
6007
|
-
}
|
|
6008
|
-
return symbol;
|
|
6009
|
-
};
|
|
6010
|
-
//#endregion
|
|
6011
5939
|
//#region src/plugin/rules/a11y/click-events-have-key-events.ts
|
|
6012
5940
|
const MESSAGE$57 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
|
|
6013
5941
|
const KEY_HANDLERS = [
|
|
@@ -6018,63 +5946,6 @@ const KEY_HANDLERS = [
|
|
|
6018
5946
|
"onKeyDownCapture",
|
|
6019
5947
|
"onKeyPressCapture"
|
|
6020
5948
|
];
|
|
6021
|
-
const CLICK_HANDLERS = ["onClick", "onClickCapture"];
|
|
6022
|
-
const TRANSPARENT_SPREAD_EVENT_NAMES = new Set([...CLICK_HANDLERS, ...KEY_HANDLERS].map((eventName) => eventName.toLowerCase()));
|
|
6023
|
-
const CONSERVATIVE_SPREAD_PROP_NAMES = new Set([
|
|
6024
|
-
"aria-hidden",
|
|
6025
|
-
"onmouseenter",
|
|
6026
|
-
"onmouseover",
|
|
6027
|
-
"role"
|
|
6028
|
-
]);
|
|
6029
|
-
const resolveSpreadObjectExpression = (expression, scopes) => {
|
|
6030
|
-
const innerExpression = stripParenExpression(expression);
|
|
6031
|
-
if (isNodeOfType(innerExpression, "ObjectExpression")) return innerExpression;
|
|
6032
|
-
if (!isNodeOfType(innerExpression, "Identifier")) return null;
|
|
6033
|
-
const symbol = resolveConstIdentifierAlias(innerExpression, scopes);
|
|
6034
|
-
if (symbol?.kind !== "const" || !symbol.initializer) return null;
|
|
6035
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
6036
|
-
return isNodeOfType(initializer, "ObjectExpression") ? initializer : null;
|
|
6037
|
-
};
|
|
6038
|
-
const collectTransparentSpreadEventNames = (expression, scopes, eventValues, visitedObjectExpressions) => {
|
|
6039
|
-
const objectExpression = resolveSpreadObjectExpression(expression, scopes);
|
|
6040
|
-
if (!objectExpression || visitedObjectExpressions.has(objectExpression)) return false;
|
|
6041
|
-
visitedObjectExpressions.add(objectExpression);
|
|
6042
|
-
let isTransparent = true;
|
|
6043
|
-
for (const property of objectExpression.properties) {
|
|
6044
|
-
if (isNodeOfType(property, "SpreadElement")) {
|
|
6045
|
-
if (!collectTransparentSpreadEventNames(property.argument, scopes, eventValues, visitedObjectExpressions)) {
|
|
6046
|
-
isTransparent = false;
|
|
6047
|
-
break;
|
|
6048
|
-
}
|
|
6049
|
-
continue;
|
|
6050
|
-
}
|
|
6051
|
-
if (!isNodeOfType(property, "Property")) {
|
|
6052
|
-
isTransparent = false;
|
|
6053
|
-
break;
|
|
6054
|
-
}
|
|
6055
|
-
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
6056
|
-
if (!propertyName) {
|
|
6057
|
-
isTransparent = false;
|
|
6058
|
-
break;
|
|
6059
|
-
}
|
|
6060
|
-
const normalizedPropertyName = propertyName.toLowerCase();
|
|
6061
|
-
if (CONSERVATIVE_SPREAD_PROP_NAMES.has(normalizedPropertyName)) {
|
|
6062
|
-
isTransparent = false;
|
|
6063
|
-
break;
|
|
6064
|
-
}
|
|
6065
|
-
if (TRANSPARENT_SPREAD_EVENT_NAMES.has(normalizedPropertyName)) eventValues.set(normalizedPropertyName, property.value);
|
|
6066
|
-
}
|
|
6067
|
-
visitedObjectExpressions.delete(objectExpression);
|
|
6068
|
-
return isTransparent;
|
|
6069
|
-
};
|
|
6070
|
-
const getTransparentSpreadEventValues = (attributes, scopes) => {
|
|
6071
|
-
const eventValues = /* @__PURE__ */ new Map();
|
|
6072
|
-
for (const attribute of attributes) {
|
|
6073
|
-
if (!isNodeOfType(attribute, "JSXSpreadAttribute")) continue;
|
|
6074
|
-
if (!collectTransparentSpreadEventNames(attribute.argument, scopes, eventValues, /* @__PURE__ */ new Set())) return null;
|
|
6075
|
-
}
|
|
6076
|
-
return eventValues;
|
|
6077
|
-
};
|
|
6078
5949
|
const FOCUSLESS_CONTAINER_TAGS = new Set([
|
|
6079
5950
|
"tr",
|
|
6080
5951
|
"td",
|
|
@@ -6122,10 +5993,7 @@ const isFocusForwardingFunctionBody = (body) => {
|
|
|
6122
5993
|
};
|
|
6123
5994
|
const resolveHandlerFunction = (attribute) => {
|
|
6124
5995
|
if (!attribute.value || !isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
|
|
6125
|
-
|
|
6126
|
-
};
|
|
6127
|
-
const resolveHandlerFunctionExpression = (handlerExpression) => {
|
|
6128
|
-
let expression = handlerExpression;
|
|
5996
|
+
let expression = attribute.value.expression;
|
|
6129
5997
|
if (isNodeOfType(expression, "Identifier")) {
|
|
6130
5998
|
const binding = findVariableInitializer(expression, expression.name);
|
|
6131
5999
|
if (!binding?.initializer) return null;
|
|
@@ -6224,22 +6092,18 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
6224
6092
|
if (!HTML_TAGS.has(tag)) return;
|
|
6225
6093
|
if (tag === "label") return;
|
|
6226
6094
|
if (!FOCUSLESS_CONTAINER_TAGS.has(tag) && isInteractiveElement(tag, node)) return;
|
|
6227
|
-
const spreadEventValues = getTransparentSpreadEventValues(node.attributes, context.scopes);
|
|
6228
|
-
if (!spreadEventValues) return;
|
|
6229
6095
|
const onClick = hasJsxPropIgnoreCase(node.attributes, "onClick") ?? hasJsxPropIgnoreCase(node.attributes, "onClickCapture");
|
|
6230
|
-
|
|
6231
|
-
if (
|
|
6232
|
-
if (
|
|
6233
|
-
if (
|
|
6234
|
-
const spreadHandlerFunction = spreadOnClickExpression ? resolveHandlerFunctionExpression(spreadOnClickExpression) : null;
|
|
6235
|
-
if (spreadHandlerFunction && (isFocusForwardingFunctionBody(spreadHandlerFunction.body ?? null) || containsBackdropDismissComparison(spreadHandlerFunction.body ?? null))) return;
|
|
6096
|
+
if (!onClick) return;
|
|
6097
|
+
if (isPureEventBlockerHandler(onClick)) return;
|
|
6098
|
+
if (isFocusForwardingHandler(onClick)) return;
|
|
6099
|
+
if (hasJsxSpreadAttribute$1(node.attributes)) return;
|
|
6236
6100
|
if (hasCompositeItemRole(node)) return;
|
|
6237
6101
|
if (isHoverSelectionListItem(tag, node)) return;
|
|
6238
|
-
if (
|
|
6102
|
+
if (isBackdropDismissHandler(onClick)) return;
|
|
6239
6103
|
if (containsKeyboardActivatableDescendant(node.parent)) return;
|
|
6240
6104
|
if (isHiddenFromScreenReader(node, context.settings)) return;
|
|
6241
6105
|
if (isPresentationRole(node)) return;
|
|
6242
|
-
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler)
|
|
6106
|
+
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
|
|
6243
6107
|
context.report({
|
|
6244
6108
|
node: node.name,
|
|
6245
6109
|
message: MESSAGE$57
|
|
@@ -6965,10 +6829,11 @@ const getTemplateInterpolations = (valueTail) => {
|
|
|
6965
6829
|
const interpolations = valueTail.slice(1, closingBacktickIndex).match(/\$\{[^}]*\}/g);
|
|
6966
6830
|
return interpolations === null ? "" : interpolations.join(" ");
|
|
6967
6831
|
};
|
|
6968
|
-
const getIdentifierDeclarationInitializer = (identifier,
|
|
6969
|
-
const
|
|
6970
|
-
if (
|
|
6971
|
-
|
|
6832
|
+
const getIdentifierDeclarationInitializer = (identifier, fileContent) => {
|
|
6833
|
+
const declarationMatch = new RegExp(`(?:const|let|var)\\s+${escapeRegExp(identifier)}\\s*(?::[^=\\n]{0,120})?=\\s*`).exec(fileContent);
|
|
6834
|
+
if (declarationMatch === null) return null;
|
|
6835
|
+
const initializerStartIndex = declarationMatch.index + declarationMatch[0].length;
|
|
6836
|
+
return fileContent.slice(initializerStartIndex, initializerStartIndex + STATIC_TEMPLATE_MAX_CHARS);
|
|
6972
6837
|
};
|
|
6973
6838
|
const isPureStringLiteralConcat = (initializerText) => {
|
|
6974
6839
|
if (!/^["']/.test(initializerText)) return false;
|
|
@@ -7038,153 +6903,6 @@ const isAllOperandsDomContentConcat = (valueExpression) => {
|
|
|
7038
6903
|
return !HTML_TAINT_PATTERN.test(withoutCast.replace(DOM_CONTENT_SOURCE_VALUE_PATTERN, ""));
|
|
7039
6904
|
});
|
|
7040
6905
|
};
|
|
7041
|
-
const findMatchingBraceIndex = (fileContent, openingBraceIndex) => {
|
|
7042
|
-
let depth = 0;
|
|
7043
|
-
let quote = null;
|
|
7044
|
-
for (let index = openingBraceIndex; index < fileContent.length; index += 1) {
|
|
7045
|
-
const character = fileContent[index];
|
|
7046
|
-
if (quote !== null) {
|
|
7047
|
-
if (character === quote && fileContent[index - 1] !== "\\") quote = null;
|
|
7048
|
-
continue;
|
|
7049
|
-
}
|
|
7050
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
7051
|
-
quote = character;
|
|
7052
|
-
continue;
|
|
7053
|
-
}
|
|
7054
|
-
if (character === "{") depth += 1;
|
|
7055
|
-
if (character === "}") {
|
|
7056
|
-
depth -= 1;
|
|
7057
|
-
if (depth === 0) return index;
|
|
7058
|
-
}
|
|
7059
|
-
}
|
|
7060
|
-
return fileContent.length;
|
|
7061
|
-
};
|
|
7062
|
-
const findContainingBlockEndIndex = (fileContent, targetIndex) => {
|
|
7063
|
-
const openingBraceIndexes = [];
|
|
7064
|
-
let quote = null;
|
|
7065
|
-
let isLineComment = false;
|
|
7066
|
-
let isBlockComment = false;
|
|
7067
|
-
for (let index = 0; index < targetIndex; index += 1) {
|
|
7068
|
-
const character = fileContent[index];
|
|
7069
|
-
const nextCharacter = fileContent[index + 1];
|
|
7070
|
-
if (isLineComment) {
|
|
7071
|
-
if (character === "\n") isLineComment = false;
|
|
7072
|
-
continue;
|
|
7073
|
-
}
|
|
7074
|
-
if (isBlockComment) {
|
|
7075
|
-
if (character === "*" && nextCharacter === "/") {
|
|
7076
|
-
isBlockComment = false;
|
|
7077
|
-
index += 1;
|
|
7078
|
-
}
|
|
7079
|
-
continue;
|
|
7080
|
-
}
|
|
7081
|
-
if (quote !== null) {
|
|
7082
|
-
if (character === quote && fileContent[index - 1] !== "\\") quote = null;
|
|
7083
|
-
continue;
|
|
7084
|
-
}
|
|
7085
|
-
if (character === "/" && nextCharacter === "/") {
|
|
7086
|
-
isLineComment = true;
|
|
7087
|
-
index += 1;
|
|
7088
|
-
continue;
|
|
7089
|
-
}
|
|
7090
|
-
if (character === "/" && nextCharacter === "*") {
|
|
7091
|
-
isBlockComment = true;
|
|
7092
|
-
index += 1;
|
|
7093
|
-
continue;
|
|
7094
|
-
}
|
|
7095
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
7096
|
-
quote = character;
|
|
7097
|
-
continue;
|
|
7098
|
-
}
|
|
7099
|
-
if (character === "{") openingBraceIndexes.push(index);
|
|
7100
|
-
if (character === "}") openingBraceIndexes.pop();
|
|
7101
|
-
}
|
|
7102
|
-
const openingBraceIndex = openingBraceIndexes.at(-1);
|
|
7103
|
-
return openingBraceIndex === void 0 ? fileContent.length : findMatchingBraceIndex(fileContent, openingBraceIndex);
|
|
7104
|
-
};
|
|
7105
|
-
const findVisibleIdentifierDeclaration = (identifier, sinkIndex, fileContent) => {
|
|
7106
|
-
const initializerPattern = new RegExp(`(?:const|let|var)\\s+${escapeRegExp(identifier)}\\s*(?::[^=\\n]*)?=\\s*([^;\\n]+)`, "g");
|
|
7107
|
-
let nearestDeclaration = null;
|
|
7108
|
-
let nearestDeclarationIndex = -1;
|
|
7109
|
-
for (const match of fileContent.matchAll(initializerPattern)) {
|
|
7110
|
-
const declarationIndex = match.index;
|
|
7111
|
-
if (declarationIndex === void 0 || declarationIndex >= sinkIndex || declarationIndex <= nearestDeclarationIndex || findContainingBlockEndIndex(fileContent, declarationIndex) < sinkIndex) continue;
|
|
7112
|
-
nearestDeclarationIndex = declarationIndex;
|
|
7113
|
-
const initializer = match[1];
|
|
7114
|
-
if (initializer === void 0) continue;
|
|
7115
|
-
nearestDeclaration = {
|
|
7116
|
-
initializer,
|
|
7117
|
-
initializerStartIndex: declarationIndex + match[0].length - initializer.length
|
|
7118
|
-
};
|
|
7119
|
-
}
|
|
7120
|
-
return nearestDeclaration;
|
|
7121
|
-
};
|
|
7122
|
-
const findContainingFunctionParameterSource = (identifier, sinkIndex, fileContent) => {
|
|
7123
|
-
const patterns = [/function\s+([\w$]+)\s*\(([^)]*)\)\s*\{/g, /(?:const|let|var)\s+([\w$]+)\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>\s*\{/g];
|
|
7124
|
-
let closestSource = null;
|
|
7125
|
-
let closestStartIndex = -1;
|
|
7126
|
-
for (const pattern of patterns) for (const match of fileContent.matchAll(pattern)) {
|
|
7127
|
-
const matchIndex = match.index;
|
|
7128
|
-
if (matchIndex === void 0 || matchIndex >= sinkIndex || matchIndex < closestStartIndex) continue;
|
|
7129
|
-
if (findMatchingBraceIndex(fileContent, matchIndex + match[0].lastIndexOf("{")) < sinkIndex) continue;
|
|
7130
|
-
const parameterIndex = (match[2] ?? "").split(",").findIndex((parameter) => parameter.trim().match(/^(?:\.\.\.)?([\w$]+)/)?.[1] === identifier);
|
|
7131
|
-
if (parameterIndex < 0) continue;
|
|
7132
|
-
closestStartIndex = matchIndex;
|
|
7133
|
-
closestSource = {
|
|
7134
|
-
functionName: match[1] ?? "",
|
|
7135
|
-
parameterIndex
|
|
7136
|
-
};
|
|
7137
|
-
}
|
|
7138
|
-
return closestSource;
|
|
7139
|
-
};
|
|
7140
|
-
const splitTopLevelArguments = (argumentText) => {
|
|
7141
|
-
const argumentsList = [];
|
|
7142
|
-
let startIndex = 0;
|
|
7143
|
-
let depth = 0;
|
|
7144
|
-
let quote = null;
|
|
7145
|
-
for (let index = 0; index < argumentText.length; index += 1) {
|
|
7146
|
-
const character = argumentText[index];
|
|
7147
|
-
if (quote !== null) {
|
|
7148
|
-
if (character === quote && argumentText[index - 1] !== "\\") quote = null;
|
|
7149
|
-
continue;
|
|
7150
|
-
}
|
|
7151
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
7152
|
-
quote = character;
|
|
7153
|
-
continue;
|
|
7154
|
-
}
|
|
7155
|
-
if (character === "(" || character === "[" || character === "{") depth += 1;
|
|
7156
|
-
if (character === ")" || character === "]" || character === "}") depth -= 1;
|
|
7157
|
-
if (character === "," && depth === 0) {
|
|
7158
|
-
argumentsList.push(argumentText.slice(startIndex, index).trim());
|
|
7159
|
-
startIndex = index + 1;
|
|
7160
|
-
}
|
|
7161
|
-
}
|
|
7162
|
-
argumentsList.push(argumentText.slice(startIndex).trim());
|
|
7163
|
-
return argumentsList;
|
|
7164
|
-
};
|
|
7165
|
-
const isHtmlTainted = (expression, fileContent, sinkIndex, visitedIdentifiers) => {
|
|
7166
|
-
const trimmedExpression = expression.trim();
|
|
7167
|
-
if (STRING_LITERAL_VALUE_PATTERN.test(`${trimmedExpression};`)) return false;
|
|
7168
|
-
if (MODULE_CONSTANT_VALUE_PATTERN.test(`${trimmedExpression};`)) return false;
|
|
7169
|
-
if (SANITIZER_PATTERN.test(trimmedExpression)) return false;
|
|
7170
|
-
if (ENV_CONFIG_VALUE_PATTERN.test(trimmedExpression)) return false;
|
|
7171
|
-
if (I18N_VALUE_PATTERN.test(trimmedExpression)) return false;
|
|
7172
|
-
if (ESCAPING_SERIALIZER_CALL_PATTERN.test(trimmedExpression)) return false;
|
|
7173
|
-
if (HTML_TAINT_PATTERN.test(trimmedExpression)) return true;
|
|
7174
|
-
const identifier = trimmedExpression.match(/^([\w$]+)\s*(?:[;,})\n]|$)/)?.[1];
|
|
7175
|
-
if (identifier === void 0 || visitedIdentifiers.has(identifier)) return false;
|
|
7176
|
-
visitedIdentifiers.add(identifier);
|
|
7177
|
-
const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
|
|
7178
|
-
if (declaration !== null && isHtmlTainted(declaration.initializer, fileContent, sinkIndex, visitedIdentifiers)) return true;
|
|
7179
|
-
const parameterSource = findContainingFunctionParameterSource(identifier, sinkIndex, fileContent);
|
|
7180
|
-
if (parameterSource === null || parameterSource.functionName.length === 0) return false;
|
|
7181
|
-
const callPattern = new RegExp(`\\b${escapeRegExp(parameterSource.functionName)}\\s*\\(([^)]*)\\)`, "g");
|
|
7182
|
-
for (const callMatch of fileContent.matchAll(callPattern)) {
|
|
7183
|
-
const argument = splitTopLevelArguments(callMatch[1] ?? "")[parameterSource.parameterIndex];
|
|
7184
|
-
if (argument !== void 0 && isHtmlTainted(argument, fileContent, sinkIndex, new Set(visitedIdentifiers))) return true;
|
|
7185
|
-
}
|
|
7186
|
-
return false;
|
|
7187
|
-
};
|
|
7188
6906
|
const dangerousHtmlSink = defineRule({
|
|
7189
6907
|
id: "dangerous-html-sink",
|
|
7190
6908
|
title: "HTML injection sink with dynamic content",
|
|
@@ -7211,7 +6929,6 @@ const dangerousHtmlSink = defineRule({
|
|
|
7211
6929
|
const valueTail = fullValueTail.slice(0, VALUE_EXPRESSION_MAX_CHARS);
|
|
7212
6930
|
const terminatorIndex = valueTail.search(/[;}]/);
|
|
7213
6931
|
const valueExpression = terminatorIndex >= 0 ? valueTail.slice(0, terminatorIndex + 1) : valueTail;
|
|
7214
|
-
const sinkIndex = lines.slice(0, lineIndex).join("\n").length + (lineIndex > 0 ? 1 : 0) + line.search(DANGEROUS_HTML_PATTERN);
|
|
7215
6932
|
if (STRING_LITERAL_VALUE_PATTERN.test(valueExpression)) continue;
|
|
7216
6933
|
if (MODULE_CONSTANT_VALUE_PATTERN.test(valueExpression)) continue;
|
|
7217
6934
|
if (DOM_CONTENT_SOURCE_VALUE_PATTERN.test(valueExpression) && !valueExpression.includes("+")) {
|
|
@@ -7223,7 +6940,7 @@ const dangerousHtmlSink = defineRule({
|
|
|
7223
6940
|
let templateInterpolations = getTemplateInterpolations(longValueTail ?? fullValueTail);
|
|
7224
6941
|
const valueIdentifier = BARE_IDENTIFIER_VALUE_PATTERN.test(valueExpression) || MEMBER_OR_INDEX_ACCESS_VALUE_PATTERN.test(valueExpression) || IDENTIFIER_WITH_LITERAL_FALLBACK_PATTERN.test(valueExpression) ? valueExpression.match(/^[\w$]+/)?.[0] : void 0;
|
|
7225
6942
|
if (templateInterpolations === null && valueIdentifier !== void 0 && BARE_IDENTIFIER_VALUE_PATTERN.test(valueExpression)) {
|
|
7226
|
-
const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier,
|
|
6943
|
+
const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier, file.content);
|
|
7227
6944
|
if (declarationInitializer !== null) {
|
|
7228
6945
|
if (isPureStringLiteralConcat(declarationInitializer)) continue;
|
|
7229
6946
|
templateInterpolations = getTemplateInterpolations(declarationInitializer);
|
|
@@ -7234,7 +6951,7 @@ const dangerousHtmlSink = defineRule({
|
|
|
7234
6951
|
if (SANITIZER_PATTERN.test(judgedExpression)) continue;
|
|
7235
6952
|
if (ENV_CONFIG_VALUE_PATTERN.test(judgedExpression)) continue;
|
|
7236
6953
|
if (I18N_VALUE_PATTERN.test(judgedExpression)) continue;
|
|
7237
|
-
if (!
|
|
6954
|
+
if (!HTML_TAINT_PATTERN.test(judgedExpression)) continue;
|
|
7238
6955
|
if (ESCAPING_SERIALIZER_CALL_PATTERN.test(valueExpression)) continue;
|
|
7239
6956
|
if (/highlighted/i.test(valueExpression)) continue;
|
|
7240
6957
|
if (/highlight/i.test(valueExpression) && HIGHLIGHTER_LIBRARY_PATTERN.test(file.content)) continue;
|
|
@@ -7681,7 +7398,7 @@ const containsJsx$1 = (root) => {
|
|
|
7681
7398
|
containsJsxCache.set(root, found);
|
|
7682
7399
|
return found;
|
|
7683
7400
|
};
|
|
7684
|
-
const getStaticMemberName$
|
|
7401
|
+
const getStaticMemberName$1 = (node) => {
|
|
7685
7402
|
if (!isNodeOfType(node, "MemberExpression")) return null;
|
|
7686
7403
|
if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
|
|
7687
7404
|
if (node.computed && isNodeOfType(node.property, "Literal") && typeof node.property.value === "string") return node.property.value;
|
|
@@ -7695,7 +7412,7 @@ const getAssignedName = (node) => {
|
|
|
7695
7412
|
if (isNodeOfType(parent, "AssignmentExpression")) {
|
|
7696
7413
|
const left = parent.left;
|
|
7697
7414
|
if (isNodeOfType(left, "Identifier")) return left.name;
|
|
7698
|
-
if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$
|
|
7415
|
+
if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$1(left);
|
|
7699
7416
|
}
|
|
7700
7417
|
return null;
|
|
7701
7418
|
};
|
|
@@ -7703,20 +7420,20 @@ const isModuleExportsAssignment = (node) => {
|
|
|
7703
7420
|
const parent = node.parent;
|
|
7704
7421
|
if (!parent || !isNodeOfType(parent, "AssignmentExpression")) return false;
|
|
7705
7422
|
const left = parent.left;
|
|
7706
|
-
return isNodeOfType(left, "MemberExpression") && isNodeOfType(left.object, "Identifier") && left.object.name === "module" && getStaticMemberName$
|
|
7423
|
+
return isNodeOfType(left, "MemberExpression") && isNodeOfType(left.object, "Identifier") && left.object.name === "module" && getStaticMemberName$1(left) === "exports";
|
|
7707
7424
|
};
|
|
7708
7425
|
const isCreateClassLikeCall = (node) => {
|
|
7709
7426
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7710
7427
|
if (isEs5Component(node)) return true;
|
|
7711
7428
|
const callee = node.callee;
|
|
7712
|
-
if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$
|
|
7429
|
+
if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$1(callee) === "createClass";
|
|
7713
7430
|
return false;
|
|
7714
7431
|
};
|
|
7715
|
-
const isCreateContextCall
|
|
7432
|
+
const isCreateContextCall = (node) => {
|
|
7716
7433
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7717
7434
|
const callee = node.callee;
|
|
7718
7435
|
if (isNodeOfType(callee, "Identifier")) return callee.name === "createContext";
|
|
7719
|
-
return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$
|
|
7436
|
+
return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$1(callee) === "createContext";
|
|
7720
7437
|
};
|
|
7721
7438
|
const isNamedFunctionLike = (node) => (isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && Boolean(node.id?.name);
|
|
7722
7439
|
const firstCallArgument = (node) => {
|
|
@@ -7774,7 +7491,7 @@ const memberExpressionPath = (node) => {
|
|
|
7774
7491
|
if (isNodeOfType(node, "Identifier")) return [node.name];
|
|
7775
7492
|
if (!isNodeOfType(node, "MemberExpression")) return [];
|
|
7776
7493
|
const objectPath = memberExpressionPath(node.object);
|
|
7777
|
-
const propertyName = getStaticMemberName$
|
|
7494
|
+
const propertyName = getStaticMemberName$1(node);
|
|
7778
7495
|
return propertyName ? [...objectPath, propertyName] : objectPath;
|
|
7779
7496
|
};
|
|
7780
7497
|
const displayNameAssignmentIndexCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -7784,7 +7501,7 @@ const getDisplayNameAssignmentIndex = (programRoot) => {
|
|
|
7784
7501
|
const identifierTargets = /* @__PURE__ */ new Set();
|
|
7785
7502
|
const memberPathSegments = /* @__PURE__ */ new Set();
|
|
7786
7503
|
const visit = (node) => {
|
|
7787
|
-
if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName$
|
|
7504
|
+
if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName$1(node.left) === "displayName") {
|
|
7788
7505
|
if (isNodeOfType(node.left.object, "Identifier")) identifierTargets.add(node.left.object.name);
|
|
7789
7506
|
for (const segment of memberExpressionPath(node.left.object)) memberPathSegments.add(segment);
|
|
7790
7507
|
}
|
|
@@ -7889,7 +7606,7 @@ const displayName = defineRule({
|
|
|
7889
7606
|
}
|
|
7890
7607
|
},
|
|
7891
7608
|
CallExpression(node) {
|
|
7892
|
-
if (settings.checkContextObjects && isReactVersionAtLeast$1(settings.reactVersion, 16, 3) && isCreateContextCall
|
|
7609
|
+
if (settings.checkContextObjects && isReactVersionAtLeast$1(settings.reactVersion, 16, 3) && isCreateContextCall(node)) {
|
|
7893
7610
|
const assignedName = getAssignedName(node);
|
|
7894
7611
|
if (assignedName) {
|
|
7895
7612
|
const programRoot = findProgramRoot(node);
|
|
@@ -7936,6 +7653,21 @@ const displayName = defineRule({
|
|
|
7936
7653
|
}
|
|
7937
7654
|
});
|
|
7938
7655
|
//#endregion
|
|
7656
|
+
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
7657
|
+
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
7658
|
+
if (!isNodeOfType(node, "Property")) return null;
|
|
7659
|
+
if (node.computed) {
|
|
7660
|
+
if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
|
|
7661
|
+
return null;
|
|
7662
|
+
}
|
|
7663
|
+
if (isNodeOfType(node.key, "Identifier")) return node.key.name;
|
|
7664
|
+
if (isNodeOfType(node.key, "Literal")) {
|
|
7665
|
+
if (typeof node.key.value === "string") return node.key.value;
|
|
7666
|
+
if (options.stringifyNonStringLiterals) return String(node.key.value);
|
|
7667
|
+
}
|
|
7668
|
+
return null;
|
|
7669
|
+
};
|
|
7670
|
+
//#endregion
|
|
7939
7671
|
//#region src/plugin/utils/read-static-boolean.ts
|
|
7940
7672
|
const readStaticBoolean = (node) => {
|
|
7941
7673
|
if (!node) return null;
|
|
@@ -7944,16 +7676,31 @@ const readStaticBoolean = (node) => {
|
|
|
7944
7676
|
return unwrappedNode.value;
|
|
7945
7677
|
};
|
|
7946
7678
|
//#endregion
|
|
7679
|
+
//#region src/plugin/utils/resolve-const-identifier-alias.ts
|
|
7680
|
+
const resolveConstIdentifierAlias = (identifier, scopes) => {
|
|
7681
|
+
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
7682
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
7683
|
+
let symbol = scopes.symbolFor(identifier);
|
|
7684
|
+
while (symbol?.kind === "const") {
|
|
7685
|
+
if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
7686
|
+
visitedSymbolIds.add(symbol.id);
|
|
7687
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
7688
|
+
if (!isNodeOfType(initializer, "Identifier")) return symbol;
|
|
7689
|
+
symbol = scopes.symbolFor(initializer);
|
|
7690
|
+
}
|
|
7691
|
+
return symbol;
|
|
7692
|
+
};
|
|
7693
|
+
//#endregion
|
|
7947
7694
|
//#region src/plugin/utils/is-react-api-call.ts
|
|
7948
7695
|
const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? apiNames === apiName : apiNames.has(apiName);
|
|
7949
7696
|
const isImportedFromReact = (symbol) => {
|
|
7950
7697
|
if (symbol.kind !== "import") return false;
|
|
7951
7698
|
const importDeclaration = symbol.declarationNode.parent;
|
|
7952
|
-
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") &&
|
|
7699
|
+
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react");
|
|
7953
7700
|
};
|
|
7954
|
-
const isNamedReactApiImport = (identifier, apiNames, scopes
|
|
7701
|
+
const isNamedReactApiImport = (identifier, apiNames, scopes) => {
|
|
7955
7702
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
7956
|
-
const symbol =
|
|
7703
|
+
const symbol = scopes.symbolFor(identifier);
|
|
7957
7704
|
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
7958
7705
|
const importedName = getImportedName(symbol.declarationNode);
|
|
7959
7706
|
return Boolean(importedName && includesApiName(apiNames, importedName));
|
|
@@ -7967,7 +7714,7 @@ const isReactApiCall = (node, apiNames, scopes, options = {}) => {
|
|
|
7967
7714
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7968
7715
|
const callee = stripParenExpression(node.callee);
|
|
7969
7716
|
if (isNodeOfType(callee, "Identifier")) {
|
|
7970
|
-
if (isNamedReactApiImport(callee, apiNames, scopes
|
|
7717
|
+
if (isNamedReactApiImport(callee, apiNames, scopes)) return true;
|
|
7971
7718
|
return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
|
|
7972
7719
|
}
|
|
7973
7720
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || !includesApiName(apiNames, callee.property.name)) return false;
|
|
@@ -9070,30 +8817,6 @@ const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, conte
|
|
|
9070
8817
|
}
|
|
9071
8818
|
return matchingBlocks.size > 0;
|
|
9072
8819
|
};
|
|
9073
|
-
const doMatchingNodesCoverEveryPathFromFunctionEntry = (owner, matchingNodes, context) => {
|
|
9074
|
-
const functionCfg = context.cfg.cfgFor(owner);
|
|
9075
|
-
if (!functionCfg) return false;
|
|
9076
|
-
const matchingBlocks = new Set(matchingNodes.flatMap((matchingNode) => {
|
|
9077
|
-
if (context.cfg.enclosingFunction(matchingNode) !== owner) return [];
|
|
9078
|
-
const matchingBlock = functionCfg.blockOf(matchingNode);
|
|
9079
|
-
return matchingBlock ? [matchingBlock] : [];
|
|
9080
|
-
}));
|
|
9081
|
-
if (matchingBlocks.size === 0) return false;
|
|
9082
|
-
const visitedBlocks = new Set([functionCfg.entry]);
|
|
9083
|
-
const pendingBlocks = [functionCfg.entry];
|
|
9084
|
-
while (pendingBlocks.length > 0) {
|
|
9085
|
-
const currentBlock = pendingBlocks.pop();
|
|
9086
|
-
if (!currentBlock) break;
|
|
9087
|
-
if (matchingBlocks.has(currentBlock)) continue;
|
|
9088
|
-
for (const edge of currentBlock.successors) {
|
|
9089
|
-
if (edge.to === functionCfg.exit) return false;
|
|
9090
|
-
if (visitedBlocks.has(edge.to)) continue;
|
|
9091
|
-
visitedBlocks.add(edge.to);
|
|
9092
|
-
pendingBlocks.push(edge.to);
|
|
9093
|
-
}
|
|
9094
|
-
}
|
|
9095
|
-
return true;
|
|
9096
|
-
};
|
|
9097
8820
|
const removeSynchronouslyReleasedUsages = (callback, usages, context) => {
|
|
9098
8821
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
|
|
9099
8822
|
if (!isNodeOfType(callback.body, "BlockStatement")) return usages;
|
|
@@ -9294,83 +9017,6 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
|
|
|
9294
9017
|
});
|
|
9295
9018
|
return didCleanupFunctionMatch;
|
|
9296
9019
|
};
|
|
9297
|
-
const doesBoundCleanupReleaseUsage = (expression, usage, context) => {
|
|
9298
|
-
const callExpression = stripParenExpression(expression);
|
|
9299
|
-
if (!isNodeOfType(callExpression, "CallExpression")) return false;
|
|
9300
|
-
const bindCallee = stripParenExpression(callExpression.callee);
|
|
9301
|
-
if (!isNodeOfType(bindCallee, "MemberExpression") || bindCallee.computed || !isNodeOfType(bindCallee.property, "Identifier") || bindCallee.property.name !== "bind") return false;
|
|
9302
|
-
const releaseMember = stripParenExpression(bindCallee.object);
|
|
9303
|
-
if (!isNodeOfType(releaseMember, "MemberExpression") || releaseMember.computed || !isNodeOfType(releaseMember.property, "Identifier")) return false;
|
|
9304
|
-
const releaseReceiverKey = resolveExpressionKey$1(releaseMember.object, context);
|
|
9305
|
-
if (releaseReceiverKey === null || releaseReceiverKey !== resolveExpressionKey$1(callExpression.arguments?.[0], context)) return false;
|
|
9306
|
-
const releaseVerbName = releaseMember.property.name;
|
|
9307
|
-
if (usage.kind === "socket") return usage.handleKey === releaseReceiverKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
|
|
9308
|
-
return usage.kind === "subscribe" && usage.handleKey === releaseReceiverKey && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub" || releaseVerbName === "close" || releaseVerbName === "unwatch" || releaseVerbName === "unlisten" || BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(releaseVerbName));
|
|
9309
|
-
};
|
|
9310
|
-
const callbackReturnsCleanupForUsage = (callback, usage, context) => {
|
|
9311
|
-
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
9312
|
-
const doesReturnedValueReleaseUsage = (returnedValue) => {
|
|
9313
|
-
if (doesBoundCleanupReleaseUsage(returnedValue, usage, context)) return true;
|
|
9314
|
-
const cleanupFunction = resolveStableValue(returnedValue, context);
|
|
9315
|
-
if (cleanupFunction && doesBoundCleanupReleaseUsage(cleanupFunction, usage, context)) return true;
|
|
9316
|
-
return Boolean(cleanupFunction && isFunctionLike$1(cleanupFunction) && doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context));
|
|
9317
|
-
};
|
|
9318
|
-
if (!isNodeOfType(callback.body, "BlockStatement")) return doesReturnedValueReleaseUsage(stripParenExpression(callback.body));
|
|
9319
|
-
const matchingCleanupReturns = [];
|
|
9320
|
-
walkInsideStatementBlocks(callback.body, (child) => {
|
|
9321
|
-
if (isNodeOfType(child, "ReturnStatement") && child.argument && doesReturnedValueReleaseUsage(stripParenExpression(child.argument))) matchingCleanupReturns.push(child);
|
|
9322
|
-
});
|
|
9323
|
-
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
|
|
9324
|
-
};
|
|
9325
|
-
const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
|
|
9326
|
-
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
|
|
9327
|
-
const functionCfg = context.cfg.cfgFor(callback);
|
|
9328
|
-
const usageBlock = functionCfg?.blockOf(usage.node);
|
|
9329
|
-
const usageStart = getRangeStart(usage.node);
|
|
9330
|
-
if (!functionCfg || !usageBlock || usageStart === null) return false;
|
|
9331
|
-
const findHandleGuard = (releaseCall) => {
|
|
9332
|
-
if (usage.handleKey === null) return null;
|
|
9333
|
-
let ancestor = releaseCall.parent;
|
|
9334
|
-
while (ancestor && ancestor !== callback.body) {
|
|
9335
|
-
if (isNodeOfType(ancestor, "IfStatement")) return ancestor.alternate === null && resolveExpressionKey$1(ancestor.test, context) === usage.handleKey && getRangeStart(ancestor) !== null && (getRangeStart(ancestor) ?? usageStart) < usageStart ? ancestor : null;
|
|
9336
|
-
ancestor = ancestor.parent;
|
|
9337
|
-
}
|
|
9338
|
-
return null;
|
|
9339
|
-
};
|
|
9340
|
-
const matchingReleaseAnchors = [];
|
|
9341
|
-
walkInsideStatementBlocks(callback.body, (child) => {
|
|
9342
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
9343
|
-
const releaseStart = getRangeStart(child);
|
|
9344
|
-
const handleGuard = findHandleGuard(child);
|
|
9345
|
-
if (releaseStart === null || releaseStart >= usageStart || functionCfg.blockOf(child) !== usageBlock && !handleGuard) return;
|
|
9346
|
-
if (doesReleaseCallMatchUsage(child, usage, context)) {
|
|
9347
|
-
matchingReleaseAnchors.push(handleGuard ?? child);
|
|
9348
|
-
return;
|
|
9349
|
-
}
|
|
9350
|
-
const helperFunction = resolveStableValue(child.callee, context);
|
|
9351
|
-
if (helperFunction && isFunctionLike$1(helperFunction) && doesCleanupFunctionReleaseUsage(helperFunction, usage, context)) matchingReleaseAnchors.push(handleGuard ?? child);
|
|
9352
|
-
});
|
|
9353
|
-
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
|
|
9354
|
-
};
|
|
9355
|
-
const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
9356
|
-
const componentFunction = findEnclosingFunction(callback);
|
|
9357
|
-
if (!componentFunction || !isNodeOfType(componentFunction, "ArrowFunctionExpression") && !isNodeOfType(componentFunction, "FunctionExpression") && !isNodeOfType(componentFunction, "FunctionDeclaration")) return false;
|
|
9358
|
-
let didFindUnmountCleanup = false;
|
|
9359
|
-
walkAst(componentFunction.body, (child) => {
|
|
9360
|
-
if (didFindUnmountCleanup) return false;
|
|
9361
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction(child) !== componentFunction) return;
|
|
9362
|
-
if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
|
|
9363
|
-
const dependencyList = child.arguments?.[1];
|
|
9364
|
-
if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
|
|
9365
|
-
const cleanupCallback = getEffectCallback(child);
|
|
9366
|
-
if (cleanupCallback && cleanupCallback !== callback && callbackReturnsCleanupForUsage(cleanupCallback, usage, context)) {
|
|
9367
|
-
didFindUnmountCleanup = true;
|
|
9368
|
-
return false;
|
|
9369
|
-
}
|
|
9370
|
-
});
|
|
9371
|
-
return didFindUnmountCleanup;
|
|
9372
|
-
};
|
|
9373
|
-
const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
|
|
9374
9020
|
const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
9375
9021
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
9376
9022
|
if (usage.kind === "subscribe" && findEnclosingFunction(usage.node) === callback && doesResourceResultEscape(usage.node, true) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
|
|
@@ -9383,10 +9029,6 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
|
9383
9029
|
if (returnStart !== null && usageStart !== null && returnStart < usageStart) return;
|
|
9384
9030
|
const returnedValue = child.argument ? stripParenExpression(child.argument) : null;
|
|
9385
9031
|
if (!returnedValue) return;
|
|
9386
|
-
if (doesBoundCleanupReleaseUsage(returnedValue, usage, context)) {
|
|
9387
|
-
matchingCleanupReturns.push(child);
|
|
9388
|
-
return;
|
|
9389
|
-
}
|
|
9390
9032
|
if (usage.kind === "subscribe" && (returnedValue === usage.node || getRangeStart(returnedValue) !== null && getRangeStart(returnedValue) === getRangeStart(usage.node)) && isCleanupReturningSubscribeLikeCallExpression(returnedValue)) {
|
|
9391
9033
|
matchingCleanupReturns.push(child);
|
|
9392
9034
|
return;
|
|
@@ -9402,17 +9044,13 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
|
9402
9044
|
if (!context.scopes.symbolFor(returnedValue)?.initializer) return;
|
|
9403
9045
|
}
|
|
9404
9046
|
const cleanupFunction = resolveStableValue(returnedValue, context);
|
|
9405
|
-
if (cleanupFunction && doesBoundCleanupReleaseUsage(cleanupFunction, usage, context)) {
|
|
9406
|
-
matchingCleanupReturns.push(child);
|
|
9407
|
-
return;
|
|
9408
|
-
}
|
|
9409
9047
|
if (!cleanupFunction || !isFunctionLike$1(cleanupFunction)) return;
|
|
9410
9048
|
if (doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context)) matchingCleanupReturns.push(child);
|
|
9411
9049
|
});
|
|
9412
9050
|
return doMatchingNodesCoverEveryPathAfterUsage(resolveCleanupPathAnchor(usage.node, callback, context), matchingCleanupReturns, context);
|
|
9413
9051
|
};
|
|
9414
9052
|
const findFirstUsageWithoutCleanup = (callback, usages, context) => {
|
|
9415
|
-
for (const usage of usages) if (!effectHasCleanupForUsage(callback, usage, context)
|
|
9053
|
+
for (const usage of usages) if (!effectHasCleanupForUsage(callback, usage, context)) return usage;
|
|
9416
9054
|
return null;
|
|
9417
9055
|
};
|
|
9418
9056
|
const isLocalAbortControllerExpression = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
@@ -10008,11 +9646,6 @@ const walkParameterReferences = (pattern, state) => {
|
|
|
10008
9646
|
const walk = (node, state) => {
|
|
10009
9647
|
if (isFunctionLike$1(node)) {
|
|
10010
9648
|
if (isNodeOfType(node, "FunctionDeclaration") && node.id) handleFunctionDeclaration(node, state);
|
|
10011
|
-
const functionParams = node.params ?? [];
|
|
10012
|
-
for (const param of functionParams) {
|
|
10013
|
-
if (!("decorators" in param) || !Array.isArray(param.decorators)) continue;
|
|
10014
|
-
for (const decorator of param.decorators) if (isAstNode(decorator)) walk(decorator, state);
|
|
10015
|
-
}
|
|
10016
9649
|
setNodeScope(node, state);
|
|
10017
9650
|
const fnScope = pushScope(node.type === "ArrowFunctionExpression" ? "arrow-function" : "function", node, state);
|
|
10018
9651
|
if ((isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && node.id) {
|
|
@@ -10025,6 +9658,7 @@ const walk = (node, state) => {
|
|
|
10025
9658
|
});
|
|
10026
9659
|
tagAsBinding(state, node.id);
|
|
10027
9660
|
}
|
|
9661
|
+
const functionParams = node.params ?? [];
|
|
10028
9662
|
handleFunctionParameters(functionParams, fnScope, state);
|
|
10029
9663
|
for (const param of functionParams) walkParameterReferences(param, state);
|
|
10030
9664
|
const body = node.body;
|
|
@@ -10034,9 +9668,6 @@ const walk = (node, state) => {
|
|
|
10034
9668
|
}
|
|
10035
9669
|
if (isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) {
|
|
10036
9670
|
if (isNodeOfType(node, "ClassDeclaration") && node.id) handleClassDeclaration(node, state);
|
|
10037
|
-
if (Array.isArray(node.decorators)) {
|
|
10038
|
-
for (const decorator of node.decorators) if (isAstNode(decorator)) walk(decorator, state);
|
|
10039
|
-
}
|
|
10040
9671
|
const classScope = pushScope("class", node, state);
|
|
10041
9672
|
setNodeScope(node, state);
|
|
10042
9673
|
if (isNodeOfType(node, "ClassExpression") && node.id) {
|
|
@@ -10239,7 +9870,7 @@ const computeClosureCaptures = (functionNode, scopes) => {
|
|
|
10239
9870
|
const functionScope = scopes.ownScopeFor(functionNode) ?? scopes.scopeFor(functionNode);
|
|
10240
9871
|
const out = [];
|
|
10241
9872
|
const seen = /* @__PURE__ */ new Set();
|
|
10242
|
-
|
|
9873
|
+
const visit = (node) => {
|
|
10243
9874
|
if (node !== functionNode && isFunctionLike$1(node)) {
|
|
10244
9875
|
const innerCaptures = closureCaptures(node, scopes);
|
|
10245
9876
|
for (const reference of innerCaptures) if (reference.resolvedSymbol && !isDescendantScope(reference.resolvedSymbol.scope, functionScope)) {
|
|
@@ -10248,7 +9879,7 @@ const computeClosureCaptures = (functionNode, scopes) => {
|
|
|
10248
9879
|
seen.add(reference.id);
|
|
10249
9880
|
}
|
|
10250
9881
|
}
|
|
10251
|
-
return
|
|
9882
|
+
return;
|
|
10252
9883
|
}
|
|
10253
9884
|
const reference = scopes.referenceFor(node);
|
|
10254
9885
|
if (reference && reference.resolvedSymbol) {
|
|
@@ -10259,7 +9890,17 @@ const computeClosureCaptures = (functionNode, scopes) => {
|
|
|
10259
9890
|
}
|
|
10260
9891
|
}
|
|
10261
9892
|
}
|
|
10262
|
-
|
|
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);
|
|
10263
9904
|
return out;
|
|
10264
9905
|
};
|
|
10265
9906
|
const capturesByAnalysis = /* @__PURE__ */ new WeakMap();
|
|
@@ -11285,11 +10926,6 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
11285
10926
|
return { CallExpression(node) {
|
|
11286
10927
|
const hookName = getHookName(node.callee, context.scopes);
|
|
11287
10928
|
if (!hookName || !isHookOfInterest(hookName, node.callee)) return;
|
|
11288
|
-
if (HOOKS_REQUIRING_DEPS_MATCH.has(hookName) && !isReactApiCall(node, HOOKS_REQUIRING_DEPS_MATCH, context.scopes, {
|
|
11289
|
-
allowGlobalReactNamespace: true,
|
|
11290
|
-
allowUnboundBareCalls: true,
|
|
11291
|
-
resolveNamedAliases: true
|
|
11292
|
-
})) return;
|
|
11293
10929
|
const callbackArgumentIndex = getCallbackArgumentIndex(hookName);
|
|
11294
10930
|
const depsArgumentIndex = getDepsArgumentIndex(hookName);
|
|
11295
10931
|
const callbackArgument = node.arguments[callbackArgumentIndex];
|
|
@@ -14584,11 +14220,7 @@ const jsIndexMaps = defineRule({
|
|
|
14584
14220
|
//#region src/plugin/utils/are-expressions-structurally-equal.ts
|
|
14585
14221
|
const areExpressionsStructurallyEqual = (a, b) => {
|
|
14586
14222
|
if (!a || !b) return a === b;
|
|
14587
|
-
const unwrappedA = stripParenExpression(a);
|
|
14588
|
-
const unwrappedB = stripParenExpression(b);
|
|
14589
|
-
if (unwrappedA !== a || unwrappedB !== b) return areExpressionsStructurallyEqual(unwrappedA, unwrappedB);
|
|
14590
14223
|
if (a.type !== b.type) return false;
|
|
14591
|
-
if (isNodeOfType(a, "ThisExpression")) return true;
|
|
14592
14224
|
if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
|
|
14593
14225
|
if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
|
|
14594
14226
|
if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
|
|
@@ -19292,7 +18924,6 @@ const jwtInsecureVerification = defineRule({
|
|
|
19292
18924
|
});
|
|
19293
18925
|
//#endregion
|
|
19294
18926
|
//#region src/plugin/rules/security-scan/key-lifecycle-risk.ts
|
|
19295
|
-
const KEY_MATERIAL_MARKER_PATTERN = /PRIVATE KEY|SSH_PRIVATE_KEY|GPG_PRIVATE_KEY|DEPLOY_KEY|SIGNING_KEY/i;
|
|
19296
18927
|
const keyLifecycleRisk = defineRule({
|
|
19297
18928
|
id: "key-lifecycle-risk",
|
|
19298
18929
|
title: "Long-lived key material in repository",
|
|
@@ -19300,7 +18931,7 @@ const keyLifecycleRisk = defineRule({
|
|
|
19300
18931
|
committedFilesOnly: true,
|
|
19301
18932
|
recommendation: "Remove private keys from source, rotate exposed credentials, prefer short-lived deploy credentials, and document revocation/expiry for release keys.",
|
|
19302
18933
|
scan: scanByPattern({
|
|
19303
|
-
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),
|
|
19304
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,
|
|
19305
18936
|
message: "Private or long-lived release key material appears in the repository."
|
|
19306
18937
|
})
|
|
@@ -20006,21 +19637,15 @@ const localRpcNativeBridgeRisk = defineRule({
|
|
|
20006
19637
|
message: "Code appears to bridge browser code to localhost/native capabilities with weak origin or update/install checks."
|
|
20007
19638
|
})
|
|
20008
19639
|
});
|
|
20009
|
-
//#endregion
|
|
20010
|
-
//#region src/plugin/rules/security-scan/mcp-tool-capability-risk.ts
|
|
20011
|
-
const MCP_IMPORT_PATTERN = /\bfrom\s+["']@modelcontextprotocol\/sdk[^"']*["']|\bMcpServer\b|\bMcpAgent\b/;
|
|
20012
|
-
const MCP_IMPORT_PREFILTER_PATTERN = /@modelcontextprotocol\/sdk|\bMcpServer\b|\bMcpAgent\b/;
|
|
20013
|
-
const MCP_TOOL_SURFACE_PATTERN = /\bserver\.\s*tool\s*\(|\bregisterTool\s*\(|\bsetRequestHandler\s*\(\s*CallToolRequestSchema/;
|
|
20014
|
-
const MCP_TOOL_SURFACE_PREFILTER_PATTERN = /\b(?:tool|registerTool|setRequestHandler)\b/;
|
|
20015
19640
|
const mcpToolCapabilityRisk = defineRule({
|
|
20016
19641
|
id: "mcp-tool-capability-risk",
|
|
20017
19642
|
title: "MCP tool exposes dangerous capability",
|
|
20018
19643
|
severity: "warn",
|
|
20019
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.",
|
|
20020
19645
|
scan: scanByPattern({
|
|
20021
|
-
shouldScan: (file) => isProductionSourcePath(file.relativePath)
|
|
20022
|
-
pattern:
|
|
20023
|
-
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],
|
|
20024
19649
|
ignoreStringLiterals: true,
|
|
20025
19650
|
message: "An MCP tool/resource/prompt handler appears to expose file, shell, network, or code-execution capability."
|
|
20026
19651
|
})
|
|
@@ -21131,8 +20756,8 @@ const catchClauseRethrowsCaught = (handler) => {
|
|
|
21131
20756
|
return didRethrow;
|
|
21132
20757
|
};
|
|
21133
20758
|
//#endregion
|
|
21134
|
-
//#region src/plugin/utils/
|
|
21135
|
-
const
|
|
20759
|
+
//#region src/plugin/utils/find-guarding-try-statement.ts
|
|
20760
|
+
const isImmediatelyInvokedFunctionCallee = (functionNode) => {
|
|
21136
20761
|
let wrappedCallee = functionNode;
|
|
21137
20762
|
let enclosing = functionNode.parent;
|
|
21138
20763
|
while (enclosing && stripParenExpression(enclosing) === functionNode) {
|
|
@@ -21141,13 +20766,11 @@ const isImmediatelyInvokedFunction = (functionNode) => {
|
|
|
21141
20766
|
}
|
|
21142
20767
|
return Boolean(enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee);
|
|
21143
20768
|
};
|
|
21144
|
-
//#endregion
|
|
21145
|
-
//#region src/plugin/utils/find-guarding-try-statement.ts
|
|
21146
20769
|
const findGuardingTryStatement = (node) => {
|
|
21147
20770
|
let child = node;
|
|
21148
20771
|
let ancestor = node.parent;
|
|
21149
20772
|
while (ancestor) {
|
|
21150
|
-
if (isFunctionLike$1(ancestor) && !
|
|
20773
|
+
if (isFunctionLike$1(ancestor) && !isImmediatelyInvokedFunctionCallee(ancestor)) return null;
|
|
21151
20774
|
if (isNodeOfType(ancestor, "TryStatement") && ancestor.block === child && ancestor.handler && !catchClauseRethrowsCaught(ancestor.handler)) return ancestor;
|
|
21152
20775
|
child = ancestor;
|
|
21153
20776
|
ancestor = ancestor.parent ?? null;
|
|
@@ -21699,7 +21322,7 @@ const FILENAME_TO_LANG = {
|
|
|
21699
21322
|
const resolveLang = (filename) => {
|
|
21700
21323
|
return FILENAME_TO_LANG[path.extname(filename).toLowerCase()] ?? "tsx";
|
|
21701
21324
|
};
|
|
21702
|
-
const parseSourceText = (
|
|
21325
|
+
const parseSourceText = (filename, sourceText) => {
|
|
21703
21326
|
try {
|
|
21704
21327
|
const result = parseSync(filename, sourceText, {
|
|
21705
21328
|
astType: "ts",
|
|
@@ -21707,7 +21330,7 @@ const parseSourceText = ({ filename, sourceText, shouldAttachParentReferences =
|
|
|
21707
21330
|
});
|
|
21708
21331
|
if (result.errors.some((parseError) => parseError.severity === "Error")) return null;
|
|
21709
21332
|
const parsedProgram = result.program;
|
|
21710
|
-
|
|
21333
|
+
attachParentReferences(parsedProgram);
|
|
21711
21334
|
return parsedProgram;
|
|
21712
21335
|
} catch {
|
|
21713
21336
|
return null;
|
|
@@ -21745,10 +21368,7 @@ const parseSourceFile = (absoluteFilePath) => {
|
|
|
21745
21368
|
});
|
|
21746
21369
|
return null;
|
|
21747
21370
|
}
|
|
21748
|
-
const parsedProgram = parseSourceText(
|
|
21749
|
-
filename: absoluteFilePath,
|
|
21750
|
-
sourceText
|
|
21751
|
-
});
|
|
21371
|
+
const parsedProgram = parseSourceText(absoluteFilePath, sourceText);
|
|
21752
21372
|
parseCache.set(absoluteFilePath, {
|
|
21753
21373
|
mtimeMs: fileStat.mtimeMs,
|
|
21754
21374
|
size: fileStat.size,
|
|
@@ -22280,8 +21900,6 @@ const DOM_QUERY_MEMBER_NAMES = new Set([
|
|
|
22280
21900
|
]);
|
|
22281
21901
|
const LAYOUT_MEASUREMENT_MEMBER_NAMES = new Set([
|
|
22282
21902
|
"current",
|
|
22283
|
-
"textContent",
|
|
22284
|
-
"innerText",
|
|
22285
21903
|
"scrollWidth",
|
|
22286
21904
|
"clientWidth",
|
|
22287
21905
|
"offsetWidth",
|
|
@@ -22303,7 +21921,7 @@ const POST_MOUNT_GLOBAL_NAMES = new Set([
|
|
|
22303
21921
|
"navigator"
|
|
22304
21922
|
]);
|
|
22305
21923
|
const REF_FACTORY_CALLEE_NAMES = new Set(["useRef", "createRef"]);
|
|
22306
|
-
const hasRefLikeName = (name) => name === "ref" || name.endsWith("Ref") || name.endsWith("ref")
|
|
21924
|
+
const hasRefLikeName = (name) => name === "ref" || name.endsWith("Ref") || name.endsWith("ref");
|
|
22307
21925
|
const isRefFactoryInitializer = (init) => {
|
|
22308
21926
|
if (!init || !isNodeOfType(init, "CallExpression")) return false;
|
|
22309
21927
|
const callee = init.callee;
|
|
@@ -22386,37 +22004,88 @@ const readsPostMountValue = (root) => {
|
|
|
22386
22004
|
return found;
|
|
22387
22005
|
};
|
|
22388
22006
|
//#endregion
|
|
22389
|
-
//#region src/plugin/rules/state-and-effects/utils/effect/
|
|
22390
|
-
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
|
+
};
|
|
22391
22019
|
//#endregion
|
|
22392
22020
|
//#region src/plugin/rules/state-and-effects/utils/effect/get-program-analysis.ts
|
|
22393
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
|
+
};
|
|
22394
22053
|
const getProgramAnalysis = (anyNode) => {
|
|
22395
22054
|
const programNode = findProgramRoot(anyNode);
|
|
22396
22055
|
if (!programNode) return null;
|
|
22397
22056
|
const cached = programToAnalysis.get(programNode);
|
|
22398
22057
|
if (cached) return cached;
|
|
22399
|
-
const
|
|
22400
|
-
|
|
22401
|
-
|
|
22058
|
+
const restorations = stripAndRecordParents(programNode);
|
|
22059
|
+
let scopeManager;
|
|
22060
|
+
try {
|
|
22061
|
+
scopeManager = analyze(programNode, {
|
|
22402
22062
|
ecmaVersion: 2024,
|
|
22403
22063
|
sourceType: "module",
|
|
22404
|
-
childVisitorKeys:
|
|
22405
|
-
fallback:
|
|
22406
|
-
})
|
|
22407
|
-
|
|
22408
|
-
|
|
22064
|
+
childVisitorKeys: VISITOR_KEYS,
|
|
22065
|
+
fallback: "iteration"
|
|
22066
|
+
});
|
|
22067
|
+
} finally {
|
|
22068
|
+
restoreParents(restorations);
|
|
22069
|
+
}
|
|
22070
|
+
const analysis = {
|
|
22071
|
+
programNode,
|
|
22072
|
+
scopeManager
|
|
22409
22073
|
};
|
|
22410
22074
|
programToAnalysis.set(programNode, analysis);
|
|
22411
22075
|
return analysis;
|
|
22412
22076
|
};
|
|
22413
|
-
const
|
|
22077
|
+
const scopeByNodeCache = /* @__PURE__ */ new WeakMap();
|
|
22078
|
+
const getScopeForNode = (node, manager) => {
|
|
22414
22079
|
if (!node.range) return null;
|
|
22415
|
-
|
|
22080
|
+
let scopeByNode = scopeByNodeCache.get(manager);
|
|
22081
|
+
if (!scopeByNode) {
|
|
22082
|
+
scopeByNode = /* @__PURE__ */ new WeakMap();
|
|
22083
|
+
scopeByNodeCache.set(manager, scopeByNode);
|
|
22084
|
+
}
|
|
22416
22085
|
if (scopeByNode.has(node)) return scopeByNode.get(node) ?? null;
|
|
22417
22086
|
let bestScope = null;
|
|
22418
22087
|
let bestSize = Infinity;
|
|
22419
|
-
for (const scope of
|
|
22088
|
+
for (const scope of manager.scopes) {
|
|
22420
22089
|
const block = scope.block;
|
|
22421
22090
|
if (!block?.range) continue;
|
|
22422
22091
|
if (node.range[0] < block.range[0] || node.range[1] > block.range[1]) continue;
|
|
@@ -22431,6 +22100,7 @@ const getScopeForNode = (node, analysis) => {
|
|
|
22431
22100
|
};
|
|
22432
22101
|
//#endregion
|
|
22433
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");
|
|
22434
22104
|
const HOOK_NAME_PATTERN$2 = /^use[A-Z0-9]/;
|
|
22435
22105
|
const isInsideCallbackArgumentOf = (identifier, initializer) => {
|
|
22436
22106
|
if (!isNodeOfType(initializer, "CallExpression") && !isNodeOfType(initializer, "NewExpression")) return false;
|
|
@@ -22466,7 +22136,7 @@ const descend = (node, visit, visited = /* @__PURE__ */ new Set()) => {
|
|
|
22466
22136
|
if (visited.has(node)) return;
|
|
22467
22137
|
visit(node);
|
|
22468
22138
|
visited.add(node);
|
|
22469
|
-
const keys =
|
|
22139
|
+
const keys = getChildKeys(node);
|
|
22470
22140
|
const record = node;
|
|
22471
22141
|
for (const key of keys) {
|
|
22472
22142
|
const child = record[key];
|
|
@@ -22499,11 +22169,16 @@ const findDownstreamNodes = (topNode, type) => {
|
|
|
22499
22169
|
});
|
|
22500
22170
|
return nodes;
|
|
22501
22171
|
};
|
|
22172
|
+
const refByIdentifierCache = /* @__PURE__ */ new WeakMap();
|
|
22502
22173
|
const getRef = (analysis, identifier) => {
|
|
22503
|
-
|
|
22174
|
+
let refByIdentifier = refByIdentifierCache.get(analysis);
|
|
22175
|
+
if (!refByIdentifier) {
|
|
22176
|
+
refByIdentifier = /* @__PURE__ */ new WeakMap();
|
|
22177
|
+
refByIdentifierCache.set(analysis, refByIdentifier);
|
|
22178
|
+
}
|
|
22504
22179
|
if (refByIdentifier.has(identifier)) return refByIdentifier.get(identifier) ?? null;
|
|
22505
22180
|
let resolvedReference = null;
|
|
22506
|
-
const scope = getScopeForNode(identifier, analysis);
|
|
22181
|
+
const scope = getScopeForNode(identifier, analysis.scopeManager);
|
|
22507
22182
|
if (scope) {
|
|
22508
22183
|
for (const reference of scope.references) if (reference.identifier === identifier) {
|
|
22509
22184
|
resolvedReference = reference;
|
|
@@ -22751,7 +22426,7 @@ const isReactFunctionalHOC = (analysis, node) => {
|
|
|
22751
22426
|
const isWrappedSeparately = () => {
|
|
22752
22427
|
if (!isNodeOfType(node.id, "Identifier")) return false;
|
|
22753
22428
|
const bindingName = node.id.name;
|
|
22754
|
-
const containingScope = getScopeForNode(node, analysis);
|
|
22429
|
+
const containingScope = getScopeForNode(node, analysis.scopeManager);
|
|
22755
22430
|
if (!containingScope) return false;
|
|
22756
22431
|
const variable = containingScope.variables.find((v) => v.name === bindingName);
|
|
22757
22432
|
if (!variable) return false;
|
|
@@ -22982,14 +22657,11 @@ const hasCleanupReturn = (analysis, node, visited = /* @__PURE__ */ new WeakSet(
|
|
|
22982
22657
|
if (isNodeOfType(node, "ReturnStatement") && node.argument != null) return isCleanupReturnArgument(analysis, node.argument);
|
|
22983
22658
|
if (!isNodeOfType(node, "BlockStatement") && isFunctionLike$1(node)) return false;
|
|
22984
22659
|
const record = node;
|
|
22985
|
-
const
|
|
22986
|
-
|
|
22987
|
-
|
|
22988
|
-
|
|
22989
|
-
|
|
22990
|
-
if (isAstNode(item) && hasCleanupReturn(analysis, item, visited)) return true;
|
|
22991
|
-
}
|
|
22992
|
-
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;
|
|
22993
22665
|
}
|
|
22994
22666
|
return false;
|
|
22995
22667
|
};
|
|
@@ -23217,73 +22889,61 @@ const PURE_GLOBAL_CALLEE_NAMES = new Set([
|
|
|
23217
22889
|
"Array",
|
|
23218
22890
|
"BigInt",
|
|
23219
22891
|
"Boolean",
|
|
23220
|
-
"encodeURIComponent",
|
|
23221
22892
|
"Number",
|
|
23222
22893
|
"Object",
|
|
23223
22894
|
"String",
|
|
23224
22895
|
"parseFloat",
|
|
23225
|
-
"parseInt"
|
|
23226
|
-
"structuredClone"
|
|
23227
|
-
]);
|
|
23228
|
-
const PURE_GLOBAL_CONSTRUCTOR_NAMES = new Set(["Date", "Set"]);
|
|
23229
|
-
const PURE_HELPER_NAMESPACE_MEMBER_NAMES = new Map([
|
|
23230
|
-
["Array", new Set(["from"])],
|
|
23231
|
-
["JSON", new Set([
|
|
23232
|
-
"isRawJSON",
|
|
23233
|
-
"parse",
|
|
23234
|
-
"rawJSON",
|
|
23235
|
-
"stringify"
|
|
23236
|
-
])],
|
|
23237
|
-
["Math", new Set([
|
|
23238
|
-
"abs",
|
|
23239
|
-
"acos",
|
|
23240
|
-
"acosh",
|
|
23241
|
-
"asin",
|
|
23242
|
-
"asinh",
|
|
23243
|
-
"atan",
|
|
23244
|
-
"atan2",
|
|
23245
|
-
"atanh",
|
|
23246
|
-
"cbrt",
|
|
23247
|
-
"ceil",
|
|
23248
|
-
"clz32",
|
|
23249
|
-
"cos",
|
|
23250
|
-
"cosh",
|
|
23251
|
-
"exp",
|
|
23252
|
-
"floor",
|
|
23253
|
-
"fround",
|
|
23254
|
-
"hypot",
|
|
23255
|
-
"imul",
|
|
23256
|
-
"log",
|
|
23257
|
-
"log10",
|
|
23258
|
-
"log1p",
|
|
23259
|
-
"log2",
|
|
23260
|
-
"max",
|
|
23261
|
-
"min",
|
|
23262
|
-
"pow",
|
|
23263
|
-
"round",
|
|
23264
|
-
"sign",
|
|
23265
|
-
"sin",
|
|
23266
|
-
"sinh",
|
|
23267
|
-
"sqrt",
|
|
23268
|
-
"tan",
|
|
23269
|
-
"tanh",
|
|
23270
|
-
"trunc"
|
|
23271
|
-
])],
|
|
23272
|
-
["Object", new Set(["assign"])]
|
|
22896
|
+
"parseInt"
|
|
23273
22897
|
]);
|
|
22898
|
+
const PURE_GLOBAL_NAMESPACE_NAMES = new Set(["JSON", "Math"]);
|
|
22899
|
+
const PURE_HELPER_NAMESPACE_MEMBER_NAMES = new Map([["JSON", new Set([
|
|
22900
|
+
"isRawJSON",
|
|
22901
|
+
"parse",
|
|
22902
|
+
"rawJSON",
|
|
22903
|
+
"stringify"
|
|
22904
|
+
])], ["Math", new Set([
|
|
22905
|
+
"abs",
|
|
22906
|
+
"acos",
|
|
22907
|
+
"acosh",
|
|
22908
|
+
"asin",
|
|
22909
|
+
"asinh",
|
|
22910
|
+
"atan",
|
|
22911
|
+
"atan2",
|
|
22912
|
+
"atanh",
|
|
22913
|
+
"cbrt",
|
|
22914
|
+
"ceil",
|
|
22915
|
+
"clz32",
|
|
22916
|
+
"cos",
|
|
22917
|
+
"cosh",
|
|
22918
|
+
"exp",
|
|
22919
|
+
"floor",
|
|
22920
|
+
"fround",
|
|
22921
|
+
"hypot",
|
|
22922
|
+
"imul",
|
|
22923
|
+
"log",
|
|
22924
|
+
"log10",
|
|
22925
|
+
"log1p",
|
|
22926
|
+
"log2",
|
|
22927
|
+
"max",
|
|
22928
|
+
"min",
|
|
22929
|
+
"pow",
|
|
22930
|
+
"round",
|
|
22931
|
+
"sign",
|
|
22932
|
+
"sin",
|
|
22933
|
+
"sinh",
|
|
22934
|
+
"sqrt",
|
|
22935
|
+
"tan",
|
|
22936
|
+
"tanh",
|
|
22937
|
+
"trunc"
|
|
22938
|
+
])]]);
|
|
23274
22939
|
const PURE_MEMBER_TRANSFORM_NAMES = new Set([
|
|
23275
22940
|
"concat",
|
|
23276
22941
|
"filter",
|
|
23277
|
-
"flatMap",
|
|
23278
22942
|
"join",
|
|
23279
22943
|
"map",
|
|
23280
|
-
"reduce",
|
|
23281
|
-
"replace",
|
|
23282
|
-
"slice",
|
|
23283
22944
|
"split",
|
|
23284
22945
|
"toLowerCase",
|
|
23285
22946
|
"toString",
|
|
23286
|
-
"toSorted",
|
|
23287
22947
|
"toUpperCase",
|
|
23288
22948
|
"trim"
|
|
23289
22949
|
]);
|
|
@@ -23292,7 +22952,7 @@ const STORAGE_GLOBAL_NAMES$1 = new Set([
|
|
|
23292
22952
|
"localStorage",
|
|
23293
22953
|
"sessionStorage"
|
|
23294
22954
|
]);
|
|
23295
|
-
const getStaticMemberName
|
|
22955
|
+
const getStaticMemberName = (node) => {
|
|
23296
22956
|
if (!isNodeOfType(node, "MemberExpression")) return null;
|
|
23297
22957
|
if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
|
|
23298
22958
|
if (node.computed && isNodeOfType(node.property, "Literal")) return typeof node.property.value === "string" ? node.property.value : null;
|
|
@@ -23307,7 +22967,7 @@ const getCallCalleeName$1 = (callExpression) => {
|
|
|
23307
22967
|
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
23308
22968
|
const callee = stripParenExpression(callExpression.callee);
|
|
23309
22969
|
if (isNodeOfType(callee, "Identifier")) return callee.name;
|
|
23310
|
-
return getStaticMemberName
|
|
22970
|
+
return getStaticMemberName(callee);
|
|
23311
22971
|
};
|
|
23312
22972
|
const getFunctionParameters = (functionNode) => isFunctionLike$1(functionNode) ? functionNode.params ?? [] : [];
|
|
23313
22973
|
const getIdentifierBindingIdentity = (analysis, identifier) => {
|
|
@@ -23423,14 +23083,14 @@ const localUseEventPreservesCallback = (analysis, callee) => {
|
|
|
23423
23083
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
23424
23084
|
const forwardedCallee = stripParenExpression(child.callee);
|
|
23425
23085
|
if (!isNodeOfType(forwardedCallee, "MemberExpression")) return;
|
|
23426
|
-
if (getStaticMemberName
|
|
23086
|
+
if (getStaticMemberName(forwardedCallee) !== "current") return;
|
|
23427
23087
|
if (!isNodeOfType(forwardedCallee.object, "Identifier")) return;
|
|
23428
23088
|
const refReference = getRef(analysis, forwardedCallee.object);
|
|
23429
23089
|
if (!callbackRefDeclarators.find((declarator) => refReference?.resolved?.defs.some((definition) => definition.node === declarator)) || !refReference?.resolved) return;
|
|
23430
23090
|
if (!refReference.resolved.references.some((candidateReference) => {
|
|
23431
23091
|
const memberExpression = candidateReference.identifier.parent;
|
|
23432
23092
|
const assignmentExpression = memberExpression?.parent;
|
|
23433
|
-
if (!memberExpression || !isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName
|
|
23093
|
+
if (!memberExpression || !isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !assignmentExpression || !isNodeOfType(assignmentExpression, "AssignmentExpression") || assignmentExpression.left !== memberExpression) return false;
|
|
23434
23094
|
return getIdentifierBindingIdentity(analysis, assignmentExpression.right) !== callbackBinding;
|
|
23435
23095
|
})) forwardsCallback = true;
|
|
23436
23096
|
});
|
|
@@ -23455,7 +23115,7 @@ const resolveWrappedCallable = (analysis, node) => {
|
|
|
23455
23115
|
return callback && isFunctionLike$1(callback) ? callback : null;
|
|
23456
23116
|
}
|
|
23457
23117
|
if (!isNodeOfType(candidate, "MemberExpression")) return null;
|
|
23458
|
-
if (getStaticMemberName
|
|
23118
|
+
if (getStaticMemberName(candidate) !== "current") return null;
|
|
23459
23119
|
if (!isNodeOfType(candidate.object, "Identifier")) return null;
|
|
23460
23120
|
const reference = getRef(analysis, candidate.object);
|
|
23461
23121
|
const declarator = reference?.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
@@ -23467,7 +23127,7 @@ const resolveWrappedCallable = (analysis, node) => {
|
|
|
23467
23127
|
return Boolean(reference?.resolved?.references.some((candidateReference) => {
|
|
23468
23128
|
const member = candidateReference.identifier.parent;
|
|
23469
23129
|
const assignment = member?.parent;
|
|
23470
|
-
return Boolean(member && isNodeOfType(member, "MemberExpression") && getStaticMemberName
|
|
23130
|
+
return Boolean(member && isNodeOfType(member, "MemberExpression") && getStaticMemberName(member) === "current" && assignment && isNodeOfType(assignment, "AssignmentExpression") && assignment.left === member);
|
|
23471
23131
|
})) ? null : initializer;
|
|
23472
23132
|
};
|
|
23473
23133
|
const functionInvokesItself = (analysis, functionNode) => {
|
|
@@ -23506,7 +23166,7 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilena
|
|
|
23506
23166
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
23507
23167
|
const callee = stripParenExpression(child.callee);
|
|
23508
23168
|
const calleeName = getCallCalleeName$1(child);
|
|
23509
|
-
const memberName = getStaticMemberName
|
|
23169
|
+
const memberName = getStaticMemberName(callee);
|
|
23510
23170
|
const isDeferringCall = calleeName !== null && DEFERRING_CALLEE_NAMES.has(calleeName) || memberName !== null && DEFERRING_MEMBER_NAMES.has(memberName);
|
|
23511
23171
|
const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES$1.has(memberName) && isNodeOfType(callee, "MemberExpression");
|
|
23512
23172
|
const enqueueFrame = (callableNode, argumentsForCallable, isDeferred, introducedBindings, allowWithoutInvocationEvidence = false) => {
|
|
@@ -23665,19 +23325,14 @@ const analyzeHelperExpression = (expression, environment, usedParameterIndices)
|
|
|
23665
23325
|
const callbackSummary = analyzeHelperStatements(node.body.body ?? [], callbackEnvironment, usedParameterIndices, analyzeHelperExpression);
|
|
23666
23326
|
return callbackSummary.isValid && !callbackSummary.canContinue;
|
|
23667
23327
|
}
|
|
23668
|
-
if (isNodeOfType(node, "NewExpression")) {
|
|
23669
|
-
const callee = stripParenExpression(node.callee);
|
|
23670
|
-
if (!isNodeOfType(callee, "Identifier") || !PURE_GLOBAL_CONSTRUCTOR_NAMES.has(callee.name) || environment.parameterIndices.has(callee.name) || environment.recursiveNames.has(callee.name) || environment.shadowedGlobalNames.has(callee.name)) return false;
|
|
23671
|
-
return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
|
|
23672
|
-
}
|
|
23673
23328
|
if (isNodeOfType(node, "CallExpression")) {
|
|
23674
23329
|
const callee = stripParenExpression(node.callee);
|
|
23675
23330
|
const calleeRoot = getMemberRoot(callee);
|
|
23676
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);
|
|
23677
23332
|
const namespaceName = isNodeOfType(calleeRoot, "Identifier") && !environment.parameterIndices.has(calleeRoot.name) && !environment.recursiveNames.has(calleeRoot.name) && !environment.shadowedGlobalNames.has(calleeRoot.name) ? calleeRoot.name : null;
|
|
23678
|
-
const namespaceMemberName = getStaticMemberName
|
|
23333
|
+
const namespaceMemberName = getStaticMemberName(callee);
|
|
23679
23334
|
const isPureNamespaceCall = isNodeOfType(callee, "MemberExpression") && namespaceName !== null && namespaceMemberName !== null && PURE_HELPER_NAMESPACE_MEMBER_NAMES.get(namespaceName)?.has(namespaceMemberName) === true;
|
|
23680
|
-
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName
|
|
23335
|
+
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
|
|
23681
23336
|
if (!isPureGlobalCall && !isPureNamespaceCall && !isPureMemberTransform) return false;
|
|
23682
23337
|
if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression") && !analyzeHelperExpression(callee.object, environment, usedParameterIndices)) return false;
|
|
23683
23338
|
return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
|
|
@@ -23751,7 +23406,7 @@ const isLocallyConstructedObjectMember = (reference, memberExpression) => isNode
|
|
|
23751
23406
|
return isNodeOfType(definitionNode, "VariableDeclarator") && Boolean(definitionNode.init) && (isNodeOfType(definitionNode.init, "ObjectExpression") || isNodeOfType(definitionNode.init, "ArrayExpression"));
|
|
23752
23407
|
}) === true;
|
|
23753
23408
|
const getUseRefDeclarator = (analysis, memberExpression) => {
|
|
23754
|
-
if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName
|
|
23409
|
+
if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !isNodeOfType(memberExpression.object, "Identifier")) return null;
|
|
23755
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;
|
|
23756
23411
|
};
|
|
23757
23412
|
const collectValueEvidence = (analysis, expression, frame, remainingCallFrames, visitedBindings = /* @__PURE__ */ new Set()) => {
|
|
@@ -23820,7 +23475,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23820
23475
|
return collectValueEvidence(analysis, initializer.init, frame, remainingCallFrames, visitedBindings);
|
|
23821
23476
|
}
|
|
23822
23477
|
if (isNodeOfType(node, "MemberExpression")) {
|
|
23823
|
-
if (getStaticMemberName
|
|
23478
|
+
if (getStaticMemberName(node) === "current" && isNodeOfType(node.object, "Identifier")) {
|
|
23824
23479
|
const refBinding = getRef(analysis, node.object)?.resolved;
|
|
23825
23480
|
const refDeclarator = useRefDeclarator;
|
|
23826
23481
|
if (refBinding && refDeclarator && isNodeOfType(refDeclarator, "VariableDeclarator") && isNodeOfType(refDeclarator.init, "CallExpression")) {
|
|
@@ -23836,7 +23491,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23836
23491
|
if (candidateReference.init) continue;
|
|
23837
23492
|
const identifier = candidateReference.identifier;
|
|
23838
23493
|
const member = identifier.parent;
|
|
23839
|
-
if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName
|
|
23494
|
+
if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName(member) !== "current") {
|
|
23840
23495
|
evidence.hasUnknownSource = true;
|
|
23841
23496
|
continue;
|
|
23842
23497
|
}
|
|
@@ -23872,8 +23527,8 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23872
23527
|
}
|
|
23873
23528
|
const callee = stripParenExpression(node.callee);
|
|
23874
23529
|
const calleeRoot = getMemberRoot(callee);
|
|
23875
|
-
const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && getIdentifierBindingIdentity(analysis, callee) === null || isNodeOfType(
|
|
23876
|
-
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName
|
|
23530
|
+
const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && getIdentifierBindingIdentity(analysis, callee) === null || isNodeOfType(calleeRoot, "Identifier") && PURE_GLOBAL_NAMESPACE_NAMES.has(calleeRoot.name) && getIdentifierBindingIdentity(analysis, calleeRoot) === null;
|
|
23531
|
+
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
|
|
23877
23532
|
if (isPureGlobalCall || isPureMemberTransform) {
|
|
23878
23533
|
if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression")) mergeEvidence(evidence, collectValueEvidence(analysis, callee.object, frame, remainingCallFrames, new Set(visitedBindings)));
|
|
23879
23534
|
for (const argument of node.arguments ?? []) {
|
|
@@ -23925,15 +23580,6 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23925
23580
|
}
|
|
23926
23581
|
return evidence;
|
|
23927
23582
|
}
|
|
23928
|
-
if (isNodeOfType(node, "NewExpression")) {
|
|
23929
|
-
const callee = stripParenExpression(node.callee);
|
|
23930
|
-
if (!isNodeOfType(callee, "Identifier") || !PURE_GLOBAL_CONSTRUCTOR_NAMES.has(callee.name) || getIdentifierBindingIdentity(analysis, callee) !== null) {
|
|
23931
|
-
evidence.hasUnknownSource = true;
|
|
23932
|
-
return evidence;
|
|
23933
|
-
}
|
|
23934
|
-
for (const argument of node.arguments ?? []) mergeEvidence(evidence, collectValueEvidence(analysis, argument, frame, remainingCallFrames, new Set(visitedBindings)));
|
|
23935
|
-
return evidence;
|
|
23936
|
-
}
|
|
23937
23583
|
if (isFunctionLike$1(node) || isNodeOfType(node, "AwaitExpression")) {
|
|
23938
23584
|
evidence.hasUnknownSource = true;
|
|
23939
23585
|
return evidence;
|
|
@@ -24352,26 +23998,6 @@ const STATEFUL_HTML_DESCENDANT_TAGS = new Set([
|
|
|
24352
23998
|
"dialog",
|
|
24353
23999
|
"canvas"
|
|
24354
24000
|
]);
|
|
24355
|
-
const STATEFUL_HTML_ATTRIBUTE_NAMES = new Set([
|
|
24356
|
-
"autofocus",
|
|
24357
|
-
"contenteditable",
|
|
24358
|
-
"draggable",
|
|
24359
|
-
"tabindex"
|
|
24360
|
-
]);
|
|
24361
|
-
const isStaticallyFalseAttributeValue = (attribute) => {
|
|
24362
|
-
if (!isNodeOfType(attribute, "JSXAttribute") || !attribute.value) return false;
|
|
24363
|
-
const value = isNodeOfType(attribute.value, "JSXExpressionContainer") ? attribute.value.expression : attribute.value;
|
|
24364
|
-
return isNodeOfType(value, "Literal") && (value.value === false || value.value === "false");
|
|
24365
|
-
};
|
|
24366
|
-
const hasStatefulHtmlAttribute = (openingElement) => {
|
|
24367
|
-
if (!isNodeOfType(openingElement, "JSXOpeningElement")) return false;
|
|
24368
|
-
return openingElement.attributes.some((attribute) => {
|
|
24369
|
-
if (!isNodeOfType(attribute, "JSXAttribute") || !isNodeOfType(attribute.name, "JSXIdentifier")) return false;
|
|
24370
|
-
const attributeName = attribute.name.name.toLowerCase();
|
|
24371
|
-
if (!STATEFUL_HTML_ATTRIBUTE_NAMES.has(attributeName)) return false;
|
|
24372
|
-
return attributeName === "tabindex" || !isStaticallyFalseAttributeValue(attribute);
|
|
24373
|
-
});
|
|
24374
|
-
};
|
|
24375
24001
|
const STATEFUL_DESCENDANT_SCAN_BUDGET = 200;
|
|
24376
24002
|
const rootIdentifierNameOf = (expression) => {
|
|
24377
24003
|
let object = expression;
|
|
@@ -24389,8 +24015,7 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
|
|
|
24389
24015
|
budget -= 1;
|
|
24390
24016
|
const node = stack.pop();
|
|
24391
24017
|
if (isNodeOfType(node, "JSXElement")) {
|
|
24392
|
-
const
|
|
24393
|
-
const name = opening.name;
|
|
24018
|
+
const name = node.openingElement.name;
|
|
24394
24019
|
if (name && isNodeOfType(name, "JSXIdentifier")) {
|
|
24395
24020
|
const tagName = name.name;
|
|
24396
24021
|
const firstChar = tagName.charCodeAt(0);
|
|
@@ -24398,7 +24023,6 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
|
|
|
24398
24023
|
if (STATEFUL_HTML_DESCENDANT_TAGS.has(tagName)) return true;
|
|
24399
24024
|
}
|
|
24400
24025
|
if (name && isNodeOfType(name, "JSXMemberExpression")) return true;
|
|
24401
|
-
if (hasStatefulHtmlAttribute(opening)) return true;
|
|
24402
24026
|
const children = node.children ?? [];
|
|
24403
24027
|
for (const child of children) stack.push(child);
|
|
24404
24028
|
continue;
|
|
@@ -24492,7 +24116,6 @@ const ARITHMETIC_KEY_OPERATORS = new Set([
|
|
|
24492
24116
|
"/",
|
|
24493
24117
|
"%"
|
|
24494
24118
|
]);
|
|
24495
|
-
const bindingMutationResultsByProgram = /* @__PURE__ */ new WeakMap();
|
|
24496
24119
|
const extractCandidateIdentifiers = (expression) => {
|
|
24497
24120
|
const node = stripParenExpression(expression);
|
|
24498
24121
|
if (isNodeOfType(node, "Identifier")) return [node];
|
|
@@ -24533,13 +24156,6 @@ const isArrayFromCall = (node) => {
|
|
|
24533
24156
|
const isBindingReassignedOrMutated = (referenceNode, bindingName) => {
|
|
24534
24157
|
const programRoot = findProgramRoot(referenceNode);
|
|
24535
24158
|
if (!programRoot) return false;
|
|
24536
|
-
let mutationResultsByBindingName = bindingMutationResultsByProgram.get(programRoot);
|
|
24537
|
-
if (!mutationResultsByBindingName) {
|
|
24538
|
-
mutationResultsByBindingName = /* @__PURE__ */ new Map();
|
|
24539
|
-
bindingMutationResultsByProgram.set(programRoot, mutationResultsByBindingName);
|
|
24540
|
-
}
|
|
24541
|
-
const cachedResult = mutationResultsByBindingName.get(bindingName);
|
|
24542
|
-
if (cachedResult !== void 0) return cachedResult;
|
|
24543
24159
|
let didFindWrite = false;
|
|
24544
24160
|
walkAst(programRoot, (child) => {
|
|
24545
24161
|
if (didFindWrite) return false;
|
|
@@ -24556,7 +24172,6 @@ const isBindingReassignedOrMutated = (referenceNode, bindingName) => {
|
|
|
24556
24172
|
return false;
|
|
24557
24173
|
}
|
|
24558
24174
|
});
|
|
24559
|
-
mutationResultsByBindingName.set(bindingName, didFindWrite);
|
|
24560
24175
|
return didFindWrite;
|
|
24561
24176
|
};
|
|
24562
24177
|
/**
|
|
@@ -24958,14 +24573,6 @@ const templateHasOuterMemberIdentity = (template, bindingFunction) => {
|
|
|
24958
24573
|
}
|
|
24959
24574
|
return false;
|
|
24960
24575
|
};
|
|
24961
|
-
const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
|
|
24962
|
-
const referencedItemNames = /* @__PURE__ */ new Set();
|
|
24963
|
-
for (const expression of template.expressions ?? []) {
|
|
24964
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
24965
|
-
if (isNodeOfType(unwrappedExpression, "Identifier") && itemNames.has(unwrappedExpression.name)) referencedItemNames.add(unwrappedExpression.name);
|
|
24966
|
-
}
|
|
24967
|
-
return referencedItemNames;
|
|
24968
|
-
};
|
|
24969
24576
|
const forLoopTestReadsDataLength = (test) => {
|
|
24970
24577
|
let didFindLengthRead = false;
|
|
24971
24578
|
walkAst(test, (child) => {
|
|
@@ -25102,11 +24709,9 @@ const noArrayIndexAsKey = defineRule({
|
|
|
25102
24709
|
} else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
|
|
25103
24710
|
const jsxElement = openingElement.parent;
|
|
25104
24711
|
if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
|
|
25105
|
-
const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
|
|
25106
|
-
const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET;
|
|
25107
24712
|
if (!containsStatefulDescendant(jsxElement, {
|
|
25108
|
-
memberRootNames:
|
|
25109
|
-
bareIdentifierNames:
|
|
24713
|
+
memberRootNames: INLINE_TEXT_LEAF_TAGS.has(elementName.name) ? itemNames : EMPTY_NAME_SET,
|
|
24714
|
+
bareIdentifierNames: derivedNames
|
|
25110
24715
|
})) return;
|
|
25111
24716
|
}
|
|
25112
24717
|
}
|
|
@@ -25274,11 +24879,7 @@ const noAsyncEffectCallback = defineRule({
|
|
|
25274
24879
|
severity: "warn",
|
|
25275
24880
|
recommendation: "Don't make the effect callback `async`. Define an async function inside the effect and call it, then return a real cleanup function if you need one.",
|
|
25276
24881
|
create: (context) => ({ CallExpression(node) {
|
|
25277
|
-
if (!
|
|
25278
|
-
allowGlobalReactNamespace: true,
|
|
25279
|
-
allowUnboundBareCalls: true,
|
|
25280
|
-
resolveNamedAliases: true
|
|
25281
|
-
})) return;
|
|
24882
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
25282
24883
|
const callback = getEffectCallback(node);
|
|
25283
24884
|
if (!callback) return;
|
|
25284
24885
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
@@ -25671,10 +25272,10 @@ const declaresAnyDependency = (manifest) => DEPENDENCY_SECTION_NAMES.some((secti
|
|
|
25671
25272
|
const section = manifest[sectionName];
|
|
25672
25273
|
return typeof section === "object" && section !== null && Object.keys(section).length > 0;
|
|
25673
25274
|
});
|
|
25674
|
-
const declaresDependency = (manifest, dependencyName) =>
|
|
25675
|
-
const
|
|
25676
|
-
return
|
|
25677
|
-
}
|
|
25275
|
+
const declaresDependency = (manifest, dependencyName) => {
|
|
25276
|
+
for (const declaredName of iterateDependencyNames(manifest)) if (declaredName === dependencyName) return true;
|
|
25277
|
+
return false;
|
|
25278
|
+
};
|
|
25678
25279
|
const cachedPlatformByManifest = /* @__PURE__ */ new WeakMap();
|
|
25679
25280
|
const classifyPackagePlatform = (filename) => {
|
|
25680
25281
|
const manifest = readNearestPackageManifest(filename);
|
|
@@ -25921,23 +25522,25 @@ const isCallArgumentFunctionExpression = (node) => {
|
|
|
25921
25522
|
return parent.arguments.some((argumentNode) => argumentNode === node);
|
|
25922
25523
|
};
|
|
25923
25524
|
const isNestedRenderEvidenceBoundary = (node) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isCallArgumentFunctionExpression(node);
|
|
25924
|
-
const containsRenderOutput$1 = (rootNode, scopes) => {
|
|
25925
|
-
|
|
25926
|
-
|
|
25927
|
-
|
|
25928
|
-
|
|
25929
|
-
|
|
25930
|
-
|
|
25931
|
-
|
|
25932
|
-
|
|
25933
|
-
|
|
25934
|
-
|
|
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;
|
|
25935
25538
|
};
|
|
25936
25539
|
const renderOutputCache = /* @__PURE__ */ new WeakMap();
|
|
25937
25540
|
const functionContainsReactRenderOutput = (functionNode, scopes) => {
|
|
25938
25541
|
const cachedEntry = renderOutputCache.get(functionNode);
|
|
25939
25542
|
if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
|
|
25940
|
-
const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
|
|
25543
|
+
const hasRenderOutput = containsRenderOutput$1(functionNode, functionNode, scopes);
|
|
25941
25544
|
renderOutputCache.set(functionNode, {
|
|
25942
25545
|
scopes,
|
|
25943
25546
|
hasRenderOutput
|
|
@@ -26476,19 +26079,6 @@ const noCloneElement = defineRule({
|
|
|
26476
26079
|
} })
|
|
26477
26080
|
});
|
|
26478
26081
|
//#endregion
|
|
26479
|
-
//#region src/plugin/utils/get-call-method-name.ts
|
|
26480
|
-
/**
|
|
26481
|
-
* Returns the static method name of a call's callee when it's a
|
|
26482
|
-
* non-computed MemberExpression (`obj.method` → `"method"`), or
|
|
26483
|
-
* `null` otherwise. Used by `no-pass-data-to-parent` and
|
|
26484
|
-
* `no-pass-live-state-to-parent` to match the receiver-bound
|
|
26485
|
-
* method-call shape against an allow/block list.
|
|
26486
|
-
*/
|
|
26487
|
-
const getCallMethodName = (callee) => {
|
|
26488
|
-
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
|
|
26489
|
-
return null;
|
|
26490
|
-
};
|
|
26491
|
-
//#endregion
|
|
26492
26082
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
26493
26083
|
const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
26494
26084
|
const CONTEXT_MODULES = [
|
|
@@ -26496,35 +26086,23 @@ const CONTEXT_MODULES = [
|
|
|
26496
26086
|
"use-context-selector",
|
|
26497
26087
|
"react-tracked"
|
|
26498
26088
|
];
|
|
26499
|
-
const
|
|
26500
|
-
if (symbol?.kind !== "import") return null;
|
|
26501
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
26502
|
-
if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration") || typeof importDeclaration.source.value !== "string" || !CONTEXT_MODULES.includes(importDeclaration.source.value)) return null;
|
|
26503
|
-
return importDeclaration.source.value;
|
|
26504
|
-
};
|
|
26505
|
-
const isSupportedNamespaceSymbol = (symbol) => getSupportedContextImportSource(symbol) !== null && Boolean(symbol && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
|
|
26506
|
-
const isDestructuredCreateContextBinding = (identifier, scopes) => {
|
|
26507
|
-
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
26508
|
-
const symbol = scopes.symbolFor(identifier);
|
|
26509
|
-
if (symbol?.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "ObjectPattern")) return false;
|
|
26510
|
-
const property = symbol.bindingIdentifier.parent;
|
|
26511
|
-
if (!property || !isNodeOfType(property, "Property") || getStaticPropertyKeyName(property, { allowComputedString: true }) !== "createContext") return false;
|
|
26512
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
26513
|
-
return isNodeOfType(initializer, "Identifier") && isSupportedNamespaceSymbol(resolveConstIdentifierAlias(initializer, scopes));
|
|
26514
|
-
};
|
|
26515
|
-
const isCreateContextCall = (node, scopes) => {
|
|
26516
|
-
const callee = stripParenExpression(node.callee);
|
|
26089
|
+
const isCreateContextCallee = (callee) => {
|
|
26517
26090
|
if (isNodeOfType(callee, "Identifier")) {
|
|
26518
|
-
|
|
26519
|
-
|
|
26520
|
-
return getSupportedContextImportSource(symbol) !== null && Boolean(symbol && getImportedName(symbol.declarationNode) === "createContext");
|
|
26091
|
+
const binding = getImportBindingForName(callee, callee.name);
|
|
26092
|
+
return binding !== null && binding.exportedName === "createContext" && CONTEXT_MODULES.includes(binding.source);
|
|
26521
26093
|
}
|
|
26522
|
-
if (
|
|
26523
|
-
|
|
26524
|
-
|
|
26525
|
-
|
|
26526
|
-
|
|
26527
|
-
|
|
26094
|
+
if (isNodeOfType(callee, "MemberExpression") && !callee.computed) {
|
|
26095
|
+
const namespaceIdentifier = callee.object;
|
|
26096
|
+
const propertyIdentifier = callee.property;
|
|
26097
|
+
if (!isNodeOfType(namespaceIdentifier, "Identifier")) return false;
|
|
26098
|
+
if (!isNodeOfType(propertyIdentifier, "Identifier")) return false;
|
|
26099
|
+
if (propertyIdentifier.name !== "createContext") return false;
|
|
26100
|
+
const namespaceName = namespaceIdentifier.name;
|
|
26101
|
+
if (isCanonicalReactNamespaceName(namespaceName)) return true;
|
|
26102
|
+
const importSource = getImportSourceForName(namespaceIdentifier, namespaceName);
|
|
26103
|
+
return importSource !== null && CONTEXT_MODULES.includes(importSource);
|
|
26104
|
+
}
|
|
26105
|
+
return false;
|
|
26528
26106
|
};
|
|
26529
26107
|
const noCreateContextInRender = defineRule({
|
|
26530
26108
|
id: "no-create-context-in-render",
|
|
@@ -26533,7 +26111,7 @@ const noCreateContextInRender = defineRule({
|
|
|
26533
26111
|
category: "Correctness",
|
|
26534
26112
|
recommendation: "Move `createContext(...)` outside the component, to the top level of the file, so it stays the same on every render.",
|
|
26535
26113
|
create: (context) => ({ CallExpression(node) {
|
|
26536
|
-
if (!
|
|
26114
|
+
if (!isCreateContextCallee(node.callee)) return;
|
|
26537
26115
|
const componentOrHookName = enclosingComponentOrHookName(node);
|
|
26538
26116
|
if (!componentOrHookName) return;
|
|
26539
26117
|
context.report({
|
|
@@ -27768,7 +27346,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
27768
27346
|
let ancestor = setStateCall.parent;
|
|
27769
27347
|
while (ancestor) {
|
|
27770
27348
|
if (!lifecycleMember) {
|
|
27771
|
-
if (FUNCTION_NODE_TYPES$1.has(ancestor.type)
|
|
27349
|
+
if (FUNCTION_NODE_TYPES$1.has(ancestor.type)) nestedFunctionCount += 1;
|
|
27772
27350
|
if (isLifecycleMember(ancestor, lifecycleNames)) lifecycleMember = ancestor;
|
|
27773
27351
|
} else if (isEs5Component(ancestor) || isEs6Component(ancestor)) {
|
|
27774
27352
|
if (nestedFunctionCount > 1 && !options.disallowInNestedFunctions) return false;
|
|
@@ -27970,7 +27548,7 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
|
|
|
27970
27548
|
const body = lifecycleFunction.body;
|
|
27971
27549
|
if (!body) return derivedNames;
|
|
27972
27550
|
walkAst(body, (node) => {
|
|
27973
|
-
if (FUNCTION_NODE_TYPES.has(node.type)
|
|
27551
|
+
if (FUNCTION_NODE_TYPES.has(node.type)) return false;
|
|
27974
27552
|
if (!isNodeOfType(node, "VariableDeclarator")) return;
|
|
27975
27553
|
const init = node.init;
|
|
27976
27554
|
if (!init) return;
|
|
@@ -27980,67 +27558,6 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
|
|
|
27980
27558
|
return derivedNames;
|
|
27981
27559
|
};
|
|
27982
27560
|
const isStatefulOperand = (node, paramNames, derivedNames) => referencesAnyName(node, paramNames) || referencesAnyName(node, derivedNames) || containsThisStateOrProps(node);
|
|
27983
|
-
const getStaticMemberName = (node) => {
|
|
27984
|
-
if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
|
|
27985
|
-
return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
|
|
27986
|
-
};
|
|
27987
|
-
const getThisStateFieldName = (node) => {
|
|
27988
|
-
const unwrappedNode = stripParenExpression(node);
|
|
27989
|
-
if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
|
|
27990
|
-
const object = stripParenExpression(unwrappedNode.object);
|
|
27991
|
-
if (!isNodeOfType(object, "MemberExpression") || !isNodeOfType(stripParenExpression(object.object), "ThisExpression") || getStaticMemberName(object) !== "state") return null;
|
|
27992
|
-
return getStaticMemberName(unwrappedNode);
|
|
27993
|
-
};
|
|
27994
|
-
const collectLocalInitializers = (lifecycleFunction) => {
|
|
27995
|
-
const initializers = /* @__PURE__ */ new Map();
|
|
27996
|
-
const body = lifecycleFunction.body;
|
|
27997
|
-
if (!body) return initializers;
|
|
27998
|
-
walkAst(body, (node) => {
|
|
27999
|
-
if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
|
|
28000
|
-
if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.init) initializers.set(node.id.name, node.init);
|
|
28001
|
-
});
|
|
28002
|
-
return initializers;
|
|
28003
|
-
};
|
|
28004
|
-
const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
|
|
28005
|
-
if (readsPostMountValue(node)) return true;
|
|
28006
|
-
const referencedNames = /* @__PURE__ */ new Set();
|
|
28007
|
-
collectReferenceIdentifierNames(node, referencedNames);
|
|
28008
|
-
for (const referencedName of referencedNames) {
|
|
28009
|
-
if (visitedNames.has(referencedName)) continue;
|
|
28010
|
-
const initializer = localInitializers.get(referencedName);
|
|
28011
|
-
if (!initializer) continue;
|
|
28012
|
-
if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
|
|
28013
|
-
}
|
|
28014
|
-
return false;
|
|
28015
|
-
};
|
|
28016
|
-
const getSetStateFieldValue = (setStateCall, fieldName) => {
|
|
28017
|
-
if (!isNodeOfType(setStateCall, "CallExpression")) return null;
|
|
28018
|
-
const argument = setStateCall.arguments?.[0];
|
|
28019
|
-
if (!argument || !isNodeOfType(argument, "ObjectExpression")) return null;
|
|
28020
|
-
for (const property of argument.properties ?? []) {
|
|
28021
|
-
if (!isNodeOfType(property, "Property") || property.computed === true) continue;
|
|
28022
|
-
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;
|
|
28023
|
-
}
|
|
28024
|
-
return null;
|
|
28025
|
-
};
|
|
28026
|
-
const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
|
|
28027
|
-
let qualifies = false;
|
|
28028
|
-
walkAst(test, (node) => {
|
|
28029
|
-
if (qualifies) return false;
|
|
28030
|
-
if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
|
|
28031
|
-
const leftFieldName = getThisStateFieldName(node.left);
|
|
28032
|
-
const rightFieldName = getThisStateFieldName(node.right);
|
|
28033
|
-
const fieldName = leftFieldName ?? rightFieldName;
|
|
28034
|
-
const comparedValue = leftFieldName ? node.right : node.left;
|
|
28035
|
-
if (!fieldName || !leftFieldName && !rightFieldName) return;
|
|
28036
|
-
const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
|
|
28037
|
-
if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
|
|
28038
|
-
if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
|
|
28039
|
-
qualifies = true;
|
|
28040
|
-
return false;
|
|
28041
|
-
});
|
|
28042
|
-
return qualifies;
|
|
28043
|
-
};
|
|
28044
27561
|
const isDiffGuardTest = (test, paramNames, derivedNames) => {
|
|
28045
27562
|
if (referencesAnyName(test, paramNames)) return true;
|
|
28046
27563
|
let qualifies = false;
|
|
@@ -28048,7 +27565,7 @@ const isDiffGuardTest = (test, paramNames, derivedNames) => {
|
|
|
28048
27565
|
if (qualifies) return false;
|
|
28049
27566
|
if (!isNodeOfType(node, "BinaryExpression")) return;
|
|
28050
27567
|
if (!EQUALITY_OPERATORS.has(node.operator)) return;
|
|
28051
|
-
if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames)
|
|
27568
|
+
if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames)) {
|
|
28052
27569
|
qualifies = true;
|
|
28053
27570
|
return false;
|
|
28054
27571
|
}
|
|
@@ -28061,12 +27578,11 @@ const isInsideDiffGuard = (setStateCall) => {
|
|
|
28061
27578
|
const paramNames = /* @__PURE__ */ new Set();
|
|
28062
27579
|
for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
|
|
28063
27580
|
const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
|
|
28064
|
-
const localInitializers = collectLocalInitializers(lifecycleFunction);
|
|
28065
27581
|
let child = setStateCall;
|
|
28066
27582
|
let ancestor = setStateCall.parent;
|
|
28067
27583
|
while (ancestor && ancestor !== lifecycleFunction) {
|
|
28068
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;
|
|
28069
|
-
if (guardTest &&
|
|
27585
|
+
if (guardTest && isDiffGuardTest(guardTest, paramNames, derivedNames)) return true;
|
|
28070
27586
|
child = ancestor;
|
|
28071
27587
|
ancestor = ancestor.parent ?? null;
|
|
28072
27588
|
}
|
|
@@ -28247,16 +27763,7 @@ const producesOpaqueInstanceValue = (expression) => {
|
|
|
28247
27763
|
const collectSetterValueObservations = (componentBody, setterNames) => {
|
|
28248
27764
|
const plainFedSetterNames = /* @__PURE__ */ new Set();
|
|
28249
27765
|
const opaqueFedSetterNames = /* @__PURE__ */ new Set();
|
|
28250
|
-
const callbackRefSetterNames = /* @__PURE__ */ new Set();
|
|
28251
27766
|
walkComponentRespectingShadows(componentBody, /* @__PURE__ */ new Set(), (node, currentlyShadowed) => {
|
|
28252
|
-
if (isNodeOfType(node, "JSXAttribute")) {
|
|
28253
|
-
const attributeName = node.name;
|
|
28254
|
-
if (isNodeOfType(attributeName, "JSXIdentifier") && attributeName.name === "ref" && node.value && isNodeOfType(node.value, "JSXExpressionContainer")) {
|
|
28255
|
-
const expression = stripParenExpression(node.value.expression);
|
|
28256
|
-
if (isNodeOfType(expression, "Identifier") && setterNames.has(expression.name)) callbackRefSetterNames.add(expression.name);
|
|
28257
|
-
}
|
|
28258
|
-
return;
|
|
28259
|
-
}
|
|
28260
27767
|
if (!isNodeOfType(node, "CallExpression")) return;
|
|
28261
27768
|
if (!isNodeOfType(node.callee, "Identifier")) return;
|
|
28262
27769
|
const setterName = node.callee.name;
|
|
@@ -28273,10 +27780,21 @@ const collectSetterValueObservations = (componentBody, setterNames) => {
|
|
|
28273
27780
|
}, true);
|
|
28274
27781
|
return {
|
|
28275
27782
|
plainFedSetterNames,
|
|
28276
|
-
opaqueFedSetterNames
|
|
28277
|
-
callbackRefSetterNames
|
|
27783
|
+
opaqueFedSetterNames
|
|
28278
27784
|
};
|
|
28279
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
|
+
};
|
|
28280
27798
|
const collectFunctionLocalBindings = (functionNode) => {
|
|
28281
27799
|
const localBindings = /* @__PURE__ */ new Set();
|
|
28282
27800
|
if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return localBindings;
|
|
@@ -28288,8 +27806,8 @@ const collectFunctionLocalBindings = (functionNode) => {
|
|
|
28288
27806
|
return localBindings;
|
|
28289
27807
|
};
|
|
28290
27808
|
const collectBlockScopedBindings = (node) => {
|
|
27809
|
+
const blockBindings = /* @__PURE__ */ new Set();
|
|
28291
27810
|
if (isNodeOfType(node, "BlockStatement")) {
|
|
28292
|
-
const blockBindings = /* @__PURE__ */ new Set();
|
|
28293
27811
|
for (const statement of node.body ?? []) {
|
|
28294
27812
|
if (!isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
28295
27813
|
for (const declarator of statement.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
|
|
@@ -28297,22 +27815,17 @@ const collectBlockScopedBindings = (node) => {
|
|
|
28297
27815
|
return blockBindings;
|
|
28298
27816
|
}
|
|
28299
27817
|
if (isNodeOfType(node, "ForStatement") && isNodeOfType(node.init, "VariableDeclaration")) {
|
|
28300
|
-
const blockBindings = /* @__PURE__ */ new Set();
|
|
28301
27818
|
for (const declarator of node.init.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
|
|
28302
27819
|
return blockBindings;
|
|
28303
27820
|
}
|
|
28304
|
-
if ((isNodeOfType(node, "ForOfStatement") || isNodeOfType(node, "ForInStatement")) && isNodeOfType(node.left, "VariableDeclaration"))
|
|
28305
|
-
|
|
28306
|
-
for (const declarator of node.left.declarations ?? []) collectPatternNames(declarator.id, blockBindings);
|
|
28307
|
-
return blockBindings;
|
|
28308
|
-
}
|
|
28309
|
-
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;
|
|
28310
27823
|
};
|
|
28311
27824
|
const walkComponentRespectingShadows = (node, shadowedStateNames, visit, isComponentBodyRoot = false) => {
|
|
28312
27825
|
if (!node || typeof node !== "object") return;
|
|
28313
27826
|
let nextShadowedStateNames = shadowedStateNames;
|
|
28314
|
-
const localBindings = isComponentBodyRoot ?
|
|
28315
|
-
if (localBindings
|
|
27827
|
+
const localBindings = isComponentBodyRoot ? /* @__PURE__ */ new Set() : isFunctionLike$1(node) ? collectFunctionLocalBindings(node) : collectBlockScopedBindings(node);
|
|
27828
|
+
if (localBindings.size > 0) {
|
|
28316
27829
|
const merged = new Set(shadowedStateNames);
|
|
28317
27830
|
for (const localName of localBindings) merged.add(localName);
|
|
28318
27831
|
nextShadowedStateNames = merged;
|
|
@@ -28338,10 +27851,11 @@ const noDirectStateMutation = defineRule({
|
|
|
28338
27851
|
const bindings = collectUseStateBindings(componentBody);
|
|
28339
27852
|
if (bindings.length === 0) return;
|
|
28340
27853
|
const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
|
|
27854
|
+
const callbackRefSetterNames = collectCallbackRefSetterNames(componentBody);
|
|
28341
27855
|
const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
|
|
28342
27856
|
const plainObjectStateValueNames = /* @__PURE__ */ new Set();
|
|
28343
27857
|
for (const binding of bindings) {
|
|
28344
|
-
if (
|
|
27858
|
+
if (callbackRefSetterNames.has(binding.setterName)) continue;
|
|
28345
27859
|
if (!isNodeOfType(binding.declarator.init, "CallExpression")) continue;
|
|
28346
27860
|
const initializerArgument = binding.declarator.init.arguments?.[0];
|
|
28347
27861
|
if (!initializerMarksPlainState(initializerArgument)) continue;
|
|
@@ -28483,31 +27997,20 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
28483
27997
|
} })
|
|
28484
27998
|
});
|
|
28485
27999
|
//#endregion
|
|
28486
|
-
//#region src/plugin/utils/get-static-property-name.ts
|
|
28487
|
-
const getStaticPropertyName = (memberExpression) => {
|
|
28488
|
-
const property = memberExpression.property;
|
|
28489
|
-
if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
28490
|
-
if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
|
|
28491
|
-
if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
|
|
28492
|
-
return null;
|
|
28493
|
-
};
|
|
28494
|
-
//#endregion
|
|
28495
28000
|
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
28496
28001
|
const MESSAGE$24 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
|
|
28497
28002
|
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
28498
|
-
const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
|
|
28499
28003
|
const noDocumentWrite = defineRule({
|
|
28500
28004
|
id: "no-document-write",
|
|
28501
28005
|
title: "document.write/writeln",
|
|
28502
28006
|
severity: "warn",
|
|
28503
28007
|
recommendation: "Don't use `document.write()`/`document.writeln()`. Append DOM nodes or set `innerHTML`/`textContent` on a specific element instead.",
|
|
28504
28008
|
create: (context) => ({ CallExpression(node) {
|
|
28505
|
-
const callee =
|
|
28506
|
-
if (!isNodeOfType(callee, "MemberExpression")) return;
|
|
28009
|
+
const callee = node.callee;
|
|
28010
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
|
|
28507
28011
|
const receiver = stripParenExpression(callee.object);
|
|
28508
|
-
if (!isNodeOfType(receiver, "Identifier") ||
|
|
28509
|
-
|
|
28510
|
-
if (methodName === null || !WRITE_METHODS.has(methodName)) return;
|
|
28012
|
+
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "document") return;
|
|
28013
|
+
if (!isNodeOfType(callee.property, "Identifier") || !WRITE_METHODS.has(callee.property.name)) return;
|
|
28511
28014
|
context.report({
|
|
28512
28015
|
node,
|
|
28513
28016
|
message: MESSAGE$24
|
|
@@ -29314,14 +28817,6 @@ const noEffectWithFreshDeps = defineRule({
|
|
|
29314
28817
|
//#endregion
|
|
29315
28818
|
//#region src/plugin/rules/security/no-eval.ts
|
|
29316
28819
|
const SANDBOX_SURFACE_PATH_PATTERN = /(?:^|[/-])sandbox(?:$|[/-])|(?:^|\/)[\w.]+-sandbox(?:\/|\.[cm]?[jt]sx?$)/i;
|
|
29317
|
-
const getExecutableGlobalName = (node, context) => {
|
|
29318
|
-
const expression = stripParenExpression(node);
|
|
29319
|
-
if (isNodeOfType(expression, "Identifier")) return context.scopes.isGlobalReference(expression) ? expression.name : null;
|
|
29320
|
-
if (!isNodeOfType(expression, "MemberExpression")) return null;
|
|
29321
|
-
const receiver = stripParenExpression(expression.object);
|
|
29322
|
-
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "globalThis" || !context.scopes.isGlobalReference(receiver)) return null;
|
|
29323
|
-
return getStaticPropertyName(expression);
|
|
29324
|
-
};
|
|
29325
28820
|
const noEval = defineRule({
|
|
29326
28821
|
id: "no-eval",
|
|
29327
28822
|
title: "eval() runs untrusted code strings",
|
|
@@ -29331,21 +28826,20 @@ const noEval = defineRule({
|
|
|
29331
28826
|
if (SANDBOX_SURFACE_PATH_PATTERN.test(normalizeFilename(context.filename ?? ""))) return {};
|
|
29332
28827
|
return {
|
|
29333
28828
|
CallExpression(node) {
|
|
29334
|
-
|
|
29335
|
-
if (executableGlobalName === "eval") {
|
|
28829
|
+
if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "eval") {
|
|
29336
28830
|
context.report({
|
|
29337
28831
|
node,
|
|
29338
28832
|
message: "eval() is a code-injection vulnerability: it runs any string as code."
|
|
29339
28833
|
});
|
|
29340
28834
|
return;
|
|
29341
28835
|
}
|
|
29342
|
-
if ((
|
|
28836
|
+
if (isNodeOfType(node.callee, "Identifier") && (node.callee.name === "setTimeout" || node.callee.name === "setInterval") && isNodeOfType(node.arguments?.[0], "Literal") && typeof node.arguments[0].value === "string") context.report({
|
|
29343
28837
|
node,
|
|
29344
|
-
message: `Passing a string to ${
|
|
28838
|
+
message: `Passing a string to ${node.callee.name}() is a code-injection vulnerability, since it runs that string as code.`
|
|
29345
28839
|
});
|
|
29346
28840
|
},
|
|
29347
28841
|
NewExpression(node) {
|
|
29348
|
-
if (
|
|
28842
|
+
if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "Function") {
|
|
29349
28843
|
const onlyArgument = node.arguments.length === 1 ? node.arguments[0] : void 0;
|
|
29350
28844
|
if (isNodeOfType(onlyArgument, "Literal") && typeof onlyArgument.value === "string" && onlyArgument.value.trim() === "return this") return;
|
|
29351
28845
|
context.report({
|
|
@@ -30604,11 +30098,7 @@ const noFetchInEffect = defineRule({
|
|
|
30604
30098
|
severity: "warn",
|
|
30605
30099
|
recommendation: "Use a data-fetching layer or Server Component so fetches do not race, double-fire, or leak from `useEffect`.",
|
|
30606
30100
|
create: (context) => ({ CallExpression(node) {
|
|
30607
|
-
if (!
|
|
30608
|
-
allowGlobalReactNamespace: true,
|
|
30609
|
-
allowUnboundBareCalls: true,
|
|
30610
|
-
resolveNamedAliases: true
|
|
30611
|
-
})) return;
|
|
30101
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
30612
30102
|
const callback = getEffectCallback(node);
|
|
30613
30103
|
if (!callback) return;
|
|
30614
30104
|
const analysisFunctions = collectEffectAnalysisFunctions(callback, context);
|
|
@@ -33083,60 +32573,25 @@ const resolveSettings$16 = (settings) => {
|
|
|
33083
32573
|
};
|
|
33084
32574
|
const isHocCall = (call, scopes) => {
|
|
33085
32575
|
if (!isNodeOfType(call, "CallExpression")) return false;
|
|
33086
|
-
|
|
33087
|
-
|
|
33088
|
-
allowUnboundBareCalls: true
|
|
33089
|
-
})) return true;
|
|
33090
|
-
if (isReactHocMemberReference(call.callee, scopes)) return true;
|
|
32576
|
+
const calleeName = flattenCalleeName(call.callee);
|
|
32577
|
+
if (calleeName && REACT_HOC_NAMES.has(calleeName)) return true;
|
|
33091
32578
|
if (!isNodeOfType(call.callee, "Identifier")) return false;
|
|
33092
32579
|
const symbol = scopes.symbolFor(call.callee);
|
|
33093
32580
|
if (!symbol) return false;
|
|
33094
|
-
return symbolMapsToHoc(symbol
|
|
33095
|
-
};
|
|
33096
|
-
const isReactImportEquals = (symbol) => {
|
|
33097
|
-
if (symbol.kind !== "ts-import-equals" || !isNodeOfType(symbol.declarationNode, "TSImportEqualsDeclaration")) return false;
|
|
33098
|
-
const moduleReference = symbol.declarationNode.moduleReference;
|
|
33099
|
-
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);
|
|
33100
32582
|
};
|
|
33101
|
-
const
|
|
33102
|
-
if (
|
|
33103
|
-
|
|
33104
|
-
return Boolean(moduleSpecifier && isNodeOfType(moduleSpecifier, "Literal") && typeof moduleSpecifier.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(moduleSpecifier.value));
|
|
33105
|
-
};
|
|
33106
|
-
const isReactNamespaceExpression = (node, scopes) => {
|
|
33107
|
-
if (isRequireReactCall$1(node, scopes)) return true;
|
|
33108
|
-
if (!isNodeOfType(node, "Identifier")) return false;
|
|
33109
|
-
const symbol = scopes.symbolFor(node);
|
|
33110
|
-
if (!symbol) return node.name === "React" && scopes.isGlobalReference(node);
|
|
33111
|
-
if (symbol.initializer && isRequireReactCall$1(symbol.initializer, scopes)) return true;
|
|
33112
|
-
if (isReactImportEquals(symbol)) return true;
|
|
33113
|
-
return isReactNamespaceImport(node, scopes);
|
|
33114
|
-
};
|
|
33115
|
-
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));
|
|
33116
|
-
const getDestructuredPropertyName$1 = (symbol) => {
|
|
33117
|
-
const property = symbol.bindingIdentifier.parent;
|
|
33118
|
-
if (!property || !isNodeOfType(property, "Property") || !property.parent || !isNodeOfType(property.parent, "ObjectPattern")) return null;
|
|
33119
|
-
if (isNodeOfType(property.key, "Identifier") && !property.computed) return property.key.name;
|
|
33120
|
-
if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
|
|
33121
|
-
return null;
|
|
33122
|
-
};
|
|
33123
|
-
const symbolMapsToHoc = (symbol, scopes, visitedSymbolIds) => {
|
|
33124
|
-
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
33125
|
-
visitedSymbolIds.add(symbol.id);
|
|
33126
|
-
if (symbol.kind === "import") {
|
|
33127
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
33128
|
-
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;
|
|
33129
32586
|
}
|
|
33130
32587
|
const init = symbol.initializer;
|
|
33131
32588
|
if (!init) return false;
|
|
33132
|
-
|
|
33133
|
-
|
|
33134
|
-
|
|
33135
|
-
if (isNodeOfType(init, "Identifier")) {
|
|
33136
|
-
const initializedFromSymbol = scopes.symbolFor(init);
|
|
33137
|
-
if (initializedFromSymbol) return symbolMapsToHoc(initializedFromSymbol, scopes, visitedSymbolIds);
|
|
33138
|
-
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;
|
|
33139
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;
|
|
33140
32595
|
return false;
|
|
33141
32596
|
};
|
|
33142
32597
|
const isTrivialPassthroughChild = (child) => {
|
|
@@ -33194,14 +32649,26 @@ const isHocComponent = (call, scopes) => {
|
|
|
33194
32649
|
};
|
|
33195
32650
|
const containsJsx = (root) => {
|
|
33196
32651
|
let found = false;
|
|
33197
|
-
|
|
33198
|
-
if (found) return
|
|
32652
|
+
const visit = (node) => {
|
|
32653
|
+
if (found) return;
|
|
33199
32654
|
if (node.type === "JSXElement" || node.type === "JSXFragment") {
|
|
33200
32655
|
found = true;
|
|
33201
|
-
return
|
|
32656
|
+
return;
|
|
33202
32657
|
}
|
|
33203
|
-
if (node !== root
|
|
33204
|
-
|
|
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);
|
|
33205
32672
|
return found;
|
|
33206
32673
|
};
|
|
33207
32674
|
const expressionContainsJsx = (expression) => {
|
|
@@ -33225,7 +32692,7 @@ const unwrapTsCast = (expression) => {
|
|
|
33225
32692
|
while (current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSNonNullExpression") current = stripParenExpression(current.expression);
|
|
33226
32693
|
return current;
|
|
33227
32694
|
};
|
|
33228
|
-
const collectReExportedNames = (program
|
|
32695
|
+
const collectReExportedNames = (program) => {
|
|
33229
32696
|
const names = /* @__PURE__ */ new Set();
|
|
33230
32697
|
if (!isNodeOfType(program, "Program")) return names;
|
|
33231
32698
|
for (const statement of program.body) {
|
|
@@ -33249,7 +32716,8 @@ const collectReExportedNames = (program, scopes) => {
|
|
|
33249
32716
|
if (!declarator.init) continue;
|
|
33250
32717
|
const init = unwrapTsCast(declarator.init);
|
|
33251
32718
|
if (isNodeOfType(init, "CallExpression")) {
|
|
33252
|
-
|
|
32719
|
+
const calleeName = flattenCalleeName(init.callee);
|
|
32720
|
+
if (calleeName && REACT_HOC_NAMES.has(calleeName)) {
|
|
33253
32721
|
const wrappedArg = init.arguments[0];
|
|
33254
32722
|
if (wrappedArg && isNodeOfType(wrappedArg, "Identifier")) names.add(wrappedArg.name);
|
|
33255
32723
|
}
|
|
@@ -33318,7 +32786,16 @@ const recordComponent = (context, name, reportNode, isStateless) => {
|
|
|
33318
32786
|
isStateless
|
|
33319
32787
|
});
|
|
33320
32788
|
};
|
|
33321
|
-
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
|
+
};
|
|
33322
32799
|
const walkComponentSearch = (node, context) => {
|
|
33323
32800
|
if (isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) {
|
|
33324
32801
|
if (isEs6Component(node)) {
|
|
@@ -33420,13 +32897,12 @@ const noMultiComp = defineRule({
|
|
|
33420
32897
|
components: [],
|
|
33421
32898
|
componentDepth: 0,
|
|
33422
32899
|
currentVarName: null,
|
|
33423
|
-
scopes: context.scopes
|
|
33424
|
-
visitChild: (child) => walkComponentSearch(child, visitContext)
|
|
32900
|
+
scopes: context.scopes
|
|
33425
32901
|
};
|
|
33426
32902
|
for (const statement of node.body) walkComponentSearch(statement, visitContext);
|
|
33427
32903
|
const flagged = settings.ignoreStateless ? visitContext.components.filter((component) => !component.isStateless) : visitContext.components;
|
|
33428
32904
|
if (flagged.length <= 2) return;
|
|
33429
|
-
const reExportedNames = collectReExportedNames(node
|
|
32905
|
+
const reExportedNames = collectReExportedNames(node);
|
|
33430
32906
|
const exportedCount = flagged.filter((component) => isExportedDeclaration(component.reportNode, reExportedNames)).length;
|
|
33431
32907
|
if (exportedCount >= flagged.length || flagged.length >= 4 && exportedCount >= Math.floor(flagged.length * .7) || flagged.length >= 8 && exportedCount >= Math.floor(flagged.length * .5)) return;
|
|
33432
32908
|
const isSmallFeatureModule = exportedCount > 0 && exportedCount <= 2 && exportedCount < flagged.length;
|
|
@@ -34818,6 +34294,19 @@ const DATA_SINK_METHOD_NAMES = new Set([
|
|
|
34818
34294
|
"deserialize"
|
|
34819
34295
|
]);
|
|
34820
34296
|
//#endregion
|
|
34297
|
+
//#region src/plugin/utils/get-call-method-name.ts
|
|
34298
|
+
/**
|
|
34299
|
+
* Returns the static method name of a call's callee when it's a
|
|
34300
|
+
* non-computed MemberExpression (`obj.method` → `"method"`), or
|
|
34301
|
+
* `null` otherwise. Used by `no-pass-data-to-parent` and
|
|
34302
|
+
* `no-pass-live-state-to-parent` to match the receiver-bound
|
|
34303
|
+
* method-call shape against an allow/block list.
|
|
34304
|
+
*/
|
|
34305
|
+
const getCallMethodName = (callee) => {
|
|
34306
|
+
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
|
|
34307
|
+
return null;
|
|
34308
|
+
};
|
|
34309
|
+
//#endregion
|
|
34821
34310
|
//#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
|
|
34822
34311
|
const isUseStateIdentifier = (identifier) => {
|
|
34823
34312
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
@@ -35443,30 +34932,6 @@ const guardsRenderShape = (comparison) => {
|
|
|
35443
34932
|
}
|
|
35444
34933
|
return false;
|
|
35445
34934
|
};
|
|
35446
|
-
const isPropsChildrenLength = (node, scopes) => {
|
|
35447
|
-
const unwrappedNode = stripParenExpression(node);
|
|
35448
|
-
return isNodeOfType(unwrappedNode, "MemberExpression") && !unwrappedNode.computed && isNodeOfType(unwrappedNode.property, "Identifier") && unwrappedNode.property.name === "length" && resolvesToPropsChildren(stripParenExpression(unwrappedNode.object), scopes);
|
|
35449
|
-
};
|
|
35450
|
-
const isLargeTextLengthComparison = (node, scopes) => {
|
|
35451
|
-
const unwrappedNode = stripParenExpression(node);
|
|
35452
|
-
if (!isNodeOfType(unwrappedNode, "BinaryExpression")) return false;
|
|
35453
|
-
const leftIsLength = isPropsChildrenLength(unwrappedNode.left, scopes);
|
|
35454
|
-
const rightIsLength = isPropsChildrenLength(unwrappedNode.right, scopes);
|
|
35455
|
-
const thresholdNode = leftIsLength ? unwrappedNode.right : unwrappedNode.left;
|
|
35456
|
-
if (!leftIsLength && !rightIsLength || !isNodeOfType(thresholdNode, "Literal")) return false;
|
|
35457
|
-
if (typeof thresholdNode.value !== "number" || thresholdNode.value < 1e3) return false;
|
|
35458
|
-
return leftIsLength ? unwrappedNode.operator === ">" || unwrappedNode.operator === ">=" : unwrappedNode.operator === "<" || unwrappedNode.operator === "<=";
|
|
35459
|
-
};
|
|
35460
|
-
const isLargeStringOptimizationGuard = (comparison, scopes) => {
|
|
35461
|
-
let current = findTransparentExpressionRoot(comparison);
|
|
35462
|
-
while (current.parent) {
|
|
35463
|
-
const parent = current.parent;
|
|
35464
|
-
if (!isNodeOfType(parent, "LogicalExpression") || parent.operator !== "&&") return false;
|
|
35465
|
-
if (isLargeTextLengthComparison(parent.left === current ? parent.right : parent.left, scopes)) return true;
|
|
35466
|
-
current = findTransparentExpressionRoot(parent);
|
|
35467
|
-
}
|
|
35468
|
-
return false;
|
|
35469
|
-
};
|
|
35470
34935
|
const noPolymorphicChildren = defineRule({
|
|
35471
34936
|
id: "no-polymorphic-children",
|
|
35472
34937
|
title: "Children type checked at runtime",
|
|
@@ -35480,7 +34945,6 @@ const noPolymorphicChildren = defineRule({
|
|
|
35480
34945
|
const isStringLiteral = (operand) => isNodeOfType(operand, "Literal") && operand.value === "string";
|
|
35481
34946
|
if (!isStringLiteral(node.left) && !isStringLiteral(node.right)) return;
|
|
35482
34947
|
if (!guardsRenderShape(node)) return;
|
|
35483
|
-
if (isLargeStringOptimizationGuard(node, context.scopes)) return;
|
|
35484
34948
|
context.report({
|
|
35485
34949
|
node,
|
|
35486
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."
|
|
@@ -35698,27 +35162,6 @@ const hasPreviousValueDep = (effectNode, depElements) => {
|
|
|
35698
35162
|
}
|
|
35699
35163
|
return false;
|
|
35700
35164
|
};
|
|
35701
|
-
const isStateLikeDependency = (analysis, element, isPropName) => {
|
|
35702
|
-
if (!isNodeOfType(element, "Identifier") || isPropName(element.name)) return false;
|
|
35703
|
-
if (!analysis) return true;
|
|
35704
|
-
const reference = getRef(analysis, element);
|
|
35705
|
-
if (!reference) return true;
|
|
35706
|
-
const upstreamReferences = getUpstreamRefs(analysis, reference);
|
|
35707
|
-
if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
|
|
35708
|
-
return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
|
|
35709
|
-
};
|
|
35710
|
-
const getRefHeldPropCallbackName = (callExpression, isPropName) => {
|
|
35711
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
35712
|
-
if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "current") return null;
|
|
35713
|
-
const receiver = stripParenExpression(callee.object);
|
|
35714
|
-
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
35715
|
-
const binding = findVariableInitializer(callExpression, receiver.name);
|
|
35716
|
-
if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
|
|
35717
|
-
if (getCalleeName$2(binding.initializer) !== "useRef") return null;
|
|
35718
|
-
const callbackArgument = binding.initializer.arguments?.[0];
|
|
35719
|
-
if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
|
|
35720
|
-
return isPropName(callbackArgument.name) ? callbackArgument.name : null;
|
|
35721
|
-
};
|
|
35722
35165
|
const noPropCallbackInEffect = defineRule({
|
|
35723
35166
|
id: "no-prop-callback-in-effect",
|
|
35724
35167
|
title: "Parent kept in sync with a callback effect",
|
|
@@ -35735,11 +35178,11 @@ const noPropCallbackInEffect = defineRule({
|
|
|
35735
35178
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
35736
35179
|
const depsNode = node.arguments[1];
|
|
35737
35180
|
if (!isNodeOfType(depsNode, "ArrayExpression") || !depsNode.elements?.length) return;
|
|
35738
|
-
const
|
|
35739
|
-
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));
|
|
35740
35182
|
if (stateLikeDeps.length === 0) return;
|
|
35741
35183
|
if (isRefLatchGuardedEffect(callback.body)) return;
|
|
35742
35184
|
if (hasPreviousValueDep(node, depsNode.elements ?? [])) return;
|
|
35185
|
+
const analysis = getProgramAnalysis(node);
|
|
35743
35186
|
if (analysis) {
|
|
35744
35187
|
const stateLikeDepRefs = [];
|
|
35745
35188
|
for (const element of stateLikeDeps) {
|
|
@@ -35751,9 +35194,9 @@ const noPropCallbackInEffect = defineRule({
|
|
|
35751
35194
|
const reportedNodes = /* @__PURE__ */ new Set();
|
|
35752
35195
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
35753
35196
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
35754
|
-
|
|
35755
|
-
const calleeName =
|
|
35756
|
-
if (!calleeName) return;
|
|
35197
|
+
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
35198
|
+
const calleeName = child.callee.name;
|
|
35199
|
+
if (!propStackTracker.isPropName(calleeName)) return;
|
|
35757
35200
|
if (!isResultDiscardedCall(child)) return;
|
|
35758
35201
|
if (reportedNodes.has(child)) return;
|
|
35759
35202
|
reportedNodes.add(child);
|
|
@@ -39927,18 +39370,34 @@ const resolveSettings$8 = (settings) => {
|
|
|
39927
39370
|
};
|
|
39928
39371
|
const expressionContainsJsxOrCreateElement = (root) => {
|
|
39929
39372
|
let found = false;
|
|
39930
|
-
|
|
39931
|
-
if (found) return
|
|
39932
|
-
if (node !== root && NESTED_FUNCTION_TYPES.has(node.type)) return false;
|
|
39373
|
+
const visit = (node) => {
|
|
39374
|
+
if (found) return;
|
|
39933
39375
|
if (node.type === "JSXElement" || node.type === "JSXFragment") {
|
|
39934
39376
|
found = true;
|
|
39935
|
-
return
|
|
39377
|
+
return;
|
|
39936
39378
|
}
|
|
39937
39379
|
if (isNodeOfType(node, "CallExpression") && isCreateElementCall(node)) {
|
|
39938
39380
|
found = true;
|
|
39939
|
-
return
|
|
39381
|
+
return;
|
|
39940
39382
|
}
|
|
39941
|
-
|
|
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);
|
|
39942
39401
|
return found;
|
|
39943
39402
|
};
|
|
39944
39403
|
const isReactClassComponent = (classNode) => {
|
|
@@ -40864,6 +40323,19 @@ const classifyExport = (name, reportNode, isFunction, initializer, state) => {
|
|
|
40864
40323
|
reportNode
|
|
40865
40324
|
};
|
|
40866
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
|
+
};
|
|
40867
40339
|
const isEntryPointFile = (filename) => {
|
|
40868
40340
|
const lastSlash = Math.max(filename.lastIndexOf("/"), filename.lastIndexOf("\\"));
|
|
40869
40341
|
const basename = lastSlash === -1 ? filename : filename.slice(lastSlash + 1);
|
|
@@ -40902,212 +40374,197 @@ const onlyExportComponents = defineRule({
|
|
|
40902
40374
|
category: "Architecture",
|
|
40903
40375
|
create: (context) => {
|
|
40904
40376
|
const settings = resolveSettings$6(context.settings);
|
|
40905
|
-
|
|
40906
|
-
|
|
40907
|
-
|
|
40908
|
-
|
|
40909
|
-
|
|
40910
|
-
|
|
40911
|
-
|
|
40912
|
-
|
|
40913
|
-
|
|
40914
|
-
|
|
40915
|
-
|
|
40916
|
-
|
|
40917
|
-
|
|
40918
|
-
|
|
40919
|
-
VariableDeclarator: pushComponentCandidate,
|
|
40920
|
-
ClassDeclaration: pushComponentCandidate,
|
|
40921
|
-
"Program:exit"() {
|
|
40922
|
-
const localComponentNames = /* @__PURE__ */ new Set();
|
|
40923
|
-
const state = {
|
|
40924
|
-
customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
|
|
40925
|
-
allowExportNames: new Set(settings.allowExportNames),
|
|
40926
|
-
allowConstantExport: settings.allowConstantExport,
|
|
40927
|
-
localComponentNames,
|
|
40928
|
-
scopes: context.scopes
|
|
40929
|
-
};
|
|
40930
|
-
for (const child of componentCandidates) {
|
|
40931
|
-
if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
|
|
40932
|
-
if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) && functionContainsReactRenderOutput(child, context.scopes)) localComponentNames.add(child.id.name);
|
|
40933
|
-
}
|
|
40934
|
-
if (isNodeOfType(child, "ClassDeclaration") && child.id) {
|
|
40935
|
-
if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
|
|
40936
|
-
}
|
|
40937
|
-
if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
|
|
40938
|
-
const initializer = child.init;
|
|
40939
|
-
if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) {
|
|
40940
|
-
const expression = initializer ? skipTsExpression(initializer) : null;
|
|
40941
|
-
if (!(expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"))) || functionContainsReactRenderOutput(expression, context.scopes)) localComponentNames.add(child.id.name);
|
|
40942
|
-
}
|
|
40943
|
-
}
|
|
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);
|
|
40944
40391
|
}
|
|
40945
|
-
|
|
40946
|
-
|
|
40947
|
-
|
|
40948
|
-
|
|
40949
|
-
|
|
40950
|
-
if (
|
|
40951
|
-
|
|
40952
|
-
|
|
40953
|
-
context.report({
|
|
40954
|
-
node: child,
|
|
40955
|
-
message: EXPORT_ALL_MESSAGE
|
|
40956
|
-
});
|
|
40957
|
-
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);
|
|
40958
40400
|
}
|
|
40959
|
-
|
|
40960
|
-
|
|
40961
|
-
|
|
40962
|
-
|
|
40963
|
-
|
|
40964
|
-
|
|
40965
|
-
|
|
40966
|
-
|
|
40967
|
-
|
|
40968
|
-
|
|
40969
|
-
|
|
40970
|
-
|
|
40971
|
-
|
|
40972
|
-
|
|
40973
|
-
|
|
40974
|
-
|
|
40975
|
-
|
|
40976
|
-
|
|
40977
|
-
|
|
40978
|
-
|
|
40979
|
-
|
|
40980
|
-
|
|
40981
|
-
|
|
40982
|
-
|
|
40983
|
-
|
|
40984
|
-
|
|
40985
|
-
|
|
40986
|
-
} 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({
|
|
40987
40428
|
node: stripped,
|
|
40988
40429
|
message: ANONYMOUS_MESSAGE
|
|
40989
40430
|
});
|
|
40990
|
-
|
|
40991
|
-
}
|
|
40992
|
-
if (isNodeOfType(stripped, "Identifier")) {
|
|
40993
|
-
exports.push(classifyExport(stripped.name, stripped, false, null, state));
|
|
40994
|
-
continue;
|
|
40431
|
+
hasReactExport = true;
|
|
40995
40432
|
}
|
|
40996
|
-
|
|
40997
|
-
|
|
40998
|
-
|
|
40999
|
-
|
|
41000
|
-
|
|
41001
|
-
|
|
41002
|
-
|
|
41003
|
-
|
|
41004
|
-
if (!firstArg) return false;
|
|
41005
|
-
const expression = skipTsExpression(firstArg);
|
|
41006
|
-
if (isNodeOfType(expression, "Identifier")) return true;
|
|
41007
|
-
if (isNodeOfType(expression, "FunctionExpression") && expression.id) return true;
|
|
41008
|
-
if (isNodeOfType(expression, "CallExpression") && isHocCallee(expression.callee, state)) return true;
|
|
41009
|
-
return false;
|
|
41010
|
-
})();
|
|
41011
|
-
if (isHoc && firstArgIsValid) hasReactExport = true;
|
|
41012
|
-
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({
|
|
41013
40441
|
kind: "non-component",
|
|
41014
|
-
reportNode:
|
|
41015
|
-
});
|
|
41016
|
-
else context.report({
|
|
41017
|
-
node: stripped,
|
|
41018
|
-
message: ANONYMOUS_MESSAGE
|
|
41019
|
-
});
|
|
41020
|
-
continue;
|
|
41021
|
-
}
|
|
41022
|
-
if (isNodeOfType(stripped, "ObjectExpression")) {
|
|
41023
|
-
context.report({
|
|
41024
|
-
node: stripped,
|
|
41025
|
-
message: objectExpressionBundlesComponents(stripped, state) ? NAMESPACE_OBJECT_MESSAGE : ANONYMOUS_MESSAGE
|
|
41026
|
-
});
|
|
41027
|
-
continue;
|
|
41028
|
-
}
|
|
41029
|
-
if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "Literal")) {
|
|
41030
|
-
context.report({
|
|
41031
|
-
node: stripped,
|
|
41032
|
-
message: ANONYMOUS_MESSAGE
|
|
40442
|
+
reportNode: idNode
|
|
41033
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;
|
|
41034
40457
|
continue;
|
|
41035
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")) {
|
|
41036
40488
|
context.report({
|
|
41037
|
-
node:
|
|
40489
|
+
node: stripped,
|
|
41038
40490
|
message: ANONYMOUS_MESSAGE
|
|
41039
40491
|
});
|
|
41040
40492
|
continue;
|
|
41041
40493
|
}
|
|
41042
|
-
|
|
41043
|
-
|
|
41044
|
-
|
|
41045
|
-
|
|
41046
|
-
|
|
41047
|
-
|
|
41048
|
-
|
|
41049
|
-
|
|
41050
|
-
|
|
41051
|
-
|
|
41052
|
-
|
|
41053
|
-
|
|
41054
|
-
|
|
41055
|
-
|
|
41056
|
-
|
|
41057
|
-
|
|
41058
|
-
|
|
41059
|
-
|
|
41060
|
-
|
|
41061
|
-
|
|
41062
|
-
}
|
|
41063
|
-
|
|
41064
|
-
|
|
41065
|
-
|
|
41066
|
-
|
|
41067
|
-
|
|
41068
|
-
}
|
|
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));
|
|
41069
40520
|
}
|
|
41070
|
-
|
|
41071
|
-
|
|
41072
|
-
|
|
41073
|
-
|
|
41074
|
-
|
|
41075
|
-
|
|
41076
|
-
|
|
41077
|
-
|
|
41078
|
-
|
|
41079
|
-
|
|
41080
|
-
|
|
41081
|
-
|
|
41082
|
-
|
|
41083
|
-
|
|
41084
|
-
|
|
41085
|
-
|
|
41086
|
-
|
|
41087
|
-
|
|
41088
|
-
|
|
41089
|
-
|
|
41090
|
-
|
|
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" });
|
|
41091
40546
|
}
|
|
40547
|
+
if (isReExportFromSource && entry.kind !== "react-component") continue;
|
|
40548
|
+
exports.push(entry);
|
|
41092
40549
|
}
|
|
41093
40550
|
}
|
|
41094
|
-
|
|
41095
|
-
|
|
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({
|
|
41096
40559
|
node: entry.reportNode,
|
|
41097
|
-
message:
|
|
40560
|
+
message: NAMED_EXPORT_MESSAGE
|
|
40561
|
+
});
|
|
40562
|
+
if (entry.kind === "react-context") context.report({
|
|
40563
|
+
node: entry.reportNode,
|
|
40564
|
+
message: REACT_CONTEXT_MESSAGE
|
|
41098
40565
|
});
|
|
41099
|
-
if (hasAnyExports && hasReactExport) for (const entry of exports) {
|
|
41100
|
-
if (entry.kind === "non-component") context.report({
|
|
41101
|
-
node: entry.reportNode,
|
|
41102
|
-
message: NAMED_EXPORT_MESSAGE
|
|
41103
|
-
});
|
|
41104
|
-
if (entry.kind === "react-context") context.report({
|
|
41105
|
-
node: entry.reportNode,
|
|
41106
|
-
message: REACT_CONTEXT_MESSAGE
|
|
41107
|
-
});
|
|
41108
|
-
}
|
|
41109
40566
|
}
|
|
41110
|
-
};
|
|
40567
|
+
} };
|
|
41111
40568
|
}
|
|
41112
40569
|
});
|
|
41113
40570
|
//#endregion
|
|
@@ -41209,12 +40666,7 @@ const postmessageOriginRisk = defineRule({
|
|
|
41209
40666
|
scan: (file) => {
|
|
41210
40667
|
if (!isProductionSourcePath(file.relativePath)) return [];
|
|
41211
40668
|
if (WORKER_FILE_PATH_PATTERN.test(file.relativePath)) return [];
|
|
41212
|
-
|
|
41213
|
-
const ast = parseSourceText({
|
|
41214
|
-
filename: file.absolutePath,
|
|
41215
|
-
sourceText: file.content,
|
|
41216
|
-
shouldAttachParentReferences: false
|
|
41217
|
-
});
|
|
40669
|
+
const ast = parseSourceText(file.absolutePath, file.content);
|
|
41218
40670
|
if (ast === null) return [];
|
|
41219
40671
|
const findings = [];
|
|
41220
40672
|
walkAst(ast, (node) => {
|
|
@@ -43594,6 +43046,15 @@ const isStableHookWrapperArgument = (node) => {
|
|
|
43594
43046
|
const parent = node.parent;
|
|
43595
43047
|
return isNodeOfType(parent, "CallExpression") && isNodeOfType(parent.callee, "Identifier") && STABLE_HOOK_WRAPPERS.has(parent.callee.name) && Boolean(parent.arguments?.some((argument) => argument === node));
|
|
43596
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
|
+
};
|
|
43597
43058
|
const queryStableQueryClient = defineRule({
|
|
43598
43059
|
id: "query-stable-query-client",
|
|
43599
43060
|
title: "Unstable QueryClient in component",
|
|
@@ -54906,6 +54367,12 @@ const webhookSignatureRisk = defineRule({
|
|
|
54906
54367
|
//#region src/plugin/rules/zod/utils/zod-ast.ts
|
|
54907
54368
|
const ZOD_MODULE = "zod";
|
|
54908
54369
|
const ZOD_MODULE_SOURCES = [ZOD_MODULE];
|
|
54370
|
+
const getStaticPropertyName = (member) => {
|
|
54371
|
+
const property = member.property;
|
|
54372
|
+
if (!member.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
54373
|
+
if (member.computed && isNodeOfType(property, "Literal") && typeof property.value === "string") return property.value;
|
|
54374
|
+
return null;
|
|
54375
|
+
};
|
|
54909
54376
|
const importInfoCache = /* @__PURE__ */ new WeakMap();
|
|
54910
54377
|
const getImportInfoForIdentifier = (identifier) => {
|
|
54911
54378
|
if (importInfoCache.has(identifier)) return importInfoCache.get(identifier) ?? null;
|
|
@@ -60086,10 +59553,7 @@ const appendNode = (builder, block, node) => {
|
|
|
60086
59553
|
};
|
|
60087
59554
|
const mapDescendantsToBlock = (builder, node, block) => {
|
|
60088
59555
|
builder.nodeBlock.set(node, block);
|
|
60089
|
-
if (isFunctionLike$1(node))
|
|
60090
|
-
builder.nestedFunctions.push(node);
|
|
60091
|
-
return;
|
|
60092
|
-
}
|
|
59556
|
+
if (isFunctionLike$1(node)) return;
|
|
60093
59557
|
const record = node;
|
|
60094
59558
|
for (const key of Object.keys(record)) {
|
|
60095
59559
|
if (key === "parent") continue;
|
|
@@ -60323,12 +59787,11 @@ const buildStatement = (builder, statement, current) => {
|
|
|
60323
59787
|
mapDescendantsToBlock(builder, statement, current);
|
|
60324
59788
|
return current;
|
|
60325
59789
|
};
|
|
60326
|
-
const buildFunctionCfg = (functionNode, body
|
|
59790
|
+
const buildFunctionCfg = (functionNode, body) => {
|
|
60327
59791
|
const builder = {
|
|
60328
59792
|
blocks: [],
|
|
60329
59793
|
entry: null,
|
|
60330
59794
|
exit: null,
|
|
60331
|
-
nestedFunctions: nestedFunctionSink,
|
|
60332
59795
|
nodeBlock: /* @__PURE__ */ new Map(),
|
|
60333
59796
|
loopStack: [],
|
|
60334
59797
|
switchStack: [],
|
|
@@ -60346,12 +59809,13 @@ const buildFunctionCfg = (functionNode, body, nestedFunctionSink) => {
|
|
|
60346
59809
|
bodyEnd = entry;
|
|
60347
59810
|
}
|
|
60348
59811
|
addEdge(bodyEnd, exit, "uncond");
|
|
59812
|
+
const blockOf = (node) => builder.nodeBlock.get(node) ?? null;
|
|
60349
59813
|
return {
|
|
60350
59814
|
owner: functionNode,
|
|
60351
59815
|
entry,
|
|
60352
59816
|
exit,
|
|
60353
59817
|
blocks: builder.blocks,
|
|
60354
|
-
blockOf
|
|
59818
|
+
blockOf
|
|
60355
59819
|
};
|
|
60356
59820
|
};
|
|
60357
59821
|
const computeUnconditionalSet = (cfg) => {
|
|
@@ -60388,9 +59852,8 @@ const computeUnconditionalSet = (cfg) => {
|
|
|
60388
59852
|
const analyzeControlFlow = (program) => {
|
|
60389
59853
|
nextBlockId = 0;
|
|
60390
59854
|
const functionCfgs = /* @__PURE__ */ new Map();
|
|
60391
|
-
const pendingFunctions = [];
|
|
60392
59855
|
const buildFor = (functionNode, body) => {
|
|
60393
|
-
const cfg = buildFunctionCfg(functionNode, body
|
|
59856
|
+
const cfg = buildFunctionCfg(functionNode, body);
|
|
60394
59857
|
functionCfgs.set(functionNode, {
|
|
60395
59858
|
cfg,
|
|
60396
59859
|
unconditionalSet: computeUnconditionalSet(cfg)
|
|
@@ -60400,18 +59863,21 @@ const analyzeControlFlow = (program) => {
|
|
|
60400
59863
|
type: "BlockStatement",
|
|
60401
59864
|
body: program.body
|
|
60402
59865
|
});
|
|
60403
|
-
|
|
60404
|
-
|
|
60405
|
-
|
|
60406
|
-
|
|
60407
|
-
|
|
60408
|
-
|
|
60409
|
-
const
|
|
60410
|
-
|
|
60411
|
-
|
|
60412
|
-
|
|
60413
|
-
|
|
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
|
+
}
|
|
60414
59879
|
};
|
|
59880
|
+
visit(program);
|
|
60415
59881
|
const enclosingFunction = (node) => {
|
|
60416
59882
|
let current = node;
|
|
60417
59883
|
while (current) {
|
|
@@ -60422,12 +59888,12 @@ const analyzeControlFlow = (program) => {
|
|
|
60422
59888
|
return null;
|
|
60423
59889
|
};
|
|
60424
59890
|
const cfgFor = (functionLike) => {
|
|
60425
|
-
return
|
|
59891
|
+
return functionCfgs.get(functionLike)?.cfg ?? null;
|
|
60426
59892
|
};
|
|
60427
59893
|
const isUnconditionalFromEntry = (node) => {
|
|
60428
59894
|
const owner = enclosingFunction(node);
|
|
60429
59895
|
if (!owner) return true;
|
|
60430
|
-
const entry =
|
|
59896
|
+
const entry = functionCfgs.get(owner);
|
|
60431
59897
|
if (!entry) return true;
|
|
60432
59898
|
const block = entry.cfg.blockOf(node);
|
|
60433
59899
|
if (!block) return true;
|
|
@@ -60501,14 +59967,17 @@ const wrapWithSemanticContext = (rule) => ({
|
|
|
60501
59967
|
}
|
|
60502
59968
|
};
|
|
60503
59969
|
const visitors = rule.create(enrichedContext);
|
|
60504
|
-
const
|
|
60505
|
-
|
|
60506
|
-
|
|
60507
|
-
|
|
60508
|
-
|
|
60509
|
-
|
|
60510
|
-
|
|
60511
|
-
|
|
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;
|
|
60512
59981
|
}
|
|
60513
59982
|
});
|
|
60514
59983
|
//#endregion
|