oxlint-plugin-react-doctor 0.7.5-dev.76cd6be → 0.7.5-dev.8fc5848
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 +572 -168
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -397,6 +397,7 @@ const isProductionFilePath = (relativePath, sourceFilePattern) => {
|
|
|
397
397
|
//#endregion
|
|
398
398
|
//#region src/plugin/rules/security-scan/utils/is-production-source-path.ts
|
|
399
399
|
const isProductionSourcePath = (relativePath) => {
|
|
400
|
+
if (/\.d\.[cm]?[jt]s$/i.test(relativePath)) return false;
|
|
400
401
|
return isProductionFilePath(relativePath, SOURCE_FILE_PATTERN);
|
|
401
402
|
};
|
|
402
403
|
//#endregion
|
|
@@ -5178,8 +5179,10 @@ const STORAGE_GLOBALS = new Set([
|
|
|
5178
5179
|
const SENSITIVE_KEY_PATTERN = /token|jwt|secret|password|passwd|credential|api[-_]?key|bearer|private[-_]?key/i;
|
|
5179
5180
|
const NON_AUTH_TOKEN_PATTERN = /csrf|xsrf|device|fcm|apns|push|design|tokeniz|syntax|css|theme|color/i;
|
|
5180
5181
|
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;
|
|
5181
5183
|
const PRODUCT_API_KEY_COLLECTION_PATTERN = /[._:-](?:created|generated|saved)[-_]?api[-_]?keys$/i;
|
|
5182
5184
|
const isAuthCredentialKey = (key) => {
|
|
5185
|
+
if (PRODUCT_API_KEY_RECORDS_PATTERN.test(key)) return false;
|
|
5183
5186
|
if (!SENSITIVE_KEY_PATTERN.test(key)) return false;
|
|
5184
5187
|
if (PRODUCT_API_KEY_COLLECTION_PATTERN.test(key)) return false;
|
|
5185
5188
|
if (NON_AUTH_TOKEN_PATTERN.test(key) && !STRONG_AUTH_KEY_PATTERN.test(key)) return false;
|
|
@@ -5931,6 +5934,21 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
5931
5934
|
}
|
|
5932
5935
|
});
|
|
5933
5936
|
//#endregion
|
|
5937
|
+
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
5938
|
+
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
5939
|
+
if (!isNodeOfType(node, "Property")) return null;
|
|
5940
|
+
if (node.computed) {
|
|
5941
|
+
if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
|
|
5942
|
+
return null;
|
|
5943
|
+
}
|
|
5944
|
+
if (isNodeOfType(node.key, "Identifier")) return node.key.name;
|
|
5945
|
+
if (isNodeOfType(node.key, "Literal")) {
|
|
5946
|
+
if (typeof node.key.value === "string") return node.key.value;
|
|
5947
|
+
if (options.stringifyNonStringLiterals) return String(node.key.value);
|
|
5948
|
+
}
|
|
5949
|
+
return null;
|
|
5950
|
+
};
|
|
5951
|
+
//#endregion
|
|
5934
5952
|
//#region src/plugin/utils/is-presentation-role.ts
|
|
5935
5953
|
const isPresentationRole = (openingElement) => {
|
|
5936
5954
|
const roleAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "role");
|
|
@@ -5975,6 +5993,21 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
5975
5993
|
return false;
|
|
5976
5994
|
};
|
|
5977
5995
|
//#endregion
|
|
5996
|
+
//#region src/plugin/utils/resolve-const-identifier-alias.ts
|
|
5997
|
+
const resolveConstIdentifierAlias = (identifier, scopes) => {
|
|
5998
|
+
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
5999
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
6000
|
+
let symbol = scopes.symbolFor(identifier);
|
|
6001
|
+
while (symbol?.kind === "const") {
|
|
6002
|
+
if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
6003
|
+
visitedSymbolIds.add(symbol.id);
|
|
6004
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
6005
|
+
if (!isNodeOfType(initializer, "Identifier")) return symbol;
|
|
6006
|
+
symbol = scopes.symbolFor(initializer);
|
|
6007
|
+
}
|
|
6008
|
+
return symbol;
|
|
6009
|
+
};
|
|
6010
|
+
//#endregion
|
|
5978
6011
|
//#region src/plugin/rules/a11y/click-events-have-key-events.ts
|
|
5979
6012
|
const MESSAGE$57 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
|
|
5980
6013
|
const KEY_HANDLERS = [
|
|
@@ -5985,6 +6018,63 @@ const KEY_HANDLERS = [
|
|
|
5985
6018
|
"onKeyDownCapture",
|
|
5986
6019
|
"onKeyPressCapture"
|
|
5987
6020
|
];
|
|
6021
|
+
const CLICK_HANDLERS = ["onClick", "onClickCapture"];
|
|
6022
|
+
const TRANSPARENT_SPREAD_EVENT_NAMES = new Set([...CLICK_HANDLERS, ...KEY_HANDLERS].map((eventName) => eventName.toLowerCase()));
|
|
6023
|
+
const CONSERVATIVE_SPREAD_PROP_NAMES = new Set([
|
|
6024
|
+
"aria-hidden",
|
|
6025
|
+
"onmouseenter",
|
|
6026
|
+
"onmouseover",
|
|
6027
|
+
"role"
|
|
6028
|
+
]);
|
|
6029
|
+
const resolveSpreadObjectExpression = (expression, scopes) => {
|
|
6030
|
+
const innerExpression = stripParenExpression(expression);
|
|
6031
|
+
if (isNodeOfType(innerExpression, "ObjectExpression")) return innerExpression;
|
|
6032
|
+
if (!isNodeOfType(innerExpression, "Identifier")) return null;
|
|
6033
|
+
const symbol = resolveConstIdentifierAlias(innerExpression, scopes);
|
|
6034
|
+
if (symbol?.kind !== "const" || !symbol.initializer) return null;
|
|
6035
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
6036
|
+
return isNodeOfType(initializer, "ObjectExpression") ? initializer : null;
|
|
6037
|
+
};
|
|
6038
|
+
const collectTransparentSpreadEventNames = (expression, scopes, eventValues, visitedObjectExpressions) => {
|
|
6039
|
+
const objectExpression = resolveSpreadObjectExpression(expression, scopes);
|
|
6040
|
+
if (!objectExpression || visitedObjectExpressions.has(objectExpression)) return false;
|
|
6041
|
+
visitedObjectExpressions.add(objectExpression);
|
|
6042
|
+
let isTransparent = true;
|
|
6043
|
+
for (const property of objectExpression.properties) {
|
|
6044
|
+
if (isNodeOfType(property, "SpreadElement")) {
|
|
6045
|
+
if (!collectTransparentSpreadEventNames(property.argument, scopes, eventValues, visitedObjectExpressions)) {
|
|
6046
|
+
isTransparent = false;
|
|
6047
|
+
break;
|
|
6048
|
+
}
|
|
6049
|
+
continue;
|
|
6050
|
+
}
|
|
6051
|
+
if (!isNodeOfType(property, "Property")) {
|
|
6052
|
+
isTransparent = false;
|
|
6053
|
+
break;
|
|
6054
|
+
}
|
|
6055
|
+
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
6056
|
+
if (!propertyName) {
|
|
6057
|
+
isTransparent = false;
|
|
6058
|
+
break;
|
|
6059
|
+
}
|
|
6060
|
+
const normalizedPropertyName = propertyName.toLowerCase();
|
|
6061
|
+
if (CONSERVATIVE_SPREAD_PROP_NAMES.has(normalizedPropertyName)) {
|
|
6062
|
+
isTransparent = false;
|
|
6063
|
+
break;
|
|
6064
|
+
}
|
|
6065
|
+
if (TRANSPARENT_SPREAD_EVENT_NAMES.has(normalizedPropertyName)) eventValues.set(normalizedPropertyName, property.value);
|
|
6066
|
+
}
|
|
6067
|
+
visitedObjectExpressions.delete(objectExpression);
|
|
6068
|
+
return isTransparent;
|
|
6069
|
+
};
|
|
6070
|
+
const getTransparentSpreadEventValues = (attributes, scopes) => {
|
|
6071
|
+
const eventValues = /* @__PURE__ */ new Map();
|
|
6072
|
+
for (const attribute of attributes) {
|
|
6073
|
+
if (!isNodeOfType(attribute, "JSXSpreadAttribute")) continue;
|
|
6074
|
+
if (!collectTransparentSpreadEventNames(attribute.argument, scopes, eventValues, /* @__PURE__ */ new Set())) return null;
|
|
6075
|
+
}
|
|
6076
|
+
return eventValues;
|
|
6077
|
+
};
|
|
5988
6078
|
const FOCUSLESS_CONTAINER_TAGS = new Set([
|
|
5989
6079
|
"tr",
|
|
5990
6080
|
"td",
|
|
@@ -6032,7 +6122,10 @@ const isFocusForwardingFunctionBody = (body) => {
|
|
|
6032
6122
|
};
|
|
6033
6123
|
const resolveHandlerFunction = (attribute) => {
|
|
6034
6124
|
if (!attribute.value || !isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
|
|
6035
|
-
|
|
6125
|
+
return resolveHandlerFunctionExpression(attribute.value.expression);
|
|
6126
|
+
};
|
|
6127
|
+
const resolveHandlerFunctionExpression = (handlerExpression) => {
|
|
6128
|
+
let expression = handlerExpression;
|
|
6036
6129
|
if (isNodeOfType(expression, "Identifier")) {
|
|
6037
6130
|
const binding = findVariableInitializer(expression, expression.name);
|
|
6038
6131
|
if (!binding?.initializer) return null;
|
|
@@ -6131,18 +6224,22 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
6131
6224
|
if (!HTML_TAGS.has(tag)) return;
|
|
6132
6225
|
if (tag === "label") return;
|
|
6133
6226
|
if (!FOCUSLESS_CONTAINER_TAGS.has(tag) && isInteractiveElement(tag, node)) return;
|
|
6227
|
+
const spreadEventValues = getTransparentSpreadEventValues(node.attributes, context.scopes);
|
|
6228
|
+
if (!spreadEventValues) return;
|
|
6134
6229
|
const onClick = hasJsxPropIgnoreCase(node.attributes, "onClick") ?? hasJsxPropIgnoreCase(node.attributes, "onClickCapture");
|
|
6135
|
-
|
|
6136
|
-
if (
|
|
6137
|
-
if (
|
|
6138
|
-
if (
|
|
6230
|
+
const spreadOnClickExpression = CLICK_HANDLERS.map((name) => spreadEventValues.get(name.toLowerCase())).find((expression) => expression !== void 0);
|
|
6231
|
+
if (!onClick && !spreadOnClickExpression) return;
|
|
6232
|
+
if (onClick && isPureEventBlockerHandler(onClick)) return;
|
|
6233
|
+
if (onClick && isFocusForwardingHandler(onClick)) return;
|
|
6234
|
+
const spreadHandlerFunction = spreadOnClickExpression ? resolveHandlerFunctionExpression(spreadOnClickExpression) : null;
|
|
6235
|
+
if (spreadHandlerFunction && (isFocusForwardingFunctionBody(spreadHandlerFunction.body ?? null) || containsBackdropDismissComparison(spreadHandlerFunction.body ?? null))) return;
|
|
6139
6236
|
if (hasCompositeItemRole(node)) return;
|
|
6140
6237
|
if (isHoverSelectionListItem(tag, node)) return;
|
|
6141
|
-
if (isBackdropDismissHandler(onClick)) return;
|
|
6238
|
+
if (onClick && isBackdropDismissHandler(onClick)) return;
|
|
6142
6239
|
if (containsKeyboardActivatableDescendant(node.parent)) return;
|
|
6143
6240
|
if (isHiddenFromScreenReader(node, context.settings)) return;
|
|
6144
6241
|
if (isPresentationRole(node)) return;
|
|
6145
|
-
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
|
|
6242
|
+
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler) || spreadEventValues.has(handler.toLowerCase()))) return;
|
|
6146
6243
|
context.report({
|
|
6147
6244
|
node: node.name,
|
|
6148
6245
|
message: MESSAGE$57
|
|
@@ -6868,11 +6965,10 @@ const getTemplateInterpolations = (valueTail) => {
|
|
|
6868
6965
|
const interpolations = valueTail.slice(1, closingBacktickIndex).match(/\$\{[^}]*\}/g);
|
|
6869
6966
|
return interpolations === null ? "" : interpolations.join(" ");
|
|
6870
6967
|
};
|
|
6871
|
-
const getIdentifierDeclarationInitializer = (identifier, fileContent) => {
|
|
6872
|
-
const
|
|
6873
|
-
if (
|
|
6874
|
-
|
|
6875
|
-
return fileContent.slice(initializerStartIndex, initializerStartIndex + STATIC_TEMPLATE_MAX_CHARS);
|
|
6968
|
+
const getIdentifierDeclarationInitializer = (identifier, sinkIndex, fileContent) => {
|
|
6969
|
+
const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
|
|
6970
|
+
if (declaration === null) return null;
|
|
6971
|
+
return fileContent.slice(declaration.initializerStartIndex, declaration.initializerStartIndex + STATIC_TEMPLATE_MAX_CHARS);
|
|
6876
6972
|
};
|
|
6877
6973
|
const isPureStringLiteralConcat = (initializerText) => {
|
|
6878
6974
|
if (!/^["']/.test(initializerText)) return false;
|
|
@@ -6942,6 +7038,153 @@ const isAllOperandsDomContentConcat = (valueExpression) => {
|
|
|
6942
7038
|
return !HTML_TAINT_PATTERN.test(withoutCast.replace(DOM_CONTENT_SOURCE_VALUE_PATTERN, ""));
|
|
6943
7039
|
});
|
|
6944
7040
|
};
|
|
7041
|
+
const findMatchingBraceIndex = (fileContent, openingBraceIndex) => {
|
|
7042
|
+
let depth = 0;
|
|
7043
|
+
let quote = null;
|
|
7044
|
+
for (let index = openingBraceIndex; index < fileContent.length; index += 1) {
|
|
7045
|
+
const character = fileContent[index];
|
|
7046
|
+
if (quote !== null) {
|
|
7047
|
+
if (character === quote && fileContent[index - 1] !== "\\") quote = null;
|
|
7048
|
+
continue;
|
|
7049
|
+
}
|
|
7050
|
+
if (character === "\"" || character === "'" || character === "`") {
|
|
7051
|
+
quote = character;
|
|
7052
|
+
continue;
|
|
7053
|
+
}
|
|
7054
|
+
if (character === "{") depth += 1;
|
|
7055
|
+
if (character === "}") {
|
|
7056
|
+
depth -= 1;
|
|
7057
|
+
if (depth === 0) return index;
|
|
7058
|
+
}
|
|
7059
|
+
}
|
|
7060
|
+
return fileContent.length;
|
|
7061
|
+
};
|
|
7062
|
+
const findContainingBlockEndIndex = (fileContent, targetIndex) => {
|
|
7063
|
+
const openingBraceIndexes = [];
|
|
7064
|
+
let quote = null;
|
|
7065
|
+
let isLineComment = false;
|
|
7066
|
+
let isBlockComment = false;
|
|
7067
|
+
for (let index = 0; index < targetIndex; index += 1) {
|
|
7068
|
+
const character = fileContent[index];
|
|
7069
|
+
const nextCharacter = fileContent[index + 1];
|
|
7070
|
+
if (isLineComment) {
|
|
7071
|
+
if (character === "\n") isLineComment = false;
|
|
7072
|
+
continue;
|
|
7073
|
+
}
|
|
7074
|
+
if (isBlockComment) {
|
|
7075
|
+
if (character === "*" && nextCharacter === "/") {
|
|
7076
|
+
isBlockComment = false;
|
|
7077
|
+
index += 1;
|
|
7078
|
+
}
|
|
7079
|
+
continue;
|
|
7080
|
+
}
|
|
7081
|
+
if (quote !== null) {
|
|
7082
|
+
if (character === quote && fileContent[index - 1] !== "\\") quote = null;
|
|
7083
|
+
continue;
|
|
7084
|
+
}
|
|
7085
|
+
if (character === "/" && nextCharacter === "/") {
|
|
7086
|
+
isLineComment = true;
|
|
7087
|
+
index += 1;
|
|
7088
|
+
continue;
|
|
7089
|
+
}
|
|
7090
|
+
if (character === "/" && nextCharacter === "*") {
|
|
7091
|
+
isBlockComment = true;
|
|
7092
|
+
index += 1;
|
|
7093
|
+
continue;
|
|
7094
|
+
}
|
|
7095
|
+
if (character === "\"" || character === "'" || character === "`") {
|
|
7096
|
+
quote = character;
|
|
7097
|
+
continue;
|
|
7098
|
+
}
|
|
7099
|
+
if (character === "{") openingBraceIndexes.push(index);
|
|
7100
|
+
if (character === "}") openingBraceIndexes.pop();
|
|
7101
|
+
}
|
|
7102
|
+
const openingBraceIndex = openingBraceIndexes.at(-1);
|
|
7103
|
+
return openingBraceIndex === void 0 ? fileContent.length : findMatchingBraceIndex(fileContent, openingBraceIndex);
|
|
7104
|
+
};
|
|
7105
|
+
const findVisibleIdentifierDeclaration = (identifier, sinkIndex, fileContent) => {
|
|
7106
|
+
const initializerPattern = new RegExp(`(?:const|let|var)\\s+${escapeRegExp(identifier)}\\s*(?::[^=\\n]*)?=\\s*([^;\\n]+)`, "g");
|
|
7107
|
+
let nearestDeclaration = null;
|
|
7108
|
+
let nearestDeclarationIndex = -1;
|
|
7109
|
+
for (const match of fileContent.matchAll(initializerPattern)) {
|
|
7110
|
+
const declarationIndex = match.index;
|
|
7111
|
+
if (declarationIndex === void 0 || declarationIndex >= sinkIndex || declarationIndex <= nearestDeclarationIndex || findContainingBlockEndIndex(fileContent, declarationIndex) < sinkIndex) continue;
|
|
7112
|
+
nearestDeclarationIndex = declarationIndex;
|
|
7113
|
+
const initializer = match[1];
|
|
7114
|
+
if (initializer === void 0) continue;
|
|
7115
|
+
nearestDeclaration = {
|
|
7116
|
+
initializer,
|
|
7117
|
+
initializerStartIndex: declarationIndex + match[0].length - initializer.length
|
|
7118
|
+
};
|
|
7119
|
+
}
|
|
7120
|
+
return nearestDeclaration;
|
|
7121
|
+
};
|
|
7122
|
+
const findContainingFunctionParameterSource = (identifier, sinkIndex, fileContent) => {
|
|
7123
|
+
const patterns = [/function\s+([\w$]+)\s*\(([^)]*)\)\s*\{/g, /(?:const|let|var)\s+([\w$]+)\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>\s*\{/g];
|
|
7124
|
+
let closestSource = null;
|
|
7125
|
+
let closestStartIndex = -1;
|
|
7126
|
+
for (const pattern of patterns) for (const match of fileContent.matchAll(pattern)) {
|
|
7127
|
+
const matchIndex = match.index;
|
|
7128
|
+
if (matchIndex === void 0 || matchIndex >= sinkIndex || matchIndex < closestStartIndex) continue;
|
|
7129
|
+
if (findMatchingBraceIndex(fileContent, matchIndex + match[0].lastIndexOf("{")) < sinkIndex) continue;
|
|
7130
|
+
const parameterIndex = (match[2] ?? "").split(",").findIndex((parameter) => parameter.trim().match(/^(?:\.\.\.)?([\w$]+)/)?.[1] === identifier);
|
|
7131
|
+
if (parameterIndex < 0) continue;
|
|
7132
|
+
closestStartIndex = matchIndex;
|
|
7133
|
+
closestSource = {
|
|
7134
|
+
functionName: match[1] ?? "",
|
|
7135
|
+
parameterIndex
|
|
7136
|
+
};
|
|
7137
|
+
}
|
|
7138
|
+
return closestSource;
|
|
7139
|
+
};
|
|
7140
|
+
const splitTopLevelArguments = (argumentText) => {
|
|
7141
|
+
const argumentsList = [];
|
|
7142
|
+
let startIndex = 0;
|
|
7143
|
+
let depth = 0;
|
|
7144
|
+
let quote = null;
|
|
7145
|
+
for (let index = 0; index < argumentText.length; index += 1) {
|
|
7146
|
+
const character = argumentText[index];
|
|
7147
|
+
if (quote !== null) {
|
|
7148
|
+
if (character === quote && argumentText[index - 1] !== "\\") quote = null;
|
|
7149
|
+
continue;
|
|
7150
|
+
}
|
|
7151
|
+
if (character === "\"" || character === "'" || character === "`") {
|
|
7152
|
+
quote = character;
|
|
7153
|
+
continue;
|
|
7154
|
+
}
|
|
7155
|
+
if (character === "(" || character === "[" || character === "{") depth += 1;
|
|
7156
|
+
if (character === ")" || character === "]" || character === "}") depth -= 1;
|
|
7157
|
+
if (character === "," && depth === 0) {
|
|
7158
|
+
argumentsList.push(argumentText.slice(startIndex, index).trim());
|
|
7159
|
+
startIndex = index + 1;
|
|
7160
|
+
}
|
|
7161
|
+
}
|
|
7162
|
+
argumentsList.push(argumentText.slice(startIndex).trim());
|
|
7163
|
+
return argumentsList;
|
|
7164
|
+
};
|
|
7165
|
+
const isHtmlTainted = (expression, fileContent, sinkIndex, visitedIdentifiers) => {
|
|
7166
|
+
const trimmedExpression = expression.trim();
|
|
7167
|
+
if (STRING_LITERAL_VALUE_PATTERN.test(`${trimmedExpression};`)) return false;
|
|
7168
|
+
if (MODULE_CONSTANT_VALUE_PATTERN.test(`${trimmedExpression};`)) return false;
|
|
7169
|
+
if (SANITIZER_PATTERN.test(trimmedExpression)) return false;
|
|
7170
|
+
if (ENV_CONFIG_VALUE_PATTERN.test(trimmedExpression)) return false;
|
|
7171
|
+
if (I18N_VALUE_PATTERN.test(trimmedExpression)) return false;
|
|
7172
|
+
if (ESCAPING_SERIALIZER_CALL_PATTERN.test(trimmedExpression)) return false;
|
|
7173
|
+
if (HTML_TAINT_PATTERN.test(trimmedExpression)) return true;
|
|
7174
|
+
const identifier = trimmedExpression.match(/^([\w$]+)\s*(?:[;,})\n]|$)/)?.[1];
|
|
7175
|
+
if (identifier === void 0 || visitedIdentifiers.has(identifier)) return false;
|
|
7176
|
+
visitedIdentifiers.add(identifier);
|
|
7177
|
+
const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
|
|
7178
|
+
if (declaration !== null && isHtmlTainted(declaration.initializer, fileContent, sinkIndex, visitedIdentifiers)) return true;
|
|
7179
|
+
const parameterSource = findContainingFunctionParameterSource(identifier, sinkIndex, fileContent);
|
|
7180
|
+
if (parameterSource === null || parameterSource.functionName.length === 0) return false;
|
|
7181
|
+
const callPattern = new RegExp(`\\b${escapeRegExp(parameterSource.functionName)}\\s*\\(([^)]*)\\)`, "g");
|
|
7182
|
+
for (const callMatch of fileContent.matchAll(callPattern)) {
|
|
7183
|
+
const argument = splitTopLevelArguments(callMatch[1] ?? "")[parameterSource.parameterIndex];
|
|
7184
|
+
if (argument !== void 0 && isHtmlTainted(argument, fileContent, sinkIndex, new Set(visitedIdentifiers))) return true;
|
|
7185
|
+
}
|
|
7186
|
+
return false;
|
|
7187
|
+
};
|
|
6945
7188
|
const dangerousHtmlSink = defineRule({
|
|
6946
7189
|
id: "dangerous-html-sink",
|
|
6947
7190
|
title: "HTML injection sink with dynamic content",
|
|
@@ -6968,6 +7211,7 @@ const dangerousHtmlSink = defineRule({
|
|
|
6968
7211
|
const valueTail = fullValueTail.slice(0, VALUE_EXPRESSION_MAX_CHARS);
|
|
6969
7212
|
const terminatorIndex = valueTail.search(/[;}]/);
|
|
6970
7213
|
const valueExpression = terminatorIndex >= 0 ? valueTail.slice(0, terminatorIndex + 1) : valueTail;
|
|
7214
|
+
const sinkIndex = lines.slice(0, lineIndex).join("\n").length + (lineIndex > 0 ? 1 : 0) + line.search(DANGEROUS_HTML_PATTERN);
|
|
6971
7215
|
if (STRING_LITERAL_VALUE_PATTERN.test(valueExpression)) continue;
|
|
6972
7216
|
if (MODULE_CONSTANT_VALUE_PATTERN.test(valueExpression)) continue;
|
|
6973
7217
|
if (DOM_CONTENT_SOURCE_VALUE_PATTERN.test(valueExpression) && !valueExpression.includes("+")) {
|
|
@@ -6979,7 +7223,7 @@ const dangerousHtmlSink = defineRule({
|
|
|
6979
7223
|
let templateInterpolations = getTemplateInterpolations(longValueTail ?? fullValueTail);
|
|
6980
7224
|
const valueIdentifier = BARE_IDENTIFIER_VALUE_PATTERN.test(valueExpression) || MEMBER_OR_INDEX_ACCESS_VALUE_PATTERN.test(valueExpression) || IDENTIFIER_WITH_LITERAL_FALLBACK_PATTERN.test(valueExpression) ? valueExpression.match(/^[\w$]+/)?.[0] : void 0;
|
|
6981
7225
|
if (templateInterpolations === null && valueIdentifier !== void 0 && BARE_IDENTIFIER_VALUE_PATTERN.test(valueExpression)) {
|
|
6982
|
-
const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier, file.content);
|
|
7226
|
+
const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier, sinkIndex, file.content);
|
|
6983
7227
|
if (declarationInitializer !== null) {
|
|
6984
7228
|
if (isPureStringLiteralConcat(declarationInitializer)) continue;
|
|
6985
7229
|
templateInterpolations = getTemplateInterpolations(declarationInitializer);
|
|
@@ -6990,7 +7234,7 @@ const dangerousHtmlSink = defineRule({
|
|
|
6990
7234
|
if (SANITIZER_PATTERN.test(judgedExpression)) continue;
|
|
6991
7235
|
if (ENV_CONFIG_VALUE_PATTERN.test(judgedExpression)) continue;
|
|
6992
7236
|
if (I18N_VALUE_PATTERN.test(judgedExpression)) continue;
|
|
6993
|
-
if (!
|
|
7237
|
+
if (!isHtmlTainted(judgedExpression, file.content, sinkIndex, /* @__PURE__ */ new Set())) continue;
|
|
6994
7238
|
if (ESCAPING_SERIALIZER_CALL_PATTERN.test(valueExpression)) continue;
|
|
6995
7239
|
if (/highlighted/i.test(valueExpression)) continue;
|
|
6996
7240
|
if (/highlight/i.test(valueExpression) && HIGHLIGHTER_LIBRARY_PATTERN.test(file.content)) continue;
|
|
@@ -7437,7 +7681,7 @@ const containsJsx$1 = (root) => {
|
|
|
7437
7681
|
containsJsxCache.set(root, found);
|
|
7438
7682
|
return found;
|
|
7439
7683
|
};
|
|
7440
|
-
const getStaticMemberName$
|
|
7684
|
+
const getStaticMemberName$2 = (node) => {
|
|
7441
7685
|
if (!isNodeOfType(node, "MemberExpression")) return null;
|
|
7442
7686
|
if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
|
|
7443
7687
|
if (node.computed && isNodeOfType(node.property, "Literal") && typeof node.property.value === "string") return node.property.value;
|
|
@@ -7451,7 +7695,7 @@ const getAssignedName = (node) => {
|
|
|
7451
7695
|
if (isNodeOfType(parent, "AssignmentExpression")) {
|
|
7452
7696
|
const left = parent.left;
|
|
7453
7697
|
if (isNodeOfType(left, "Identifier")) return left.name;
|
|
7454
|
-
if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$
|
|
7698
|
+
if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$2(left);
|
|
7455
7699
|
}
|
|
7456
7700
|
return null;
|
|
7457
7701
|
};
|
|
@@ -7459,20 +7703,20 @@ const isModuleExportsAssignment = (node) => {
|
|
|
7459
7703
|
const parent = node.parent;
|
|
7460
7704
|
if (!parent || !isNodeOfType(parent, "AssignmentExpression")) return false;
|
|
7461
7705
|
const left = parent.left;
|
|
7462
|
-
return isNodeOfType(left, "MemberExpression") && isNodeOfType(left.object, "Identifier") && left.object.name === "module" && getStaticMemberName$
|
|
7706
|
+
return isNodeOfType(left, "MemberExpression") && isNodeOfType(left.object, "Identifier") && left.object.name === "module" && getStaticMemberName$2(left) === "exports";
|
|
7463
7707
|
};
|
|
7464
7708
|
const isCreateClassLikeCall = (node) => {
|
|
7465
7709
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7466
7710
|
if (isEs5Component(node)) return true;
|
|
7467
7711
|
const callee = node.callee;
|
|
7468
|
-
if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$
|
|
7712
|
+
if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$2(callee) === "createClass";
|
|
7469
7713
|
return false;
|
|
7470
7714
|
};
|
|
7471
|
-
const isCreateContextCall = (node) => {
|
|
7715
|
+
const isCreateContextCall$1 = (node) => {
|
|
7472
7716
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7473
7717
|
const callee = node.callee;
|
|
7474
7718
|
if (isNodeOfType(callee, "Identifier")) return callee.name === "createContext";
|
|
7475
|
-
return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$
|
|
7719
|
+
return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$2(callee) === "createContext";
|
|
7476
7720
|
};
|
|
7477
7721
|
const isNamedFunctionLike = (node) => (isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && Boolean(node.id?.name);
|
|
7478
7722
|
const firstCallArgument = (node) => {
|
|
@@ -7530,7 +7774,7 @@ const memberExpressionPath = (node) => {
|
|
|
7530
7774
|
if (isNodeOfType(node, "Identifier")) return [node.name];
|
|
7531
7775
|
if (!isNodeOfType(node, "MemberExpression")) return [];
|
|
7532
7776
|
const objectPath = memberExpressionPath(node.object);
|
|
7533
|
-
const propertyName = getStaticMemberName$
|
|
7777
|
+
const propertyName = getStaticMemberName$2(node);
|
|
7534
7778
|
return propertyName ? [...objectPath, propertyName] : objectPath;
|
|
7535
7779
|
};
|
|
7536
7780
|
const displayNameAssignmentIndexCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -7540,7 +7784,7 @@ const getDisplayNameAssignmentIndex = (programRoot) => {
|
|
|
7540
7784
|
const identifierTargets = /* @__PURE__ */ new Set();
|
|
7541
7785
|
const memberPathSegments = /* @__PURE__ */ new Set();
|
|
7542
7786
|
const visit = (node) => {
|
|
7543
|
-
if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName$
|
|
7787
|
+
if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName$2(node.left) === "displayName") {
|
|
7544
7788
|
if (isNodeOfType(node.left.object, "Identifier")) identifierTargets.add(node.left.object.name);
|
|
7545
7789
|
for (const segment of memberExpressionPath(node.left.object)) memberPathSegments.add(segment);
|
|
7546
7790
|
}
|
|
@@ -7645,7 +7889,7 @@ const displayName = defineRule({
|
|
|
7645
7889
|
}
|
|
7646
7890
|
},
|
|
7647
7891
|
CallExpression(node) {
|
|
7648
|
-
if (settings.checkContextObjects && isReactVersionAtLeast$1(settings.reactVersion, 16, 3) && isCreateContextCall(node)) {
|
|
7892
|
+
if (settings.checkContextObjects && isReactVersionAtLeast$1(settings.reactVersion, 16, 3) && isCreateContextCall$1(node)) {
|
|
7649
7893
|
const assignedName = getAssignedName(node);
|
|
7650
7894
|
if (assignedName) {
|
|
7651
7895
|
const programRoot = findProgramRoot(node);
|
|
@@ -7692,21 +7936,6 @@ const displayName = defineRule({
|
|
|
7692
7936
|
}
|
|
7693
7937
|
});
|
|
7694
7938
|
//#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
|
|
7710
7939
|
//#region src/plugin/utils/read-static-boolean.ts
|
|
7711
7940
|
const readStaticBoolean = (node) => {
|
|
7712
7941
|
if (!node) return null;
|
|
@@ -7715,21 +7944,6 @@ const readStaticBoolean = (node) => {
|
|
|
7715
7944
|
return unwrappedNode.value;
|
|
7716
7945
|
};
|
|
7717
7946
|
//#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
|
|
7733
7947
|
//#region src/plugin/utils/is-react-api-call.ts
|
|
7734
7948
|
const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? apiNames === apiName : apiNames.has(apiName);
|
|
7735
7949
|
const isImportedFromReact = (symbol) => {
|
|
@@ -7737,9 +7951,9 @@ const isImportedFromReact = (symbol) => {
|
|
|
7737
7951
|
const importDeclaration = symbol.declarationNode.parent;
|
|
7738
7952
|
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(importDeclaration.source.value));
|
|
7739
7953
|
};
|
|
7740
|
-
const isNamedReactApiImport = (identifier, apiNames, scopes) => {
|
|
7954
|
+
const isNamedReactApiImport = (identifier, apiNames, scopes, resolveAliases) => {
|
|
7741
7955
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
7742
|
-
const symbol = scopes.symbolFor(identifier);
|
|
7956
|
+
const symbol = resolveAliases ? resolveConstIdentifierAlias(identifier, scopes) : scopes.symbolFor(identifier);
|
|
7743
7957
|
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
7744
7958
|
const importedName = getImportedName(symbol.declarationNode);
|
|
7745
7959
|
return Boolean(importedName && includesApiName(apiNames, importedName));
|
|
@@ -7753,7 +7967,7 @@ const isReactApiCall = (node, apiNames, scopes, options = {}) => {
|
|
|
7753
7967
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7754
7968
|
const callee = stripParenExpression(node.callee);
|
|
7755
7969
|
if (isNodeOfType(callee, "Identifier")) {
|
|
7756
|
-
if (isNamedReactApiImport(callee, apiNames, scopes)) return true;
|
|
7970
|
+
if (isNamedReactApiImport(callee, apiNames, scopes, Boolean(options.resolveNamedAliases))) return true;
|
|
7757
7971
|
return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
|
|
7758
7972
|
}
|
|
7759
7973
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || !includesApiName(apiNames, callee.property.name)) return false;
|
|
@@ -10962,6 +11176,11 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
10962
11176
|
return { CallExpression(node) {
|
|
10963
11177
|
const hookName = getHookName(node.callee, context.scopes);
|
|
10964
11178
|
if (!hookName || !isHookOfInterest(hookName, node.callee)) return;
|
|
11179
|
+
if (HOOKS_REQUIRING_DEPS_MATCH.has(hookName) && !isReactApiCall(node, HOOKS_REQUIRING_DEPS_MATCH, context.scopes, {
|
|
11180
|
+
allowGlobalReactNamespace: true,
|
|
11181
|
+
allowUnboundBareCalls: true,
|
|
11182
|
+
resolveNamedAliases: true
|
|
11183
|
+
})) return;
|
|
10965
11184
|
const callbackArgumentIndex = getCallbackArgumentIndex(hookName);
|
|
10966
11185
|
const depsArgumentIndex = getDepsArgumentIndex(hookName);
|
|
10967
11186
|
const callbackArgument = node.arguments[callbackArgumentIndex];
|
|
@@ -14256,7 +14475,11 @@ const jsIndexMaps = defineRule({
|
|
|
14256
14475
|
//#region src/plugin/utils/are-expressions-structurally-equal.ts
|
|
14257
14476
|
const areExpressionsStructurallyEqual = (a, b) => {
|
|
14258
14477
|
if (!a || !b) return a === b;
|
|
14478
|
+
const unwrappedA = stripParenExpression(a);
|
|
14479
|
+
const unwrappedB = stripParenExpression(b);
|
|
14480
|
+
if (unwrappedA !== a || unwrappedB !== b) return areExpressionsStructurallyEqual(unwrappedA, unwrappedB);
|
|
14259
14481
|
if (a.type !== b.type) return false;
|
|
14482
|
+
if (isNodeOfType(a, "ThisExpression")) return true;
|
|
14260
14483
|
if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
|
|
14261
14484
|
if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
|
|
14262
14485
|
if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
|
|
@@ -20799,8 +21022,8 @@ const catchClauseRethrowsCaught = (handler) => {
|
|
|
20799
21022
|
return didRethrow;
|
|
20800
21023
|
};
|
|
20801
21024
|
//#endregion
|
|
20802
|
-
//#region src/plugin/utils/
|
|
20803
|
-
const
|
|
21025
|
+
//#region src/plugin/utils/is-immediately-invoked-function.ts
|
|
21026
|
+
const isImmediatelyInvokedFunction = (functionNode) => {
|
|
20804
21027
|
let wrappedCallee = functionNode;
|
|
20805
21028
|
let enclosing = functionNode.parent;
|
|
20806
21029
|
while (enclosing && stripParenExpression(enclosing) === functionNode) {
|
|
@@ -20809,11 +21032,13 @@ const isImmediatelyInvokedFunctionCallee = (functionNode) => {
|
|
|
20809
21032
|
}
|
|
20810
21033
|
return Boolean(enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee);
|
|
20811
21034
|
};
|
|
21035
|
+
//#endregion
|
|
21036
|
+
//#region src/plugin/utils/find-guarding-try-statement.ts
|
|
20812
21037
|
const findGuardingTryStatement = (node) => {
|
|
20813
21038
|
let child = node;
|
|
20814
21039
|
let ancestor = node.parent;
|
|
20815
21040
|
while (ancestor) {
|
|
20816
|
-
if (isFunctionLike$1(ancestor) && !
|
|
21041
|
+
if (isFunctionLike$1(ancestor) && !isImmediatelyInvokedFunction(ancestor)) return null;
|
|
20817
21042
|
if (isNodeOfType(ancestor, "TryStatement") && ancestor.block === child && ancestor.handler && !catchClauseRethrowsCaught(ancestor.handler)) return ancestor;
|
|
20818
21043
|
child = ancestor;
|
|
20819
21044
|
ancestor = ancestor.parent ?? null;
|
|
@@ -21946,6 +22171,8 @@ const DOM_QUERY_MEMBER_NAMES = new Set([
|
|
|
21946
22171
|
]);
|
|
21947
22172
|
const LAYOUT_MEASUREMENT_MEMBER_NAMES = new Set([
|
|
21948
22173
|
"current",
|
|
22174
|
+
"textContent",
|
|
22175
|
+
"innerText",
|
|
21949
22176
|
"scrollWidth",
|
|
21950
22177
|
"clientWidth",
|
|
21951
22178
|
"offsetWidth",
|
|
@@ -21967,7 +22194,7 @@ const POST_MOUNT_GLOBAL_NAMES = new Set([
|
|
|
21967
22194
|
"navigator"
|
|
21968
22195
|
]);
|
|
21969
22196
|
const REF_FACTORY_CALLEE_NAMES = new Set(["useRef", "createRef"]);
|
|
21970
|
-
const hasRefLikeName = (name) => name === "ref" || name.endsWith("Ref") || name.endsWith("ref");
|
|
22197
|
+
const hasRefLikeName = (name) => name === "ref" || name.endsWith("Ref") || name.endsWith("ref") || name.endsWith("Node") || name.endsWith("node") || name.endsWith("Element") || name.endsWith("element");
|
|
21971
22198
|
const isRefFactoryInitializer = (init) => {
|
|
21972
22199
|
if (!init || !isNodeOfType(init, "CallExpression")) return false;
|
|
21973
22200
|
const callee = init.callee;
|
|
@@ -22881,61 +23108,73 @@ const PURE_GLOBAL_CALLEE_NAMES = new Set([
|
|
|
22881
23108
|
"Array",
|
|
22882
23109
|
"BigInt",
|
|
22883
23110
|
"Boolean",
|
|
23111
|
+
"encodeURIComponent",
|
|
22884
23112
|
"Number",
|
|
22885
23113
|
"Object",
|
|
22886
23114
|
"String",
|
|
22887
23115
|
"parseFloat",
|
|
22888
|
-
"parseInt"
|
|
23116
|
+
"parseInt",
|
|
23117
|
+
"structuredClone"
|
|
23118
|
+
]);
|
|
23119
|
+
const PURE_GLOBAL_CONSTRUCTOR_NAMES = new Set(["Date", "Set"]);
|
|
23120
|
+
const PURE_HELPER_NAMESPACE_MEMBER_NAMES = new Map([
|
|
23121
|
+
["Array", new Set(["from"])],
|
|
23122
|
+
["JSON", new Set([
|
|
23123
|
+
"isRawJSON",
|
|
23124
|
+
"parse",
|
|
23125
|
+
"rawJSON",
|
|
23126
|
+
"stringify"
|
|
23127
|
+
])],
|
|
23128
|
+
["Math", new Set([
|
|
23129
|
+
"abs",
|
|
23130
|
+
"acos",
|
|
23131
|
+
"acosh",
|
|
23132
|
+
"asin",
|
|
23133
|
+
"asinh",
|
|
23134
|
+
"atan",
|
|
23135
|
+
"atan2",
|
|
23136
|
+
"atanh",
|
|
23137
|
+
"cbrt",
|
|
23138
|
+
"ceil",
|
|
23139
|
+
"clz32",
|
|
23140
|
+
"cos",
|
|
23141
|
+
"cosh",
|
|
23142
|
+
"exp",
|
|
23143
|
+
"floor",
|
|
23144
|
+
"fround",
|
|
23145
|
+
"hypot",
|
|
23146
|
+
"imul",
|
|
23147
|
+
"log",
|
|
23148
|
+
"log10",
|
|
23149
|
+
"log1p",
|
|
23150
|
+
"log2",
|
|
23151
|
+
"max",
|
|
23152
|
+
"min",
|
|
23153
|
+
"pow",
|
|
23154
|
+
"round",
|
|
23155
|
+
"sign",
|
|
23156
|
+
"sin",
|
|
23157
|
+
"sinh",
|
|
23158
|
+
"sqrt",
|
|
23159
|
+
"tan",
|
|
23160
|
+
"tanh",
|
|
23161
|
+
"trunc"
|
|
23162
|
+
])],
|
|
23163
|
+
["Object", new Set(["assign"])]
|
|
22889
23164
|
]);
|
|
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
|
-
])]]);
|
|
22931
23165
|
const PURE_MEMBER_TRANSFORM_NAMES = new Set([
|
|
22932
23166
|
"concat",
|
|
22933
23167
|
"filter",
|
|
23168
|
+
"flatMap",
|
|
22934
23169
|
"join",
|
|
22935
23170
|
"map",
|
|
23171
|
+
"reduce",
|
|
23172
|
+
"replace",
|
|
23173
|
+
"slice",
|
|
22936
23174
|
"split",
|
|
22937
23175
|
"toLowerCase",
|
|
22938
23176
|
"toString",
|
|
23177
|
+
"toSorted",
|
|
22939
23178
|
"toUpperCase",
|
|
22940
23179
|
"trim"
|
|
22941
23180
|
]);
|
|
@@ -22944,7 +23183,7 @@ const STORAGE_GLOBAL_NAMES$1 = new Set([
|
|
|
22944
23183
|
"localStorage",
|
|
22945
23184
|
"sessionStorage"
|
|
22946
23185
|
]);
|
|
22947
|
-
const getStaticMemberName = (node) => {
|
|
23186
|
+
const getStaticMemberName$1 = (node) => {
|
|
22948
23187
|
if (!isNodeOfType(node, "MemberExpression")) return null;
|
|
22949
23188
|
if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
|
|
22950
23189
|
if (node.computed && isNodeOfType(node.property, "Literal")) return typeof node.property.value === "string" ? node.property.value : null;
|
|
@@ -22959,7 +23198,7 @@ const getCallCalleeName$1 = (callExpression) => {
|
|
|
22959
23198
|
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
22960
23199
|
const callee = stripParenExpression(callExpression.callee);
|
|
22961
23200
|
if (isNodeOfType(callee, "Identifier")) return callee.name;
|
|
22962
|
-
return getStaticMemberName(callee);
|
|
23201
|
+
return getStaticMemberName$1(callee);
|
|
22963
23202
|
};
|
|
22964
23203
|
const getFunctionParameters = (functionNode) => isFunctionLike$1(functionNode) ? functionNode.params ?? [] : [];
|
|
22965
23204
|
const getIdentifierBindingIdentity = (analysis, identifier) => {
|
|
@@ -23075,14 +23314,14 @@ const localUseEventPreservesCallback = (analysis, callee) => {
|
|
|
23075
23314
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
23076
23315
|
const forwardedCallee = stripParenExpression(child.callee);
|
|
23077
23316
|
if (!isNodeOfType(forwardedCallee, "MemberExpression")) return;
|
|
23078
|
-
if (getStaticMemberName(forwardedCallee) !== "current") return;
|
|
23317
|
+
if (getStaticMemberName$1(forwardedCallee) !== "current") return;
|
|
23079
23318
|
if (!isNodeOfType(forwardedCallee.object, "Identifier")) return;
|
|
23080
23319
|
const refReference = getRef(analysis, forwardedCallee.object);
|
|
23081
23320
|
if (!callbackRefDeclarators.find((declarator) => refReference?.resolved?.defs.some((definition) => definition.node === declarator)) || !refReference?.resolved) return;
|
|
23082
23321
|
if (!refReference.resolved.references.some((candidateReference) => {
|
|
23083
23322
|
const memberExpression = candidateReference.identifier.parent;
|
|
23084
23323
|
const assignmentExpression = memberExpression?.parent;
|
|
23085
|
-
if (!memberExpression || !isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !assignmentExpression || !isNodeOfType(assignmentExpression, "AssignmentExpression") || assignmentExpression.left !== memberExpression) return false;
|
|
23324
|
+
if (!memberExpression || !isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName$1(memberExpression) !== "current" || !assignmentExpression || !isNodeOfType(assignmentExpression, "AssignmentExpression") || assignmentExpression.left !== memberExpression) return false;
|
|
23086
23325
|
return getIdentifierBindingIdentity(analysis, assignmentExpression.right) !== callbackBinding;
|
|
23087
23326
|
})) forwardsCallback = true;
|
|
23088
23327
|
});
|
|
@@ -23107,7 +23346,7 @@ const resolveWrappedCallable = (analysis, node) => {
|
|
|
23107
23346
|
return callback && isFunctionLike$1(callback) ? callback : null;
|
|
23108
23347
|
}
|
|
23109
23348
|
if (!isNodeOfType(candidate, "MemberExpression")) return null;
|
|
23110
|
-
if (getStaticMemberName(candidate) !== "current") return null;
|
|
23349
|
+
if (getStaticMemberName$1(candidate) !== "current") return null;
|
|
23111
23350
|
if (!isNodeOfType(candidate.object, "Identifier")) return null;
|
|
23112
23351
|
const reference = getRef(analysis, candidate.object);
|
|
23113
23352
|
const declarator = reference?.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
@@ -23119,7 +23358,7 @@ const resolveWrappedCallable = (analysis, node) => {
|
|
|
23119
23358
|
return Boolean(reference?.resolved?.references.some((candidateReference) => {
|
|
23120
23359
|
const member = candidateReference.identifier.parent;
|
|
23121
23360
|
const assignment = member?.parent;
|
|
23122
|
-
return Boolean(member && isNodeOfType(member, "MemberExpression") && getStaticMemberName(member) === "current" && assignment && isNodeOfType(assignment, "AssignmentExpression") && assignment.left === member);
|
|
23361
|
+
return Boolean(member && isNodeOfType(member, "MemberExpression") && getStaticMemberName$1(member) === "current" && assignment && isNodeOfType(assignment, "AssignmentExpression") && assignment.left === member);
|
|
23123
23362
|
})) ? null : initializer;
|
|
23124
23363
|
};
|
|
23125
23364
|
const functionInvokesItself = (analysis, functionNode) => {
|
|
@@ -23158,7 +23397,7 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilena
|
|
|
23158
23397
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
23159
23398
|
const callee = stripParenExpression(child.callee);
|
|
23160
23399
|
const calleeName = getCallCalleeName$1(child);
|
|
23161
|
-
const memberName = getStaticMemberName(callee);
|
|
23400
|
+
const memberName = getStaticMemberName$1(callee);
|
|
23162
23401
|
const isDeferringCall = calleeName !== null && DEFERRING_CALLEE_NAMES.has(calleeName) || memberName !== null && DEFERRING_MEMBER_NAMES.has(memberName);
|
|
23163
23402
|
const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES$1.has(memberName) && isNodeOfType(callee, "MemberExpression");
|
|
23164
23403
|
const enqueueFrame = (callableNode, argumentsForCallable, isDeferred, introducedBindings, allowWithoutInvocationEvidence = false) => {
|
|
@@ -23317,14 +23556,19 @@ const analyzeHelperExpression = (expression, environment, usedParameterIndices)
|
|
|
23317
23556
|
const callbackSummary = analyzeHelperStatements(node.body.body ?? [], callbackEnvironment, usedParameterIndices, analyzeHelperExpression);
|
|
23318
23557
|
return callbackSummary.isValid && !callbackSummary.canContinue;
|
|
23319
23558
|
}
|
|
23559
|
+
if (isNodeOfType(node, "NewExpression")) {
|
|
23560
|
+
const callee = stripParenExpression(node.callee);
|
|
23561
|
+
if (!isNodeOfType(callee, "Identifier") || !PURE_GLOBAL_CONSTRUCTOR_NAMES.has(callee.name) || environment.parameterIndices.has(callee.name) || environment.recursiveNames.has(callee.name) || environment.shadowedGlobalNames.has(callee.name)) return false;
|
|
23562
|
+
return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
|
|
23563
|
+
}
|
|
23320
23564
|
if (isNodeOfType(node, "CallExpression")) {
|
|
23321
23565
|
const callee = stripParenExpression(node.callee);
|
|
23322
23566
|
const calleeRoot = getMemberRoot(callee);
|
|
23323
23567
|
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);
|
|
23324
23568
|
const namespaceName = isNodeOfType(calleeRoot, "Identifier") && !environment.parameterIndices.has(calleeRoot.name) && !environment.recursiveNames.has(calleeRoot.name) && !environment.shadowedGlobalNames.has(calleeRoot.name) ? calleeRoot.name : null;
|
|
23325
|
-
const namespaceMemberName = getStaticMemberName(callee);
|
|
23569
|
+
const namespaceMemberName = getStaticMemberName$1(callee);
|
|
23326
23570
|
const isPureNamespaceCall = isNodeOfType(callee, "MemberExpression") && namespaceName !== null && namespaceMemberName !== null && PURE_HELPER_NAMESPACE_MEMBER_NAMES.get(namespaceName)?.has(namespaceMemberName) === true;
|
|
23327
|
-
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
|
|
23571
|
+
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName$1(callee) ?? "");
|
|
23328
23572
|
if (!isPureGlobalCall && !isPureNamespaceCall && !isPureMemberTransform) return false;
|
|
23329
23573
|
if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression") && !analyzeHelperExpression(callee.object, environment, usedParameterIndices)) return false;
|
|
23330
23574
|
return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
|
|
@@ -23398,7 +23642,7 @@ const isLocallyConstructedObjectMember = (reference, memberExpression) => isNode
|
|
|
23398
23642
|
return isNodeOfType(definitionNode, "VariableDeclarator") && Boolean(definitionNode.init) && (isNodeOfType(definitionNode.init, "ObjectExpression") || isNodeOfType(definitionNode.init, "ArrayExpression"));
|
|
23399
23643
|
}) === true;
|
|
23400
23644
|
const getUseRefDeclarator = (analysis, memberExpression) => {
|
|
23401
|
-
if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !isNodeOfType(memberExpression.object, "Identifier")) return null;
|
|
23645
|
+
if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName$1(memberExpression) !== "current" || !isNodeOfType(memberExpression.object, "Identifier")) return null;
|
|
23402
23646
|
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;
|
|
23403
23647
|
};
|
|
23404
23648
|
const collectValueEvidence = (analysis, expression, frame, remainingCallFrames, visitedBindings = /* @__PURE__ */ new Set()) => {
|
|
@@ -23467,7 +23711,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23467
23711
|
return collectValueEvidence(analysis, initializer.init, frame, remainingCallFrames, visitedBindings);
|
|
23468
23712
|
}
|
|
23469
23713
|
if (isNodeOfType(node, "MemberExpression")) {
|
|
23470
|
-
if (getStaticMemberName(node) === "current" && isNodeOfType(node.object, "Identifier")) {
|
|
23714
|
+
if (getStaticMemberName$1(node) === "current" && isNodeOfType(node.object, "Identifier")) {
|
|
23471
23715
|
const refBinding = getRef(analysis, node.object)?.resolved;
|
|
23472
23716
|
const refDeclarator = useRefDeclarator;
|
|
23473
23717
|
if (refBinding && refDeclarator && isNodeOfType(refDeclarator, "VariableDeclarator") && isNodeOfType(refDeclarator.init, "CallExpression")) {
|
|
@@ -23483,7 +23727,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23483
23727
|
if (candidateReference.init) continue;
|
|
23484
23728
|
const identifier = candidateReference.identifier;
|
|
23485
23729
|
const member = identifier.parent;
|
|
23486
|
-
if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName(member) !== "current") {
|
|
23730
|
+
if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName$1(member) !== "current") {
|
|
23487
23731
|
evidence.hasUnknownSource = true;
|
|
23488
23732
|
continue;
|
|
23489
23733
|
}
|
|
@@ -23519,8 +23763,8 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23519
23763
|
}
|
|
23520
23764
|
const callee = stripParenExpression(node.callee);
|
|
23521
23765
|
const calleeRoot = getMemberRoot(callee);
|
|
23522
|
-
const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && getIdentifierBindingIdentity(analysis, callee) === null || isNodeOfType(
|
|
23523
|
-
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
|
|
23766
|
+
const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && getIdentifierBindingIdentity(analysis, callee) === null || isNodeOfType(callee, "MemberExpression") && isNodeOfType(calleeRoot, "Identifier") && getIdentifierBindingIdentity(analysis, calleeRoot) === null && PURE_HELPER_NAMESPACE_MEMBER_NAMES.get(calleeRoot.name)?.has(getStaticMemberName$1(callee) ?? "") === true;
|
|
23767
|
+
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName$1(callee) ?? "");
|
|
23524
23768
|
if (isPureGlobalCall || isPureMemberTransform) {
|
|
23525
23769
|
if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression")) mergeEvidence(evidence, collectValueEvidence(analysis, callee.object, frame, remainingCallFrames, new Set(visitedBindings)));
|
|
23526
23770
|
for (const argument of node.arguments ?? []) {
|
|
@@ -23572,6 +23816,15 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23572
23816
|
}
|
|
23573
23817
|
return evidence;
|
|
23574
23818
|
}
|
|
23819
|
+
if (isNodeOfType(node, "NewExpression")) {
|
|
23820
|
+
const callee = stripParenExpression(node.callee);
|
|
23821
|
+
if (!isNodeOfType(callee, "Identifier") || !PURE_GLOBAL_CONSTRUCTOR_NAMES.has(callee.name) || getIdentifierBindingIdentity(analysis, callee) !== null) {
|
|
23822
|
+
evidence.hasUnknownSource = true;
|
|
23823
|
+
return evidence;
|
|
23824
|
+
}
|
|
23825
|
+
for (const argument of node.arguments ?? []) mergeEvidence(evidence, collectValueEvidence(analysis, argument, frame, remainingCallFrames, new Set(visitedBindings)));
|
|
23826
|
+
return evidence;
|
|
23827
|
+
}
|
|
23575
23828
|
if (isFunctionLike$1(node) || isNodeOfType(node, "AwaitExpression")) {
|
|
23576
23829
|
evidence.hasUnknownSource = true;
|
|
23577
23830
|
return evidence;
|
|
@@ -23990,6 +24243,26 @@ const STATEFUL_HTML_DESCENDANT_TAGS = new Set([
|
|
|
23990
24243
|
"dialog",
|
|
23991
24244
|
"canvas"
|
|
23992
24245
|
]);
|
|
24246
|
+
const STATEFUL_HTML_ATTRIBUTE_NAMES = new Set([
|
|
24247
|
+
"autofocus",
|
|
24248
|
+
"contenteditable",
|
|
24249
|
+
"draggable",
|
|
24250
|
+
"tabindex"
|
|
24251
|
+
]);
|
|
24252
|
+
const isStaticallyFalseAttributeValue = (attribute) => {
|
|
24253
|
+
if (!isNodeOfType(attribute, "JSXAttribute") || !attribute.value) return false;
|
|
24254
|
+
const value = isNodeOfType(attribute.value, "JSXExpressionContainer") ? attribute.value.expression : attribute.value;
|
|
24255
|
+
return isNodeOfType(value, "Literal") && (value.value === false || value.value === "false");
|
|
24256
|
+
};
|
|
24257
|
+
const hasStatefulHtmlAttribute = (openingElement) => {
|
|
24258
|
+
if (!isNodeOfType(openingElement, "JSXOpeningElement")) return false;
|
|
24259
|
+
return openingElement.attributes.some((attribute) => {
|
|
24260
|
+
if (!isNodeOfType(attribute, "JSXAttribute") || !isNodeOfType(attribute.name, "JSXIdentifier")) return false;
|
|
24261
|
+
const attributeName = attribute.name.name.toLowerCase();
|
|
24262
|
+
if (!STATEFUL_HTML_ATTRIBUTE_NAMES.has(attributeName)) return false;
|
|
24263
|
+
return attributeName === "tabindex" || !isStaticallyFalseAttributeValue(attribute);
|
|
24264
|
+
});
|
|
24265
|
+
};
|
|
23993
24266
|
const STATEFUL_DESCENDANT_SCAN_BUDGET = 200;
|
|
23994
24267
|
const rootIdentifierNameOf = (expression) => {
|
|
23995
24268
|
let object = expression;
|
|
@@ -24007,7 +24280,8 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
|
|
|
24007
24280
|
budget -= 1;
|
|
24008
24281
|
const node = stack.pop();
|
|
24009
24282
|
if (isNodeOfType(node, "JSXElement")) {
|
|
24010
|
-
const
|
|
24283
|
+
const opening = node.openingElement;
|
|
24284
|
+
const name = opening.name;
|
|
24011
24285
|
if (name && isNodeOfType(name, "JSXIdentifier")) {
|
|
24012
24286
|
const tagName = name.name;
|
|
24013
24287
|
const firstChar = tagName.charCodeAt(0);
|
|
@@ -24015,6 +24289,7 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
|
|
|
24015
24289
|
if (STATEFUL_HTML_DESCENDANT_TAGS.has(tagName)) return true;
|
|
24016
24290
|
}
|
|
24017
24291
|
if (name && isNodeOfType(name, "JSXMemberExpression")) return true;
|
|
24292
|
+
if (hasStatefulHtmlAttribute(opening)) return true;
|
|
24018
24293
|
const children = node.children ?? [];
|
|
24019
24294
|
for (const child of children) stack.push(child);
|
|
24020
24295
|
continue;
|
|
@@ -24574,6 +24849,14 @@ const templateHasOuterMemberIdentity = (template, bindingFunction) => {
|
|
|
24574
24849
|
}
|
|
24575
24850
|
return false;
|
|
24576
24851
|
};
|
|
24852
|
+
const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
|
|
24853
|
+
const referencedItemNames = /* @__PURE__ */ new Set();
|
|
24854
|
+
for (const expression of template.expressions ?? []) {
|
|
24855
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
24856
|
+
if (isNodeOfType(unwrappedExpression, "Identifier") && itemNames.has(unwrappedExpression.name)) referencedItemNames.add(unwrappedExpression.name);
|
|
24857
|
+
}
|
|
24858
|
+
return referencedItemNames;
|
|
24859
|
+
};
|
|
24577
24860
|
const forLoopTestReadsDataLength = (test) => {
|
|
24578
24861
|
let didFindLengthRead = false;
|
|
24579
24862
|
walkAst(test, (child) => {
|
|
@@ -24710,9 +24993,11 @@ const noArrayIndexAsKey = defineRule({
|
|
|
24710
24993
|
} else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
|
|
24711
24994
|
const jsxElement = openingElement.parent;
|
|
24712
24995
|
if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
|
|
24996
|
+
const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
|
|
24997
|
+
const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET;
|
|
24713
24998
|
if (!containsStatefulDescendant(jsxElement, {
|
|
24714
|
-
memberRootNames:
|
|
24715
|
-
bareIdentifierNames: derivedNames
|
|
24999
|
+
memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET,
|
|
25000
|
+
bareIdentifierNames: primitiveItemNames.size > 0 ? new Set([...derivedNames, ...primitiveItemNames]) : derivedNames
|
|
24716
25001
|
})) return;
|
|
24717
25002
|
}
|
|
24718
25003
|
}
|
|
@@ -24880,7 +25165,11 @@ const noAsyncEffectCallback = defineRule({
|
|
|
24880
25165
|
severity: "warn",
|
|
24881
25166
|
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.",
|
|
24882
25167
|
create: (context) => ({ CallExpression(node) {
|
|
24883
|
-
if (!
|
|
25168
|
+
if (!isReactApiCall(node, EFFECT_HOOK_NAMES$1, context.scopes, {
|
|
25169
|
+
allowGlobalReactNamespace: true,
|
|
25170
|
+
allowUnboundBareCalls: true,
|
|
25171
|
+
resolveNamedAliases: true
|
|
25172
|
+
})) return;
|
|
24884
25173
|
const callback = getEffectCallback(node);
|
|
24885
25174
|
if (!callback) return;
|
|
24886
25175
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
@@ -26078,6 +26367,19 @@ const noCloneElement = defineRule({
|
|
|
26078
26367
|
} })
|
|
26079
26368
|
});
|
|
26080
26369
|
//#endregion
|
|
26370
|
+
//#region src/plugin/utils/get-call-method-name.ts
|
|
26371
|
+
/**
|
|
26372
|
+
* Returns the static method name of a call's callee when it's a
|
|
26373
|
+
* non-computed MemberExpression (`obj.method` → `"method"`), or
|
|
26374
|
+
* `null` otherwise. Used by `no-pass-data-to-parent` and
|
|
26375
|
+
* `no-pass-live-state-to-parent` to match the receiver-bound
|
|
26376
|
+
* method-call shape against an allow/block list.
|
|
26377
|
+
*/
|
|
26378
|
+
const getCallMethodName = (callee) => {
|
|
26379
|
+
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
|
|
26380
|
+
return null;
|
|
26381
|
+
};
|
|
26382
|
+
//#endregion
|
|
26081
26383
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
26082
26384
|
const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
26083
26385
|
const CONTEXT_MODULES = [
|
|
@@ -26085,23 +26387,35 @@ const CONTEXT_MODULES = [
|
|
|
26085
26387
|
"use-context-selector",
|
|
26086
26388
|
"react-tracked"
|
|
26087
26389
|
];
|
|
26088
|
-
const
|
|
26390
|
+
const getSupportedContextImportSource = (symbol) => {
|
|
26391
|
+
if (symbol?.kind !== "import") return null;
|
|
26392
|
+
const importDeclaration = symbol.declarationNode.parent;
|
|
26393
|
+
if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration") || typeof importDeclaration.source.value !== "string" || !CONTEXT_MODULES.includes(importDeclaration.source.value)) return null;
|
|
26394
|
+
return importDeclaration.source.value;
|
|
26395
|
+
};
|
|
26396
|
+
const isSupportedNamespaceSymbol = (symbol) => getSupportedContextImportSource(symbol) !== null && Boolean(symbol && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
|
|
26397
|
+
const isDestructuredCreateContextBinding = (identifier, scopes) => {
|
|
26398
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
26399
|
+
const symbol = scopes.symbolFor(identifier);
|
|
26400
|
+
if (symbol?.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "ObjectPattern")) return false;
|
|
26401
|
+
const property = symbol.bindingIdentifier.parent;
|
|
26402
|
+
if (!property || !isNodeOfType(property, "Property") || getStaticPropertyKeyName(property, { allowComputedString: true }) !== "createContext") return false;
|
|
26403
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
26404
|
+
return isNodeOfType(initializer, "Identifier") && isSupportedNamespaceSymbol(resolveConstIdentifierAlias(initializer, scopes));
|
|
26405
|
+
};
|
|
26406
|
+
const isCreateContextCall = (node, scopes) => {
|
|
26407
|
+
const callee = stripParenExpression(node.callee);
|
|
26089
26408
|
if (isNodeOfType(callee, "Identifier")) {
|
|
26090
|
-
|
|
26091
|
-
|
|
26409
|
+
if (isDestructuredCreateContextBinding(callee, scopes)) return true;
|
|
26410
|
+
const symbol = resolveConstIdentifierAlias(callee, scopes);
|
|
26411
|
+
return getSupportedContextImportSource(symbol) !== null && Boolean(symbol && getImportedName(symbol.declarationNode) === "createContext");
|
|
26092
26412
|
}
|
|
26093
|
-
if (isNodeOfType(callee, "MemberExpression")
|
|
26094
|
-
|
|
26095
|
-
|
|
26096
|
-
|
|
26097
|
-
|
|
26098
|
-
|
|
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;
|
|
26413
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
26414
|
+
if ((getCallMethodName(callee) ?? (callee.computed && isNodeOfType(callee.property, "Literal") && typeof callee.property.value === "string" ? callee.property.value : null)) !== "createContext") return false;
|
|
26415
|
+
const receiver = stripParenExpression(callee.object);
|
|
26416
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
26417
|
+
if (receiver.name === "React" && scopes.isGlobalReference(receiver)) return true;
|
|
26418
|
+
return isSupportedNamespaceSymbol(resolveConstIdentifierAlias(receiver, scopes));
|
|
26105
26419
|
};
|
|
26106
26420
|
const noCreateContextInRender = defineRule({
|
|
26107
26421
|
id: "no-create-context-in-render",
|
|
@@ -26110,7 +26424,7 @@ const noCreateContextInRender = defineRule({
|
|
|
26110
26424
|
category: "Correctness",
|
|
26111
26425
|
recommendation: "Move `createContext(...)` outside the component, to the top level of the file, so it stays the same on every render.",
|
|
26112
26426
|
create: (context) => ({ CallExpression(node) {
|
|
26113
|
-
if (!
|
|
26427
|
+
if (!isCreateContextCall(node, context.scopes)) return;
|
|
26114
26428
|
const componentOrHookName = enclosingComponentOrHookName(node);
|
|
26115
26429
|
if (!componentOrHookName) return;
|
|
26116
26430
|
context.report({
|
|
@@ -27345,7 +27659,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
27345
27659
|
let ancestor = setStateCall.parent;
|
|
27346
27660
|
while (ancestor) {
|
|
27347
27661
|
if (!lifecycleMember) {
|
|
27348
|
-
if (FUNCTION_NODE_TYPES$1.has(ancestor.type)) nestedFunctionCount += 1;
|
|
27662
|
+
if (FUNCTION_NODE_TYPES$1.has(ancestor.type) && (!isImmediatelyInvokedFunction(ancestor) || !("async" in ancestor) || ancestor.async === true)) nestedFunctionCount += 1;
|
|
27349
27663
|
if (isLifecycleMember(ancestor, lifecycleNames)) lifecycleMember = ancestor;
|
|
27350
27664
|
} else if (isEs5Component(ancestor) || isEs6Component(ancestor)) {
|
|
27351
27665
|
if (nestedFunctionCount > 1 && !options.disallowInNestedFunctions) return false;
|
|
@@ -27547,7 +27861,7 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
|
|
|
27547
27861
|
const body = lifecycleFunction.body;
|
|
27548
27862
|
if (!body) return derivedNames;
|
|
27549
27863
|
walkAst(body, (node) => {
|
|
27550
|
-
if (FUNCTION_NODE_TYPES.has(node.type)) return false;
|
|
27864
|
+
if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
|
|
27551
27865
|
if (!isNodeOfType(node, "VariableDeclarator")) return;
|
|
27552
27866
|
const init = node.init;
|
|
27553
27867
|
if (!init) return;
|
|
@@ -27557,6 +27871,67 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
|
|
|
27557
27871
|
return derivedNames;
|
|
27558
27872
|
};
|
|
27559
27873
|
const isStatefulOperand = (node, paramNames, derivedNames) => referencesAnyName(node, paramNames) || referencesAnyName(node, derivedNames) || containsThisStateOrProps(node);
|
|
27874
|
+
const getStaticMemberName = (node) => {
|
|
27875
|
+
if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
|
|
27876
|
+
return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
|
|
27877
|
+
};
|
|
27878
|
+
const getThisStateFieldName = (node) => {
|
|
27879
|
+
const unwrappedNode = stripParenExpression(node);
|
|
27880
|
+
if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
|
|
27881
|
+
const object = stripParenExpression(unwrappedNode.object);
|
|
27882
|
+
if (!isNodeOfType(object, "MemberExpression") || !isNodeOfType(stripParenExpression(object.object), "ThisExpression") || getStaticMemberName(object) !== "state") return null;
|
|
27883
|
+
return getStaticMemberName(unwrappedNode);
|
|
27884
|
+
};
|
|
27885
|
+
const collectLocalInitializers = (lifecycleFunction) => {
|
|
27886
|
+
const initializers = /* @__PURE__ */ new Map();
|
|
27887
|
+
const body = lifecycleFunction.body;
|
|
27888
|
+
if (!body) return initializers;
|
|
27889
|
+
walkAst(body, (node) => {
|
|
27890
|
+
if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
|
|
27891
|
+
if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.init) initializers.set(node.id.name, node.init);
|
|
27892
|
+
});
|
|
27893
|
+
return initializers;
|
|
27894
|
+
};
|
|
27895
|
+
const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
|
|
27896
|
+
if (readsPostMountValue(node)) return true;
|
|
27897
|
+
const referencedNames = /* @__PURE__ */ new Set();
|
|
27898
|
+
collectReferenceIdentifierNames(node, referencedNames);
|
|
27899
|
+
for (const referencedName of referencedNames) {
|
|
27900
|
+
if (visitedNames.has(referencedName)) continue;
|
|
27901
|
+
const initializer = localInitializers.get(referencedName);
|
|
27902
|
+
if (!initializer) continue;
|
|
27903
|
+
if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
|
|
27904
|
+
}
|
|
27905
|
+
return false;
|
|
27906
|
+
};
|
|
27907
|
+
const getSetStateFieldValue = (setStateCall, fieldName) => {
|
|
27908
|
+
if (!isNodeOfType(setStateCall, "CallExpression")) return null;
|
|
27909
|
+
const argument = setStateCall.arguments?.[0];
|
|
27910
|
+
if (!argument || !isNodeOfType(argument, "ObjectExpression")) return null;
|
|
27911
|
+
for (const property of argument.properties ?? []) {
|
|
27912
|
+
if (!isNodeOfType(property, "Property") || property.computed === true) continue;
|
|
27913
|
+
if ((isNodeOfType(property.key, "Identifier") && property.key.name || isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" && property.key.value || null) === fieldName) return property.value;
|
|
27914
|
+
}
|
|
27915
|
+
return null;
|
|
27916
|
+
};
|
|
27917
|
+
const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
|
|
27918
|
+
let qualifies = false;
|
|
27919
|
+
walkAst(test, (node) => {
|
|
27920
|
+
if (qualifies) return false;
|
|
27921
|
+
if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
|
|
27922
|
+
const leftFieldName = getThisStateFieldName(node.left);
|
|
27923
|
+
const rightFieldName = getThisStateFieldName(node.right);
|
|
27924
|
+
const fieldName = leftFieldName ?? rightFieldName;
|
|
27925
|
+
const comparedValue = leftFieldName ? node.right : node.left;
|
|
27926
|
+
if (!fieldName || !leftFieldName && !rightFieldName) return;
|
|
27927
|
+
const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
|
|
27928
|
+
if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
|
|
27929
|
+
if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
|
|
27930
|
+
qualifies = true;
|
|
27931
|
+
return false;
|
|
27932
|
+
});
|
|
27933
|
+
return qualifies;
|
|
27934
|
+
};
|
|
27560
27935
|
const isDiffGuardTest = (test, paramNames, derivedNames) => {
|
|
27561
27936
|
if (referencesAnyName(test, paramNames)) return true;
|
|
27562
27937
|
let qualifies = false;
|
|
@@ -27564,7 +27939,7 @@ const isDiffGuardTest = (test, paramNames, derivedNames) => {
|
|
|
27564
27939
|
if (qualifies) return false;
|
|
27565
27940
|
if (!isNodeOfType(node, "BinaryExpression")) return;
|
|
27566
27941
|
if (!EQUALITY_OPERATORS.has(node.operator)) return;
|
|
27567
|
-
if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames)) {
|
|
27942
|
+
if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames) && (referencesAnyName(node.left, derivedNames) || referencesAnyName(node.right, derivedNames))) {
|
|
27568
27943
|
qualifies = true;
|
|
27569
27944
|
return false;
|
|
27570
27945
|
}
|
|
@@ -27577,11 +27952,12 @@ const isInsideDiffGuard = (setStateCall) => {
|
|
|
27577
27952
|
const paramNames = /* @__PURE__ */ new Set();
|
|
27578
27953
|
for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
|
|
27579
27954
|
const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
|
|
27955
|
+
const localInitializers = collectLocalInitializers(lifecycleFunction);
|
|
27580
27956
|
let child = setStateCall;
|
|
27581
27957
|
let ancestor = setStateCall.parent;
|
|
27582
27958
|
while (ancestor && ancestor !== lifecycleFunction) {
|
|
27583
27959
|
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;
|
|
27584
|
-
if (guardTest && isDiffGuardTest(guardTest, paramNames, derivedNames)) return true;
|
|
27960
|
+
if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers))) return true;
|
|
27585
27961
|
child = ancestor;
|
|
27586
27962
|
ancestor = ancestor.parent ?? null;
|
|
27587
27963
|
}
|
|
@@ -30099,7 +30475,11 @@ const noFetchInEffect = defineRule({
|
|
|
30099
30475
|
severity: "warn",
|
|
30100
30476
|
recommendation: "Use a data-fetching layer or Server Component so fetches do not race, double-fire, or leak from `useEffect`.",
|
|
30101
30477
|
create: (context) => ({ CallExpression(node) {
|
|
30102
|
-
if (!
|
|
30478
|
+
if (!isReactApiCall(node, EFFECT_HOOK_NAMES$1, context.scopes, {
|
|
30479
|
+
allowGlobalReactNamespace: true,
|
|
30480
|
+
allowUnboundBareCalls: true,
|
|
30481
|
+
resolveNamedAliases: true
|
|
30482
|
+
})) return;
|
|
30103
30483
|
const callback = getEffectCallback(node);
|
|
30104
30484
|
if (!callback) return;
|
|
30105
30485
|
const analysisFunctions = collectEffectAnalysisFunctions(callback, context);
|
|
@@ -34309,19 +34689,6 @@ const DATA_SINK_METHOD_NAMES = new Set([
|
|
|
34309
34689
|
"deserialize"
|
|
34310
34690
|
]);
|
|
34311
34691
|
//#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
|
|
34325
34692
|
//#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
|
|
34326
34693
|
const isUseStateIdentifier = (identifier) => {
|
|
34327
34694
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
@@ -34947,6 +35314,30 @@ const guardsRenderShape = (comparison) => {
|
|
|
34947
35314
|
}
|
|
34948
35315
|
return false;
|
|
34949
35316
|
};
|
|
35317
|
+
const isPropsChildrenLength = (node, scopes) => {
|
|
35318
|
+
const unwrappedNode = stripParenExpression(node);
|
|
35319
|
+
return isNodeOfType(unwrappedNode, "MemberExpression") && !unwrappedNode.computed && isNodeOfType(unwrappedNode.property, "Identifier") && unwrappedNode.property.name === "length" && resolvesToPropsChildren(stripParenExpression(unwrappedNode.object), scopes);
|
|
35320
|
+
};
|
|
35321
|
+
const isLargeTextLengthComparison = (node, scopes) => {
|
|
35322
|
+
const unwrappedNode = stripParenExpression(node);
|
|
35323
|
+
if (!isNodeOfType(unwrappedNode, "BinaryExpression")) return false;
|
|
35324
|
+
const leftIsLength = isPropsChildrenLength(unwrappedNode.left, scopes);
|
|
35325
|
+
const rightIsLength = isPropsChildrenLength(unwrappedNode.right, scopes);
|
|
35326
|
+
const thresholdNode = leftIsLength ? unwrappedNode.right : unwrappedNode.left;
|
|
35327
|
+
if (!leftIsLength && !rightIsLength || !isNodeOfType(thresholdNode, "Literal")) return false;
|
|
35328
|
+
if (typeof thresholdNode.value !== "number" || thresholdNode.value < 1e3) return false;
|
|
35329
|
+
return leftIsLength ? unwrappedNode.operator === ">" || unwrappedNode.operator === ">=" : unwrappedNode.operator === "<" || unwrappedNode.operator === "<=";
|
|
35330
|
+
};
|
|
35331
|
+
const isLargeStringOptimizationGuard = (comparison, scopes) => {
|
|
35332
|
+
let current = findTransparentExpressionRoot(comparison);
|
|
35333
|
+
while (current.parent) {
|
|
35334
|
+
const parent = current.parent;
|
|
35335
|
+
if (!isNodeOfType(parent, "LogicalExpression") || parent.operator !== "&&") return false;
|
|
35336
|
+
if (isLargeTextLengthComparison(parent.left === current ? parent.right : parent.left, scopes)) return true;
|
|
35337
|
+
current = findTransparentExpressionRoot(parent);
|
|
35338
|
+
}
|
|
35339
|
+
return false;
|
|
35340
|
+
};
|
|
34950
35341
|
const noPolymorphicChildren = defineRule({
|
|
34951
35342
|
id: "no-polymorphic-children",
|
|
34952
35343
|
title: "Children type checked at runtime",
|
|
@@ -34960,6 +35351,7 @@ const noPolymorphicChildren = defineRule({
|
|
|
34960
35351
|
const isStringLiteral = (operand) => isNodeOfType(operand, "Literal") && operand.value === "string";
|
|
34961
35352
|
if (!isStringLiteral(node.left) && !isStringLiteral(node.right)) return;
|
|
34962
35353
|
if (!guardsRenderShape(node)) return;
|
|
35354
|
+
if (isLargeStringOptimizationGuard(node, context.scopes)) return;
|
|
34963
35355
|
context.report({
|
|
34964
35356
|
node,
|
|
34965
35357
|
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."
|
|
@@ -35177,6 +35569,27 @@ const hasPreviousValueDep = (effectNode, depElements) => {
|
|
|
35177
35569
|
}
|
|
35178
35570
|
return false;
|
|
35179
35571
|
};
|
|
35572
|
+
const isStateLikeDependency = (analysis, element, isPropName) => {
|
|
35573
|
+
if (!isNodeOfType(element, "Identifier") || isPropName(element.name)) return false;
|
|
35574
|
+
if (!analysis) return true;
|
|
35575
|
+
const reference = getRef(analysis, element);
|
|
35576
|
+
if (!reference) return true;
|
|
35577
|
+
const upstreamReferences = getUpstreamRefs(analysis, reference);
|
|
35578
|
+
if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
|
|
35579
|
+
return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
|
|
35580
|
+
};
|
|
35581
|
+
const getRefHeldPropCallbackName = (callExpression, isPropName) => {
|
|
35582
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
35583
|
+
if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "current") return null;
|
|
35584
|
+
const receiver = stripParenExpression(callee.object);
|
|
35585
|
+
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
35586
|
+
const binding = findVariableInitializer(callExpression, receiver.name);
|
|
35587
|
+
if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
|
|
35588
|
+
if (getCalleeName$2(binding.initializer) !== "useRef") return null;
|
|
35589
|
+
const callbackArgument = binding.initializer.arguments?.[0];
|
|
35590
|
+
if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
|
|
35591
|
+
return isPropName(callbackArgument.name) ? callbackArgument.name : null;
|
|
35592
|
+
};
|
|
35180
35593
|
const noPropCallbackInEffect = defineRule({
|
|
35181
35594
|
id: "no-prop-callback-in-effect",
|
|
35182
35595
|
title: "Parent kept in sync with a callback effect",
|
|
@@ -35193,11 +35606,11 @@ const noPropCallbackInEffect = defineRule({
|
|
|
35193
35606
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
35194
35607
|
const depsNode = node.arguments[1];
|
|
35195
35608
|
if (!isNodeOfType(depsNode, "ArrayExpression") || !depsNode.elements?.length) return;
|
|
35196
|
-
const
|
|
35609
|
+
const analysis = getProgramAnalysis(node);
|
|
35610
|
+
const stateLikeDeps = (depsNode.elements ?? []).filter((element) => element && isStateLikeDependency(analysis, element, propStackTracker.isPropName));
|
|
35197
35611
|
if (stateLikeDeps.length === 0) return;
|
|
35198
35612
|
if (isRefLatchGuardedEffect(callback.body)) return;
|
|
35199
35613
|
if (hasPreviousValueDep(node, depsNode.elements ?? [])) return;
|
|
35200
|
-
const analysis = getProgramAnalysis(node);
|
|
35201
35614
|
if (analysis) {
|
|
35202
35615
|
const stateLikeDepRefs = [];
|
|
35203
35616
|
for (const element of stateLikeDeps) {
|
|
@@ -35209,9 +35622,9 @@ const noPropCallbackInEffect = defineRule({
|
|
|
35209
35622
|
const reportedNodes = /* @__PURE__ */ new Set();
|
|
35210
35623
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
35211
35624
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
35212
|
-
|
|
35213
|
-
const calleeName =
|
|
35214
|
-
if (!
|
|
35625
|
+
const directCallee = stripParenExpression(child.callee);
|
|
35626
|
+
const calleeName = isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName);
|
|
35627
|
+
if (!calleeName) return;
|
|
35215
35628
|
if (!isResultDiscardedCall(child)) return;
|
|
35216
35629
|
if (reportedNodes.has(child)) return;
|
|
35217
35630
|
reportedNodes.add(child);
|
|
@@ -43052,15 +43465,6 @@ const isStableHookWrapperArgument = (node) => {
|
|
|
43052
43465
|
const parent = node.parent;
|
|
43053
43466
|
return isNodeOfType(parent, "CallExpression") && isNodeOfType(parent.callee, "Identifier") && STABLE_HOOK_WRAPPERS.has(parent.callee.name) && Boolean(parent.arguments?.some((argument) => argument === node));
|
|
43054
43467
|
};
|
|
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
|
-
};
|
|
43064
43468
|
const queryStableQueryClient = defineRule({
|
|
43065
43469
|
id: "query-stable-query-client",
|
|
43066
43470
|
title: "Unstable QueryClient in component",
|