oxlint-plugin-react-doctor 0.7.5-dev.21da48f → 0.7.5-dev.22bb155
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 +524 -138
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -397,6 +397,7 @@ const isProductionFilePath = (relativePath, sourceFilePattern) => {
|
|
|
397
397
|
//#endregion
|
|
398
398
|
//#region src/plugin/rules/security-scan/utils/is-production-source-path.ts
|
|
399
399
|
const isProductionSourcePath = (relativePath) => {
|
|
400
|
+
if (/\.d\.[cm]?[jt]s$/i.test(relativePath)) return false;
|
|
400
401
|
return isProductionFilePath(relativePath, SOURCE_FILE_PATTERN);
|
|
401
402
|
};
|
|
402
403
|
//#endregion
|
|
@@ -5933,6 +5934,21 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
5933
5934
|
}
|
|
5934
5935
|
});
|
|
5935
5936
|
//#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
|
|
5936
5952
|
//#region src/plugin/utils/is-presentation-role.ts
|
|
5937
5953
|
const isPresentationRole = (openingElement) => {
|
|
5938
5954
|
const roleAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "role");
|
|
@@ -5977,6 +5993,21 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
5977
5993
|
return false;
|
|
5978
5994
|
};
|
|
5979
5995
|
//#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
|
|
5980
6011
|
//#region src/plugin/rules/a11y/click-events-have-key-events.ts
|
|
5981
6012
|
const MESSAGE$57 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
|
|
5982
6013
|
const KEY_HANDLERS = [
|
|
@@ -5987,6 +6018,63 @@ const KEY_HANDLERS = [
|
|
|
5987
6018
|
"onKeyDownCapture",
|
|
5988
6019
|
"onKeyPressCapture"
|
|
5989
6020
|
];
|
|
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
|
+
};
|
|
5990
6078
|
const FOCUSLESS_CONTAINER_TAGS = new Set([
|
|
5991
6079
|
"tr",
|
|
5992
6080
|
"td",
|
|
@@ -6034,7 +6122,10 @@ const isFocusForwardingFunctionBody = (body) => {
|
|
|
6034
6122
|
};
|
|
6035
6123
|
const resolveHandlerFunction = (attribute) => {
|
|
6036
6124
|
if (!attribute.value || !isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
|
|
6037
|
-
|
|
6125
|
+
return resolveHandlerFunctionExpression(attribute.value.expression);
|
|
6126
|
+
};
|
|
6127
|
+
const resolveHandlerFunctionExpression = (handlerExpression) => {
|
|
6128
|
+
let expression = handlerExpression;
|
|
6038
6129
|
if (isNodeOfType(expression, "Identifier")) {
|
|
6039
6130
|
const binding = findVariableInitializer(expression, expression.name);
|
|
6040
6131
|
if (!binding?.initializer) return null;
|
|
@@ -6133,18 +6224,22 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
6133
6224
|
if (!HTML_TAGS.has(tag)) return;
|
|
6134
6225
|
if (tag === "label") return;
|
|
6135
6226
|
if (!FOCUSLESS_CONTAINER_TAGS.has(tag) && isInteractiveElement(tag, node)) return;
|
|
6227
|
+
const spreadEventValues = getTransparentSpreadEventValues(node.attributes, context.scopes);
|
|
6228
|
+
if (!spreadEventValues) return;
|
|
6136
6229
|
const onClick = hasJsxPropIgnoreCase(node.attributes, "onClick") ?? hasJsxPropIgnoreCase(node.attributes, "onClickCapture");
|
|
6137
|
-
|
|
6138
|
-
if (
|
|
6139
|
-
if (
|
|
6140
|
-
if (
|
|
6230
|
+
const spreadOnClickExpression = CLICK_HANDLERS.map((name) => spreadEventValues.get(name.toLowerCase())).find((expression) => expression !== void 0);
|
|
6231
|
+
if (!onClick && !spreadOnClickExpression) return;
|
|
6232
|
+
if (onClick && isPureEventBlockerHandler(onClick)) return;
|
|
6233
|
+
if (onClick && isFocusForwardingHandler(onClick)) return;
|
|
6234
|
+
const spreadHandlerFunction = spreadOnClickExpression ? resolveHandlerFunctionExpression(spreadOnClickExpression) : null;
|
|
6235
|
+
if (spreadHandlerFunction && (isFocusForwardingFunctionBody(spreadHandlerFunction.body ?? null) || containsBackdropDismissComparison(spreadHandlerFunction.body ?? null))) return;
|
|
6141
6236
|
if (hasCompositeItemRole(node)) return;
|
|
6142
6237
|
if (isHoverSelectionListItem(tag, node)) return;
|
|
6143
|
-
if (isBackdropDismissHandler(onClick)) return;
|
|
6238
|
+
if (onClick && isBackdropDismissHandler(onClick)) return;
|
|
6144
6239
|
if (containsKeyboardActivatableDescendant(node.parent)) return;
|
|
6145
6240
|
if (isHiddenFromScreenReader(node, context.settings)) return;
|
|
6146
6241
|
if (isPresentationRole(node)) return;
|
|
6147
|
-
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
|
|
6242
|
+
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler) || spreadEventValues.has(handler.toLowerCase()))) return;
|
|
6148
6243
|
context.report({
|
|
6149
6244
|
node: node.name,
|
|
6150
6245
|
message: MESSAGE$57
|
|
@@ -6870,11 +6965,10 @@ const getTemplateInterpolations = (valueTail) => {
|
|
|
6870
6965
|
const interpolations = valueTail.slice(1, closingBacktickIndex).match(/\$\{[^}]*\}/g);
|
|
6871
6966
|
return interpolations === null ? "" : interpolations.join(" ");
|
|
6872
6967
|
};
|
|
6873
|
-
const getIdentifierDeclarationInitializer = (identifier, fileContent) => {
|
|
6874
|
-
const
|
|
6875
|
-
if (
|
|
6876
|
-
|
|
6877
|
-
return fileContent.slice(initializerStartIndex, initializerStartIndex + STATIC_TEMPLATE_MAX_CHARS);
|
|
6968
|
+
const getIdentifierDeclarationInitializer = (identifier, sinkIndex, fileContent) => {
|
|
6969
|
+
const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
|
|
6970
|
+
if (declaration === null) return null;
|
|
6971
|
+
return fileContent.slice(declaration.initializerStartIndex, declaration.initializerStartIndex + STATIC_TEMPLATE_MAX_CHARS);
|
|
6878
6972
|
};
|
|
6879
6973
|
const isPureStringLiteralConcat = (initializerText) => {
|
|
6880
6974
|
if (!/^["']/.test(initializerText)) return false;
|
|
@@ -6944,6 +7038,153 @@ const isAllOperandsDomContentConcat = (valueExpression) => {
|
|
|
6944
7038
|
return !HTML_TAINT_PATTERN.test(withoutCast.replace(DOM_CONTENT_SOURCE_VALUE_PATTERN, ""));
|
|
6945
7039
|
});
|
|
6946
7040
|
};
|
|
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
|
+
};
|
|
6947
7188
|
const dangerousHtmlSink = defineRule({
|
|
6948
7189
|
id: "dangerous-html-sink",
|
|
6949
7190
|
title: "HTML injection sink with dynamic content",
|
|
@@ -6970,6 +7211,7 @@ const dangerousHtmlSink = defineRule({
|
|
|
6970
7211
|
const valueTail = fullValueTail.slice(0, VALUE_EXPRESSION_MAX_CHARS);
|
|
6971
7212
|
const terminatorIndex = valueTail.search(/[;}]/);
|
|
6972
7213
|
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);
|
|
6973
7215
|
if (STRING_LITERAL_VALUE_PATTERN.test(valueExpression)) continue;
|
|
6974
7216
|
if (MODULE_CONSTANT_VALUE_PATTERN.test(valueExpression)) continue;
|
|
6975
7217
|
if (DOM_CONTENT_SOURCE_VALUE_PATTERN.test(valueExpression) && !valueExpression.includes("+")) {
|
|
@@ -6981,7 +7223,7 @@ const dangerousHtmlSink = defineRule({
|
|
|
6981
7223
|
let templateInterpolations = getTemplateInterpolations(longValueTail ?? fullValueTail);
|
|
6982
7224
|
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;
|
|
6983
7225
|
if (templateInterpolations === null && valueIdentifier !== void 0 && BARE_IDENTIFIER_VALUE_PATTERN.test(valueExpression)) {
|
|
6984
|
-
const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier, file.content);
|
|
7226
|
+
const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier, sinkIndex, file.content);
|
|
6985
7227
|
if (declarationInitializer !== null) {
|
|
6986
7228
|
if (isPureStringLiteralConcat(declarationInitializer)) continue;
|
|
6987
7229
|
templateInterpolations = getTemplateInterpolations(declarationInitializer);
|
|
@@ -6992,7 +7234,7 @@ const dangerousHtmlSink = defineRule({
|
|
|
6992
7234
|
if (SANITIZER_PATTERN.test(judgedExpression)) continue;
|
|
6993
7235
|
if (ENV_CONFIG_VALUE_PATTERN.test(judgedExpression)) continue;
|
|
6994
7236
|
if (I18N_VALUE_PATTERN.test(judgedExpression)) continue;
|
|
6995
|
-
if (!
|
|
7237
|
+
if (!isHtmlTainted(judgedExpression, file.content, sinkIndex, /* @__PURE__ */ new Set())) continue;
|
|
6996
7238
|
if (ESCAPING_SERIALIZER_CALL_PATTERN.test(valueExpression)) continue;
|
|
6997
7239
|
if (/highlighted/i.test(valueExpression)) continue;
|
|
6998
7240
|
if (/highlight/i.test(valueExpression) && HIGHLIGHTER_LIBRARY_PATTERN.test(file.content)) continue;
|
|
@@ -7470,7 +7712,7 @@ const isCreateClassLikeCall = (node) => {
|
|
|
7470
7712
|
if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$2(callee) === "createClass";
|
|
7471
7713
|
return false;
|
|
7472
7714
|
};
|
|
7473
|
-
const isCreateContextCall = (node) => {
|
|
7715
|
+
const isCreateContextCall$1 = (node) => {
|
|
7474
7716
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7475
7717
|
const callee = node.callee;
|
|
7476
7718
|
if (isNodeOfType(callee, "Identifier")) return callee.name === "createContext";
|
|
@@ -7647,7 +7889,7 @@ const displayName = defineRule({
|
|
|
7647
7889
|
}
|
|
7648
7890
|
},
|
|
7649
7891
|
CallExpression(node) {
|
|
7650
|
-
if (settings.checkContextObjects && isReactVersionAtLeast$1(settings.reactVersion, 16, 3) && isCreateContextCall(node)) {
|
|
7892
|
+
if (settings.checkContextObjects && isReactVersionAtLeast$1(settings.reactVersion, 16, 3) && isCreateContextCall$1(node)) {
|
|
7651
7893
|
const assignedName = getAssignedName(node);
|
|
7652
7894
|
if (assignedName) {
|
|
7653
7895
|
const programRoot = findProgramRoot(node);
|
|
@@ -7694,21 +7936,6 @@ const displayName = defineRule({
|
|
|
7694
7936
|
}
|
|
7695
7937
|
});
|
|
7696
7938
|
//#endregion
|
|
7697
|
-
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
7698
|
-
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
7699
|
-
if (!isNodeOfType(node, "Property")) return null;
|
|
7700
|
-
if (node.computed) {
|
|
7701
|
-
if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
|
|
7702
|
-
return null;
|
|
7703
|
-
}
|
|
7704
|
-
if (isNodeOfType(node.key, "Identifier")) return node.key.name;
|
|
7705
|
-
if (isNodeOfType(node.key, "Literal")) {
|
|
7706
|
-
if (typeof node.key.value === "string") return node.key.value;
|
|
7707
|
-
if (options.stringifyNonStringLiterals) return String(node.key.value);
|
|
7708
|
-
}
|
|
7709
|
-
return null;
|
|
7710
|
-
};
|
|
7711
|
-
//#endregion
|
|
7712
7939
|
//#region src/plugin/utils/read-static-boolean.ts
|
|
7713
7940
|
const readStaticBoolean = (node) => {
|
|
7714
7941
|
if (!node) return null;
|
|
@@ -7717,21 +7944,6 @@ const readStaticBoolean = (node) => {
|
|
|
7717
7944
|
return unwrappedNode.value;
|
|
7718
7945
|
};
|
|
7719
7946
|
//#endregion
|
|
7720
|
-
//#region src/plugin/utils/resolve-const-identifier-alias.ts
|
|
7721
|
-
const resolveConstIdentifierAlias = (identifier, scopes) => {
|
|
7722
|
-
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
7723
|
-
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
7724
|
-
let symbol = scopes.symbolFor(identifier);
|
|
7725
|
-
while (symbol?.kind === "const") {
|
|
7726
|
-
if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
7727
|
-
visitedSymbolIds.add(symbol.id);
|
|
7728
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
7729
|
-
if (!isNodeOfType(initializer, "Identifier")) return symbol;
|
|
7730
|
-
symbol = scopes.symbolFor(initializer);
|
|
7731
|
-
}
|
|
7732
|
-
return symbol;
|
|
7733
|
-
};
|
|
7734
|
-
//#endregion
|
|
7735
7947
|
//#region src/plugin/utils/is-react-api-call.ts
|
|
7736
7948
|
const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? apiNames === apiName : apiNames.has(apiName);
|
|
7737
7949
|
const isImportedFromReact = (symbol) => {
|
|
@@ -7739,9 +7951,9 @@ const isImportedFromReact = (symbol) => {
|
|
|
7739
7951
|
const importDeclaration = symbol.declarationNode.parent;
|
|
7740
7952
|
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(importDeclaration.source.value));
|
|
7741
7953
|
};
|
|
7742
|
-
const isNamedReactApiImport = (identifier, apiNames, scopes) => {
|
|
7954
|
+
const isNamedReactApiImport = (identifier, apiNames, scopes, resolveAliases) => {
|
|
7743
7955
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
7744
|
-
const symbol = scopes.symbolFor(identifier);
|
|
7956
|
+
const symbol = resolveAliases ? resolveConstIdentifierAlias(identifier, scopes) : scopes.symbolFor(identifier);
|
|
7745
7957
|
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
7746
7958
|
const importedName = getImportedName(symbol.declarationNode);
|
|
7747
7959
|
return Boolean(importedName && includesApiName(apiNames, importedName));
|
|
@@ -7755,7 +7967,7 @@ const isReactApiCall = (node, apiNames, scopes, options = {}) => {
|
|
|
7755
7967
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7756
7968
|
const callee = stripParenExpression(node.callee);
|
|
7757
7969
|
if (isNodeOfType(callee, "Identifier")) {
|
|
7758
|
-
if (isNamedReactApiImport(callee, apiNames, scopes)) return true;
|
|
7970
|
+
if (isNamedReactApiImport(callee, apiNames, scopes, Boolean(options.resolveNamedAliases))) return true;
|
|
7759
7971
|
return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
|
|
7760
7972
|
}
|
|
7761
7973
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || !includesApiName(apiNames, callee.property.name)) return false;
|
|
@@ -8858,6 +9070,30 @@ const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, conte
|
|
|
8858
9070
|
}
|
|
8859
9071
|
return matchingBlocks.size > 0;
|
|
8860
9072
|
};
|
|
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
|
+
};
|
|
8861
9097
|
const removeSynchronouslyReleasedUsages = (callback, usages, context) => {
|
|
8862
9098
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
|
|
8863
9099
|
if (!isNodeOfType(callback.body, "BlockStatement")) return usages;
|
|
@@ -9058,6 +9294,83 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
|
|
|
9058
9294
|
});
|
|
9059
9295
|
return didCleanupFunctionMatch;
|
|
9060
9296
|
};
|
|
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);
|
|
9061
9374
|
const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
9062
9375
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
9063
9376
|
if (usage.kind === "subscribe" && findEnclosingFunction(usage.node) === callback && doesResourceResultEscape(usage.node, true) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
|
|
@@ -9070,6 +9383,10 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
|
9070
9383
|
if (returnStart !== null && usageStart !== null && returnStart < usageStart) return;
|
|
9071
9384
|
const returnedValue = child.argument ? stripParenExpression(child.argument) : null;
|
|
9072
9385
|
if (!returnedValue) return;
|
|
9386
|
+
if (doesBoundCleanupReleaseUsage(returnedValue, usage, context)) {
|
|
9387
|
+
matchingCleanupReturns.push(child);
|
|
9388
|
+
return;
|
|
9389
|
+
}
|
|
9073
9390
|
if (usage.kind === "subscribe" && (returnedValue === usage.node || getRangeStart(returnedValue) !== null && getRangeStart(returnedValue) === getRangeStart(usage.node)) && isCleanupReturningSubscribeLikeCallExpression(returnedValue)) {
|
|
9074
9391
|
matchingCleanupReturns.push(child);
|
|
9075
9392
|
return;
|
|
@@ -9085,13 +9402,17 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
|
9085
9402
|
if (!context.scopes.symbolFor(returnedValue)?.initializer) return;
|
|
9086
9403
|
}
|
|
9087
9404
|
const cleanupFunction = resolveStableValue(returnedValue, context);
|
|
9405
|
+
if (cleanupFunction && doesBoundCleanupReleaseUsage(cleanupFunction, usage, context)) {
|
|
9406
|
+
matchingCleanupReturns.push(child);
|
|
9407
|
+
return;
|
|
9408
|
+
}
|
|
9088
9409
|
if (!cleanupFunction || !isFunctionLike$1(cleanupFunction)) return;
|
|
9089
9410
|
if (doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context)) matchingCleanupReturns.push(child);
|
|
9090
9411
|
});
|
|
9091
9412
|
return doMatchingNodesCoverEveryPathAfterUsage(resolveCleanupPathAnchor(usage.node, callback, context), matchingCleanupReturns, context);
|
|
9092
9413
|
};
|
|
9093
9414
|
const findFirstUsageWithoutCleanup = (callback, usages, context) => {
|
|
9094
|
-
for (const usage of usages) if (!effectHasCleanupForUsage(callback, usage, context)) return usage;
|
|
9415
|
+
for (const usage of usages) if (!effectHasCleanupForUsage(callback, usage, context) && !hasSplitLifecycleCleanup(callback, usage, context)) return usage;
|
|
9095
9416
|
return null;
|
|
9096
9417
|
};
|
|
9097
9418
|
const isLocalAbortControllerExpression = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
@@ -10964,6 +11285,11 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
10964
11285
|
return { CallExpression(node) {
|
|
10965
11286
|
const hookName = getHookName(node.callee, context.scopes);
|
|
10966
11287
|
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;
|
|
10967
11293
|
const callbackArgumentIndex = getCallbackArgumentIndex(hookName);
|
|
10968
11294
|
const depsArgumentIndex = getDepsArgumentIndex(hookName);
|
|
10969
11295
|
const callbackArgument = node.arguments[callbackArgumentIndex];
|
|
@@ -22891,61 +23217,73 @@ const PURE_GLOBAL_CALLEE_NAMES = new Set([
|
|
|
22891
23217
|
"Array",
|
|
22892
23218
|
"BigInt",
|
|
22893
23219
|
"Boolean",
|
|
23220
|
+
"encodeURIComponent",
|
|
22894
23221
|
"Number",
|
|
22895
23222
|
"Object",
|
|
22896
23223
|
"String",
|
|
22897
23224
|
"parseFloat",
|
|
22898
|
-
"parseInt"
|
|
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"])]
|
|
22899
23273
|
]);
|
|
22900
|
-
const PURE_GLOBAL_NAMESPACE_NAMES = new Set(["JSON", "Math"]);
|
|
22901
|
-
const PURE_HELPER_NAMESPACE_MEMBER_NAMES = new Map([["JSON", new Set([
|
|
22902
|
-
"isRawJSON",
|
|
22903
|
-
"parse",
|
|
22904
|
-
"rawJSON",
|
|
22905
|
-
"stringify"
|
|
22906
|
-
])], ["Math", new Set([
|
|
22907
|
-
"abs",
|
|
22908
|
-
"acos",
|
|
22909
|
-
"acosh",
|
|
22910
|
-
"asin",
|
|
22911
|
-
"asinh",
|
|
22912
|
-
"atan",
|
|
22913
|
-
"atan2",
|
|
22914
|
-
"atanh",
|
|
22915
|
-
"cbrt",
|
|
22916
|
-
"ceil",
|
|
22917
|
-
"clz32",
|
|
22918
|
-
"cos",
|
|
22919
|
-
"cosh",
|
|
22920
|
-
"exp",
|
|
22921
|
-
"floor",
|
|
22922
|
-
"fround",
|
|
22923
|
-
"hypot",
|
|
22924
|
-
"imul",
|
|
22925
|
-
"log",
|
|
22926
|
-
"log10",
|
|
22927
|
-
"log1p",
|
|
22928
|
-
"log2",
|
|
22929
|
-
"max",
|
|
22930
|
-
"min",
|
|
22931
|
-
"pow",
|
|
22932
|
-
"round",
|
|
22933
|
-
"sign",
|
|
22934
|
-
"sin",
|
|
22935
|
-
"sinh",
|
|
22936
|
-
"sqrt",
|
|
22937
|
-
"tan",
|
|
22938
|
-
"tanh",
|
|
22939
|
-
"trunc"
|
|
22940
|
-
])]]);
|
|
22941
23274
|
const PURE_MEMBER_TRANSFORM_NAMES = new Set([
|
|
22942
23275
|
"concat",
|
|
22943
23276
|
"filter",
|
|
23277
|
+
"flatMap",
|
|
22944
23278
|
"join",
|
|
22945
23279
|
"map",
|
|
23280
|
+
"reduce",
|
|
23281
|
+
"replace",
|
|
23282
|
+
"slice",
|
|
22946
23283
|
"split",
|
|
22947
23284
|
"toLowerCase",
|
|
22948
23285
|
"toString",
|
|
23286
|
+
"toSorted",
|
|
22949
23287
|
"toUpperCase",
|
|
22950
23288
|
"trim"
|
|
22951
23289
|
]);
|
|
@@ -23327,6 +23665,11 @@ const analyzeHelperExpression = (expression, environment, usedParameterIndices)
|
|
|
23327
23665
|
const callbackSummary = analyzeHelperStatements(node.body.body ?? [], callbackEnvironment, usedParameterIndices, analyzeHelperExpression);
|
|
23328
23666
|
return callbackSummary.isValid && !callbackSummary.canContinue;
|
|
23329
23667
|
}
|
|
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
|
+
}
|
|
23330
23673
|
if (isNodeOfType(node, "CallExpression")) {
|
|
23331
23674
|
const callee = stripParenExpression(node.callee);
|
|
23332
23675
|
const calleeRoot = getMemberRoot(callee);
|
|
@@ -23529,7 +23872,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23529
23872
|
}
|
|
23530
23873
|
const callee = stripParenExpression(node.callee);
|
|
23531
23874
|
const calleeRoot = getMemberRoot(callee);
|
|
23532
|
-
const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && getIdentifierBindingIdentity(analysis, callee) === null || isNodeOfType(
|
|
23875
|
+
const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && getIdentifierBindingIdentity(analysis, callee) === null || isNodeOfType(callee, "MemberExpression") && isNodeOfType(calleeRoot, "Identifier") && getIdentifierBindingIdentity(analysis, calleeRoot) === null && PURE_HELPER_NAMESPACE_MEMBER_NAMES.get(calleeRoot.name)?.has(getStaticMemberName$1(callee) ?? "") === true;
|
|
23533
23876
|
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName$1(callee) ?? "");
|
|
23534
23877
|
if (isPureGlobalCall || isPureMemberTransform) {
|
|
23535
23878
|
if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression")) mergeEvidence(evidence, collectValueEvidence(analysis, callee.object, frame, remainingCallFrames, new Set(visitedBindings)));
|
|
@@ -23582,6 +23925,15 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23582
23925
|
}
|
|
23583
23926
|
return evidence;
|
|
23584
23927
|
}
|
|
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
|
+
}
|
|
23585
23937
|
if (isFunctionLike$1(node) || isNodeOfType(node, "AwaitExpression")) {
|
|
23586
23938
|
evidence.hasUnknownSource = true;
|
|
23587
23939
|
return evidence;
|
|
@@ -24922,7 +25274,11 @@ const noAsyncEffectCallback = defineRule({
|
|
|
24922
25274
|
severity: "warn",
|
|
24923
25275
|
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.",
|
|
24924
25276
|
create: (context) => ({ CallExpression(node) {
|
|
24925
|
-
if (!
|
|
25277
|
+
if (!isReactApiCall(node, EFFECT_HOOK_NAMES$1, context.scopes, {
|
|
25278
|
+
allowGlobalReactNamespace: true,
|
|
25279
|
+
allowUnboundBareCalls: true,
|
|
25280
|
+
resolveNamedAliases: true
|
|
25281
|
+
})) return;
|
|
24926
25282
|
const callback = getEffectCallback(node);
|
|
24927
25283
|
if (!callback) return;
|
|
24928
25284
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
@@ -26120,6 +26476,19 @@ const noCloneElement = defineRule({
|
|
|
26120
26476
|
} })
|
|
26121
26477
|
});
|
|
26122
26478
|
//#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
|
|
26123
26492
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
26124
26493
|
const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
26125
26494
|
const CONTEXT_MODULES = [
|
|
@@ -26127,23 +26496,35 @@ const CONTEXT_MODULES = [
|
|
|
26127
26496
|
"use-context-selector",
|
|
26128
26497
|
"react-tracked"
|
|
26129
26498
|
];
|
|
26130
|
-
const
|
|
26499
|
+
const getSupportedContextImportSource = (symbol) => {
|
|
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);
|
|
26131
26517
|
if (isNodeOfType(callee, "Identifier")) {
|
|
26132
|
-
|
|
26133
|
-
|
|
26518
|
+
if (isDestructuredCreateContextBinding(callee, scopes)) return true;
|
|
26519
|
+
const symbol = resolveConstIdentifierAlias(callee, scopes);
|
|
26520
|
+
return getSupportedContextImportSource(symbol) !== null && Boolean(symbol && getImportedName(symbol.declarationNode) === "createContext");
|
|
26134
26521
|
}
|
|
26135
|
-
if (isNodeOfType(callee, "MemberExpression")
|
|
26136
|
-
|
|
26137
|
-
|
|
26138
|
-
|
|
26139
|
-
|
|
26140
|
-
|
|
26141
|
-
const namespaceName = namespaceIdentifier.name;
|
|
26142
|
-
if (isCanonicalReactNamespaceName(namespaceName)) return true;
|
|
26143
|
-
const importSource = getImportSourceForName(namespaceIdentifier, namespaceName);
|
|
26144
|
-
return importSource !== null && CONTEXT_MODULES.includes(importSource);
|
|
26145
|
-
}
|
|
26146
|
-
return false;
|
|
26522
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
26523
|
+
if ((getCallMethodName(callee) ?? (callee.computed && isNodeOfType(callee.property, "Literal") && typeof callee.property.value === "string" ? callee.property.value : null)) !== "createContext") return false;
|
|
26524
|
+
const receiver = stripParenExpression(callee.object);
|
|
26525
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
26526
|
+
if (receiver.name === "React" && scopes.isGlobalReference(receiver)) return true;
|
|
26527
|
+
return isSupportedNamespaceSymbol(resolveConstIdentifierAlias(receiver, scopes));
|
|
26147
26528
|
};
|
|
26148
26529
|
const noCreateContextInRender = defineRule({
|
|
26149
26530
|
id: "no-create-context-in-render",
|
|
@@ -26152,7 +26533,7 @@ const noCreateContextInRender = defineRule({
|
|
|
26152
26533
|
category: "Correctness",
|
|
26153
26534
|
recommendation: "Move `createContext(...)` outside the component, to the top level of the file, so it stays the same on every render.",
|
|
26154
26535
|
create: (context) => ({ CallExpression(node) {
|
|
26155
|
-
if (!
|
|
26536
|
+
if (!isCreateContextCall(node, context.scopes)) return;
|
|
26156
26537
|
const componentOrHookName = enclosingComponentOrHookName(node);
|
|
26157
26538
|
if (!componentOrHookName) return;
|
|
26158
26539
|
context.report({
|
|
@@ -28102,20 +28483,31 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
28102
28483
|
} })
|
|
28103
28484
|
});
|
|
28104
28485
|
//#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
|
|
28105
28495
|
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
28106
28496
|
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.";
|
|
28107
28497
|
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
28498
|
+
const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
|
|
28108
28499
|
const noDocumentWrite = defineRule({
|
|
28109
28500
|
id: "no-document-write",
|
|
28110
28501
|
title: "document.write/writeln",
|
|
28111
28502
|
severity: "warn",
|
|
28112
28503
|
recommendation: "Don't use `document.write()`/`document.writeln()`. Append DOM nodes or set `innerHTML`/`textContent` on a specific element instead.",
|
|
28113
28504
|
create: (context) => ({ CallExpression(node) {
|
|
28114
|
-
const callee = node.callee;
|
|
28115
|
-
if (!isNodeOfType(callee, "MemberExpression")
|
|
28505
|
+
const callee = stripParenExpression(node.callee);
|
|
28506
|
+
if (!isNodeOfType(callee, "MemberExpression")) return;
|
|
28116
28507
|
const receiver = stripParenExpression(callee.object);
|
|
28117
|
-
if (!isNodeOfType(receiver, "Identifier") || receiver
|
|
28118
|
-
|
|
28508
|
+
if (!isNodeOfType(receiver, "Identifier") || !isGlobalDocumentReference(receiver, context)) return;
|
|
28509
|
+
const methodName = getStaticPropertyName(callee);
|
|
28510
|
+
if (methodName === null || !WRITE_METHODS.has(methodName)) return;
|
|
28119
28511
|
context.report({
|
|
28120
28512
|
node,
|
|
28121
28513
|
message: MESSAGE$24
|
|
@@ -28922,6 +29314,14 @@ const noEffectWithFreshDeps = defineRule({
|
|
|
28922
29314
|
//#endregion
|
|
28923
29315
|
//#region src/plugin/rules/security/no-eval.ts
|
|
28924
29316
|
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
|
+
};
|
|
28925
29325
|
const noEval = defineRule({
|
|
28926
29326
|
id: "no-eval",
|
|
28927
29327
|
title: "eval() runs untrusted code strings",
|
|
@@ -28931,20 +29331,21 @@ const noEval = defineRule({
|
|
|
28931
29331
|
if (SANDBOX_SURFACE_PATH_PATTERN.test(normalizeFilename(context.filename ?? ""))) return {};
|
|
28932
29332
|
return {
|
|
28933
29333
|
CallExpression(node) {
|
|
28934
|
-
|
|
29334
|
+
const executableGlobalName = getExecutableGlobalName(node.callee, context);
|
|
29335
|
+
if (executableGlobalName === "eval") {
|
|
28935
29336
|
context.report({
|
|
28936
29337
|
node,
|
|
28937
29338
|
message: "eval() is a code-injection vulnerability: it runs any string as code."
|
|
28938
29339
|
});
|
|
28939
29340
|
return;
|
|
28940
29341
|
}
|
|
28941
|
-
if (
|
|
29342
|
+
if ((executableGlobalName === "setTimeout" || executableGlobalName === "setInterval") && isNodeOfType(node.arguments?.[0], "Literal") && typeof node.arguments[0].value === "string") context.report({
|
|
28942
29343
|
node,
|
|
28943
|
-
message: `Passing a string to ${
|
|
29344
|
+
message: `Passing a string to ${executableGlobalName}() is a code-injection vulnerability, since it runs that string as code.`
|
|
28944
29345
|
});
|
|
28945
29346
|
},
|
|
28946
29347
|
NewExpression(node) {
|
|
28947
|
-
if (
|
|
29348
|
+
if (getExecutableGlobalName(node.callee, context) === "Function") {
|
|
28948
29349
|
const onlyArgument = node.arguments.length === 1 ? node.arguments[0] : void 0;
|
|
28949
29350
|
if (isNodeOfType(onlyArgument, "Literal") && typeof onlyArgument.value === "string" && onlyArgument.value.trim() === "return this") return;
|
|
28950
29351
|
context.report({
|
|
@@ -30203,7 +30604,11 @@ const noFetchInEffect = defineRule({
|
|
|
30203
30604
|
severity: "warn",
|
|
30204
30605
|
recommendation: "Use a data-fetching layer or Server Component so fetches do not race, double-fire, or leak from `useEffect`.",
|
|
30205
30606
|
create: (context) => ({ CallExpression(node) {
|
|
30206
|
-
if (!
|
|
30607
|
+
if (!isReactApiCall(node, EFFECT_HOOK_NAMES$1, context.scopes, {
|
|
30608
|
+
allowGlobalReactNamespace: true,
|
|
30609
|
+
allowUnboundBareCalls: true,
|
|
30610
|
+
resolveNamedAliases: true
|
|
30611
|
+
})) return;
|
|
30207
30612
|
const callback = getEffectCallback(node);
|
|
30208
30613
|
if (!callback) return;
|
|
30209
30614
|
const analysisFunctions = collectEffectAnalysisFunctions(callback, context);
|
|
@@ -34413,19 +34818,6 @@ const DATA_SINK_METHOD_NAMES = new Set([
|
|
|
34413
34818
|
"deserialize"
|
|
34414
34819
|
]);
|
|
34415
34820
|
//#endregion
|
|
34416
|
-
//#region src/plugin/utils/get-call-method-name.ts
|
|
34417
|
-
/**
|
|
34418
|
-
* Returns the static method name of a call's callee when it's a
|
|
34419
|
-
* non-computed MemberExpression (`obj.method` → `"method"`), or
|
|
34420
|
-
* `null` otherwise. Used by `no-pass-data-to-parent` and
|
|
34421
|
-
* `no-pass-live-state-to-parent` to match the receiver-bound
|
|
34422
|
-
* method-call shape against an allow/block list.
|
|
34423
|
-
*/
|
|
34424
|
-
const getCallMethodName = (callee) => {
|
|
34425
|
-
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
|
|
34426
|
-
return null;
|
|
34427
|
-
};
|
|
34428
|
-
//#endregion
|
|
34429
34821
|
//#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
|
|
34430
34822
|
const isUseStateIdentifier = (identifier) => {
|
|
34431
34823
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
@@ -54514,12 +54906,6 @@ const webhookSignatureRisk = defineRule({
|
|
|
54514
54906
|
//#region src/plugin/rules/zod/utils/zod-ast.ts
|
|
54515
54907
|
const ZOD_MODULE = "zod";
|
|
54516
54908
|
const ZOD_MODULE_SOURCES = [ZOD_MODULE];
|
|
54517
|
-
const getStaticPropertyName = (member) => {
|
|
54518
|
-
const property = member.property;
|
|
54519
|
-
if (!member.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
54520
|
-
if (member.computed && isNodeOfType(property, "Literal") && typeof property.value === "string") return property.value;
|
|
54521
|
-
return null;
|
|
54522
|
-
};
|
|
54523
54909
|
const importInfoCache = /* @__PURE__ */ new WeakMap();
|
|
54524
54910
|
const getImportInfoForIdentifier = (identifier) => {
|
|
54525
54911
|
if (importInfoCache.has(identifier)) return importInfoCache.get(identifier) ?? null;
|