oxlint-plugin-react-doctor 0.7.9-dev.842a55f → 0.7.9-dev.95f9937
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.d.ts +0 -46
- package/dist/index.js +249 -2222
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1237,13 +1237,12 @@ const stripParenExpression = (node) => {
|
|
|
1237
1237
|
};
|
|
1238
1238
|
//#endregion
|
|
1239
1239
|
//#region src/plugin/utils/resolve-const-identifier-alias.ts
|
|
1240
|
-
const resolveConstIdentifierAlias = (identifier, scopes
|
|
1240
|
+
const resolveConstIdentifierAlias = (identifier, scopes) => {
|
|
1241
1241
|
if (!isNodeOfType(identifier, "Identifier") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
|
|
1242
1242
|
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
1243
1243
|
let symbol = scopes.symbolFor(identifier);
|
|
1244
1244
|
while (symbol?.kind === "const") {
|
|
1245
|
-
if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return null;
|
|
1246
|
-
if (symbol.declarationNode.id !== symbol.bindingIdentifier) return allowPatternBinding ? symbol : null;
|
|
1245
|
+
if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
1247
1246
|
visitedSymbolIds.add(symbol.id);
|
|
1248
1247
|
const initializer = stripParenExpression(symbol.initializer);
|
|
1249
1248
|
if (!isNodeOfType(initializer, "Identifier")) return symbol;
|
|
@@ -6737,28 +6736,6 @@ const asyncParallel = defineRule({
|
|
|
6737
6736
|
}
|
|
6738
6737
|
});
|
|
6739
6738
|
//#endregion
|
|
6740
|
-
//#region src/plugin/utils/collect-function-return-statements.ts
|
|
6741
|
-
const collectFunctionReturnStatements = (functionNode) => {
|
|
6742
|
-
if (!isFunctionLike$1(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return [];
|
|
6743
|
-
const returnStatements = [];
|
|
6744
|
-
walkAst(functionNode.body, (node) => {
|
|
6745
|
-
if (node !== functionNode.body && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
6746
|
-
if (isNodeOfType(node, "ReturnStatement")) returnStatements.push(node);
|
|
6747
|
-
});
|
|
6748
|
-
return returnStatements;
|
|
6749
|
-
};
|
|
6750
|
-
//#endregion
|
|
6751
|
-
//#region src/plugin/utils/is-nullish-expression.ts
|
|
6752
|
-
const isNullishExpression = (expression) => isNodeOfType(expression, "Literal") && expression.value === null || isNodeOfType(expression, "Identifier") && expression.name === "undefined" || isNodeOfType(expression, "UnaryExpression") && expression.operator === "void";
|
|
6753
|
-
//#endregion
|
|
6754
|
-
//#region src/plugin/utils/strip-this-parameter.ts
|
|
6755
|
-
const stripThisParameter = (parameters) => {
|
|
6756
|
-
const firstParameter = parameters[0];
|
|
6757
|
-
if (!firstParameter) return parameters;
|
|
6758
|
-
if (isNodeOfType(firstParameter, "Identifier") && firstParameter.name === "this") return parameters.slice(1);
|
|
6759
|
-
return parameters;
|
|
6760
|
-
};
|
|
6761
|
-
//#endregion
|
|
6762
6739
|
//#region src/plugin/rules/security/auth-token-in-web-storage.ts
|
|
6763
6740
|
const MESSAGE$60 = "Storing an auth token in `localStorage`/`sessionStorage` exposes it to any XSS on the page: JavaScript can read web storage and exfiltrate the token. Keep tokens in an `HttpOnly`, `Secure`, `SameSite` cookie instead.";
|
|
6764
6741
|
const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
|
|
@@ -6770,8 +6747,12 @@ const STORAGE_GLOBALS = new Set([
|
|
|
6770
6747
|
const SENSITIVE_KEY_PATTERN = /token|jwt|secret|password|passwd|credential|api[-_]?key|bearer|private[-_]?key/i;
|
|
6771
6748
|
const NON_AUTH_TOKEN_PATTERN = /csrf|xsrf|device|fcm|apns|push|design|tokeniz|syntax|css|theme|color/i;
|
|
6772
6749
|
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;
|
|
6750
|
+
const PRODUCT_API_KEY_RECORDS_PATTERN = /(?:^|[._:-])(?:created|saved|integration|mailing)[-_]?api[-_]?keys$/i;
|
|
6751
|
+
const PRODUCT_API_KEY_COLLECTION_PATTERN = /[._:-](?:created|generated|saved)[-_]?api[-_]?keys$/i;
|
|
6773
6752
|
const isAuthCredentialKey = (key) => {
|
|
6753
|
+
if (PRODUCT_API_KEY_RECORDS_PATTERN.test(key)) return false;
|
|
6774
6754
|
if (!SENSITIVE_KEY_PATTERN.test(key)) return false;
|
|
6755
|
+
if (PRODUCT_API_KEY_COLLECTION_PATTERN.test(key)) return false;
|
|
6775
6756
|
if (NON_AUTH_TOKEN_PATTERN.test(key) && !STRONG_AUTH_KEY_PATTERN.test(key)) return false;
|
|
6776
6757
|
return true;
|
|
6777
6758
|
};
|
|
@@ -6780,63 +6761,11 @@ const isDirectWebStorageObject = (node) => {
|
|
|
6780
6761
|
if (isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.object, "Identifier") && STORAGE_GLOBALS.has(node.object.name) && isNodeOfType(node.property, "Identifier")) return STORAGE_NAMES.has(node.property.name);
|
|
6781
6762
|
return false;
|
|
6782
6763
|
};
|
|
6783
|
-
const
|
|
6784
|
-
if (
|
|
6785
|
-
|
|
6786
|
-
const binding = findVariableInitializer(
|
|
6787
|
-
|
|
6788
|
-
if (isNodeOfType(binding.initializer, "FunctionDeclaration")) return binding.initializer;
|
|
6789
|
-
const declarator = binding.bindingIdentifier.parent;
|
|
6790
|
-
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return null;
|
|
6791
|
-
const declaration = declarator.parent;
|
|
6792
|
-
if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) return null;
|
|
6793
|
-
if (declaration.kind !== "const") return null;
|
|
6794
|
-
const initializer = stripParenExpression(binding.initializer);
|
|
6795
|
-
if (!isNodeOfType(initializer, "Identifier")) return initializer;
|
|
6796
|
-
return immutableInitializer(initializer, visitedIdentifiers) ?? initializer;
|
|
6797
|
-
};
|
|
6798
|
-
const isWebStorageFactoryResult = (node, visitedNodes) => {
|
|
6799
|
-
const expression = stripParenExpression(node);
|
|
6800
|
-
if (isWebStorageObject(expression, new Set(visitedNodes))) return true;
|
|
6801
|
-
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
6802
|
-
const consequent = stripParenExpression(expression.consequent);
|
|
6803
|
-
const alternate = stripParenExpression(expression.alternate);
|
|
6804
|
-
return (isNullishExpression(consequent) || isWebStorageFactoryResult(consequent, visitedNodes)) && (isNullishExpression(alternate) || isWebStorageFactoryResult(alternate, visitedNodes)) && (!isNullishExpression(consequent) || !isNullishExpression(alternate));
|
|
6805
|
-
}
|
|
6806
|
-
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
6807
|
-
if (expression.operator === "&&") return isWebStorageFactoryResult(expression.right, visitedNodes);
|
|
6808
|
-
const left = stripParenExpression(expression.left);
|
|
6809
|
-
const right = stripParenExpression(expression.right);
|
|
6810
|
-
const isLeftStorage = isWebStorageFactoryResult(left, visitedNodes);
|
|
6811
|
-
const isRightStorage = isWebStorageFactoryResult(right, visitedNodes);
|
|
6812
|
-
return (isLeftStorage || isNullishExpression(left)) && (isRightStorage || isNullishExpression(right)) && (isLeftStorage || isRightStorage);
|
|
6813
|
-
}
|
|
6814
|
-
return false;
|
|
6815
|
-
};
|
|
6816
|
-
const isWebStorageObject = (node, visitedNodes = /* @__PURE__ */ new Set()) => {
|
|
6817
|
-
const expression = stripParenExpression(node);
|
|
6818
|
-
if (visitedNodes.has(expression)) return false;
|
|
6819
|
-
visitedNodes.add(expression);
|
|
6820
|
-
if (isDirectWebStorageObject(expression)) return true;
|
|
6821
|
-
if (isNodeOfType(expression, "Identifier")) {
|
|
6822
|
-
const initializer = immutableInitializer(expression);
|
|
6823
|
-
return initializer ? isWebStorageObject(initializer, new Set(visitedNodes)) : false;
|
|
6824
|
-
}
|
|
6825
|
-
if (!isNodeOfType(expression, "CallExpression")) return false;
|
|
6826
|
-
const callee = stripParenExpression(expression.callee);
|
|
6827
|
-
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
6828
|
-
const factory = immutableInitializer(callee);
|
|
6829
|
-
if (!isFunctionLike$1(factory)) return false;
|
|
6830
|
-
if (isNodeOfType(factory, "ArrowFunctionExpression") && !isNodeOfType(factory.body, "BlockStatement")) return isWebStorageFactoryResult(factory.body, visitedNodes);
|
|
6831
|
-
let didReturnWebStorage = false;
|
|
6832
|
-
for (const returnStatement of collectFunctionReturnStatements(factory)) {
|
|
6833
|
-
if (!returnStatement.argument) continue;
|
|
6834
|
-
const strippedReturn = stripParenExpression(returnStatement.argument);
|
|
6835
|
-
if (isNullishExpression(strippedReturn)) continue;
|
|
6836
|
-
if (!isWebStorageFactoryResult(strippedReturn, visitedNodes)) return false;
|
|
6837
|
-
didReturnWebStorage = true;
|
|
6838
|
-
}
|
|
6839
|
-
return didReturnWebStorage;
|
|
6764
|
+
const isWebStorageObject = (node) => {
|
|
6765
|
+
if (isDirectWebStorageObject(node)) return true;
|
|
6766
|
+
if (!isNodeOfType(node, "Identifier")) return false;
|
|
6767
|
+
const binding = findVariableInitializer(node, node.name);
|
|
6768
|
+
return binding?.initializer ? isDirectWebStorageObject(binding.initializer) : false;
|
|
6840
6769
|
};
|
|
6841
6770
|
const resolveStaticKeyString = (node) => {
|
|
6842
6771
|
if (isNodeOfType(node, "Literal") && typeof node.value === "string") return node.value;
|
|
@@ -6856,56 +6785,6 @@ const staticMemberName = (member) => {
|
|
|
6856
6785
|
if (member.computed && isNodeOfType(member.property, "Literal") && typeof member.property.value === "string") return member.property.value;
|
|
6857
6786
|
return null;
|
|
6858
6787
|
};
|
|
6859
|
-
const parameterIndex = (expression, parameterSymbolIds, scopes, canUnwrapSerialization, visitedNodes = /* @__PURE__ */ new Set()) => {
|
|
6860
|
-
const strippedExpression = stripParenExpression(expression);
|
|
6861
|
-
if (visitedNodes.has(strippedExpression)) return null;
|
|
6862
|
-
visitedNodes.add(strippedExpression);
|
|
6863
|
-
if (isNodeOfType(strippedExpression, "Identifier")) {
|
|
6864
|
-
const directSymbolId = scopes.symbolFor(strippedExpression)?.id;
|
|
6865
|
-
const directIndex = directSymbolId === void 0 ? -1 : parameterSymbolIds.indexOf(directSymbolId);
|
|
6866
|
-
if (directIndex !== -1) return directIndex;
|
|
6867
|
-
const initializer = immutableInitializer(strippedExpression);
|
|
6868
|
-
return initializer ? parameterIndex(initializer, parameterSymbolIds, scopes, canUnwrapSerialization, visitedNodes) : null;
|
|
6869
|
-
}
|
|
6870
|
-
if (canUnwrapSerialization && isNodeOfType(strippedExpression, "CallExpression") && isNodeOfType(strippedExpression.callee, "MemberExpression") && !strippedExpression.callee.computed && isNodeOfType(strippedExpression.callee.object, "Identifier") && strippedExpression.callee.object.name === "JSON" && isNodeOfType(strippedExpression.callee.property, "Identifier") && strippedExpression.callee.property.name === "stringify") {
|
|
6871
|
-
const serializedArgument = strippedExpression.arguments[0];
|
|
6872
|
-
return serializedArgument ? parameterIndex(serializedArgument, parameterSymbolIds, scopes, true, visitedNodes) : null;
|
|
6873
|
-
}
|
|
6874
|
-
return null;
|
|
6875
|
-
};
|
|
6876
|
-
const storageHelperSinkCache = /* @__PURE__ */ new WeakMap();
|
|
6877
|
-
const findStorageHelperSinks = (functionNode, scopes) => {
|
|
6878
|
-
const cachedSinks = storageHelperSinkCache.get(functionNode);
|
|
6879
|
-
if (cachedSinks) return cachedSinks;
|
|
6880
|
-
if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) {
|
|
6881
|
-
storageHelperSinkCache.set(functionNode, []);
|
|
6882
|
-
return [];
|
|
6883
|
-
}
|
|
6884
|
-
const parameterSymbolIds = stripThisParameter(functionNode.params).map((parameter) => {
|
|
6885
|
-
const strippedParameter = stripParenExpression(parameter);
|
|
6886
|
-
const identifier = isNodeOfType(strippedParameter, "Identifier") ? strippedParameter : isNodeOfType(strippedParameter, "AssignmentPattern") && isNodeOfType(strippedParameter.left, "Identifier") ? strippedParameter.left : null;
|
|
6887
|
-
return identifier ? scopes.symbolFor(identifier)?.id ?? null : null;
|
|
6888
|
-
});
|
|
6889
|
-
const helperSinks = [];
|
|
6890
|
-
walkAst(functionNode.body, (child) => {
|
|
6891
|
-
if (child !== functionNode.body && isFunctionLike$1(child)) return false;
|
|
6892
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
6893
|
-
const callee = stripParenExpression(child.callee);
|
|
6894
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "setItem" || !isWebStorageObject(callee.object)) return;
|
|
6895
|
-
const keyExpression = child.arguments[0];
|
|
6896
|
-
const valueExpression = child.arguments[1];
|
|
6897
|
-
if (!keyExpression || !valueExpression) return;
|
|
6898
|
-
const keyParameterIndex = parameterIndex(keyExpression, parameterSymbolIds, scopes, false);
|
|
6899
|
-
const valueParameterIndex = parameterIndex(valueExpression, parameterSymbolIds, scopes, true);
|
|
6900
|
-
if (keyParameterIndex === null || valueParameterIndex === null) return;
|
|
6901
|
-
helperSinks.push({
|
|
6902
|
-
keyParameterIndex,
|
|
6903
|
-
valueParameterIndex
|
|
6904
|
-
});
|
|
6905
|
-
});
|
|
6906
|
-
storageHelperSinkCache.set(functionNode, helperSinks);
|
|
6907
|
-
return helperSinks;
|
|
6908
|
-
};
|
|
6909
6788
|
const authTokenInWebStorage = defineRule({
|
|
6910
6789
|
id: "auth-token-in-web-storage",
|
|
6911
6790
|
title: "Auth token in web storage",
|
|
@@ -6913,23 +6792,14 @@ const authTokenInWebStorage = defineRule({
|
|
|
6913
6792
|
recommendation: "Don't persist auth tokens (JWTs, access/refresh tokens, secrets) in `localStorage`/`sessionStorage`; they're readable by any XSS. Use an `HttpOnly` cookie set by the server.",
|
|
6914
6793
|
create: skipNonProductionFiles((context) => ({
|
|
6915
6794
|
CallExpression(node) {
|
|
6916
|
-
const callee =
|
|
6917
|
-
|
|
6918
|
-
if (
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
for (const helperSink of helperSinks) {
|
|
6925
|
-
const keyArgument = node.arguments[helperSink.keyParameterIndex];
|
|
6926
|
-
if (keyArgument && node.arguments[helperSink.valueParameterIndex]) keyArguments.push(keyArgument);
|
|
6927
|
-
}
|
|
6928
|
-
}
|
|
6929
|
-
if (!keyArguments.some((keyArgument) => {
|
|
6930
|
-
const keyString = resolveStaticKeyString(keyArgument);
|
|
6931
|
-
return keyString !== null && isAuthCredentialKey(keyString);
|
|
6932
|
-
})) return;
|
|
6795
|
+
const callee = node.callee;
|
|
6796
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
|
|
6797
|
+
if (!isNodeOfType(callee.property, "Identifier") || callee.property.name !== "setItem") return;
|
|
6798
|
+
if (!isWebStorageObject(stripParenExpression(callee.object))) return;
|
|
6799
|
+
const keyArgument = node.arguments?.[0];
|
|
6800
|
+
if (!keyArgument) return;
|
|
6801
|
+
const keyString = resolveStaticKeyString(keyArgument);
|
|
6802
|
+
if (keyString === null || !isAuthCredentialKey(keyString)) return;
|
|
6933
6803
|
context.report({
|
|
6934
6804
|
node,
|
|
6935
6805
|
message: MESSAGE$60
|
|
@@ -7177,6 +7047,9 @@ const isCreateElementCall = (node) => {
|
|
|
7177
7047
|
return false;
|
|
7178
7048
|
};
|
|
7179
7049
|
//#endregion
|
|
7050
|
+
//#region src/plugin/utils/is-nullish-expression.ts
|
|
7051
|
+
const isNullishExpression = (expression) => isNodeOfType(expression, "Literal") && expression.value === null || isNodeOfType(expression, "Identifier") && expression.name === "undefined" || isNodeOfType(expression, "UnaryExpression") && expression.operator === "void";
|
|
7052
|
+
//#endregion
|
|
7180
7053
|
//#region src/plugin/rules/react-builtins/button-has-type.ts
|
|
7181
7054
|
const MISSING_MESSAGE$2 = "Your users can submit the form by accident because a `<button>` with no `type` defaults to submit.";
|
|
7182
7055
|
const INVALID_MESSAGE = "This button has an invalid `type`, so the browser may treat it like a submit button.";
|
|
@@ -7638,7 +7511,6 @@ const areExpressionsStructurallyEqual = (a, b) => {
|
|
|
7638
7511
|
if (a.type !== b.type) return false;
|
|
7639
7512
|
if (isNodeOfType(a, "ThisExpression")) return true;
|
|
7640
7513
|
if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
|
|
7641
|
-
if (isNodeOfType(a, "PrivateIdentifier") && isNodeOfType(b, "PrivateIdentifier")) return a.name === b.name;
|
|
7642
7514
|
if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
|
|
7643
7515
|
if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
|
|
7644
7516
|
if (a.computed !== b.computed) return false;
|
|
@@ -8694,6 +8566,17 @@ const createMethodMutationAnalysis = (context) => {
|
|
|
8694
8566
|
//#region src/plugin/utils/is-member-property.ts
|
|
8695
8567
|
const isMemberProperty = (node, propertyName) => Boolean(node && isNodeOfType(node, "MemberExpression") && isNodeOfType(node.property, "Identifier") && node.property.name === propertyName);
|
|
8696
8568
|
//#endregion
|
|
8569
|
+
//#region src/plugin/utils/collect-function-return-statements.ts
|
|
8570
|
+
const collectFunctionReturnStatements = (functionNode) => {
|
|
8571
|
+
if (!isFunctionLike$1(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return [];
|
|
8572
|
+
const returnStatements = [];
|
|
8573
|
+
walkAst(functionNode.body, (node) => {
|
|
8574
|
+
if (node !== functionNode.body && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
8575
|
+
if (isNodeOfType(node, "ReturnStatement")) returnStatements.push(node);
|
|
8576
|
+
});
|
|
8577
|
+
return returnStatements;
|
|
8578
|
+
};
|
|
8579
|
+
//#endregion
|
|
8697
8580
|
//#region src/plugin/utils/statement-always-exits.ts
|
|
8698
8581
|
const statementAlwaysExits = (statement) => {
|
|
8699
8582
|
if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement")) return true;
|
|
@@ -8960,7 +8843,7 @@ const isReactNamespaceImport = (identifier, scopes) => {
|
|
|
8960
8843
|
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
8961
8844
|
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
8962
8845
|
};
|
|
8963
|
-
const isReactNamespaceReceiver
|
|
8846
|
+
const isReactNamespaceReceiver = (receiver, scopes, options) => {
|
|
8964
8847
|
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
8965
8848
|
if (isReactNamespaceImport(receiver, scopes)) return true;
|
|
8966
8849
|
return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
|
|
@@ -8973,7 +8856,7 @@ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) =>
|
|
|
8973
8856
|
for (const property of pattern.properties) {
|
|
8974
8857
|
if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
|
|
8975
8858
|
const propertyName = getStaticPropertyKeyName(property);
|
|
8976
|
-
return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver
|
|
8859
|
+
return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver(stripParenExpression(symbol.initializer), scopes, options));
|
|
8977
8860
|
}
|
|
8978
8861
|
return false;
|
|
8979
8862
|
};
|
|
@@ -8997,7 +8880,7 @@ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds
|
|
|
8997
8880
|
return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
|
|
8998
8881
|
}
|
|
8999
8882
|
if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
|
|
9000
|
-
return isReactNamespaceReceiver
|
|
8883
|
+
return isReactNamespaceReceiver(stripParenExpression(callee.object), scopes, options);
|
|
9001
8884
|
};
|
|
9002
8885
|
//#endregion
|
|
9003
8886
|
//#region src/plugin/utils/is-proven-browser-api-receiver.ts
|
|
@@ -13653,7 +13536,7 @@ const getPromiseChainCallForCallback = (candidate) => {
|
|
|
13653
13536
|
if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
|
|
13654
13537
|
return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
|
|
13655
13538
|
};
|
|
13656
|
-
const
|
|
13539
|
+
const collectEffectInvokedFunctions = (effectCallback) => {
|
|
13657
13540
|
const invokedFunctions = new Set([effectCallback]);
|
|
13658
13541
|
const localFunctionBindings = /* @__PURE__ */ new Map();
|
|
13659
13542
|
const calledBindingNames = /* @__PURE__ */ new Set();
|
|
@@ -13687,14 +13570,12 @@ const collectInvokedFunctions = (effectCallback, includePromiseCallbacks) => {
|
|
|
13687
13570
|
calledBindingNames.add(callee.name);
|
|
13688
13571
|
return;
|
|
13689
13572
|
}
|
|
13690
|
-
if (
|
|
13573
|
+
if (isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
|
|
13691
13574
|
});
|
|
13692
13575
|
for (const calledName of calledBindingNames) enqueue(localFunctionBindings.get(calledName));
|
|
13693
13576
|
}
|
|
13694
13577
|
return invokedFunctions;
|
|
13695
13578
|
};
|
|
13696
|
-
const collectEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, true);
|
|
13697
|
-
const collectSynchronouslyEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, false);
|
|
13698
13579
|
//#endregion
|
|
13699
13580
|
//#region src/plugin/utils/is-react-hook-name.ts
|
|
13700
13581
|
const isReactHookName = (name) => {
|
|
@@ -13853,19 +13734,15 @@ const resolveReactRefSymbol = (memberExpression, scopes) => {
|
|
|
13853
13734
|
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
13854
13735
|
return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
|
|
13855
13736
|
};
|
|
13856
|
-
const
|
|
13737
|
+
const hasReactRefCurrentOrigin = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
13857
13738
|
const expression = stripParenExpression(node);
|
|
13858
|
-
|
|
13859
|
-
if (
|
|
13860
|
-
|
|
13861
|
-
|
|
13862
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return null;
|
|
13863
|
-
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
13864
|
-
if (!initializer) return null;
|
|
13739
|
+
if (resolveReactRefSymbol(expression, scopes)) return true;
|
|
13740
|
+
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
13741
|
+
const symbol = resolveConstIdentifierAlias(expression, scopes);
|
|
13742
|
+
if (!symbol?.initializer || visitedSymbolIds.has(symbol.id)) return false;
|
|
13865
13743
|
visitedSymbolIds.add(symbol.id);
|
|
13866
|
-
return
|
|
13744
|
+
return hasReactRefCurrentOrigin(symbol.initializer, scopes, visitedSymbolIds);
|
|
13867
13745
|
};
|
|
13868
|
-
const hasReactRefCurrentOrigin = (node, scopes) => resolveReactRefCurrentOriginSymbol(node, scopes) !== null;
|
|
13869
13746
|
//#endregion
|
|
13870
13747
|
//#region src/plugin/utils/walk-inside-statement-blocks.ts
|
|
13871
13748
|
const walkInsideStatementBlocks = (node, visitor) => {
|
|
@@ -14347,39 +14224,6 @@ const findContainingCollectionKey = (resourceNode, context) => {
|
|
|
14347
14224
|
}
|
|
14348
14225
|
return null;
|
|
14349
14226
|
};
|
|
14350
|
-
const findPushedResourceCollectionKey = (usage, context) => {
|
|
14351
|
-
if (!isNodeOfType(usage.node, "CallExpression")) return null;
|
|
14352
|
-
const registrationCallee = stripParenExpression(usage.node.callee);
|
|
14353
|
-
if (!isNodeOfType(registrationCallee, "MemberExpression") || registrationCallee.computed) return null;
|
|
14354
|
-
const resourceIdentifier = stripParenExpression(registrationCallee.object);
|
|
14355
|
-
if (!isPrivatePlainConstIdentifier(resourceIdentifier, context)) return null;
|
|
14356
|
-
const resourceSymbol = context.scopes.symbolFor(resourceIdentifier);
|
|
14357
|
-
if (!resourceSymbol) return null;
|
|
14358
|
-
const pushCalls = resourceSymbol.references.flatMap((reference) => {
|
|
14359
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
14360
|
-
const callNode = referenceRoot.parent;
|
|
14361
|
-
if (!isNodeOfType(callNode, "CallExpression") || !callNode.arguments?.some((argument) => argument === referenceRoot)) return [];
|
|
14362
|
-
const pushCallee = stripParenExpression(callNode.callee);
|
|
14363
|
-
return isNodeOfType(pushCallee, "MemberExpression") && !pushCallee.computed && isNodeOfType(pushCallee.object, "Identifier") && isNodeOfType(pushCallee.property, "Identifier") && pushCallee.property.name === "push" ? [callNode] : [];
|
|
14364
|
-
});
|
|
14365
|
-
if (pushCalls.length !== 1) return null;
|
|
14366
|
-
const pushCall = pushCalls[0];
|
|
14367
|
-
if (findEnclosingFunction$1(pushCall) !== findEnclosingFunction$1(usage.node) || !doMatchingNodesCoverEveryPathAfterUsage(usage.node, [pushCall], context)) return null;
|
|
14368
|
-
const pushCallee = stripParenExpression(pushCall.callee);
|
|
14369
|
-
if (!isNodeOfType(pushCallee, "MemberExpression") || !isNodeOfType(pushCallee.object, "Identifier") || !isPrivatePlainConstIdentifier(pushCallee.object, context)) return null;
|
|
14370
|
-
const collectionSymbol = context.scopes.symbolFor(pushCallee.object);
|
|
14371
|
-
const collectionInitializer = collectionSymbol?.initializer ? stripParenExpression(collectionSymbol.initializer) : null;
|
|
14372
|
-
if (!collectionSymbol || !isNodeOfType(collectionInitializer, "ArrayExpression") || (collectionInitializer.elements?.length ?? 0) !== 0 || findEnclosingFunction$1(collectionSymbol.declarationNode) !== findEnclosingFunction$1(usage.node)) return null;
|
|
14373
|
-
return collectionSymbol.references.every((reference) => {
|
|
14374
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
14375
|
-
const forOfStatement = referenceRoot.parent;
|
|
14376
|
-
if (isNodeOfType(forOfStatement, "ForOfStatement") && forOfStatement.right === referenceRoot && forOfStatement.await !== true) return true;
|
|
14377
|
-
const memberNode = referenceRoot.parent;
|
|
14378
|
-
const callNode = memberNode?.parent;
|
|
14379
|
-
if (!isNodeOfType(memberNode, "MemberExpression") || memberNode.object !== referenceRoot || memberNode.computed || !isNodeOfType(memberNode.property, "Identifier") || !isNodeOfType(callNode, "CallExpression") || callNode.callee !== memberNode) return false;
|
|
14380
|
-
return memberNode.property.name === "forEach" || memberNode.property.name === "push";
|
|
14381
|
-
}) ? resolveExpressionKey(pushCallee.object, context) : null;
|
|
14382
|
-
};
|
|
14383
14227
|
const isWithinAssignmentTarget = (identifier) => {
|
|
14384
14228
|
let currentNode = identifier;
|
|
14385
14229
|
let parentNode = currentNode.parent;
|
|
@@ -14438,14 +14282,6 @@ const isSynchronousIteratorCallback = (functionNode) => {
|
|
|
14438
14282
|
if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments?.[1] === functionNode;
|
|
14439
14283
|
return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments?.[0] === functionNode;
|
|
14440
14284
|
};
|
|
14441
|
-
const findEnclosingForEachCall = (node) => {
|
|
14442
|
-
const callbackNode = findEnclosingFunction$1(node);
|
|
14443
|
-
if (!callbackNode) return null;
|
|
14444
|
-
const callNode = callbackNode.parent;
|
|
14445
|
-
if (!isNodeOfType(callNode, "CallExpression") || callNode.arguments?.[0] !== callbackNode) return null;
|
|
14446
|
-
const callee = stripParenExpression(callNode.callee);
|
|
14447
|
-
return isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "forEach" ? callNode : null;
|
|
14448
|
-
};
|
|
14449
14285
|
const findDirectCallForReference = (identifier) => {
|
|
14450
14286
|
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
14451
14287
|
const callNode = expressionRoot.parent;
|
|
@@ -14460,7 +14296,7 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
|
|
|
14460
14296
|
const callNode = findDirectCallForReference(reference.identifier);
|
|
14461
14297
|
return callNode ? [callNode] : [];
|
|
14462
14298
|
});
|
|
14463
|
-
if (invocationCalls.length !== 1
|
|
14299
|
+
if (invocationCalls.length !== 1) return null;
|
|
14464
14300
|
const invocationCall = invocationCalls[0];
|
|
14465
14301
|
return findEnclosingFunction$1(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
|
|
14466
14302
|
};
|
|
@@ -14494,15 +14330,7 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
|
|
|
14494
14330
|
if (cleanupChild !== cleanupFunction.body && isFunctionLike$1(cleanupChild) && !isSynchronousIteratorCallback(cleanupChild)) return false;
|
|
14495
14331
|
const cleanupCall = isNodeOfType(cleanupChild, "ChainExpression") ? cleanupChild.expression : cleanupChild;
|
|
14496
14332
|
if (doesReleaseCallMatchUsage(cleanupChild, usage, context)) {
|
|
14497
|
-
const
|
|
14498
|
-
const cleanupCallee = isNodeOfType(cleanupCall, "CallExpression") ? stripParenExpression(cleanupCall.callee) : null;
|
|
14499
|
-
const cleanupReceiverForOfStatement = isNodeOfType(cleanupCallee, "MemberExpression") ? findForOfStatementForIteratorExpression(cleanupCallee.object, context) : null;
|
|
14500
|
-
const cleanupReceiverCollectionKey = cleanupReceiverForOfStatement ? resolveExpressionKey(cleanupReceiverForOfStatement.right, context) : isNodeOfType(cleanupCallee, "MemberExpression") ? resolveIteratorCollectionKey(cleanupCallee.object, context) : null;
|
|
14501
|
-
if (cleanupReceiverCollectionKey !== null && findEnclosingFunction$1(cleanupChild) !== cleanupFunction) {
|
|
14502
|
-
if (cleanupForEachCall && findPushedResourceCollectionKey(usage, context) === cleanupReceiverCollectionKey) matchingLoopOrHelperAnchors.push(cleanupForEachCall);
|
|
14503
|
-
return;
|
|
14504
|
-
}
|
|
14505
|
-
const cleanupForOfStatement = findForOfStatementForIteratorExpression(isNodeOfType(cleanupCall, "CallExpression") ? cleanupCall.arguments?.[0] : null, context) ?? cleanupReceiverForOfStatement;
|
|
14333
|
+
const cleanupForOfStatement = findForOfStatementForIteratorExpression(isNodeOfType(cleanupCall, "CallExpression") ? cleanupCall.arguments?.[0] : null, context);
|
|
14506
14334
|
if (!cleanupForOfStatement) {
|
|
14507
14335
|
didCleanupFunctionMatch = true;
|
|
14508
14336
|
return false;
|
|
@@ -14546,28 +14374,28 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
|
|
|
14546
14374
|
});
|
|
14547
14375
|
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
|
|
14548
14376
|
};
|
|
14549
|
-
const
|
|
14550
|
-
if (
|
|
14551
|
-
const
|
|
14552
|
-
|
|
14553
|
-
|
|
14554
|
-
|
|
14555
|
-
|
|
14377
|
+
const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => {
|
|
14378
|
+
if (usage.handleKey === null) return null;
|
|
14379
|
+
const doesTestRequireLiveHandle = (test) => {
|
|
14380
|
+
if (resolveExpressionKey(test, context) === usage.handleKey) return true;
|
|
14381
|
+
const unwrappedTest = stripParenExpression(test);
|
|
14382
|
+
if (!isNodeOfType(unwrappedTest, "BinaryExpression") || unwrappedTest.operator !== "!=" && unwrappedTest.operator !== "!==") return false;
|
|
14383
|
+
const isNullishOperand = (operand) => {
|
|
14384
|
+
const unwrappedOperand = stripParenExpression(operand);
|
|
14385
|
+
return isNodeOfType(unwrappedOperand, "Literal") && unwrappedOperand.value === null || isNodeOfType(unwrappedOperand, "Identifier") && unwrappedOperand.name === "undefined" && context.scopes.isGlobalReference(unwrappedOperand);
|
|
14386
|
+
};
|
|
14387
|
+
return resolveExpressionKey(unwrappedTest.left, context) === usage.handleKey && isNullishOperand(unwrappedTest.right) || resolveExpressionKey(unwrappedTest.right, context) === usage.handleKey && isNullishOperand(unwrappedTest.left);
|
|
14556
14388
|
};
|
|
14557
|
-
return resolveExpressionKey(unwrappedTest.left, context) === expressionKey && isNullishOperand(unwrappedTest.right) || resolveExpressionKey(unwrappedTest.right, context) === expressionKey && isNullishOperand(unwrappedTest.left);
|
|
14558
|
-
};
|
|
14559
|
-
const findLiveExpressionGuardForRelease = (releaseCall, owner, expressionKey, context) => {
|
|
14560
14389
|
let ancestor = releaseCall.parent;
|
|
14561
14390
|
while (ancestor && ancestor !== owner) {
|
|
14562
14391
|
if (isNodeOfType(ancestor, "IfStatement")) {
|
|
14563
|
-
if (ancestor.alternate !== null || !
|
|
14392
|
+
if (ancestor.alternate !== null || !doesTestRequireLiveHandle(ancestor.test) || !doMatchingNodesCoverEveryPathAfterUsage(ancestor.consequent, [releaseCall], context)) return null;
|
|
14564
14393
|
return ancestor;
|
|
14565
14394
|
}
|
|
14566
14395
|
ancestor = ancestor.parent;
|
|
14567
14396
|
}
|
|
14568
14397
|
return null;
|
|
14569
14398
|
};
|
|
14570
|
-
const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => usage.handleKey === null ? null : findLiveExpressionGuardForRelease(releaseCall, owner, usage.handleKey, context);
|
|
14571
14399
|
const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
|
|
14572
14400
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
|
|
14573
14401
|
const functionCfg = context.cfg.cfgFor(callback);
|
|
@@ -14755,108 +14583,7 @@ const hasPotentialInterruptionAfterGuard = (callback, guardState, usageNode, con
|
|
|
14755
14583
|
});
|
|
14756
14584
|
return hasPotentialInterruption;
|
|
14757
14585
|
};
|
|
14758
|
-
const getNumericReactRefCurrentKey = (expression, context) => {
|
|
14759
|
-
const refSymbol = resolveReactRefSymbol(stripParenExpression(expression), context.scopes);
|
|
14760
|
-
const initializer = refSymbol?.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
14761
|
-
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
14762
|
-
const initialValue = initializer.arguments?.[0] ? stripParenExpression(initializer.arguments[0]) : null;
|
|
14763
|
-
if (!isNodeOfType(initialValue, "Literal") || typeof initialValue.value !== "number") return null;
|
|
14764
|
-
return resolveExpressionKey(expression, context);
|
|
14765
|
-
};
|
|
14766
|
-
const getBlockingGenerationKey = (expression, context) => {
|
|
14767
|
-
const test = stripParenExpression(expression);
|
|
14768
|
-
if (isNodeOfType(test, "LogicalExpression") && test.operator === "||") return getBlockingGenerationKey(test.left, context) ?? getBlockingGenerationKey(test.right, context);
|
|
14769
|
-
if (!isNodeOfType(test, "BinaryExpression") || test.operator !== "!==" && test.operator !== "!=") return null;
|
|
14770
|
-
const leftKey = getNumericReactRefCurrentKey(test.left, context);
|
|
14771
|
-
const rightKey = getNumericReactRefCurrentKey(test.right, context);
|
|
14772
|
-
const snapshotExpression = leftKey ? stripParenExpression(test.right) : stripParenExpression(test.left);
|
|
14773
|
-
const key = leftKey ?? rightKey;
|
|
14774
|
-
return key && isNodeOfType(snapshotExpression, "Identifier") ? key : null;
|
|
14775
|
-
};
|
|
14776
|
-
const findGenerationGuardKeyForDeferredUsage = (usageFunction, usageNode, context) => {
|
|
14777
|
-
if (!isFunctionLike$1(usageFunction)) return null;
|
|
14778
|
-
let generationKey = null;
|
|
14779
|
-
walkAst(usageFunction.body, (child) => {
|
|
14780
|
-
if (generationKey) return false;
|
|
14781
|
-
if (child !== usageFunction.body && isFunctionLike$1(child)) return false;
|
|
14782
|
-
if (!isNodeOfType(child, "IfStatement") || child.alternate) return;
|
|
14783
|
-
const key = getBlockingGenerationKey(child.test, context);
|
|
14784
|
-
if (!key || canNodeReachLaterNodeWithinFunction(child.consequent, usageNode, usageFunction, context) || !doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], usageFunction, context)) return;
|
|
14785
|
-
generationKey = key;
|
|
14786
|
-
});
|
|
14787
|
-
return generationKey;
|
|
14788
|
-
};
|
|
14789
|
-
const isGenerationAdvance = (node, generationKey, context) => {
|
|
14790
|
-
if (isNodeOfType(node, "UpdateExpression") && resolveExpressionKey(node.argument, context) === generationKey) return true;
|
|
14791
|
-
if (!isNodeOfType(node, "AssignmentExpression") || resolveExpressionKey(node.left, context) !== generationKey || node.operator !== "+=" && node.operator !== "-=") return false;
|
|
14792
|
-
const amount = stripParenExpression(node.right);
|
|
14793
|
-
return isNodeOfType(amount, "Literal") && typeof amount.value === "number" && amount.value !== 0;
|
|
14794
|
-
};
|
|
14795
|
-
const functionAdvancesGeneration = (owner, generationKey, context) => {
|
|
14796
|
-
if (!isFunctionLike$1(owner)) return false;
|
|
14797
|
-
let didAdvanceGeneration = false;
|
|
14798
|
-
walkAst(owner.body, (child) => {
|
|
14799
|
-
if (didAdvanceGeneration) return false;
|
|
14800
|
-
if (child !== owner.body && isFunctionLike$1(child)) return false;
|
|
14801
|
-
if (isGenerationAdvance(child, generationKey, context)) {
|
|
14802
|
-
didAdvanceGeneration = true;
|
|
14803
|
-
return false;
|
|
14804
|
-
}
|
|
14805
|
-
});
|
|
14806
|
-
return didAdvanceGeneration;
|
|
14807
|
-
};
|
|
14808
|
-
const cleanupReturnsReleaseUsage = (cleanupReturns, usage, context) => cleanupReturns.length > 0 && cleanupReturns.every((cleanupReturn) => {
|
|
14809
|
-
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
|
|
14810
|
-
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
14811
|
-
return Boolean(cleanupFunction && isFunctionLike$1(cleanupFunction) && doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context));
|
|
14812
|
-
});
|
|
14813
|
-
const getOwnedFunctionReference = (reference, usageFunction, usageNode, callback, cleanupReturns, context) => {
|
|
14814
|
-
const directCall = findDirectCallForReference(reference);
|
|
14815
|
-
if (directCall) {
|
|
14816
|
-
const referenceOwner = findEnclosingFunction$1(directCall);
|
|
14817
|
-
if (referenceOwner && referenceOwner !== usageFunction && collectSynchronouslyEffectInvokedFunctions(callback).has(referenceOwner)) return { generationKey: null };
|
|
14818
|
-
const generationKey = referenceOwner ? findGenerationGuardKeyForDeferredUsage(referenceOwner, directCall, context) : null;
|
|
14819
|
-
return generationKey ? { generationKey } : null;
|
|
14820
|
-
}
|
|
14821
|
-
const referenceRoot = findTransparentExpressionRoot(reference);
|
|
14822
|
-
const schedulerCall = referenceRoot.parent;
|
|
14823
|
-
if (!isNodeOfType(schedulerCall, "CallExpression") || !schedulerCall.arguments.some((argument) => argument === referenceRoot) || !isNodeOfType(schedulerCall.callee, "Identifier") || schedulerCall.callee.name !== "setTimeout" || !context.scopes.isGlobalReference(schedulerCall.callee)) return null;
|
|
14824
|
-
const schedulerUsage = {
|
|
14825
|
-
kind: "timer",
|
|
14826
|
-
node: schedulerCall,
|
|
14827
|
-
resourceName: schedulerCall.callee.name,
|
|
14828
|
-
handleKey: findAssignedResourceKey(schedulerCall, context),
|
|
14829
|
-
receiverKey: null,
|
|
14830
|
-
registrationVerbName: schedulerCall.callee.name,
|
|
14831
|
-
eventKey: null,
|
|
14832
|
-
handlerKey: null
|
|
14833
|
-
};
|
|
14834
|
-
const generationKey = findGenerationGuardKeyForDeferredUsage(usageFunction, usageNode, context);
|
|
14835
|
-
return schedulerUsage.handleKey !== null && cleanupReturnsReleaseUsage(cleanupReturns, schedulerUsage, context) && generationKey ? { generationKey } : null;
|
|
14836
|
-
};
|
|
14837
|
-
const hasGuardedRefOwnedNestedCleanup = (callback, usage, cleanupReturns, context) => {
|
|
14838
|
-
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
14839
|
-
const usageExpression = findTransparentExpressionRoot(usage.node);
|
|
14840
|
-
const usageAssignment = usageExpression.parent;
|
|
14841
|
-
if (usage.kind !== "subscribe" && usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usageAssignment, "AssignmentExpression") || usageAssignment.operator !== "=" || usageAssignment.right !== usageExpression || !resolveReactRefSymbol(stripParenExpression(usageAssignment.left), context.scopes) || !collectSynchronouslyEffectInvokedFunctions(callback).has(usageFunction) || !cleanupReturnsReleaseUsage(cleanupReturns, usage, context) || !doMatchingNodesCoverEveryPathFromFunctionEntry(callback, cleanupReturns, context)) return false;
|
|
14842
|
-
const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
|
|
14843
|
-
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
|
|
14844
|
-
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
14845
|
-
return cleanupFunction && isFunctionLike$1(cleanupFunction) ? [cleanupFunction] : [];
|
|
14846
|
-
});
|
|
14847
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(usageFunction);
|
|
14848
|
-
const functionSymbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
|
|
14849
|
-
if (!functionSymbol || functionSymbol.references.length === 0) return false;
|
|
14850
|
-
const ownedReferences = functionSymbol.references.map((reference) => getOwnedFunctionReference(reference.identifier, usageFunction, usage.node, callback, cleanupReturns, context));
|
|
14851
|
-
if (ownedReferences.some((reference) => reference === null)) return false;
|
|
14852
|
-
const generationKeys = new Set(ownedReferences.flatMap((reference) => reference?.generationKey ? [reference.generationKey] : []));
|
|
14853
|
-
if (generationKeys.size !== 1) return false;
|
|
14854
|
-
const generationKey = generationKeys.values().next().value;
|
|
14855
|
-
if (typeof generationKey !== "string") return false;
|
|
14856
|
-
return [...collectSynchronouslyEffectInvokedFunctions(callback), ...cleanupFunctions].some((owner) => functionAdvancesGeneration(owner, generationKey, context));
|
|
14857
|
-
};
|
|
14858
14586
|
const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
|
|
14859
|
-
if (hasGuardedRefOwnedNestedCleanup(callback, usage, cleanupReturns, context)) return true;
|
|
14860
14587
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
14861
14588
|
const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
|
|
14862
14589
|
if (usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usage.node, "CallExpression") || !isNodeOfType(usage.node.callee, "Identifier") || !context.scopes.isGlobalReference(usage.node.callee) || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
|
|
@@ -15024,85 +14751,6 @@ const getReleaseVerbName = (node) => {
|
|
|
15024
14751
|
}
|
|
15025
14752
|
return null;
|
|
15026
14753
|
};
|
|
15027
|
-
const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) => {
|
|
15028
|
-
const releaseFunction = findEnclosingFunction$1(releaseReceiver);
|
|
15029
|
-
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15030
|
-
if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction) || !resolveReactRefCurrentOriginSymbol(releaseReceiver, context.scopes)) return false;
|
|
15031
|
-
const controllerKey = getListenerAbortControllerKey(usage, context);
|
|
15032
|
-
const refCurrentKey = resolveExpressionKey(releaseReceiver, context);
|
|
15033
|
-
if (controllerKey === null || refCurrentKey === null) return false;
|
|
15034
|
-
const usageFunctionBody = usageFunction.body;
|
|
15035
|
-
const previousAbortCalls = [];
|
|
15036
|
-
const ownershipAssignments = [];
|
|
15037
|
-
walkAst(usageFunctionBody, (child) => {
|
|
15038
|
-
if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
|
|
15039
|
-
if (isNodeOfType(child, "AssignmentExpression") && resolveExpressionKey(child.left, context) === refCurrentKey && resolveExpressionKey(child.right, context) === controllerKey) {
|
|
15040
|
-
ownershipAssignments.push(child);
|
|
15041
|
-
return;
|
|
15042
|
-
}
|
|
15043
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
15044
|
-
const childCallee = isNodeOfType(child.callee, "ChainExpression") ? child.callee.expression : stripParenExpression(child.callee);
|
|
15045
|
-
if (isNodeOfType(childCallee, "MemberExpression") && !childCallee.computed && isNodeOfType(childCallee.property, "Identifier") && childCallee.property.name === "abort" && resolveExpressionKey(childCallee.object, context) === refCurrentKey) previousAbortCalls.push(child);
|
|
15046
|
-
});
|
|
15047
|
-
const safeOwnershipAssignments = ownershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, previousAbortCalls, usageFunction, context));
|
|
15048
|
-
return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
|
|
15049
|
-
};
|
|
15050
|
-
const isJsxRefAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && node.name.name === "ref";
|
|
15051
|
-
const isFunctionForwardedToReactRef = (functionNode, context) => {
|
|
15052
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
15053
|
-
if (!bindingIdentifier) return false;
|
|
15054
|
-
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
15055
|
-
if (!symbol) return false;
|
|
15056
|
-
return symbol.references.some((reference) => {
|
|
15057
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
15058
|
-
const expressionContainer = referenceRoot.parent;
|
|
15059
|
-
return Boolean(isNodeOfType(expressionContainer, "JSXExpressionContainer") && expressionContainer.expression === referenceRoot && isJsxRefAttribute(expressionContainer.parent));
|
|
15060
|
-
});
|
|
15061
|
-
};
|
|
15062
|
-
const isFunctionReturnedFromReactHook = (functionNode, context, requireRefPropertyName) => {
|
|
15063
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
15064
|
-
if (!bindingIdentifier) return false;
|
|
15065
|
-
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
15066
|
-
if (!symbol) return false;
|
|
15067
|
-
return symbol.references.some((reference) => {
|
|
15068
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
15069
|
-
const property = referenceRoot.parent;
|
|
15070
|
-
const propertyName = isNodeOfType(property, "Property") ? getStaticPropertyKeyName(property) : null;
|
|
15071
|
-
if (!isNodeOfType(property, "Property") || property.value !== referenceRoot || !isNodeOfType(property.parent, "ObjectExpression") || requireRefPropertyName && propertyName !== "ref" && !propertyName?.endsWith("Ref")) return false;
|
|
15072
|
-
const returnedObject = findTransparentExpressionRoot(property.parent);
|
|
15073
|
-
const returnStatement = returnedObject.parent;
|
|
15074
|
-
if (!isNodeOfType(returnStatement, "ReturnStatement") || returnStatement.argument !== returnedObject) return false;
|
|
15075
|
-
const ownerFunction = findEnclosingFunction$1(returnStatement);
|
|
15076
|
-
return Boolean(ownerFunction && isReactHookName(getFunctionBindingIdentifier$1(ownerFunction)?.name ?? ""));
|
|
15077
|
-
});
|
|
15078
|
-
};
|
|
15079
|
-
const isFunctionUsedAsReactRef = (functionNode, context) => isFunctionForwardedToReactRef(functionNode, context) || isFunctionReturnedFromReactHook(functionNode, context, true);
|
|
15080
|
-
const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
|
|
15081
|
-
if (!isNodeOfType(usage.node, "CallExpression")) return false;
|
|
15082
|
-
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15083
|
-
if (!usageFunction || !isFunctionLike$1(usageFunction) || usageFunction !== findEnclosingFunction$1(releaseCall) || !isFunctionUsedAsReactRef(usageFunction, context)) return false;
|
|
15084
|
-
const registrationCallee = stripParenExpression(usage.node.callee);
|
|
15085
|
-
const releaseCallee = stripParenExpression(releaseCall.callee);
|
|
15086
|
-
const releaseRefSymbol = isNodeOfType(releaseCallee, "MemberExpression") ? resolveReactRefCurrentOriginSymbol(releaseCallee.object, context.scopes) : null;
|
|
15087
|
-
if (!isNodeOfType(registrationCallee, "MemberExpression") || registrationCallee.computed || !isNodeOfType(registrationCallee.property, "Identifier") || registrationCallee.property.name !== "addEventListener" || !isNodeOfType(releaseCallee, "MemberExpression") || releaseCallee.computed || !isNodeOfType(releaseCallee.property, "Identifier") || releaseCallee.property.name !== "removeEventListener" || !releaseRefSymbol) return false;
|
|
15088
|
-
const registrationReceiverKey = resolveExpressionKey(stripParenExpression(registrationCallee.object), context);
|
|
15089
|
-
const nodeParameterKey = resolveExpressionKey(usageFunction.params?.[0], context);
|
|
15090
|
-
const releaseReceiverKey = resolveExpressionKey(releaseCallee.object, context);
|
|
15091
|
-
if (registrationReceiverKey === null || registrationReceiverKey !== nodeParameterKey || releaseReceiverKey === null || usage.eventKey === null || usage.eventKey !== resolveExpressionKey(releaseCall.arguments?.[0], context) || usage.handlerKey === null || usage.handlerKey !== resolveExpressionKey(releaseCall.arguments?.[1], context)) return false;
|
|
15092
|
-
const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15093
|
-
const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15094
|
-
if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
|
|
15095
|
-
const releaseStart = getRangeStart(releaseCall);
|
|
15096
|
-
const matchingOwnershipAssignments = [];
|
|
15097
|
-
const usageFunctionBody = usageFunction.body;
|
|
15098
|
-
walkAst(usageFunctionBody, (child) => {
|
|
15099
|
-
if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
|
|
15100
|
-
if (isNodeOfType(child, "AssignmentExpression") && child.operator === "=" && resolveReactRefSymbol(stripParenExpression(child.left), context.scopes)?.id === releaseRefSymbol.id && resolveExpressionKey(child.right, context) === registrationReceiverKey && releaseStart !== null && (getRangeStart(child) ?? -1) > releaseStart) matchingOwnershipAssignments.push(child);
|
|
15101
|
-
});
|
|
15102
|
-
const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
|
|
15103
|
-
const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
|
|
15104
|
-
return doMatchingNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
|
|
15105
|
-
};
|
|
15106
14754
|
const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
15107
14755
|
const callNode = isNodeOfType(node, "ChainExpression") ? node.expression : node;
|
|
15108
14756
|
if (!isNodeOfType(callNode, "CallExpression")) return false;
|
|
@@ -15119,21 +14767,13 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15119
14767
|
if (!releaseVerbName) return false;
|
|
15120
14768
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
|
|
15121
14769
|
const releaseReceiverKey = resolveExpressionKey(callee.object, context);
|
|
15122
|
-
const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
|
|
15123
|
-
const pairedReleaseVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
|
|
15124
|
-
const pushedResourceCollectionKey = findPushedResourceCollectionKey(usage, context);
|
|
15125
|
-
const releaseReceiverForOfStatement = findForOfStatementForIteratorExpression(callee.object, context);
|
|
15126
|
-
const releaseReceiverCollectionKey = releaseReceiverForOfStatement ? resolveExpressionKey(releaseReceiverForOfStatement.right, context) : resolveIteratorCollectionKey(callee.object, context);
|
|
15127
|
-
if (pairedReleaseVerbNames && matchesPairedReleaseVerb(releaseVerbName, pairedReleaseVerbNames) && pushedResourceCollectionKey !== null && pushedResourceCollectionKey === releaseReceiverCollectionKey && (releaseVerbName !== "unobserve" || usage.eventKey !== null && releaseEventKey === usage.eventKey)) return true;
|
|
15128
|
-
if (isReactRefListenerReplacementRelease(callNode, usage, context)) return true;
|
|
15129
14770
|
if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
|
|
15130
14771
|
if (usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub" || releaseVerbName === "close" || releaseVerbName === "unwatch" || releaseVerbName === "unlisten" || BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(releaseVerbName))) return true;
|
|
15131
14772
|
if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
|
|
15132
|
-
if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
|
|
15133
14773
|
if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) return false;
|
|
15134
|
-
if (usage.registrationVerbName === "subscribe" && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub") && usage.handleKey !== null && resolveExpressionKey(callNode.arguments?.[0], context) === usage.handleKey) return true;
|
|
15135
14774
|
const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
|
|
15136
14775
|
if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
|
|
14776
|
+
const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
|
|
15137
14777
|
const usageEventArgument = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[0] : null;
|
|
15138
14778
|
const releaseEventArgument = callNode.arguments?.[0];
|
|
15139
14779
|
if (isAssignmentFormForOfIteratorReference(usageEventArgument, context) || isAssignmentFormForOfIteratorReference(releaseEventArgument, context)) return false;
|
|
@@ -15163,12 +14803,9 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15163
14803
|
return isNodeOfType(handlerArgument, "Literal") && handlerArgument.value === null;
|
|
15164
14804
|
}
|
|
15165
14805
|
if (releaseVerbName === "removeEventListener" || releaseVerbName === "removeListener" || releaseVerbName === "off") {
|
|
15166
|
-
const
|
|
15167
|
-
const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
|
|
14806
|
+
const releaseHandler = callNode.arguments?.[1];
|
|
15168
14807
|
if (!releaseHandler) return releaseVerbName === "off";
|
|
15169
|
-
|
|
15170
|
-
const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignature ? 0 : 1] : null;
|
|
15171
|
-
return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
|
|
14808
|
+
return usage.handlerKey !== null && resolveExpressionKey(releaseHandler, context) === usage.handlerKey;
|
|
15172
14809
|
}
|
|
15173
14810
|
if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
|
|
15174
14811
|
return true;
|
|
@@ -15181,7 +14818,8 @@ const isReturnedEffectCleanupFunction = (functionNode) => {
|
|
|
15181
14818
|
currentNode = parentNode;
|
|
15182
14819
|
parentNode = currentNode.parent;
|
|
15183
14820
|
}
|
|
15184
|
-
|
|
14821
|
+
if (!isNodeOfType(parentNode, "ReturnStatement") || parentNode.argument !== currentNode) return false;
|
|
14822
|
+
const effectCallback = findEnclosingFunction$1(parentNode);
|
|
15185
14823
|
const effectCall = effectCallback?.parent;
|
|
15186
14824
|
return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
|
|
15187
14825
|
};
|
|
@@ -15193,165 +14831,13 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
|
|
|
15193
14831
|
if (!symbol) return false;
|
|
15194
14832
|
return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
|
|
15195
14833
|
};
|
|
15196
|
-
const findRetainedDisposerStorages = (disposerFunction, usage, context) => {
|
|
15197
|
-
if (!isFunctionLike$1(disposerFunction) || disposerFunction.async || disposerFunction.generator) return [];
|
|
15198
|
-
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15199
|
-
if (!usageFunction || !isFunctionLike$1(usageFunction)) return [];
|
|
15200
|
-
const assignments = /* @__PURE__ */ new Map();
|
|
15201
|
-
const collectAssignment = (expression) => {
|
|
15202
|
-
const expressionRoot = findTransparentExpressionRoot(expression);
|
|
15203
|
-
const assignment = expressionRoot.parent;
|
|
15204
|
-
if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.right !== expressionRoot) return;
|
|
15205
|
-
const refSymbol = resolveReactRefSymbol(stripParenExpression(assignment.left), context.scopes);
|
|
15206
|
-
const refCurrentKey = resolveExpressionKey(assignment.left, context);
|
|
15207
|
-
const retainedFunction = findEnclosingFunction$1(assignment);
|
|
15208
|
-
const assignmentStart = getRangeStart(assignment);
|
|
15209
|
-
if (!refSymbol || !refCurrentKey || !retainedFunction || retainedFunction !== usageFunction || assignmentStart === null) return;
|
|
15210
|
-
assignments.set(assignmentStart, {
|
|
15211
|
-
assignmentNode: assignment,
|
|
15212
|
-
refCurrentKey,
|
|
15213
|
-
retainedFunction
|
|
15214
|
-
});
|
|
15215
|
-
};
|
|
15216
|
-
collectAssignment(disposerFunction);
|
|
15217
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(disposerFunction);
|
|
15218
|
-
const symbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
|
|
15219
|
-
for (const reference of symbol?.references ?? []) collectAssignment(reference.identifier);
|
|
15220
|
-
walkAst(usageFunction.body, (child) => {
|
|
15221
|
-
if (child !== usageFunction.body && isFunctionLike$1(child)) return false;
|
|
15222
|
-
if (isNodeOfType(child, "AssignmentExpression") && resolveStableValue(child.right, context) === disposerFunction) collectAssignment(child.right);
|
|
15223
|
-
});
|
|
15224
|
-
return [...assignments.values()];
|
|
15225
|
-
};
|
|
15226
|
-
const isRetainedDisposerStorageEstablished = (storage, usage, context) => doMatchingNodesCoverEveryPathBeforeUsage(usage.node, [storage.assignmentNode], storage.retainedFunction, context) || doMatchingNodesCoverEveryPathAfterUsage(usage.node, [storage.assignmentNode], context);
|
|
15227
|
-
const hasUnsafeRetainedDisposerOverwrite = (storage, usage, context) => {
|
|
15228
|
-
let hasUnsafeOverwrite = false;
|
|
15229
|
-
walkAst(storage.retainedFunction.body, (child) => {
|
|
15230
|
-
if (hasUnsafeOverwrite) return false;
|
|
15231
|
-
if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
|
|
15232
|
-
if (!isNodeOfType(child, "AssignmentExpression") || child === storage.assignmentNode || resolveExpressionKey(child.left, context) !== storage.refCurrentKey || !canNodeReachLaterNodeWithinFunction(usage.node, child, storage.retainedFunction, context)) return;
|
|
15233
|
-
const storedValue = resolveStableValue(child.right, context);
|
|
15234
|
-
if (!storedValue || !isFunctionLike$1(storedValue) || !doesCleanupFunctionReleaseUsage(storedValue, usage, context)) {
|
|
15235
|
-
hasUnsafeOverwrite = true;
|
|
15236
|
-
return false;
|
|
15237
|
-
}
|
|
15238
|
-
});
|
|
15239
|
-
return hasUnsafeOverwrite;
|
|
15240
|
-
};
|
|
15241
|
-
const hasEffectCleanupInvocation = (storage, usage, context) => {
|
|
15242
|
-
const componentFunction = findEnclosingFunction$1(storage.retainedFunction);
|
|
15243
|
-
if (!componentFunction || !isFunctionLike$1(componentFunction)) return false;
|
|
15244
|
-
const cleanupFunctionInvokesRef = (cleanupFunction) => {
|
|
15245
|
-
if (!isFunctionLike$1(cleanupFunction)) return false;
|
|
15246
|
-
let didFindCleanupCall = false;
|
|
15247
|
-
walkAst(cleanupFunction.body, (child) => {
|
|
15248
|
-
if (didFindCleanupCall) return false;
|
|
15249
|
-
if (child !== cleanupFunction.body && isFunctionLike$1(child)) return false;
|
|
15250
|
-
if (isNodeOfType(child, "CallExpression") && resolveExpressionKey(child.callee, context) === storage.refCurrentKey) {
|
|
15251
|
-
const callRoot = findTransparentExpressionRoot(child);
|
|
15252
|
-
const callStatement = callRoot.parent;
|
|
15253
|
-
const isDirectBlockStatement = isNodeOfType(cleanupFunction.body, "BlockStatement") && isNodeOfType(callStatement, "ExpressionStatement") && callStatement.parent === cleanupFunction.body;
|
|
15254
|
-
const isConciseBody = cleanupFunction.body === callRoot;
|
|
15255
|
-
if ((isDirectBlockStatement || isConciseBody) && !hasUnprovenReturnBeforeRefOwnedRelease(cleanupFunction, child, storage.refCurrentKey, context)) {
|
|
15256
|
-
didFindCleanupCall = true;
|
|
15257
|
-
return false;
|
|
15258
|
-
}
|
|
15259
|
-
}
|
|
15260
|
-
});
|
|
15261
|
-
return didFindCleanupCall;
|
|
15262
|
-
};
|
|
15263
|
-
const effectReturnsCleanup = (effectCallback) => {
|
|
15264
|
-
if (!isFunctionLike$1(effectCallback)) return false;
|
|
15265
|
-
if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
|
|
15266
|
-
const cleanupFunction = resolveRefOwnedCleanupFunction(effectCallback.body, context);
|
|
15267
|
-
return Boolean(cleanupFunction && cleanupFunctionInvokesRef(cleanupFunction));
|
|
15268
|
-
}
|
|
15269
|
-
const matchingReturns = [];
|
|
15270
|
-
walkInsideStatementBlocks(effectCallback.body, (child) => {
|
|
15271
|
-
if (!isNodeOfType(child, "ReturnStatement") || !child.argument) return;
|
|
15272
|
-
const cleanupFunction = resolveRefOwnedCleanupFunction(child.argument, context);
|
|
15273
|
-
if (!cleanupFunction || !cleanupFunctionInvokesRef(cleanupFunction)) return;
|
|
15274
|
-
matchingReturns.push(child);
|
|
15275
|
-
});
|
|
15276
|
-
return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
|
|
15277
|
-
};
|
|
15278
|
-
let didFindInvocation = false;
|
|
15279
|
-
walkAst(componentFunction.body, (child) => {
|
|
15280
|
-
if (didFindInvocation) return false;
|
|
15281
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
|
|
15282
|
-
const effectCallback = getEffectCallback(child);
|
|
15283
|
-
if (effectCallback && effectReturnsCleanup(effectCallback)) {
|
|
15284
|
-
didFindInvocation = true;
|
|
15285
|
-
return false;
|
|
15286
|
-
}
|
|
15287
|
-
});
|
|
15288
|
-
return didFindInvocation;
|
|
15289
|
-
};
|
|
15290
|
-
const hasCallbackRefReplacementInvocation = (storage, usage, context) => {
|
|
15291
|
-
const isReturnedCallbackRefShape = () => {
|
|
15292
|
-
if (!isFunctionLike$1(storage.retainedFunction)) return false;
|
|
15293
|
-
const callbackCall = findTransparentExpressionRoot(storage.retainedFunction).parent;
|
|
15294
|
-
if (!isNodeOfType(callbackCall, "CallExpression") || !isReactApiCall(callbackCall, "useCallback", context.scopes)) return false;
|
|
15295
|
-
const nodeParameter = storage.retainedFunction.params?.[0];
|
|
15296
|
-
const nodeParameterKey = resolveExpressionKey(nodeParameter, context);
|
|
15297
|
-
if (!nodeParameterKey || usage.receiverKey !== nodeParameterKey) return false;
|
|
15298
|
-
if (!isFunctionReturnedFromReactHook(storage.retainedFunction, context, false)) return false;
|
|
15299
|
-
const usageStart = getRangeStart(usage.node);
|
|
15300
|
-
if (usageStart === null) return false;
|
|
15301
|
-
let hasNullExit = false;
|
|
15302
|
-
walkAst(storage.retainedFunction.body, (child) => {
|
|
15303
|
-
if (hasNullExit) return false;
|
|
15304
|
-
if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
|
|
15305
|
-
if (!isNodeOfType(child, "IfStatement") || (getRangeStart(child) ?? usageStart) >= usageStart) return;
|
|
15306
|
-
const test = stripParenExpression(child.test);
|
|
15307
|
-
if (!isNodeOfType(test, "UnaryExpression") || test.operator !== "!" || resolveExpressionKey(test.argument, context) !== nodeParameterKey) return;
|
|
15308
|
-
const consequent = child.consequent;
|
|
15309
|
-
hasNullExit = isNodeOfType(consequent, "ReturnStatement") || isNodeOfType(consequent, "BlockStatement") && consequent.body.some((statement) => isNodeOfType(statement, "ReturnStatement"));
|
|
15310
|
-
if (hasNullExit) return false;
|
|
15311
|
-
});
|
|
15312
|
-
return hasNullExit;
|
|
15313
|
-
};
|
|
15314
|
-
if (!isFunctionForwardedToReactRef(storage.retainedFunction, context) && !isReturnedCallbackRefShape()) return false;
|
|
15315
|
-
const cleanupCalls = [];
|
|
15316
|
-
walkAst(storage.retainedFunction.body, (child) => {
|
|
15317
|
-
if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
|
|
15318
|
-
if (isNodeOfType(child, "CallExpression") && resolveExpressionKey(child.callee, context) === storage.refCurrentKey) cleanupCalls.push(child);
|
|
15319
|
-
});
|
|
15320
|
-
return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, cleanupCalls, storage.retainedFunction, context);
|
|
15321
|
-
};
|
|
15322
|
-
const isRetainedDisposerRefRelease = (releaseNode, usage, context) => {
|
|
15323
|
-
const disposerFunction = findEnclosingFunction$1(releaseNode);
|
|
15324
|
-
if (!disposerFunction) return false;
|
|
15325
|
-
return findRetainedDisposerStorages(disposerFunction, usage, context).some((storage) => isRetainedDisposerStorageEstablished(storage, usage, context) && !hasUnsafeRetainedDisposerOverwrite(storage, usage, context) && (hasEffectCleanupInvocation(storage, usage, context) || hasCallbackRefReplacementInvocation(storage, usage, context)));
|
|
15326
|
-
};
|
|
15327
|
-
const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
|
|
15328
|
-
if (usage.kind !== "subscribe" || usage.registrationVerbName !== "addEventListener" || usage.receiverKey === null || usage.eventKey === null || !isNodeOfType(usage.node, "CallExpression") || !isFunctionLike$1(releaseFunction) || releaseFunction.async || releaseFunction.generator || !isNodeOfType(releaseFunction.body, "BlockStatement") || !doMatchingNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseNode], context)) return false;
|
|
15329
|
-
const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15330
|
-
const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
|
|
15331
|
-
if (!isNodeOfType(releaseCall, "CallExpression")) return false;
|
|
15332
|
-
const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15333
|
-
if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
|
|
15334
|
-
const ownerFunction = findEnclosingFunction$1(releaseFunction);
|
|
15335
|
-
if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
|
|
15336
|
-
const triggerRegistrations = [];
|
|
15337
|
-
walkAst(ownerFunction.body, (child) => {
|
|
15338
|
-
if (child !== ownerFunction.body && isFunctionLike$1(child)) return false;
|
|
15339
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
15340
|
-
const registrationDetails = getCallRegistrationDetails(child, context);
|
|
15341
|
-
if (registrationDetails.registrationVerbName === "addEventListener" && registrationDetails.receiverKey === usage.receiverKey && resolveStableValue(child.arguments?.[1], context) === releaseFunction) triggerRegistrations.push(child);
|
|
15342
|
-
});
|
|
15343
|
-
if (triggerRegistrations.some((triggerRegistration) => triggerRegistration === usage.node)) return true;
|
|
15344
|
-
return doMatchingNodesCoverEveryPathAfterUsage(usage.node, triggerRegistrations, context) || doMatchingNodesCoverEveryPathBeforeUsage(usage.node, triggerRegistrations, ownerFunction, context);
|
|
15345
|
-
};
|
|
15346
14834
|
const isReleaseReachableForUsage = (releaseNode, usage, context) => {
|
|
15347
14835
|
if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
|
|
15348
14836
|
const releaseFunction = findEnclosingFunction$1(releaseNode);
|
|
15349
14837
|
if (!releaseFunction) return true;
|
|
15350
14838
|
if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
|
|
15351
|
-
if (isRetainedDisposerRefRelease(releaseNode, usage, context)) return true;
|
|
15352
14839
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15353
14840
|
if (usageFunction && isFunctionLike$1(usageFunction) && getAssignedReactRefSymbol(usageFunction, context) && isCleanupFunctionReferencedByReturn(usageFunction, releaseFunction, context)) return isReactRefCallbackCleanupOwnedByEffect(usageFunction, releaseFunction, usage, context);
|
|
15354
|
-
if (isSelfReleasingListenerRelease(releaseNode, releaseFunction, usage, context)) return true;
|
|
15355
14841
|
return isPotentiallyReachableFunction(releaseFunction, context);
|
|
15356
14842
|
};
|
|
15357
14843
|
const fileContainsReleaseForUsage = (usage, context) => {
|
|
@@ -15619,11 +15105,6 @@ const doesResourceResultEscape = (resourceNode, allowReturnedResourceEscape, all
|
|
|
15619
15105
|
parentNode = currentNode.parent;
|
|
15620
15106
|
continue;
|
|
15621
15107
|
}
|
|
15622
|
-
if (isNodeOfType(parentNode, "ConditionalExpression") && (parentNode.consequent === currentNode || parentNode.alternate === currentNode) || isNodeOfType(parentNode, "LogicalExpression") && (parentNode.right === currentNode || parentNode.left === currentNode && parentNode.operator !== "&&")) {
|
|
15623
|
-
currentNode = parentNode;
|
|
15624
|
-
parentNode = currentNode.parent;
|
|
15625
|
-
continue;
|
|
15626
|
-
}
|
|
15627
15108
|
if (isNodeOfType(parentNode, "VariableDeclarator") && parentNode.init === currentNode && isNodeOfType(parentNode.id, "Identifier") && isNodeOfType(parentNode.parent, "VariableDeclaration") && parentNode.parent.kind === "const") {
|
|
15628
15109
|
const ownerFunction = findEnclosingFunction$1(resourceNode);
|
|
15629
15110
|
const resourceSymbol = context.scopes.symbolFor(parentNode.id);
|
|
@@ -17283,38 +16764,7 @@ const symbolHasStableImportedAlias = (symbol, scopes) => {
|
|
|
17283
16764
|
const resolvedSymbol = resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes);
|
|
17284
16765
|
return resolvedSymbol !== null && resolvedSymbol !== symbol && resolvedSymbol.kind === "import";
|
|
17285
16766
|
};
|
|
17286
|
-
const
|
|
17287
|
-
let currentNode = findTransparentExpressionRoot(node);
|
|
17288
|
-
while (currentNode.parent) {
|
|
17289
|
-
const parentNode = currentNode.parent;
|
|
17290
|
-
if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
|
|
17291
|
-
if (isNodeOfType(parentNode, "UpdateExpression")) return parentNode.argument === currentNode;
|
|
17292
|
-
if (isNodeOfType(parentNode, "UnaryExpression")) return parentNode.operator === "delete" && parentNode.argument === currentNode;
|
|
17293
|
-
if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
|
|
17294
|
-
if (isNodeOfType(parentNode, "ArrayPattern") && parentNode.elements.some((element) => element === currentNode) || isNodeOfType(parentNode, "ObjectPattern") && parentNode.properties.some((property) => property === currentNode) || isNodeOfType(parentNode, "RestElement") && parentNode.argument === currentNode || isNodeOfType(parentNode, "AssignmentPattern") && parentNode.left === currentNode || isNodeOfType(parentNode, "Property") && parentNode.value === currentNode && isNodeOfType(parentNode.parent, "ObjectPattern")) {
|
|
17295
|
-
currentNode = parentNode;
|
|
17296
|
-
continue;
|
|
17297
|
-
}
|
|
17298
|
-
return false;
|
|
17299
|
-
}
|
|
17300
|
-
return false;
|
|
17301
|
-
};
|
|
17302
|
-
const symbolHasStableRefLazyInitialization = (symbol, scopes) => {
|
|
17303
|
-
if (symbol.kind !== "const" || symbol.references.some((reference) => reference.flag !== "read")) return false;
|
|
17304
|
-
const initializer = symbol.initializer ? unwrapExpression$3(symbol.initializer) : null;
|
|
17305
|
-
if (!isNodeOfType(initializer, "AssignmentExpression") || initializer.operator !== "??=") return false;
|
|
17306
|
-
const refSymbol = resolveReactRefSymbol(unwrapExpression$3(initializer.left), scopes);
|
|
17307
|
-
if (!refSymbol) return false;
|
|
17308
|
-
return refSymbol.references.every((reference) => {
|
|
17309
|
-
const memberExpression = findTransparentExpressionRoot(reference.identifier).parent;
|
|
17310
|
-
if (!isNodeOfType(memberExpression, "MemberExpression") || unwrapExpression$3(memberExpression.object) !== reference.identifier || getStaticPropertyName(memberExpression) !== "current") return false;
|
|
17311
|
-
const referenceRoot = findTransparentExpressionRoot(memberExpression);
|
|
17312
|
-
const parentNode = referenceRoot.parent;
|
|
17313
|
-
if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.left === referenceRoot) return parentNode === initializer;
|
|
17314
|
-
return !isAssignmentTarget(referenceRoot);
|
|
17315
|
-
});
|
|
17316
|
-
};
|
|
17317
|
-
const symbolHasStableValue = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => symbolHasStableHookOrigin(symbol, scopes) || symbolHasStableRefLazyInitialization(symbol, scopes) || symbolHasStableImportedAlias(symbol, scopes) || symbolHasStableFunctionOrigin(symbol, scopes, visitedSymbolIds) || symbolHasStableMemoizedOrigin(symbol, scopes, visitedSymbolIds);
|
|
16767
|
+
const symbolHasStableValue = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => symbolHasStableHookOrigin(symbol, scopes) || symbolHasStableImportedAlias(symbol, scopes) || symbolHasStableFunctionOrigin(symbol, scopes, visitedSymbolIds) || symbolHasStableMemoizedOrigin(symbol, scopes, visitedSymbolIds);
|
|
17318
16768
|
//#endregion
|
|
17319
16769
|
//#region src/plugin/utils/symbol-has-react-use-effect-event-origin.ts
|
|
17320
16770
|
const symbolHasReactUseEffectEventOrigin = (symbol, scopes) => {
|
|
@@ -17516,7 +16966,7 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allow
|
|
|
17516
16966
|
keys.add(depKey);
|
|
17517
16967
|
continue;
|
|
17518
16968
|
}
|
|
17519
|
-
const identitySourceKeys =
|
|
16969
|
+
const identitySourceKeys = resolveReactiveIdentitySourceKeys(symbol, scopes);
|
|
17520
16970
|
if (identitySourceKeys) {
|
|
17521
16971
|
if (identitySourceKeys.size === 0) stableCapturedNames.add(depKey);
|
|
17522
16972
|
for (const identitySourceKey of identitySourceKeys) keys.add(identitySourceKey);
|
|
@@ -17599,161 +17049,6 @@ const resolveReactiveIdentitySourceKeys = (symbol, scopes) => {
|
|
|
17599
17049
|
if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier || symbol.references.some((reference) => reference.flag !== "read")) return null;
|
|
17600
17050
|
return resolveIdentitySourceKeysFromExpression(symbol.initializer, scopes, new Set([symbol.id]));
|
|
17601
17051
|
};
|
|
17602
|
-
const isPureDerivedExpression = (expression) => {
|
|
17603
|
-
const candidate = unwrapExpression$3(expression);
|
|
17604
|
-
if (isNodeOfType(candidate, "Literal") || isNodeOfType(candidate, "Identifier")) return true;
|
|
17605
|
-
if (isNodeOfType(candidate, "MemberExpression")) return isPureDerivedExpression(candidate.object) && (!candidate.computed || isPureDerivedExpression(candidate.property));
|
|
17606
|
-
if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return isPureDerivedExpression(candidate.left) && isPureDerivedExpression(candidate.right);
|
|
17607
|
-
if (isNodeOfType(candidate, "UnaryExpression")) return candidate.operator !== "delete" && isPureDerivedExpression(candidate.argument);
|
|
17608
|
-
if (isNodeOfType(candidate, "ConditionalExpression")) return isPureDerivedExpression(candidate.test) && isPureDerivedExpression(candidate.consequent) && isPureDerivedExpression(candidate.alternate);
|
|
17609
|
-
if (isNodeOfType(candidate, "TemplateLiteral")) return candidate.expressions.every((nestedExpression) => isPureDerivedExpression(nestedExpression));
|
|
17610
|
-
return false;
|
|
17611
|
-
};
|
|
17612
|
-
const isPureDerivedStatement = (statement) => {
|
|
17613
|
-
if (isNodeOfType(statement, "BlockStatement")) return statement.body.every((nestedStatement) => isPureDerivedStatement(nestedStatement));
|
|
17614
|
-
if (isNodeOfType(statement, "ReturnStatement")) return !statement.argument || isPureDerivedExpression(statement.argument);
|
|
17615
|
-
if (isNodeOfType(statement, "IfStatement")) return isPureDerivedExpression(statement.test) && isPureDerivedStatement(statement.consequent) && (!statement.alternate || isPureDerivedStatement(statement.alternate));
|
|
17616
|
-
return false;
|
|
17617
|
-
};
|
|
17618
|
-
const isPureDerivedFunction = (functionNode) => {
|
|
17619
|
-
if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return false;
|
|
17620
|
-
if (functionNode.async || functionNode.generator) return false;
|
|
17621
|
-
return isNodeOfType(functionNode.body, "BlockStatement") ? isPureDerivedStatement(functionNode.body) : isPureDerivedExpression(functionNode.body);
|
|
17622
|
-
};
|
|
17623
|
-
const resolvePureCalledFunctionSourceKeys = (reference, symbol, scopes) => {
|
|
17624
|
-
if (symbol.references.some((symbolReference) => symbolReference.flag !== "read")) return null;
|
|
17625
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
17626
|
-
const callExpression = referenceRoot.parent;
|
|
17627
|
-
if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== referenceRoot) return null;
|
|
17628
|
-
const functionNode = getFunctionValueNode(symbol);
|
|
17629
|
-
if (!functionNode || !isPureDerivedFunction(functionNode)) return null;
|
|
17630
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17631
|
-
for (const capturedReference of closureCaptures(functionNode, scopes)) {
|
|
17632
|
-
const capturedSymbol = capturedReference.resolvedSymbol;
|
|
17633
|
-
if (!capturedSymbol || capturedSymbol.id === symbol.id) continue;
|
|
17634
|
-
if (isOutsideAllFunctions(capturedSymbol) || symbolHasStableValue(capturedSymbol, scopes)) continue;
|
|
17635
|
-
const capturedKey = computeDepKey(capturedReference);
|
|
17636
|
-
if (!capturedKey) return null;
|
|
17637
|
-
if (capturedKey === capturedSymbol.name) {
|
|
17638
|
-
const nestedSourceKeys = resolveReactiveIdentitySourceKeys(capturedSymbol, scopes);
|
|
17639
|
-
if (nestedSourceKeys) {
|
|
17640
|
-
for (const nestedSourceKey of nestedSourceKeys) sourceKeys.add(nestedSourceKey);
|
|
17641
|
-
continue;
|
|
17642
|
-
}
|
|
17643
|
-
}
|
|
17644
|
-
sourceKeys.add(capturedKey);
|
|
17645
|
-
}
|
|
17646
|
-
return sourceKeys.size > 0 ? sourceKeys : null;
|
|
17647
|
-
};
|
|
17648
|
-
const mergeDerivedExpressionSourceKeys = (expressions, scopes, visitedSymbolIds) => {
|
|
17649
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17650
|
-
for (const expression of expressions) {
|
|
17651
|
-
const expressionSourceKeys = resolveDerivedExpressionSourceKeys(expression, scopes, visitedSymbolIds);
|
|
17652
|
-
if (!expressionSourceKeys) return null;
|
|
17653
|
-
for (const expressionSourceKey of expressionSourceKeys) sourceKeys.add(expressionSourceKey);
|
|
17654
|
-
}
|
|
17655
|
-
return sourceKeys;
|
|
17656
|
-
};
|
|
17657
|
-
const resolveDerivedExpressionSourceKeys = (expression, scopes, visitedSymbolIds) => {
|
|
17658
|
-
const candidate = unwrapExpression$3(expression);
|
|
17659
|
-
if (isNodeOfType(candidate, "Literal")) return /* @__PURE__ */ new Set();
|
|
17660
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
17661
|
-
if (scopes.isGlobalReference(candidate)) return /* @__PURE__ */ new Set();
|
|
17662
|
-
const sourceSymbol = scopes.symbolFor(candidate);
|
|
17663
|
-
if (!sourceSymbol) return null;
|
|
17664
|
-
if (isOutsideAllFunctions(sourceSymbol) || symbolHasStableValue(sourceSymbol, scopes)) return /* @__PURE__ */ new Set();
|
|
17665
|
-
if (sourceSymbol.kind === "const" && sourceSymbol.initializer && isNodeOfType(sourceSymbol.declarationNode, "VariableDeclarator") && sourceSymbol.declarationNode.id === sourceSymbol.bindingIdentifier && sourceSymbol.references.every((sourceReference) => sourceReference.flag === "read") && !visitedSymbolIds.has(sourceSymbol.id)) {
|
|
17666
|
-
visitedSymbolIds.add(sourceSymbol.id);
|
|
17667
|
-
const sourceKeys = resolveDerivedExpressionSourceKeys(sourceSymbol.initializer, scopes, visitedSymbolIds);
|
|
17668
|
-
visitedSymbolIds.delete(sourceSymbol.id);
|
|
17669
|
-
if (sourceKeys) return sourceKeys;
|
|
17670
|
-
}
|
|
17671
|
-
return new Set([sourceSymbol.name]);
|
|
17672
|
-
}
|
|
17673
|
-
if (isNodeOfType(candidate, "MemberExpression")) {
|
|
17674
|
-
if (hasComputedMemberExpression(candidate)) return null;
|
|
17675
|
-
const sourceKey = stringifyMemberChain(candidate);
|
|
17676
|
-
const rootIdentifier = getMemberRootIdentifier(candidate);
|
|
17677
|
-
const rootSymbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
|
|
17678
|
-
if (!sourceKey || !rootSymbol) return null;
|
|
17679
|
-
if (isOutsideAllFunctions(rootSymbol) || symbolHasStableValue(rootSymbol, scopes)) return /* @__PURE__ */ new Set();
|
|
17680
|
-
return new Set([sourceKey]);
|
|
17681
|
-
}
|
|
17682
|
-
if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return mergeDerivedExpressionSourceKeys([candidate.left, candidate.right], scopes, visitedSymbolIds);
|
|
17683
|
-
if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator !== "delete") return resolveDerivedExpressionSourceKeys(candidate.argument, scopes, visitedSymbolIds);
|
|
17684
|
-
if (isNodeOfType(candidate, "ConditionalExpression")) return mergeDerivedExpressionSourceKeys([
|
|
17685
|
-
candidate.test,
|
|
17686
|
-
candidate.consequent,
|
|
17687
|
-
candidate.alternate
|
|
17688
|
-
], scopes, visitedSymbolIds);
|
|
17689
|
-
if (isNodeOfType(candidate, "TemplateLiteral")) return mergeDerivedExpressionSourceKeys(candidate.expressions, scopes, visitedSymbolIds);
|
|
17690
|
-
if (isNodeOfType(candidate, "NewExpression")) {
|
|
17691
|
-
const callee = unwrapExpression$3(candidate.callee);
|
|
17692
|
-
if (!isNodeOfType(callee, "Identifier") || callee.name !== "Error" || !scopes.isGlobalReference(callee)) return null;
|
|
17693
|
-
const argumentsToAnalyze = [];
|
|
17694
|
-
for (const argument of candidate.arguments) {
|
|
17695
|
-
if (!isAstNode(argument) || isNodeOfType(argument, "SpreadElement")) return null;
|
|
17696
|
-
argumentsToAnalyze.push(argument);
|
|
17697
|
-
}
|
|
17698
|
-
return mergeDerivedExpressionSourceKeys(argumentsToAnalyze, scopes, visitedSymbolIds);
|
|
17699
|
-
}
|
|
17700
|
-
return null;
|
|
17701
|
-
};
|
|
17702
|
-
const resolveWriteControlSourceKeys = (assignment, boundaryFunction, scopes) => {
|
|
17703
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17704
|
-
let currentNode = assignment;
|
|
17705
|
-
while (currentNode.parent && currentNode.parent !== boundaryFunction) {
|
|
17706
|
-
const parentNode = currentNode.parent;
|
|
17707
|
-
if (isNodeOfType(parentNode, "IfStatement")) {
|
|
17708
|
-
if (parentNode.test === currentNode) return null;
|
|
17709
|
-
const testSourceKeys = resolveDerivedExpressionSourceKeys(parentNode.test, scopes, /* @__PURE__ */ new Set());
|
|
17710
|
-
if (!testSourceKeys) return null;
|
|
17711
|
-
for (const testSourceKey of testSourceKeys) sourceKeys.add(testSourceKey);
|
|
17712
|
-
} else if (!isNodeOfType(parentNode, "ExpressionStatement") && !isNodeOfType(parentNode, "BlockStatement")) return null;
|
|
17713
|
-
currentNode = parentNode;
|
|
17714
|
-
}
|
|
17715
|
-
return currentNode.parent === boundaryFunction ? sourceKeys : null;
|
|
17716
|
-
};
|
|
17717
|
-
const isReadOnlyInitialStateUse = (referenceNode, scopes) => {
|
|
17718
|
-
const referenceRoot = findTransparentExpressionRoot(referenceNode);
|
|
17719
|
-
const callExpression = referenceRoot.parent;
|
|
17720
|
-
return isNodeOfType(callExpression, "CallExpression") && callExpression.arguments.some((argument) => argument === referenceRoot) && isReactApiCall(callExpression, "useState", scopes, {
|
|
17721
|
-
allowGlobalReactNamespace: true,
|
|
17722
|
-
allowUnboundBareCalls: true,
|
|
17723
|
-
resolveNamedAliases: true
|
|
17724
|
-
});
|
|
17725
|
-
};
|
|
17726
|
-
const resolveRenderDerivedMutableSourceKeys = (capturedReference, symbol, scopes) => {
|
|
17727
|
-
if (symbol.kind !== "let" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
17728
|
-
const boundaryFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
17729
|
-
if (!boundaryFunction) return null;
|
|
17730
|
-
const capturingFunction = findEnclosingFunction$1(capturedReference.identifier);
|
|
17731
|
-
if (!capturingFunction || capturingFunction === boundaryFunction) return null;
|
|
17732
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17733
|
-
if (symbol.initializer) {
|
|
17734
|
-
const initializerSourceKeys = resolveDerivedExpressionSourceKeys(symbol.initializer, scopes, new Set([symbol.id]));
|
|
17735
|
-
if (!initializerSourceKeys) return null;
|
|
17736
|
-
for (const initializerSourceKey of initializerSourceKeys) sourceKeys.add(initializerSourceKey);
|
|
17737
|
-
}
|
|
17738
|
-
let writeCount = 0;
|
|
17739
|
-
for (const symbolReference of symbol.references) {
|
|
17740
|
-
if (symbolReference.flag === "read") {
|
|
17741
|
-
if (findEnclosingFunction$1(symbolReference.identifier) !== capturingFunction && !isReadOnlyInitialStateUse(symbolReference.identifier, scopes)) return null;
|
|
17742
|
-
continue;
|
|
17743
|
-
}
|
|
17744
|
-
if (symbolReference.flag !== "write") return null;
|
|
17745
|
-
const referenceRoot = findTransparentExpressionRoot(symbolReference.identifier);
|
|
17746
|
-
const assignment = referenceRoot.parent;
|
|
17747
|
-
if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== referenceRoot || findEnclosingFunction$1(referenceRoot) !== boundaryFunction) return null;
|
|
17748
|
-
const assignmentSourceKeys = resolveDerivedExpressionSourceKeys(assignment.right, scopes, new Set([symbol.id]));
|
|
17749
|
-
const controlSourceKeys = resolveWriteControlSourceKeys(assignment, boundaryFunction, scopes);
|
|
17750
|
-
if (!assignmentSourceKeys || !controlSourceKeys) return null;
|
|
17751
|
-
for (const assignmentSourceKey of assignmentSourceKeys) sourceKeys.add(assignmentSourceKey);
|
|
17752
|
-
for (const controlSourceKey of controlSourceKeys) sourceKeys.add(controlSourceKey);
|
|
17753
|
-
writeCount += 1;
|
|
17754
|
-
}
|
|
17755
|
-
return writeCount > 0 && sourceKeys.size > 0 ? sourceKeys : null;
|
|
17756
|
-
};
|
|
17757
17052
|
const isUseCallbackResultDep = (node, scopes) => {
|
|
17758
17053
|
const rootSymbol = getRootSymbol(node, scopes);
|
|
17759
17054
|
const initializer = rootSymbol?.initializer ? unwrapExpression$3(rootSymbol.initializer) : null;
|
|
@@ -18497,7 +17792,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
18497
17792
|
if (!isUsed) continue;
|
|
18498
17793
|
const reportNode = declaredKeyToReportNode.get(declaredKey) ?? depsArgument;
|
|
18499
17794
|
const rootSymbol = getRootSymbol(reportNode, context.scopes);
|
|
18500
|
-
if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) ||
|
|
17795
|
+
if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) || !isUnstableInitializer(rootSymbol.initializer)) continue;
|
|
18501
17796
|
context.report({
|
|
18502
17797
|
node: reportNode,
|
|
18503
17798
|
message: buildUnstableDepMessage(hookName, declaredKey)
|
|
@@ -29981,7 +29276,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
29981
29276
|
//#endregion
|
|
29982
29277
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
29983
29278
|
const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
29984
|
-
const isUndefinedIdentifier
|
|
29279
|
+
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
29985
29280
|
const noAccessKey = defineRule({
|
|
29986
29281
|
id: "no-access-key",
|
|
29987
29282
|
title: "accessKey attribute used",
|
|
@@ -30006,7 +29301,7 @@ const noAccessKey = defineRule({
|
|
|
30006
29301
|
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
30007
29302
|
const expression = attributeValue.expression;
|
|
30008
29303
|
if (!expression || expression.type === "JSXEmptyExpression") return;
|
|
30009
|
-
if (isUndefinedIdentifier
|
|
29304
|
+
if (isUndefinedIdentifier(expression)) return;
|
|
30010
29305
|
context.report({
|
|
30011
29306
|
node: accessKey,
|
|
30012
29307
|
message: MESSAGE$39
|
|
@@ -30771,12 +30066,6 @@ const isReactNamespaceImportReference = (ref) => Boolean(ref?.resolved?.defs.som
|
|
|
30771
30066
|
const importDeclaration = declarationNode.parent;
|
|
30772
30067
|
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
|
|
30773
30068
|
}));
|
|
30774
|
-
const isReactNamespaceReceiver = (analysis, node) => {
|
|
30775
|
-
const receiver = stripParenExpression(node);
|
|
30776
|
-
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
30777
|
-
const namespaceReference = getRef(analysis, receiver);
|
|
30778
|
-
return namespaceReference?.resolved ? isReactNamespaceImportReference(namespaceReference) : receiver.name === "React";
|
|
30779
|
-
};
|
|
30780
30069
|
const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
|
|
30781
30070
|
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return false;
|
|
30782
30071
|
const callee = stripParenExpression(declarator.init.callee);
|
|
@@ -30785,20 +30074,24 @@ const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
|
|
|
30785
30074
|
if (!reference?.resolved) return callee.name === hookName;
|
|
30786
30075
|
return isReactNamedImportReference(reference, hookName);
|
|
30787
30076
|
}
|
|
30788
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
|
|
30789
|
-
|
|
30077
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
|
|
30078
|
+
const namespaceReference = getRef(analysis, callee.object);
|
|
30079
|
+
if (!namespaceReference?.resolved) return callee.object.name === "React";
|
|
30080
|
+
return isReactNamespaceImportReference(namespaceReference);
|
|
30790
30081
|
};
|
|
30791
30082
|
const isHookCallee$1 = (analysis, node, hookName) => {
|
|
30792
30083
|
if (!node) return false;
|
|
30793
30084
|
if (isNodeOfType(node, "Identifier")) {
|
|
30794
30085
|
if (node.name === hookName) return true;
|
|
30795
30086
|
if (isReactNamedImportReference(getRef(analysis, node), hookName)) return true;
|
|
30796
|
-
const
|
|
30797
|
-
|
|
30798
|
-
if (parent && isNodeOfType(parent, "MemberExpression") && parent.object === receiverRoot && isReactNamespaceReceiver(analysis, node) && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
|
|
30087
|
+
const parent = node.parent;
|
|
30088
|
+
if (parent && isNodeOfType(parent, "MemberExpression") && isNodeOfType(parent.object, "Identifier") && parent.object.name === "React" && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
|
|
30799
30089
|
return false;
|
|
30800
30090
|
}
|
|
30801
|
-
if (isNodeOfType(node, "MemberExpression"))
|
|
30091
|
+
if (isNodeOfType(node, "MemberExpression")) {
|
|
30092
|
+
const receiver = stripParenExpression(node.object);
|
|
30093
|
+
return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
|
|
30094
|
+
}
|
|
30802
30095
|
return false;
|
|
30803
30096
|
};
|
|
30804
30097
|
const isUseEffect = (node) => {
|
|
@@ -30906,27 +30199,7 @@ const isRefCurrent = (ref) => {
|
|
|
30906
30199
|
if (!isNodeOfType(parent.property, "Identifier")) return false;
|
|
30907
30200
|
return parent.property.name === "current";
|
|
30908
30201
|
};
|
|
30909
|
-
const
|
|
30910
|
-
const visitedReferences = /* @__PURE__ */ new Set();
|
|
30911
|
-
let currentReference = ref;
|
|
30912
|
-
while (currentReference && !visitedReferences.has(currentReference)) {
|
|
30913
|
-
if (isStateSetter(analysis, currentReference)) return currentReference;
|
|
30914
|
-
visitedReferences.add(currentReference);
|
|
30915
|
-
const definitions = currentReference.resolved?.defs ?? [];
|
|
30916
|
-
if (definitions.length !== 1) return null;
|
|
30917
|
-
const definitionNode = definitions[0].node;
|
|
30918
|
-
if (!isNodeOfType(definitionNode, "VariableDeclarator")) return null;
|
|
30919
|
-
if (!isNodeOfType(definitionNode.id, "Identifier")) return null;
|
|
30920
|
-
const declaration = definitionNode.parent;
|
|
30921
|
-
if (!isNodeOfType(declaration, "VariableDeclaration") || declaration.kind !== "const") return null;
|
|
30922
|
-
if (!definitionNode.init) return null;
|
|
30923
|
-
const initializer = stripParenExpression(definitionNode.init);
|
|
30924
|
-
if (!isNodeOfType(initializer, "Identifier")) return null;
|
|
30925
|
-
currentReference = getRef(analysis, initializer);
|
|
30926
|
-
}
|
|
30927
|
-
return null;
|
|
30928
|
-
};
|
|
30929
|
-
const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => resolveStateSetterReference(analysis, innerRef) !== null);
|
|
30202
|
+
const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isStateSetter(analysis, innerRef));
|
|
30930
30203
|
const isSyncStateSetterCall = (analysis, ref, effectFn) => isStateSetterCall(analysis, ref) && isSynchronous(ref.identifier, effectFn) && !resolvesToAsyncFunction(ref);
|
|
30931
30204
|
const HANDLER_NAMED_METHOD_PATTERN = /^(on|handle)[A-Z]/;
|
|
30932
30205
|
const SYNCHRONOUS_CALLBACK_ARGUMENT_INDEX_BY_METHOD = new Map([
|
|
@@ -31077,11 +30350,9 @@ const isPropCallbackInvocationRef = (analysis, ref, options = {}) => {
|
|
|
31077
30350
|
};
|
|
31078
30351
|
const isRefCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isRefCurrent(innerRef) || isRef(analysis, innerRef));
|
|
31079
30352
|
const getUseStateDecl = (analysis, ref) => {
|
|
31080
|
-
|
|
31081
|
-
|
|
31082
|
-
|
|
31083
|
-
});
|
|
31084
|
-
return definition ? definition.node : null;
|
|
30353
|
+
let node = getUpstreamRefs(analysis, ref).find((upRef) => isHookCallee$1(analysis, upRef.identifier, "useState"))?.identifier;
|
|
30354
|
+
while (node && !isNodeOfType(node, "VariableDeclarator")) node = node.parent;
|
|
30355
|
+
return node ?? null;
|
|
31085
30356
|
};
|
|
31086
30357
|
const isCleanupReturnArgument = (analysis, node) => {
|
|
31087
30358
|
if (isFunctionLike$1(node)) return true;
|
|
@@ -31244,88 +30515,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
|
|
|
31244
30515
|
if (HANDLER_BINDING_NAME_PATTERN.test(bindingName)) return true;
|
|
31245
30516
|
return isSetterWiredToJsxHandler(componentFunction, bindingName);
|
|
31246
30517
|
};
|
|
31247
|
-
const
|
|
31248
|
-
const functionMetadata = functionNode;
|
|
31249
|
-
return functionMetadata.async !== true && functionMetadata.generator !== true;
|
|
31250
|
-
};
|
|
31251
|
-
const findBindingVariable = (analysis, bindingIdentifier) => {
|
|
31252
|
-
for (const scope of analysis.scopeManager.scopes) for (const variable of scope.variables) if (variable.identifiers.includes(bindingIdentifier)) return variable;
|
|
31253
|
-
return null;
|
|
31254
|
-
};
|
|
31255
|
-
const getImmutableFunctionVariable = (analysis, componentFunction, functionNode) => {
|
|
31256
|
-
if (!isSynchronousFunction(functionNode) || !isAstDescendant(functionNode, componentFunction)) return null;
|
|
31257
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
31258
|
-
if (!bindingIdentifier) return null;
|
|
31259
|
-
const variable = findBindingVariable(analysis, bindingIdentifier);
|
|
31260
|
-
if (!variable || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && !reference.init)) return null;
|
|
31261
|
-
const definition = variable.defs[0];
|
|
31262
|
-
if (definition.type === "FunctionName") return definition.node === functionNode ? variable : null;
|
|
31263
|
-
if (definition.type !== "Variable") return null;
|
|
31264
|
-
const declarator = definition.node;
|
|
31265
|
-
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return null;
|
|
31266
|
-
if (declarator.init === functionNode) return variable;
|
|
31267
|
-
if (isNodeOfType(declarator.init, "CallExpression") && declarator.init.arguments?.[0] === functionNode && isGenuineReactHookDeclarator(analysis, declarator, "useCallback")) return variable;
|
|
31268
|
-
return null;
|
|
31269
|
-
};
|
|
31270
|
-
const getJsxEventValueAttribute = (identifier) => {
|
|
31271
|
-
const expression = findTransparentExpressionRoot(identifier);
|
|
31272
|
-
const expressionContainer = expression.parent;
|
|
31273
|
-
if (!isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== expression) return null;
|
|
31274
|
-
const attribute = expressionContainer.parent;
|
|
31275
|
-
if (!isNodeOfType(attribute, "JSXAttribute")) return null;
|
|
31276
|
-
const attributeName = getJsxAttributeName(attribute.name);
|
|
31277
|
-
return attributeName && isEventHandlerName(attributeName) ? attribute : null;
|
|
31278
|
-
};
|
|
31279
|
-
const getInlineJsxEventCallbackAttribute = (callExpression) => {
|
|
31280
|
-
const callbackFunction = findEnclosingFunction$1(callExpression);
|
|
31281
|
-
if (!callbackFunction || !isSynchronousFunction(callbackFunction)) return null;
|
|
31282
|
-
return getJsxEventValueAttribute(callbackFunction);
|
|
31283
|
-
};
|
|
31284
|
-
const isReactHookDependencyReference = (identifier) => {
|
|
31285
|
-
const expression = findTransparentExpressionRoot(identifier);
|
|
31286
|
-
const dependencyArray = expression.parent;
|
|
31287
|
-
if (!isNodeOfType(dependencyArray, "ArrayExpression") || !(dependencyArray.elements ?? []).includes(expression)) return false;
|
|
31288
|
-
const hookCall = dependencyArray.parent;
|
|
31289
|
-
if (!isNodeOfType(hookCall, "CallExpression") || hookCall.arguments?.[1] !== dependencyArray) return false;
|
|
31290
|
-
const callee = hookCall.callee;
|
|
31291
|
-
if (isNodeOfType(callee, "Identifier")) return /^use[A-Z0-9]/.test(callee.name);
|
|
31292
|
-
return Boolean(isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && /^use[A-Z0-9]/.test(callee.property.name));
|
|
31293
|
-
};
|
|
31294
|
-
const hasReachableJsxEventCallPath = (analysis, context, componentFunction, functionVariable, visitedVariables) => {
|
|
31295
|
-
if (visitedVariables.has(functionVariable)) return false;
|
|
31296
|
-
const nextVisitedVariables = new Set(visitedVariables).add(functionVariable);
|
|
31297
|
-
const callExpressions = [];
|
|
31298
|
-
let hasDirectJsxEventReference = false;
|
|
31299
|
-
for (const reference of functionVariable.references) {
|
|
31300
|
-
if (reference.init) continue;
|
|
31301
|
-
const identifier = reference.identifier;
|
|
31302
|
-
if (reference.isWrite()) return false;
|
|
31303
|
-
const jsxEventValueAttribute = getJsxEventValueAttribute(identifier);
|
|
31304
|
-
if (jsxEventValueAttribute) {
|
|
31305
|
-
if (isNodeReachableWithinFunction(jsxEventValueAttribute, context)) hasDirectJsxEventReference = true;
|
|
31306
|
-
continue;
|
|
31307
|
-
}
|
|
31308
|
-
if (isReactHookDependencyReference(identifier)) continue;
|
|
31309
|
-
const callExpression = getCallExpr(reference);
|
|
31310
|
-
if (!callExpression) return false;
|
|
31311
|
-
const jsxEventCallbackAttribute = getInlineJsxEventCallbackAttribute(callExpression);
|
|
31312
|
-
if (jsxEventCallbackAttribute) {
|
|
31313
|
-
if (isNodeReachableWithinFunction(callExpression, context) && isNodeReachableWithinFunction(jsxEventCallbackAttribute, context)) hasDirectJsxEventReference = true;
|
|
31314
|
-
continue;
|
|
31315
|
-
}
|
|
31316
|
-
callExpressions.push(callExpression);
|
|
31317
|
-
}
|
|
31318
|
-
if (hasDirectJsxEventReference) return true;
|
|
31319
|
-
for (const callExpression of callExpressions) {
|
|
31320
|
-
if (!isNodeReachableWithinFunction(callExpression, context)) continue;
|
|
31321
|
-
const callerFunction = findEnclosingFunction$1(callExpression);
|
|
31322
|
-
if (!callerFunction || callerFunction === componentFunction) continue;
|
|
31323
|
-
const callerVariable = getImmutableFunctionVariable(analysis, componentFunction, callerFunction);
|
|
31324
|
-
if (callerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, callerVariable, nextVisitedVariables)) return true;
|
|
31325
|
-
}
|
|
31326
|
-
return false;
|
|
31327
|
-
};
|
|
31328
|
-
const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, includeDeferredWriters = false) => {
|
|
30518
|
+
const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
|
|
31329
30519
|
if (!setterRef.resolved) return false;
|
|
31330
30520
|
const componentFunction = findEnclosingFunction$1(effectNode);
|
|
31331
30521
|
if (!componentFunction) return false;
|
|
@@ -31334,11 +30524,6 @@ const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, incl
|
|
|
31334
30524
|
const identifier = reference.identifier;
|
|
31335
30525
|
if (isAstDescendant(identifier, effectNode)) continue;
|
|
31336
30526
|
if (isIndependentWriterIdentifier(componentFunction, identifier, includeDeferredWriters)) return true;
|
|
31337
|
-
if (!isNodeReachableWithinFunction(identifier, context)) continue;
|
|
31338
|
-
const writerFunction = findEnclosingFunction$1(identifier);
|
|
31339
|
-
if (!writerFunction || writerFunction === componentFunction) continue;
|
|
31340
|
-
const writerVariable = getImmutableFunctionVariable(analysis, componentFunction, writerFunction);
|
|
31341
|
-
if (writerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, writerVariable, /* @__PURE__ */ new Set())) return true;
|
|
31342
30527
|
}
|
|
31343
30528
|
return false;
|
|
31344
30529
|
};
|
|
@@ -32228,7 +31413,7 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
|
|
|
32228
31413
|
}
|
|
32229
31414
|
return false;
|
|
32230
31415
|
};
|
|
32231
|
-
const collectEffectStateWriteFacts = (analysis,
|
|
31416
|
+
const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
|
|
32232
31417
|
const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
|
|
32233
31418
|
if (frames.length === 0) return [];
|
|
32234
31419
|
const effectHasCleanup = hasCleanup(analysis, effectNode);
|
|
@@ -32258,7 +31443,7 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
|
|
|
32258
31443
|
for (const returnedExpression of returnedExpressions) mergeEvidence(valueEvidence, collectValueEvidence(analysis, returnedExpression, updaterFrame, remainingValueCallFrames));
|
|
32259
31444
|
} else valueEvidence = collectValueEvidence(analysis, writtenValue, frame, remainingValueCallFrames);
|
|
32260
31445
|
const sourceReferences = [...valueEvidence.sourceReferences].filter((sourceReference) => getUseStateDecl(analysis, sourceReference) !== stateDeclarator);
|
|
32261
|
-
const hasIndependentWriter = hasUserInputSetterWriter(
|
|
31446
|
+
const hasIndependentWriter = hasUserInputSetterWriter(setterReference, effectNode, true);
|
|
32262
31447
|
const doesMatchStateInitializer = matchesStateInitializer(analysis, callExpression, stateDeclarator);
|
|
32263
31448
|
if (effectHasCleanup && (frame.isDeferred || valueEvidence.hasUnknownSource || valueEvidence.hasDeferredIntroducedValue || valueEvidence.readsExternalValue)) cleanupManagedStateDeclarators.add(stateDeclarator);
|
|
32264
31449
|
const isRenderKnownCopy = sourceReferences.length > 0 && !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue && !hasIndependentWriter;
|
|
@@ -32293,18 +31478,13 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
32293
31478
|
tags: ["test-noise"],
|
|
32294
31479
|
recommendation: "Adjust the state inline during render with a `prev`-prop comparison (`if (prop !== prevProp) { setPrevProp(prop); setX(...); }`), or refactor to remove the duplicated state. Routing the adjustment through a useEffect forces an extra render with a stale UI between the two commits. See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes",
|
|
32295
31480
|
create: (context) => ({ CallExpression(node) {
|
|
32296
|
-
if (!
|
|
32297
|
-
allowGlobalReactNamespace: true,
|
|
32298
|
-
allowUnboundBareCalls: true,
|
|
32299
|
-
resolveConditionalAliases: true,
|
|
32300
|
-
resolveNamedAliases: true
|
|
32301
|
-
})) return;
|
|
31481
|
+
if (!isUseEffect(node)) return;
|
|
32302
31482
|
const analysis = getProgramAnalysis(node);
|
|
32303
31483
|
if (!analysis) return;
|
|
32304
31484
|
const dependencyReferences = getEffectDepsRefs(analysis, node);
|
|
32305
31485
|
if (!dependencyReferences) return;
|
|
32306
31486
|
if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
|
|
32307
|
-
for (const fact of collectEffectStateWriteFacts(analysis,
|
|
31487
|
+
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
32308
31488
|
if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
|
|
32309
31489
|
context.report({
|
|
32310
31490
|
node: fact.callExpression,
|
|
@@ -35025,7 +34205,7 @@ const noChainStateUpdates = defineRule({
|
|
|
35025
34205
|
if (!callExpr) continue;
|
|
35026
34206
|
if (!isReachableFromStateTrigger(callExpr)) continue;
|
|
35027
34207
|
if (!readsPostMountValueThroughLocals(callExpr, effectFn, { ignoreBareRefCurrent: true })) continue;
|
|
35028
|
-
const declarator = getUseStateDeclarator(
|
|
34208
|
+
const declarator = getUseStateDeclarator(ref);
|
|
35029
34209
|
if (declarator) domSyncedStateDeclarators.add(declarator);
|
|
35030
34210
|
}
|
|
35031
34211
|
for (const ref of effectFnRefs) {
|
|
@@ -35034,7 +34214,7 @@ const noChainStateUpdates = defineRule({
|
|
|
35034
34214
|
if (!callExpr) continue;
|
|
35035
34215
|
if (!isReachableFromStateTrigger(callExpr)) continue;
|
|
35036
34216
|
if (getArgsUpstreamRefs(analysis, ref).some((argRef) => isState(analysis, argRef))) continue;
|
|
35037
|
-
const setterDeclarator = getUseStateDeclarator(
|
|
34217
|
+
const setterDeclarator = getUseStateDeclarator(ref);
|
|
35038
34218
|
if (setterDeclarator && domSyncedStateDeclarators.has(setterDeclarator)) continue;
|
|
35039
34219
|
const isSelfTargeting = setterDeclarator !== null && stateDepDeclarators.has(setterDeclarator);
|
|
35040
34220
|
const setterArguments = isNodeOfType(callExpr, "CallExpression") ? callExpr.arguments ?? [] : [];
|
|
@@ -36875,15 +36055,10 @@ const noDerivedState = defineRule({
|
|
|
36875
36055
|
for (const fact of collectRenderStateWriteFacts(analysis, componentBody, context.filename)) reportStateWrite(fact.callExpression, fact.stateDeclarator);
|
|
36876
36056
|
} }).visitors,
|
|
36877
36057
|
CallExpression(node) {
|
|
36878
|
-
if (!
|
|
36879
|
-
allowGlobalReactNamespace: true,
|
|
36880
|
-
allowUnboundBareCalls: true,
|
|
36881
|
-
resolveConditionalAliases: true,
|
|
36882
|
-
resolveNamedAliases: true
|
|
36883
|
-
})) return;
|
|
36058
|
+
if (!isUseEffect(node)) return;
|
|
36884
36059
|
const analysis = getProgramAnalysis(node);
|
|
36885
36060
|
if (!analysis) return;
|
|
36886
|
-
for (const fact of collectEffectStateWriteFacts(analysis,
|
|
36061
|
+
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
36887
36062
|
if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
|
|
36888
36063
|
reportStateWrite(fact.callExpression, fact.stateDeclarator);
|
|
36889
36064
|
}
|
|
@@ -36900,15 +36075,10 @@ const noDerivedStateEffect = defineRule({
|
|
|
36900
36075
|
tags: ["test-noise"],
|
|
36901
36076
|
recommendation: "Work out derived values while rendering: `const x = fn(dep)`. To reset a component's state when a prop changes, give it a key prop: `<Component key={prop} />`. See https://react.dev/learn/you-might-not-need-an-effect",
|
|
36902
36077
|
create: (context) => ({ CallExpression(node) {
|
|
36903
|
-
if (!
|
|
36904
|
-
allowGlobalReactNamespace: true,
|
|
36905
|
-
allowUnboundBareCalls: true,
|
|
36906
|
-
resolveConditionalAliases: true,
|
|
36907
|
-
resolveNamedAliases: true
|
|
36908
|
-
})) return;
|
|
36078
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
36909
36079
|
const analysis = getProgramAnalysis(node);
|
|
36910
36080
|
if (!analysis) return;
|
|
36911
|
-
if (!collectEffectStateWriteFacts(analysis,
|
|
36081
|
+
if (!collectEffectStateWriteFacts(analysis, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
|
|
36912
36082
|
context.report({
|
|
36913
36083
|
node,
|
|
36914
36084
|
message: "You pay an extra render for state you can derive from other values."
|
|
@@ -37384,20 +36554,9 @@ const noDidMountSetState = defineRule({
|
|
|
37384
36554
|
}
|
|
37385
36555
|
});
|
|
37386
36556
|
//#endregion
|
|
37387
|
-
//#region src/plugin/utils/find-enclosing-class.ts
|
|
37388
|
-
const findEnclosingClass = (node) => {
|
|
37389
|
-
let ancestor = node.parent;
|
|
37390
|
-
while (ancestor) {
|
|
37391
|
-
if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
|
|
37392
|
-
ancestor = ancestor.parent ?? null;
|
|
37393
|
-
}
|
|
37394
|
-
return null;
|
|
37395
|
-
};
|
|
37396
|
-
//#endregion
|
|
37397
36557
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
37398
36558
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
37399
36559
|
const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
37400
|
-
const DIFFERENCE_OPERATORS = new Set(["!=", "!=="]);
|
|
37401
36560
|
const EQUALITY_OPERATORS = new Set([
|
|
37402
36561
|
"==",
|
|
37403
36562
|
"===",
|
|
@@ -37409,8 +36568,6 @@ const FUNCTION_NODE_TYPES = new Set([
|
|
|
37409
36568
|
"FunctionExpression",
|
|
37410
36569
|
"ArrowFunctionExpression"
|
|
37411
36570
|
]);
|
|
37412
|
-
const CLASS_NODE_TYPES = new Set(["ClassDeclaration", "ClassExpression"]);
|
|
37413
|
-
const callbackRefFieldNamesByClass = /* @__PURE__ */ new WeakMap();
|
|
37414
36571
|
const isLifecycleMethodFunction = (node) => {
|
|
37415
36572
|
if (!FUNCTION_NODE_TYPES.has(node.type)) return false;
|
|
37416
36573
|
const parent = node.parent;
|
|
@@ -37466,187 +36623,6 @@ const getStaticMemberName = (node) => {
|
|
|
37466
36623
|
if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
|
|
37467
36624
|
return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
|
|
37468
36625
|
};
|
|
37469
|
-
const getMemberIdentity = (property) => {
|
|
37470
|
-
const propertyName = getPropertyKeyName$2(property);
|
|
37471
|
-
if (propertyName !== void 0) return isNodeOfType(property, "PrivateIdentifier") ? `#${propertyName}` : propertyName;
|
|
37472
|
-
return isNodeOfType(property, "Literal") && typeof property.value === "string" ? property.value : null;
|
|
37473
|
-
};
|
|
37474
|
-
const collectPreviousSourcePaths = (pattern, domain, members, previousSourcePaths) => {
|
|
37475
|
-
if (!pattern) return;
|
|
37476
|
-
const unwrappedPattern = stripParenExpression(pattern);
|
|
37477
|
-
if (isNodeOfType(unwrappedPattern, "Identifier")) {
|
|
37478
|
-
previousSourcePaths.set(unwrappedPattern.name, {
|
|
37479
|
-
domain,
|
|
37480
|
-
members: [...members],
|
|
37481
|
-
source: "previous"
|
|
37482
|
-
});
|
|
37483
|
-
return;
|
|
37484
|
-
}
|
|
37485
|
-
if (isNodeOfType(unwrappedPattern, "AssignmentPattern")) {
|
|
37486
|
-
collectPreviousSourcePaths(unwrappedPattern.left, domain, members, previousSourcePaths);
|
|
37487
|
-
return;
|
|
37488
|
-
}
|
|
37489
|
-
if (!isNodeOfType(unwrappedPattern, "ObjectPattern")) return;
|
|
37490
|
-
for (const property of unwrappedPattern.properties) {
|
|
37491
|
-
if (!isNodeOfType(property, "Property")) continue;
|
|
37492
|
-
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
37493
|
-
if (!propertyName) continue;
|
|
37494
|
-
collectPreviousSourcePaths(property.value, domain, [...members, propertyName], previousSourcePaths);
|
|
37495
|
-
}
|
|
37496
|
-
};
|
|
37497
|
-
const getStateSourcePath = (node, previousSourcePaths) => {
|
|
37498
|
-
let currentNode = stripParenExpression(node);
|
|
37499
|
-
const members = [];
|
|
37500
|
-
while (isNodeOfType(currentNode, "MemberExpression")) {
|
|
37501
|
-
const memberName = getStaticMemberName(currentNode);
|
|
37502
|
-
if (!memberName) return null;
|
|
37503
|
-
members.unshift(memberName);
|
|
37504
|
-
currentNode = stripParenExpression(currentNode.object);
|
|
37505
|
-
}
|
|
37506
|
-
if (isNodeOfType(currentNode, "ThisExpression")) {
|
|
37507
|
-
const [domain, ...pathMembers] = members;
|
|
37508
|
-
if (domain !== "props" && domain !== "state") return null;
|
|
37509
|
-
return {
|
|
37510
|
-
domain,
|
|
37511
|
-
members: pathMembers,
|
|
37512
|
-
source: "current"
|
|
37513
|
-
};
|
|
37514
|
-
}
|
|
37515
|
-
if (!isNodeOfType(currentNode, "Identifier")) return null;
|
|
37516
|
-
const previousSourcePath = previousSourcePaths.get(currentNode.name);
|
|
37517
|
-
return previousSourcePath ? {
|
|
37518
|
-
...previousSourcePath,
|
|
37519
|
-
members: [...previousSourcePath.members, ...members]
|
|
37520
|
-
} : null;
|
|
37521
|
-
};
|
|
37522
|
-
const haveMatchingStateSourcePaths = (left, right) => left.domain === right.domain && left.members.length === right.members.length && left.members.every((member, index) => member === right.members[index]);
|
|
37523
|
-
const collectConjunctiveStateSourceComparisons = (test, previousSourcePaths, comparisons) => {
|
|
37524
|
-
const expression = stripParenExpression(test);
|
|
37525
|
-
if (isNodeOfType(expression, "LogicalExpression") && expression.operator === "&&") {
|
|
37526
|
-
collectConjunctiveStateSourceComparisons(expression.left, previousSourcePaths, comparisons);
|
|
37527
|
-
collectConjunctiveStateSourceComparisons(expression.right, previousSourcePaths, comparisons);
|
|
37528
|
-
return;
|
|
37529
|
-
}
|
|
37530
|
-
if (!isNodeOfType(expression, "BinaryExpression") || !EQUALITY_OPERATORS.has(expression.operator)) return;
|
|
37531
|
-
const leftPath = getStateSourcePath(expression.left, previousSourcePaths);
|
|
37532
|
-
const rightPath = getStateSourcePath(expression.right, previousSourcePaths);
|
|
37533
|
-
if (Boolean(leftPath) === Boolean(rightPath)) return;
|
|
37534
|
-
const path = leftPath ?? rightPath;
|
|
37535
|
-
if (!path) return;
|
|
37536
|
-
comparisons.push({
|
|
37537
|
-
comparedValue: leftPath ? expression.right : expression.left,
|
|
37538
|
-
isDifference: DIFFERENCE_OPERATORS.has(expression.operator),
|
|
37539
|
-
path
|
|
37540
|
-
});
|
|
37541
|
-
};
|
|
37542
|
-
const isHistoricalToCurrentTransitionGuard = (test, previousSourcePaths) => {
|
|
37543
|
-
const expression = stripParenExpression(test);
|
|
37544
|
-
if (isNodeOfType(expression, "LogicalExpression") && expression.operator === "||") return isHistoricalToCurrentTransitionGuard(expression.left, previousSourcePaths) && isHistoricalToCurrentTransitionGuard(expression.right, previousSourcePaths);
|
|
37545
|
-
const comparisons = [];
|
|
37546
|
-
collectConjunctiveStateSourceComparisons(expression, previousSourcePaths, comparisons);
|
|
37547
|
-
return comparisons.some((comparison, index) => comparisons.slice(index + 1).some((candidate) => comparison.path.source !== candidate.path.source && comparison.isDifference !== candidate.isDifference && haveMatchingStateSourcePaths(comparison.path, candidate.path) && areExpressionsStructurallyEqual(comparison.comparedValue, candidate.comparedValue)));
|
|
37548
|
-
};
|
|
37549
|
-
const getThisFieldName = (node) => {
|
|
37550
|
-
const unwrappedNode = stripParenExpression(node);
|
|
37551
|
-
if (!isNodeOfType(unwrappedNode, "MemberExpression") || unwrappedNode.computed === true || !isNodeOfType(stripParenExpression(unwrappedNode.object), "ThisExpression")) return null;
|
|
37552
|
-
return getMemberIdentity(unwrappedNode.property);
|
|
37553
|
-
};
|
|
37554
|
-
const isUndefinedIdentifier = (node) => {
|
|
37555
|
-
const unwrappedNode = stripParenExpression(node);
|
|
37556
|
-
return isNodeOfType(unwrappedNode, "Identifier") && unwrappedNode.name === "undefined";
|
|
37557
|
-
};
|
|
37558
|
-
const isDirectRefParameterValue = (node, parameterSymbolId, scopes) => {
|
|
37559
|
-
const unwrappedNode = stripParenExpression(node);
|
|
37560
|
-
if (isNodeOfType(unwrappedNode, "Identifier")) return scopes.symbolFor(unwrappedNode)?.id === parameterSymbolId;
|
|
37561
|
-
if (!isNodeOfType(unwrappedNode, "LogicalExpression") || unwrappedNode.operator !== "??") return false;
|
|
37562
|
-
const left = stripParenExpression(unwrappedNode.left);
|
|
37563
|
-
return isNodeOfType(left, "Identifier") && scopes.symbolFor(left)?.id === parameterSymbolId && isUndefinedIdentifier(unwrappedNode.right);
|
|
37564
|
-
};
|
|
37565
|
-
const getCallbackRefAssignedFields = (callback, scopes) => {
|
|
37566
|
-
const firstParameter = (callback.params ?? [])[0];
|
|
37567
|
-
if (!firstParameter) return /* @__PURE__ */ new Set();
|
|
37568
|
-
const parameterIdentifier = isNodeOfType(firstParameter, "AssignmentPattern") ? firstParameter.left : firstParameter;
|
|
37569
|
-
if (!isNodeOfType(parameterIdentifier, "Identifier")) return /* @__PURE__ */ new Set();
|
|
37570
|
-
const parameterSymbolId = scopes.symbolFor(parameterIdentifier)?.id;
|
|
37571
|
-
if (parameterSymbolId === void 0) return /* @__PURE__ */ new Set();
|
|
37572
|
-
const body = callback.body;
|
|
37573
|
-
if (!body) return /* @__PURE__ */ new Set();
|
|
37574
|
-
const assignedFieldNames = /* @__PURE__ */ new Set();
|
|
37575
|
-
walkAst(body, (node) => {
|
|
37576
|
-
if (node !== body && (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node) || CLASS_NODE_TYPES.has(node.type))) return false;
|
|
37577
|
-
const assignmentTarget = isNodeOfType(node, "AssignmentExpression") && node.left || isNodeOfType(node, "UpdateExpression") && node.argument || isNodeOfType(node, "UnaryExpression") && node.operator === "delete" && node.argument || null;
|
|
37578
|
-
if (!assignmentTarget) return;
|
|
37579
|
-
const fieldName = getThisFieldName(assignmentTarget);
|
|
37580
|
-
if (!fieldName) return;
|
|
37581
|
-
if (isNodeOfType(node, "AssignmentExpression") && node.operator === "=" && isDirectRefParameterValue(node.right, parameterSymbolId, scopes)) {
|
|
37582
|
-
assignedFieldNames.add(fieldName);
|
|
37583
|
-
return;
|
|
37584
|
-
}
|
|
37585
|
-
assignedFieldNames.delete(fieldName);
|
|
37586
|
-
});
|
|
37587
|
-
return assignedFieldNames;
|
|
37588
|
-
};
|
|
37589
|
-
const getClassMemberCallback = (classNode, memberName) => {
|
|
37590
|
-
const classBody = classNode.body?.body ?? [];
|
|
37591
|
-
for (const member of classBody) {
|
|
37592
|
-
if (!isNodeOfType(member, "MethodDefinition") && !isNodeOfType(member, "PropertyDefinition")) continue;
|
|
37593
|
-
if (member.static === true) continue;
|
|
37594
|
-
const key = member.key;
|
|
37595
|
-
if (getMemberIdentity(key) !== memberName) continue;
|
|
37596
|
-
const value = member.value;
|
|
37597
|
-
return value && FUNCTION_NODE_TYPES.has(value.type) ? value : null;
|
|
37598
|
-
}
|
|
37599
|
-
return null;
|
|
37600
|
-
};
|
|
37601
|
-
const collectCallbackRefFieldsFromExpression = (expression, classNode, fieldNames, scopes) => {
|
|
37602
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
37603
|
-
if (FUNCTION_NODE_TYPES.has(unwrappedExpression.type)) {
|
|
37604
|
-
for (const fieldName of getCallbackRefAssignedFields(unwrappedExpression, scopes)) fieldNames.add(fieldName);
|
|
37605
|
-
return;
|
|
37606
|
-
}
|
|
37607
|
-
const handlerName = getThisFieldName(unwrappedExpression);
|
|
37608
|
-
if (handlerName) {
|
|
37609
|
-
const callback = getClassMemberCallback(classNode, handlerName);
|
|
37610
|
-
if (callback) for (const fieldName of getCallbackRefAssignedFields(callback, scopes)) fieldNames.add(fieldName);
|
|
37611
|
-
return;
|
|
37612
|
-
}
|
|
37613
|
-
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
|
|
37614
|
-
collectCallbackRefFieldsFromExpression(unwrappedExpression.consequent, classNode, fieldNames, scopes);
|
|
37615
|
-
collectCallbackRefFieldsFromExpression(unwrappedExpression.alternate, classNode, fieldNames, scopes);
|
|
37616
|
-
return;
|
|
37617
|
-
}
|
|
37618
|
-
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
37619
|
-
if (unwrappedExpression.operator !== "&&") collectCallbackRefFieldsFromExpression(unwrappedExpression.left, classNode, fieldNames, scopes);
|
|
37620
|
-
collectCallbackRefFieldsFromExpression(unwrappedExpression.right, classNode, fieldNames, scopes);
|
|
37621
|
-
}
|
|
37622
|
-
};
|
|
37623
|
-
const getCallbackRefFieldNames = (classNode, scopes) => {
|
|
37624
|
-
if (!classNode) return /* @__PURE__ */ new Set();
|
|
37625
|
-
const cachedFieldNames = callbackRefFieldNamesByClass.get(classNode);
|
|
37626
|
-
if (cachedFieldNames) return cachedFieldNames;
|
|
37627
|
-
const fieldNames = /* @__PURE__ */ new Set();
|
|
37628
|
-
const classBody = classNode.body;
|
|
37629
|
-
if (classBody) walkAst(classBody, (node) => {
|
|
37630
|
-
if (node !== classBody && CLASS_NODE_TYPES.has(node.type)) return false;
|
|
37631
|
-
if (!isNodeOfType(node, "JSXAttribute") || !isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "ref" || !node.value || !isNodeOfType(node.value, "JSXExpressionContainer") || !node.value.expression) return;
|
|
37632
|
-
collectCallbackRefFieldsFromExpression(node.value.expression, classNode, fieldNames, scopes);
|
|
37633
|
-
});
|
|
37634
|
-
callbackRefFieldNamesByClass.set(classNode, fieldNames);
|
|
37635
|
-
return fieldNames;
|
|
37636
|
-
};
|
|
37637
|
-
const collectLifecycleWrittenFieldNames = (lifecycleFunction) => {
|
|
37638
|
-
const fieldNames = /* @__PURE__ */ new Set();
|
|
37639
|
-
const body = lifecycleFunction.body;
|
|
37640
|
-
if (!body) return fieldNames;
|
|
37641
|
-
walkAst(body, (node) => {
|
|
37642
|
-
if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
|
|
37643
|
-
const target = isNodeOfType(node, "AssignmentExpression") && node.left || isNodeOfType(node, "UpdateExpression") && node.argument || null;
|
|
37644
|
-
if (!target) return;
|
|
37645
|
-
const fieldName = getThisFieldName(target);
|
|
37646
|
-
if (fieldName) fieldNames.add(fieldName);
|
|
37647
|
-
});
|
|
37648
|
-
return fieldNames;
|
|
37649
|
-
};
|
|
37650
36626
|
const getThisStateFieldName = (node) => {
|
|
37651
36627
|
const unwrappedNode = stripParenExpression(node);
|
|
37652
36628
|
if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
|
|
@@ -37664,17 +36640,15 @@ const collectLocalInitializers = (lifecycleFunction) => {
|
|
|
37664
36640
|
});
|
|
37665
36641
|
return initializers;
|
|
37666
36642
|
};
|
|
37667
|
-
const derivesFromPostMountValue = (node, localInitializers,
|
|
36643
|
+
const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
|
|
37668
36644
|
if (readsPostMountValue(node)) return true;
|
|
37669
|
-
const fieldName = getThisFieldName(node);
|
|
37670
|
-
if (fieldName && callbackRefFieldNames.has(fieldName)) return true;
|
|
37671
36645
|
const referencedNames = /* @__PURE__ */ new Set();
|
|
37672
36646
|
collectReferenceIdentifierNames(node, referencedNames);
|
|
37673
36647
|
for (const referencedName of referencedNames) {
|
|
37674
36648
|
if (visitedNames.has(referencedName)) continue;
|
|
37675
36649
|
const initializer = localInitializers.get(referencedName);
|
|
37676
36650
|
if (!initializer) continue;
|
|
37677
|
-
if (derivesFromPostMountValue(initializer, localInitializers,
|
|
36651
|
+
if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
|
|
37678
36652
|
}
|
|
37679
36653
|
return false;
|
|
37680
36654
|
};
|
|
@@ -37688,84 +36662,50 @@ const getSetStateFieldValue = (setStateCall, fieldName) => {
|
|
|
37688
36662
|
}
|
|
37689
36663
|
return null;
|
|
37690
36664
|
};
|
|
37691
|
-
const isConvergentPostMountGuard = (test, setStateCall, localInitializers
|
|
37692
|
-
|
|
37693
|
-
|
|
37694
|
-
if (
|
|
37695
|
-
|
|
37696
|
-
const
|
|
37697
|
-
|
|
37698
|
-
|
|
37699
|
-
|
|
37700
|
-
|
|
37701
|
-
|
|
37702
|
-
|
|
37703
|
-
|
|
37704
|
-
|
|
37705
|
-
|
|
37706
|
-
|
|
37707
|
-
return
|
|
37708
|
-
};
|
|
37709
|
-
const
|
|
37710
|
-
|
|
37711
|
-
|
|
37712
|
-
|
|
37713
|
-
|
|
37714
|
-
|
|
37715
|
-
|
|
37716
|
-
|
|
37717
|
-
|
|
37718
|
-
|
|
37719
|
-
|
|
37720
|
-
|
|
37721
|
-
|
|
37722
|
-
}
|
|
37723
|
-
return false;
|
|
37724
|
-
};
|
|
37725
|
-
const isDiffGuardTest = (test, paramNames, derivedNames, isTruthfulBranch) => {
|
|
37726
|
-
const expression = stripParenExpression(test);
|
|
37727
|
-
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
37728
|
-
if (expression.operator !== "&&" && expression.operator !== "||") return false;
|
|
37729
|
-
const leftIsDiffGuard = isDiffGuardTest(expression.left, paramNames, derivedNames, isTruthfulBranch);
|
|
37730
|
-
const rightIsDiffGuard = isDiffGuardTest(expression.right, paramNames, derivedNames, isTruthfulBranch);
|
|
37731
|
-
return isTruthfulBranch && expression.operator === "||" || !isTruthfulBranch && expression.operator === "&&" ? leftIsDiffGuard && rightIsDiffGuard : leftIsDiffGuard || rightIsDiffGuard;
|
|
37732
|
-
}
|
|
37733
|
-
if (!isNodeOfType(expression, "BinaryExpression") || !(isTruthfulBranch ? DIFFERENCE_OPERATORS.has(expression.operator) : EQUALITY_OPERATORS.has(expression.operator) && !DIFFERENCE_OPERATORS.has(expression.operator))) return false;
|
|
37734
|
-
return isStatefulOperand(expression.left, paramNames, derivedNames) && isStatefulOperand(expression.right, paramNames, derivedNames) && (referencesAnyName(expression.left, paramNames) || referencesAnyName(expression.right, paramNames) || referencesAnyName(expression.left, derivedNames) || referencesAnyName(expression.right, derivedNames));
|
|
36665
|
+
const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
|
|
36666
|
+
let qualifies = false;
|
|
36667
|
+
walkAst(test, (node) => {
|
|
36668
|
+
if (qualifies) return false;
|
|
36669
|
+
if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
|
|
36670
|
+
const leftFieldName = getThisStateFieldName(node.left);
|
|
36671
|
+
const rightFieldName = getThisStateFieldName(node.right);
|
|
36672
|
+
const fieldName = leftFieldName ?? rightFieldName;
|
|
36673
|
+
const comparedValue = leftFieldName ? node.right : node.left;
|
|
36674
|
+
if (!fieldName || !leftFieldName && !rightFieldName) return;
|
|
36675
|
+
const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
|
|
36676
|
+
if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
|
|
36677
|
+
if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
|
|
36678
|
+
qualifies = true;
|
|
36679
|
+
return false;
|
|
36680
|
+
});
|
|
36681
|
+
return qualifies;
|
|
36682
|
+
};
|
|
36683
|
+
const isDiffGuardTest = (test, paramNames, derivedNames) => {
|
|
36684
|
+
if (referencesAnyName(test, paramNames)) return true;
|
|
36685
|
+
let qualifies = false;
|
|
36686
|
+
walkAst(test, (node) => {
|
|
36687
|
+
if (qualifies) return false;
|
|
36688
|
+
if (!isNodeOfType(node, "BinaryExpression")) return;
|
|
36689
|
+
if (!EQUALITY_OPERATORS.has(node.operator)) return;
|
|
36690
|
+
if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames) && (referencesAnyName(node.left, derivedNames) || referencesAnyName(node.right, derivedNames))) {
|
|
36691
|
+
qualifies = true;
|
|
36692
|
+
return false;
|
|
36693
|
+
}
|
|
36694
|
+
});
|
|
36695
|
+
return qualifies;
|
|
37735
36696
|
};
|
|
37736
|
-
const isInsideDiffGuard = (setStateCall
|
|
36697
|
+
const isInsideDiffGuard = (setStateCall) => {
|
|
37737
36698
|
const lifecycleFunction = findEnclosingLifecycleFunction(setStateCall);
|
|
37738
36699
|
if (!lifecycleFunction) return false;
|
|
37739
36700
|
const paramNames = /* @__PURE__ */ new Set();
|
|
37740
|
-
const
|
|
37741
|
-
for (const param of parameters) collectPatternNames(param, paramNames);
|
|
37742
|
-
const previousSourcePaths = /* @__PURE__ */ new Map();
|
|
37743
|
-
const [previousPropsParameter, previousStateParameter] = parameters;
|
|
37744
|
-
collectPreviousSourcePaths(previousPropsParameter, "props", [], previousSourcePaths);
|
|
37745
|
-
collectPreviousSourcePaths(previousStateParameter, "state", [], previousSourcePaths);
|
|
36701
|
+
for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
|
|
37746
36702
|
const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
|
|
37747
36703
|
const localInitializers = collectLocalInitializers(lifecycleFunction);
|
|
37748
|
-
const lifecycleWrittenFieldNames = collectLifecycleWrittenFieldNames(lifecycleFunction);
|
|
37749
|
-
const callbackRefFieldNames = new Set([...getCallbackRefFieldNames(findEnclosingClass(lifecycleFunction), scopes)].filter((fieldName) => !lifecycleWrittenFieldNames.has(fieldName)));
|
|
37750
36704
|
let child = setStateCall;
|
|
37751
36705
|
let ancestor = setStateCall.parent;
|
|
37752
36706
|
while (ancestor && ancestor !== lifecycleFunction) {
|
|
37753
|
-
|
|
37754
|
-
|
|
37755
|
-
if (isNodeOfType(ancestor, "IfStatement")) {
|
|
37756
|
-
if (child === ancestor.consequent) guardTest = ancestor.test;
|
|
37757
|
-
else if (child === ancestor.alternate) {
|
|
37758
|
-
guardTest = ancestor.test;
|
|
37759
|
-
isTruthfulBranch = false;
|
|
37760
|
-
}
|
|
37761
|
-
} else if (isNodeOfType(ancestor, "ConditionalExpression")) {
|
|
37762
|
-
if (child === ancestor.consequent) guardTest = ancestor.test;
|
|
37763
|
-
else if (child === ancestor.alternate) {
|
|
37764
|
-
guardTest = ancestor.test;
|
|
37765
|
-
isTruthfulBranch = false;
|
|
37766
|
-
}
|
|
37767
|
-
} else if (isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right) guardTest = ancestor.left;
|
|
37768
|
-
if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames, isTruthfulBranch) || isTruthfulBranch && isHistoricalToCurrentTransitionGuard(guardTest, previousSourcePaths) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch) || isTruthfulBranch && isConvergentUndefinedClearGuard(guardTest, setStateCall))) return true;
|
|
36707
|
+
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;
|
|
36708
|
+
if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers))) return true;
|
|
37769
36709
|
child = ancestor;
|
|
37770
36710
|
ancestor = ancestor.parent ?? null;
|
|
37771
36711
|
}
|
|
@@ -37787,7 +36727,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
37787
36727
|
if (!isNodeOfType(stripParenExpression(node.callee.object), "ThisExpression")) return;
|
|
37788
36728
|
if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
|
|
37789
36729
|
if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
37790
|
-
if (isInsideDiffGuard(node
|
|
36730
|
+
if (isInsideDiffGuard(node)) return;
|
|
37791
36731
|
context.report({
|
|
37792
36732
|
node: node.callee,
|
|
37793
36733
|
message: MESSAGE$27
|
|
@@ -37857,7 +36797,7 @@ const noDirectMutationState = defineRule({
|
|
|
37857
36797
|
const isSetterIdentifier = (name) => SETTER_PATTERN.test(name);
|
|
37858
36798
|
//#endregion
|
|
37859
36799
|
//#region src/plugin/rules/state-and-effects/utils/collect-use-state-bindings.ts
|
|
37860
|
-
const collectUseStateBindings = (componentBody
|
|
36800
|
+
const collectUseStateBindings = (componentBody) => {
|
|
37861
36801
|
const bindings = [];
|
|
37862
36802
|
if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
|
|
37863
36803
|
for (const statement of componentBody.body ?? []) {
|
|
@@ -37870,12 +36810,7 @@ const collectUseStateBindings = (componentBody, scopes) => {
|
|
|
37870
36810
|
const setterElement = elements[1];
|
|
37871
36811
|
if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
|
|
37872
36812
|
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
37873
|
-
if (!(
|
|
37874
|
-
allowGlobalReactNamespace: true,
|
|
37875
|
-
allowUnboundBareCalls: true,
|
|
37876
|
-
resolveConditionalAliases: true,
|
|
37877
|
-
resolveNamedAliases: true
|
|
37878
|
-
}) : isHookCall$2(declarator.init, "useState"))) continue;
|
|
36813
|
+
if (!isHookCall$2(declarator.init, "useState")) continue;
|
|
37879
36814
|
bindings.push({
|
|
37880
36815
|
valueName: valueElement.name,
|
|
37881
36816
|
setterName: setterElement.name,
|
|
@@ -38602,36 +37537,25 @@ const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSub
|
|
|
38602
37537
|
};
|
|
38603
37538
|
//#endregion
|
|
38604
37539
|
//#region src/plugin/rules/state-and-effects/no-effect-chain.ts
|
|
38605
|
-
const findTopLevelEffectCalls = (componentBody
|
|
37540
|
+
const findTopLevelEffectCalls = (componentBody) => {
|
|
38606
37541
|
const effectCalls = [];
|
|
38607
37542
|
if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
|
|
38608
37543
|
for (const statement of componentBody.body ?? []) {
|
|
38609
37544
|
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
38610
37545
|
const expression = unwrapDiscardedExpression(statement);
|
|
38611
37546
|
if (!isNodeOfType(expression, "CallExpression")) continue;
|
|
38612
|
-
if (!
|
|
38613
|
-
allowGlobalReactNamespace: true,
|
|
38614
|
-
allowUnboundBareCalls: true,
|
|
38615
|
-
resolveConditionalAliases: true,
|
|
38616
|
-
resolveNamedAliases: true
|
|
38617
|
-
})) continue;
|
|
37547
|
+
if (!isHookCall$2(expression, EFFECT_HOOK_NAMES$1)) continue;
|
|
38618
37548
|
effectCalls.push(expression);
|
|
38619
37549
|
}
|
|
38620
37550
|
return effectCalls;
|
|
38621
37551
|
};
|
|
38622
|
-
const
|
|
38623
|
-
const
|
|
38624
|
-
if (!isNodeOfType(effectNode, "CallExpression")) return
|
|
37552
|
+
const collectDepIdentifierNames = (effectNode) => {
|
|
37553
|
+
const depNames = /* @__PURE__ */ new Set();
|
|
37554
|
+
if (!isNodeOfType(effectNode, "CallExpression")) return depNames;
|
|
38625
37555
|
const depsNode = effectNode.arguments?.[1];
|
|
38626
|
-
if (!isNodeOfType(depsNode, "ArrayExpression")) return
|
|
38627
|
-
for (const element of depsNode.elements ?? [])
|
|
38628
|
-
|
|
38629
|
-
const rootIdentifier = getRootIdentifier$1(element);
|
|
38630
|
-
if (!isNodeOfType(rootIdentifier, "Identifier")) continue;
|
|
38631
|
-
const symbol = resolveConstIdentifierAlias(rootIdentifier, scopes, true);
|
|
38632
|
-
if (symbol && stateSymbolIds.has(symbol.id)) dependencyStateSymbolIds.add(symbol.id);
|
|
38633
|
-
}
|
|
38634
|
-
return dependencyStateSymbolIds;
|
|
37556
|
+
if (!isNodeOfType(depsNode, "ArrayExpression")) return depNames;
|
|
37557
|
+
for (const element of depsNode.elements ?? []) if (isNodeOfType(element, "Identifier")) depNames.add(element.name);
|
|
37558
|
+
return depNames;
|
|
38635
37559
|
};
|
|
38636
37560
|
const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
|
|
38637
37561
|
const analysisFunctions = new Set([effectCallback]);
|
|
@@ -38737,13 +37661,12 @@ const readStaticSetterValue = (setterCall, scopes) => {
|
|
|
38737
37661
|
if (updater) return readStaticUpdaterReturnValue(updater, scopes);
|
|
38738
37662
|
return readStaticEffectValue(argument, scopes, null, null);
|
|
38739
37663
|
};
|
|
38740
|
-
const collectStateWritesInEffect = (analysisFunctions,
|
|
37664
|
+
const collectStateWritesInEffect = (analysisFunctions, setterToStateName, scopes) => {
|
|
38741
37665
|
const stateWrites = /* @__PURE__ */ new Map();
|
|
38742
37666
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
38743
37667
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
38744
37668
|
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
38745
|
-
const
|
|
38746
|
-
const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
|
|
37669
|
+
const stateName = setterToStateName.get(child.callee.name);
|
|
38747
37670
|
if (!stateName) return;
|
|
38748
37671
|
const writeInfo = stateWrites.get(stateName) ?? {
|
|
38749
37672
|
values: /* @__PURE__ */ new Set(),
|
|
@@ -38829,12 +37752,11 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
|
|
|
38829
37752
|
"keys",
|
|
38830
37753
|
"values"
|
|
38831
37754
|
]);
|
|
38832
|
-
const isFunctionShapedReturn = (returnedValue, setterToStateName,
|
|
37755
|
+
const isFunctionShapedReturn = (returnedValue, setterToStateName, isExplicitReturnStatement) => {
|
|
38833
37756
|
if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
|
|
38834
37757
|
if (isNodeOfType(returnedValue, "CallExpression")) {
|
|
38835
37758
|
if (isNodeOfType(returnedValue.callee, "Identifier")) {
|
|
38836
|
-
|
|
38837
|
-
if (setterToStateName.has(returnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
|
|
37759
|
+
if (setterToStateName.has(returnedValue.callee.name)) return false;
|
|
38838
37760
|
if (isSetterIdentifier(returnedValue.callee.name)) return true;
|
|
38839
37761
|
}
|
|
38840
37762
|
return isCleanupReturn(returnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
|
|
@@ -39001,11 +37923,11 @@ const isExternalSyncNode = (node) => {
|
|
|
39001
37923
|
const receiverRootName = getRootIdentifierName(node.callee.object);
|
|
39002
37924
|
return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
|
|
39003
37925
|
};
|
|
39004
|
-
const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName,
|
|
37926
|
+
const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, scopes, allowCommittedDomSync) => {
|
|
39005
37927
|
if (!isFunctionLike$1(effectCallback)) return false;
|
|
39006
37928
|
if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
|
|
39007
|
-
if (isFunctionShapedReturn(effectCallback.body, setterToStateName,
|
|
39008
|
-
} else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName,
|
|
37929
|
+
if (isFunctionShapedReturn(effectCallback.body, setterToStateName, false)) return true;
|
|
37930
|
+
} else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, true)) return true;
|
|
39009
37931
|
let didFindExternalCall = false;
|
|
39010
37932
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
39011
37933
|
if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
|
|
@@ -39021,11 +37943,10 @@ const noEffectChain = defineRule({
|
|
|
39021
37943
|
create: (context) => {
|
|
39022
37944
|
const checkComponent = (componentBody) => {
|
|
39023
37945
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
39024
|
-
const useStateBindings = collectUseStateBindings(componentBody
|
|
37946
|
+
const useStateBindings = collectUseStateBindings(componentBody);
|
|
39025
37947
|
if (useStateBindings.length === 0) return;
|
|
39026
37948
|
const setterToStateName = /* @__PURE__ */ new Map();
|
|
39027
37949
|
const stateSymbolIds = /* @__PURE__ */ new Map();
|
|
39028
|
-
const setterSymbolIdToStateName = /* @__PURE__ */ new Map();
|
|
39029
37950
|
for (const binding of useStateBindings) {
|
|
39030
37951
|
setterToStateName.set(binding.setterName, binding.valueName);
|
|
39031
37952
|
if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
|
|
@@ -39034,27 +37955,21 @@ const noEffectChain = defineRule({
|
|
|
39034
37955
|
const stateSymbol = context.scopes.symbolFor(stateIdentifier);
|
|
39035
37956
|
if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
|
|
39036
37957
|
}
|
|
39037
|
-
const setterIdentifier = binding.declarator.id.elements[1];
|
|
39038
|
-
if (isNodeOfType(setterIdentifier, "Identifier")) {
|
|
39039
|
-
const setterSymbol = context.scopes.symbolFor(setterIdentifier);
|
|
39040
|
-
if (setterSymbol) setterSymbolIdToStateName.set(setterSymbol.id, binding.valueName);
|
|
39041
|
-
}
|
|
39042
37958
|
}
|
|
39043
37959
|
const storageSetterNames = collectStorageHookSetterNames(componentBody);
|
|
39044
|
-
const stateSymbolIdSet = new Set(stateSymbolIds.values());
|
|
39045
37960
|
const effectInfos = [];
|
|
39046
|
-
for (const effectCall of findTopLevelEffectCalls(componentBody
|
|
37961
|
+
for (const effectCall of findTopLevelEffectCalls(componentBody)) {
|
|
39047
37962
|
const callback = getEffectCallback(effectCall, context.scopes);
|
|
39048
37963
|
if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
|
|
39049
37964
|
const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
|
|
39050
|
-
const stateWrites = collectStateWritesInEffect(analysisFunctions,
|
|
37965
|
+
const stateWrites = collectStateWritesInEffect(analysisFunctions, setterToStateName, context.scopes);
|
|
39051
37966
|
const writtenStateNames = new Set(stateWrites.keys());
|
|
39052
37967
|
effectInfos.push({
|
|
39053
37968
|
node: effectCall,
|
|
39054
|
-
|
|
37969
|
+
depNames: collectDepIdentifierNames(effectCall),
|
|
39055
37970
|
stateWrites,
|
|
39056
37971
|
analysisFunctions,
|
|
39057
|
-
isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName,
|
|
37972
|
+
isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
|
|
39058
37973
|
});
|
|
39059
37974
|
}
|
|
39060
37975
|
if (effectInfos.length < 2) return;
|
|
@@ -39065,11 +37980,10 @@ const noEffectChain = defineRule({
|
|
|
39065
37980
|
for (const readerEffect of effectInfos) {
|
|
39066
37981
|
if (readerEffect === writerEffect) continue;
|
|
39067
37982
|
if (readerEffect.isExternalSync) continue;
|
|
39068
|
-
if (readerEffect.
|
|
37983
|
+
if (readerEffect.depNames.size === 0) continue;
|
|
39069
37984
|
let chainedStateName = null;
|
|
39070
37985
|
for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
|
|
39071
|
-
|
|
39072
|
-
if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
|
|
37986
|
+
if (!readerEffect.depNames.has(writtenName)) continue;
|
|
39073
37987
|
if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
|
|
39074
37988
|
chainedStateName = writtenName;
|
|
39075
37989
|
break;
|
|
@@ -40885,17 +39799,6 @@ const DOM_MEASUREMENT_NAMES = new Set([
|
|
|
40885
39799
|
"scrollHeight"
|
|
40886
39800
|
]);
|
|
40887
39801
|
const MEASUREMENT_HELPER_CALLEE_PATTERN = /^(?:get|measure|read)\w*(?:Width|Height|Rect|Rects|Size|Bounds|Position)$/;
|
|
40888
|
-
const IMPERATIVE_DOM_MUTATION_NAMES = new Set([
|
|
40889
|
-
"blur",
|
|
40890
|
-
"focus",
|
|
40891
|
-
"restoreSelection",
|
|
40892
|
-
"scroll",
|
|
40893
|
-
"scrollBy",
|
|
40894
|
-
"scrollIntoView",
|
|
40895
|
-
"scrollTo",
|
|
40896
|
-
"setRangeText",
|
|
40897
|
-
"setSelectionRange"
|
|
40898
|
-
]);
|
|
40899
39802
|
const subtreeReadsDomMeasurement = (root) => {
|
|
40900
39803
|
if (!root) return false;
|
|
40901
39804
|
let found = false;
|
|
@@ -40914,66 +39817,29 @@ const subtreeReadsDomMeasurement = (root) => {
|
|
|
40914
39817
|
});
|
|
40915
39818
|
return found;
|
|
40916
39819
|
};
|
|
40917
|
-
const
|
|
39820
|
+
const collectMeasuringFunctionNames = (program) => {
|
|
40918
39821
|
const names = /* @__PURE__ */ new Set();
|
|
40919
39822
|
walkAst(program, (child) => {
|
|
40920
39823
|
if (isNodeOfType(child, "FunctionDeclaration")) {
|
|
40921
|
-
if (child.id && isNodeOfType(child.id, "Identifier") &&
|
|
39824
|
+
if (child.id && isNodeOfType(child.id, "Identifier") && subtreeReadsDomMeasurement(child.body)) names.add(child.id.name);
|
|
40922
39825
|
return;
|
|
40923
39826
|
}
|
|
40924
39827
|
if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier")) return;
|
|
40925
39828
|
let functionValue = child.init;
|
|
40926
39829
|
if (functionValue && isNodeOfType(functionValue, "CallExpression") && isNodeOfType(functionValue.callee, "Identifier") && /^use[A-Z]/.test(functionValue.callee.name)) functionValue = functionValue.arguments?.[0];
|
|
40927
|
-
if (functionValue && isFunctionLike$1(functionValue) &&
|
|
39830
|
+
if (functionValue && isFunctionLike$1(functionValue) && subtreeReadsDomMeasurement(functionValue.body)) names.add(child.id.name);
|
|
40928
39831
|
});
|
|
40929
39832
|
return names;
|
|
40930
39833
|
};
|
|
40931
|
-
const
|
|
40932
|
-
|
|
40933
|
-
if (!root || isFunctionLike$1(root)) return false;
|
|
40934
|
-
let found = false;
|
|
40935
|
-
walkAst(root, (child) => {
|
|
40936
|
-
if (found) return false;
|
|
40937
|
-
if (child !== root && isFunctionLike$1(child)) return false;
|
|
40938
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
40939
|
-
const callee = stripParenExpression(child.callee);
|
|
40940
|
-
const propertyName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
|
|
40941
|
-
if (propertyName !== null && IMPERATIVE_DOM_MUTATION_NAMES.has(propertyName)) {
|
|
40942
|
-
found = true;
|
|
40943
|
-
return false;
|
|
40944
|
-
}
|
|
40945
|
-
});
|
|
40946
|
-
return found;
|
|
40947
|
-
};
|
|
40948
|
-
const collectImperativeDomFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeMutatesDomImperatively);
|
|
40949
|
-
const callsAnyName = (root, names, shouldSkipNestedFunctions = false) => {
|
|
40950
|
-
if (!root || names.size === 0 || shouldSkipNestedFunctions && isFunctionLike$1(root)) return false;
|
|
39834
|
+
const callsAnyName = (root, names) => {
|
|
39835
|
+
if (!root || names.size === 0) return false;
|
|
40951
39836
|
let found = false;
|
|
40952
39837
|
walkAst(root, (child) => {
|
|
40953
39838
|
if (found) return false;
|
|
40954
|
-
if (shouldSkipNestedFunctions && child !== root && isFunctionLike$1(child)) return false;
|
|
40955
39839
|
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && names.has(child.callee.name)) found = true;
|
|
40956
39840
|
});
|
|
40957
39841
|
return found;
|
|
40958
39842
|
};
|
|
40959
|
-
const isFollowedByImperativeDomMutation = (call, imperativeDomFunctionNames) => {
|
|
40960
|
-
let statement = call;
|
|
40961
|
-
let parent = statement.parent;
|
|
40962
|
-
while (parent) {
|
|
40963
|
-
const statements = isNodeOfType(parent, "BlockStatement") || isNodeOfType(parent, "Program") || isNodeOfType(parent, "StaticBlock") ? parent.body : isNodeOfType(parent, "SwitchCase") ? parent.consequent : null;
|
|
40964
|
-
if (statements) {
|
|
40965
|
-
const statementIndex = statements.findIndex((siblingStatement) => siblingStatement === statement);
|
|
40966
|
-
if (statementIndex >= 0) {
|
|
40967
|
-
const nextStatement = statements[statementIndex + 1];
|
|
40968
|
-
return subtreeMutatesDomImperatively(nextStatement) || callsAnyName(nextStatement, imperativeDomFunctionNames, true);
|
|
40969
|
-
}
|
|
40970
|
-
}
|
|
40971
|
-
if (isFunctionLike$1(parent) || parent.type.endsWith("Statement") && !isNodeOfType(parent, "ExpressionStatement")) return false;
|
|
40972
|
-
statement = parent;
|
|
40973
|
-
parent = parent.parent;
|
|
40974
|
-
}
|
|
40975
|
-
return false;
|
|
40976
|
-
};
|
|
40977
39843
|
const isInsideStartViewTransition = (node) => {
|
|
40978
39844
|
let cursor = node.parent;
|
|
40979
39845
|
while (cursor) {
|
|
@@ -41014,12 +39880,11 @@ const importsImperativeDomLibrary = (program) => {
|
|
|
41014
39880
|
};
|
|
41015
39881
|
const hasExemptFlushSyncCall = (program, localName) => {
|
|
41016
39882
|
const measuringFunctionNames = collectMeasuringFunctionNames(program);
|
|
41017
|
-
const imperativeDomFunctionNames = collectImperativeDomFunctionNames(program);
|
|
41018
39883
|
let exempt = false;
|
|
41019
39884
|
walkAst(program, (child) => {
|
|
41020
39885
|
if (exempt) return false;
|
|
41021
39886
|
if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "Identifier") || child.callee.name !== localName) return;
|
|
41022
|
-
if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames)
|
|
39887
|
+
if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames)) {
|
|
41023
39888
|
exempt = true;
|
|
41024
39889
|
return false;
|
|
41025
39890
|
}
|
|
@@ -41584,223 +40449,41 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
|
|
|
41584
40449
|
if (leftResult === false && rightResult === false) return false;
|
|
41585
40450
|
return null;
|
|
41586
40451
|
};
|
|
41587
|
-
const readHydrationConditionResult = (expression, context, runtime
|
|
40452
|
+
const readHydrationConditionResult = (expression, context, runtime) => {
|
|
41588
40453
|
const unwrappedExpression = stripParenExpression(expression);
|
|
41589
40454
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
41590
40455
|
if (predicateMatch) return predicateMatch[`${runtime}Result`];
|
|
41591
40456
|
const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
|
|
41592
40457
|
if (staticResult !== null) return staticResult;
|
|
41593
|
-
const expressionSymbol = isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression) : null;
|
|
41594
|
-
const parameterValue = expressionSymbol ? state.parameterValuesBySymbolId.get(expressionSymbol.id) : null;
|
|
41595
|
-
if (expressionSymbol && parameterValue && !state.visitedSymbolIds.has(expressionSymbol.id)) {
|
|
41596
|
-
state.visitedSymbolIds.add(expressionSymbol.id);
|
|
41597
|
-
const result = readHydrationConditionResult(parameterValue, context, runtime, state);
|
|
41598
|
-
state.visitedSymbolIds.delete(expressionSymbol.id);
|
|
41599
|
-
return result;
|
|
41600
|
-
}
|
|
41601
|
-
if (expressionSymbol && expressionSymbol.kind === "const" && expressionSymbol.initializer && expressionSymbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(expressionSymbol.id)) {
|
|
41602
|
-
state.visitedSymbolIds.add(expressionSymbol.id);
|
|
41603
|
-
const result = readHydrationConditionResult(expressionSymbol.initializer, context, runtime, state);
|
|
41604
|
-
state.visitedSymbolIds.delete(expressionSymbol.id);
|
|
41605
|
-
return result;
|
|
41606
|
-
}
|
|
41607
|
-
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
41608
|
-
const callArguments = unwrappedExpression.arguments ?? [];
|
|
41609
|
-
if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
|
|
41610
|
-
allowGlobalReactNamespace: true,
|
|
41611
|
-
resolveNamedAliases: true
|
|
41612
|
-
})) {
|
|
41613
|
-
const callbackArgument = callArguments[0];
|
|
41614
|
-
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
|
|
41615
|
-
const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
41616
|
-
return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? readHydrationFunctionResult(callbackFunction, context, runtime, state) : null;
|
|
41617
|
-
}
|
|
41618
|
-
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
41619
|
-
if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return readHydrationConditionResult(callArguments[0], context, runtime, state);
|
|
41620
|
-
const helperFunction = resolveExactLocalFunction(callee, context.scopes);
|
|
41621
|
-
if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
|
|
41622
|
-
const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
|
|
41623
|
-
for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
|
|
41624
|
-
const parameter = helperFunction.params[parameterIndex];
|
|
41625
|
-
const argument = callArguments[parameterIndex];
|
|
41626
|
-
if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
|
|
41627
|
-
const parameterSymbol = context.scopes.symbolFor(parameter);
|
|
41628
|
-
if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
|
|
41629
|
-
}
|
|
41630
|
-
return readHydrationFunctionResult(helperFunction, context, runtime, {
|
|
41631
|
-
...state,
|
|
41632
|
-
parameterValuesBySymbolId
|
|
41633
|
-
});
|
|
41634
|
-
}
|
|
41635
40458
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
|
|
41636
|
-
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime
|
|
40459
|
+
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
|
|
41637
40460
|
return argumentResult === null ? null : !argumentResult;
|
|
41638
40461
|
}
|
|
41639
40462
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
41640
|
-
return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime
|
|
41641
|
-
};
|
|
41642
|
-
const readHydrationStatementResult = (statement, context, runtime, state) => {
|
|
41643
|
-
if (isNodeOfType(statement, "ReturnStatement")) return {
|
|
41644
|
-
didReturn: true,
|
|
41645
|
-
value: statement.argument ? readHydrationConditionResult(statement.argument, context, runtime, state) : null
|
|
41646
|
-
};
|
|
41647
|
-
if (isNodeOfType(statement, "BlockStatement")) {
|
|
41648
|
-
for (const childStatement of statement.body) {
|
|
41649
|
-
const result = readHydrationStatementResult(childStatement, context, runtime, state);
|
|
41650
|
-
if (result.didReturn) return result;
|
|
41651
|
-
if (statementAlwaysExits(childStatement)) break;
|
|
41652
|
-
}
|
|
41653
|
-
return {
|
|
41654
|
-
didReturn: false,
|
|
41655
|
-
value: null
|
|
41656
|
-
};
|
|
41657
|
-
}
|
|
41658
|
-
if (!isNodeOfType(statement, "IfStatement")) return {
|
|
41659
|
-
didReturn: false,
|
|
41660
|
-
value: null
|
|
41661
|
-
};
|
|
41662
|
-
const conditionResult = readHydrationConditionResult(statement.test, context, runtime, state);
|
|
41663
|
-
if (conditionResult !== null) {
|
|
41664
|
-
const selectedBranch = conditionResult ? statement.consequent : statement.alternate;
|
|
41665
|
-
return selectedBranch ? readHydrationStatementResult(selectedBranch, context, runtime, state) : {
|
|
41666
|
-
didReturn: false,
|
|
41667
|
-
value: null
|
|
41668
|
-
};
|
|
41669
|
-
}
|
|
41670
|
-
const consequentResult = readHydrationStatementResult(statement.consequent, context, runtime, state);
|
|
41671
|
-
const alternateResult = statement.alternate ? readHydrationStatementResult(statement.alternate, context, runtime, state) : {
|
|
41672
|
-
didReturn: false,
|
|
41673
|
-
value: null
|
|
41674
|
-
};
|
|
41675
|
-
return consequentResult.didReturn && alternateResult.didReturn && consequentResult.value !== null && consequentResult.value === alternateResult.value ? consequentResult : {
|
|
41676
|
-
didReturn: consequentResult.didReturn || alternateResult.didReturn,
|
|
41677
|
-
value: null
|
|
41678
|
-
};
|
|
40463
|
+
return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
|
|
41679
40464
|
};
|
|
41680
|
-
const
|
|
41681
|
-
if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
|
|
41682
|
-
state.visitedFunctionNodes.add(functionNode);
|
|
41683
|
-
const result = isNodeOfType(functionNode.body, "BlockStatement") ? readHydrationStatementResult(functionNode.body, context, runtime, state).value : readHydrationConditionResult(functionNode.body, context, runtime, state);
|
|
41684
|
-
state.visitedFunctionNodes.delete(functionNode);
|
|
41685
|
-
return result;
|
|
41686
|
-
};
|
|
41687
|
-
const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, scopes) => {
|
|
41688
|
-
const left = stripParenExpression(leftExpression);
|
|
41689
|
-
const right = stripParenExpression(rightExpression);
|
|
41690
|
-
if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
|
|
41691
|
-
const leftSymbol = scopes.symbolFor(left);
|
|
41692
|
-
const rightSymbol = scopes.symbolFor(right);
|
|
41693
|
-
return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
|
|
41694
|
-
}
|
|
41695
|
-
if (isNodeOfType(left, "MemberExpression") && isNodeOfType(right, "MemberExpression")) return doEquivalentExpressionBindingsMatch(left.object, right.object, scopes) && (!left.computed || doEquivalentExpressionBindingsMatch(left.property, right.property, scopes));
|
|
41696
|
-
if (isNodeOfType(left, "CallExpression") && isNodeOfType(right, "CallExpression")) {
|
|
41697
|
-
const rightArguments = right.arguments ?? [];
|
|
41698
|
-
return doEquivalentExpressionBindingsMatch(left.callee, right.callee, scopes) && (left.arguments ?? []).every((argument, index) => {
|
|
41699
|
-
const rightArgument = rightArguments[index];
|
|
41700
|
-
return Boolean(rightArgument && doEquivalentExpressionBindingsMatch(argument, rightArgument, scopes));
|
|
41701
|
-
});
|
|
41702
|
-
}
|
|
41703
|
-
return true;
|
|
41704
|
-
};
|
|
41705
|
-
const areHelperReturnValuesEquivalent = (leftValue, rightValue, context) => {
|
|
41706
|
-
if (areExpressionsStructurallyEqual(leftValue, rightValue)) return doEquivalentExpressionBindingsMatch(leftValue, rightValue, context.scopes);
|
|
41707
|
-
const leftBoolean = readInitialStateBoolean(leftValue, context.scopes);
|
|
41708
|
-
const rightBoolean = readInitialStateBoolean(rightValue, context.scopes);
|
|
41709
|
-
return leftBoolean !== null && rightBoolean !== null && leftBoolean === rightBoolean;
|
|
41710
|
-
};
|
|
41711
|
-
const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
|
|
41712
|
-
const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
|
|
41713
|
-
return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
|
|
41714
|
-
};
|
|
41715
|
-
const matchHydrationConditionInternal = (expression, context, state) => {
|
|
40465
|
+
const matchHydrationCondition = (expression, context) => {
|
|
41716
40466
|
const unwrappedExpression = stripParenExpression(expression);
|
|
41717
40467
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
41718
40468
|
if (predicateMatch) return {
|
|
41719
40469
|
predicateMatch,
|
|
41720
40470
|
predicateNode: unwrappedExpression
|
|
41721
40471
|
};
|
|
41722
|
-
if (isNodeOfType(unwrappedExpression, "
|
|
41723
|
-
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
41724
|
-
const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
|
|
41725
|
-
if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
|
|
41726
|
-
state.visitedSymbolIds.add(symbol.id);
|
|
41727
|
-
const match = matchHydrationConditionInternal(parameterValue, context, state);
|
|
41728
|
-
state.visitedSymbolIds.delete(symbol.id);
|
|
41729
|
-
return match;
|
|
41730
|
-
}
|
|
41731
|
-
if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
|
|
41732
|
-
state.visitedSymbolIds.add(symbol.id);
|
|
41733
|
-
const match = matchHydrationConditionInternal(symbol.initializer, context, state);
|
|
41734
|
-
state.visitedSymbolIds.delete(symbol.id);
|
|
41735
|
-
return match;
|
|
41736
|
-
}
|
|
41737
|
-
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
41738
|
-
const callArguments = unwrappedExpression.arguments ?? [];
|
|
41739
|
-
if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
|
|
41740
|
-
allowGlobalReactNamespace: true,
|
|
41741
|
-
resolveNamedAliases: true
|
|
41742
|
-
})) {
|
|
41743
|
-
const callbackArgument = callArguments[0];
|
|
41744
|
-
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
|
|
41745
|
-
const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
41746
|
-
return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionResult(callbackFunction, context, state) : null;
|
|
41747
|
-
}
|
|
41748
|
-
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
41749
|
-
if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return matchHydrationConditionInternal(callArguments[0], context, state);
|
|
41750
|
-
const helperFunction = resolveExactLocalFunction(callee, context.scopes);
|
|
41751
|
-
if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
|
|
41752
|
-
const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
|
|
41753
|
-
for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
|
|
41754
|
-
const parameter = helperFunction.params[parameterIndex];
|
|
41755
|
-
const argument = callArguments[parameterIndex];
|
|
41756
|
-
if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
|
|
41757
|
-
const parameterSymbol = context.scopes.symbolFor(parameter);
|
|
41758
|
-
if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
|
|
41759
|
-
}
|
|
41760
|
-
return matchHydrationFunctionResult(helperFunction, context, {
|
|
41761
|
-
...state,
|
|
41762
|
-
parameterValuesBySymbolId
|
|
41763
|
-
});
|
|
41764
|
-
}
|
|
41765
|
-
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
|
|
40472
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
|
|
41766
40473
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
41767
|
-
const leftMatch =
|
|
41768
|
-
const rightMatch =
|
|
40474
|
+
const leftMatch = matchHydrationCondition(unwrappedExpression.left, context);
|
|
40475
|
+
const rightMatch = matchHydrationCondition(unwrappedExpression.right, context);
|
|
40476
|
+
if (leftMatch && rightMatch) {
|
|
40477
|
+
const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client");
|
|
40478
|
+
const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server");
|
|
40479
|
+
return clientResult !== null && serverResult !== null && clientResult !== serverResult ? leftMatch : null;
|
|
40480
|
+
}
|
|
41769
40481
|
const nestedMatch = leftMatch ?? rightMatch;
|
|
41770
40482
|
if (!nestedMatch) return null;
|
|
41771
|
-
const
|
|
41772
|
-
|
|
41773
|
-
return
|
|
40483
|
+
const otherResult = readInitialStateBoolean(leftMatch ? unwrappedExpression.right : unwrappedExpression.left, context.scopes);
|
|
40484
|
+
if (unwrappedExpression.operator === "&&" && otherResult === false || unwrappedExpression.operator === "||" && otherResult === true) return null;
|
|
40485
|
+
return nestedMatch;
|
|
41774
40486
|
};
|
|
41775
|
-
const matchHydrationReturningStatement = (statement, context, state) => {
|
|
41776
|
-
if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? matchHydrationConditionInternal(statement.argument, context, state) : null;
|
|
41777
|
-
if (isNodeOfType(statement, "IfStatement")) {
|
|
41778
|
-
const conditionMatch = matchHydrationConditionInternal(statement.test, context, state);
|
|
41779
|
-
const consequentValues = getReturnedValues(statement.consequent);
|
|
41780
|
-
const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
|
|
41781
|
-
if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
|
|
41782
|
-
return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
|
|
41783
|
-
}
|
|
41784
|
-
if (!isNodeOfType(statement, "BlockStatement")) return null;
|
|
41785
|
-
for (const childStatement of statement.body) {
|
|
41786
|
-
const match = matchHydrationReturningStatement(childStatement, context, state);
|
|
41787
|
-
if (match) return match;
|
|
41788
|
-
if (statementAlwaysExits(childStatement)) break;
|
|
41789
|
-
}
|
|
41790
|
-
return null;
|
|
41791
|
-
};
|
|
41792
|
-
const matchHydrationFunctionResult = (functionNode, context, state) => {
|
|
41793
|
-
if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
|
|
41794
|
-
state.visitedFunctionNodes.add(functionNode);
|
|
41795
|
-
const match = isNodeOfType(functionNode.body, "BlockStatement") ? matchHydrationReturningStatement(functionNode.body, context, state) : matchHydrationConditionInternal(functionNode.body, context, state);
|
|
41796
|
-
state.visitedFunctionNodes.delete(functionNode);
|
|
41797
|
-
return match;
|
|
41798
|
-
};
|
|
41799
|
-
const matchHydrationCondition = (expression, context) => matchHydrationConditionInternal(expression, context, {
|
|
41800
|
-
parameterValuesBySymbolId: /* @__PURE__ */ new Map(),
|
|
41801
|
-
visitedFunctionNodes: /* @__PURE__ */ new Set(),
|
|
41802
|
-
visitedSymbolIds: /* @__PURE__ */ new Set()
|
|
41803
|
-
});
|
|
41804
40487
|
const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
|
|
41805
40488
|
const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
|
|
41806
40489
|
if (!leftNode || !rightNode) return leftNode === rightNode;
|
|
@@ -41943,17 +40626,17 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
41943
40626
|
const { predicateMatch, predicateNode } = conditionMatch;
|
|
41944
40627
|
if (reportedNodes.has(predicateNode)) return;
|
|
41945
40628
|
if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
|
|
41946
|
-
const componentOrHookNode = findRenderPhaseComponentOrHook(
|
|
40629
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(predicateNode, context.scopes);
|
|
41947
40630
|
if (!componentOrHookNode) return;
|
|
41948
40631
|
if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
|
|
41949
|
-
if (requiresRenderedContext && !isInRenderedOutput(
|
|
40632
|
+
if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
|
|
41950
40633
|
if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
|
|
41951
|
-
const attribute = findEnclosingJsxAttribute(
|
|
40634
|
+
const attribute = findEnclosingJsxAttribute(predicateNode);
|
|
41952
40635
|
if (!attribute || isEventHandlerAttribute(attribute)) return;
|
|
41953
40636
|
}
|
|
41954
|
-
if (fileIsEmailTemplate || isGatedByFalsyInitialState(
|
|
41955
|
-
if (isAfterClientOnlyEarlyReturn(
|
|
41956
|
-
const openingElement = findEnclosingJsxOpeningElement(
|
|
40637
|
+
if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
|
|
40638
|
+
if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
|
|
40639
|
+
const openingElement = findEnclosingJsxOpeningElement(predicateNode);
|
|
41957
40640
|
if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
|
|
41958
40641
|
if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
|
|
41959
40642
|
if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
|
|
@@ -42335,7 +41018,7 @@ const noInitializeState = defineRule({
|
|
|
42335
41018
|
if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
|
|
42336
41019
|
const analysis = getProgramAnalysis(node);
|
|
42337
41020
|
if (!analysis) return;
|
|
42338
|
-
for (const fact of collectEffectStateWriteFacts(analysis,
|
|
41021
|
+
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
42339
41022
|
if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
|
|
42340
41023
|
const stateName = getStateName(fact.stateDeclarator);
|
|
42341
41024
|
context.report({
|
|
@@ -42693,8 +41376,7 @@ const noJsxElementType = defineRule({
|
|
|
42693
41376
|
create: (context) => {
|
|
42694
41377
|
let isJsxImported = false;
|
|
42695
41378
|
const flaggedAnnotations = [];
|
|
42696
|
-
const
|
|
42697
|
-
if (!(isNodeOfType(functionNode, "TSDeclareFunction") ? Boolean(functionNode.id && isReactComponentName(functionNode.id.name)) : isComponentFunction$1(functionNode))) return;
|
|
41379
|
+
const checkReturnType = (returnType) => {
|
|
42698
41380
|
const typeAnnotation = extractReturnTypeAnnotation(returnType);
|
|
42699
41381
|
if (!typeAnnotation) return;
|
|
42700
41382
|
if (isJsxElementTypeReference(typeAnnotation)) flaggedAnnotations.push(typeAnnotation);
|
|
@@ -42704,16 +41386,19 @@ const noJsxElementType = defineRule({
|
|
|
42704
41386
|
if (isJsxImportBinding(node)) isJsxImported = true;
|
|
42705
41387
|
},
|
|
42706
41388
|
FunctionDeclaration(node) {
|
|
42707
|
-
|
|
41389
|
+
checkReturnType(node.returnType);
|
|
42708
41390
|
},
|
|
42709
41391
|
ArrowFunctionExpression(node) {
|
|
42710
|
-
|
|
41392
|
+
checkReturnType(node.returnType);
|
|
42711
41393
|
},
|
|
42712
41394
|
FunctionExpression(node) {
|
|
42713
|
-
|
|
41395
|
+
checkReturnType(node.returnType);
|
|
42714
41396
|
},
|
|
42715
41397
|
TSDeclareFunction(node) {
|
|
42716
|
-
|
|
41398
|
+
checkReturnType(node.returnType);
|
|
41399
|
+
},
|
|
41400
|
+
TSMethodSignature(node) {
|
|
41401
|
+
checkReturnType(node.returnType);
|
|
42717
41402
|
},
|
|
42718
41403
|
"Program:exit"() {
|
|
42719
41404
|
if (isJsxImported) return;
|
|
@@ -45740,114 +44425,6 @@ const DATA_SINK_METHOD_NAMES = new Set([
|
|
|
45740
44425
|
"deserialize"
|
|
45741
44426
|
]);
|
|
45742
44427
|
//#endregion
|
|
45743
|
-
//#region src/plugin/utils/get-transparent-react-callback-wrapper-argument.ts
|
|
45744
|
-
const getTransparentReactCallbackWrapperArgument = (initializer, resultSymbol, scopes) => {
|
|
45745
|
-
const callExpression = stripParenExpression(initializer);
|
|
45746
|
-
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
45747
|
-
const callbackArgument = callExpression.arguments[0];
|
|
45748
|
-
if (!callbackArgument) return null;
|
|
45749
|
-
if (resultSymbol && symbolHasReactUseEffectEventOrigin(resultSymbol, scopes)) return callbackArgument;
|
|
45750
|
-
return isReactApiCall(callExpression, "useCallback", scopes, {
|
|
45751
|
-
allowGlobalReactNamespace: true,
|
|
45752
|
-
allowUnboundBareCalls: true
|
|
45753
|
-
}) ? callbackArgument : null;
|
|
45754
|
-
};
|
|
45755
|
-
//#endregion
|
|
45756
|
-
//#region src/plugin/rules/state-and-effects/utils/resolve-parent-callback-provenance.ts
|
|
45757
|
-
const getDeclarationKind$1 = (declarator) => {
|
|
45758
|
-
const declaration = declarator.parent;
|
|
45759
|
-
return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
|
|
45760
|
-
};
|
|
45761
|
-
const hasMutableBindingWrite$2 = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
45762
|
-
const mergeRequiredBranches = (leftNames, rightNames) => {
|
|
45763
|
-
if (!leftNames || !rightNames) return null;
|
|
45764
|
-
return new Set([...leftNames, ...rightNames]);
|
|
45765
|
-
};
|
|
45766
|
-
const getPropReferenceName = (analysis, identifier) => {
|
|
45767
|
-
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
45768
|
-
const reference = getRef(analysis, identifier);
|
|
45769
|
-
if (!reference || !isProp(analysis, reference) || isWholePropsObjectReference(analysis, reference)) return null;
|
|
45770
|
-
const bindingIdentifier = (reference.resolved?.defs.find((definition) => definition.type === "Parameter"))?.name;
|
|
45771
|
-
return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? identifier.name;
|
|
45772
|
-
};
|
|
45773
|
-
const getSingleConstDeclarator = (reference) => {
|
|
45774
|
-
if (!reference.resolved || hasMutableBindingWrite$2(reference)) return null;
|
|
45775
|
-
const declarators = reference.resolved.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
45776
|
-
if (declarators.length !== 1) return null;
|
|
45777
|
-
const declarator = declarators[0];
|
|
45778
|
-
if (!declarator || getDeclarationKind$1(declarator) !== "const") return null;
|
|
45779
|
-
return declarator;
|
|
45780
|
-
};
|
|
45781
|
-
const resolveParentCallbackPropNames = (analysis, expression, scopes, visitedReferences, allowFunctionForwarder = false) => {
|
|
45782
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
45783
|
-
if (isFunctionLike$1(unwrappedExpression)) {
|
|
45784
|
-
if (!allowFunctionForwarder || Boolean(unwrappedExpression.async)) return null;
|
|
45785
|
-
const callbackNames = /* @__PURE__ */ new Set();
|
|
45786
|
-
walkInsideStatementBlocks(unwrappedExpression.body, (child) => {
|
|
45787
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
45788
|
-
const resolvedNames = resolveParentCallbackPropNames(analysis, child.callee, scopes, new Set(visitedReferences), false);
|
|
45789
|
-
if (!resolvedNames) return;
|
|
45790
|
-
for (const resolvedName of resolvedNames) callbackNames.add(resolvedName);
|
|
45791
|
-
});
|
|
45792
|
-
return callbackNames.size > 0 ? callbackNames : null;
|
|
45793
|
-
}
|
|
45794
|
-
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return mergeRequiredBranches(resolveParentCallbackPropNames(analysis, unwrappedExpression.consequent, scopes, new Set(visitedReferences), false), resolveParentCallbackPropNames(analysis, unwrappedExpression.alternate, scopes, new Set(visitedReferences), false));
|
|
45795
|
-
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return mergeRequiredBranches(resolveParentCallbackPropNames(analysis, unwrappedExpression.left, scopes, new Set(visitedReferences), false), resolveParentCallbackPropNames(analysis, unwrappedExpression.right, scopes, new Set(visitedReferences)));
|
|
45796
|
-
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
45797
|
-
const propName = getPropReferenceName(analysis, unwrappedExpression);
|
|
45798
|
-
if (propName) return new Set([propName]);
|
|
45799
|
-
const reference = getRef(analysis, unwrappedExpression);
|
|
45800
|
-
if (!reference?.resolved || visitedReferences.has(reference.resolved)) return null;
|
|
45801
|
-
const declarator = getSingleConstDeclarator(reference);
|
|
45802
|
-
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return null;
|
|
45803
|
-
visitedReferences.add(reference.resolved);
|
|
45804
|
-
const wrappedArgument = getTransparentReactCallbackWrapperArgument(declarator.init, scopes.symbolFor(unwrappedExpression), scopes);
|
|
45805
|
-
const allowsFunctionForwarder = Boolean(wrappedArgument && !isReactApiCall(declarator.init, "useCallback", scopes, {
|
|
45806
|
-
allowGlobalReactNamespace: true,
|
|
45807
|
-
allowUnboundBareCalls: true
|
|
45808
|
-
}));
|
|
45809
|
-
return resolveParentCallbackPropNames(analysis, wrappedArgument ?? declarator.init, scopes, visitedReferences, allowsFunctionForwarder);
|
|
45810
|
-
}
|
|
45811
|
-
if (!isNodeOfType(unwrappedExpression, "MemberExpression")) return null;
|
|
45812
|
-
const propertyName = getStaticMemberPropertyName(unwrappedExpression);
|
|
45813
|
-
if (!propertyName) return null;
|
|
45814
|
-
const receiver = stripParenExpression(unwrappedExpression.object);
|
|
45815
|
-
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
45816
|
-
const receiverReference = getRef(analysis, receiver);
|
|
45817
|
-
if (!receiverReference?.resolved || visitedReferences.has(receiverReference.resolved)) return null;
|
|
45818
|
-
if (isWholePropsObjectReference(analysis, receiverReference)) return new Set([propertyName]);
|
|
45819
|
-
const declarator = getSingleConstDeclarator(receiverReference);
|
|
45820
|
-
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return null;
|
|
45821
|
-
visitedReferences.add(receiverReference.resolved);
|
|
45822
|
-
const initializer = stripParenExpression(declarator.init);
|
|
45823
|
-
if (propertyName === "current" && isNodeOfType(initializer, "CallExpression")) {
|
|
45824
|
-
if (!isReactApiCall(initializer, "useRef", scopes, {
|
|
45825
|
-
allowGlobalReactNamespace: true,
|
|
45826
|
-
allowUnboundBareCalls: true
|
|
45827
|
-
})) return null;
|
|
45828
|
-
const callbackArgument = initializer.arguments[0];
|
|
45829
|
-
if (!callbackArgument) return null;
|
|
45830
|
-
let callbackNames = resolveParentCallbackPropNames(analysis, callbackArgument, scopes, new Set(visitedReferences), false);
|
|
45831
|
-
if (!callbackNames) return null;
|
|
45832
|
-
for (const candidateReference of receiverReference.resolved.references) {
|
|
45833
|
-
const candidateIdentifier = candidateReference.identifier;
|
|
45834
|
-
const candidateMember = candidateIdentifier.parent;
|
|
45835
|
-
if (!candidateMember || !isNodeOfType(candidateMember, "MemberExpression") || candidateMember.object !== candidateIdentifier || getStaticMemberPropertyName(candidateMember) !== "current") continue;
|
|
45836
|
-
const assignment = candidateMember.parent;
|
|
45837
|
-
if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.left !== candidateMember) continue;
|
|
45838
|
-
if (assignment.operator !== "=") return null;
|
|
45839
|
-
callbackNames = mergeRequiredBranches(callbackNames, resolveParentCallbackPropNames(analysis, assignment.right, scopes, new Set(visitedReferences), false));
|
|
45840
|
-
if (!callbackNames) return null;
|
|
45841
|
-
}
|
|
45842
|
-
return callbackNames;
|
|
45843
|
-
}
|
|
45844
|
-
if (!isNodeOfType(initializer, "ObjectExpression")) return null;
|
|
45845
|
-
const property = initializer.properties.find((candidateProperty) => isNodeOfType(candidateProperty, "Property") && getStaticPropertyKeyName(candidateProperty, { allowComputedString: true }) === propertyName);
|
|
45846
|
-
if (!property || !isNodeOfType(property, "Property")) return null;
|
|
45847
|
-
return resolveParentCallbackPropNames(analysis, property.value, scopes, visitedReferences, false);
|
|
45848
|
-
};
|
|
45849
|
-
const getParentCallbackPropNames = ({ analysis, expression, scopes }) => resolveParentCallbackPropNames(analysis, expression, scopes, /* @__PURE__ */ new Set(), false);
|
|
45850
|
-
//#endregion
|
|
45851
44428
|
//#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
|
|
45852
44429
|
const isUseStateIdentifier = (identifier) => {
|
|
45853
44430
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
@@ -45876,18 +44453,14 @@ const FUNCTION_WRAPPER_HOOK_NAMES$1 = new Set([
|
|
|
45876
44453
|
"useStableCallback",
|
|
45877
44454
|
"useCallbackRef"
|
|
45878
44455
|
]);
|
|
45879
|
-
const getWrapperHookWrappedFunction = (initializer
|
|
44456
|
+
const getWrapperHookWrappedFunction = (initializer) => {
|
|
45880
44457
|
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
45881
|
-
const transparentReactArgument = getTransparentReactCallbackWrapperArgument(initializer, resultSymbol, scopes);
|
|
45882
|
-
if (transparentReactArgument) return transparentReactArgument;
|
|
45883
44458
|
const callee = initializer.callee;
|
|
45884
44459
|
const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") ? callee.property.name : null;
|
|
45885
44460
|
if (!calleeName || !FUNCTION_WRAPPER_HOOK_NAMES$1.has(calleeName)) return null;
|
|
45886
44461
|
const wrapped = initializer.arguments?.[0];
|
|
45887
|
-
if (!wrapped) return null;
|
|
45888
|
-
|
|
45889
|
-
if (isFunctionLike$1(wrapped)) return wrapped;
|
|
45890
|
-
return null;
|
|
44462
|
+
if (!wrapped || !isFunctionLike$1(wrapped)) return null;
|
|
44463
|
+
return wrapped;
|
|
45891
44464
|
};
|
|
45892
44465
|
const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
|
|
45893
44466
|
const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
|
|
@@ -45897,29 +44470,16 @@ const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstre
|
|
|
45897
44470
|
const innerParent = innerIdentifier.parent;
|
|
45898
44471
|
return Boolean(innerParent && isNodeOfType(innerParent, "CallExpression") && innerParent.callee === innerIdentifier);
|
|
45899
44472
|
});
|
|
45900
|
-
const isDirectParentCallbackRef = (analysis, ref
|
|
44473
|
+
const isDirectParentCallbackRef = (analysis, ref) => {
|
|
45901
44474
|
if (isProp(analysis, ref)) return true;
|
|
45902
|
-
if (hasMutableBindingWrite$1(ref)) {
|
|
45903
|
-
if (!(ref.resolved?.references.filter((candidateReference) => candidateReference.isWrite() && !candidateReference.init) ?? []).every((candidateReference) => {
|
|
45904
|
-
const candidateIdentifier = candidateReference.identifier;
|
|
45905
|
-
const assignment = candidateIdentifier.parent;
|
|
45906
|
-
if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== candidateIdentifier) return false;
|
|
45907
|
-
const assignedReferences = getDownstreamRefs(analysis, assignment.right);
|
|
45908
|
-
return assignedReferences.length > 0 && assignedReferences.every((assignedReference) => isProp(analysis, assignedReference));
|
|
45909
|
-
})) return false;
|
|
45910
|
-
}
|
|
45911
44475
|
return Boolean(ref.resolved?.defs.some((def) => {
|
|
45912
44476
|
const node = def.node;
|
|
45913
44477
|
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
45914
44478
|
const initializer = unwrapChainExpression(node.init);
|
|
45915
|
-
const wrappedFunction = getWrapperHookWrappedFunction(initializer
|
|
44479
|
+
const wrappedFunction = getWrapperHookWrappedFunction(initializer);
|
|
45916
44480
|
if (wrappedFunction) {
|
|
45917
44481
|
if (wrappedFunction.async) return false;
|
|
45918
|
-
|
|
45919
|
-
const directName = getParentCallbackPropName(analysis, wrappedFunction);
|
|
45920
|
-
const downstreamReferences = getDownstreamRefs(analysis, wrappedFunction);
|
|
45921
|
-
if (directName !== null) return true;
|
|
45922
|
-
return downstreamReferences.some((wrappedReference) => !hasMutableBindingWrite$1(wrappedReference) && getUpstreamRefs(analysis, wrappedReference).some((upstreamReference) => isProp(analysis, upstreamReference)));
|
|
44482
|
+
return wrappedFunctionNotifiesParent(analysis, wrappedFunction);
|
|
45923
44483
|
}
|
|
45924
44484
|
if (!isNodeOfType(initializer, "Identifier") && !isNodeOfType(initializer, "MemberExpression")) return false;
|
|
45925
44485
|
return getDownstreamRefs(analysis, initializer).some((initializerRef) => getUpstreamRefs(analysis, initializerRef).some((upstreamRef) => isProp(analysis, upstreamRef)));
|
|
@@ -45929,7 +44489,7 @@ const getDeclarationKind = (declarator) => {
|
|
|
45929
44489
|
const declaration = declarator.parent;
|
|
45930
44490
|
return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
|
|
45931
44491
|
};
|
|
45932
|
-
const hasMutableBindingWrite
|
|
44492
|
+
const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
45933
44493
|
const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
|
|
45934
44494
|
const unwrappedExpression = stripParenExpression(expression);
|
|
45935
44495
|
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
@@ -45941,7 +44501,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
45941
44501
|
const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
|
|
45942
44502
|
return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
|
|
45943
44503
|
}
|
|
45944
|
-
if (hasMutableBindingWrite
|
|
44504
|
+
if (hasMutableBindingWrite(callbackReference)) return null;
|
|
45945
44505
|
const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
45946
44506
|
if (definitions.length !== 1) return null;
|
|
45947
44507
|
const declarator = definitions[0];
|
|
@@ -46007,7 +44567,7 @@ const getRefAliasDeclarator = (identifier) => {
|
|
|
46007
44567
|
const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
|
|
46008
44568
|
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
46009
44569
|
const receiverReference = getRef(analysis, receiver);
|
|
46010
|
-
if (!receiverReference?.resolved || hasMutableBindingWrite
|
|
44570
|
+
if (!receiverReference?.resolved || hasMutableBindingWrite(receiverReference)) return null;
|
|
46011
44571
|
const variables = /* @__PURE__ */ new Set();
|
|
46012
44572
|
let currentVariable = receiverReference.resolved;
|
|
46013
44573
|
let refCall = null;
|
|
@@ -46023,7 +44583,7 @@ const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
|
|
|
46023
44583
|
}
|
|
46024
44584
|
if (getDeclarationKind(declarator) !== "const" || !isNodeOfType(stripParenExpression(declarator.init), "Identifier")) return null;
|
|
46025
44585
|
const upstreamReference = getRef(analysis, stripParenExpression(declarator.init));
|
|
46026
|
-
if (!upstreamReference?.resolved || hasMutableBindingWrite
|
|
44586
|
+
if (!upstreamReference?.resolved || hasMutableBindingWrite(upstreamReference)) return null;
|
|
46027
44587
|
currentVariable = upstreamReference.resolved;
|
|
46028
44588
|
}
|
|
46029
44589
|
if (!refCall) return null;
|
|
@@ -46098,7 +44658,7 @@ const isParentPropsContextMerge = (analysis, expression) => {
|
|
|
46098
44658
|
while (isNodeOfType(currentExpression, "Identifier")) {
|
|
46099
44659
|
const currentReference = getRef(analysis, currentExpression);
|
|
46100
44660
|
const currentVariable = currentReference?.resolved;
|
|
46101
|
-
if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite
|
|
44661
|
+
if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return false;
|
|
46102
44662
|
visitedVariables.add(currentVariable);
|
|
46103
44663
|
const definitions = currentVariable.defs.filter((definition) => isNodeOfType(definition.node, "VariableDeclarator"));
|
|
46104
44664
|
if (definitions.length !== 1) return false;
|
|
@@ -46112,11 +44672,11 @@ const isParentPropsContextMerge = (analysis, expression) => {
|
|
|
46112
44672
|
const propsExpression = stripParenExpression(propsSpread.argument);
|
|
46113
44673
|
if (!isNodeOfType(propsExpression, "Identifier")) return false;
|
|
46114
44674
|
const propsReference = getRef(analysis, propsExpression);
|
|
46115
|
-
if (!propsReference?.resolved || !isWholePropsObjectReference(analysis, propsReference) || hasMutableBindingWrite
|
|
44675
|
+
if (!propsReference?.resolved || !isWholePropsObjectReference(analysis, propsReference) || hasMutableBindingWrite(propsReference) || propsReference.resolved.references.some((candidateReference) => candidateReference !== propsReference)) return false;
|
|
46116
44676
|
const contextExpression = stripParenExpression(contextSpread.argument);
|
|
46117
44677
|
if (!isNodeOfType(contextExpression, "Identifier")) return false;
|
|
46118
44678
|
const contextReference = getRef(analysis, contextExpression);
|
|
46119
|
-
if (!contextReference?.resolved || hasMutableBindingWrite
|
|
44679
|
+
if (!contextReference?.resolved || hasMutableBindingWrite(contextReference) || contextReference.resolved.references.some((candidateReference) => !candidateReference.init && candidateReference !== contextReference)) return false;
|
|
46120
44680
|
const contextInitializer = contextReference.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
46121
44681
|
if (!contextInitializer || !isNodeOfType(contextInitializer, "VariableDeclarator") || getDeclarationKind(contextInitializer) !== "const" || !contextInitializer.init || !isNodeOfType(contextInitializer.init, "CallExpression")) return false;
|
|
46122
44682
|
const contextHook = stripParenExpression(contextInitializer.init.callee);
|
|
@@ -46130,7 +44690,7 @@ const getImmutableParentCallbackPropName = (analysis, expression) => {
|
|
|
46130
44690
|
while (isNodeOfType(currentExpression, "Identifier")) {
|
|
46131
44691
|
const currentReference = getRef(analysis, currentExpression);
|
|
46132
44692
|
const currentVariable = currentReference?.resolved;
|
|
46133
|
-
if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite
|
|
44693
|
+
if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return null;
|
|
46134
44694
|
visitedVariables.add(currentVariable);
|
|
46135
44695
|
const definition = currentVariable.defs.length === 1 ? currentVariable.defs[0] : null;
|
|
46136
44696
|
const bindingIdentifier = definition?.name;
|
|
@@ -46199,7 +44759,7 @@ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) =>
|
|
|
46199
44759
|
while (isNodeOfType(currentExpression, "Identifier")) {
|
|
46200
44760
|
const callbackReference = getRef(analysis, currentExpression);
|
|
46201
44761
|
const callbackVariable = callbackReference?.resolved;
|
|
46202
|
-
if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite
|
|
44762
|
+
if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite(callbackReference)) return null;
|
|
46203
44763
|
visitedVariables.add(callbackVariable);
|
|
46204
44764
|
const definition = callbackVariable.defs.length === 1 ? callbackVariable.defs[0] : null;
|
|
46205
44765
|
const declarator = definition?.node;
|
|
@@ -46219,11 +44779,10 @@ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) =>
|
|
|
46219
44779
|
if (!propertyName || !COMMAND_PROP_NAME_PATTERN.test(propertyName)) return null;
|
|
46220
44780
|
return refCurrentObjectPreservesCallbackProperty(analysis, currentExpression.object, propertyName, isReactUseRefCall) ? propertyName : null;
|
|
46221
44781
|
};
|
|
46222
|
-
const isWrapperHookCallbackRef = (analysis, ref
|
|
44782
|
+
const isWrapperHookCallbackRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
|
|
46223
44783
|
const node = def.node;
|
|
46224
44784
|
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
46225
|
-
|
|
46226
|
-
return getWrapperHookWrappedFunction(unwrapChainExpression(node.init), resultSymbol, scopes) !== null;
|
|
44785
|
+
return getWrapperHookWrappedFunction(unwrapChainExpression(node.init)) !== null;
|
|
46227
44786
|
}));
|
|
46228
44787
|
const isHandlerBagArgument = (analysis, argument) => {
|
|
46229
44788
|
if (!isNodeOfType(argument, "ObjectExpression")) return false;
|
|
@@ -46242,26 +44801,14 @@ const isHandlerBagArgument = (analysis, argument) => {
|
|
|
46242
44801
|
};
|
|
46243
44802
|
const getFunctionalUpdaterDataRefs = (analysis, updater) => getDownstreamRefs(analysis, updater).filter((updaterRef) => !updaterRef.resolved?.defs.some((def) => def.type === "Parameter" && def.node === updater));
|
|
46244
44803
|
const HOOK_NAME_PATTERN$1 = /^use[A-Z0-9]/;
|
|
46245
|
-
const EXTERNAL_SUBSCRIPTION_HOOK_NAMES
|
|
44804
|
+
const EXTERNAL_SUBSCRIPTION_HOOK_NAMES = new Set([
|
|
46246
44805
|
"useIntersectionObserver",
|
|
46247
44806
|
"useMatchMedia",
|
|
46248
|
-
"useMediaJobProgress",
|
|
46249
44807
|
"useMediaQuery",
|
|
46250
|
-
"useMediaQueryState",
|
|
46251
44808
|
"useResizeObserver",
|
|
46252
44809
|
"useVisibility",
|
|
46253
44810
|
"useWindowSize"
|
|
46254
44811
|
]);
|
|
46255
|
-
const isCallbackPropReference = (analysis, ref) => {
|
|
46256
|
-
if (!isProp(analysis, ref)) return false;
|
|
46257
|
-
const identifier = ref.identifier;
|
|
46258
|
-
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
46259
|
-
if (!isWholePropsObjectReference(analysis, ref)) return HANDLER_NAMED_PROP_PATTERN.test(identifier.name);
|
|
46260
|
-
const member = identifier.parent;
|
|
46261
|
-
if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier) return false;
|
|
46262
|
-
const propertyName = getStaticMemberPropertyName(member);
|
|
46263
|
-
return Boolean(propertyName && HANDLER_NAMED_PROP_PATTERN.test(propertyName));
|
|
46264
|
-
};
|
|
46265
44812
|
const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
|
|
46266
44813
|
const node = def.node;
|
|
46267
44814
|
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
@@ -46269,7 +44816,7 @@ const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs
|
|
|
46269
44816
|
if (!isNodeOfType(init, "CallExpression")) return false;
|
|
46270
44817
|
const callee = init.callee;
|
|
46271
44818
|
if (!isNodeOfType(callee, "Identifier") || !HOOK_NAME_PATTERN$1.test(callee.name)) return false;
|
|
46272
|
-
return (init.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) =>
|
|
44819
|
+
return (init.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isProp(analysis, downstreamRef)));
|
|
46273
44820
|
}));
|
|
46274
44821
|
const isParentWiredHookResultArgument = (analysis, argument) => {
|
|
46275
44822
|
if (!isNodeOfType(argument, "Identifier")) return false;
|
|
@@ -46282,40 +44829,19 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
|
|
|
46282
44829
|
if (!isNodeOfType(identifier, "Identifier") || !HOOK_NAME_PATTERN$1.test(identifier.name)) return false;
|
|
46283
44830
|
const parent = identifier.parent;
|
|
46284
44831
|
if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
|
|
46285
|
-
return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) =>
|
|
44832
|
+
return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isProp(analysis, downstreamRef)));
|
|
46286
44833
|
};
|
|
46287
|
-
const
|
|
46288
|
-
let hookFunction = resolveToFunction(ref);
|
|
46289
|
-
if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
|
|
46290
|
-
const definitionNode = definition.node;
|
|
46291
|
-
if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
|
|
46292
|
-
const initializer = stripParenExpression(definitionNode.init);
|
|
46293
|
-
if (!isNodeOfType(initializer, "CallExpression")) continue;
|
|
46294
|
-
const callee = stripParenExpression(initializer.callee);
|
|
46295
|
-
if (!isNodeOfType(callee, "Identifier")) continue;
|
|
46296
|
-
const calleeReference = getRef(analysis, callee);
|
|
46297
|
-
if (!calleeReference) continue;
|
|
46298
|
-
hookFunction = resolveToFunction(calleeReference);
|
|
46299
|
-
if (hookFunction) break;
|
|
46300
|
-
}
|
|
46301
|
-
if (!hookFunction) return null;
|
|
46302
|
-
const returnedReferences = collectFunctionReturnStatements(hookFunction).flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
|
|
46303
|
-
if (returnedReferences.length === 0) return null;
|
|
46304
|
-
return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
|
|
46305
|
-
};
|
|
46306
|
-
const isExternalSubscriptionHookRef = (analysis, ref) => {
|
|
44834
|
+
const isExternalSubscriptionHookRef = (ref) => {
|
|
46307
44835
|
const identifier = ref.identifier;
|
|
46308
44836
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
46309
|
-
|
|
46310
|
-
if (localHookProof !== null) return localHookProof;
|
|
46311
|
-
if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
|
|
44837
|
+
if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(identifier.name) && isCalleePosition(identifier)) return true;
|
|
46312
44838
|
return Boolean(ref.resolved?.defs.some((def) => {
|
|
46313
44839
|
const node = def.node;
|
|
46314
44840
|
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
46315
44841
|
const initializer = stripParenExpression(node.init);
|
|
46316
44842
|
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
46317
44843
|
const callee = stripParenExpression(initializer.callee);
|
|
46318
|
-
return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES
|
|
44844
|
+
return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(callee.name);
|
|
46319
44845
|
}));
|
|
46320
44846
|
};
|
|
46321
44847
|
const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
|
|
@@ -46351,22 +44877,16 @@ const noPassDataToParent = defineRule({
|
|
|
46351
44877
|
const callExpr = getCallExpr(ref);
|
|
46352
44878
|
if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
|
|
46353
44879
|
const callbackRefProvenance = getCallbackRefProvenance(analysis, node, callExpr, isReactUseRefCall, isReactUseEffectCall);
|
|
44880
|
+
if (isRefCall(analysis, ref) && !callbackRefProvenance) continue;
|
|
46354
44881
|
if (!isSynchronous(ref.identifier, effectFn)) continue;
|
|
46355
44882
|
const calleeNode = unwrapChainExpression(callExpr.callee);
|
|
46356
44883
|
const identifier = ref.identifier;
|
|
46357
|
-
|
|
46358
|
-
|
|
46359
|
-
expression: calleeNode,
|
|
46360
|
-
scopes: context.scopes
|
|
46361
|
-
});
|
|
46362
|
-
const callbackPropNames = callbackRefProvenance?.callbackPropNames ?? resolvedCallbackPropNames;
|
|
46363
|
-
if (isRefCall(analysis, ref) && !callbackPropNames) continue;
|
|
46364
|
-
if (callbackPropNames) {
|
|
46365
|
-
if ([...callbackPropNames].some((callbackPropName) => COMMAND_PROP_NAME_PATTERN.test(callbackPropName))) continue;
|
|
44884
|
+
if (callbackRefProvenance) {
|
|
44885
|
+
if ([...callbackRefProvenance.callbackPropNames].some((callbackPropName) => COMMAND_PROP_NAME_PATTERN.test(callbackPropName))) continue;
|
|
46366
44886
|
} else if (calleeNode === identifier) {
|
|
46367
44887
|
const callbackPropName = getCommandCallbackPropName(analysis, identifier, isReactUseRefCall);
|
|
46368
44888
|
if (callbackPropName && COMMAND_PROP_NAME_PATTERN.test(callbackPropName)) continue;
|
|
46369
|
-
if (!isDirectParentCallbackRef(analysis, ref
|
|
44889
|
+
if (!isDirectParentCallbackRef(analysis, ref)) continue;
|
|
46370
44890
|
if (isNodeOfType(identifier, "Identifier") && COMMAND_PROP_NAME_PATTERN.test(identifier.name)) continue;
|
|
46371
44891
|
} else if (isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === identifier) {
|
|
46372
44892
|
if (!isWholePropsObjectReference(analysis, ref)) continue;
|
|
@@ -46374,10 +44894,10 @@ const noPassDataToParent = defineRule({
|
|
|
46374
44894
|
} else continue;
|
|
46375
44895
|
const methodName = getCallMethodName(calleeNode);
|
|
46376
44896
|
const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
|
|
46377
|
-
if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead
|
|
44897
|
+
if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
|
|
46378
44898
|
if (methodName && COMMAND_PROP_NAME_PATTERN.test(methodName)) continue;
|
|
46379
|
-
if (!
|
|
46380
|
-
const isSetterNamedCallee =
|
|
44899
|
+
if (!callbackRefProvenance && isNamespacedApiCallee(calleeNode)) continue;
|
|
44900
|
+
const isSetterNamedCallee = callbackRefProvenance ? [...callbackRefProvenance.callbackPropNames].every((callbackPropName) => SETTER_NAMED_PROP_PATTERN.test(callbackPropName)) : Boolean((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) && SETTER_NAMED_PROP_PATTERN.test((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) ?? ""));
|
|
46381
44901
|
const isLeafRef = (argRef) => getUpstreamRefs(analysis, argRef).length === 1;
|
|
46382
44902
|
const argsUpstreamRefs = (callExpr.arguments ?? []).flatMap((argument) => {
|
|
46383
44903
|
if (isFunctionLike$1(argument)) {
|
|
@@ -46391,11 +44911,11 @@ const noPassDataToParent = defineRule({
|
|
|
46391
44911
|
if (argumentRef && resolveToFunction(argumentRef)) return [];
|
|
46392
44912
|
}
|
|
46393
44913
|
return getDownstreamRefs(analysis, argument);
|
|
46394
|
-
}).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef)
|
|
46395
|
-
if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref
|
|
44914
|
+
}).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
|
|
44915
|
+
if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
|
|
46396
44916
|
if (!argsUpstreamRefs.some((argRef) => {
|
|
46397
44917
|
if (isUseStateIdentifier(argRef.identifier)) return false;
|
|
46398
|
-
if (isExternalSubscriptionHookRef(
|
|
44918
|
+
if (isExternalSubscriptionHookRef(argRef)) return false;
|
|
46399
44919
|
if (isProp(analysis, argRef)) return false;
|
|
46400
44920
|
if (isUseRefIdentifier(argRef.identifier)) return false;
|
|
46401
44921
|
if (isRefCurrent(argRef)) return false;
|
|
@@ -46430,47 +44950,9 @@ const isCallResultConsumedAsArgument = (callExpression) => {
|
|
|
46430
44950
|
return false;
|
|
46431
44951
|
};
|
|
46432
44952
|
//#endregion
|
|
46433
|
-
//#region src/plugin/rules/state-and-effects/utils/is-custom-hook-state-result-reference.ts
|
|
46434
|
-
const NON_STATE_CUSTOM_HOOK_NAMES = new Set([
|
|
46435
|
-
"useCallbackRef",
|
|
46436
|
-
"useEffectEvent",
|
|
46437
|
-
"useEvent",
|
|
46438
|
-
"useEventCallback",
|
|
46439
|
-
"useLatest",
|
|
46440
|
-
"useMemoizedFn",
|
|
46441
|
-
"useStableCallback"
|
|
46442
|
-
]);
|
|
46443
|
-
const EXTERNAL_SUBSCRIPTION_HOOK_NAMES = new Set([
|
|
46444
|
-
"useIntersectionObserver",
|
|
46445
|
-
"useMatchMedia",
|
|
46446
|
-
"useMediaJobProgress",
|
|
46447
|
-
"useMediaQuery",
|
|
46448
|
-
"useResizeObserver",
|
|
46449
|
-
"useVisibility",
|
|
46450
|
-
"useWindowSize"
|
|
46451
|
-
]);
|
|
46452
|
-
const getHookCalleeName = (initializer) => {
|
|
46453
|
-
const unwrappedInitializer = stripParenExpression(initializer);
|
|
46454
|
-
if (!isNodeOfType(unwrappedInitializer, "CallExpression")) return null;
|
|
46455
|
-
const callee = stripParenExpression(unwrappedInitializer.callee);
|
|
46456
|
-
if (isNodeOfType(callee, "Identifier")) return callee.name;
|
|
46457
|
-
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
|
|
46458
|
-
return null;
|
|
46459
|
-
};
|
|
46460
|
-
const isCustomHookStateResultReference = (analysis, reference) => Boolean(reference.resolved?.defs.some((definition) => {
|
|
46461
|
-
const declarator = definition.node;
|
|
46462
|
-
if (!isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return false;
|
|
46463
|
-
const calleeName = getHookCalleeName(declarator.init);
|
|
46464
|
-
if (!calleeName || !HOOK_NAME_PATTERN$3.test(calleeName) || BUILTIN_HOOK_NAMES.has(calleeName) || NON_STATE_CUSTOM_HOOK_NAMES.has(calleeName) || EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(calleeName)) return false;
|
|
46465
|
-
const initializer = stripParenExpression(declarator.init);
|
|
46466
|
-
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
46467
|
-
return initializer.arguments.some((argument) => getDownstreamRefs(analysis, argument).some((argumentReference) => isProp(analysis, argumentReference)));
|
|
46468
|
-
}));
|
|
46469
|
-
//#endregion
|
|
46470
44953
|
//#region src/plugin/rules/state-and-effects/no-pass-live-state-to-parent.ts
|
|
46471
44954
|
const SETTER_NAMED_CALLBACK_PATTERN = /^set[A-Z]/;
|
|
46472
44955
|
const DATA_FETCHING_CALLBACK_PATTERN = /^(fetch|refetch|load|query|request)([A-Z_]|$)/;
|
|
46473
|
-
const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
46474
44956
|
const getCallCalleeName = (callExpr) => {
|
|
46475
44957
|
if (!isNodeOfType(callExpr, "CallExpression")) return null;
|
|
46476
44958
|
const callee = callExpr.callee;
|
|
@@ -46515,10 +44997,6 @@ const collectUpstreamStateRefs = (analysis, ref, stateRefs, visited) => {
|
|
|
46515
44997
|
stateRefs.push(ref);
|
|
46516
44998
|
return;
|
|
46517
44999
|
}
|
|
46518
|
-
if (isCustomHookStateResultReference(analysis, ref)) {
|
|
46519
|
-
stateRefs.push(ref);
|
|
46520
|
-
return;
|
|
46521
|
-
}
|
|
46522
45000
|
for (const def of ref.resolved?.defs ?? []) {
|
|
46523
45001
|
if (def.type === "ImportBinding" || def.type === "Parameter") continue;
|
|
46524
45002
|
const defNode = def.node;
|
|
@@ -46548,32 +45026,6 @@ const collectPropCallbackBoundStateRefs = (analysis, ref, isPropCallbackRef) =>
|
|
|
46548
45026
|
}
|
|
46549
45027
|
return stateRefs;
|
|
46550
45028
|
};
|
|
46551
|
-
const collectDirectCallStateRefs = (analysis, callExpression) => {
|
|
46552
|
-
const stateReferences = [];
|
|
46553
|
-
for (const argument of callExpression.arguments) {
|
|
46554
|
-
if (isFunctionLike$1(argument)) continue;
|
|
46555
|
-
for (const argumentReference of getDownstreamRefs(analysis, argument)) {
|
|
46556
|
-
if (resolveToFunction(argumentReference)) continue;
|
|
46557
|
-
collectUpstreamStateRefs(analysis, argumentReference, stateReferences, /* @__PURE__ */ new Set());
|
|
46558
|
-
}
|
|
46559
|
-
}
|
|
46560
|
-
return stateReferences;
|
|
46561
|
-
};
|
|
46562
|
-
const getTransparentWrapperPropReference = (analysis, reference, context) => {
|
|
46563
|
-
for (const definition of reference.resolved?.defs ?? []) {
|
|
46564
|
-
const declarator = definition.node;
|
|
46565
|
-
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
|
|
46566
|
-
const resultSymbol = context.scopes.symbolFor(declarator.id);
|
|
46567
|
-
const callbackArgument = getTransparentReactCallbackWrapperArgument(declarator.init, resultSymbol, context.scopes);
|
|
46568
|
-
if (!callbackArgument) continue;
|
|
46569
|
-
const callbackReferences = getDownstreamRefs(analysis, callbackArgument);
|
|
46570
|
-
const callbackReference = callbackReferences.find((candidateReference) => isPropCallbackInvocationRef(analysis, candidateReference));
|
|
46571
|
-
if (callbackReference) return callbackReference;
|
|
46572
|
-
const propReference = callbackReferences.find((candidateReference) => isProp(analysis, candidateReference) && !candidateReference.resolved?.references.some((candidateUsage) => candidateUsage.isWrite() && !candidateUsage.init));
|
|
46573
|
-
if (propReference) return propReference;
|
|
46574
|
-
}
|
|
46575
|
-
return null;
|
|
46576
|
-
};
|
|
46577
45029
|
const isSetterNamedCallbackReceivingData = (callbackRef) => {
|
|
46578
45030
|
const callExpr = getCallExpr(callbackRef);
|
|
46579
45031
|
if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) return false;
|
|
@@ -46609,16 +45061,6 @@ const resolvesToLocalHookReturnBinding = (ref) => Boolean(ref?.resolved?.defs?.s
|
|
|
46609
45061
|
const calleeName = getInitializerCalleeName(node.init);
|
|
46610
45062
|
return calleeName !== null && isReactHookName(calleeName) && !FUNCTION_WRAPPER_HOOK_NAMES.has(calleeName);
|
|
46611
45063
|
}));
|
|
46612
|
-
const getDirectLocalEffectHelper = (callExpression, effectFunction, context) => {
|
|
46613
|
-
const helperFunction = resolveExactLocalFunction(callExpression.callee, context.scopes);
|
|
46614
|
-
if (!helperFunction) return null;
|
|
46615
|
-
let ancestor = callExpression.parent;
|
|
46616
|
-
while (ancestor && ancestor !== effectFunction) {
|
|
46617
|
-
if (isFunctionLike$1(ancestor)) return null;
|
|
46618
|
-
ancestor = ancestor.parent;
|
|
46619
|
-
}
|
|
46620
|
-
return ancestor === effectFunction ? helperFunction : null;
|
|
46621
|
-
};
|
|
46622
45064
|
const noPassLiveStateToParent = defineRule({
|
|
46623
45065
|
id: "no-pass-live-state-to-parent",
|
|
46624
45066
|
title: "Live state pushed to parent via effect",
|
|
@@ -46633,32 +45075,20 @@ const noPassLiveStateToParent = defineRule({
|
|
|
46633
45075
|
if (!effectFnRefs) return;
|
|
46634
45076
|
const effectFn = getEffectFn(analysis, node);
|
|
46635
45077
|
if (!effectFn) return;
|
|
46636
|
-
const effectFunctionBody = isNodeOfType(effectFn, "ArrowFunctionExpression") || isNodeOfType(effectFn, "FunctionExpression") || isNodeOfType(effectFn, "FunctionDeclaration") ? effectFn.body : null;
|
|
46637
45078
|
for (const ref of effectFnRefs) {
|
|
45079
|
+
const propCallbackRefs = getEventualCallRefsTo(analysis, ref, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef));
|
|
45080
|
+
if (propCallbackRefs.length === 0) continue;
|
|
45081
|
+
if (resolvesToLocalHookReturnBinding(ref)) continue;
|
|
45082
|
+
if (!isSynchronous(ref.identifier, effectFn)) continue;
|
|
46638
45083
|
const callExpr = getCallExpr(ref);
|
|
46639
|
-
if (!callExpr
|
|
46640
|
-
const directLocalEffectHelper = getDirectLocalEffectHelper(callExpr, effectFn, context);
|
|
46641
|
-
const callGraphReferences = directLocalEffectHelper ? [ref, ...getDownstreamRefs(analysis, directLocalEffectHelper)] : [ref];
|
|
46642
|
-
const resolvedCallbackPropNames = getParentCallbackPropNames({
|
|
46643
|
-
analysis,
|
|
46644
|
-
expression: callExpr.callee,
|
|
46645
|
-
scopes: context.scopes
|
|
46646
|
-
});
|
|
46647
|
-
const callExpressionRoot = findTransparentExpressionRoot(callExpr);
|
|
46648
|
-
const notificationCallbackPropNames = Boolean(resolvedCallbackPropNames && callExpr.arguments.length > 0 && (!isCallResultCapturedToLocal(callExpr) || isNodeOfType(callExpressionRoot.parent, "ReturnStatement") && callExpressionRoot.parent.parent === effectFunctionBody) && [...resolvedCallbackPropNames].every((callbackPropName) => !DATA_FETCHING_CALLBACK_PATTERN.test(callbackPropName))) ? resolvedCallbackPropNames : null;
|
|
46649
|
-
if (!notificationCallbackPropNames && hasMutableBindingWrite(ref)) continue;
|
|
46650
|
-
const propCallbackRefs = callGraphReferences.flatMap((callGraphReference) => getEventualCallRefsTo(analysis, callGraphReference, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef)));
|
|
46651
|
-
const transparentPropReference = propCallbackRefs.length === 0 ? getTransparentWrapperPropReference(analysis, ref, context) : null;
|
|
46652
|
-
if (propCallbackRefs.length === 0 && !transparentPropReference && !notificationCallbackPropNames) continue;
|
|
46653
|
-
if (!notificationCallbackPropNames && resolvesToLocalHookReturnBinding(ref)) continue;
|
|
46654
|
-
if (!isSynchronous(ref.identifier, effectFn) && !directLocalEffectHelper) continue;
|
|
45084
|
+
if (!callExpr) continue;
|
|
46655
45085
|
if (isCallResultConsumedAsArgument(callExpr)) continue;
|
|
46656
45086
|
const calleeNode = callExpr.callee;
|
|
46657
45087
|
const methodName = calleeNode ? getCallMethodName(calleeNode) : null;
|
|
46658
45088
|
const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && calleeNode && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
|
|
46659
|
-
if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead
|
|
46660
|
-
if (
|
|
46661
|
-
const stateArgRefs =
|
|
45089
|
+
if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
|
|
45090
|
+
if (calleeNode && isNamespacedApiCallee(calleeNode)) continue;
|
|
45091
|
+
const stateArgRefs = collectPropCallbackBoundStateRefs(analysis, ref, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef));
|
|
46662
45092
|
const handsSetterNamedCallbackData = propCallbackRefs.some(isSetterNamedCallbackReceivingData);
|
|
46663
45093
|
if (stateArgRefs.length === 0 && !handsSetterNamedCallbackData) continue;
|
|
46664
45094
|
context.report({
|
|
@@ -47051,7 +45481,6 @@ const isStateLikeDependency = (analysis, element, isPropName) => {
|
|
|
47051
45481
|
if (!analysis) return true;
|
|
47052
45482
|
const reference = getRef(analysis, element);
|
|
47053
45483
|
if (!reference) return true;
|
|
47054
|
-
if (isCustomHookStateResultReference(analysis, reference)) return true;
|
|
47055
45484
|
const upstreamReferences = getUpstreamRefs(analysis, reference);
|
|
47056
45485
|
if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
|
|
47057
45486
|
return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
|
|
@@ -47068,22 +45497,6 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
|
|
|
47068
45497
|
if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
|
|
47069
45498
|
return isPropName(callbackArgument.name) ? callbackArgument.name : null;
|
|
47070
45499
|
};
|
|
47071
|
-
const getTransparentWrappedPropCallbackName = (callExpression, context, isPropName) => {
|
|
47072
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
47073
|
-
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
47074
|
-
const binding = findVariableInitializer(callExpression, callee.name);
|
|
47075
|
-
if (!binding?.initializer) return null;
|
|
47076
|
-
const resultSymbol = context.scopes.symbolFor(callee);
|
|
47077
|
-
const callbackArgument = getTransparentReactCallbackWrapperArgument(binding.initializer, resultSymbol, context.scopes);
|
|
47078
|
-
if (!callbackArgument) return null;
|
|
47079
|
-
const callbackSource = stripParenExpression(callbackArgument);
|
|
47080
|
-
if (isNodeOfType(callbackSource, "Identifier")) return isPropName(callbackSource.name, callbackSource) ? callbackSource.name : null;
|
|
47081
|
-
if (!isNodeOfType(callbackSource, "MemberExpression")) return null;
|
|
47082
|
-
const receiver = stripParenExpression(callbackSource.object);
|
|
47083
|
-
const propertyName = getStaticPropertyName(callbackSource);
|
|
47084
|
-
if (!isNodeOfType(receiver, "Identifier") || !propertyName) return null;
|
|
47085
|
-
return isPropName(receiver.name, receiver) ? propertyName : null;
|
|
47086
|
-
};
|
|
47087
45500
|
const noPropCallbackInEffect = defineRule({
|
|
47088
45501
|
id: "no-prop-callback-in-effect",
|
|
47089
45502
|
title: "Parent kept in sync with a callback effect",
|
|
@@ -47117,16 +45530,9 @@ const noPropCallbackInEffect = defineRule({
|
|
|
47117
45530
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
47118
45531
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
47119
45532
|
const directCallee = stripParenExpression(child.callee);
|
|
47120
|
-
const
|
|
47121
|
-
analysis,
|
|
47122
|
-
expression: directCallee,
|
|
47123
|
-
scopes: context.scopes
|
|
47124
|
-
}) : null;
|
|
47125
|
-
const calleeName = resolvedCallbackPropNames && [...resolvedCallbackPropNames][0] || isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName) || getTransparentWrappedPropCallbackName(child, context, propStackTracker.isPropName);
|
|
45533
|
+
const calleeName = isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName);
|
|
47126
45534
|
if (!calleeName) return;
|
|
47127
|
-
|
|
47128
|
-
const isDirectEffectReturn = isNodeOfType(callExpressionRoot.parent, "ReturnStatement") && callExpressionRoot.parent.parent === callback.body;
|
|
47129
|
-
if (!isResultDiscardedCall(child) && !isDirectEffectReturn) return;
|
|
45535
|
+
if (!isResultDiscardedCall(child)) return;
|
|
47130
45536
|
if (reportedNodes.has(child)) return;
|
|
47131
45537
|
reportedNodes.add(child);
|
|
47132
45538
|
context.report({
|
|
@@ -47959,69 +46365,6 @@ const noRedundantShouldComponentUpdate = defineRule({
|
|
|
47959
46365
|
}
|
|
47960
46366
|
});
|
|
47961
46367
|
//#endregion
|
|
47962
|
-
//#region src/plugin/rules/correctness/no-ref-callback-cleanup-before-react-19.ts
|
|
47963
|
-
const resolveFunctionExpressions = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
47964
|
-
const expression = stripParenExpression(rawExpression);
|
|
47965
|
-
if (isFunctionLike$1(expression)) return expression.async || expression.generator ? [] : [expression];
|
|
47966
|
-
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
47967
|
-
if (isNodeOfType(expression.test, "Literal")) return resolveFunctionExpressions(expression.test.value ? expression.consequent : expression.alternate, scopes, visitedSymbolIds);
|
|
47968
|
-
return [...resolveFunctionExpressions(expression.consequent, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.alternate, scopes, visitedSymbolIds)];
|
|
47969
|
-
}
|
|
47970
|
-
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
47971
|
-
if (isNodeOfType(expression.left, "Literal")) {
|
|
47972
|
-
const isLeftTruthy = Boolean(expression.left.value);
|
|
47973
|
-
if (expression.operator === "&&" && !isLeftTruthy) return [];
|
|
47974
|
-
if (expression.operator === "||" && isLeftTruthy) return [];
|
|
47975
|
-
if (expression.operator === "??" && expression.left.value !== null) return [];
|
|
47976
|
-
}
|
|
47977
|
-
if (expression.operator === "&&") return resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds);
|
|
47978
|
-
return [...resolveFunctionExpressions(expression.left, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds)];
|
|
47979
|
-
}
|
|
47980
|
-
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
47981
|
-
const finalExpression = expression.expressions.at(-1);
|
|
47982
|
-
return finalExpression ? resolveFunctionExpressions(finalExpression, scopes, visitedSymbolIds) : [];
|
|
47983
|
-
}
|
|
47984
|
-
if (isNodeOfType(expression, "CallExpression")) {
|
|
47985
|
-
if (!isReactApiCall(expression, "useCallback", scopes)) return [];
|
|
47986
|
-
const callback = expression.arguments[0];
|
|
47987
|
-
return callback && !isNodeOfType(callback, "SpreadElement") ? resolveFunctionExpressions(callback, scopes, visitedSymbolIds) : [];
|
|
47988
|
-
}
|
|
47989
|
-
if (!isNodeOfType(expression, "Identifier")) return [];
|
|
47990
|
-
const symbol = scopes.symbolFor(expression);
|
|
47991
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return [];
|
|
47992
|
-
if (symbol.kind === "function" && isNodeOfType(symbol.declarationNode, "FunctionDeclaration") && symbol.references.every((reference) => reference.flag === "read")) return resolveFunctionExpressions(symbol.declarationNode, scopes, new Set([...visitedSymbolIds, symbol.id]));
|
|
47993
|
-
const initializer = getDirectConstInitializer(symbol);
|
|
47994
|
-
if (!initializer) return [];
|
|
47995
|
-
return resolveFunctionExpressions(initializer, scopes, new Set([...visitedSymbolIds, symbol.id]));
|
|
47996
|
-
};
|
|
47997
|
-
const functionReturnsCleanupFunction = (functionExpression, scopes) => {
|
|
47998
|
-
if (!isFunctionLike$1(functionExpression)) return false;
|
|
47999
|
-
if (!isNodeOfType(functionExpression.body, "BlockStatement")) return resolveFunctionExpressions(functionExpression.body, scopes).length > 0;
|
|
48000
|
-
return collectFunctionReturnStatements(functionExpression).some((returnStatement) => Boolean(returnStatement.argument && resolveFunctionExpressions(returnStatement.argument, scopes).length > 0));
|
|
48001
|
-
};
|
|
48002
|
-
const callbackReturnsCleanupFunction = (callback, scopes) => {
|
|
48003
|
-
return resolveFunctionExpressions(callback, scopes).some((functionExpression) => functionReturnsCleanupFunction(functionExpression, scopes));
|
|
48004
|
-
};
|
|
48005
|
-
const noRefCallbackCleanupBeforeReact19 = defineRule({
|
|
48006
|
-
id: "no-ref-callback-cleanup-before-react-19",
|
|
48007
|
-
title: "Ref cleanup requires React 19",
|
|
48008
|
-
requires: ["react:18"],
|
|
48009
|
-
disabledWhen: ["react:19"],
|
|
48010
|
-
severity: "warn",
|
|
48011
|
-
recommendation: "React 18 ignores functions returned from ref callbacks. Handle cleanup when React calls the ref with `null`, or require React 19 before returning a cleanup function.",
|
|
48012
|
-
create: (context) => ({ JSXAttribute(node) {
|
|
48013
|
-
if (getJsxAttributeName(node.name) !== "ref") return;
|
|
48014
|
-
if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
|
|
48015
|
-
const callback = node.value.expression;
|
|
48016
|
-
if (!callback || isNodeOfType(callback, "JSXEmptyExpression")) return;
|
|
48017
|
-
if (!callbackReturnsCleanupFunction(callback, context.scopes)) return;
|
|
48018
|
-
context.report({
|
|
48019
|
-
node,
|
|
48020
|
-
message: "This ref callback returns a cleanup function, but React 18 ignores ref cleanup returns, so the cleanup never runs. Handle detachment when React calls the ref with `null`, or require React 19."
|
|
48021
|
-
});
|
|
48022
|
-
} })
|
|
48023
|
-
});
|
|
48024
|
-
//#endregion
|
|
48025
46368
|
//#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
|
|
48026
46369
|
const REPEATED_ANCESTOR_TYPES = new Set([
|
|
48027
46370
|
"DoWhileStatement",
|
|
@@ -54782,6 +53125,12 @@ const isInsideEs6Component$1 = (methodDefinition) => {
|
|
|
54782
53125
|
if (!owningClass) return false;
|
|
54783
53126
|
return isPreactOrReactComponentClass(owningClass);
|
|
54784
53127
|
};
|
|
53128
|
+
const stripThisParameter = (params) => {
|
|
53129
|
+
const first = params[0];
|
|
53130
|
+
if (!first) return params;
|
|
53131
|
+
if (isNodeOfType(first, "Identifier") && first.name === "this") return params.slice(1);
|
|
53132
|
+
return params;
|
|
53133
|
+
};
|
|
54785
53134
|
const preactNoRenderArguments = defineRule({
|
|
54786
53135
|
id: "preact-no-render-arguments",
|
|
54787
53136
|
title: "render() reads props from arguments",
|
|
@@ -57983,39 +56332,8 @@ const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node,
|
|
|
57983
56332
|
destructureIndex: 1
|
|
57984
56333
|
});
|
|
57985
56334
|
//#endregion
|
|
57986
|
-
//#region src/plugin/utils/unwrap-return-expression.ts
|
|
57987
|
-
const unwrapReturnExpression = (node) => isNodeOfType(node, "ReturnStatement") && node.argument ? node.argument : node;
|
|
57988
|
-
//#endregion
|
|
57989
56335
|
//#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
|
|
57990
56336
|
const USE_EFFECT_ONLY = new Set(["useEffect"]);
|
|
57991
|
-
const USE_CALLBACK_ONLY = new Set(["useCallback"]);
|
|
57992
|
-
const USE_STATE_ONLY = new Set(["useState"]);
|
|
57993
|
-
const REACT_API_CALL_OPTIONS = {
|
|
57994
|
-
allowGlobalReactNamespace: true,
|
|
57995
|
-
allowUnboundBareCalls: true,
|
|
57996
|
-
resolveNamedAliases: true
|
|
57997
|
-
};
|
|
57998
|
-
const expressionReadsDerivedSymbol = (context, expression, stateDerivedSymbolIds) => {
|
|
57999
|
-
let readsDerivedSymbol = false;
|
|
58000
|
-
walkAst(expression, (node) => {
|
|
58001
|
-
if (readsDerivedSymbol) return false;
|
|
58002
|
-
if (node !== expression && isFunctionLike$1(node)) return false;
|
|
58003
|
-
if (isNodeOfType(node, "Identifier") && stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) readsDerivedSymbol = true;
|
|
58004
|
-
});
|
|
58005
|
-
return readsDerivedSymbol;
|
|
58006
|
-
};
|
|
58007
|
-
const getStaticObjectPropertyName = (property) => {
|
|
58008
|
-
if (!isNodeOfType(property, "Property") || property.computed || property.method || property.kind !== "init") return null;
|
|
58009
|
-
if (isNodeOfType(property.key, "Identifier")) return property.key.name;
|
|
58010
|
-
if (isNodeOfType(property.key, "Literal") && (typeof property.key.value === "string" || typeof property.key.value === "number")) return String(property.key.value);
|
|
58011
|
-
return null;
|
|
58012
|
-
};
|
|
58013
|
-
const isNonVisibleJsxSpreadProperty = (propertyName) => propertyName === "id" || propertyName.startsWith("aria-") || /^on[A-Z]/.test(propertyName);
|
|
58014
|
-
const isTransparentAssignmentTarget = (identifier) => {
|
|
58015
|
-
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
58016
|
-
const parent = expressionRoot.parent;
|
|
58017
|
-
return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expressionRoot || isNodeOfType(parent, "UpdateExpression") && parent.argument === expressionRoot || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expressionRoot);
|
|
58018
|
-
};
|
|
58019
56337
|
const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
|
|
58020
56338
|
let readsCurrent = false;
|
|
58021
56339
|
walkAst(argument, (child) => {
|
|
@@ -58067,166 +56385,6 @@ const isStateUsedOnlyInIdOrAriaAttributes = (setterCall, setterName) => {
|
|
|
58067
56385
|
});
|
|
58068
56386
|
return referenceCount > 0 && !nonAriaReferenceFound;
|
|
58069
56387
|
};
|
|
58070
|
-
const isGlobalWindowMember = (context, node, propertyName) => {
|
|
58071
|
-
const member = stripParenExpression(node);
|
|
58072
|
-
if (!isNodeOfType(member, "MemberExpression") || member.computed) return false;
|
|
58073
|
-
const receiver = stripParenExpression(member.object);
|
|
58074
|
-
return isNodeOfType(receiver, "Identifier") && receiver.name === "window" && context.scopes.isGlobalReference(receiver) && isNodeOfType(member.property, "Identifier") && member.property.name === propertyName;
|
|
58075
|
-
};
|
|
58076
|
-
const getDirectWindowWidthSetter = (context, statement) => {
|
|
58077
|
-
const call = unwrapDiscardedExpression(statement);
|
|
58078
|
-
if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 1) return null;
|
|
58079
|
-
if (!isNodeOfType(call.callee, "Identifier") || !isSetterCall(call)) return null;
|
|
58080
|
-
const argument = call.arguments[0];
|
|
58081
|
-
return isGlobalWindowMember(context, argument, "innerWidth") ? call : null;
|
|
58082
|
-
};
|
|
58083
|
-
const getResizeListenerHandler = (context, statement, methodName) => {
|
|
58084
|
-
const call = unwrapDiscardedExpression(statement);
|
|
58085
|
-
if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 2) return null;
|
|
58086
|
-
if (!isGlobalWindowMember(context, call.callee, methodName)) return null;
|
|
58087
|
-
const eventName = call.arguments[0];
|
|
58088
|
-
const handler = call.arguments[1];
|
|
58089
|
-
if (!isNodeOfType(eventName, "Literal") || eventName.value !== "resize") return null;
|
|
58090
|
-
return isNodeOfType(handler, "Identifier") ? handler : null;
|
|
58091
|
-
};
|
|
58092
|
-
const getCleanupResizeHandler = (context, statement) => {
|
|
58093
|
-
if (!isNodeOfType(statement, "ReturnStatement") || !isFunctionLike$1(statement.argument)) return null;
|
|
58094
|
-
const cleanupStatements = getCallbackStatements(statement.argument);
|
|
58095
|
-
if (cleanupStatements.length !== 1) return null;
|
|
58096
|
-
return getResizeListenerHandler(context, unwrapReturnExpression(cleanupStatements[0]), "removeEventListener");
|
|
58097
|
-
};
|
|
58098
|
-
const findExactViewportState = (context, componentFunction, setterCall) => {
|
|
58099
|
-
if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
|
|
58100
|
-
const componentBody = componentFunction.body;
|
|
58101
|
-
if (!isNodeOfType(setterCall.callee, "Identifier")) return null;
|
|
58102
|
-
const setterSymbol = context.scopes.symbolFor(setterCall.callee);
|
|
58103
|
-
if (!setterSymbol || setterSymbol.kind !== "const" || !isNodeOfType(setterSymbol.declarationNode, "VariableDeclarator")) return null;
|
|
58104
|
-
const declarator = setterSymbol.declarationNode;
|
|
58105
|
-
if (!isNodeOfType(declarator.id, "ArrayPattern")) return null;
|
|
58106
|
-
const stateIdentifier = declarator.id.elements?.[0];
|
|
58107
|
-
const setterIdentifier = declarator.id.elements?.[1];
|
|
58108
|
-
if (!isNodeOfType(stateIdentifier, "Identifier") || !isNodeOfType(setterIdentifier, "Identifier") || setterIdentifier !== setterSymbol.bindingIdentifier || !isNodeOfType(declarator.init, "CallExpression") || !isReactApiCall(declarator.init, USE_STATE_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return null;
|
|
58109
|
-
const initializer = declarator.init.arguments?.[0];
|
|
58110
|
-
if (!isNodeOfType(initializer, "Literal") || initializer.value !== 0) return null;
|
|
58111
|
-
const stateSymbol = context.scopes.symbolFor(stateIdentifier);
|
|
58112
|
-
if (!stateSymbol) return null;
|
|
58113
|
-
const stateDerivedSymbolIds = new Set([stateSymbol.id]);
|
|
58114
|
-
let didAddDerivedSymbol = true;
|
|
58115
|
-
while (didAddDerivedSymbol) {
|
|
58116
|
-
didAddDerivedSymbol = false;
|
|
58117
|
-
for (const statement of componentBody.body ?? []) {
|
|
58118
|
-
if (!isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
58119
|
-
for (const candidateDeclarator of statement.declarations ?? []) {
|
|
58120
|
-
if (!isNodeOfType(candidateDeclarator.id, "Identifier") || !candidateDeclarator.init) continue;
|
|
58121
|
-
const candidateInitializer = stripParenExpression(candidateDeclarator.init);
|
|
58122
|
-
if (isFunctionLike$1(candidateInitializer) || isNodeOfType(candidateInitializer, "CallExpression") && isReactApiCall(candidateInitializer, USE_CALLBACK_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) continue;
|
|
58123
|
-
if (!expressionReadsDerivedSymbol(context, candidateInitializer, stateDerivedSymbolIds)) continue;
|
|
58124
|
-
const candidateSymbol = context.scopes.symbolFor(candidateDeclarator.id);
|
|
58125
|
-
if (candidateSymbol?.kind === "const" && candidateSymbol.references.every((reference) => reference.flag === "read" && !isTransparentAssignmentTarget(reference.identifier)) && !stateDerivedSymbolIds.has(candidateSymbol.id)) {
|
|
58126
|
-
stateDerivedSymbolIds.add(candidateSymbol.id);
|
|
58127
|
-
didAddDerivedSymbol = true;
|
|
58128
|
-
}
|
|
58129
|
-
}
|
|
58130
|
-
}
|
|
58131
|
-
}
|
|
58132
|
-
const staticSpreadVisibilityBySymbolId = /* @__PURE__ */ new Map();
|
|
58133
|
-
const hasOnlyStaticObjectReferences = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
58134
|
-
const symbol = context.scopes.symbolFor(identifier);
|
|
58135
|
-
if (!symbol) return false;
|
|
58136
|
-
if (visitedSymbolIds.has(symbol.id)) return true;
|
|
58137
|
-
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
58138
|
-
nextVisitedSymbolIds.add(symbol.id);
|
|
58139
|
-
let hasUnknownReference = false;
|
|
58140
|
-
walkAst(componentBody, (node) => {
|
|
58141
|
-
if (hasUnknownReference || !isNodeOfType(node, "Identifier") || context.scopes.symbolFor(node)?.id !== symbol.id || node === symbol.bindingIdentifier) return;
|
|
58142
|
-
const referenceRoot = findTransparentExpressionRoot(node);
|
|
58143
|
-
const parent = referenceRoot.parent;
|
|
58144
|
-
if (isNodeOfType(parent, "JSXSpreadAttribute") && parent.argument === referenceRoot) return;
|
|
58145
|
-
if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier") && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const" && hasOnlyStaticObjectReferences(parent.id, nextVisitedSymbolIds)) return;
|
|
58146
|
-
hasUnknownReference = true;
|
|
58147
|
-
return false;
|
|
58148
|
-
});
|
|
58149
|
-
return !hasUnknownReference;
|
|
58150
|
-
};
|
|
58151
|
-
const classifyStaticSpreadObject = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
58152
|
-
const symbol = context.scopes.symbolFor(identifier);
|
|
58153
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return "unknown";
|
|
58154
|
-
const cachedVisibility = staticSpreadVisibilityBySymbolId.get(symbol.id);
|
|
58155
|
-
if (cachedVisibility) return cachedVisibility;
|
|
58156
|
-
if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || symbol.declarationNode.id !== symbol.bindingIdentifier || !symbol.declarationNode.init) return "unknown";
|
|
58157
|
-
if (!hasOnlyStaticObjectReferences(identifier)) return "unknown";
|
|
58158
|
-
const initializer = stripParenExpression(symbol.declarationNode.init);
|
|
58159
|
-
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
58160
|
-
nextVisitedSymbolIds.add(symbol.id);
|
|
58161
|
-
if (isNodeOfType(initializer, "Identifier")) {
|
|
58162
|
-
const visibility = classifyStaticSpreadObject(initializer, nextVisitedSymbolIds);
|
|
58163
|
-
staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
|
|
58164
|
-
return visibility;
|
|
58165
|
-
}
|
|
58166
|
-
if (!isNodeOfType(initializer, "ObjectExpression")) return "unknown";
|
|
58167
|
-
let visibility = "non-visible";
|
|
58168
|
-
for (const property of initializer.properties ?? []) {
|
|
58169
|
-
const propertyName = getStaticObjectPropertyName(property);
|
|
58170
|
-
if (!isNodeOfType(property, "Property") || !propertyName) {
|
|
58171
|
-
visibility = "unknown";
|
|
58172
|
-
break;
|
|
58173
|
-
}
|
|
58174
|
-
if (expressionReadsDerivedSymbol(context, property.value, stateDerivedSymbolIds) && !isNonVisibleJsxSpreadProperty(propertyName)) visibility = "visible";
|
|
58175
|
-
}
|
|
58176
|
-
staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
|
|
58177
|
-
return visibility;
|
|
58178
|
-
};
|
|
58179
|
-
let hasNonAriaReference = false;
|
|
58180
|
-
walkAst(componentBody, (node) => {
|
|
58181
|
-
if (hasNonAriaReference) return false;
|
|
58182
|
-
if (!isNodeOfType(node, "Identifier") || !stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) return;
|
|
58183
|
-
if (findEnclosingFunction$1(node) !== componentFunction) return;
|
|
58184
|
-
const parent = node.parent;
|
|
58185
|
-
if (parent && (isNodeOfType(parent, "MemberExpression") && parent.property === node && !parent.computed || isNodeOfType(parent, "Property") && parent.key === node && !parent.computed)) return;
|
|
58186
|
-
let cursor = parent;
|
|
58187
|
-
while (cursor && cursor !== componentBody) {
|
|
58188
|
-
if (isFunctionLike$1(cursor)) return;
|
|
58189
|
-
if (isNodeOfType(cursor, "JSXSpreadAttribute")) {
|
|
58190
|
-
if (isNodeOfType(node, "Identifier") && classifyStaticSpreadObject(node) === "visible") hasNonAriaReference = true;
|
|
58191
|
-
return;
|
|
58192
|
-
}
|
|
58193
|
-
if (isNodeOfType(cursor, "JSXAttribute")) {
|
|
58194
|
-
if (isEventHandlerAttribute(cursor)) return;
|
|
58195
|
-
if (!isInsideIdOrAriaAttribute(node)) hasNonAriaReference = true;
|
|
58196
|
-
return;
|
|
58197
|
-
}
|
|
58198
|
-
if (isNodeOfType(cursor, "ReturnStatement")) {
|
|
58199
|
-
hasNonAriaReference = true;
|
|
58200
|
-
return;
|
|
58201
|
-
}
|
|
58202
|
-
cursor = cursor.parent;
|
|
58203
|
-
}
|
|
58204
|
-
});
|
|
58205
|
-
return hasNonAriaReference ? stateIdentifier.name : null;
|
|
58206
|
-
};
|
|
58207
|
-
const isExactViewportSubscriptionEffect = (context, effectCall, callback) => {
|
|
58208
|
-
if (!isReactApiCall(effectCall, USE_EFFECT_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return false;
|
|
58209
|
-
if (!isFunctionLike$1(callback) || callback.async || !isNodeOfType(callback.body, "BlockStatement")) return false;
|
|
58210
|
-
const statements = getCallbackStatements(callback);
|
|
58211
|
-
if (statements.length !== 4) return false;
|
|
58212
|
-
const handlerDeclaration = statements[0];
|
|
58213
|
-
if (!isNodeOfType(handlerDeclaration, "VariableDeclaration") || handlerDeclaration.kind !== "const" || handlerDeclaration.declarations?.length !== 1) return false;
|
|
58214
|
-
const handlerDeclarator = handlerDeclaration.declarations[0];
|
|
58215
|
-
if (!isNodeOfType(handlerDeclarator.id, "Identifier") || !isFunctionLike$1(handlerDeclarator.init)) return false;
|
|
58216
|
-
const handlerStatements = getCallbackStatements(handlerDeclarator.init);
|
|
58217
|
-
if (handlerStatements.length !== 1) return false;
|
|
58218
|
-
const handlerSetter = getDirectWindowWidthSetter(context, unwrapReturnExpression(handlerStatements[0]));
|
|
58219
|
-
const subscribedHandler = getResizeListenerHandler(context, statements[1], "addEventListener");
|
|
58220
|
-
const immediateSetter = getDirectWindowWidthSetter(context, statements[2]);
|
|
58221
|
-
const cleanupHandler = getCleanupResizeHandler(context, statements[3]);
|
|
58222
|
-
if (!handlerSetter || !subscribedHandler || !immediateSetter || !cleanupHandler) return false;
|
|
58223
|
-
const handlerSymbol = context.scopes.symbolFor(handlerDeclarator.id);
|
|
58224
|
-
if (!handlerSymbol || context.scopes.symbolFor(subscribedHandler) !== handlerSymbol || context.scopes.symbolFor(cleanupHandler) !== handlerSymbol) return false;
|
|
58225
|
-
if (!isNodeOfType(handlerSetter.callee, "Identifier") || !isNodeOfType(immediateSetter.callee, "Identifier") || context.scopes.symbolFor(handlerSetter.callee) !== context.scopes.symbolFor(immediateSetter.callee)) return false;
|
|
58226
|
-
const componentFunction = findEnclosingFunction$1(effectCall);
|
|
58227
|
-
if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return false;
|
|
58228
|
-
return findExactViewportState(context, componentFunction, immediateSetter) !== null;
|
|
58229
|
-
};
|
|
58230
56388
|
const renderingHydrationNoFlicker = defineRule({
|
|
58231
56389
|
id: "rendering-hydration-no-flicker",
|
|
58232
56390
|
title: "useEffect setState flashes on mount",
|
|
@@ -58239,14 +56397,7 @@ const renderingHydrationNoFlicker = defineRule({
|
|
|
58239
56397
|
if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
|
|
58240
56398
|
const callback = getEffectCallback(node);
|
|
58241
56399
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
58242
|
-
|
|
58243
|
-
context.report({
|
|
58244
|
-
node,
|
|
58245
|
-
message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
|
|
58246
|
-
});
|
|
58247
|
-
return;
|
|
58248
|
-
}
|
|
58249
|
-
const bodyStatements = getCallbackStatements(callback);
|
|
56400
|
+
const bodyStatements = (isNodeOfType(callback.body, "BlockStatement") ? callback.body.body ?? [] : [callback.body]).filter((statement) => !isNoOpStatement(statement));
|
|
58250
56401
|
if (bodyStatements.length !== 1) return;
|
|
58251
56402
|
const soleStatement = bodyStatements[0];
|
|
58252
56403
|
if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
|
|
@@ -58409,125 +56560,6 @@ const ASYNC_DATA_CALLEE_NAMES = new Set([
|
|
|
58409
56560
|
const RESOURCE_LOAD_EVENT_ATTRIBUTE_PATTERN = /^on(?:Load|Error|Abort|Progress|CanPlay|Stalled|Suspend|Waiting|Ended)/;
|
|
58410
56561
|
const JSX_EVENT_HANDLER_ATTRIBUTE_PATTERN = /^on[A-Z]/;
|
|
58411
56562
|
const REDUX_DISPATCH_HOOK_PATTERN = /^use\w*Dispatch$/;
|
|
58412
|
-
const FILE_READER_READ_METHOD_NAMES = new Set([
|
|
58413
|
-
"readAsArrayBuffer",
|
|
58414
|
-
"readAsBinaryString",
|
|
58415
|
-
"readAsDataURL",
|
|
58416
|
-
"readAsText"
|
|
58417
|
-
]);
|
|
58418
|
-
const isGlobalFileReaderConstruction = (expression, context) => {
|
|
58419
|
-
if (!expression) return false;
|
|
58420
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
58421
|
-
if (!isNodeOfType(unwrappedExpression, "NewExpression") || !isNodeOfType(unwrappedExpression.callee, "Identifier")) return false;
|
|
58422
|
-
return unwrappedExpression.callee.name === "FileReader" && context.scopes.isGlobalReference(unwrappedExpression.callee);
|
|
58423
|
-
};
|
|
58424
|
-
const getFileReaderOriginStartBefore = (readerSymbol, readCall, context) => {
|
|
58425
|
-
const readFunction = findEnclosingFunction$1(readCall);
|
|
58426
|
-
let latestValue = null;
|
|
58427
|
-
let latestStart = null;
|
|
58428
|
-
if (readerSymbol.initializer && findEnclosingFunction$1(readerSymbol.declarationNode) === readFunction && readerSymbol.declarationNode.range[0] < readCall.range[0]) {
|
|
58429
|
-
latestValue = readerSymbol.initializer;
|
|
58430
|
-
latestStart = readerSymbol.declarationNode.range[0];
|
|
58431
|
-
}
|
|
58432
|
-
for (const reference of readerSymbol.references) {
|
|
58433
|
-
if (reference.flag === "read" || reference.identifier.range[0] >= readCall.range[0] || latestStart !== null && reference.identifier.range[0] <= latestStart || findEnclosingFunction$1(reference.identifier) !== readFunction) continue;
|
|
58434
|
-
const assignment = reference.identifier.parent;
|
|
58435
|
-
if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== reference.identifier) continue;
|
|
58436
|
-
latestValue = assignment.right;
|
|
58437
|
-
latestStart = reference.identifier.range[0];
|
|
58438
|
-
}
|
|
58439
|
-
return isGlobalFileReaderConstruction(latestValue, context) ? latestStart : null;
|
|
58440
|
-
};
|
|
58441
|
-
const resolveLoadingCompletionFunction = (expression, context) => {
|
|
58442
|
-
const directFunction = resolveExactLocalFunction(expression, context.scopes);
|
|
58443
|
-
if (directFunction) return directFunction;
|
|
58444
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
58445
|
-
if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
|
|
58446
|
-
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
58447
|
-
const initializer = symbol ? getDirectUnreassignedInitializer(symbol) : null;
|
|
58448
|
-
if (!initializer || !isNodeOfType(initializer, "CallExpression") || !isReactApiCall(initializer, "useCallback", context.scopes)) return null;
|
|
58449
|
-
const callback = initializer.arguments?.[0];
|
|
58450
|
-
return callback && isFunctionLike$1(callback) ? callback : null;
|
|
58451
|
-
};
|
|
58452
|
-
const isSetterBooleanCall = (node, setterSymbol, value, context) => {
|
|
58453
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
58454
|
-
const callee = stripParenExpression(node.callee);
|
|
58455
|
-
const argument = node.arguments?.[0];
|
|
58456
|
-
const unwrappedArgument = argument ? stripParenExpression(argument) : null;
|
|
58457
|
-
return Boolean(isNodeOfType(callee, "Identifier") && context.scopes.symbolFor(callee) === setterSymbol && unwrappedArgument && isNodeOfType(unwrappedArgument, "Literal") && unwrappedArgument.value === value);
|
|
58458
|
-
};
|
|
58459
|
-
const functionClearsLoadingState = (functionNode, setterSymbol, context, visitedFunctions) => {
|
|
58460
|
-
if (visitedFunctions.has(functionNode) || !isFunctionLike$1(functionNode)) return false;
|
|
58461
|
-
visitedFunctions.add(functionNode);
|
|
58462
|
-
let didClearLoadingState = false;
|
|
58463
|
-
walkAst(functionNode.body, (child) => {
|
|
58464
|
-
if (didClearLoadingState) return false;
|
|
58465
|
-
if (child !== functionNode.body && isFunctionLike$1(child)) return false;
|
|
58466
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
58467
|
-
if (isSetterBooleanCall(child, setterSymbol, false, context)) {
|
|
58468
|
-
didClearLoadingState = true;
|
|
58469
|
-
return false;
|
|
58470
|
-
}
|
|
58471
|
-
const helperFunction = resolveLoadingCompletionFunction(child.callee, context);
|
|
58472
|
-
if (helperFunction && functionClearsLoadingState(helperFunction, setterSymbol, context, visitedFunctions)) {
|
|
58473
|
-
didClearLoadingState = true;
|
|
58474
|
-
return false;
|
|
58475
|
-
}
|
|
58476
|
-
});
|
|
58477
|
-
return didClearLoadingState;
|
|
58478
|
-
};
|
|
58479
|
-
const getLatestFileReaderCallbackBefore = (readCall, readerSymbol, propertyName, originStart, context) => {
|
|
58480
|
-
const readFunction = findEnclosingFunction$1(readCall);
|
|
58481
|
-
if (!readFunction || !isFunctionLike$1(readFunction)) return null;
|
|
58482
|
-
let callback = null;
|
|
58483
|
-
let callbackStart = originStart;
|
|
58484
|
-
walkAst(readFunction.body, (child) => {
|
|
58485
|
-
if (child !== readFunction.body && isFunctionLike$1(child)) return false;
|
|
58486
|
-
if (!isNodeOfType(child, "AssignmentExpression") || child.operator !== "=" || child.range[0] >= readCall.range[0] || child.range[0] <= callbackStart || !isNodeOfType(child.left, "MemberExpression") || getStaticPropertyName(child.left) !== propertyName) return;
|
|
58487
|
-
const receiver = stripParenExpression(child.left.object);
|
|
58488
|
-
if (!isNodeOfType(receiver, "Identifier") || context.scopes.symbolFor(receiver) !== readerSymbol) return;
|
|
58489
|
-
callback = child.right;
|
|
58490
|
-
callbackStart = child.range[0];
|
|
58491
|
-
});
|
|
58492
|
-
return callback;
|
|
58493
|
-
};
|
|
58494
|
-
const setterStartsLoadingBefore = (readCall, setterSymbol, context) => {
|
|
58495
|
-
const readFunction = findEnclosingFunction$1(readCall);
|
|
58496
|
-
if (!readFunction || !isFunctionLike$1(readFunction)) return false;
|
|
58497
|
-
let didStartLoading = false;
|
|
58498
|
-
walkAst(readFunction.body, (child) => {
|
|
58499
|
-
if (didStartLoading) return false;
|
|
58500
|
-
if (child !== readFunction.body && isFunctionLike$1(child)) return false;
|
|
58501
|
-
if (!isNodeOfType(child, "CallExpression") || child.range[0] >= readCall.range[0]) return;
|
|
58502
|
-
if (isSetterBooleanCall(child, setterSymbol, true, context)) {
|
|
58503
|
-
didStartLoading = true;
|
|
58504
|
-
return false;
|
|
58505
|
-
}
|
|
58506
|
-
});
|
|
58507
|
-
return didStartLoading;
|
|
58508
|
-
};
|
|
58509
|
-
const setterTracksFileReader = (functionBody, setterSymbol, context) => {
|
|
58510
|
-
let didFindFileReaderLifecycle = false;
|
|
58511
|
-
walkAst(functionBody, (child) => {
|
|
58512
|
-
if (didFindFileReaderLifecycle) return false;
|
|
58513
|
-
if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || !FILE_READER_READ_METHOD_NAMES.has(getStaticPropertyName(child.callee) ?? "")) return;
|
|
58514
|
-
const receiver = stripParenExpression(child.callee.object);
|
|
58515
|
-
if (!isNodeOfType(receiver, "Identifier")) return;
|
|
58516
|
-
const readerSymbol = context.scopes.symbolFor(receiver);
|
|
58517
|
-
if (!readerSymbol) return;
|
|
58518
|
-
const originStart = getFileReaderOriginStartBefore(readerSymbol, child, context);
|
|
58519
|
-
if (originStart === null || !setterStartsLoadingBefore(child, setterSymbol, context)) return;
|
|
58520
|
-
const loadCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onload", originStart, context);
|
|
58521
|
-
const errorCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onerror", originStart, context);
|
|
58522
|
-
const loadFunction = loadCallback ? resolveLoadingCompletionFunction(loadCallback, context) : null;
|
|
58523
|
-
const errorFunction = errorCallback ? resolveLoadingCompletionFunction(errorCallback, context) : null;
|
|
58524
|
-
if (loadFunction && errorFunction && functionClearsLoadingState(loadFunction, setterSymbol, context, /* @__PURE__ */ new Set()) && functionClearsLoadingState(errorFunction, setterSymbol, context, /* @__PURE__ */ new Set())) {
|
|
58525
|
-
didFindFileReaderLifecycle = true;
|
|
58526
|
-
return false;
|
|
58527
|
-
}
|
|
58528
|
-
});
|
|
58529
|
-
return didFindFileReaderLifecycle;
|
|
58530
|
-
};
|
|
58531
56563
|
const hasAsyncLoadingWork = (fnBody, setterName) => {
|
|
58532
56564
|
let found = false;
|
|
58533
56565
|
walkAst(fnBody, (child) => {
|
|
@@ -58701,8 +56733,6 @@ const renderingUsetransitionLoading = defineRule({
|
|
|
58701
56733
|
const fnBody = enclosingFunctionBody(node);
|
|
58702
56734
|
if (fnBody && hasAsyncLoadingWork(fnBody, setterName)) return;
|
|
58703
56735
|
if (fnBody && setterName) {
|
|
58704
|
-
const setterSymbol = isNodeOfType(secondBinding, "Identifier") ? context.scopes.symbolFor(secondBinding) : null;
|
|
58705
|
-
if (setterSymbol && setterTracksFileReader(fnBody, setterSymbol, context)) return;
|
|
58706
56736
|
if (setterEscapes(fnBody, setterName, node)) return;
|
|
58707
56737
|
if (setterCalledAlongsideAsyncSignal(fnBody, setterName)) return;
|
|
58708
56738
|
if (setterCalledInEventListenerHandler(fnBody, setterName)) return;
|
|
@@ -68199,6 +66229,14 @@ const isStateKey = (key) => {
|
|
|
68199
66229
|
if (isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value === "state";
|
|
68200
66230
|
return false;
|
|
68201
66231
|
};
|
|
66232
|
+
const findEnclosingClass = (node) => {
|
|
66233
|
+
let ancestor = node.parent;
|
|
66234
|
+
while (ancestor) {
|
|
66235
|
+
if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
|
|
66236
|
+
ancestor = ancestor.parent ?? null;
|
|
66237
|
+
}
|
|
66238
|
+
return null;
|
|
66239
|
+
};
|
|
68202
66240
|
const isInConstructor = (node) => {
|
|
68203
66241
|
let ancestor = node.parent;
|
|
68204
66242
|
while (ancestor) {
|
|
@@ -72910,17 +70948,6 @@ const reactDoctorRules = [
|
|
|
72910
70948
|
requires: [...new Set(["react", ...noRedundantShouldComponentUpdate.requires ?? []])]
|
|
72911
70949
|
}
|
|
72912
70950
|
},
|
|
72913
|
-
{
|
|
72914
|
-
key: "react-doctor/no-ref-callback-cleanup-before-react-19",
|
|
72915
|
-
id: "no-ref-callback-cleanup-before-react-19",
|
|
72916
|
-
source: "react-doctor",
|
|
72917
|
-
originallyExternal: false,
|
|
72918
|
-
rule: {
|
|
72919
|
-
...noRefCallbackCleanupBeforeReact19,
|
|
72920
|
-
framework: "global",
|
|
72921
|
-
category: "Bugs"
|
|
72922
|
-
}
|
|
72923
|
-
},
|
|
72924
70951
|
{
|
|
72925
70952
|
key: "react-doctor/no-ref-current-in-render",
|
|
72926
70953
|
id: "no-ref-current-in-render",
|