oxlint-plugin-react-doctor 0.7.5-dev.70eff9a → 0.7.5-dev.76cd6be
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 +184 -733
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -397,7 +397,6 @@ 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;
|
|
401
400
|
return isProductionFilePath(relativePath, SOURCE_FILE_PATTERN);
|
|
402
401
|
};
|
|
403
402
|
//#endregion
|
|
@@ -5179,10 +5178,8 @@ const STORAGE_GLOBALS = new Set([
|
|
|
5179
5178
|
const SENSITIVE_KEY_PATTERN = /token|jwt|secret|password|passwd|credential|api[-_]?key|bearer|private[-_]?key/i;
|
|
5180
5179
|
const NON_AUTH_TOKEN_PATTERN = /csrf|xsrf|device|fcm|apns|push|design|tokeniz|syntax|css|theme|color/i;
|
|
5181
5180
|
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
5181
|
const PRODUCT_API_KEY_COLLECTION_PATTERN = /[._:-](?:created|generated|saved)[-_]?api[-_]?keys$/i;
|
|
5184
5182
|
const isAuthCredentialKey = (key) => {
|
|
5185
|
-
if (PRODUCT_API_KEY_RECORDS_PATTERN.test(key)) return false;
|
|
5186
5183
|
if (!SENSITIVE_KEY_PATTERN.test(key)) return false;
|
|
5187
5184
|
if (PRODUCT_API_KEY_COLLECTION_PATTERN.test(key)) return false;
|
|
5188
5185
|
if (NON_AUTH_TOKEN_PATTERN.test(key) && !STRONG_AUTH_KEY_PATTERN.test(key)) return false;
|
|
@@ -5934,21 +5931,6 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
5934
5931
|
}
|
|
5935
5932
|
});
|
|
5936
5933
|
//#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
5934
|
//#region src/plugin/utils/is-presentation-role.ts
|
|
5953
5935
|
const isPresentationRole = (openingElement) => {
|
|
5954
5936
|
const roleAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "role");
|
|
@@ -5993,21 +5975,6 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
5993
5975
|
return false;
|
|
5994
5976
|
};
|
|
5995
5977
|
//#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
5978
|
//#region src/plugin/rules/a11y/click-events-have-key-events.ts
|
|
6012
5979
|
const MESSAGE$57 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
|
|
6013
5980
|
const KEY_HANDLERS = [
|
|
@@ -6018,63 +5985,6 @@ const KEY_HANDLERS = [
|
|
|
6018
5985
|
"onKeyDownCapture",
|
|
6019
5986
|
"onKeyPressCapture"
|
|
6020
5987
|
];
|
|
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
5988
|
const FOCUSLESS_CONTAINER_TAGS = new Set([
|
|
6079
5989
|
"tr",
|
|
6080
5990
|
"td",
|
|
@@ -6122,10 +6032,7 @@ const isFocusForwardingFunctionBody = (body) => {
|
|
|
6122
6032
|
};
|
|
6123
6033
|
const resolveHandlerFunction = (attribute) => {
|
|
6124
6034
|
if (!attribute.value || !isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
|
|
6125
|
-
|
|
6126
|
-
};
|
|
6127
|
-
const resolveHandlerFunctionExpression = (handlerExpression) => {
|
|
6128
|
-
let expression = handlerExpression;
|
|
6035
|
+
let expression = attribute.value.expression;
|
|
6129
6036
|
if (isNodeOfType(expression, "Identifier")) {
|
|
6130
6037
|
const binding = findVariableInitializer(expression, expression.name);
|
|
6131
6038
|
if (!binding?.initializer) return null;
|
|
@@ -6224,22 +6131,18 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
6224
6131
|
if (!HTML_TAGS.has(tag)) return;
|
|
6225
6132
|
if (tag === "label") return;
|
|
6226
6133
|
if (!FOCUSLESS_CONTAINER_TAGS.has(tag) && isInteractiveElement(tag, node)) return;
|
|
6227
|
-
const spreadEventValues = getTransparentSpreadEventValues(node.attributes, context.scopes);
|
|
6228
|
-
if (!spreadEventValues) return;
|
|
6229
6134
|
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;
|
|
6135
|
+
if (!onClick) return;
|
|
6136
|
+
if (isPureEventBlockerHandler(onClick)) return;
|
|
6137
|
+
if (isFocusForwardingHandler(onClick)) return;
|
|
6138
|
+
if (hasJsxSpreadAttribute$1(node.attributes)) return;
|
|
6236
6139
|
if (hasCompositeItemRole(node)) return;
|
|
6237
6140
|
if (isHoverSelectionListItem(tag, node)) return;
|
|
6238
|
-
if (
|
|
6141
|
+
if (isBackdropDismissHandler(onClick)) return;
|
|
6239
6142
|
if (containsKeyboardActivatableDescendant(node.parent)) return;
|
|
6240
6143
|
if (isHiddenFromScreenReader(node, context.settings)) return;
|
|
6241
6144
|
if (isPresentationRole(node)) return;
|
|
6242
|
-
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler)
|
|
6145
|
+
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
|
|
6243
6146
|
context.report({
|
|
6244
6147
|
node: node.name,
|
|
6245
6148
|
message: MESSAGE$57
|
|
@@ -6965,10 +6868,11 @@ const getTemplateInterpolations = (valueTail) => {
|
|
|
6965
6868
|
const interpolations = valueTail.slice(1, closingBacktickIndex).match(/\$\{[^}]*\}/g);
|
|
6966
6869
|
return interpolations === null ? "" : interpolations.join(" ");
|
|
6967
6870
|
};
|
|
6968
|
-
const getIdentifierDeclarationInitializer = (identifier,
|
|
6969
|
-
const
|
|
6970
|
-
if (
|
|
6971
|
-
|
|
6871
|
+
const getIdentifierDeclarationInitializer = (identifier, fileContent) => {
|
|
6872
|
+
const declarationMatch = new RegExp(`(?:const|let|var)\\s+${escapeRegExp(identifier)}\\s*(?::[^=\\n]{0,120})?=\\s*`).exec(fileContent);
|
|
6873
|
+
if (declarationMatch === null) return null;
|
|
6874
|
+
const initializerStartIndex = declarationMatch.index + declarationMatch[0].length;
|
|
6875
|
+
return fileContent.slice(initializerStartIndex, initializerStartIndex + STATIC_TEMPLATE_MAX_CHARS);
|
|
6972
6876
|
};
|
|
6973
6877
|
const isPureStringLiteralConcat = (initializerText) => {
|
|
6974
6878
|
if (!/^["']/.test(initializerText)) return false;
|
|
@@ -7038,153 +6942,6 @@ const isAllOperandsDomContentConcat = (valueExpression) => {
|
|
|
7038
6942
|
return !HTML_TAINT_PATTERN.test(withoutCast.replace(DOM_CONTENT_SOURCE_VALUE_PATTERN, ""));
|
|
7039
6943
|
});
|
|
7040
6944
|
};
|
|
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
6945
|
const dangerousHtmlSink = defineRule({
|
|
7189
6946
|
id: "dangerous-html-sink",
|
|
7190
6947
|
title: "HTML injection sink with dynamic content",
|
|
@@ -7211,7 +6968,6 @@ const dangerousHtmlSink = defineRule({
|
|
|
7211
6968
|
const valueTail = fullValueTail.slice(0, VALUE_EXPRESSION_MAX_CHARS);
|
|
7212
6969
|
const terminatorIndex = valueTail.search(/[;}]/);
|
|
7213
6970
|
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
6971
|
if (STRING_LITERAL_VALUE_PATTERN.test(valueExpression)) continue;
|
|
7216
6972
|
if (MODULE_CONSTANT_VALUE_PATTERN.test(valueExpression)) continue;
|
|
7217
6973
|
if (DOM_CONTENT_SOURCE_VALUE_PATTERN.test(valueExpression) && !valueExpression.includes("+")) {
|
|
@@ -7223,7 +6979,7 @@ const dangerousHtmlSink = defineRule({
|
|
|
7223
6979
|
let templateInterpolations = getTemplateInterpolations(longValueTail ?? fullValueTail);
|
|
7224
6980
|
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
6981
|
if (templateInterpolations === null && valueIdentifier !== void 0 && BARE_IDENTIFIER_VALUE_PATTERN.test(valueExpression)) {
|
|
7226
|
-
const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier,
|
|
6982
|
+
const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier, file.content);
|
|
7227
6983
|
if (declarationInitializer !== null) {
|
|
7228
6984
|
if (isPureStringLiteralConcat(declarationInitializer)) continue;
|
|
7229
6985
|
templateInterpolations = getTemplateInterpolations(declarationInitializer);
|
|
@@ -7234,7 +6990,7 @@ const dangerousHtmlSink = defineRule({
|
|
|
7234
6990
|
if (SANITIZER_PATTERN.test(judgedExpression)) continue;
|
|
7235
6991
|
if (ENV_CONFIG_VALUE_PATTERN.test(judgedExpression)) continue;
|
|
7236
6992
|
if (I18N_VALUE_PATTERN.test(judgedExpression)) continue;
|
|
7237
|
-
if (!
|
|
6993
|
+
if (!HTML_TAINT_PATTERN.test(judgedExpression)) continue;
|
|
7238
6994
|
if (ESCAPING_SERIALIZER_CALL_PATTERN.test(valueExpression)) continue;
|
|
7239
6995
|
if (/highlighted/i.test(valueExpression)) continue;
|
|
7240
6996
|
if (/highlight/i.test(valueExpression) && HIGHLIGHTER_LIBRARY_PATTERN.test(file.content)) continue;
|
|
@@ -7681,7 +7437,7 @@ const containsJsx$1 = (root) => {
|
|
|
7681
7437
|
containsJsxCache.set(root, found);
|
|
7682
7438
|
return found;
|
|
7683
7439
|
};
|
|
7684
|
-
const getStaticMemberName$
|
|
7440
|
+
const getStaticMemberName$1 = (node) => {
|
|
7685
7441
|
if (!isNodeOfType(node, "MemberExpression")) return null;
|
|
7686
7442
|
if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
|
|
7687
7443
|
if (node.computed && isNodeOfType(node.property, "Literal") && typeof node.property.value === "string") return node.property.value;
|
|
@@ -7695,7 +7451,7 @@ const getAssignedName = (node) => {
|
|
|
7695
7451
|
if (isNodeOfType(parent, "AssignmentExpression")) {
|
|
7696
7452
|
const left = parent.left;
|
|
7697
7453
|
if (isNodeOfType(left, "Identifier")) return left.name;
|
|
7698
|
-
if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$
|
|
7454
|
+
if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$1(left);
|
|
7699
7455
|
}
|
|
7700
7456
|
return null;
|
|
7701
7457
|
};
|
|
@@ -7703,20 +7459,20 @@ const isModuleExportsAssignment = (node) => {
|
|
|
7703
7459
|
const parent = node.parent;
|
|
7704
7460
|
if (!parent || !isNodeOfType(parent, "AssignmentExpression")) return false;
|
|
7705
7461
|
const left = parent.left;
|
|
7706
|
-
return isNodeOfType(left, "MemberExpression") && isNodeOfType(left.object, "Identifier") && left.object.name === "module" && getStaticMemberName$
|
|
7462
|
+
return isNodeOfType(left, "MemberExpression") && isNodeOfType(left.object, "Identifier") && left.object.name === "module" && getStaticMemberName$1(left) === "exports";
|
|
7707
7463
|
};
|
|
7708
7464
|
const isCreateClassLikeCall = (node) => {
|
|
7709
7465
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7710
7466
|
if (isEs5Component(node)) return true;
|
|
7711
7467
|
const callee = node.callee;
|
|
7712
|
-
if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$
|
|
7468
|
+
if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$1(callee) === "createClass";
|
|
7713
7469
|
return false;
|
|
7714
7470
|
};
|
|
7715
|
-
const isCreateContextCall
|
|
7471
|
+
const isCreateContextCall = (node) => {
|
|
7716
7472
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7717
7473
|
const callee = node.callee;
|
|
7718
7474
|
if (isNodeOfType(callee, "Identifier")) return callee.name === "createContext";
|
|
7719
|
-
return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$
|
|
7475
|
+
return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$1(callee) === "createContext";
|
|
7720
7476
|
};
|
|
7721
7477
|
const isNamedFunctionLike = (node) => (isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && Boolean(node.id?.name);
|
|
7722
7478
|
const firstCallArgument = (node) => {
|
|
@@ -7774,7 +7530,7 @@ const memberExpressionPath = (node) => {
|
|
|
7774
7530
|
if (isNodeOfType(node, "Identifier")) return [node.name];
|
|
7775
7531
|
if (!isNodeOfType(node, "MemberExpression")) return [];
|
|
7776
7532
|
const objectPath = memberExpressionPath(node.object);
|
|
7777
|
-
const propertyName = getStaticMemberName$
|
|
7533
|
+
const propertyName = getStaticMemberName$1(node);
|
|
7778
7534
|
return propertyName ? [...objectPath, propertyName] : objectPath;
|
|
7779
7535
|
};
|
|
7780
7536
|
const displayNameAssignmentIndexCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -7784,7 +7540,7 @@ const getDisplayNameAssignmentIndex = (programRoot) => {
|
|
|
7784
7540
|
const identifierTargets = /* @__PURE__ */ new Set();
|
|
7785
7541
|
const memberPathSegments = /* @__PURE__ */ new Set();
|
|
7786
7542
|
const visit = (node) => {
|
|
7787
|
-
if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName$
|
|
7543
|
+
if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName$1(node.left) === "displayName") {
|
|
7788
7544
|
if (isNodeOfType(node.left.object, "Identifier")) identifierTargets.add(node.left.object.name);
|
|
7789
7545
|
for (const segment of memberExpressionPath(node.left.object)) memberPathSegments.add(segment);
|
|
7790
7546
|
}
|
|
@@ -7889,7 +7645,7 @@ const displayName = defineRule({
|
|
|
7889
7645
|
}
|
|
7890
7646
|
},
|
|
7891
7647
|
CallExpression(node) {
|
|
7892
|
-
if (settings.checkContextObjects && isReactVersionAtLeast$1(settings.reactVersion, 16, 3) && isCreateContextCall
|
|
7648
|
+
if (settings.checkContextObjects && isReactVersionAtLeast$1(settings.reactVersion, 16, 3) && isCreateContextCall(node)) {
|
|
7893
7649
|
const assignedName = getAssignedName(node);
|
|
7894
7650
|
if (assignedName) {
|
|
7895
7651
|
const programRoot = findProgramRoot(node);
|
|
@@ -7936,6 +7692,21 @@ const displayName = defineRule({
|
|
|
7936
7692
|
}
|
|
7937
7693
|
});
|
|
7938
7694
|
//#endregion
|
|
7695
|
+
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
7696
|
+
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
7697
|
+
if (!isNodeOfType(node, "Property")) return null;
|
|
7698
|
+
if (node.computed) {
|
|
7699
|
+
if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
|
|
7700
|
+
return null;
|
|
7701
|
+
}
|
|
7702
|
+
if (isNodeOfType(node.key, "Identifier")) return node.key.name;
|
|
7703
|
+
if (isNodeOfType(node.key, "Literal")) {
|
|
7704
|
+
if (typeof node.key.value === "string") return node.key.value;
|
|
7705
|
+
if (options.stringifyNonStringLiterals) return String(node.key.value);
|
|
7706
|
+
}
|
|
7707
|
+
return null;
|
|
7708
|
+
};
|
|
7709
|
+
//#endregion
|
|
7939
7710
|
//#region src/plugin/utils/read-static-boolean.ts
|
|
7940
7711
|
const readStaticBoolean = (node) => {
|
|
7941
7712
|
if (!node) return null;
|
|
@@ -7944,6 +7715,21 @@ const readStaticBoolean = (node) => {
|
|
|
7944
7715
|
return unwrappedNode.value;
|
|
7945
7716
|
};
|
|
7946
7717
|
//#endregion
|
|
7718
|
+
//#region src/plugin/utils/resolve-const-identifier-alias.ts
|
|
7719
|
+
const resolveConstIdentifierAlias = (identifier, scopes) => {
|
|
7720
|
+
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
7721
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
7722
|
+
let symbol = scopes.symbolFor(identifier);
|
|
7723
|
+
while (symbol?.kind === "const") {
|
|
7724
|
+
if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
7725
|
+
visitedSymbolIds.add(symbol.id);
|
|
7726
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
7727
|
+
if (!isNodeOfType(initializer, "Identifier")) return symbol;
|
|
7728
|
+
symbol = scopes.symbolFor(initializer);
|
|
7729
|
+
}
|
|
7730
|
+
return symbol;
|
|
7731
|
+
};
|
|
7732
|
+
//#endregion
|
|
7947
7733
|
//#region src/plugin/utils/is-react-api-call.ts
|
|
7948
7734
|
const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? apiNames === apiName : apiNames.has(apiName);
|
|
7949
7735
|
const isImportedFromReact = (symbol) => {
|
|
@@ -7951,9 +7737,9 @@ const isImportedFromReact = (symbol) => {
|
|
|
7951
7737
|
const importDeclaration = symbol.declarationNode.parent;
|
|
7952
7738
|
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(importDeclaration.source.value));
|
|
7953
7739
|
};
|
|
7954
|
-
const isNamedReactApiImport = (identifier, apiNames, scopes
|
|
7740
|
+
const isNamedReactApiImport = (identifier, apiNames, scopes) => {
|
|
7955
7741
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
7956
|
-
const symbol =
|
|
7742
|
+
const symbol = scopes.symbolFor(identifier);
|
|
7957
7743
|
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
7958
7744
|
const importedName = getImportedName(symbol.declarationNode);
|
|
7959
7745
|
return Boolean(importedName && includesApiName(apiNames, importedName));
|
|
@@ -7967,7 +7753,7 @@ const isReactApiCall = (node, apiNames, scopes, options = {}) => {
|
|
|
7967
7753
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7968
7754
|
const callee = stripParenExpression(node.callee);
|
|
7969
7755
|
if (isNodeOfType(callee, "Identifier")) {
|
|
7970
|
-
if (isNamedReactApiImport(callee, apiNames, scopes
|
|
7756
|
+
if (isNamedReactApiImport(callee, apiNames, scopes)) return true;
|
|
7971
7757
|
return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
|
|
7972
7758
|
}
|
|
7973
7759
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || !includesApiName(apiNames, callee.property.name)) return false;
|
|
@@ -9070,30 +8856,6 @@ const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, conte
|
|
|
9070
8856
|
}
|
|
9071
8857
|
return matchingBlocks.size > 0;
|
|
9072
8858
|
};
|
|
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
8859
|
const removeSynchronouslyReleasedUsages = (callback, usages, context) => {
|
|
9098
8860
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
|
|
9099
8861
|
if (!isNodeOfType(callback.body, "BlockStatement")) return usages;
|
|
@@ -9294,83 +9056,6 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
|
|
|
9294
9056
|
});
|
|
9295
9057
|
return didCleanupFunctionMatch;
|
|
9296
9058
|
};
|
|
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
9059
|
const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
9375
9060
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
9376
9061
|
if (usage.kind === "subscribe" && findEnclosingFunction(usage.node) === callback && doesResourceResultEscape(usage.node, true) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
|
|
@@ -9383,10 +9068,6 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
|
9383
9068
|
if (returnStart !== null && usageStart !== null && returnStart < usageStart) return;
|
|
9384
9069
|
const returnedValue = child.argument ? stripParenExpression(child.argument) : null;
|
|
9385
9070
|
if (!returnedValue) return;
|
|
9386
|
-
if (doesBoundCleanupReleaseUsage(returnedValue, usage, context)) {
|
|
9387
|
-
matchingCleanupReturns.push(child);
|
|
9388
|
-
return;
|
|
9389
|
-
}
|
|
9390
9071
|
if (usage.kind === "subscribe" && (returnedValue === usage.node || getRangeStart(returnedValue) !== null && getRangeStart(returnedValue) === getRangeStart(usage.node)) && isCleanupReturningSubscribeLikeCallExpression(returnedValue)) {
|
|
9391
9072
|
matchingCleanupReturns.push(child);
|
|
9392
9073
|
return;
|
|
@@ -9402,17 +9083,13 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
|
9402
9083
|
if (!context.scopes.symbolFor(returnedValue)?.initializer) return;
|
|
9403
9084
|
}
|
|
9404
9085
|
const cleanupFunction = resolveStableValue(returnedValue, context);
|
|
9405
|
-
if (cleanupFunction && doesBoundCleanupReleaseUsage(cleanupFunction, usage, context)) {
|
|
9406
|
-
matchingCleanupReturns.push(child);
|
|
9407
|
-
return;
|
|
9408
|
-
}
|
|
9409
9086
|
if (!cleanupFunction || !isFunctionLike$1(cleanupFunction)) return;
|
|
9410
9087
|
if (doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context)) matchingCleanupReturns.push(child);
|
|
9411
9088
|
});
|
|
9412
9089
|
return doMatchingNodesCoverEveryPathAfterUsage(resolveCleanupPathAnchor(usage.node, callback, context), matchingCleanupReturns, context);
|
|
9413
9090
|
};
|
|
9414
9091
|
const findFirstUsageWithoutCleanup = (callback, usages, context) => {
|
|
9415
|
-
for (const usage of usages) if (!effectHasCleanupForUsage(callback, usage, context)
|
|
9092
|
+
for (const usage of usages) if (!effectHasCleanupForUsage(callback, usage, context)) return usage;
|
|
9416
9093
|
return null;
|
|
9417
9094
|
};
|
|
9418
9095
|
const isLocalAbortControllerExpression = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
@@ -11285,11 +10962,6 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
11285
10962
|
return { CallExpression(node) {
|
|
11286
10963
|
const hookName = getHookName(node.callee, context.scopes);
|
|
11287
10964
|
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
10965
|
const callbackArgumentIndex = getCallbackArgumentIndex(hookName);
|
|
11294
10966
|
const depsArgumentIndex = getDepsArgumentIndex(hookName);
|
|
11295
10967
|
const callbackArgument = node.arguments[callbackArgumentIndex];
|
|
@@ -14584,11 +14256,7 @@ const jsIndexMaps = defineRule({
|
|
|
14584
14256
|
//#region src/plugin/utils/are-expressions-structurally-equal.ts
|
|
14585
14257
|
const areExpressionsStructurallyEqual = (a, b) => {
|
|
14586
14258
|
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
14259
|
if (a.type !== b.type) return false;
|
|
14591
|
-
if (isNodeOfType(a, "ThisExpression")) return true;
|
|
14592
14260
|
if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
|
|
14593
14261
|
if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
|
|
14594
14262
|
if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
|
|
@@ -21131,8 +20799,8 @@ const catchClauseRethrowsCaught = (handler) => {
|
|
|
21131
20799
|
return didRethrow;
|
|
21132
20800
|
};
|
|
21133
20801
|
//#endregion
|
|
21134
|
-
//#region src/plugin/utils/
|
|
21135
|
-
const
|
|
20802
|
+
//#region src/plugin/utils/find-guarding-try-statement.ts
|
|
20803
|
+
const isImmediatelyInvokedFunctionCallee = (functionNode) => {
|
|
21136
20804
|
let wrappedCallee = functionNode;
|
|
21137
20805
|
let enclosing = functionNode.parent;
|
|
21138
20806
|
while (enclosing && stripParenExpression(enclosing) === functionNode) {
|
|
@@ -21141,13 +20809,11 @@ const isImmediatelyInvokedFunction = (functionNode) => {
|
|
|
21141
20809
|
}
|
|
21142
20810
|
return Boolean(enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee);
|
|
21143
20811
|
};
|
|
21144
|
-
//#endregion
|
|
21145
|
-
//#region src/plugin/utils/find-guarding-try-statement.ts
|
|
21146
20812
|
const findGuardingTryStatement = (node) => {
|
|
21147
20813
|
let child = node;
|
|
21148
20814
|
let ancestor = node.parent;
|
|
21149
20815
|
while (ancestor) {
|
|
21150
|
-
if (isFunctionLike$1(ancestor) && !
|
|
20816
|
+
if (isFunctionLike$1(ancestor) && !isImmediatelyInvokedFunctionCallee(ancestor)) return null;
|
|
21151
20817
|
if (isNodeOfType(ancestor, "TryStatement") && ancestor.block === child && ancestor.handler && !catchClauseRethrowsCaught(ancestor.handler)) return ancestor;
|
|
21152
20818
|
child = ancestor;
|
|
21153
20819
|
ancestor = ancestor.parent ?? null;
|
|
@@ -22280,8 +21946,6 @@ const DOM_QUERY_MEMBER_NAMES = new Set([
|
|
|
22280
21946
|
]);
|
|
22281
21947
|
const LAYOUT_MEASUREMENT_MEMBER_NAMES = new Set([
|
|
22282
21948
|
"current",
|
|
22283
|
-
"textContent",
|
|
22284
|
-
"innerText",
|
|
22285
21949
|
"scrollWidth",
|
|
22286
21950
|
"clientWidth",
|
|
22287
21951
|
"offsetWidth",
|
|
@@ -22303,7 +21967,7 @@ const POST_MOUNT_GLOBAL_NAMES = new Set([
|
|
|
22303
21967
|
"navigator"
|
|
22304
21968
|
]);
|
|
22305
21969
|
const REF_FACTORY_CALLEE_NAMES = new Set(["useRef", "createRef"]);
|
|
22306
|
-
const hasRefLikeName = (name) => name === "ref" || name.endsWith("Ref") || name.endsWith("ref")
|
|
21970
|
+
const hasRefLikeName = (name) => name === "ref" || name.endsWith("Ref") || name.endsWith("ref");
|
|
22307
21971
|
const isRefFactoryInitializer = (init) => {
|
|
22308
21972
|
if (!init || !isNodeOfType(init, "CallExpression")) return false;
|
|
22309
21973
|
const callee = init.callee;
|
|
@@ -23217,73 +22881,61 @@ const PURE_GLOBAL_CALLEE_NAMES = new Set([
|
|
|
23217
22881
|
"Array",
|
|
23218
22882
|
"BigInt",
|
|
23219
22883
|
"Boolean",
|
|
23220
|
-
"encodeURIComponent",
|
|
23221
22884
|
"Number",
|
|
23222
22885
|
"Object",
|
|
23223
22886
|
"String",
|
|
23224
22887
|
"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"])]
|
|
22888
|
+
"parseInt"
|
|
23273
22889
|
]);
|
|
22890
|
+
const PURE_GLOBAL_NAMESPACE_NAMES = new Set(["JSON", "Math"]);
|
|
22891
|
+
const PURE_HELPER_NAMESPACE_MEMBER_NAMES = new Map([["JSON", new Set([
|
|
22892
|
+
"isRawJSON",
|
|
22893
|
+
"parse",
|
|
22894
|
+
"rawJSON",
|
|
22895
|
+
"stringify"
|
|
22896
|
+
])], ["Math", new Set([
|
|
22897
|
+
"abs",
|
|
22898
|
+
"acos",
|
|
22899
|
+
"acosh",
|
|
22900
|
+
"asin",
|
|
22901
|
+
"asinh",
|
|
22902
|
+
"atan",
|
|
22903
|
+
"atan2",
|
|
22904
|
+
"atanh",
|
|
22905
|
+
"cbrt",
|
|
22906
|
+
"ceil",
|
|
22907
|
+
"clz32",
|
|
22908
|
+
"cos",
|
|
22909
|
+
"cosh",
|
|
22910
|
+
"exp",
|
|
22911
|
+
"floor",
|
|
22912
|
+
"fround",
|
|
22913
|
+
"hypot",
|
|
22914
|
+
"imul",
|
|
22915
|
+
"log",
|
|
22916
|
+
"log10",
|
|
22917
|
+
"log1p",
|
|
22918
|
+
"log2",
|
|
22919
|
+
"max",
|
|
22920
|
+
"min",
|
|
22921
|
+
"pow",
|
|
22922
|
+
"round",
|
|
22923
|
+
"sign",
|
|
22924
|
+
"sin",
|
|
22925
|
+
"sinh",
|
|
22926
|
+
"sqrt",
|
|
22927
|
+
"tan",
|
|
22928
|
+
"tanh",
|
|
22929
|
+
"trunc"
|
|
22930
|
+
])]]);
|
|
23274
22931
|
const PURE_MEMBER_TRANSFORM_NAMES = new Set([
|
|
23275
22932
|
"concat",
|
|
23276
22933
|
"filter",
|
|
23277
|
-
"flatMap",
|
|
23278
22934
|
"join",
|
|
23279
22935
|
"map",
|
|
23280
|
-
"reduce",
|
|
23281
|
-
"replace",
|
|
23282
|
-
"slice",
|
|
23283
22936
|
"split",
|
|
23284
22937
|
"toLowerCase",
|
|
23285
22938
|
"toString",
|
|
23286
|
-
"toSorted",
|
|
23287
22939
|
"toUpperCase",
|
|
23288
22940
|
"trim"
|
|
23289
22941
|
]);
|
|
@@ -23292,7 +22944,7 @@ const STORAGE_GLOBAL_NAMES$1 = new Set([
|
|
|
23292
22944
|
"localStorage",
|
|
23293
22945
|
"sessionStorage"
|
|
23294
22946
|
]);
|
|
23295
|
-
const getStaticMemberName
|
|
22947
|
+
const getStaticMemberName = (node) => {
|
|
23296
22948
|
if (!isNodeOfType(node, "MemberExpression")) return null;
|
|
23297
22949
|
if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
|
|
23298
22950
|
if (node.computed && isNodeOfType(node.property, "Literal")) return typeof node.property.value === "string" ? node.property.value : null;
|
|
@@ -23307,7 +22959,7 @@ const getCallCalleeName$1 = (callExpression) => {
|
|
|
23307
22959
|
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
23308
22960
|
const callee = stripParenExpression(callExpression.callee);
|
|
23309
22961
|
if (isNodeOfType(callee, "Identifier")) return callee.name;
|
|
23310
|
-
return getStaticMemberName
|
|
22962
|
+
return getStaticMemberName(callee);
|
|
23311
22963
|
};
|
|
23312
22964
|
const getFunctionParameters = (functionNode) => isFunctionLike$1(functionNode) ? functionNode.params ?? [] : [];
|
|
23313
22965
|
const getIdentifierBindingIdentity = (analysis, identifier) => {
|
|
@@ -23423,14 +23075,14 @@ const localUseEventPreservesCallback = (analysis, callee) => {
|
|
|
23423
23075
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
23424
23076
|
const forwardedCallee = stripParenExpression(child.callee);
|
|
23425
23077
|
if (!isNodeOfType(forwardedCallee, "MemberExpression")) return;
|
|
23426
|
-
if (getStaticMemberName
|
|
23078
|
+
if (getStaticMemberName(forwardedCallee) !== "current") return;
|
|
23427
23079
|
if (!isNodeOfType(forwardedCallee.object, "Identifier")) return;
|
|
23428
23080
|
const refReference = getRef(analysis, forwardedCallee.object);
|
|
23429
23081
|
if (!callbackRefDeclarators.find((declarator) => refReference?.resolved?.defs.some((definition) => definition.node === declarator)) || !refReference?.resolved) return;
|
|
23430
23082
|
if (!refReference.resolved.references.some((candidateReference) => {
|
|
23431
23083
|
const memberExpression = candidateReference.identifier.parent;
|
|
23432
23084
|
const assignmentExpression = memberExpression?.parent;
|
|
23433
|
-
if (!memberExpression || !isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName
|
|
23085
|
+
if (!memberExpression || !isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !assignmentExpression || !isNodeOfType(assignmentExpression, "AssignmentExpression") || assignmentExpression.left !== memberExpression) return false;
|
|
23434
23086
|
return getIdentifierBindingIdentity(analysis, assignmentExpression.right) !== callbackBinding;
|
|
23435
23087
|
})) forwardsCallback = true;
|
|
23436
23088
|
});
|
|
@@ -23455,7 +23107,7 @@ const resolveWrappedCallable = (analysis, node) => {
|
|
|
23455
23107
|
return callback && isFunctionLike$1(callback) ? callback : null;
|
|
23456
23108
|
}
|
|
23457
23109
|
if (!isNodeOfType(candidate, "MemberExpression")) return null;
|
|
23458
|
-
if (getStaticMemberName
|
|
23110
|
+
if (getStaticMemberName(candidate) !== "current") return null;
|
|
23459
23111
|
if (!isNodeOfType(candidate.object, "Identifier")) return null;
|
|
23460
23112
|
const reference = getRef(analysis, candidate.object);
|
|
23461
23113
|
const declarator = reference?.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
@@ -23467,7 +23119,7 @@ const resolveWrappedCallable = (analysis, node) => {
|
|
|
23467
23119
|
return Boolean(reference?.resolved?.references.some((candidateReference) => {
|
|
23468
23120
|
const member = candidateReference.identifier.parent;
|
|
23469
23121
|
const assignment = member?.parent;
|
|
23470
|
-
return Boolean(member && isNodeOfType(member, "MemberExpression") && getStaticMemberName
|
|
23122
|
+
return Boolean(member && isNodeOfType(member, "MemberExpression") && getStaticMemberName(member) === "current" && assignment && isNodeOfType(assignment, "AssignmentExpression") && assignment.left === member);
|
|
23471
23123
|
})) ? null : initializer;
|
|
23472
23124
|
};
|
|
23473
23125
|
const functionInvokesItself = (analysis, functionNode) => {
|
|
@@ -23506,7 +23158,7 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilena
|
|
|
23506
23158
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
23507
23159
|
const callee = stripParenExpression(child.callee);
|
|
23508
23160
|
const calleeName = getCallCalleeName$1(child);
|
|
23509
|
-
const memberName = getStaticMemberName
|
|
23161
|
+
const memberName = getStaticMemberName(callee);
|
|
23510
23162
|
const isDeferringCall = calleeName !== null && DEFERRING_CALLEE_NAMES.has(calleeName) || memberName !== null && DEFERRING_MEMBER_NAMES.has(memberName);
|
|
23511
23163
|
const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES$1.has(memberName) && isNodeOfType(callee, "MemberExpression");
|
|
23512
23164
|
const enqueueFrame = (callableNode, argumentsForCallable, isDeferred, introducedBindings, allowWithoutInvocationEvidence = false) => {
|
|
@@ -23665,19 +23317,14 @@ const analyzeHelperExpression = (expression, environment, usedParameterIndices)
|
|
|
23665
23317
|
const callbackSummary = analyzeHelperStatements(node.body.body ?? [], callbackEnvironment, usedParameterIndices, analyzeHelperExpression);
|
|
23666
23318
|
return callbackSummary.isValid && !callbackSummary.canContinue;
|
|
23667
23319
|
}
|
|
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
23320
|
if (isNodeOfType(node, "CallExpression")) {
|
|
23674
23321
|
const callee = stripParenExpression(node.callee);
|
|
23675
23322
|
const calleeRoot = getMemberRoot(callee);
|
|
23676
23323
|
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
23324
|
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
|
|
23325
|
+
const namespaceMemberName = getStaticMemberName(callee);
|
|
23679
23326
|
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
|
|
23327
|
+
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
|
|
23681
23328
|
if (!isPureGlobalCall && !isPureNamespaceCall && !isPureMemberTransform) return false;
|
|
23682
23329
|
if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression") && !analyzeHelperExpression(callee.object, environment, usedParameterIndices)) return false;
|
|
23683
23330
|
return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
|
|
@@ -23751,7 +23398,7 @@ const isLocallyConstructedObjectMember = (reference, memberExpression) => isNode
|
|
|
23751
23398
|
return isNodeOfType(definitionNode, "VariableDeclarator") && Boolean(definitionNode.init) && (isNodeOfType(definitionNode.init, "ObjectExpression") || isNodeOfType(definitionNode.init, "ArrayExpression"));
|
|
23752
23399
|
}) === true;
|
|
23753
23400
|
const getUseRefDeclarator = (analysis, memberExpression) => {
|
|
23754
|
-
if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName
|
|
23401
|
+
if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !isNodeOfType(memberExpression.object, "Identifier")) return null;
|
|
23755
23402
|
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
23403
|
};
|
|
23757
23404
|
const collectValueEvidence = (analysis, expression, frame, remainingCallFrames, visitedBindings = /* @__PURE__ */ new Set()) => {
|
|
@@ -23820,7 +23467,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23820
23467
|
return collectValueEvidence(analysis, initializer.init, frame, remainingCallFrames, visitedBindings);
|
|
23821
23468
|
}
|
|
23822
23469
|
if (isNodeOfType(node, "MemberExpression")) {
|
|
23823
|
-
if (getStaticMemberName
|
|
23470
|
+
if (getStaticMemberName(node) === "current" && isNodeOfType(node.object, "Identifier")) {
|
|
23824
23471
|
const refBinding = getRef(analysis, node.object)?.resolved;
|
|
23825
23472
|
const refDeclarator = useRefDeclarator;
|
|
23826
23473
|
if (refBinding && refDeclarator && isNodeOfType(refDeclarator, "VariableDeclarator") && isNodeOfType(refDeclarator.init, "CallExpression")) {
|
|
@@ -23836,7 +23483,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23836
23483
|
if (candidateReference.init) continue;
|
|
23837
23484
|
const identifier = candidateReference.identifier;
|
|
23838
23485
|
const member = identifier.parent;
|
|
23839
|
-
if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName
|
|
23486
|
+
if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName(member) !== "current") {
|
|
23840
23487
|
evidence.hasUnknownSource = true;
|
|
23841
23488
|
continue;
|
|
23842
23489
|
}
|
|
@@ -23872,8 +23519,8 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23872
23519
|
}
|
|
23873
23520
|
const callee = stripParenExpression(node.callee);
|
|
23874
23521
|
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
|
|
23522
|
+
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;
|
|
23523
|
+
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
|
|
23877
23524
|
if (isPureGlobalCall || isPureMemberTransform) {
|
|
23878
23525
|
if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression")) mergeEvidence(evidence, collectValueEvidence(analysis, callee.object, frame, remainingCallFrames, new Set(visitedBindings)));
|
|
23879
23526
|
for (const argument of node.arguments ?? []) {
|
|
@@ -23925,15 +23572,6 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23925
23572
|
}
|
|
23926
23573
|
return evidence;
|
|
23927
23574
|
}
|
|
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
23575
|
if (isFunctionLike$1(node) || isNodeOfType(node, "AwaitExpression")) {
|
|
23938
23576
|
evidence.hasUnknownSource = true;
|
|
23939
23577
|
return evidence;
|
|
@@ -24352,26 +23990,6 @@ const STATEFUL_HTML_DESCENDANT_TAGS = new Set([
|
|
|
24352
23990
|
"dialog",
|
|
24353
23991
|
"canvas"
|
|
24354
23992
|
]);
|
|
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
23993
|
const STATEFUL_DESCENDANT_SCAN_BUDGET = 200;
|
|
24376
23994
|
const rootIdentifierNameOf = (expression) => {
|
|
24377
23995
|
let object = expression;
|
|
@@ -24389,8 +24007,7 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
|
|
|
24389
24007
|
budget -= 1;
|
|
24390
24008
|
const node = stack.pop();
|
|
24391
24009
|
if (isNodeOfType(node, "JSXElement")) {
|
|
24392
|
-
const
|
|
24393
|
-
const name = opening.name;
|
|
24010
|
+
const name = node.openingElement.name;
|
|
24394
24011
|
if (name && isNodeOfType(name, "JSXIdentifier")) {
|
|
24395
24012
|
const tagName = name.name;
|
|
24396
24013
|
const firstChar = tagName.charCodeAt(0);
|
|
@@ -24398,7 +24015,6 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
|
|
|
24398
24015
|
if (STATEFUL_HTML_DESCENDANT_TAGS.has(tagName)) return true;
|
|
24399
24016
|
}
|
|
24400
24017
|
if (name && isNodeOfType(name, "JSXMemberExpression")) return true;
|
|
24401
|
-
if (hasStatefulHtmlAttribute(opening)) return true;
|
|
24402
24018
|
const children = node.children ?? [];
|
|
24403
24019
|
for (const child of children) stack.push(child);
|
|
24404
24020
|
continue;
|
|
@@ -24958,14 +24574,6 @@ const templateHasOuterMemberIdentity = (template, bindingFunction) => {
|
|
|
24958
24574
|
}
|
|
24959
24575
|
return false;
|
|
24960
24576
|
};
|
|
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
24577
|
const forLoopTestReadsDataLength = (test) => {
|
|
24970
24578
|
let didFindLengthRead = false;
|
|
24971
24579
|
walkAst(test, (child) => {
|
|
@@ -25102,11 +24710,9 @@ const noArrayIndexAsKey = defineRule({
|
|
|
25102
24710
|
} else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
|
|
25103
24711
|
const jsxElement = openingElement.parent;
|
|
25104
24712
|
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
24713
|
if (!containsStatefulDescendant(jsxElement, {
|
|
25108
|
-
memberRootNames:
|
|
25109
|
-
bareIdentifierNames:
|
|
24714
|
+
memberRootNames: INLINE_TEXT_LEAF_TAGS.has(elementName.name) ? itemNames : EMPTY_NAME_SET,
|
|
24715
|
+
bareIdentifierNames: derivedNames
|
|
25110
24716
|
})) return;
|
|
25111
24717
|
}
|
|
25112
24718
|
}
|
|
@@ -25274,11 +24880,7 @@ const noAsyncEffectCallback = defineRule({
|
|
|
25274
24880
|
severity: "warn",
|
|
25275
24881
|
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
24882
|
create: (context) => ({ CallExpression(node) {
|
|
25277
|
-
if (!
|
|
25278
|
-
allowGlobalReactNamespace: true,
|
|
25279
|
-
allowUnboundBareCalls: true,
|
|
25280
|
-
resolveNamedAliases: true
|
|
25281
|
-
})) return;
|
|
24883
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
25282
24884
|
const callback = getEffectCallback(node);
|
|
25283
24885
|
if (!callback) return;
|
|
25284
24886
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
@@ -26476,19 +26078,6 @@ const noCloneElement = defineRule({
|
|
|
26476
26078
|
} })
|
|
26477
26079
|
});
|
|
26478
26080
|
//#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
26081
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
26493
26082
|
const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
26494
26083
|
const CONTEXT_MODULES = [
|
|
@@ -26496,35 +26085,23 @@ const CONTEXT_MODULES = [
|
|
|
26496
26085
|
"use-context-selector",
|
|
26497
26086
|
"react-tracked"
|
|
26498
26087
|
];
|
|
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);
|
|
26088
|
+
const isCreateContextCallee = (callee) => {
|
|
26517
26089
|
if (isNodeOfType(callee, "Identifier")) {
|
|
26518
|
-
|
|
26519
|
-
|
|
26520
|
-
return getSupportedContextImportSource(symbol) !== null && Boolean(symbol && getImportedName(symbol.declarationNode) === "createContext");
|
|
26090
|
+
const binding = getImportBindingForName(callee, callee.name);
|
|
26091
|
+
return binding !== null && binding.exportedName === "createContext" && CONTEXT_MODULES.includes(binding.source);
|
|
26521
26092
|
}
|
|
26522
|
-
if (
|
|
26523
|
-
|
|
26524
|
-
|
|
26525
|
-
|
|
26526
|
-
|
|
26527
|
-
|
|
26093
|
+
if (isNodeOfType(callee, "MemberExpression") && !callee.computed) {
|
|
26094
|
+
const namespaceIdentifier = callee.object;
|
|
26095
|
+
const propertyIdentifier = callee.property;
|
|
26096
|
+
if (!isNodeOfType(namespaceIdentifier, "Identifier")) return false;
|
|
26097
|
+
if (!isNodeOfType(propertyIdentifier, "Identifier")) return false;
|
|
26098
|
+
if (propertyIdentifier.name !== "createContext") return false;
|
|
26099
|
+
const namespaceName = namespaceIdentifier.name;
|
|
26100
|
+
if (isCanonicalReactNamespaceName(namespaceName)) return true;
|
|
26101
|
+
const importSource = getImportSourceForName(namespaceIdentifier, namespaceName);
|
|
26102
|
+
return importSource !== null && CONTEXT_MODULES.includes(importSource);
|
|
26103
|
+
}
|
|
26104
|
+
return false;
|
|
26528
26105
|
};
|
|
26529
26106
|
const noCreateContextInRender = defineRule({
|
|
26530
26107
|
id: "no-create-context-in-render",
|
|
@@ -26533,7 +26110,7 @@ const noCreateContextInRender = defineRule({
|
|
|
26533
26110
|
category: "Correctness",
|
|
26534
26111
|
recommendation: "Move `createContext(...)` outside the component, to the top level of the file, so it stays the same on every render.",
|
|
26535
26112
|
create: (context) => ({ CallExpression(node) {
|
|
26536
|
-
if (!
|
|
26113
|
+
if (!isCreateContextCallee(node.callee)) return;
|
|
26537
26114
|
const componentOrHookName = enclosingComponentOrHookName(node);
|
|
26538
26115
|
if (!componentOrHookName) return;
|
|
26539
26116
|
context.report({
|
|
@@ -27768,7 +27345,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
27768
27345
|
let ancestor = setStateCall.parent;
|
|
27769
27346
|
while (ancestor) {
|
|
27770
27347
|
if (!lifecycleMember) {
|
|
27771
|
-
if (FUNCTION_NODE_TYPES$1.has(ancestor.type)
|
|
27348
|
+
if (FUNCTION_NODE_TYPES$1.has(ancestor.type)) nestedFunctionCount += 1;
|
|
27772
27349
|
if (isLifecycleMember(ancestor, lifecycleNames)) lifecycleMember = ancestor;
|
|
27773
27350
|
} else if (isEs5Component(ancestor) || isEs6Component(ancestor)) {
|
|
27774
27351
|
if (nestedFunctionCount > 1 && !options.disallowInNestedFunctions) return false;
|
|
@@ -27970,7 +27547,7 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
|
|
|
27970
27547
|
const body = lifecycleFunction.body;
|
|
27971
27548
|
if (!body) return derivedNames;
|
|
27972
27549
|
walkAst(body, (node) => {
|
|
27973
|
-
if (FUNCTION_NODE_TYPES.has(node.type)
|
|
27550
|
+
if (FUNCTION_NODE_TYPES.has(node.type)) return false;
|
|
27974
27551
|
if (!isNodeOfType(node, "VariableDeclarator")) return;
|
|
27975
27552
|
const init = node.init;
|
|
27976
27553
|
if (!init) return;
|
|
@@ -27980,67 +27557,6 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
|
|
|
27980
27557
|
return derivedNames;
|
|
27981
27558
|
};
|
|
27982
27559
|
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
27560
|
const isDiffGuardTest = (test, paramNames, derivedNames) => {
|
|
28045
27561
|
if (referencesAnyName(test, paramNames)) return true;
|
|
28046
27562
|
let qualifies = false;
|
|
@@ -28048,7 +27564,7 @@ const isDiffGuardTest = (test, paramNames, derivedNames) => {
|
|
|
28048
27564
|
if (qualifies) return false;
|
|
28049
27565
|
if (!isNodeOfType(node, "BinaryExpression")) return;
|
|
28050
27566
|
if (!EQUALITY_OPERATORS.has(node.operator)) return;
|
|
28051
|
-
if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames)
|
|
27567
|
+
if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames)) {
|
|
28052
27568
|
qualifies = true;
|
|
28053
27569
|
return false;
|
|
28054
27570
|
}
|
|
@@ -28061,12 +27577,11 @@ const isInsideDiffGuard = (setStateCall) => {
|
|
|
28061
27577
|
const paramNames = /* @__PURE__ */ new Set();
|
|
28062
27578
|
for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
|
|
28063
27579
|
const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
|
|
28064
|
-
const localInitializers = collectLocalInitializers(lifecycleFunction);
|
|
28065
27580
|
let child = setStateCall;
|
|
28066
27581
|
let ancestor = setStateCall.parent;
|
|
28067
27582
|
while (ancestor && ancestor !== lifecycleFunction) {
|
|
28068
27583
|
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 &&
|
|
27584
|
+
if (guardTest && isDiffGuardTest(guardTest, paramNames, derivedNames)) return true;
|
|
28070
27585
|
child = ancestor;
|
|
28071
27586
|
ancestor = ancestor.parent ?? null;
|
|
28072
27587
|
}
|
|
@@ -28483,31 +27998,20 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
28483
27998
|
} })
|
|
28484
27999
|
});
|
|
28485
28000
|
//#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
28001
|
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
28496
28002
|
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
28003
|
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
28498
|
-
const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
|
|
28499
28004
|
const noDocumentWrite = defineRule({
|
|
28500
28005
|
id: "no-document-write",
|
|
28501
28006
|
title: "document.write/writeln",
|
|
28502
28007
|
severity: "warn",
|
|
28503
28008
|
recommendation: "Don't use `document.write()`/`document.writeln()`. Append DOM nodes or set `innerHTML`/`textContent` on a specific element instead.",
|
|
28504
28009
|
create: (context) => ({ CallExpression(node) {
|
|
28505
|
-
const callee =
|
|
28506
|
-
if (!isNodeOfType(callee, "MemberExpression")) return;
|
|
28010
|
+
const callee = node.callee;
|
|
28011
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
|
|
28507
28012
|
const receiver = stripParenExpression(callee.object);
|
|
28508
|
-
if (!isNodeOfType(receiver, "Identifier") ||
|
|
28509
|
-
|
|
28510
|
-
if (methodName === null || !WRITE_METHODS.has(methodName)) return;
|
|
28013
|
+
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "document") return;
|
|
28014
|
+
if (!isNodeOfType(callee.property, "Identifier") || !WRITE_METHODS.has(callee.property.name)) return;
|
|
28511
28015
|
context.report({
|
|
28512
28016
|
node,
|
|
28513
28017
|
message: MESSAGE$24
|
|
@@ -29314,14 +28818,6 @@ const noEffectWithFreshDeps = defineRule({
|
|
|
29314
28818
|
//#endregion
|
|
29315
28819
|
//#region src/plugin/rules/security/no-eval.ts
|
|
29316
28820
|
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
28821
|
const noEval = defineRule({
|
|
29326
28822
|
id: "no-eval",
|
|
29327
28823
|
title: "eval() runs untrusted code strings",
|
|
@@ -29331,21 +28827,20 @@ const noEval = defineRule({
|
|
|
29331
28827
|
if (SANDBOX_SURFACE_PATH_PATTERN.test(normalizeFilename(context.filename ?? ""))) return {};
|
|
29332
28828
|
return {
|
|
29333
28829
|
CallExpression(node) {
|
|
29334
|
-
|
|
29335
|
-
if (executableGlobalName === "eval") {
|
|
28830
|
+
if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "eval") {
|
|
29336
28831
|
context.report({
|
|
29337
28832
|
node,
|
|
29338
28833
|
message: "eval() is a code-injection vulnerability: it runs any string as code."
|
|
29339
28834
|
});
|
|
29340
28835
|
return;
|
|
29341
28836
|
}
|
|
29342
|
-
if ((
|
|
28837
|
+
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
28838
|
node,
|
|
29344
|
-
message: `Passing a string to ${
|
|
28839
|
+
message: `Passing a string to ${node.callee.name}() is a code-injection vulnerability, since it runs that string as code.`
|
|
29345
28840
|
});
|
|
29346
28841
|
},
|
|
29347
28842
|
NewExpression(node) {
|
|
29348
|
-
if (
|
|
28843
|
+
if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "Function") {
|
|
29349
28844
|
const onlyArgument = node.arguments.length === 1 ? node.arguments[0] : void 0;
|
|
29350
28845
|
if (isNodeOfType(onlyArgument, "Literal") && typeof onlyArgument.value === "string" && onlyArgument.value.trim() === "return this") return;
|
|
29351
28846
|
context.report({
|
|
@@ -30604,11 +30099,7 @@ const noFetchInEffect = defineRule({
|
|
|
30604
30099
|
severity: "warn",
|
|
30605
30100
|
recommendation: "Use a data-fetching layer or Server Component so fetches do not race, double-fire, or leak from `useEffect`.",
|
|
30606
30101
|
create: (context) => ({ CallExpression(node) {
|
|
30607
|
-
if (!
|
|
30608
|
-
allowGlobalReactNamespace: true,
|
|
30609
|
-
allowUnboundBareCalls: true,
|
|
30610
|
-
resolveNamedAliases: true
|
|
30611
|
-
})) return;
|
|
30102
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
30612
30103
|
const callback = getEffectCallback(node);
|
|
30613
30104
|
if (!callback) return;
|
|
30614
30105
|
const analysisFunctions = collectEffectAnalysisFunctions(callback, context);
|
|
@@ -34818,6 +34309,19 @@ const DATA_SINK_METHOD_NAMES = new Set([
|
|
|
34818
34309
|
"deserialize"
|
|
34819
34310
|
]);
|
|
34820
34311
|
//#endregion
|
|
34312
|
+
//#region src/plugin/utils/get-call-method-name.ts
|
|
34313
|
+
/**
|
|
34314
|
+
* Returns the static method name of a call's callee when it's a
|
|
34315
|
+
* non-computed MemberExpression (`obj.method` → `"method"`), or
|
|
34316
|
+
* `null` otherwise. Used by `no-pass-data-to-parent` and
|
|
34317
|
+
* `no-pass-live-state-to-parent` to match the receiver-bound
|
|
34318
|
+
* method-call shape against an allow/block list.
|
|
34319
|
+
*/
|
|
34320
|
+
const getCallMethodName = (callee) => {
|
|
34321
|
+
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
|
|
34322
|
+
return null;
|
|
34323
|
+
};
|
|
34324
|
+
//#endregion
|
|
34821
34325
|
//#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
|
|
34822
34326
|
const isUseStateIdentifier = (identifier) => {
|
|
34823
34327
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
@@ -35062,14 +34566,6 @@ const isHandlerBagArgument = (analysis, argument) => {
|
|
|
35062
34566
|
};
|
|
35063
34567
|
const getFunctionalUpdaterDataRefs = (analysis, updater) => getDownstreamRefs(analysis, updater).filter((updaterRef) => !updaterRef.resolved?.defs.some((def) => def.type === "Parameter" && def.node === updater));
|
|
35064
34568
|
const HOOK_NAME_PATTERN$1 = /^use[A-Z0-9]/;
|
|
35065
|
-
const EXTERNAL_SUBSCRIPTION_HOOK_NAMES = new Set([
|
|
35066
|
-
"useIntersectionObserver",
|
|
35067
|
-
"useMatchMedia",
|
|
35068
|
-
"useMediaQuery",
|
|
35069
|
-
"useResizeObserver",
|
|
35070
|
-
"useVisibility",
|
|
35071
|
-
"useWindowSize"
|
|
35072
|
-
]);
|
|
35073
34569
|
const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
|
|
35074
34570
|
const node = def.node;
|
|
35075
34571
|
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
@@ -35092,19 +34588,6 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
|
|
|
35092
34588
|
if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
|
|
35093
34589
|
return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isProp(analysis, downstreamRef)));
|
|
35094
34590
|
};
|
|
35095
|
-
const isExternalSubscriptionHookRef = (ref) => {
|
|
35096
|
-
const identifier = ref.identifier;
|
|
35097
|
-
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
35098
|
-
if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(identifier.name) && isCalleePosition(identifier)) return true;
|
|
35099
|
-
return Boolean(ref.resolved?.defs.some((def) => {
|
|
35100
|
-
const node = def.node;
|
|
35101
|
-
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
35102
|
-
const initializer = stripParenExpression(node.init);
|
|
35103
|
-
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
35104
|
-
const callee = stripParenExpression(initializer.callee);
|
|
35105
|
-
return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(callee.name);
|
|
35106
|
-
}));
|
|
35107
|
-
};
|
|
35108
34591
|
const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
|
|
35109
34592
|
const isCalleePosition = (identifier) => {
|
|
35110
34593
|
const parent = identifier.parent;
|
|
@@ -35166,11 +34649,10 @@ const noPassDataToParent = defineRule({
|
|
|
35166
34649
|
if (argumentRef && resolveToFunction(argumentRef)) return [];
|
|
35167
34650
|
}
|
|
35168
34651
|
return getDownstreamRefs(analysis, argument);
|
|
35169
|
-
}).flatMap((argumentRef) =>
|
|
34652
|
+
}).flatMap((argumentRef) => getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
|
|
35170
34653
|
if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
|
|
35171
34654
|
if (!argsUpstreamRefs.some((argRef) => {
|
|
35172
34655
|
if (isUseStateIdentifier(argRef.identifier)) return false;
|
|
35173
|
-
if (isExternalSubscriptionHookRef(argRef)) return false;
|
|
35174
34656
|
if (isProp(analysis, argRef)) return false;
|
|
35175
34657
|
if (isUseRefIdentifier(argRef.identifier)) return false;
|
|
35176
34658
|
if (isRefCurrent(argRef)) return false;
|
|
@@ -35465,30 +34947,6 @@ const guardsRenderShape = (comparison) => {
|
|
|
35465
34947
|
}
|
|
35466
34948
|
return false;
|
|
35467
34949
|
};
|
|
35468
|
-
const isPropsChildrenLength = (node, scopes) => {
|
|
35469
|
-
const unwrappedNode = stripParenExpression(node);
|
|
35470
|
-
return isNodeOfType(unwrappedNode, "MemberExpression") && !unwrappedNode.computed && isNodeOfType(unwrappedNode.property, "Identifier") && unwrappedNode.property.name === "length" && resolvesToPropsChildren(stripParenExpression(unwrappedNode.object), scopes);
|
|
35471
|
-
};
|
|
35472
|
-
const isLargeTextLengthComparison = (node, scopes) => {
|
|
35473
|
-
const unwrappedNode = stripParenExpression(node);
|
|
35474
|
-
if (!isNodeOfType(unwrappedNode, "BinaryExpression")) return false;
|
|
35475
|
-
const leftIsLength = isPropsChildrenLength(unwrappedNode.left, scopes);
|
|
35476
|
-
const rightIsLength = isPropsChildrenLength(unwrappedNode.right, scopes);
|
|
35477
|
-
const thresholdNode = leftIsLength ? unwrappedNode.right : unwrappedNode.left;
|
|
35478
|
-
if (!leftIsLength && !rightIsLength || !isNodeOfType(thresholdNode, "Literal")) return false;
|
|
35479
|
-
if (typeof thresholdNode.value !== "number" || thresholdNode.value < 1e3) return false;
|
|
35480
|
-
return leftIsLength ? unwrappedNode.operator === ">" || unwrappedNode.operator === ">=" : unwrappedNode.operator === "<" || unwrappedNode.operator === "<=";
|
|
35481
|
-
};
|
|
35482
|
-
const isLargeStringOptimizationGuard = (comparison, scopes) => {
|
|
35483
|
-
let current = findTransparentExpressionRoot(comparison);
|
|
35484
|
-
while (current.parent) {
|
|
35485
|
-
const parent = current.parent;
|
|
35486
|
-
if (!isNodeOfType(parent, "LogicalExpression") || parent.operator !== "&&") return false;
|
|
35487
|
-
if (isLargeTextLengthComparison(parent.left === current ? parent.right : parent.left, scopes)) return true;
|
|
35488
|
-
current = findTransparentExpressionRoot(parent);
|
|
35489
|
-
}
|
|
35490
|
-
return false;
|
|
35491
|
-
};
|
|
35492
34950
|
const noPolymorphicChildren = defineRule({
|
|
35493
34951
|
id: "no-polymorphic-children",
|
|
35494
34952
|
title: "Children type checked at runtime",
|
|
@@ -35502,7 +34960,6 @@ const noPolymorphicChildren = defineRule({
|
|
|
35502
34960
|
const isStringLiteral = (operand) => isNodeOfType(operand, "Literal") && operand.value === "string";
|
|
35503
34961
|
if (!isStringLiteral(node.left) && !isStringLiteral(node.right)) return;
|
|
35504
34962
|
if (!guardsRenderShape(node)) return;
|
|
35505
|
-
if (isLargeStringOptimizationGuard(node, context.scopes)) return;
|
|
35506
34963
|
context.report({
|
|
35507
34964
|
node,
|
|
35508
34965
|
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."
|
|
@@ -35720,27 +35177,6 @@ const hasPreviousValueDep = (effectNode, depElements) => {
|
|
|
35720
35177
|
}
|
|
35721
35178
|
return false;
|
|
35722
35179
|
};
|
|
35723
|
-
const isStateLikeDependency = (analysis, element, isPropName) => {
|
|
35724
|
-
if (!isNodeOfType(element, "Identifier") || isPropName(element.name)) return false;
|
|
35725
|
-
if (!analysis) return true;
|
|
35726
|
-
const reference = getRef(analysis, element);
|
|
35727
|
-
if (!reference) return true;
|
|
35728
|
-
const upstreamReferences = getUpstreamRefs(analysis, reference);
|
|
35729
|
-
if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
|
|
35730
|
-
return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
|
|
35731
|
-
};
|
|
35732
|
-
const getRefHeldPropCallbackName = (callExpression, isPropName) => {
|
|
35733
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
35734
|
-
if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "current") return null;
|
|
35735
|
-
const receiver = stripParenExpression(callee.object);
|
|
35736
|
-
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
35737
|
-
const binding = findVariableInitializer(callExpression, receiver.name);
|
|
35738
|
-
if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
|
|
35739
|
-
if (getCalleeName$2(binding.initializer) !== "useRef") return null;
|
|
35740
|
-
const callbackArgument = binding.initializer.arguments?.[0];
|
|
35741
|
-
if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
|
|
35742
|
-
return isPropName(callbackArgument.name) ? callbackArgument.name : null;
|
|
35743
|
-
};
|
|
35744
35180
|
const noPropCallbackInEffect = defineRule({
|
|
35745
35181
|
id: "no-prop-callback-in-effect",
|
|
35746
35182
|
title: "Parent kept in sync with a callback effect",
|
|
@@ -35757,11 +35193,11 @@ const noPropCallbackInEffect = defineRule({
|
|
|
35757
35193
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
35758
35194
|
const depsNode = node.arguments[1];
|
|
35759
35195
|
if (!isNodeOfType(depsNode, "ArrayExpression") || !depsNode.elements?.length) return;
|
|
35760
|
-
const
|
|
35761
|
-
const stateLikeDeps = (depsNode.elements ?? []).filter((element) => element && isStateLikeDependency(analysis, element, propStackTracker.isPropName));
|
|
35196
|
+
const stateLikeDeps = (depsNode.elements ?? []).filter((element) => isNodeOfType(element, "Identifier") && !propStackTracker.isPropName(element.name));
|
|
35762
35197
|
if (stateLikeDeps.length === 0) return;
|
|
35763
35198
|
if (isRefLatchGuardedEffect(callback.body)) return;
|
|
35764
35199
|
if (hasPreviousValueDep(node, depsNode.elements ?? [])) return;
|
|
35200
|
+
const analysis = getProgramAnalysis(node);
|
|
35765
35201
|
if (analysis) {
|
|
35766
35202
|
const stateLikeDepRefs = [];
|
|
35767
35203
|
for (const element of stateLikeDeps) {
|
|
@@ -35773,9 +35209,9 @@ const noPropCallbackInEffect = defineRule({
|
|
|
35773
35209
|
const reportedNodes = /* @__PURE__ */ new Set();
|
|
35774
35210
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
35775
35211
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
35776
|
-
|
|
35777
|
-
const calleeName =
|
|
35778
|
-
if (!calleeName) return;
|
|
35212
|
+
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
35213
|
+
const calleeName = child.callee.name;
|
|
35214
|
+
if (!propStackTracker.isPropName(calleeName)) return;
|
|
35779
35215
|
if (!isResultDiscardedCall(child)) return;
|
|
35780
35216
|
if (reportedNodes.has(child)) return;
|
|
35781
35217
|
reportedNodes.add(child);
|
|
@@ -43616,6 +43052,15 @@ const isStableHookWrapperArgument = (node) => {
|
|
|
43616
43052
|
const parent = node.parent;
|
|
43617
43053
|
return isNodeOfType(parent, "CallExpression") && isNodeOfType(parent.callee, "Identifier") && STABLE_HOOK_WRAPPERS.has(parent.callee.name) && Boolean(parent.arguments?.some((argument) => argument === node));
|
|
43618
43054
|
};
|
|
43055
|
+
const isImmediatelyInvokedFunction = (functionNode) => {
|
|
43056
|
+
let wrappedCallee = functionNode;
|
|
43057
|
+
let enclosing = functionNode.parent;
|
|
43058
|
+
while (enclosing && stripParenExpression(enclosing) === functionNode) {
|
|
43059
|
+
wrappedCallee = enclosing;
|
|
43060
|
+
enclosing = enclosing.parent ?? null;
|
|
43061
|
+
}
|
|
43062
|
+
return Boolean(enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee);
|
|
43063
|
+
};
|
|
43619
43064
|
const queryStableQueryClient = defineRule({
|
|
43620
43065
|
id: "query-stable-query-client",
|
|
43621
43066
|
title: "Unstable QueryClient in component",
|
|
@@ -54928,6 +54373,12 @@ const webhookSignatureRisk = defineRule({
|
|
|
54928
54373
|
//#region src/plugin/rules/zod/utils/zod-ast.ts
|
|
54929
54374
|
const ZOD_MODULE = "zod";
|
|
54930
54375
|
const ZOD_MODULE_SOURCES = [ZOD_MODULE];
|
|
54376
|
+
const getStaticPropertyName = (member) => {
|
|
54377
|
+
const property = member.property;
|
|
54378
|
+
if (!member.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
54379
|
+
if (member.computed && isNodeOfType(property, "Literal") && typeof property.value === "string") return property.value;
|
|
54380
|
+
return null;
|
|
54381
|
+
};
|
|
54931
54382
|
const importInfoCache = /* @__PURE__ */ new WeakMap();
|
|
54932
54383
|
const getImportInfoForIdentifier = (identifier) => {
|
|
54933
54384
|
if (importInfoCache.has(identifier)) return importInfoCache.get(identifier) ?? null;
|