oxlint-plugin-react-doctor 0.7.5-dev.654849 → 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 +88 -402
- 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;
|
|
@@ -6871,10 +6868,11 @@ const getTemplateInterpolations = (valueTail) => {
|
|
|
6871
6868
|
const interpolations = valueTail.slice(1, closingBacktickIndex).match(/\$\{[^}]*\}/g);
|
|
6872
6869
|
return interpolations === null ? "" : interpolations.join(" ");
|
|
6873
6870
|
};
|
|
6874
|
-
const getIdentifierDeclarationInitializer = (identifier,
|
|
6875
|
-
const
|
|
6876
|
-
if (
|
|
6877
|
-
|
|
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);
|
|
6878
6876
|
};
|
|
6879
6877
|
const isPureStringLiteralConcat = (initializerText) => {
|
|
6880
6878
|
if (!/^["']/.test(initializerText)) return false;
|
|
@@ -6944,153 +6942,6 @@ const isAllOperandsDomContentConcat = (valueExpression) => {
|
|
|
6944
6942
|
return !HTML_TAINT_PATTERN.test(withoutCast.replace(DOM_CONTENT_SOURCE_VALUE_PATTERN, ""));
|
|
6945
6943
|
});
|
|
6946
6944
|
};
|
|
6947
|
-
const findMatchingBraceIndex = (fileContent, openingBraceIndex) => {
|
|
6948
|
-
let depth = 0;
|
|
6949
|
-
let quote = null;
|
|
6950
|
-
for (let index = openingBraceIndex; index < fileContent.length; index += 1) {
|
|
6951
|
-
const character = fileContent[index];
|
|
6952
|
-
if (quote !== null) {
|
|
6953
|
-
if (character === quote && fileContent[index - 1] !== "\\") quote = null;
|
|
6954
|
-
continue;
|
|
6955
|
-
}
|
|
6956
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
6957
|
-
quote = character;
|
|
6958
|
-
continue;
|
|
6959
|
-
}
|
|
6960
|
-
if (character === "{") depth += 1;
|
|
6961
|
-
if (character === "}") {
|
|
6962
|
-
depth -= 1;
|
|
6963
|
-
if (depth === 0) return index;
|
|
6964
|
-
}
|
|
6965
|
-
}
|
|
6966
|
-
return fileContent.length;
|
|
6967
|
-
};
|
|
6968
|
-
const findContainingBlockEndIndex = (fileContent, targetIndex) => {
|
|
6969
|
-
const openingBraceIndexes = [];
|
|
6970
|
-
let quote = null;
|
|
6971
|
-
let isLineComment = false;
|
|
6972
|
-
let isBlockComment = false;
|
|
6973
|
-
for (let index = 0; index < targetIndex; index += 1) {
|
|
6974
|
-
const character = fileContent[index];
|
|
6975
|
-
const nextCharacter = fileContent[index + 1];
|
|
6976
|
-
if (isLineComment) {
|
|
6977
|
-
if (character === "\n") isLineComment = false;
|
|
6978
|
-
continue;
|
|
6979
|
-
}
|
|
6980
|
-
if (isBlockComment) {
|
|
6981
|
-
if (character === "*" && nextCharacter === "/") {
|
|
6982
|
-
isBlockComment = false;
|
|
6983
|
-
index += 1;
|
|
6984
|
-
}
|
|
6985
|
-
continue;
|
|
6986
|
-
}
|
|
6987
|
-
if (quote !== null) {
|
|
6988
|
-
if (character === quote && fileContent[index - 1] !== "\\") quote = null;
|
|
6989
|
-
continue;
|
|
6990
|
-
}
|
|
6991
|
-
if (character === "/" && nextCharacter === "/") {
|
|
6992
|
-
isLineComment = true;
|
|
6993
|
-
index += 1;
|
|
6994
|
-
continue;
|
|
6995
|
-
}
|
|
6996
|
-
if (character === "/" && nextCharacter === "*") {
|
|
6997
|
-
isBlockComment = true;
|
|
6998
|
-
index += 1;
|
|
6999
|
-
continue;
|
|
7000
|
-
}
|
|
7001
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
7002
|
-
quote = character;
|
|
7003
|
-
continue;
|
|
7004
|
-
}
|
|
7005
|
-
if (character === "{") openingBraceIndexes.push(index);
|
|
7006
|
-
if (character === "}") openingBraceIndexes.pop();
|
|
7007
|
-
}
|
|
7008
|
-
const openingBraceIndex = openingBraceIndexes.at(-1);
|
|
7009
|
-
return openingBraceIndex === void 0 ? fileContent.length : findMatchingBraceIndex(fileContent, openingBraceIndex);
|
|
7010
|
-
};
|
|
7011
|
-
const findVisibleIdentifierDeclaration = (identifier, sinkIndex, fileContent) => {
|
|
7012
|
-
const initializerPattern = new RegExp(`(?:const|let|var)\\s+${escapeRegExp(identifier)}\\s*(?::[^=\\n]*)?=\\s*([^;\\n]+)`, "g");
|
|
7013
|
-
let nearestDeclaration = null;
|
|
7014
|
-
let nearestDeclarationIndex = -1;
|
|
7015
|
-
for (const match of fileContent.matchAll(initializerPattern)) {
|
|
7016
|
-
const declarationIndex = match.index;
|
|
7017
|
-
if (declarationIndex === void 0 || declarationIndex >= sinkIndex || declarationIndex <= nearestDeclarationIndex || findContainingBlockEndIndex(fileContent, declarationIndex) < sinkIndex) continue;
|
|
7018
|
-
nearestDeclarationIndex = declarationIndex;
|
|
7019
|
-
const initializer = match[1];
|
|
7020
|
-
if (initializer === void 0) continue;
|
|
7021
|
-
nearestDeclaration = {
|
|
7022
|
-
initializer,
|
|
7023
|
-
initializerStartIndex: declarationIndex + match[0].length - initializer.length
|
|
7024
|
-
};
|
|
7025
|
-
}
|
|
7026
|
-
return nearestDeclaration;
|
|
7027
|
-
};
|
|
7028
|
-
const findContainingFunctionParameterSource = (identifier, sinkIndex, fileContent) => {
|
|
7029
|
-
const patterns = [/function\s+([\w$]+)\s*\(([^)]*)\)\s*\{/g, /(?:const|let|var)\s+([\w$]+)\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>\s*\{/g];
|
|
7030
|
-
let closestSource = null;
|
|
7031
|
-
let closestStartIndex = -1;
|
|
7032
|
-
for (const pattern of patterns) for (const match of fileContent.matchAll(pattern)) {
|
|
7033
|
-
const matchIndex = match.index;
|
|
7034
|
-
if (matchIndex === void 0 || matchIndex >= sinkIndex || matchIndex < closestStartIndex) continue;
|
|
7035
|
-
if (findMatchingBraceIndex(fileContent, matchIndex + match[0].lastIndexOf("{")) < sinkIndex) continue;
|
|
7036
|
-
const parameterIndex = (match[2] ?? "").split(",").findIndex((parameter) => parameter.trim().match(/^(?:\.\.\.)?([\w$]+)/)?.[1] === identifier);
|
|
7037
|
-
if (parameterIndex < 0) continue;
|
|
7038
|
-
closestStartIndex = matchIndex;
|
|
7039
|
-
closestSource = {
|
|
7040
|
-
functionName: match[1] ?? "",
|
|
7041
|
-
parameterIndex
|
|
7042
|
-
};
|
|
7043
|
-
}
|
|
7044
|
-
return closestSource;
|
|
7045
|
-
};
|
|
7046
|
-
const splitTopLevelArguments = (argumentText) => {
|
|
7047
|
-
const argumentsList = [];
|
|
7048
|
-
let startIndex = 0;
|
|
7049
|
-
let depth = 0;
|
|
7050
|
-
let quote = null;
|
|
7051
|
-
for (let index = 0; index < argumentText.length; index += 1) {
|
|
7052
|
-
const character = argumentText[index];
|
|
7053
|
-
if (quote !== null) {
|
|
7054
|
-
if (character === quote && argumentText[index - 1] !== "\\") quote = null;
|
|
7055
|
-
continue;
|
|
7056
|
-
}
|
|
7057
|
-
if (character === "\"" || character === "'" || character === "`") {
|
|
7058
|
-
quote = character;
|
|
7059
|
-
continue;
|
|
7060
|
-
}
|
|
7061
|
-
if (character === "(" || character === "[" || character === "{") depth += 1;
|
|
7062
|
-
if (character === ")" || character === "]" || character === "}") depth -= 1;
|
|
7063
|
-
if (character === "," && depth === 0) {
|
|
7064
|
-
argumentsList.push(argumentText.slice(startIndex, index).trim());
|
|
7065
|
-
startIndex = index + 1;
|
|
7066
|
-
}
|
|
7067
|
-
}
|
|
7068
|
-
argumentsList.push(argumentText.slice(startIndex).trim());
|
|
7069
|
-
return argumentsList;
|
|
7070
|
-
};
|
|
7071
|
-
const isHtmlTainted = (expression, fileContent, sinkIndex, visitedIdentifiers) => {
|
|
7072
|
-
const trimmedExpression = expression.trim();
|
|
7073
|
-
if (STRING_LITERAL_VALUE_PATTERN.test(`${trimmedExpression};`)) return false;
|
|
7074
|
-
if (MODULE_CONSTANT_VALUE_PATTERN.test(`${trimmedExpression};`)) return false;
|
|
7075
|
-
if (SANITIZER_PATTERN.test(trimmedExpression)) return false;
|
|
7076
|
-
if (ENV_CONFIG_VALUE_PATTERN.test(trimmedExpression)) return false;
|
|
7077
|
-
if (I18N_VALUE_PATTERN.test(trimmedExpression)) return false;
|
|
7078
|
-
if (ESCAPING_SERIALIZER_CALL_PATTERN.test(trimmedExpression)) return false;
|
|
7079
|
-
if (HTML_TAINT_PATTERN.test(trimmedExpression)) return true;
|
|
7080
|
-
const identifier = trimmedExpression.match(/^([\w$]+)\s*(?:[;,})\n]|$)/)?.[1];
|
|
7081
|
-
if (identifier === void 0 || visitedIdentifiers.has(identifier)) return false;
|
|
7082
|
-
visitedIdentifiers.add(identifier);
|
|
7083
|
-
const declaration = findVisibleIdentifierDeclaration(identifier, sinkIndex, fileContent);
|
|
7084
|
-
if (declaration !== null && isHtmlTainted(declaration.initializer, fileContent, sinkIndex, visitedIdentifiers)) return true;
|
|
7085
|
-
const parameterSource = findContainingFunctionParameterSource(identifier, sinkIndex, fileContent);
|
|
7086
|
-
if (parameterSource === null || parameterSource.functionName.length === 0) return false;
|
|
7087
|
-
const callPattern = new RegExp(`\\b${escapeRegExp(parameterSource.functionName)}\\s*\\(([^)]*)\\)`, "g");
|
|
7088
|
-
for (const callMatch of fileContent.matchAll(callPattern)) {
|
|
7089
|
-
const argument = splitTopLevelArguments(callMatch[1] ?? "")[parameterSource.parameterIndex];
|
|
7090
|
-
if (argument !== void 0 && isHtmlTainted(argument, fileContent, sinkIndex, new Set(visitedIdentifiers))) return true;
|
|
7091
|
-
}
|
|
7092
|
-
return false;
|
|
7093
|
-
};
|
|
7094
6945
|
const dangerousHtmlSink = defineRule({
|
|
7095
6946
|
id: "dangerous-html-sink",
|
|
7096
6947
|
title: "HTML injection sink with dynamic content",
|
|
@@ -7117,7 +6968,6 @@ const dangerousHtmlSink = defineRule({
|
|
|
7117
6968
|
const valueTail = fullValueTail.slice(0, VALUE_EXPRESSION_MAX_CHARS);
|
|
7118
6969
|
const terminatorIndex = valueTail.search(/[;}]/);
|
|
7119
6970
|
const valueExpression = terminatorIndex >= 0 ? valueTail.slice(0, terminatorIndex + 1) : valueTail;
|
|
7120
|
-
const sinkIndex = lines.slice(0, lineIndex).join("\n").length + (lineIndex > 0 ? 1 : 0) + line.search(DANGEROUS_HTML_PATTERN);
|
|
7121
6971
|
if (STRING_LITERAL_VALUE_PATTERN.test(valueExpression)) continue;
|
|
7122
6972
|
if (MODULE_CONSTANT_VALUE_PATTERN.test(valueExpression)) continue;
|
|
7123
6973
|
if (DOM_CONTENT_SOURCE_VALUE_PATTERN.test(valueExpression) && !valueExpression.includes("+")) {
|
|
@@ -7129,7 +6979,7 @@ const dangerousHtmlSink = defineRule({
|
|
|
7129
6979
|
let templateInterpolations = getTemplateInterpolations(longValueTail ?? fullValueTail);
|
|
7130
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;
|
|
7131
6981
|
if (templateInterpolations === null && valueIdentifier !== void 0 && BARE_IDENTIFIER_VALUE_PATTERN.test(valueExpression)) {
|
|
7132
|
-
const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier,
|
|
6982
|
+
const declarationInitializer = getIdentifierDeclarationInitializer(valueIdentifier, file.content);
|
|
7133
6983
|
if (declarationInitializer !== null) {
|
|
7134
6984
|
if (isPureStringLiteralConcat(declarationInitializer)) continue;
|
|
7135
6985
|
templateInterpolations = getTemplateInterpolations(declarationInitializer);
|
|
@@ -7140,7 +6990,7 @@ const dangerousHtmlSink = defineRule({
|
|
|
7140
6990
|
if (SANITIZER_PATTERN.test(judgedExpression)) continue;
|
|
7141
6991
|
if (ENV_CONFIG_VALUE_PATTERN.test(judgedExpression)) continue;
|
|
7142
6992
|
if (I18N_VALUE_PATTERN.test(judgedExpression)) continue;
|
|
7143
|
-
if (!
|
|
6993
|
+
if (!HTML_TAINT_PATTERN.test(judgedExpression)) continue;
|
|
7144
6994
|
if (ESCAPING_SERIALIZER_CALL_PATTERN.test(valueExpression)) continue;
|
|
7145
6995
|
if (/highlighted/i.test(valueExpression)) continue;
|
|
7146
6996
|
if (/highlight/i.test(valueExpression) && HIGHLIGHTER_LIBRARY_PATTERN.test(file.content)) continue;
|
|
@@ -7587,7 +7437,7 @@ const containsJsx$1 = (root) => {
|
|
|
7587
7437
|
containsJsxCache.set(root, found);
|
|
7588
7438
|
return found;
|
|
7589
7439
|
};
|
|
7590
|
-
const getStaticMemberName$
|
|
7440
|
+
const getStaticMemberName$1 = (node) => {
|
|
7591
7441
|
if (!isNodeOfType(node, "MemberExpression")) return null;
|
|
7592
7442
|
if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
|
|
7593
7443
|
if (node.computed && isNodeOfType(node.property, "Literal") && typeof node.property.value === "string") return node.property.value;
|
|
@@ -7601,7 +7451,7 @@ const getAssignedName = (node) => {
|
|
|
7601
7451
|
if (isNodeOfType(parent, "AssignmentExpression")) {
|
|
7602
7452
|
const left = parent.left;
|
|
7603
7453
|
if (isNodeOfType(left, "Identifier")) return left.name;
|
|
7604
|
-
if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$
|
|
7454
|
+
if (isNodeOfType(left, "MemberExpression")) return getStaticMemberName$1(left);
|
|
7605
7455
|
}
|
|
7606
7456
|
return null;
|
|
7607
7457
|
};
|
|
@@ -7609,20 +7459,20 @@ const isModuleExportsAssignment = (node) => {
|
|
|
7609
7459
|
const parent = node.parent;
|
|
7610
7460
|
if (!parent || !isNodeOfType(parent, "AssignmentExpression")) return false;
|
|
7611
7461
|
const left = parent.left;
|
|
7612
|
-
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";
|
|
7613
7463
|
};
|
|
7614
7464
|
const isCreateClassLikeCall = (node) => {
|
|
7615
7465
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7616
7466
|
if (isEs5Component(node)) return true;
|
|
7617
7467
|
const callee = node.callee;
|
|
7618
|
-
if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$
|
|
7468
|
+
if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$1(callee) === "createClass";
|
|
7619
7469
|
return false;
|
|
7620
7470
|
};
|
|
7621
|
-
const isCreateContextCall
|
|
7471
|
+
const isCreateContextCall = (node) => {
|
|
7622
7472
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7623
7473
|
const callee = node.callee;
|
|
7624
7474
|
if (isNodeOfType(callee, "Identifier")) return callee.name === "createContext";
|
|
7625
|
-
return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$
|
|
7475
|
+
return isNodeOfType(callee, "MemberExpression") && getStaticMemberName$1(callee) === "createContext";
|
|
7626
7476
|
};
|
|
7627
7477
|
const isNamedFunctionLike = (node) => (isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && Boolean(node.id?.name);
|
|
7628
7478
|
const firstCallArgument = (node) => {
|
|
@@ -7680,7 +7530,7 @@ const memberExpressionPath = (node) => {
|
|
|
7680
7530
|
if (isNodeOfType(node, "Identifier")) return [node.name];
|
|
7681
7531
|
if (!isNodeOfType(node, "MemberExpression")) return [];
|
|
7682
7532
|
const objectPath = memberExpressionPath(node.object);
|
|
7683
|
-
const propertyName = getStaticMemberName$
|
|
7533
|
+
const propertyName = getStaticMemberName$1(node);
|
|
7684
7534
|
return propertyName ? [...objectPath, propertyName] : objectPath;
|
|
7685
7535
|
};
|
|
7686
7536
|
const displayNameAssignmentIndexCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -7690,7 +7540,7 @@ const getDisplayNameAssignmentIndex = (programRoot) => {
|
|
|
7690
7540
|
const identifierTargets = /* @__PURE__ */ new Set();
|
|
7691
7541
|
const memberPathSegments = /* @__PURE__ */ new Set();
|
|
7692
7542
|
const visit = (node) => {
|
|
7693
|
-
if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName$
|
|
7543
|
+
if (isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && getStaticMemberName$1(node.left) === "displayName") {
|
|
7694
7544
|
if (isNodeOfType(node.left.object, "Identifier")) identifierTargets.add(node.left.object.name);
|
|
7695
7545
|
for (const segment of memberExpressionPath(node.left.object)) memberPathSegments.add(segment);
|
|
7696
7546
|
}
|
|
@@ -7795,7 +7645,7 @@ const displayName = defineRule({
|
|
|
7795
7645
|
}
|
|
7796
7646
|
},
|
|
7797
7647
|
CallExpression(node) {
|
|
7798
|
-
if (settings.checkContextObjects && isReactVersionAtLeast$1(settings.reactVersion, 16, 3) && isCreateContextCall
|
|
7648
|
+
if (settings.checkContextObjects && isReactVersionAtLeast$1(settings.reactVersion, 16, 3) && isCreateContextCall(node)) {
|
|
7799
7649
|
const assignedName = getAssignedName(node);
|
|
7800
7650
|
if (assignedName) {
|
|
7801
7651
|
const programRoot = findProgramRoot(node);
|
|
@@ -7887,9 +7737,9 @@ const isImportedFromReact = (symbol) => {
|
|
|
7887
7737
|
const importDeclaration = symbol.declarationNode.parent;
|
|
7888
7738
|
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(importDeclaration.source.value));
|
|
7889
7739
|
};
|
|
7890
|
-
const isNamedReactApiImport = (identifier, apiNames, scopes
|
|
7740
|
+
const isNamedReactApiImport = (identifier, apiNames, scopes) => {
|
|
7891
7741
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
7892
|
-
const symbol =
|
|
7742
|
+
const symbol = scopes.symbolFor(identifier);
|
|
7893
7743
|
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
7894
7744
|
const importedName = getImportedName(symbol.declarationNode);
|
|
7895
7745
|
return Boolean(importedName && includesApiName(apiNames, importedName));
|
|
@@ -7903,7 +7753,7 @@ const isReactApiCall = (node, apiNames, scopes, options = {}) => {
|
|
|
7903
7753
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7904
7754
|
const callee = stripParenExpression(node.callee);
|
|
7905
7755
|
if (isNodeOfType(callee, "Identifier")) {
|
|
7906
|
-
if (isNamedReactApiImport(callee, apiNames, scopes
|
|
7756
|
+
if (isNamedReactApiImport(callee, apiNames, scopes)) return true;
|
|
7907
7757
|
return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
|
|
7908
7758
|
}
|
|
7909
7759
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || !includesApiName(apiNames, callee.property.name)) return false;
|
|
@@ -11112,11 +10962,6 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
11112
10962
|
return { CallExpression(node) {
|
|
11113
10963
|
const hookName = getHookName(node.callee, context.scopes);
|
|
11114
10964
|
if (!hookName || !isHookOfInterest(hookName, node.callee)) return;
|
|
11115
|
-
if (HOOKS_REQUIRING_DEPS_MATCH.has(hookName) && !isReactApiCall(node, HOOKS_REQUIRING_DEPS_MATCH, context.scopes, {
|
|
11116
|
-
allowGlobalReactNamespace: true,
|
|
11117
|
-
allowUnboundBareCalls: true,
|
|
11118
|
-
resolveNamedAliases: true
|
|
11119
|
-
})) return;
|
|
11120
10965
|
const callbackArgumentIndex = getCallbackArgumentIndex(hookName);
|
|
11121
10966
|
const depsArgumentIndex = getDepsArgumentIndex(hookName);
|
|
11122
10967
|
const callbackArgument = node.arguments[callbackArgumentIndex];
|
|
@@ -14411,11 +14256,7 @@ const jsIndexMaps = defineRule({
|
|
|
14411
14256
|
//#region src/plugin/utils/are-expressions-structurally-equal.ts
|
|
14412
14257
|
const areExpressionsStructurallyEqual = (a, b) => {
|
|
14413
14258
|
if (!a || !b) return a === b;
|
|
14414
|
-
const unwrappedA = stripParenExpression(a);
|
|
14415
|
-
const unwrappedB = stripParenExpression(b);
|
|
14416
|
-
if (unwrappedA !== a || unwrappedB !== b) return areExpressionsStructurallyEqual(unwrappedA, unwrappedB);
|
|
14417
14259
|
if (a.type !== b.type) return false;
|
|
14418
|
-
if (isNodeOfType(a, "ThisExpression")) return true;
|
|
14419
14260
|
if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
|
|
14420
14261
|
if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
|
|
14421
14262
|
if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
|
|
@@ -20958,8 +20799,8 @@ const catchClauseRethrowsCaught = (handler) => {
|
|
|
20958
20799
|
return didRethrow;
|
|
20959
20800
|
};
|
|
20960
20801
|
//#endregion
|
|
20961
|
-
//#region src/plugin/utils/
|
|
20962
|
-
const
|
|
20802
|
+
//#region src/plugin/utils/find-guarding-try-statement.ts
|
|
20803
|
+
const isImmediatelyInvokedFunctionCallee = (functionNode) => {
|
|
20963
20804
|
let wrappedCallee = functionNode;
|
|
20964
20805
|
let enclosing = functionNode.parent;
|
|
20965
20806
|
while (enclosing && stripParenExpression(enclosing) === functionNode) {
|
|
@@ -20968,13 +20809,11 @@ const isImmediatelyInvokedFunction = (functionNode) => {
|
|
|
20968
20809
|
}
|
|
20969
20810
|
return Boolean(enclosing && isNodeOfType(enclosing, "CallExpression") && enclosing.callee === wrappedCallee);
|
|
20970
20811
|
};
|
|
20971
|
-
//#endregion
|
|
20972
|
-
//#region src/plugin/utils/find-guarding-try-statement.ts
|
|
20973
20812
|
const findGuardingTryStatement = (node) => {
|
|
20974
20813
|
let child = node;
|
|
20975
20814
|
let ancestor = node.parent;
|
|
20976
20815
|
while (ancestor) {
|
|
20977
|
-
if (isFunctionLike$1(ancestor) && !
|
|
20816
|
+
if (isFunctionLike$1(ancestor) && !isImmediatelyInvokedFunctionCallee(ancestor)) return null;
|
|
20978
20817
|
if (isNodeOfType(ancestor, "TryStatement") && ancestor.block === child && ancestor.handler && !catchClauseRethrowsCaught(ancestor.handler)) return ancestor;
|
|
20979
20818
|
child = ancestor;
|
|
20980
20819
|
ancestor = ancestor.parent ?? null;
|
|
@@ -22107,8 +21946,6 @@ const DOM_QUERY_MEMBER_NAMES = new Set([
|
|
|
22107
21946
|
]);
|
|
22108
21947
|
const LAYOUT_MEASUREMENT_MEMBER_NAMES = new Set([
|
|
22109
21948
|
"current",
|
|
22110
|
-
"textContent",
|
|
22111
|
-
"innerText",
|
|
22112
21949
|
"scrollWidth",
|
|
22113
21950
|
"clientWidth",
|
|
22114
21951
|
"offsetWidth",
|
|
@@ -22130,7 +21967,7 @@ const POST_MOUNT_GLOBAL_NAMES = new Set([
|
|
|
22130
21967
|
"navigator"
|
|
22131
21968
|
]);
|
|
22132
21969
|
const REF_FACTORY_CALLEE_NAMES = new Set(["useRef", "createRef"]);
|
|
22133
|
-
const hasRefLikeName = (name) => name === "ref" || name.endsWith("Ref") || name.endsWith("ref")
|
|
21970
|
+
const hasRefLikeName = (name) => name === "ref" || name.endsWith("Ref") || name.endsWith("ref");
|
|
22134
21971
|
const isRefFactoryInitializer = (init) => {
|
|
22135
21972
|
if (!init || !isNodeOfType(init, "CallExpression")) return false;
|
|
22136
21973
|
const callee = init.callee;
|
|
@@ -23107,7 +22944,7 @@ const STORAGE_GLOBAL_NAMES$1 = new Set([
|
|
|
23107
22944
|
"localStorage",
|
|
23108
22945
|
"sessionStorage"
|
|
23109
22946
|
]);
|
|
23110
|
-
const getStaticMemberName
|
|
22947
|
+
const getStaticMemberName = (node) => {
|
|
23111
22948
|
if (!isNodeOfType(node, "MemberExpression")) return null;
|
|
23112
22949
|
if (!node.computed && isNodeOfType(node.property, "Identifier")) return node.property.name;
|
|
23113
22950
|
if (node.computed && isNodeOfType(node.property, "Literal")) return typeof node.property.value === "string" ? node.property.value : null;
|
|
@@ -23122,7 +22959,7 @@ const getCallCalleeName$1 = (callExpression) => {
|
|
|
23122
22959
|
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
23123
22960
|
const callee = stripParenExpression(callExpression.callee);
|
|
23124
22961
|
if (isNodeOfType(callee, "Identifier")) return callee.name;
|
|
23125
|
-
return getStaticMemberName
|
|
22962
|
+
return getStaticMemberName(callee);
|
|
23126
22963
|
};
|
|
23127
22964
|
const getFunctionParameters = (functionNode) => isFunctionLike$1(functionNode) ? functionNode.params ?? [] : [];
|
|
23128
22965
|
const getIdentifierBindingIdentity = (analysis, identifier) => {
|
|
@@ -23238,14 +23075,14 @@ const localUseEventPreservesCallback = (analysis, callee) => {
|
|
|
23238
23075
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
23239
23076
|
const forwardedCallee = stripParenExpression(child.callee);
|
|
23240
23077
|
if (!isNodeOfType(forwardedCallee, "MemberExpression")) return;
|
|
23241
|
-
if (getStaticMemberName
|
|
23078
|
+
if (getStaticMemberName(forwardedCallee) !== "current") return;
|
|
23242
23079
|
if (!isNodeOfType(forwardedCallee.object, "Identifier")) return;
|
|
23243
23080
|
const refReference = getRef(analysis, forwardedCallee.object);
|
|
23244
23081
|
if (!callbackRefDeclarators.find((declarator) => refReference?.resolved?.defs.some((definition) => definition.node === declarator)) || !refReference?.resolved) return;
|
|
23245
23082
|
if (!refReference.resolved.references.some((candidateReference) => {
|
|
23246
23083
|
const memberExpression = candidateReference.identifier.parent;
|
|
23247
23084
|
const assignmentExpression = memberExpression?.parent;
|
|
23248
|
-
if (!memberExpression || !isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName
|
|
23085
|
+
if (!memberExpression || !isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !assignmentExpression || !isNodeOfType(assignmentExpression, "AssignmentExpression") || assignmentExpression.left !== memberExpression) return false;
|
|
23249
23086
|
return getIdentifierBindingIdentity(analysis, assignmentExpression.right) !== callbackBinding;
|
|
23250
23087
|
})) forwardsCallback = true;
|
|
23251
23088
|
});
|
|
@@ -23270,7 +23107,7 @@ const resolveWrappedCallable = (analysis, node) => {
|
|
|
23270
23107
|
return callback && isFunctionLike$1(callback) ? callback : null;
|
|
23271
23108
|
}
|
|
23272
23109
|
if (!isNodeOfType(candidate, "MemberExpression")) return null;
|
|
23273
|
-
if (getStaticMemberName
|
|
23110
|
+
if (getStaticMemberName(candidate) !== "current") return null;
|
|
23274
23111
|
if (!isNodeOfType(candidate.object, "Identifier")) return null;
|
|
23275
23112
|
const reference = getRef(analysis, candidate.object);
|
|
23276
23113
|
const declarator = reference?.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
@@ -23282,7 +23119,7 @@ const resolveWrappedCallable = (analysis, node) => {
|
|
|
23282
23119
|
return Boolean(reference?.resolved?.references.some((candidateReference) => {
|
|
23283
23120
|
const member = candidateReference.identifier.parent;
|
|
23284
23121
|
const assignment = member?.parent;
|
|
23285
|
-
return Boolean(member && isNodeOfType(member, "MemberExpression") && getStaticMemberName
|
|
23122
|
+
return Boolean(member && isNodeOfType(member, "MemberExpression") && getStaticMemberName(member) === "current" && assignment && isNodeOfType(assignment, "AssignmentExpression") && assignment.left === member);
|
|
23286
23123
|
})) ? null : initializer;
|
|
23287
23124
|
};
|
|
23288
23125
|
const functionInvokesItself = (analysis, functionNode) => {
|
|
@@ -23321,7 +23158,7 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilena
|
|
|
23321
23158
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
23322
23159
|
const callee = stripParenExpression(child.callee);
|
|
23323
23160
|
const calleeName = getCallCalleeName$1(child);
|
|
23324
|
-
const memberName = getStaticMemberName
|
|
23161
|
+
const memberName = getStaticMemberName(callee);
|
|
23325
23162
|
const isDeferringCall = calleeName !== null && DEFERRING_CALLEE_NAMES.has(calleeName) || memberName !== null && DEFERRING_MEMBER_NAMES.has(memberName);
|
|
23326
23163
|
const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES$1.has(memberName) && isNodeOfType(callee, "MemberExpression");
|
|
23327
23164
|
const enqueueFrame = (callableNode, argumentsForCallable, isDeferred, introducedBindings, allowWithoutInvocationEvidence = false) => {
|
|
@@ -23485,9 +23322,9 @@ const analyzeHelperExpression = (expression, environment, usedParameterIndices)
|
|
|
23485
23322
|
const calleeRoot = getMemberRoot(callee);
|
|
23486
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);
|
|
23487
23324
|
const namespaceName = isNodeOfType(calleeRoot, "Identifier") && !environment.parameterIndices.has(calleeRoot.name) && !environment.recursiveNames.has(calleeRoot.name) && !environment.shadowedGlobalNames.has(calleeRoot.name) ? calleeRoot.name : null;
|
|
23488
|
-
const namespaceMemberName = getStaticMemberName
|
|
23325
|
+
const namespaceMemberName = getStaticMemberName(callee);
|
|
23489
23326
|
const isPureNamespaceCall = isNodeOfType(callee, "MemberExpression") && namespaceName !== null && namespaceMemberName !== null && PURE_HELPER_NAMESPACE_MEMBER_NAMES.get(namespaceName)?.has(namespaceMemberName) === true;
|
|
23490
|
-
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName
|
|
23327
|
+
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
|
|
23491
23328
|
if (!isPureGlobalCall && !isPureNamespaceCall && !isPureMemberTransform) return false;
|
|
23492
23329
|
if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression") && !analyzeHelperExpression(callee.object, environment, usedParameterIndices)) return false;
|
|
23493
23330
|
return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
|
|
@@ -23561,7 +23398,7 @@ const isLocallyConstructedObjectMember = (reference, memberExpression) => isNode
|
|
|
23561
23398
|
return isNodeOfType(definitionNode, "VariableDeclarator") && Boolean(definitionNode.init) && (isNodeOfType(definitionNode.init, "ObjectExpression") || isNodeOfType(definitionNode.init, "ArrayExpression"));
|
|
23562
23399
|
}) === true;
|
|
23563
23400
|
const getUseRefDeclarator = (analysis, memberExpression) => {
|
|
23564
|
-
if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName
|
|
23401
|
+
if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !isNodeOfType(memberExpression.object, "Identifier")) return null;
|
|
23565
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;
|
|
23566
23403
|
};
|
|
23567
23404
|
const collectValueEvidence = (analysis, expression, frame, remainingCallFrames, visitedBindings = /* @__PURE__ */ new Set()) => {
|
|
@@ -23630,7 +23467,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23630
23467
|
return collectValueEvidence(analysis, initializer.init, frame, remainingCallFrames, visitedBindings);
|
|
23631
23468
|
}
|
|
23632
23469
|
if (isNodeOfType(node, "MemberExpression")) {
|
|
23633
|
-
if (getStaticMemberName
|
|
23470
|
+
if (getStaticMemberName(node) === "current" && isNodeOfType(node.object, "Identifier")) {
|
|
23634
23471
|
const refBinding = getRef(analysis, node.object)?.resolved;
|
|
23635
23472
|
const refDeclarator = useRefDeclarator;
|
|
23636
23473
|
if (refBinding && refDeclarator && isNodeOfType(refDeclarator, "VariableDeclarator") && isNodeOfType(refDeclarator.init, "CallExpression")) {
|
|
@@ -23646,7 +23483,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23646
23483
|
if (candidateReference.init) continue;
|
|
23647
23484
|
const identifier = candidateReference.identifier;
|
|
23648
23485
|
const member = identifier.parent;
|
|
23649
|
-
if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName
|
|
23486
|
+
if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName(member) !== "current") {
|
|
23650
23487
|
evidence.hasUnknownSource = true;
|
|
23651
23488
|
continue;
|
|
23652
23489
|
}
|
|
@@ -23683,7 +23520,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23683
23520
|
const callee = stripParenExpression(node.callee);
|
|
23684
23521
|
const calleeRoot = getMemberRoot(callee);
|
|
23685
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;
|
|
23686
|
-
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName
|
|
23523
|
+
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
|
|
23687
23524
|
if (isPureGlobalCall || isPureMemberTransform) {
|
|
23688
23525
|
if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression")) mergeEvidence(evidence, collectValueEvidence(analysis, callee.object, frame, remainingCallFrames, new Set(visitedBindings)));
|
|
23689
23526
|
for (const argument of node.arguments ?? []) {
|
|
@@ -24153,26 +23990,6 @@ const STATEFUL_HTML_DESCENDANT_TAGS = new Set([
|
|
|
24153
23990
|
"dialog",
|
|
24154
23991
|
"canvas"
|
|
24155
23992
|
]);
|
|
24156
|
-
const STATEFUL_HTML_ATTRIBUTE_NAMES = new Set([
|
|
24157
|
-
"autofocus",
|
|
24158
|
-
"contenteditable",
|
|
24159
|
-
"draggable",
|
|
24160
|
-
"tabindex"
|
|
24161
|
-
]);
|
|
24162
|
-
const isStaticallyFalseAttributeValue = (attribute) => {
|
|
24163
|
-
if (!isNodeOfType(attribute, "JSXAttribute") || !attribute.value) return false;
|
|
24164
|
-
const value = isNodeOfType(attribute.value, "JSXExpressionContainer") ? attribute.value.expression : attribute.value;
|
|
24165
|
-
return isNodeOfType(value, "Literal") && (value.value === false || value.value === "false");
|
|
24166
|
-
};
|
|
24167
|
-
const hasStatefulHtmlAttribute = (openingElement) => {
|
|
24168
|
-
if (!isNodeOfType(openingElement, "JSXOpeningElement")) return false;
|
|
24169
|
-
return openingElement.attributes.some((attribute) => {
|
|
24170
|
-
if (!isNodeOfType(attribute, "JSXAttribute") || !isNodeOfType(attribute.name, "JSXIdentifier")) return false;
|
|
24171
|
-
const attributeName = attribute.name.name.toLowerCase();
|
|
24172
|
-
if (!STATEFUL_HTML_ATTRIBUTE_NAMES.has(attributeName)) return false;
|
|
24173
|
-
return attributeName === "tabindex" || !isStaticallyFalseAttributeValue(attribute);
|
|
24174
|
-
});
|
|
24175
|
-
};
|
|
24176
23993
|
const STATEFUL_DESCENDANT_SCAN_BUDGET = 200;
|
|
24177
23994
|
const rootIdentifierNameOf = (expression) => {
|
|
24178
23995
|
let object = expression;
|
|
@@ -24190,8 +24007,7 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
|
|
|
24190
24007
|
budget -= 1;
|
|
24191
24008
|
const node = stack.pop();
|
|
24192
24009
|
if (isNodeOfType(node, "JSXElement")) {
|
|
24193
|
-
const
|
|
24194
|
-
const name = opening.name;
|
|
24010
|
+
const name = node.openingElement.name;
|
|
24195
24011
|
if (name && isNodeOfType(name, "JSXIdentifier")) {
|
|
24196
24012
|
const tagName = name.name;
|
|
24197
24013
|
const firstChar = tagName.charCodeAt(0);
|
|
@@ -24199,7 +24015,6 @@ const containsStatefulDescendant = (jsxElement, options = {}) => {
|
|
|
24199
24015
|
if (STATEFUL_HTML_DESCENDANT_TAGS.has(tagName)) return true;
|
|
24200
24016
|
}
|
|
24201
24017
|
if (name && isNodeOfType(name, "JSXMemberExpression")) return true;
|
|
24202
|
-
if (hasStatefulHtmlAttribute(opening)) return true;
|
|
24203
24018
|
const children = node.children ?? [];
|
|
24204
24019
|
for (const child of children) stack.push(child);
|
|
24205
24020
|
continue;
|
|
@@ -24759,14 +24574,6 @@ const templateHasOuterMemberIdentity = (template, bindingFunction) => {
|
|
|
24759
24574
|
}
|
|
24760
24575
|
return false;
|
|
24761
24576
|
};
|
|
24762
|
-
const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
|
|
24763
|
-
const referencedItemNames = /* @__PURE__ */ new Set();
|
|
24764
|
-
for (const expression of template.expressions ?? []) {
|
|
24765
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
24766
|
-
if (isNodeOfType(unwrappedExpression, "Identifier") && itemNames.has(unwrappedExpression.name)) referencedItemNames.add(unwrappedExpression.name);
|
|
24767
|
-
}
|
|
24768
|
-
return referencedItemNames;
|
|
24769
|
-
};
|
|
24770
24577
|
const forLoopTestReadsDataLength = (test) => {
|
|
24771
24578
|
let didFindLengthRead = false;
|
|
24772
24579
|
walkAst(test, (child) => {
|
|
@@ -24903,11 +24710,9 @@ const noArrayIndexAsKey = defineRule({
|
|
|
24903
24710
|
} else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
|
|
24904
24711
|
const jsxElement = openingElement.parent;
|
|
24905
24712
|
if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
|
|
24906
|
-
const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
|
|
24907
|
-
const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET;
|
|
24908
24713
|
if (!containsStatefulDescendant(jsxElement, {
|
|
24909
|
-
memberRootNames:
|
|
24910
|
-
bareIdentifierNames:
|
|
24714
|
+
memberRootNames: INLINE_TEXT_LEAF_TAGS.has(elementName.name) ? itemNames : EMPTY_NAME_SET,
|
|
24715
|
+
bareIdentifierNames: derivedNames
|
|
24911
24716
|
})) return;
|
|
24912
24717
|
}
|
|
24913
24718
|
}
|
|
@@ -25075,11 +24880,7 @@ const noAsyncEffectCallback = defineRule({
|
|
|
25075
24880
|
severity: "warn",
|
|
25076
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.",
|
|
25077
24882
|
create: (context) => ({ CallExpression(node) {
|
|
25078
|
-
if (!
|
|
25079
|
-
allowGlobalReactNamespace: true,
|
|
25080
|
-
allowUnboundBareCalls: true,
|
|
25081
|
-
resolveNamedAliases: true
|
|
25082
|
-
})) return;
|
|
24883
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
25083
24884
|
const callback = getEffectCallback(node);
|
|
25084
24885
|
if (!callback) return;
|
|
25085
24886
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
@@ -26277,19 +26078,6 @@ const noCloneElement = defineRule({
|
|
|
26277
26078
|
} })
|
|
26278
26079
|
});
|
|
26279
26080
|
//#endregion
|
|
26280
|
-
//#region src/plugin/utils/get-call-method-name.ts
|
|
26281
|
-
/**
|
|
26282
|
-
* Returns the static method name of a call's callee when it's a
|
|
26283
|
-
* non-computed MemberExpression (`obj.method` → `"method"`), or
|
|
26284
|
-
* `null` otherwise. Used by `no-pass-data-to-parent` and
|
|
26285
|
-
* `no-pass-live-state-to-parent` to match the receiver-bound
|
|
26286
|
-
* method-call shape against an allow/block list.
|
|
26287
|
-
*/
|
|
26288
|
-
const getCallMethodName = (callee) => {
|
|
26289
|
-
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
|
|
26290
|
-
return null;
|
|
26291
|
-
};
|
|
26292
|
-
//#endregion
|
|
26293
26081
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
26294
26082
|
const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
26295
26083
|
const CONTEXT_MODULES = [
|
|
@@ -26297,35 +26085,23 @@ const CONTEXT_MODULES = [
|
|
|
26297
26085
|
"use-context-selector",
|
|
26298
26086
|
"react-tracked"
|
|
26299
26087
|
];
|
|
26300
|
-
const
|
|
26301
|
-
if (symbol?.kind !== "import") return null;
|
|
26302
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
26303
|
-
if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration") || typeof importDeclaration.source.value !== "string" || !CONTEXT_MODULES.includes(importDeclaration.source.value)) return null;
|
|
26304
|
-
return importDeclaration.source.value;
|
|
26305
|
-
};
|
|
26306
|
-
const isSupportedNamespaceSymbol = (symbol) => getSupportedContextImportSource(symbol) !== null && Boolean(symbol && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
|
|
26307
|
-
const isDestructuredCreateContextBinding = (identifier, scopes) => {
|
|
26308
|
-
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
26309
|
-
const symbol = scopes.symbolFor(identifier);
|
|
26310
|
-
if (symbol?.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "ObjectPattern")) return false;
|
|
26311
|
-
const property = symbol.bindingIdentifier.parent;
|
|
26312
|
-
if (!property || !isNodeOfType(property, "Property") || getStaticPropertyKeyName(property, { allowComputedString: true }) !== "createContext") return false;
|
|
26313
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
26314
|
-
return isNodeOfType(initializer, "Identifier") && isSupportedNamespaceSymbol(resolveConstIdentifierAlias(initializer, scopes));
|
|
26315
|
-
};
|
|
26316
|
-
const isCreateContextCall = (node, scopes) => {
|
|
26317
|
-
const callee = stripParenExpression(node.callee);
|
|
26088
|
+
const isCreateContextCallee = (callee) => {
|
|
26318
26089
|
if (isNodeOfType(callee, "Identifier")) {
|
|
26319
|
-
|
|
26320
|
-
|
|
26321
|
-
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);
|
|
26322
26092
|
}
|
|
26323
|
-
if (
|
|
26324
|
-
|
|
26325
|
-
|
|
26326
|
-
|
|
26327
|
-
|
|
26328
|
-
|
|
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;
|
|
26329
26105
|
};
|
|
26330
26106
|
const noCreateContextInRender = defineRule({
|
|
26331
26107
|
id: "no-create-context-in-render",
|
|
@@ -26334,7 +26110,7 @@ const noCreateContextInRender = defineRule({
|
|
|
26334
26110
|
category: "Correctness",
|
|
26335
26111
|
recommendation: "Move `createContext(...)` outside the component, to the top level of the file, so it stays the same on every render.",
|
|
26336
26112
|
create: (context) => ({ CallExpression(node) {
|
|
26337
|
-
if (!
|
|
26113
|
+
if (!isCreateContextCallee(node.callee)) return;
|
|
26338
26114
|
const componentOrHookName = enclosingComponentOrHookName(node);
|
|
26339
26115
|
if (!componentOrHookName) return;
|
|
26340
26116
|
context.report({
|
|
@@ -27569,7 +27345,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
|
|
|
27569
27345
|
let ancestor = setStateCall.parent;
|
|
27570
27346
|
while (ancestor) {
|
|
27571
27347
|
if (!lifecycleMember) {
|
|
27572
|
-
if (FUNCTION_NODE_TYPES$1.has(ancestor.type)
|
|
27348
|
+
if (FUNCTION_NODE_TYPES$1.has(ancestor.type)) nestedFunctionCount += 1;
|
|
27573
27349
|
if (isLifecycleMember(ancestor, lifecycleNames)) lifecycleMember = ancestor;
|
|
27574
27350
|
} else if (isEs5Component(ancestor) || isEs6Component(ancestor)) {
|
|
27575
27351
|
if (nestedFunctionCount > 1 && !options.disallowInNestedFunctions) return false;
|
|
@@ -27771,7 +27547,7 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
|
|
|
27771
27547
|
const body = lifecycleFunction.body;
|
|
27772
27548
|
if (!body) return derivedNames;
|
|
27773
27549
|
walkAst(body, (node) => {
|
|
27774
|
-
if (FUNCTION_NODE_TYPES.has(node.type)
|
|
27550
|
+
if (FUNCTION_NODE_TYPES.has(node.type)) return false;
|
|
27775
27551
|
if (!isNodeOfType(node, "VariableDeclarator")) return;
|
|
27776
27552
|
const init = node.init;
|
|
27777
27553
|
if (!init) return;
|
|
@@ -27781,67 +27557,6 @@ const collectDiffSourceLocalNames = (lifecycleFunction, paramNames) => {
|
|
|
27781
27557
|
return derivedNames;
|
|
27782
27558
|
};
|
|
27783
27559
|
const isStatefulOperand = (node, paramNames, derivedNames) => referencesAnyName(node, paramNames) || referencesAnyName(node, derivedNames) || containsThisStateOrProps(node);
|
|
27784
|
-
const getStaticMemberName = (node) => {
|
|
27785
|
-
if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
|
|
27786
|
-
return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
|
|
27787
|
-
};
|
|
27788
|
-
const getThisStateFieldName = (node) => {
|
|
27789
|
-
const unwrappedNode = stripParenExpression(node);
|
|
27790
|
-
if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
|
|
27791
|
-
const object = stripParenExpression(unwrappedNode.object);
|
|
27792
|
-
if (!isNodeOfType(object, "MemberExpression") || !isNodeOfType(stripParenExpression(object.object), "ThisExpression") || getStaticMemberName(object) !== "state") return null;
|
|
27793
|
-
return getStaticMemberName(unwrappedNode);
|
|
27794
|
-
};
|
|
27795
|
-
const collectLocalInitializers = (lifecycleFunction) => {
|
|
27796
|
-
const initializers = /* @__PURE__ */ new Map();
|
|
27797
|
-
const body = lifecycleFunction.body;
|
|
27798
|
-
if (!body) return initializers;
|
|
27799
|
-
walkAst(body, (node) => {
|
|
27800
|
-
if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
|
|
27801
|
-
if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.init) initializers.set(node.id.name, node.init);
|
|
27802
|
-
});
|
|
27803
|
-
return initializers;
|
|
27804
|
-
};
|
|
27805
|
-
const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
|
|
27806
|
-
if (readsPostMountValue(node)) return true;
|
|
27807
|
-
const referencedNames = /* @__PURE__ */ new Set();
|
|
27808
|
-
collectReferenceIdentifierNames(node, referencedNames);
|
|
27809
|
-
for (const referencedName of referencedNames) {
|
|
27810
|
-
if (visitedNames.has(referencedName)) continue;
|
|
27811
|
-
const initializer = localInitializers.get(referencedName);
|
|
27812
|
-
if (!initializer) continue;
|
|
27813
|
-
if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
|
|
27814
|
-
}
|
|
27815
|
-
return false;
|
|
27816
|
-
};
|
|
27817
|
-
const getSetStateFieldValue = (setStateCall, fieldName) => {
|
|
27818
|
-
if (!isNodeOfType(setStateCall, "CallExpression")) return null;
|
|
27819
|
-
const argument = setStateCall.arguments?.[0];
|
|
27820
|
-
if (!argument || !isNodeOfType(argument, "ObjectExpression")) return null;
|
|
27821
|
-
for (const property of argument.properties ?? []) {
|
|
27822
|
-
if (!isNodeOfType(property, "Property") || property.computed === true) continue;
|
|
27823
|
-
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;
|
|
27824
|
-
}
|
|
27825
|
-
return null;
|
|
27826
|
-
};
|
|
27827
|
-
const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
|
|
27828
|
-
let qualifies = false;
|
|
27829
|
-
walkAst(test, (node) => {
|
|
27830
|
-
if (qualifies) return false;
|
|
27831
|
-
if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
|
|
27832
|
-
const leftFieldName = getThisStateFieldName(node.left);
|
|
27833
|
-
const rightFieldName = getThisStateFieldName(node.right);
|
|
27834
|
-
const fieldName = leftFieldName ?? rightFieldName;
|
|
27835
|
-
const comparedValue = leftFieldName ? node.right : node.left;
|
|
27836
|
-
if (!fieldName || !leftFieldName && !rightFieldName) return;
|
|
27837
|
-
const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
|
|
27838
|
-
if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
|
|
27839
|
-
if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
|
|
27840
|
-
qualifies = true;
|
|
27841
|
-
return false;
|
|
27842
|
-
});
|
|
27843
|
-
return qualifies;
|
|
27844
|
-
};
|
|
27845
27560
|
const isDiffGuardTest = (test, paramNames, derivedNames) => {
|
|
27846
27561
|
if (referencesAnyName(test, paramNames)) return true;
|
|
27847
27562
|
let qualifies = false;
|
|
@@ -27849,7 +27564,7 @@ const isDiffGuardTest = (test, paramNames, derivedNames) => {
|
|
|
27849
27564
|
if (qualifies) return false;
|
|
27850
27565
|
if (!isNodeOfType(node, "BinaryExpression")) return;
|
|
27851
27566
|
if (!EQUALITY_OPERATORS.has(node.operator)) return;
|
|
27852
|
-
if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames)
|
|
27567
|
+
if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames)) {
|
|
27853
27568
|
qualifies = true;
|
|
27854
27569
|
return false;
|
|
27855
27570
|
}
|
|
@@ -27862,12 +27577,11 @@ const isInsideDiffGuard = (setStateCall) => {
|
|
|
27862
27577
|
const paramNames = /* @__PURE__ */ new Set();
|
|
27863
27578
|
for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
|
|
27864
27579
|
const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
|
|
27865
|
-
const localInitializers = collectLocalInitializers(lifecycleFunction);
|
|
27866
27580
|
let child = setStateCall;
|
|
27867
27581
|
let ancestor = setStateCall.parent;
|
|
27868
27582
|
while (ancestor && ancestor !== lifecycleFunction) {
|
|
27869
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;
|
|
27870
|
-
if (guardTest &&
|
|
27584
|
+
if (guardTest && isDiffGuardTest(guardTest, paramNames, derivedNames)) return true;
|
|
27871
27585
|
child = ancestor;
|
|
27872
27586
|
ancestor = ancestor.parent ?? null;
|
|
27873
27587
|
}
|
|
@@ -30385,11 +30099,7 @@ const noFetchInEffect = defineRule({
|
|
|
30385
30099
|
severity: "warn",
|
|
30386
30100
|
recommendation: "Use a data-fetching layer or Server Component so fetches do not race, double-fire, or leak from `useEffect`.",
|
|
30387
30101
|
create: (context) => ({ CallExpression(node) {
|
|
30388
|
-
if (!
|
|
30389
|
-
allowGlobalReactNamespace: true,
|
|
30390
|
-
allowUnboundBareCalls: true,
|
|
30391
|
-
resolveNamedAliases: true
|
|
30392
|
-
})) return;
|
|
30102
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
30393
30103
|
const callback = getEffectCallback(node);
|
|
30394
30104
|
if (!callback) return;
|
|
30395
30105
|
const analysisFunctions = collectEffectAnalysisFunctions(callback, context);
|
|
@@ -34599,6 +34309,19 @@ const DATA_SINK_METHOD_NAMES = new Set([
|
|
|
34599
34309
|
"deserialize"
|
|
34600
34310
|
]);
|
|
34601
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
|
|
34602
34325
|
//#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
|
|
34603
34326
|
const isUseStateIdentifier = (identifier) => {
|
|
34604
34327
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
@@ -35224,30 +34947,6 @@ const guardsRenderShape = (comparison) => {
|
|
|
35224
34947
|
}
|
|
35225
34948
|
return false;
|
|
35226
34949
|
};
|
|
35227
|
-
const isPropsChildrenLength = (node, scopes) => {
|
|
35228
|
-
const unwrappedNode = stripParenExpression(node);
|
|
35229
|
-
return isNodeOfType(unwrappedNode, "MemberExpression") && !unwrappedNode.computed && isNodeOfType(unwrappedNode.property, "Identifier") && unwrappedNode.property.name === "length" && resolvesToPropsChildren(stripParenExpression(unwrappedNode.object), scopes);
|
|
35230
|
-
};
|
|
35231
|
-
const isLargeTextLengthComparison = (node, scopes) => {
|
|
35232
|
-
const unwrappedNode = stripParenExpression(node);
|
|
35233
|
-
if (!isNodeOfType(unwrappedNode, "BinaryExpression")) return false;
|
|
35234
|
-
const leftIsLength = isPropsChildrenLength(unwrappedNode.left, scopes);
|
|
35235
|
-
const rightIsLength = isPropsChildrenLength(unwrappedNode.right, scopes);
|
|
35236
|
-
const thresholdNode = leftIsLength ? unwrappedNode.right : unwrappedNode.left;
|
|
35237
|
-
if (!leftIsLength && !rightIsLength || !isNodeOfType(thresholdNode, "Literal")) return false;
|
|
35238
|
-
if (typeof thresholdNode.value !== "number" || thresholdNode.value < 1e3) return false;
|
|
35239
|
-
return leftIsLength ? unwrappedNode.operator === ">" || unwrappedNode.operator === ">=" : unwrappedNode.operator === "<" || unwrappedNode.operator === "<=";
|
|
35240
|
-
};
|
|
35241
|
-
const isLargeStringOptimizationGuard = (comparison, scopes) => {
|
|
35242
|
-
let current = findTransparentExpressionRoot(comparison);
|
|
35243
|
-
while (current.parent) {
|
|
35244
|
-
const parent = current.parent;
|
|
35245
|
-
if (!isNodeOfType(parent, "LogicalExpression") || parent.operator !== "&&") return false;
|
|
35246
|
-
if (isLargeTextLengthComparison(parent.left === current ? parent.right : parent.left, scopes)) return true;
|
|
35247
|
-
current = findTransparentExpressionRoot(parent);
|
|
35248
|
-
}
|
|
35249
|
-
return false;
|
|
35250
|
-
};
|
|
35251
34950
|
const noPolymorphicChildren = defineRule({
|
|
35252
34951
|
id: "no-polymorphic-children",
|
|
35253
34952
|
title: "Children type checked at runtime",
|
|
@@ -35261,7 +34960,6 @@ const noPolymorphicChildren = defineRule({
|
|
|
35261
34960
|
const isStringLiteral = (operand) => isNodeOfType(operand, "Literal") && operand.value === "string";
|
|
35262
34961
|
if (!isStringLiteral(node.left) && !isStringLiteral(node.right)) return;
|
|
35263
34962
|
if (!guardsRenderShape(node)) return;
|
|
35264
|
-
if (isLargeStringOptimizationGuard(node, context.scopes)) return;
|
|
35265
34963
|
context.report({
|
|
35266
34964
|
node,
|
|
35267
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."
|
|
@@ -35479,27 +35177,6 @@ const hasPreviousValueDep = (effectNode, depElements) => {
|
|
|
35479
35177
|
}
|
|
35480
35178
|
return false;
|
|
35481
35179
|
};
|
|
35482
|
-
const isStateLikeDependency = (analysis, element, isPropName) => {
|
|
35483
|
-
if (!isNodeOfType(element, "Identifier") || isPropName(element.name)) return false;
|
|
35484
|
-
if (!analysis) return true;
|
|
35485
|
-
const reference = getRef(analysis, element);
|
|
35486
|
-
if (!reference) return true;
|
|
35487
|
-
const upstreamReferences = getUpstreamRefs(analysis, reference);
|
|
35488
|
-
if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
|
|
35489
|
-
return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
|
|
35490
|
-
};
|
|
35491
|
-
const getRefHeldPropCallbackName = (callExpression, isPropName) => {
|
|
35492
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
35493
|
-
if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "current") return null;
|
|
35494
|
-
const receiver = stripParenExpression(callee.object);
|
|
35495
|
-
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
35496
|
-
const binding = findVariableInitializer(callExpression, receiver.name);
|
|
35497
|
-
if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
|
|
35498
|
-
if (getCalleeName$2(binding.initializer) !== "useRef") return null;
|
|
35499
|
-
const callbackArgument = binding.initializer.arguments?.[0];
|
|
35500
|
-
if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
|
|
35501
|
-
return isPropName(callbackArgument.name) ? callbackArgument.name : null;
|
|
35502
|
-
};
|
|
35503
35180
|
const noPropCallbackInEffect = defineRule({
|
|
35504
35181
|
id: "no-prop-callback-in-effect",
|
|
35505
35182
|
title: "Parent kept in sync with a callback effect",
|
|
@@ -35516,11 +35193,11 @@ const noPropCallbackInEffect = defineRule({
|
|
|
35516
35193
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
35517
35194
|
const depsNode = node.arguments[1];
|
|
35518
35195
|
if (!isNodeOfType(depsNode, "ArrayExpression") || !depsNode.elements?.length) return;
|
|
35519
|
-
const
|
|
35520
|
-
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));
|
|
35521
35197
|
if (stateLikeDeps.length === 0) return;
|
|
35522
35198
|
if (isRefLatchGuardedEffect(callback.body)) return;
|
|
35523
35199
|
if (hasPreviousValueDep(node, depsNode.elements ?? [])) return;
|
|
35200
|
+
const analysis = getProgramAnalysis(node);
|
|
35524
35201
|
if (analysis) {
|
|
35525
35202
|
const stateLikeDepRefs = [];
|
|
35526
35203
|
for (const element of stateLikeDeps) {
|
|
@@ -35532,9 +35209,9 @@ const noPropCallbackInEffect = defineRule({
|
|
|
35532
35209
|
const reportedNodes = /* @__PURE__ */ new Set();
|
|
35533
35210
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
35534
35211
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
35535
|
-
|
|
35536
|
-
const calleeName =
|
|
35537
|
-
if (!calleeName) return;
|
|
35212
|
+
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
35213
|
+
const calleeName = child.callee.name;
|
|
35214
|
+
if (!propStackTracker.isPropName(calleeName)) return;
|
|
35538
35215
|
if (!isResultDiscardedCall(child)) return;
|
|
35539
35216
|
if (reportedNodes.has(child)) return;
|
|
35540
35217
|
reportedNodes.add(child);
|
|
@@ -43375,6 +43052,15 @@ const isStableHookWrapperArgument = (node) => {
|
|
|
43375
43052
|
const parent = node.parent;
|
|
43376
43053
|
return isNodeOfType(parent, "CallExpression") && isNodeOfType(parent.callee, "Identifier") && STABLE_HOOK_WRAPPERS.has(parent.callee.name) && Boolean(parent.arguments?.some((argument) => argument === node));
|
|
43377
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
|
+
};
|
|
43378
43064
|
const queryStableQueryClient = defineRule({
|
|
43379
43065
|
id: "query-stable-query-client",
|
|
43380
43066
|
title: "Unstable QueryClient in component",
|