oxlint-plugin-react-doctor 0.7.9-dev.8835214 → 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 +260 -2285
- 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) => {
|
|
@@ -13883,7 +13760,6 @@ const walkInsideStatementBlocks = (node, visitor) => {
|
|
|
13883
13760
|
};
|
|
13884
13761
|
//#endregion
|
|
13885
13762
|
//#region src/plugin/rules/state-and-effects/utils/is-subscribe-like-call-expression.ts
|
|
13886
|
-
const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
|
|
13887
13763
|
const getSubscribeLikeMethodName = (node) => {
|
|
13888
13764
|
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
13889
13765
|
if (!isNodeOfType(node.callee, "MemberExpression")) return null;
|
|
@@ -13894,11 +13770,6 @@ const isSubscribeLikeCallExpression = (node) => {
|
|
|
13894
13770
|
const methodName = getSubscribeLikeMethodName(node);
|
|
13895
13771
|
return methodName !== null && SUBSCRIPTION_METHOD_NAMES.has(methodName);
|
|
13896
13772
|
};
|
|
13897
|
-
const getSubscribeOrObserveMethodName = (node) => {
|
|
13898
|
-
const methodName = getSubscribeLikeMethodName(node);
|
|
13899
|
-
return methodName !== null && (SUBSCRIPTION_METHOD_NAMES.has(methodName) || methodName === "observe") ? methodName : null;
|
|
13900
|
-
};
|
|
13901
|
-
const isSubscribeOrObserveCallExpression = (node) => getSubscribeOrObserveMethodName(node) !== null;
|
|
13902
13773
|
const isCleanupReturningSubscribeLikeCallExpression = (node) => {
|
|
13903
13774
|
const methodName = getSubscribeLikeMethodName(node);
|
|
13904
13775
|
if (methodName === null || !CLEANUP_RETURNING_SUBSCRIPTION_METHOD_NAMES.has(methodName)) return false;
|
|
@@ -13951,6 +13822,7 @@ const isNodeReachableWithinFunction = (node, context) => {
|
|
|
13951
13822
|
};
|
|
13952
13823
|
//#endregion
|
|
13953
13824
|
//#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
|
|
13825
|
+
const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
|
|
13954
13826
|
const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
|
|
13955
13827
|
const REPLAYABLE_ITERATOR_COLLECTION_CACHE = /* @__PURE__ */ new WeakMap();
|
|
13956
13828
|
const REACT_REF_EFFECT_ANALYSIS_CACHE = /* @__PURE__ */ new WeakMap();
|
|
@@ -13960,6 +13832,10 @@ const RESOURCE_NOUN_BY_KIND = {
|
|
|
13960
13832
|
socket: "connection"
|
|
13961
13833
|
};
|
|
13962
13834
|
const isSocketConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && SOCKET_CONSTRUCTOR_NAMES_REQUIRING_CLEANUP.has(node.callee.name);
|
|
13835
|
+
const isSubscribeOrObserveCall = (node) => {
|
|
13836
|
+
if (isSubscribeLikeCallExpression(node)) return true;
|
|
13837
|
+
return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === OBSERVER_REGISTRATION_METHOD_NAME;
|
|
13838
|
+
};
|
|
13963
13839
|
const resolveExpressionKey = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
13964
13840
|
if (!expression) return null;
|
|
13965
13841
|
const unwrappedExpression = stripParenExpression(expression);
|
|
@@ -14061,13 +13937,12 @@ const findSubscribeLikeUsages = (callback, context) => {
|
|
|
14061
13937
|
});
|
|
14062
13938
|
return;
|
|
14063
13939
|
}
|
|
14064
|
-
|
|
14065
|
-
if (subscribeOrObserveMethodName !== null) {
|
|
13940
|
+
if (isNodeOfType(child.callee, "MemberExpression") && isNodeOfType(child.callee.property, "Identifier") && (SUBSCRIPTION_METHOD_NAMES.has(child.callee.property.name) || child.callee.property.name === OBSERVER_REGISTRATION_METHOD_NAME)) {
|
|
14066
13941
|
const registrationDetails = getCallRegistrationDetails(child, context);
|
|
14067
13942
|
usages.push({
|
|
14068
13943
|
kind: "subscribe",
|
|
14069
13944
|
node: child,
|
|
14070
|
-
resourceName:
|
|
13945
|
+
resourceName: child.callee.property.name,
|
|
14071
13946
|
handleKey: findAssignedResourceKey(child, context),
|
|
14072
13947
|
...registrationDetails
|
|
14073
13948
|
});
|
|
@@ -14349,39 +14224,6 @@ const findContainingCollectionKey = (resourceNode, context) => {
|
|
|
14349
14224
|
}
|
|
14350
14225
|
return null;
|
|
14351
14226
|
};
|
|
14352
|
-
const findPushedResourceCollectionKey = (usage, context) => {
|
|
14353
|
-
if (!isNodeOfType(usage.node, "CallExpression")) return null;
|
|
14354
|
-
const registrationCallee = stripParenExpression(usage.node.callee);
|
|
14355
|
-
if (!isNodeOfType(registrationCallee, "MemberExpression") || registrationCallee.computed) return null;
|
|
14356
|
-
const resourceIdentifier = stripParenExpression(registrationCallee.object);
|
|
14357
|
-
if (!isPrivatePlainConstIdentifier(resourceIdentifier, context)) return null;
|
|
14358
|
-
const resourceSymbol = context.scopes.symbolFor(resourceIdentifier);
|
|
14359
|
-
if (!resourceSymbol) return null;
|
|
14360
|
-
const pushCalls = resourceSymbol.references.flatMap((reference) => {
|
|
14361
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
14362
|
-
const callNode = referenceRoot.parent;
|
|
14363
|
-
if (!isNodeOfType(callNode, "CallExpression") || !callNode.arguments?.some((argument) => argument === referenceRoot)) return [];
|
|
14364
|
-
const pushCallee = stripParenExpression(callNode.callee);
|
|
14365
|
-
return isNodeOfType(pushCallee, "MemberExpression") && !pushCallee.computed && isNodeOfType(pushCallee.object, "Identifier") && isNodeOfType(pushCallee.property, "Identifier") && pushCallee.property.name === "push" ? [callNode] : [];
|
|
14366
|
-
});
|
|
14367
|
-
if (pushCalls.length !== 1) return null;
|
|
14368
|
-
const pushCall = pushCalls[0];
|
|
14369
|
-
if (findEnclosingFunction$1(pushCall) !== findEnclosingFunction$1(usage.node) || !doMatchingNodesCoverEveryPathAfterUsage(usage.node, [pushCall], context)) return null;
|
|
14370
|
-
const pushCallee = stripParenExpression(pushCall.callee);
|
|
14371
|
-
if (!isNodeOfType(pushCallee, "MemberExpression") || !isNodeOfType(pushCallee.object, "Identifier") || !isPrivatePlainConstIdentifier(pushCallee.object, context)) return null;
|
|
14372
|
-
const collectionSymbol = context.scopes.symbolFor(pushCallee.object);
|
|
14373
|
-
const collectionInitializer = collectionSymbol?.initializer ? stripParenExpression(collectionSymbol.initializer) : null;
|
|
14374
|
-
if (!collectionSymbol || !isNodeOfType(collectionInitializer, "ArrayExpression") || (collectionInitializer.elements?.length ?? 0) !== 0 || findEnclosingFunction$1(collectionSymbol.declarationNode) !== findEnclosingFunction$1(usage.node)) return null;
|
|
14375
|
-
return collectionSymbol.references.every((reference) => {
|
|
14376
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
14377
|
-
const forOfStatement = referenceRoot.parent;
|
|
14378
|
-
if (isNodeOfType(forOfStatement, "ForOfStatement") && forOfStatement.right === referenceRoot && forOfStatement.await !== true) return true;
|
|
14379
|
-
const memberNode = referenceRoot.parent;
|
|
14380
|
-
const callNode = memberNode?.parent;
|
|
14381
|
-
if (!isNodeOfType(memberNode, "MemberExpression") || memberNode.object !== referenceRoot || memberNode.computed || !isNodeOfType(memberNode.property, "Identifier") || !isNodeOfType(callNode, "CallExpression") || callNode.callee !== memberNode) return false;
|
|
14382
|
-
return memberNode.property.name === "forEach" || memberNode.property.name === "push";
|
|
14383
|
-
}) ? resolveExpressionKey(pushCallee.object, context) : null;
|
|
14384
|
-
};
|
|
14385
14227
|
const isWithinAssignmentTarget = (identifier) => {
|
|
14386
14228
|
let currentNode = identifier;
|
|
14387
14229
|
let parentNode = currentNode.parent;
|
|
@@ -14440,14 +14282,6 @@ const isSynchronousIteratorCallback = (functionNode) => {
|
|
|
14440
14282
|
if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments?.[1] === functionNode;
|
|
14441
14283
|
return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments?.[0] === functionNode;
|
|
14442
14284
|
};
|
|
14443
|
-
const findEnclosingForEachCall = (node) => {
|
|
14444
|
-
const callbackNode = findEnclosingFunction$1(node);
|
|
14445
|
-
if (!callbackNode) return null;
|
|
14446
|
-
const callNode = callbackNode.parent;
|
|
14447
|
-
if (!isNodeOfType(callNode, "CallExpression") || callNode.arguments?.[0] !== callbackNode) return null;
|
|
14448
|
-
const callee = stripParenExpression(callNode.callee);
|
|
14449
|
-
return isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "forEach" ? callNode : null;
|
|
14450
|
-
};
|
|
14451
14285
|
const findDirectCallForReference = (identifier) => {
|
|
14452
14286
|
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
14453
14287
|
const callNode = expressionRoot.parent;
|
|
@@ -14462,7 +14296,7 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
|
|
|
14462
14296
|
const callNode = findDirectCallForReference(reference.identifier);
|
|
14463
14297
|
return callNode ? [callNode] : [];
|
|
14464
14298
|
});
|
|
14465
|
-
if (invocationCalls.length !== 1
|
|
14299
|
+
if (invocationCalls.length !== 1) return null;
|
|
14466
14300
|
const invocationCall = invocationCalls[0];
|
|
14467
14301
|
return findEnclosingFunction$1(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
|
|
14468
14302
|
};
|
|
@@ -14496,15 +14330,7 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
|
|
|
14496
14330
|
if (cleanupChild !== cleanupFunction.body && isFunctionLike$1(cleanupChild) && !isSynchronousIteratorCallback(cleanupChild)) return false;
|
|
14497
14331
|
const cleanupCall = isNodeOfType(cleanupChild, "ChainExpression") ? cleanupChild.expression : cleanupChild;
|
|
14498
14332
|
if (doesReleaseCallMatchUsage(cleanupChild, usage, context)) {
|
|
14499
|
-
const
|
|
14500
|
-
const cleanupCallee = isNodeOfType(cleanupCall, "CallExpression") ? stripParenExpression(cleanupCall.callee) : null;
|
|
14501
|
-
const cleanupReceiverForOfStatement = isNodeOfType(cleanupCallee, "MemberExpression") ? findForOfStatementForIteratorExpression(cleanupCallee.object, context) : null;
|
|
14502
|
-
const cleanupReceiverCollectionKey = cleanupReceiverForOfStatement ? resolveExpressionKey(cleanupReceiverForOfStatement.right, context) : isNodeOfType(cleanupCallee, "MemberExpression") ? resolveIteratorCollectionKey(cleanupCallee.object, context) : null;
|
|
14503
|
-
if (cleanupReceiverCollectionKey !== null && findEnclosingFunction$1(cleanupChild) !== cleanupFunction) {
|
|
14504
|
-
if (cleanupForEachCall && findPushedResourceCollectionKey(usage, context) === cleanupReceiverCollectionKey) matchingLoopOrHelperAnchors.push(cleanupForEachCall);
|
|
14505
|
-
return;
|
|
14506
|
-
}
|
|
14507
|
-
const cleanupForOfStatement = findForOfStatementForIteratorExpression(isNodeOfType(cleanupCall, "CallExpression") ? cleanupCall.arguments?.[0] : null, context) ?? cleanupReceiverForOfStatement;
|
|
14333
|
+
const cleanupForOfStatement = findForOfStatementForIteratorExpression(isNodeOfType(cleanupCall, "CallExpression") ? cleanupCall.arguments?.[0] : null, context);
|
|
14508
14334
|
if (!cleanupForOfStatement) {
|
|
14509
14335
|
didCleanupFunctionMatch = true;
|
|
14510
14336
|
return false;
|
|
@@ -14548,28 +14374,28 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
|
|
|
14548
14374
|
});
|
|
14549
14375
|
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
|
|
14550
14376
|
};
|
|
14551
|
-
const
|
|
14552
|
-
if (
|
|
14553
|
-
const
|
|
14554
|
-
|
|
14555
|
-
|
|
14556
|
-
|
|
14557
|
-
|
|
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);
|
|
14558
14388
|
};
|
|
14559
|
-
return resolveExpressionKey(unwrappedTest.left, context) === expressionKey && isNullishOperand(unwrappedTest.right) || resolveExpressionKey(unwrappedTest.right, context) === expressionKey && isNullishOperand(unwrappedTest.left);
|
|
14560
|
-
};
|
|
14561
|
-
const findLiveExpressionGuardForRelease = (releaseCall, owner, expressionKey, context) => {
|
|
14562
14389
|
let ancestor = releaseCall.parent;
|
|
14563
14390
|
while (ancestor && ancestor !== owner) {
|
|
14564
14391
|
if (isNodeOfType(ancestor, "IfStatement")) {
|
|
14565
|
-
if (ancestor.alternate !== null || !
|
|
14392
|
+
if (ancestor.alternate !== null || !doesTestRequireLiveHandle(ancestor.test) || !doMatchingNodesCoverEveryPathAfterUsage(ancestor.consequent, [releaseCall], context)) return null;
|
|
14566
14393
|
return ancestor;
|
|
14567
14394
|
}
|
|
14568
14395
|
ancestor = ancestor.parent;
|
|
14569
14396
|
}
|
|
14570
14397
|
return null;
|
|
14571
14398
|
};
|
|
14572
|
-
const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => usage.handleKey === null ? null : findLiveExpressionGuardForRelease(releaseCall, owner, usage.handleKey, context);
|
|
14573
14399
|
const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
|
|
14574
14400
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
|
|
14575
14401
|
const functionCfg = context.cfg.cfgFor(callback);
|
|
@@ -14757,108 +14583,7 @@ const hasPotentialInterruptionAfterGuard = (callback, guardState, usageNode, con
|
|
|
14757
14583
|
});
|
|
14758
14584
|
return hasPotentialInterruption;
|
|
14759
14585
|
};
|
|
14760
|
-
const getNumericReactRefCurrentKey = (expression, context) => {
|
|
14761
|
-
const refSymbol = resolveReactRefSymbol(stripParenExpression(expression), context.scopes);
|
|
14762
|
-
const initializer = refSymbol?.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
14763
|
-
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
14764
|
-
const initialValue = initializer.arguments?.[0] ? stripParenExpression(initializer.arguments[0]) : null;
|
|
14765
|
-
if (!isNodeOfType(initialValue, "Literal") || typeof initialValue.value !== "number") return null;
|
|
14766
|
-
return resolveExpressionKey(expression, context);
|
|
14767
|
-
};
|
|
14768
|
-
const getBlockingGenerationKey = (expression, context) => {
|
|
14769
|
-
const test = stripParenExpression(expression);
|
|
14770
|
-
if (isNodeOfType(test, "LogicalExpression") && test.operator === "||") return getBlockingGenerationKey(test.left, context) ?? getBlockingGenerationKey(test.right, context);
|
|
14771
|
-
if (!isNodeOfType(test, "BinaryExpression") || test.operator !== "!==" && test.operator !== "!=") return null;
|
|
14772
|
-
const leftKey = getNumericReactRefCurrentKey(test.left, context);
|
|
14773
|
-
const rightKey = getNumericReactRefCurrentKey(test.right, context);
|
|
14774
|
-
const snapshotExpression = leftKey ? stripParenExpression(test.right) : stripParenExpression(test.left);
|
|
14775
|
-
const key = leftKey ?? rightKey;
|
|
14776
|
-
return key && isNodeOfType(snapshotExpression, "Identifier") ? key : null;
|
|
14777
|
-
};
|
|
14778
|
-
const findGenerationGuardKeyForDeferredUsage = (usageFunction, usageNode, context) => {
|
|
14779
|
-
if (!isFunctionLike$1(usageFunction)) return null;
|
|
14780
|
-
let generationKey = null;
|
|
14781
|
-
walkAst(usageFunction.body, (child) => {
|
|
14782
|
-
if (generationKey) return false;
|
|
14783
|
-
if (child !== usageFunction.body && isFunctionLike$1(child)) return false;
|
|
14784
|
-
if (!isNodeOfType(child, "IfStatement") || child.alternate) return;
|
|
14785
|
-
const key = getBlockingGenerationKey(child.test, context);
|
|
14786
|
-
if (!key || canNodeReachLaterNodeWithinFunction(child.consequent, usageNode, usageFunction, context) || !doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], usageFunction, context)) return;
|
|
14787
|
-
generationKey = key;
|
|
14788
|
-
});
|
|
14789
|
-
return generationKey;
|
|
14790
|
-
};
|
|
14791
|
-
const isGenerationAdvance = (node, generationKey, context) => {
|
|
14792
|
-
if (isNodeOfType(node, "UpdateExpression") && resolveExpressionKey(node.argument, context) === generationKey) return true;
|
|
14793
|
-
if (!isNodeOfType(node, "AssignmentExpression") || resolveExpressionKey(node.left, context) !== generationKey || node.operator !== "+=" && node.operator !== "-=") return false;
|
|
14794
|
-
const amount = stripParenExpression(node.right);
|
|
14795
|
-
return isNodeOfType(amount, "Literal") && typeof amount.value === "number" && amount.value !== 0;
|
|
14796
|
-
};
|
|
14797
|
-
const functionAdvancesGeneration = (owner, generationKey, context) => {
|
|
14798
|
-
if (!isFunctionLike$1(owner)) return false;
|
|
14799
|
-
let didAdvanceGeneration = false;
|
|
14800
|
-
walkAst(owner.body, (child) => {
|
|
14801
|
-
if (didAdvanceGeneration) return false;
|
|
14802
|
-
if (child !== owner.body && isFunctionLike$1(child)) return false;
|
|
14803
|
-
if (isGenerationAdvance(child, generationKey, context)) {
|
|
14804
|
-
didAdvanceGeneration = true;
|
|
14805
|
-
return false;
|
|
14806
|
-
}
|
|
14807
|
-
});
|
|
14808
|
-
return didAdvanceGeneration;
|
|
14809
|
-
};
|
|
14810
|
-
const cleanupReturnsReleaseUsage = (cleanupReturns, usage, context) => cleanupReturns.length > 0 && cleanupReturns.every((cleanupReturn) => {
|
|
14811
|
-
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
|
|
14812
|
-
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
14813
|
-
return Boolean(cleanupFunction && isFunctionLike$1(cleanupFunction) && doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context));
|
|
14814
|
-
});
|
|
14815
|
-
const getOwnedFunctionReference = (reference, usageFunction, usageNode, callback, cleanupReturns, context) => {
|
|
14816
|
-
const directCall = findDirectCallForReference(reference);
|
|
14817
|
-
if (directCall) {
|
|
14818
|
-
const referenceOwner = findEnclosingFunction$1(directCall);
|
|
14819
|
-
if (referenceOwner && referenceOwner !== usageFunction && collectSynchronouslyEffectInvokedFunctions(callback).has(referenceOwner)) return { generationKey: null };
|
|
14820
|
-
const generationKey = referenceOwner ? findGenerationGuardKeyForDeferredUsage(referenceOwner, directCall, context) : null;
|
|
14821
|
-
return generationKey ? { generationKey } : null;
|
|
14822
|
-
}
|
|
14823
|
-
const referenceRoot = findTransparentExpressionRoot(reference);
|
|
14824
|
-
const schedulerCall = referenceRoot.parent;
|
|
14825
|
-
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;
|
|
14826
|
-
const schedulerUsage = {
|
|
14827
|
-
kind: "timer",
|
|
14828
|
-
node: schedulerCall,
|
|
14829
|
-
resourceName: schedulerCall.callee.name,
|
|
14830
|
-
handleKey: findAssignedResourceKey(schedulerCall, context),
|
|
14831
|
-
receiverKey: null,
|
|
14832
|
-
registrationVerbName: schedulerCall.callee.name,
|
|
14833
|
-
eventKey: null,
|
|
14834
|
-
handlerKey: null
|
|
14835
|
-
};
|
|
14836
|
-
const generationKey = findGenerationGuardKeyForDeferredUsage(usageFunction, usageNode, context);
|
|
14837
|
-
return schedulerUsage.handleKey !== null && cleanupReturnsReleaseUsage(cleanupReturns, schedulerUsage, context) && generationKey ? { generationKey } : null;
|
|
14838
|
-
};
|
|
14839
|
-
const hasGuardedRefOwnedNestedCleanup = (callback, usage, cleanupReturns, context) => {
|
|
14840
|
-
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
14841
|
-
const usageExpression = findTransparentExpressionRoot(usage.node);
|
|
14842
|
-
const usageAssignment = usageExpression.parent;
|
|
14843
|
-
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;
|
|
14844
|
-
const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
|
|
14845
|
-
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
|
|
14846
|
-
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
14847
|
-
return cleanupFunction && isFunctionLike$1(cleanupFunction) ? [cleanupFunction] : [];
|
|
14848
|
-
});
|
|
14849
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(usageFunction);
|
|
14850
|
-
const functionSymbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
|
|
14851
|
-
if (!functionSymbol || functionSymbol.references.length === 0) return false;
|
|
14852
|
-
const ownedReferences = functionSymbol.references.map((reference) => getOwnedFunctionReference(reference.identifier, usageFunction, usage.node, callback, cleanupReturns, context));
|
|
14853
|
-
if (ownedReferences.some((reference) => reference === null)) return false;
|
|
14854
|
-
const generationKeys = new Set(ownedReferences.flatMap((reference) => reference?.generationKey ? [reference.generationKey] : []));
|
|
14855
|
-
if (generationKeys.size !== 1) return false;
|
|
14856
|
-
const generationKey = generationKeys.values().next().value;
|
|
14857
|
-
if (typeof generationKey !== "string") return false;
|
|
14858
|
-
return [...collectSynchronouslyEffectInvokedFunctions(callback), ...cleanupFunctions].some((owner) => functionAdvancesGeneration(owner, generationKey, context));
|
|
14859
|
-
};
|
|
14860
14586
|
const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
|
|
14861
|
-
if (hasGuardedRefOwnedNestedCleanup(callback, usage, cleanupReturns, context)) return true;
|
|
14862
14587
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
14863
14588
|
const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
|
|
14864
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;
|
|
@@ -15026,85 +14751,6 @@ const getReleaseVerbName = (node) => {
|
|
|
15026
14751
|
}
|
|
15027
14752
|
return null;
|
|
15028
14753
|
};
|
|
15029
|
-
const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) => {
|
|
15030
|
-
const releaseFunction = findEnclosingFunction$1(releaseReceiver);
|
|
15031
|
-
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15032
|
-
if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction) || !resolveReactRefCurrentOriginSymbol(releaseReceiver, context.scopes)) return false;
|
|
15033
|
-
const controllerKey = getListenerAbortControllerKey(usage, context);
|
|
15034
|
-
const refCurrentKey = resolveExpressionKey(releaseReceiver, context);
|
|
15035
|
-
if (controllerKey === null || refCurrentKey === null) return false;
|
|
15036
|
-
const usageFunctionBody = usageFunction.body;
|
|
15037
|
-
const previousAbortCalls = [];
|
|
15038
|
-
const ownershipAssignments = [];
|
|
15039
|
-
walkAst(usageFunctionBody, (child) => {
|
|
15040
|
-
if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
|
|
15041
|
-
if (isNodeOfType(child, "AssignmentExpression") && resolveExpressionKey(child.left, context) === refCurrentKey && resolveExpressionKey(child.right, context) === controllerKey) {
|
|
15042
|
-
ownershipAssignments.push(child);
|
|
15043
|
-
return;
|
|
15044
|
-
}
|
|
15045
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
15046
|
-
const childCallee = isNodeOfType(child.callee, "ChainExpression") ? child.callee.expression : stripParenExpression(child.callee);
|
|
15047
|
-
if (isNodeOfType(childCallee, "MemberExpression") && !childCallee.computed && isNodeOfType(childCallee.property, "Identifier") && childCallee.property.name === "abort" && resolveExpressionKey(childCallee.object, context) === refCurrentKey) previousAbortCalls.push(child);
|
|
15048
|
-
});
|
|
15049
|
-
const safeOwnershipAssignments = ownershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, previousAbortCalls, usageFunction, context));
|
|
15050
|
-
return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
|
|
15051
|
-
};
|
|
15052
|
-
const isJsxRefAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && node.name.name === "ref";
|
|
15053
|
-
const isFunctionForwardedToReactRef = (functionNode, context) => {
|
|
15054
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
15055
|
-
if (!bindingIdentifier) return false;
|
|
15056
|
-
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
15057
|
-
if (!symbol) return false;
|
|
15058
|
-
return symbol.references.some((reference) => {
|
|
15059
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
15060
|
-
const expressionContainer = referenceRoot.parent;
|
|
15061
|
-
return Boolean(isNodeOfType(expressionContainer, "JSXExpressionContainer") && expressionContainer.expression === referenceRoot && isJsxRefAttribute(expressionContainer.parent));
|
|
15062
|
-
});
|
|
15063
|
-
};
|
|
15064
|
-
const isFunctionReturnedFromReactHook = (functionNode, context, requireRefPropertyName) => {
|
|
15065
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
15066
|
-
if (!bindingIdentifier) return false;
|
|
15067
|
-
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
15068
|
-
if (!symbol) return false;
|
|
15069
|
-
return symbol.references.some((reference) => {
|
|
15070
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
15071
|
-
const property = referenceRoot.parent;
|
|
15072
|
-
const propertyName = isNodeOfType(property, "Property") ? getStaticPropertyKeyName(property) : null;
|
|
15073
|
-
if (!isNodeOfType(property, "Property") || property.value !== referenceRoot || !isNodeOfType(property.parent, "ObjectExpression") || requireRefPropertyName && propertyName !== "ref" && !propertyName?.endsWith("Ref")) return false;
|
|
15074
|
-
const returnedObject = findTransparentExpressionRoot(property.parent);
|
|
15075
|
-
const returnStatement = returnedObject.parent;
|
|
15076
|
-
if (!isNodeOfType(returnStatement, "ReturnStatement") || returnStatement.argument !== returnedObject) return false;
|
|
15077
|
-
const ownerFunction = findEnclosingFunction$1(returnStatement);
|
|
15078
|
-
return Boolean(ownerFunction && isReactHookName(getFunctionBindingIdentifier$1(ownerFunction)?.name ?? ""));
|
|
15079
|
-
});
|
|
15080
|
-
};
|
|
15081
|
-
const isFunctionUsedAsReactRef = (functionNode, context) => isFunctionForwardedToReactRef(functionNode, context) || isFunctionReturnedFromReactHook(functionNode, context, true);
|
|
15082
|
-
const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
|
|
15083
|
-
if (!isNodeOfType(usage.node, "CallExpression")) return false;
|
|
15084
|
-
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15085
|
-
if (!usageFunction || !isFunctionLike$1(usageFunction) || usageFunction !== findEnclosingFunction$1(releaseCall) || !isFunctionUsedAsReactRef(usageFunction, context)) return false;
|
|
15086
|
-
const registrationCallee = stripParenExpression(usage.node.callee);
|
|
15087
|
-
const releaseCallee = stripParenExpression(releaseCall.callee);
|
|
15088
|
-
const releaseRefSymbol = isNodeOfType(releaseCallee, "MemberExpression") ? resolveReactRefCurrentOriginSymbol(releaseCallee.object, context.scopes) : null;
|
|
15089
|
-
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;
|
|
15090
|
-
const registrationReceiverKey = resolveExpressionKey(stripParenExpression(registrationCallee.object), context);
|
|
15091
|
-
const nodeParameterKey = resolveExpressionKey(usageFunction.params?.[0], context);
|
|
15092
|
-
const releaseReceiverKey = resolveExpressionKey(releaseCallee.object, context);
|
|
15093
|
-
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;
|
|
15094
|
-
const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15095
|
-
const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15096
|
-
if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
|
|
15097
|
-
const releaseStart = getRangeStart(releaseCall);
|
|
15098
|
-
const matchingOwnershipAssignments = [];
|
|
15099
|
-
const usageFunctionBody = usageFunction.body;
|
|
15100
|
-
walkAst(usageFunctionBody, (child) => {
|
|
15101
|
-
if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
|
|
15102
|
-
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);
|
|
15103
|
-
});
|
|
15104
|
-
const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
|
|
15105
|
-
const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
|
|
15106
|
-
return doMatchingNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
|
|
15107
|
-
};
|
|
15108
14754
|
const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
15109
14755
|
const callNode = isNodeOfType(node, "ChainExpression") ? node.expression : node;
|
|
15110
14756
|
if (!isNodeOfType(callNode, "CallExpression")) return false;
|
|
@@ -15121,21 +14767,13 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15121
14767
|
if (!releaseVerbName) return false;
|
|
15122
14768
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
|
|
15123
14769
|
const releaseReceiverKey = resolveExpressionKey(callee.object, context);
|
|
15124
|
-
const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
|
|
15125
|
-
const pairedReleaseVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
|
|
15126
|
-
const pushedResourceCollectionKey = findPushedResourceCollectionKey(usage, context);
|
|
15127
|
-
const releaseReceiverForOfStatement = findForOfStatementForIteratorExpression(callee.object, context);
|
|
15128
|
-
const releaseReceiverCollectionKey = releaseReceiverForOfStatement ? resolveExpressionKey(releaseReceiverForOfStatement.right, context) : resolveIteratorCollectionKey(callee.object, context);
|
|
15129
|
-
if (pairedReleaseVerbNames && matchesPairedReleaseVerb(releaseVerbName, pairedReleaseVerbNames) && pushedResourceCollectionKey !== null && pushedResourceCollectionKey === releaseReceiverCollectionKey && (releaseVerbName !== "unobserve" || usage.eventKey !== null && releaseEventKey === usage.eventKey)) return true;
|
|
15130
|
-
if (isReactRefListenerReplacementRelease(callNode, usage, context)) return true;
|
|
15131
14770
|
if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
|
|
15132
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;
|
|
15133
14772
|
if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
|
|
15134
|
-
if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
|
|
15135
14773
|
if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) return false;
|
|
15136
|
-
if (usage.registrationVerbName === "subscribe" && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub") && usage.handleKey !== null && resolveExpressionKey(callNode.arguments?.[0], context) === usage.handleKey) return true;
|
|
15137
14774
|
const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
|
|
15138
14775
|
if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
|
|
14776
|
+
const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
|
|
15139
14777
|
const usageEventArgument = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[0] : null;
|
|
15140
14778
|
const releaseEventArgument = callNode.arguments?.[0];
|
|
15141
14779
|
if (isAssignmentFormForOfIteratorReference(usageEventArgument, context) || isAssignmentFormForOfIteratorReference(releaseEventArgument, context)) return false;
|
|
@@ -15165,12 +14803,9 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15165
14803
|
return isNodeOfType(handlerArgument, "Literal") && handlerArgument.value === null;
|
|
15166
14804
|
}
|
|
15167
14805
|
if (releaseVerbName === "removeEventListener" || releaseVerbName === "removeListener" || releaseVerbName === "off") {
|
|
15168
|
-
const
|
|
15169
|
-
const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
|
|
14806
|
+
const releaseHandler = callNode.arguments?.[1];
|
|
15170
14807
|
if (!releaseHandler) return releaseVerbName === "off";
|
|
15171
|
-
|
|
15172
|
-
const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignature ? 0 : 1] : null;
|
|
15173
|
-
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;
|
|
15174
14809
|
}
|
|
15175
14810
|
if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
|
|
15176
14811
|
return true;
|
|
@@ -15183,7 +14818,8 @@ const isReturnedEffectCleanupFunction = (functionNode) => {
|
|
|
15183
14818
|
currentNode = parentNode;
|
|
15184
14819
|
parentNode = currentNode.parent;
|
|
15185
14820
|
}
|
|
15186
|
-
|
|
14821
|
+
if (!isNodeOfType(parentNode, "ReturnStatement") || parentNode.argument !== currentNode) return false;
|
|
14822
|
+
const effectCallback = findEnclosingFunction$1(parentNode);
|
|
15187
14823
|
const effectCall = effectCallback?.parent;
|
|
15188
14824
|
return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
|
|
15189
14825
|
};
|
|
@@ -15195,165 +14831,13 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
|
|
|
15195
14831
|
if (!symbol) return false;
|
|
15196
14832
|
return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
|
|
15197
14833
|
};
|
|
15198
|
-
const findRetainedDisposerStorages = (disposerFunction, usage, context) => {
|
|
15199
|
-
if (!isFunctionLike$1(disposerFunction) || disposerFunction.async || disposerFunction.generator) return [];
|
|
15200
|
-
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15201
|
-
if (!usageFunction || !isFunctionLike$1(usageFunction)) return [];
|
|
15202
|
-
const assignments = /* @__PURE__ */ new Map();
|
|
15203
|
-
const collectAssignment = (expression) => {
|
|
15204
|
-
const expressionRoot = findTransparentExpressionRoot(expression);
|
|
15205
|
-
const assignment = expressionRoot.parent;
|
|
15206
|
-
if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.right !== expressionRoot) return;
|
|
15207
|
-
const refSymbol = resolveReactRefSymbol(stripParenExpression(assignment.left), context.scopes);
|
|
15208
|
-
const refCurrentKey = resolveExpressionKey(assignment.left, context);
|
|
15209
|
-
const retainedFunction = findEnclosingFunction$1(assignment);
|
|
15210
|
-
const assignmentStart = getRangeStart(assignment);
|
|
15211
|
-
if (!refSymbol || !refCurrentKey || !retainedFunction || retainedFunction !== usageFunction || assignmentStart === null) return;
|
|
15212
|
-
assignments.set(assignmentStart, {
|
|
15213
|
-
assignmentNode: assignment,
|
|
15214
|
-
refCurrentKey,
|
|
15215
|
-
retainedFunction
|
|
15216
|
-
});
|
|
15217
|
-
};
|
|
15218
|
-
collectAssignment(disposerFunction);
|
|
15219
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(disposerFunction);
|
|
15220
|
-
const symbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
|
|
15221
|
-
for (const reference of symbol?.references ?? []) collectAssignment(reference.identifier);
|
|
15222
|
-
walkAst(usageFunction.body, (child) => {
|
|
15223
|
-
if (child !== usageFunction.body && isFunctionLike$1(child)) return false;
|
|
15224
|
-
if (isNodeOfType(child, "AssignmentExpression") && resolveStableValue(child.right, context) === disposerFunction) collectAssignment(child.right);
|
|
15225
|
-
});
|
|
15226
|
-
return [...assignments.values()];
|
|
15227
|
-
};
|
|
15228
|
-
const isRetainedDisposerStorageEstablished = (storage, usage, context) => doMatchingNodesCoverEveryPathBeforeUsage(usage.node, [storage.assignmentNode], storage.retainedFunction, context) || doMatchingNodesCoverEveryPathAfterUsage(usage.node, [storage.assignmentNode], context);
|
|
15229
|
-
const hasUnsafeRetainedDisposerOverwrite = (storage, usage, context) => {
|
|
15230
|
-
let hasUnsafeOverwrite = false;
|
|
15231
|
-
walkAst(storage.retainedFunction.body, (child) => {
|
|
15232
|
-
if (hasUnsafeOverwrite) return false;
|
|
15233
|
-
if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
|
|
15234
|
-
if (!isNodeOfType(child, "AssignmentExpression") || child === storage.assignmentNode || resolveExpressionKey(child.left, context) !== storage.refCurrentKey || !canNodeReachLaterNodeWithinFunction(usage.node, child, storage.retainedFunction, context)) return;
|
|
15235
|
-
const storedValue = resolveStableValue(child.right, context);
|
|
15236
|
-
if (!storedValue || !isFunctionLike$1(storedValue) || !doesCleanupFunctionReleaseUsage(storedValue, usage, context)) {
|
|
15237
|
-
hasUnsafeOverwrite = true;
|
|
15238
|
-
return false;
|
|
15239
|
-
}
|
|
15240
|
-
});
|
|
15241
|
-
return hasUnsafeOverwrite;
|
|
15242
|
-
};
|
|
15243
|
-
const hasEffectCleanupInvocation = (storage, usage, context) => {
|
|
15244
|
-
const componentFunction = findEnclosingFunction$1(storage.retainedFunction);
|
|
15245
|
-
if (!componentFunction || !isFunctionLike$1(componentFunction)) return false;
|
|
15246
|
-
const cleanupFunctionInvokesRef = (cleanupFunction) => {
|
|
15247
|
-
if (!isFunctionLike$1(cleanupFunction)) return false;
|
|
15248
|
-
let didFindCleanupCall = false;
|
|
15249
|
-
walkAst(cleanupFunction.body, (child) => {
|
|
15250
|
-
if (didFindCleanupCall) return false;
|
|
15251
|
-
if (child !== cleanupFunction.body && isFunctionLike$1(child)) return false;
|
|
15252
|
-
if (isNodeOfType(child, "CallExpression") && resolveExpressionKey(child.callee, context) === storage.refCurrentKey) {
|
|
15253
|
-
const callRoot = findTransparentExpressionRoot(child);
|
|
15254
|
-
const callStatement = callRoot.parent;
|
|
15255
|
-
const isDirectBlockStatement = isNodeOfType(cleanupFunction.body, "BlockStatement") && isNodeOfType(callStatement, "ExpressionStatement") && callStatement.parent === cleanupFunction.body;
|
|
15256
|
-
const isConciseBody = cleanupFunction.body === callRoot;
|
|
15257
|
-
if ((isDirectBlockStatement || isConciseBody) && !hasUnprovenReturnBeforeRefOwnedRelease(cleanupFunction, child, storage.refCurrentKey, context)) {
|
|
15258
|
-
didFindCleanupCall = true;
|
|
15259
|
-
return false;
|
|
15260
|
-
}
|
|
15261
|
-
}
|
|
15262
|
-
});
|
|
15263
|
-
return didFindCleanupCall;
|
|
15264
|
-
};
|
|
15265
|
-
const effectReturnsCleanup = (effectCallback) => {
|
|
15266
|
-
if (!isFunctionLike$1(effectCallback)) return false;
|
|
15267
|
-
if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
|
|
15268
|
-
const cleanupFunction = resolveRefOwnedCleanupFunction(effectCallback.body, context);
|
|
15269
|
-
return Boolean(cleanupFunction && cleanupFunctionInvokesRef(cleanupFunction));
|
|
15270
|
-
}
|
|
15271
|
-
const matchingReturns = [];
|
|
15272
|
-
walkInsideStatementBlocks(effectCallback.body, (child) => {
|
|
15273
|
-
if (!isNodeOfType(child, "ReturnStatement") || !child.argument) return;
|
|
15274
|
-
const cleanupFunction = resolveRefOwnedCleanupFunction(child.argument, context);
|
|
15275
|
-
if (!cleanupFunction || !cleanupFunctionInvokesRef(cleanupFunction)) return;
|
|
15276
|
-
matchingReturns.push(child);
|
|
15277
|
-
});
|
|
15278
|
-
return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
|
|
15279
|
-
};
|
|
15280
|
-
let didFindInvocation = false;
|
|
15281
|
-
walkAst(componentFunction.body, (child) => {
|
|
15282
|
-
if (didFindInvocation) return false;
|
|
15283
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
|
|
15284
|
-
const effectCallback = getEffectCallback(child);
|
|
15285
|
-
if (effectCallback && effectReturnsCleanup(effectCallback)) {
|
|
15286
|
-
didFindInvocation = true;
|
|
15287
|
-
return false;
|
|
15288
|
-
}
|
|
15289
|
-
});
|
|
15290
|
-
return didFindInvocation;
|
|
15291
|
-
};
|
|
15292
|
-
const hasCallbackRefReplacementInvocation = (storage, usage, context) => {
|
|
15293
|
-
const isReturnedCallbackRefShape = () => {
|
|
15294
|
-
if (!isFunctionLike$1(storage.retainedFunction)) return false;
|
|
15295
|
-
const callbackCall = findTransparentExpressionRoot(storage.retainedFunction).parent;
|
|
15296
|
-
if (!isNodeOfType(callbackCall, "CallExpression") || !isReactApiCall(callbackCall, "useCallback", context.scopes)) return false;
|
|
15297
|
-
const nodeParameter = storage.retainedFunction.params?.[0];
|
|
15298
|
-
const nodeParameterKey = resolveExpressionKey(nodeParameter, context);
|
|
15299
|
-
if (!nodeParameterKey || usage.receiverKey !== nodeParameterKey) return false;
|
|
15300
|
-
if (!isFunctionReturnedFromReactHook(storage.retainedFunction, context, false)) return false;
|
|
15301
|
-
const usageStart = getRangeStart(usage.node);
|
|
15302
|
-
if (usageStart === null) return false;
|
|
15303
|
-
let hasNullExit = false;
|
|
15304
|
-
walkAst(storage.retainedFunction.body, (child) => {
|
|
15305
|
-
if (hasNullExit) return false;
|
|
15306
|
-
if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
|
|
15307
|
-
if (!isNodeOfType(child, "IfStatement") || (getRangeStart(child) ?? usageStart) >= usageStart) return;
|
|
15308
|
-
const test = stripParenExpression(child.test);
|
|
15309
|
-
if (!isNodeOfType(test, "UnaryExpression") || test.operator !== "!" || resolveExpressionKey(test.argument, context) !== nodeParameterKey) return;
|
|
15310
|
-
const consequent = child.consequent;
|
|
15311
|
-
hasNullExit = isNodeOfType(consequent, "ReturnStatement") || isNodeOfType(consequent, "BlockStatement") && consequent.body.some((statement) => isNodeOfType(statement, "ReturnStatement"));
|
|
15312
|
-
if (hasNullExit) return false;
|
|
15313
|
-
});
|
|
15314
|
-
return hasNullExit;
|
|
15315
|
-
};
|
|
15316
|
-
if (!isFunctionForwardedToReactRef(storage.retainedFunction, context) && !isReturnedCallbackRefShape()) return false;
|
|
15317
|
-
const cleanupCalls = [];
|
|
15318
|
-
walkAst(storage.retainedFunction.body, (child) => {
|
|
15319
|
-
if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
|
|
15320
|
-
if (isNodeOfType(child, "CallExpression") && resolveExpressionKey(child.callee, context) === storage.refCurrentKey) cleanupCalls.push(child);
|
|
15321
|
-
});
|
|
15322
|
-
return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, cleanupCalls, storage.retainedFunction, context);
|
|
15323
|
-
};
|
|
15324
|
-
const isRetainedDisposerRefRelease = (releaseNode, usage, context) => {
|
|
15325
|
-
const disposerFunction = findEnclosingFunction$1(releaseNode);
|
|
15326
|
-
if (!disposerFunction) return false;
|
|
15327
|
-
return findRetainedDisposerStorages(disposerFunction, usage, context).some((storage) => isRetainedDisposerStorageEstablished(storage, usage, context) && !hasUnsafeRetainedDisposerOverwrite(storage, usage, context) && (hasEffectCleanupInvocation(storage, usage, context) || hasCallbackRefReplacementInvocation(storage, usage, context)));
|
|
15328
|
-
};
|
|
15329
|
-
const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
|
|
15330
|
-
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;
|
|
15331
|
-
const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15332
|
-
const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
|
|
15333
|
-
if (!isNodeOfType(releaseCall, "CallExpression")) return false;
|
|
15334
|
-
const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15335
|
-
if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
|
|
15336
|
-
const ownerFunction = findEnclosingFunction$1(releaseFunction);
|
|
15337
|
-
if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
|
|
15338
|
-
const triggerRegistrations = [];
|
|
15339
|
-
walkAst(ownerFunction.body, (child) => {
|
|
15340
|
-
if (child !== ownerFunction.body && isFunctionLike$1(child)) return false;
|
|
15341
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
15342
|
-
const registrationDetails = getCallRegistrationDetails(child, context);
|
|
15343
|
-
if (registrationDetails.registrationVerbName === "addEventListener" && registrationDetails.receiverKey === usage.receiverKey && resolveStableValue(child.arguments?.[1], context) === releaseFunction) triggerRegistrations.push(child);
|
|
15344
|
-
});
|
|
15345
|
-
if (triggerRegistrations.some((triggerRegistration) => triggerRegistration === usage.node)) return true;
|
|
15346
|
-
return doMatchingNodesCoverEveryPathAfterUsage(usage.node, triggerRegistrations, context) || doMatchingNodesCoverEveryPathBeforeUsage(usage.node, triggerRegistrations, ownerFunction, context);
|
|
15347
|
-
};
|
|
15348
14834
|
const isReleaseReachableForUsage = (releaseNode, usage, context) => {
|
|
15349
14835
|
if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
|
|
15350
14836
|
const releaseFunction = findEnclosingFunction$1(releaseNode);
|
|
15351
14837
|
if (!releaseFunction) return true;
|
|
15352
14838
|
if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
|
|
15353
|
-
if (isRetainedDisposerRefRelease(releaseNode, usage, context)) return true;
|
|
15354
14839
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15355
14840
|
if (usageFunction && isFunctionLike$1(usageFunction) && getAssignedReactRefSymbol(usageFunction, context) && isCleanupFunctionReferencedByReturn(usageFunction, releaseFunction, context)) return isReactRefCallbackCleanupOwnedByEffect(usageFunction, releaseFunction, usage, context);
|
|
15356
|
-
if (isSelfReleasingListenerRelease(releaseNode, releaseFunction, usage, context)) return true;
|
|
15357
14841
|
return isPotentiallyReachableFunction(releaseFunction, context);
|
|
15358
14842
|
};
|
|
15359
14843
|
const fileContainsReleaseForUsage = (usage, context) => {
|
|
@@ -15621,11 +15105,6 @@ const doesResourceResultEscape = (resourceNode, allowReturnedResourceEscape, all
|
|
|
15621
15105
|
parentNode = currentNode.parent;
|
|
15622
15106
|
continue;
|
|
15623
15107
|
}
|
|
15624
|
-
if (isNodeOfType(parentNode, "ConditionalExpression") && (parentNode.consequent === currentNode || parentNode.alternate === currentNode) || isNodeOfType(parentNode, "LogicalExpression") && (parentNode.right === currentNode || parentNode.left === currentNode && parentNode.operator !== "&&")) {
|
|
15625
|
-
currentNode = parentNode;
|
|
15626
|
-
parentNode = currentNode.parent;
|
|
15627
|
-
continue;
|
|
15628
|
-
}
|
|
15629
15108
|
if (isNodeOfType(parentNode, "VariableDeclarator") && parentNode.init === currentNode && isNodeOfType(parentNode.id, "Identifier") && isNodeOfType(parentNode.parent, "VariableDeclaration") && parentNode.parent.kind === "const") {
|
|
15630
15109
|
const ownerFunction = findEnclosingFunction$1(resourceNode);
|
|
15631
15110
|
const resourceSymbol = context.scopes.symbolFor(parentNode.id);
|
|
@@ -15685,7 +15164,7 @@ const findRetainedFunctionLeak = (retainedFunction, context, options) => {
|
|
|
15685
15164
|
return false;
|
|
15686
15165
|
}
|
|
15687
15166
|
}
|
|
15688
|
-
if (
|
|
15167
|
+
if (isSubscribeOrObserveCall(child) && (!doesResourceResultEscape(child, allowReturnedResourceEscape, allowReturnedResourceEscape, context) || options?.requireCallableReturnedResource === true && !isCleanupReturningSubscribeLikeCallExpression(child))) {
|
|
15689
15168
|
const registrationDetails = getCallRegistrationDetails(child, context);
|
|
15690
15169
|
const subscriptionUsage = {
|
|
15691
15170
|
kind: "subscribe",
|
|
@@ -17285,38 +16764,7 @@ const symbolHasStableImportedAlias = (symbol, scopes) => {
|
|
|
17285
16764
|
const resolvedSymbol = resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes);
|
|
17286
16765
|
return resolvedSymbol !== null && resolvedSymbol !== symbol && resolvedSymbol.kind === "import";
|
|
17287
16766
|
};
|
|
17288
|
-
const
|
|
17289
|
-
let currentNode = findTransparentExpressionRoot(node);
|
|
17290
|
-
while (currentNode.parent) {
|
|
17291
|
-
const parentNode = currentNode.parent;
|
|
17292
|
-
if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
|
|
17293
|
-
if (isNodeOfType(parentNode, "UpdateExpression")) return parentNode.argument === currentNode;
|
|
17294
|
-
if (isNodeOfType(parentNode, "UnaryExpression")) return parentNode.operator === "delete" && parentNode.argument === currentNode;
|
|
17295
|
-
if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
|
|
17296
|
-
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")) {
|
|
17297
|
-
currentNode = parentNode;
|
|
17298
|
-
continue;
|
|
17299
|
-
}
|
|
17300
|
-
return false;
|
|
17301
|
-
}
|
|
17302
|
-
return false;
|
|
17303
|
-
};
|
|
17304
|
-
const symbolHasStableRefLazyInitialization = (symbol, scopes) => {
|
|
17305
|
-
if (symbol.kind !== "const" || symbol.references.some((reference) => reference.flag !== "read")) return false;
|
|
17306
|
-
const initializer = symbol.initializer ? unwrapExpression$3(symbol.initializer) : null;
|
|
17307
|
-
if (!isNodeOfType(initializer, "AssignmentExpression") || initializer.operator !== "??=") return false;
|
|
17308
|
-
const refSymbol = resolveReactRefSymbol(unwrapExpression$3(initializer.left), scopes);
|
|
17309
|
-
if (!refSymbol) return false;
|
|
17310
|
-
return refSymbol.references.every((reference) => {
|
|
17311
|
-
const memberExpression = findTransparentExpressionRoot(reference.identifier).parent;
|
|
17312
|
-
if (!isNodeOfType(memberExpression, "MemberExpression") || unwrapExpression$3(memberExpression.object) !== reference.identifier || getStaticPropertyName(memberExpression) !== "current") return false;
|
|
17313
|
-
const referenceRoot = findTransparentExpressionRoot(memberExpression);
|
|
17314
|
-
const parentNode = referenceRoot.parent;
|
|
17315
|
-
if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.left === referenceRoot) return parentNode === initializer;
|
|
17316
|
-
return !isAssignmentTarget(referenceRoot);
|
|
17317
|
-
});
|
|
17318
|
-
};
|
|
17319
|
-
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);
|
|
17320
16768
|
//#endregion
|
|
17321
16769
|
//#region src/plugin/utils/symbol-has-react-use-effect-event-origin.ts
|
|
17322
16770
|
const symbolHasReactUseEffectEventOrigin = (symbol, scopes) => {
|
|
@@ -17518,7 +16966,7 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allow
|
|
|
17518
16966
|
keys.add(depKey);
|
|
17519
16967
|
continue;
|
|
17520
16968
|
}
|
|
17521
|
-
const identitySourceKeys =
|
|
16969
|
+
const identitySourceKeys = resolveReactiveIdentitySourceKeys(symbol, scopes);
|
|
17522
16970
|
if (identitySourceKeys) {
|
|
17523
16971
|
if (identitySourceKeys.size === 0) stableCapturedNames.add(depKey);
|
|
17524
16972
|
for (const identitySourceKey of identitySourceKeys) keys.add(identitySourceKey);
|
|
@@ -17601,161 +17049,6 @@ const resolveReactiveIdentitySourceKeys = (symbol, scopes) => {
|
|
|
17601
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;
|
|
17602
17050
|
return resolveIdentitySourceKeysFromExpression(symbol.initializer, scopes, new Set([symbol.id]));
|
|
17603
17051
|
};
|
|
17604
|
-
const isPureDerivedExpression = (expression) => {
|
|
17605
|
-
const candidate = unwrapExpression$3(expression);
|
|
17606
|
-
if (isNodeOfType(candidate, "Literal") || isNodeOfType(candidate, "Identifier")) return true;
|
|
17607
|
-
if (isNodeOfType(candidate, "MemberExpression")) return isPureDerivedExpression(candidate.object) && (!candidate.computed || isPureDerivedExpression(candidate.property));
|
|
17608
|
-
if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return isPureDerivedExpression(candidate.left) && isPureDerivedExpression(candidate.right);
|
|
17609
|
-
if (isNodeOfType(candidate, "UnaryExpression")) return candidate.operator !== "delete" && isPureDerivedExpression(candidate.argument);
|
|
17610
|
-
if (isNodeOfType(candidate, "ConditionalExpression")) return isPureDerivedExpression(candidate.test) && isPureDerivedExpression(candidate.consequent) && isPureDerivedExpression(candidate.alternate);
|
|
17611
|
-
if (isNodeOfType(candidate, "TemplateLiteral")) return candidate.expressions.every((nestedExpression) => isPureDerivedExpression(nestedExpression));
|
|
17612
|
-
return false;
|
|
17613
|
-
};
|
|
17614
|
-
const isPureDerivedStatement = (statement) => {
|
|
17615
|
-
if (isNodeOfType(statement, "BlockStatement")) return statement.body.every((nestedStatement) => isPureDerivedStatement(nestedStatement));
|
|
17616
|
-
if (isNodeOfType(statement, "ReturnStatement")) return !statement.argument || isPureDerivedExpression(statement.argument);
|
|
17617
|
-
if (isNodeOfType(statement, "IfStatement")) return isPureDerivedExpression(statement.test) && isPureDerivedStatement(statement.consequent) && (!statement.alternate || isPureDerivedStatement(statement.alternate));
|
|
17618
|
-
return false;
|
|
17619
|
-
};
|
|
17620
|
-
const isPureDerivedFunction = (functionNode) => {
|
|
17621
|
-
if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return false;
|
|
17622
|
-
if (functionNode.async || functionNode.generator) return false;
|
|
17623
|
-
return isNodeOfType(functionNode.body, "BlockStatement") ? isPureDerivedStatement(functionNode.body) : isPureDerivedExpression(functionNode.body);
|
|
17624
|
-
};
|
|
17625
|
-
const resolvePureCalledFunctionSourceKeys = (reference, symbol, scopes) => {
|
|
17626
|
-
if (symbol.references.some((symbolReference) => symbolReference.flag !== "read")) return null;
|
|
17627
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
17628
|
-
const callExpression = referenceRoot.parent;
|
|
17629
|
-
if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== referenceRoot) return null;
|
|
17630
|
-
const functionNode = getFunctionValueNode(symbol);
|
|
17631
|
-
if (!functionNode || !isPureDerivedFunction(functionNode)) return null;
|
|
17632
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17633
|
-
for (const capturedReference of closureCaptures(functionNode, scopes)) {
|
|
17634
|
-
const capturedSymbol = capturedReference.resolvedSymbol;
|
|
17635
|
-
if (!capturedSymbol || capturedSymbol.id === symbol.id) continue;
|
|
17636
|
-
if (isOutsideAllFunctions(capturedSymbol) || symbolHasStableValue(capturedSymbol, scopes)) continue;
|
|
17637
|
-
const capturedKey = computeDepKey(capturedReference);
|
|
17638
|
-
if (!capturedKey) return null;
|
|
17639
|
-
if (capturedKey === capturedSymbol.name) {
|
|
17640
|
-
const nestedSourceKeys = resolveReactiveIdentitySourceKeys(capturedSymbol, scopes);
|
|
17641
|
-
if (nestedSourceKeys) {
|
|
17642
|
-
for (const nestedSourceKey of nestedSourceKeys) sourceKeys.add(nestedSourceKey);
|
|
17643
|
-
continue;
|
|
17644
|
-
}
|
|
17645
|
-
}
|
|
17646
|
-
sourceKeys.add(capturedKey);
|
|
17647
|
-
}
|
|
17648
|
-
return sourceKeys.size > 0 ? sourceKeys : null;
|
|
17649
|
-
};
|
|
17650
|
-
const mergeDerivedExpressionSourceKeys = (expressions, scopes, visitedSymbolIds) => {
|
|
17651
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17652
|
-
for (const expression of expressions) {
|
|
17653
|
-
const expressionSourceKeys = resolveDerivedExpressionSourceKeys(expression, scopes, visitedSymbolIds);
|
|
17654
|
-
if (!expressionSourceKeys) return null;
|
|
17655
|
-
for (const expressionSourceKey of expressionSourceKeys) sourceKeys.add(expressionSourceKey);
|
|
17656
|
-
}
|
|
17657
|
-
return sourceKeys;
|
|
17658
|
-
};
|
|
17659
|
-
const resolveDerivedExpressionSourceKeys = (expression, scopes, visitedSymbolIds) => {
|
|
17660
|
-
const candidate = unwrapExpression$3(expression);
|
|
17661
|
-
if (isNodeOfType(candidate, "Literal")) return /* @__PURE__ */ new Set();
|
|
17662
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
17663
|
-
if (scopes.isGlobalReference(candidate)) return /* @__PURE__ */ new Set();
|
|
17664
|
-
const sourceSymbol = scopes.symbolFor(candidate);
|
|
17665
|
-
if (!sourceSymbol) return null;
|
|
17666
|
-
if (isOutsideAllFunctions(sourceSymbol) || symbolHasStableValue(sourceSymbol, scopes)) return /* @__PURE__ */ new Set();
|
|
17667
|
-
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)) {
|
|
17668
|
-
visitedSymbolIds.add(sourceSymbol.id);
|
|
17669
|
-
const sourceKeys = resolveDerivedExpressionSourceKeys(sourceSymbol.initializer, scopes, visitedSymbolIds);
|
|
17670
|
-
visitedSymbolIds.delete(sourceSymbol.id);
|
|
17671
|
-
if (sourceKeys) return sourceKeys;
|
|
17672
|
-
}
|
|
17673
|
-
return new Set([sourceSymbol.name]);
|
|
17674
|
-
}
|
|
17675
|
-
if (isNodeOfType(candidate, "MemberExpression")) {
|
|
17676
|
-
if (hasComputedMemberExpression(candidate)) return null;
|
|
17677
|
-
const sourceKey = stringifyMemberChain(candidate);
|
|
17678
|
-
const rootIdentifier = getMemberRootIdentifier(candidate);
|
|
17679
|
-
const rootSymbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
|
|
17680
|
-
if (!sourceKey || !rootSymbol) return null;
|
|
17681
|
-
if (isOutsideAllFunctions(rootSymbol) || symbolHasStableValue(rootSymbol, scopes)) return /* @__PURE__ */ new Set();
|
|
17682
|
-
return new Set([sourceKey]);
|
|
17683
|
-
}
|
|
17684
|
-
if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return mergeDerivedExpressionSourceKeys([candidate.left, candidate.right], scopes, visitedSymbolIds);
|
|
17685
|
-
if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator !== "delete") return resolveDerivedExpressionSourceKeys(candidate.argument, scopes, visitedSymbolIds);
|
|
17686
|
-
if (isNodeOfType(candidate, "ConditionalExpression")) return mergeDerivedExpressionSourceKeys([
|
|
17687
|
-
candidate.test,
|
|
17688
|
-
candidate.consequent,
|
|
17689
|
-
candidate.alternate
|
|
17690
|
-
], scopes, visitedSymbolIds);
|
|
17691
|
-
if (isNodeOfType(candidate, "TemplateLiteral")) return mergeDerivedExpressionSourceKeys(candidate.expressions, scopes, visitedSymbolIds);
|
|
17692
|
-
if (isNodeOfType(candidate, "NewExpression")) {
|
|
17693
|
-
const callee = unwrapExpression$3(candidate.callee);
|
|
17694
|
-
if (!isNodeOfType(callee, "Identifier") || callee.name !== "Error" || !scopes.isGlobalReference(callee)) return null;
|
|
17695
|
-
const argumentsToAnalyze = [];
|
|
17696
|
-
for (const argument of candidate.arguments) {
|
|
17697
|
-
if (!isAstNode(argument) || isNodeOfType(argument, "SpreadElement")) return null;
|
|
17698
|
-
argumentsToAnalyze.push(argument);
|
|
17699
|
-
}
|
|
17700
|
-
return mergeDerivedExpressionSourceKeys(argumentsToAnalyze, scopes, visitedSymbolIds);
|
|
17701
|
-
}
|
|
17702
|
-
return null;
|
|
17703
|
-
};
|
|
17704
|
-
const resolveWriteControlSourceKeys = (assignment, boundaryFunction, scopes) => {
|
|
17705
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17706
|
-
let currentNode = assignment;
|
|
17707
|
-
while (currentNode.parent && currentNode.parent !== boundaryFunction) {
|
|
17708
|
-
const parentNode = currentNode.parent;
|
|
17709
|
-
if (isNodeOfType(parentNode, "IfStatement")) {
|
|
17710
|
-
if (parentNode.test === currentNode) return null;
|
|
17711
|
-
const testSourceKeys = resolveDerivedExpressionSourceKeys(parentNode.test, scopes, /* @__PURE__ */ new Set());
|
|
17712
|
-
if (!testSourceKeys) return null;
|
|
17713
|
-
for (const testSourceKey of testSourceKeys) sourceKeys.add(testSourceKey);
|
|
17714
|
-
} else if (!isNodeOfType(parentNode, "ExpressionStatement") && !isNodeOfType(parentNode, "BlockStatement")) return null;
|
|
17715
|
-
currentNode = parentNode;
|
|
17716
|
-
}
|
|
17717
|
-
return currentNode.parent === boundaryFunction ? sourceKeys : null;
|
|
17718
|
-
};
|
|
17719
|
-
const isReadOnlyInitialStateUse = (referenceNode, scopes) => {
|
|
17720
|
-
const referenceRoot = findTransparentExpressionRoot(referenceNode);
|
|
17721
|
-
const callExpression = referenceRoot.parent;
|
|
17722
|
-
return isNodeOfType(callExpression, "CallExpression") && callExpression.arguments.some((argument) => argument === referenceRoot) && isReactApiCall(callExpression, "useState", scopes, {
|
|
17723
|
-
allowGlobalReactNamespace: true,
|
|
17724
|
-
allowUnboundBareCalls: true,
|
|
17725
|
-
resolveNamedAliases: true
|
|
17726
|
-
});
|
|
17727
|
-
};
|
|
17728
|
-
const resolveRenderDerivedMutableSourceKeys = (capturedReference, symbol, scopes) => {
|
|
17729
|
-
if (symbol.kind !== "let" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
17730
|
-
const boundaryFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
17731
|
-
if (!boundaryFunction) return null;
|
|
17732
|
-
const capturingFunction = findEnclosingFunction$1(capturedReference.identifier);
|
|
17733
|
-
if (!capturingFunction || capturingFunction === boundaryFunction) return null;
|
|
17734
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17735
|
-
if (symbol.initializer) {
|
|
17736
|
-
const initializerSourceKeys = resolveDerivedExpressionSourceKeys(symbol.initializer, scopes, new Set([symbol.id]));
|
|
17737
|
-
if (!initializerSourceKeys) return null;
|
|
17738
|
-
for (const initializerSourceKey of initializerSourceKeys) sourceKeys.add(initializerSourceKey);
|
|
17739
|
-
}
|
|
17740
|
-
let writeCount = 0;
|
|
17741
|
-
for (const symbolReference of symbol.references) {
|
|
17742
|
-
if (symbolReference.flag === "read") {
|
|
17743
|
-
if (findEnclosingFunction$1(symbolReference.identifier) !== capturingFunction && !isReadOnlyInitialStateUse(symbolReference.identifier, scopes)) return null;
|
|
17744
|
-
continue;
|
|
17745
|
-
}
|
|
17746
|
-
if (symbolReference.flag !== "write") return null;
|
|
17747
|
-
const referenceRoot = findTransparentExpressionRoot(symbolReference.identifier);
|
|
17748
|
-
const assignment = referenceRoot.parent;
|
|
17749
|
-
if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== referenceRoot || findEnclosingFunction$1(referenceRoot) !== boundaryFunction) return null;
|
|
17750
|
-
const assignmentSourceKeys = resolveDerivedExpressionSourceKeys(assignment.right, scopes, new Set([symbol.id]));
|
|
17751
|
-
const controlSourceKeys = resolveWriteControlSourceKeys(assignment, boundaryFunction, scopes);
|
|
17752
|
-
if (!assignmentSourceKeys || !controlSourceKeys) return null;
|
|
17753
|
-
for (const assignmentSourceKey of assignmentSourceKeys) sourceKeys.add(assignmentSourceKey);
|
|
17754
|
-
for (const controlSourceKey of controlSourceKeys) sourceKeys.add(controlSourceKey);
|
|
17755
|
-
writeCount += 1;
|
|
17756
|
-
}
|
|
17757
|
-
return writeCount > 0 && sourceKeys.size > 0 ? sourceKeys : null;
|
|
17758
|
-
};
|
|
17759
17052
|
const isUseCallbackResultDep = (node, scopes) => {
|
|
17760
17053
|
const rootSymbol = getRootSymbol(node, scopes);
|
|
17761
17054
|
const initializer = rootSymbol?.initializer ? unwrapExpression$3(rootSymbol.initializer) : null;
|
|
@@ -18499,7 +17792,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
18499
17792
|
if (!isUsed) continue;
|
|
18500
17793
|
const reportNode = declaredKeyToReportNode.get(declaredKey) ?? depsArgument;
|
|
18501
17794
|
const rootSymbol = getRootSymbol(reportNode, context.scopes);
|
|
18502
|
-
if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) ||
|
|
17795
|
+
if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) || !isUnstableInitializer(rootSymbol.initializer)) continue;
|
|
18503
17796
|
context.report({
|
|
18504
17797
|
node: reportNode,
|
|
18505
17798
|
message: buildUnstableDepMessage(hookName, declaredKey)
|
|
@@ -29983,7 +29276,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
29983
29276
|
//#endregion
|
|
29984
29277
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
29985
29278
|
const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
29986
|
-
const isUndefinedIdentifier
|
|
29279
|
+
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
29987
29280
|
const noAccessKey = defineRule({
|
|
29988
29281
|
id: "no-access-key",
|
|
29989
29282
|
title: "accessKey attribute used",
|
|
@@ -30008,7 +29301,7 @@ const noAccessKey = defineRule({
|
|
|
30008
29301
|
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
30009
29302
|
const expression = attributeValue.expression;
|
|
30010
29303
|
if (!expression || expression.type === "JSXEmptyExpression") return;
|
|
30011
|
-
if (isUndefinedIdentifier
|
|
29304
|
+
if (isUndefinedIdentifier(expression)) return;
|
|
30012
29305
|
context.report({
|
|
30013
29306
|
node: accessKey,
|
|
30014
29307
|
message: MESSAGE$39
|
|
@@ -30773,12 +30066,6 @@ const isReactNamespaceImportReference = (ref) => Boolean(ref?.resolved?.defs.som
|
|
|
30773
30066
|
const importDeclaration = declarationNode.parent;
|
|
30774
30067
|
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
|
|
30775
30068
|
}));
|
|
30776
|
-
const isReactNamespaceReceiver = (analysis, node) => {
|
|
30777
|
-
const receiver = stripParenExpression(node);
|
|
30778
|
-
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
30779
|
-
const namespaceReference = getRef(analysis, receiver);
|
|
30780
|
-
return namespaceReference?.resolved ? isReactNamespaceImportReference(namespaceReference) : receiver.name === "React";
|
|
30781
|
-
};
|
|
30782
30069
|
const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
|
|
30783
30070
|
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return false;
|
|
30784
30071
|
const callee = stripParenExpression(declarator.init.callee);
|
|
@@ -30787,20 +30074,24 @@ const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
|
|
|
30787
30074
|
if (!reference?.resolved) return callee.name === hookName;
|
|
30788
30075
|
return isReactNamedImportReference(reference, hookName);
|
|
30789
30076
|
}
|
|
30790
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
|
|
30791
|
-
|
|
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);
|
|
30792
30081
|
};
|
|
30793
30082
|
const isHookCallee$1 = (analysis, node, hookName) => {
|
|
30794
30083
|
if (!node) return false;
|
|
30795
30084
|
if (isNodeOfType(node, "Identifier")) {
|
|
30796
30085
|
if (node.name === hookName) return true;
|
|
30797
30086
|
if (isReactNamedImportReference(getRef(analysis, node), hookName)) return true;
|
|
30798
|
-
const
|
|
30799
|
-
|
|
30800
|
-
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;
|
|
30801
30089
|
return false;
|
|
30802
30090
|
}
|
|
30803
|
-
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
|
+
}
|
|
30804
30095
|
return false;
|
|
30805
30096
|
};
|
|
30806
30097
|
const isUseEffect = (node) => {
|
|
@@ -30908,27 +30199,7 @@ const isRefCurrent = (ref) => {
|
|
|
30908
30199
|
if (!isNodeOfType(parent.property, "Identifier")) return false;
|
|
30909
30200
|
return parent.property.name === "current";
|
|
30910
30201
|
};
|
|
30911
|
-
const
|
|
30912
|
-
const visitedReferences = /* @__PURE__ */ new Set();
|
|
30913
|
-
let currentReference = ref;
|
|
30914
|
-
while (currentReference && !visitedReferences.has(currentReference)) {
|
|
30915
|
-
if (isStateSetter(analysis, currentReference)) return currentReference;
|
|
30916
|
-
visitedReferences.add(currentReference);
|
|
30917
|
-
const definitions = currentReference.resolved?.defs ?? [];
|
|
30918
|
-
if (definitions.length !== 1) return null;
|
|
30919
|
-
const definitionNode = definitions[0].node;
|
|
30920
|
-
if (!isNodeOfType(definitionNode, "VariableDeclarator")) return null;
|
|
30921
|
-
if (!isNodeOfType(definitionNode.id, "Identifier")) return null;
|
|
30922
|
-
const declaration = definitionNode.parent;
|
|
30923
|
-
if (!isNodeOfType(declaration, "VariableDeclaration") || declaration.kind !== "const") return null;
|
|
30924
|
-
if (!definitionNode.init) return null;
|
|
30925
|
-
const initializer = stripParenExpression(definitionNode.init);
|
|
30926
|
-
if (!isNodeOfType(initializer, "Identifier")) return null;
|
|
30927
|
-
currentReference = getRef(analysis, initializer);
|
|
30928
|
-
}
|
|
30929
|
-
return null;
|
|
30930
|
-
};
|
|
30931
|
-
const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => resolveStateSetterReference(analysis, innerRef) !== null);
|
|
30202
|
+
const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isStateSetter(analysis, innerRef));
|
|
30932
30203
|
const isSyncStateSetterCall = (analysis, ref, effectFn) => isStateSetterCall(analysis, ref) && isSynchronous(ref.identifier, effectFn) && !resolvesToAsyncFunction(ref);
|
|
30933
30204
|
const HANDLER_NAMED_METHOD_PATTERN = /^(on|handle)[A-Z]/;
|
|
30934
30205
|
const SYNCHRONOUS_CALLBACK_ARGUMENT_INDEX_BY_METHOD = new Map([
|
|
@@ -31079,11 +30350,9 @@ const isPropCallbackInvocationRef = (analysis, ref, options = {}) => {
|
|
|
31079
30350
|
};
|
|
31080
30351
|
const isRefCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isRefCurrent(innerRef) || isRef(analysis, innerRef));
|
|
31081
30352
|
const getUseStateDecl = (analysis, ref) => {
|
|
31082
|
-
|
|
31083
|
-
|
|
31084
|
-
|
|
31085
|
-
});
|
|
31086
|
-
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;
|
|
31087
30356
|
};
|
|
31088
30357
|
const isCleanupReturnArgument = (analysis, node) => {
|
|
31089
30358
|
if (isFunctionLike$1(node)) return true;
|
|
@@ -31246,88 +30515,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
|
|
|
31246
30515
|
if (HANDLER_BINDING_NAME_PATTERN.test(bindingName)) return true;
|
|
31247
30516
|
return isSetterWiredToJsxHandler(componentFunction, bindingName);
|
|
31248
30517
|
};
|
|
31249
|
-
const
|
|
31250
|
-
const functionMetadata = functionNode;
|
|
31251
|
-
return functionMetadata.async !== true && functionMetadata.generator !== true;
|
|
31252
|
-
};
|
|
31253
|
-
const findBindingVariable = (analysis, bindingIdentifier) => {
|
|
31254
|
-
for (const scope of analysis.scopeManager.scopes) for (const variable of scope.variables) if (variable.identifiers.includes(bindingIdentifier)) return variable;
|
|
31255
|
-
return null;
|
|
31256
|
-
};
|
|
31257
|
-
const getImmutableFunctionVariable = (analysis, componentFunction, functionNode) => {
|
|
31258
|
-
if (!isSynchronousFunction(functionNode) || !isAstDescendant(functionNode, componentFunction)) return null;
|
|
31259
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
31260
|
-
if (!bindingIdentifier) return null;
|
|
31261
|
-
const variable = findBindingVariable(analysis, bindingIdentifier);
|
|
31262
|
-
if (!variable || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && !reference.init)) return null;
|
|
31263
|
-
const definition = variable.defs[0];
|
|
31264
|
-
if (definition.type === "FunctionName") return definition.node === functionNode ? variable : null;
|
|
31265
|
-
if (definition.type !== "Variable") return null;
|
|
31266
|
-
const declarator = definition.node;
|
|
31267
|
-
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return null;
|
|
31268
|
-
if (declarator.init === functionNode) return variable;
|
|
31269
|
-
if (isNodeOfType(declarator.init, "CallExpression") && declarator.init.arguments?.[0] === functionNode && isGenuineReactHookDeclarator(analysis, declarator, "useCallback")) return variable;
|
|
31270
|
-
return null;
|
|
31271
|
-
};
|
|
31272
|
-
const getJsxEventValueAttribute = (identifier) => {
|
|
31273
|
-
const expression = findTransparentExpressionRoot(identifier);
|
|
31274
|
-
const expressionContainer = expression.parent;
|
|
31275
|
-
if (!isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== expression) return null;
|
|
31276
|
-
const attribute = expressionContainer.parent;
|
|
31277
|
-
if (!isNodeOfType(attribute, "JSXAttribute")) return null;
|
|
31278
|
-
const attributeName = getJsxAttributeName(attribute.name);
|
|
31279
|
-
return attributeName && isEventHandlerName(attributeName) ? attribute : null;
|
|
31280
|
-
};
|
|
31281
|
-
const getInlineJsxEventCallbackAttribute = (callExpression) => {
|
|
31282
|
-
const callbackFunction = findEnclosingFunction$1(callExpression);
|
|
31283
|
-
if (!callbackFunction || !isSynchronousFunction(callbackFunction)) return null;
|
|
31284
|
-
return getJsxEventValueAttribute(callbackFunction);
|
|
31285
|
-
};
|
|
31286
|
-
const isReactHookDependencyReference = (identifier) => {
|
|
31287
|
-
const expression = findTransparentExpressionRoot(identifier);
|
|
31288
|
-
const dependencyArray = expression.parent;
|
|
31289
|
-
if (!isNodeOfType(dependencyArray, "ArrayExpression") || !(dependencyArray.elements ?? []).includes(expression)) return false;
|
|
31290
|
-
const hookCall = dependencyArray.parent;
|
|
31291
|
-
if (!isNodeOfType(hookCall, "CallExpression") || hookCall.arguments?.[1] !== dependencyArray) return false;
|
|
31292
|
-
const callee = hookCall.callee;
|
|
31293
|
-
if (isNodeOfType(callee, "Identifier")) return /^use[A-Z0-9]/.test(callee.name);
|
|
31294
|
-
return Boolean(isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && /^use[A-Z0-9]/.test(callee.property.name));
|
|
31295
|
-
};
|
|
31296
|
-
const hasReachableJsxEventCallPath = (analysis, context, componentFunction, functionVariable, visitedVariables) => {
|
|
31297
|
-
if (visitedVariables.has(functionVariable)) return false;
|
|
31298
|
-
const nextVisitedVariables = new Set(visitedVariables).add(functionVariable);
|
|
31299
|
-
const callExpressions = [];
|
|
31300
|
-
let hasDirectJsxEventReference = false;
|
|
31301
|
-
for (const reference of functionVariable.references) {
|
|
31302
|
-
if (reference.init) continue;
|
|
31303
|
-
const identifier = reference.identifier;
|
|
31304
|
-
if (reference.isWrite()) return false;
|
|
31305
|
-
const jsxEventValueAttribute = getJsxEventValueAttribute(identifier);
|
|
31306
|
-
if (jsxEventValueAttribute) {
|
|
31307
|
-
if (isNodeReachableWithinFunction(jsxEventValueAttribute, context)) hasDirectJsxEventReference = true;
|
|
31308
|
-
continue;
|
|
31309
|
-
}
|
|
31310
|
-
if (isReactHookDependencyReference(identifier)) continue;
|
|
31311
|
-
const callExpression = getCallExpr(reference);
|
|
31312
|
-
if (!callExpression) return false;
|
|
31313
|
-
const jsxEventCallbackAttribute = getInlineJsxEventCallbackAttribute(callExpression);
|
|
31314
|
-
if (jsxEventCallbackAttribute) {
|
|
31315
|
-
if (isNodeReachableWithinFunction(callExpression, context) && isNodeReachableWithinFunction(jsxEventCallbackAttribute, context)) hasDirectJsxEventReference = true;
|
|
31316
|
-
continue;
|
|
31317
|
-
}
|
|
31318
|
-
callExpressions.push(callExpression);
|
|
31319
|
-
}
|
|
31320
|
-
if (hasDirectJsxEventReference) return true;
|
|
31321
|
-
for (const callExpression of callExpressions) {
|
|
31322
|
-
if (!isNodeReachableWithinFunction(callExpression, context)) continue;
|
|
31323
|
-
const callerFunction = findEnclosingFunction$1(callExpression);
|
|
31324
|
-
if (!callerFunction || callerFunction === componentFunction) continue;
|
|
31325
|
-
const callerVariable = getImmutableFunctionVariable(analysis, componentFunction, callerFunction);
|
|
31326
|
-
if (callerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, callerVariable, nextVisitedVariables)) return true;
|
|
31327
|
-
}
|
|
31328
|
-
return false;
|
|
31329
|
-
};
|
|
31330
|
-
const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, includeDeferredWriters = false) => {
|
|
30518
|
+
const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
|
|
31331
30519
|
if (!setterRef.resolved) return false;
|
|
31332
30520
|
const componentFunction = findEnclosingFunction$1(effectNode);
|
|
31333
30521
|
if (!componentFunction) return false;
|
|
@@ -31336,11 +30524,6 @@ const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, incl
|
|
|
31336
30524
|
const identifier = reference.identifier;
|
|
31337
30525
|
if (isAstDescendant(identifier, effectNode)) continue;
|
|
31338
30526
|
if (isIndependentWriterIdentifier(componentFunction, identifier, includeDeferredWriters)) return true;
|
|
31339
|
-
if (!isNodeReachableWithinFunction(identifier, context)) continue;
|
|
31340
|
-
const writerFunction = findEnclosingFunction$1(identifier);
|
|
31341
|
-
if (!writerFunction || writerFunction === componentFunction) continue;
|
|
31342
|
-
const writerVariable = getImmutableFunctionVariable(analysis, componentFunction, writerFunction);
|
|
31343
|
-
if (writerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, writerVariable, /* @__PURE__ */ new Set())) return true;
|
|
31344
30527
|
}
|
|
31345
30528
|
return false;
|
|
31346
30529
|
};
|
|
@@ -32230,7 +31413,7 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
|
|
|
32230
31413
|
}
|
|
32231
31414
|
return false;
|
|
32232
31415
|
};
|
|
32233
|
-
const collectEffectStateWriteFacts = (analysis,
|
|
31416
|
+
const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
|
|
32234
31417
|
const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
|
|
32235
31418
|
if (frames.length === 0) return [];
|
|
32236
31419
|
const effectHasCleanup = hasCleanup(analysis, effectNode);
|
|
@@ -32260,7 +31443,7 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
|
|
|
32260
31443
|
for (const returnedExpression of returnedExpressions) mergeEvidence(valueEvidence, collectValueEvidence(analysis, returnedExpression, updaterFrame, remainingValueCallFrames));
|
|
32261
31444
|
} else valueEvidence = collectValueEvidence(analysis, writtenValue, frame, remainingValueCallFrames);
|
|
32262
31445
|
const sourceReferences = [...valueEvidence.sourceReferences].filter((sourceReference) => getUseStateDecl(analysis, sourceReference) !== stateDeclarator);
|
|
32263
|
-
const hasIndependentWriter = hasUserInputSetterWriter(
|
|
31446
|
+
const hasIndependentWriter = hasUserInputSetterWriter(setterReference, effectNode, true);
|
|
32264
31447
|
const doesMatchStateInitializer = matchesStateInitializer(analysis, callExpression, stateDeclarator);
|
|
32265
31448
|
if (effectHasCleanup && (frame.isDeferred || valueEvidence.hasUnknownSource || valueEvidence.hasDeferredIntroducedValue || valueEvidence.readsExternalValue)) cleanupManagedStateDeclarators.add(stateDeclarator);
|
|
32266
31449
|
const isRenderKnownCopy = sourceReferences.length > 0 && !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue && !hasIndependentWriter;
|
|
@@ -32272,7 +31455,6 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
|
|
|
32272
31455
|
sourceReferences,
|
|
32273
31456
|
isDeferred: frame.isDeferred,
|
|
32274
31457
|
isRenderKnownCopy,
|
|
32275
|
-
isSynchronousRenderValue: !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue,
|
|
32276
31458
|
matchesStateInitializer: doesMatchStateInitializer,
|
|
32277
31459
|
resetsSourceState: false
|
|
32278
31460
|
});
|
|
@@ -32288,76 +31470,22 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
|
|
|
32288
31470
|
});
|
|
32289
31471
|
};
|
|
32290
31472
|
//#endregion
|
|
32291
|
-
//#region src/plugin/rules/state-and-effects/utils/has-deferred-or-external-effect-work.ts
|
|
32292
|
-
const DEFERRED_MEMBER_NAMES = new Set([
|
|
32293
|
-
"catch",
|
|
32294
|
-
"finally",
|
|
32295
|
-
"then"
|
|
32296
|
-
]);
|
|
32297
|
-
const hasDeferredOrExternalEffectWork = (analysis, effectNode, scopes) => {
|
|
32298
|
-
const effectFunction = getEffectFn(analysis, effectNode);
|
|
32299
|
-
if (!effectFunction) return false;
|
|
32300
|
-
if (containsFetchCall(effectFunction, { stopAtFunctionBoundary: true })) return true;
|
|
32301
|
-
const effectInvokedFunctions = collectEffectInvokedFunctions(effectFunction);
|
|
32302
|
-
let didFindDeferredOrExternalWork = false;
|
|
32303
|
-
walkAst(effectFunction, (child) => {
|
|
32304
|
-
if (didFindDeferredOrExternalWork) return false;
|
|
32305
|
-
if (child !== effectFunction && isFunctionLike$1(child) && !effectInvokedFunctions.has(child)) return false;
|
|
32306
|
-
if (isNodeOfType(child, "AssignmentExpression")) {
|
|
32307
|
-
const assignmentTarget = child.left;
|
|
32308
|
-
if ((isNodeOfType(assignmentTarget, "MemberExpression") ? getStaticPropertyName(assignmentTarget) : null)?.startsWith("on") && isFunctionLike$1(child.right)) {
|
|
32309
|
-
didFindDeferredOrExternalWork = true;
|
|
32310
|
-
return false;
|
|
32311
|
-
}
|
|
32312
|
-
}
|
|
32313
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
32314
|
-
if (isSubscribeOrObserveCallExpression(child)) {
|
|
32315
|
-
didFindDeferredOrExternalWork = true;
|
|
32316
|
-
return false;
|
|
32317
|
-
}
|
|
32318
|
-
const localFunction = resolveExactLocalFunction(child.callee, scopes);
|
|
32319
|
-
if (isFunctionLike$1(localFunction) && localFunction.async) {
|
|
32320
|
-
didFindDeferredOrExternalWork = true;
|
|
32321
|
-
return false;
|
|
32322
|
-
}
|
|
32323
|
-
const callee = child.callee;
|
|
32324
|
-
if (isNodeOfType(callee, "Identifier") && TIMER_AND_SCHEDULER_DIRECT_CALLEE_NAMES.has(callee.name)) {
|
|
32325
|
-
didFindDeferredOrExternalWork = true;
|
|
32326
|
-
return false;
|
|
32327
|
-
}
|
|
32328
|
-
const memberName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
|
|
32329
|
-
if (memberName && DEFERRED_MEMBER_NAMES.has(memberName)) {
|
|
32330
|
-
didFindDeferredOrExternalWork = true;
|
|
32331
|
-
return false;
|
|
32332
|
-
}
|
|
32333
|
-
});
|
|
32334
|
-
return didFindDeferredOrExternalWork;
|
|
32335
|
-
};
|
|
32336
|
-
//#endregion
|
|
32337
31473
|
//#region src/plugin/rules/state-and-effects/no-adjust-state-on-prop-change.ts
|
|
32338
31474
|
const noAdjustStateOnPropChange = defineRule({
|
|
32339
31475
|
id: "no-adjust-state-on-prop-change",
|
|
32340
|
-
title: "State
|
|
31476
|
+
title: "State synced to a prop inside an effect",
|
|
32341
31477
|
severity: "warn",
|
|
32342
31478
|
tags: ["test-noise"],
|
|
32343
|
-
recommendation: "
|
|
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",
|
|
32344
31480
|
create: (context) => ({ CallExpression(node) {
|
|
32345
|
-
if (!
|
|
32346
|
-
allowGlobalReactNamespace: true,
|
|
32347
|
-
allowUnboundBareCalls: true,
|
|
32348
|
-
resolveConditionalAliases: true,
|
|
32349
|
-
resolveNamedAliases: true
|
|
32350
|
-
})) return;
|
|
31481
|
+
if (!isUseEffect(node)) return;
|
|
32351
31482
|
const analysis = getProgramAnalysis(node);
|
|
32352
31483
|
if (!analysis) return;
|
|
32353
31484
|
const dependencyReferences = getEffectDepsRefs(analysis, node);
|
|
32354
31485
|
if (!dependencyReferences) return;
|
|
32355
31486
|
if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
|
|
32356
|
-
const
|
|
32357
|
-
|
|
32358
|
-
for (const fact of facts) {
|
|
32359
|
-
if (!fact.isSynchronousRenderValue || fact.resetsSourceState) continue;
|
|
32360
|
-
if (fact.sourceReferences.flatMap((reference) => getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) continue;
|
|
31487
|
+
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
31488
|
+
if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
|
|
32361
31489
|
context.report({
|
|
32362
31490
|
node: fact.callExpression,
|
|
32363
31491
|
message: "This effect adjusts state after a prop changes, so users briefly see the stale value."
|
|
@@ -35077,7 +34205,7 @@ const noChainStateUpdates = defineRule({
|
|
|
35077
34205
|
if (!callExpr) continue;
|
|
35078
34206
|
if (!isReachableFromStateTrigger(callExpr)) continue;
|
|
35079
34207
|
if (!readsPostMountValueThroughLocals(callExpr, effectFn, { ignoreBareRefCurrent: true })) continue;
|
|
35080
|
-
const declarator = getUseStateDeclarator(
|
|
34208
|
+
const declarator = getUseStateDeclarator(ref);
|
|
35081
34209
|
if (declarator) domSyncedStateDeclarators.add(declarator);
|
|
35082
34210
|
}
|
|
35083
34211
|
for (const ref of effectFnRefs) {
|
|
@@ -35086,7 +34214,7 @@ const noChainStateUpdates = defineRule({
|
|
|
35086
34214
|
if (!callExpr) continue;
|
|
35087
34215
|
if (!isReachableFromStateTrigger(callExpr)) continue;
|
|
35088
34216
|
if (getArgsUpstreamRefs(analysis, ref).some((argRef) => isState(analysis, argRef))) continue;
|
|
35089
|
-
const setterDeclarator = getUseStateDeclarator(
|
|
34217
|
+
const setterDeclarator = getUseStateDeclarator(ref);
|
|
35090
34218
|
if (setterDeclarator && domSyncedStateDeclarators.has(setterDeclarator)) continue;
|
|
35091
34219
|
const isSelfTargeting = setterDeclarator !== null && stateDepDeclarators.has(setterDeclarator);
|
|
35092
34220
|
const setterArguments = isNodeOfType(callExpr, "CallExpression") ? callExpr.arguments ?? [] : [];
|
|
@@ -36927,15 +36055,10 @@ const noDerivedState = defineRule({
|
|
|
36927
36055
|
for (const fact of collectRenderStateWriteFacts(analysis, componentBody, context.filename)) reportStateWrite(fact.callExpression, fact.stateDeclarator);
|
|
36928
36056
|
} }).visitors,
|
|
36929
36057
|
CallExpression(node) {
|
|
36930
|
-
if (!
|
|
36931
|
-
allowGlobalReactNamespace: true,
|
|
36932
|
-
allowUnboundBareCalls: true,
|
|
36933
|
-
resolveConditionalAliases: true,
|
|
36934
|
-
resolveNamedAliases: true
|
|
36935
|
-
})) return;
|
|
36058
|
+
if (!isUseEffect(node)) return;
|
|
36936
36059
|
const analysis = getProgramAnalysis(node);
|
|
36937
36060
|
if (!analysis) return;
|
|
36938
|
-
for (const fact of collectEffectStateWriteFacts(analysis,
|
|
36061
|
+
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
36939
36062
|
if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
|
|
36940
36063
|
reportStateWrite(fact.callExpression, fact.stateDeclarator);
|
|
36941
36064
|
}
|
|
@@ -36952,15 +36075,10 @@ const noDerivedStateEffect = defineRule({
|
|
|
36952
36075
|
tags: ["test-noise"],
|
|
36953
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",
|
|
36954
36077
|
create: (context) => ({ CallExpression(node) {
|
|
36955
|
-
if (!
|
|
36956
|
-
allowGlobalReactNamespace: true,
|
|
36957
|
-
allowUnboundBareCalls: true,
|
|
36958
|
-
resolveConditionalAliases: true,
|
|
36959
|
-
resolveNamedAliases: true
|
|
36960
|
-
})) return;
|
|
36078
|
+
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
36961
36079
|
const analysis = getProgramAnalysis(node);
|
|
36962
36080
|
if (!analysis) return;
|
|
36963
|
-
if (!collectEffectStateWriteFacts(analysis,
|
|
36081
|
+
if (!collectEffectStateWriteFacts(analysis, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
|
|
36964
36082
|
context.report({
|
|
36965
36083
|
node,
|
|
36966
36084
|
message: "You pay an extra render for state you can derive from other values."
|
|
@@ -37436,20 +36554,9 @@ const noDidMountSetState = defineRule({
|
|
|
37436
36554
|
}
|
|
37437
36555
|
});
|
|
37438
36556
|
//#endregion
|
|
37439
|
-
//#region src/plugin/utils/find-enclosing-class.ts
|
|
37440
|
-
const findEnclosingClass = (node) => {
|
|
37441
|
-
let ancestor = node.parent;
|
|
37442
|
-
while (ancestor) {
|
|
37443
|
-
if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
|
|
37444
|
-
ancestor = ancestor.parent ?? null;
|
|
37445
|
-
}
|
|
37446
|
-
return null;
|
|
37447
|
-
};
|
|
37448
|
-
//#endregion
|
|
37449
36557
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
37450
36558
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
37451
36559
|
const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
37452
|
-
const DIFFERENCE_OPERATORS = new Set(["!=", "!=="]);
|
|
37453
36560
|
const EQUALITY_OPERATORS = new Set([
|
|
37454
36561
|
"==",
|
|
37455
36562
|
"===",
|
|
@@ -37461,8 +36568,6 @@ const FUNCTION_NODE_TYPES = new Set([
|
|
|
37461
36568
|
"FunctionExpression",
|
|
37462
36569
|
"ArrowFunctionExpression"
|
|
37463
36570
|
]);
|
|
37464
|
-
const CLASS_NODE_TYPES = new Set(["ClassDeclaration", "ClassExpression"]);
|
|
37465
|
-
const callbackRefFieldNamesByClass = /* @__PURE__ */ new WeakMap();
|
|
37466
36571
|
const isLifecycleMethodFunction = (node) => {
|
|
37467
36572
|
if (!FUNCTION_NODE_TYPES.has(node.type)) return false;
|
|
37468
36573
|
const parent = node.parent;
|
|
@@ -37518,187 +36623,6 @@ const getStaticMemberName = (node) => {
|
|
|
37518
36623
|
if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
|
|
37519
36624
|
return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
|
|
37520
36625
|
};
|
|
37521
|
-
const getMemberIdentity = (property) => {
|
|
37522
|
-
const propertyName = getPropertyKeyName$2(property);
|
|
37523
|
-
if (propertyName !== void 0) return isNodeOfType(property, "PrivateIdentifier") ? `#${propertyName}` : propertyName;
|
|
37524
|
-
return isNodeOfType(property, "Literal") && typeof property.value === "string" ? property.value : null;
|
|
37525
|
-
};
|
|
37526
|
-
const collectPreviousSourcePaths = (pattern, domain, members, previousSourcePaths) => {
|
|
37527
|
-
if (!pattern) return;
|
|
37528
|
-
const unwrappedPattern = stripParenExpression(pattern);
|
|
37529
|
-
if (isNodeOfType(unwrappedPattern, "Identifier")) {
|
|
37530
|
-
previousSourcePaths.set(unwrappedPattern.name, {
|
|
37531
|
-
domain,
|
|
37532
|
-
members: [...members],
|
|
37533
|
-
source: "previous"
|
|
37534
|
-
});
|
|
37535
|
-
return;
|
|
37536
|
-
}
|
|
37537
|
-
if (isNodeOfType(unwrappedPattern, "AssignmentPattern")) {
|
|
37538
|
-
collectPreviousSourcePaths(unwrappedPattern.left, domain, members, previousSourcePaths);
|
|
37539
|
-
return;
|
|
37540
|
-
}
|
|
37541
|
-
if (!isNodeOfType(unwrappedPattern, "ObjectPattern")) return;
|
|
37542
|
-
for (const property of unwrappedPattern.properties) {
|
|
37543
|
-
if (!isNodeOfType(property, "Property")) continue;
|
|
37544
|
-
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
37545
|
-
if (!propertyName) continue;
|
|
37546
|
-
collectPreviousSourcePaths(property.value, domain, [...members, propertyName], previousSourcePaths);
|
|
37547
|
-
}
|
|
37548
|
-
};
|
|
37549
|
-
const getStateSourcePath = (node, previousSourcePaths) => {
|
|
37550
|
-
let currentNode = stripParenExpression(node);
|
|
37551
|
-
const members = [];
|
|
37552
|
-
while (isNodeOfType(currentNode, "MemberExpression")) {
|
|
37553
|
-
const memberName = getStaticMemberName(currentNode);
|
|
37554
|
-
if (!memberName) return null;
|
|
37555
|
-
members.unshift(memberName);
|
|
37556
|
-
currentNode = stripParenExpression(currentNode.object);
|
|
37557
|
-
}
|
|
37558
|
-
if (isNodeOfType(currentNode, "ThisExpression")) {
|
|
37559
|
-
const [domain, ...pathMembers] = members;
|
|
37560
|
-
if (domain !== "props" && domain !== "state") return null;
|
|
37561
|
-
return {
|
|
37562
|
-
domain,
|
|
37563
|
-
members: pathMembers,
|
|
37564
|
-
source: "current"
|
|
37565
|
-
};
|
|
37566
|
-
}
|
|
37567
|
-
if (!isNodeOfType(currentNode, "Identifier")) return null;
|
|
37568
|
-
const previousSourcePath = previousSourcePaths.get(currentNode.name);
|
|
37569
|
-
return previousSourcePath ? {
|
|
37570
|
-
...previousSourcePath,
|
|
37571
|
-
members: [...previousSourcePath.members, ...members]
|
|
37572
|
-
} : null;
|
|
37573
|
-
};
|
|
37574
|
-
const haveMatchingStateSourcePaths = (left, right) => left.domain === right.domain && left.members.length === right.members.length && left.members.every((member, index) => member === right.members[index]);
|
|
37575
|
-
const collectConjunctiveStateSourceComparisons = (test, previousSourcePaths, comparisons) => {
|
|
37576
|
-
const expression = stripParenExpression(test);
|
|
37577
|
-
if (isNodeOfType(expression, "LogicalExpression") && expression.operator === "&&") {
|
|
37578
|
-
collectConjunctiveStateSourceComparisons(expression.left, previousSourcePaths, comparisons);
|
|
37579
|
-
collectConjunctiveStateSourceComparisons(expression.right, previousSourcePaths, comparisons);
|
|
37580
|
-
return;
|
|
37581
|
-
}
|
|
37582
|
-
if (!isNodeOfType(expression, "BinaryExpression") || !EQUALITY_OPERATORS.has(expression.operator)) return;
|
|
37583
|
-
const leftPath = getStateSourcePath(expression.left, previousSourcePaths);
|
|
37584
|
-
const rightPath = getStateSourcePath(expression.right, previousSourcePaths);
|
|
37585
|
-
if (Boolean(leftPath) === Boolean(rightPath)) return;
|
|
37586
|
-
const path = leftPath ?? rightPath;
|
|
37587
|
-
if (!path) return;
|
|
37588
|
-
comparisons.push({
|
|
37589
|
-
comparedValue: leftPath ? expression.right : expression.left,
|
|
37590
|
-
isDifference: DIFFERENCE_OPERATORS.has(expression.operator),
|
|
37591
|
-
path
|
|
37592
|
-
});
|
|
37593
|
-
};
|
|
37594
|
-
const isHistoricalToCurrentTransitionGuard = (test, previousSourcePaths) => {
|
|
37595
|
-
const expression = stripParenExpression(test);
|
|
37596
|
-
if (isNodeOfType(expression, "LogicalExpression") && expression.operator === "||") return isHistoricalToCurrentTransitionGuard(expression.left, previousSourcePaths) && isHistoricalToCurrentTransitionGuard(expression.right, previousSourcePaths);
|
|
37597
|
-
const comparisons = [];
|
|
37598
|
-
collectConjunctiveStateSourceComparisons(expression, previousSourcePaths, comparisons);
|
|
37599
|
-
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)));
|
|
37600
|
-
};
|
|
37601
|
-
const getThisFieldName = (node) => {
|
|
37602
|
-
const unwrappedNode = stripParenExpression(node);
|
|
37603
|
-
if (!isNodeOfType(unwrappedNode, "MemberExpression") || unwrappedNode.computed === true || !isNodeOfType(stripParenExpression(unwrappedNode.object), "ThisExpression")) return null;
|
|
37604
|
-
return getMemberIdentity(unwrappedNode.property);
|
|
37605
|
-
};
|
|
37606
|
-
const isUndefinedIdentifier = (node) => {
|
|
37607
|
-
const unwrappedNode = stripParenExpression(node);
|
|
37608
|
-
return isNodeOfType(unwrappedNode, "Identifier") && unwrappedNode.name === "undefined";
|
|
37609
|
-
};
|
|
37610
|
-
const isDirectRefParameterValue = (node, parameterSymbolId, scopes) => {
|
|
37611
|
-
const unwrappedNode = stripParenExpression(node);
|
|
37612
|
-
if (isNodeOfType(unwrappedNode, "Identifier")) return scopes.symbolFor(unwrappedNode)?.id === parameterSymbolId;
|
|
37613
|
-
if (!isNodeOfType(unwrappedNode, "LogicalExpression") || unwrappedNode.operator !== "??") return false;
|
|
37614
|
-
const left = stripParenExpression(unwrappedNode.left);
|
|
37615
|
-
return isNodeOfType(left, "Identifier") && scopes.symbolFor(left)?.id === parameterSymbolId && isUndefinedIdentifier(unwrappedNode.right);
|
|
37616
|
-
};
|
|
37617
|
-
const getCallbackRefAssignedFields = (callback, scopes) => {
|
|
37618
|
-
const firstParameter = (callback.params ?? [])[0];
|
|
37619
|
-
if (!firstParameter) return /* @__PURE__ */ new Set();
|
|
37620
|
-
const parameterIdentifier = isNodeOfType(firstParameter, "AssignmentPattern") ? firstParameter.left : firstParameter;
|
|
37621
|
-
if (!isNodeOfType(parameterIdentifier, "Identifier")) return /* @__PURE__ */ new Set();
|
|
37622
|
-
const parameterSymbolId = scopes.symbolFor(parameterIdentifier)?.id;
|
|
37623
|
-
if (parameterSymbolId === void 0) return /* @__PURE__ */ new Set();
|
|
37624
|
-
const body = callback.body;
|
|
37625
|
-
if (!body) return /* @__PURE__ */ new Set();
|
|
37626
|
-
const assignedFieldNames = /* @__PURE__ */ new Set();
|
|
37627
|
-
walkAst(body, (node) => {
|
|
37628
|
-
if (node !== body && (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node) || CLASS_NODE_TYPES.has(node.type))) return false;
|
|
37629
|
-
const assignmentTarget = isNodeOfType(node, "AssignmentExpression") && node.left || isNodeOfType(node, "UpdateExpression") && node.argument || isNodeOfType(node, "UnaryExpression") && node.operator === "delete" && node.argument || null;
|
|
37630
|
-
if (!assignmentTarget) return;
|
|
37631
|
-
const fieldName = getThisFieldName(assignmentTarget);
|
|
37632
|
-
if (!fieldName) return;
|
|
37633
|
-
if (isNodeOfType(node, "AssignmentExpression") && node.operator === "=" && isDirectRefParameterValue(node.right, parameterSymbolId, scopes)) {
|
|
37634
|
-
assignedFieldNames.add(fieldName);
|
|
37635
|
-
return;
|
|
37636
|
-
}
|
|
37637
|
-
assignedFieldNames.delete(fieldName);
|
|
37638
|
-
});
|
|
37639
|
-
return assignedFieldNames;
|
|
37640
|
-
};
|
|
37641
|
-
const getClassMemberCallback = (classNode, memberName) => {
|
|
37642
|
-
const classBody = classNode.body?.body ?? [];
|
|
37643
|
-
for (const member of classBody) {
|
|
37644
|
-
if (!isNodeOfType(member, "MethodDefinition") && !isNodeOfType(member, "PropertyDefinition")) continue;
|
|
37645
|
-
if (member.static === true) continue;
|
|
37646
|
-
const key = member.key;
|
|
37647
|
-
if (getMemberIdentity(key) !== memberName) continue;
|
|
37648
|
-
const value = member.value;
|
|
37649
|
-
return value && FUNCTION_NODE_TYPES.has(value.type) ? value : null;
|
|
37650
|
-
}
|
|
37651
|
-
return null;
|
|
37652
|
-
};
|
|
37653
|
-
const collectCallbackRefFieldsFromExpression = (expression, classNode, fieldNames, scopes) => {
|
|
37654
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
37655
|
-
if (FUNCTION_NODE_TYPES.has(unwrappedExpression.type)) {
|
|
37656
|
-
for (const fieldName of getCallbackRefAssignedFields(unwrappedExpression, scopes)) fieldNames.add(fieldName);
|
|
37657
|
-
return;
|
|
37658
|
-
}
|
|
37659
|
-
const handlerName = getThisFieldName(unwrappedExpression);
|
|
37660
|
-
if (handlerName) {
|
|
37661
|
-
const callback = getClassMemberCallback(classNode, handlerName);
|
|
37662
|
-
if (callback) for (const fieldName of getCallbackRefAssignedFields(callback, scopes)) fieldNames.add(fieldName);
|
|
37663
|
-
return;
|
|
37664
|
-
}
|
|
37665
|
-
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
|
|
37666
|
-
collectCallbackRefFieldsFromExpression(unwrappedExpression.consequent, classNode, fieldNames, scopes);
|
|
37667
|
-
collectCallbackRefFieldsFromExpression(unwrappedExpression.alternate, classNode, fieldNames, scopes);
|
|
37668
|
-
return;
|
|
37669
|
-
}
|
|
37670
|
-
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
37671
|
-
if (unwrappedExpression.operator !== "&&") collectCallbackRefFieldsFromExpression(unwrappedExpression.left, classNode, fieldNames, scopes);
|
|
37672
|
-
collectCallbackRefFieldsFromExpression(unwrappedExpression.right, classNode, fieldNames, scopes);
|
|
37673
|
-
}
|
|
37674
|
-
};
|
|
37675
|
-
const getCallbackRefFieldNames = (classNode, scopes) => {
|
|
37676
|
-
if (!classNode) return /* @__PURE__ */ new Set();
|
|
37677
|
-
const cachedFieldNames = callbackRefFieldNamesByClass.get(classNode);
|
|
37678
|
-
if (cachedFieldNames) return cachedFieldNames;
|
|
37679
|
-
const fieldNames = /* @__PURE__ */ new Set();
|
|
37680
|
-
const classBody = classNode.body;
|
|
37681
|
-
if (classBody) walkAst(classBody, (node) => {
|
|
37682
|
-
if (node !== classBody && CLASS_NODE_TYPES.has(node.type)) return false;
|
|
37683
|
-
if (!isNodeOfType(node, "JSXAttribute") || !isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "ref" || !node.value || !isNodeOfType(node.value, "JSXExpressionContainer") || !node.value.expression) return;
|
|
37684
|
-
collectCallbackRefFieldsFromExpression(node.value.expression, classNode, fieldNames, scopes);
|
|
37685
|
-
});
|
|
37686
|
-
callbackRefFieldNamesByClass.set(classNode, fieldNames);
|
|
37687
|
-
return fieldNames;
|
|
37688
|
-
};
|
|
37689
|
-
const collectLifecycleWrittenFieldNames = (lifecycleFunction) => {
|
|
37690
|
-
const fieldNames = /* @__PURE__ */ new Set();
|
|
37691
|
-
const body = lifecycleFunction.body;
|
|
37692
|
-
if (!body) return fieldNames;
|
|
37693
|
-
walkAst(body, (node) => {
|
|
37694
|
-
if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
|
|
37695
|
-
const target = isNodeOfType(node, "AssignmentExpression") && node.left || isNodeOfType(node, "UpdateExpression") && node.argument || null;
|
|
37696
|
-
if (!target) return;
|
|
37697
|
-
const fieldName = getThisFieldName(target);
|
|
37698
|
-
if (fieldName) fieldNames.add(fieldName);
|
|
37699
|
-
});
|
|
37700
|
-
return fieldNames;
|
|
37701
|
-
};
|
|
37702
36626
|
const getThisStateFieldName = (node) => {
|
|
37703
36627
|
const unwrappedNode = stripParenExpression(node);
|
|
37704
36628
|
if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
|
|
@@ -37716,17 +36640,15 @@ const collectLocalInitializers = (lifecycleFunction) => {
|
|
|
37716
36640
|
});
|
|
37717
36641
|
return initializers;
|
|
37718
36642
|
};
|
|
37719
|
-
const derivesFromPostMountValue = (node, localInitializers,
|
|
36643
|
+
const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
|
|
37720
36644
|
if (readsPostMountValue(node)) return true;
|
|
37721
|
-
const fieldName = getThisFieldName(node);
|
|
37722
|
-
if (fieldName && callbackRefFieldNames.has(fieldName)) return true;
|
|
37723
36645
|
const referencedNames = /* @__PURE__ */ new Set();
|
|
37724
36646
|
collectReferenceIdentifierNames(node, referencedNames);
|
|
37725
36647
|
for (const referencedName of referencedNames) {
|
|
37726
36648
|
if (visitedNames.has(referencedName)) continue;
|
|
37727
36649
|
const initializer = localInitializers.get(referencedName);
|
|
37728
36650
|
if (!initializer) continue;
|
|
37729
|
-
if (derivesFromPostMountValue(initializer, localInitializers,
|
|
36651
|
+
if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
|
|
37730
36652
|
}
|
|
37731
36653
|
return false;
|
|
37732
36654
|
};
|
|
@@ -37740,84 +36662,50 @@ const getSetStateFieldValue = (setStateCall, fieldName) => {
|
|
|
37740
36662
|
}
|
|
37741
36663
|
return null;
|
|
37742
36664
|
};
|
|
37743
|
-
const isConvergentPostMountGuard = (test, setStateCall, localInitializers
|
|
37744
|
-
|
|
37745
|
-
|
|
37746
|
-
if (
|
|
37747
|
-
|
|
37748
|
-
const
|
|
37749
|
-
|
|
37750
|
-
|
|
37751
|
-
|
|
37752
|
-
|
|
37753
|
-
|
|
37754
|
-
|
|
37755
|
-
|
|
37756
|
-
|
|
37757
|
-
|
|
37758
|
-
|
|
37759
|
-
return
|
|
37760
|
-
};
|
|
37761
|
-
const
|
|
37762
|
-
|
|
37763
|
-
|
|
37764
|
-
|
|
37765
|
-
|
|
37766
|
-
|
|
37767
|
-
|
|
37768
|
-
|
|
37769
|
-
|
|
37770
|
-
|
|
37771
|
-
|
|
37772
|
-
|
|
37773
|
-
|
|
37774
|
-
}
|
|
37775
|
-
return false;
|
|
37776
|
-
};
|
|
37777
|
-
const isDiffGuardTest = (test, paramNames, derivedNames, isTruthfulBranch) => {
|
|
37778
|
-
const expression = stripParenExpression(test);
|
|
37779
|
-
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
37780
|
-
if (expression.operator !== "&&" && expression.operator !== "||") return false;
|
|
37781
|
-
const leftIsDiffGuard = isDiffGuardTest(expression.left, paramNames, derivedNames, isTruthfulBranch);
|
|
37782
|
-
const rightIsDiffGuard = isDiffGuardTest(expression.right, paramNames, derivedNames, isTruthfulBranch);
|
|
37783
|
-
return isTruthfulBranch && expression.operator === "||" || !isTruthfulBranch && expression.operator === "&&" ? leftIsDiffGuard && rightIsDiffGuard : leftIsDiffGuard || rightIsDiffGuard;
|
|
37784
|
-
}
|
|
37785
|
-
if (!isNodeOfType(expression, "BinaryExpression") || !(isTruthfulBranch ? DIFFERENCE_OPERATORS.has(expression.operator) : EQUALITY_OPERATORS.has(expression.operator) && !DIFFERENCE_OPERATORS.has(expression.operator))) return false;
|
|
37786
|
-
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;
|
|
37787
36696
|
};
|
|
37788
|
-
const isInsideDiffGuard = (setStateCall
|
|
36697
|
+
const isInsideDiffGuard = (setStateCall) => {
|
|
37789
36698
|
const lifecycleFunction = findEnclosingLifecycleFunction(setStateCall);
|
|
37790
36699
|
if (!lifecycleFunction) return false;
|
|
37791
36700
|
const paramNames = /* @__PURE__ */ new Set();
|
|
37792
|
-
const
|
|
37793
|
-
for (const param of parameters) collectPatternNames(param, paramNames);
|
|
37794
|
-
const previousSourcePaths = /* @__PURE__ */ new Map();
|
|
37795
|
-
const [previousPropsParameter, previousStateParameter] = parameters;
|
|
37796
|
-
collectPreviousSourcePaths(previousPropsParameter, "props", [], previousSourcePaths);
|
|
37797
|
-
collectPreviousSourcePaths(previousStateParameter, "state", [], previousSourcePaths);
|
|
36701
|
+
for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
|
|
37798
36702
|
const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
|
|
37799
36703
|
const localInitializers = collectLocalInitializers(lifecycleFunction);
|
|
37800
|
-
const lifecycleWrittenFieldNames = collectLifecycleWrittenFieldNames(lifecycleFunction);
|
|
37801
|
-
const callbackRefFieldNames = new Set([...getCallbackRefFieldNames(findEnclosingClass(lifecycleFunction), scopes)].filter((fieldName) => !lifecycleWrittenFieldNames.has(fieldName)));
|
|
37802
36704
|
let child = setStateCall;
|
|
37803
36705
|
let ancestor = setStateCall.parent;
|
|
37804
36706
|
while (ancestor && ancestor !== lifecycleFunction) {
|
|
37805
|
-
|
|
37806
|
-
|
|
37807
|
-
if (isNodeOfType(ancestor, "IfStatement")) {
|
|
37808
|
-
if (child === ancestor.consequent) guardTest = ancestor.test;
|
|
37809
|
-
else if (child === ancestor.alternate) {
|
|
37810
|
-
guardTest = ancestor.test;
|
|
37811
|
-
isTruthfulBranch = false;
|
|
37812
|
-
}
|
|
37813
|
-
} else if (isNodeOfType(ancestor, "ConditionalExpression")) {
|
|
37814
|
-
if (child === ancestor.consequent) guardTest = ancestor.test;
|
|
37815
|
-
else if (child === ancestor.alternate) {
|
|
37816
|
-
guardTest = ancestor.test;
|
|
37817
|
-
isTruthfulBranch = false;
|
|
37818
|
-
}
|
|
37819
|
-
} else if (isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right) guardTest = ancestor.left;
|
|
37820
|
-
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;
|
|
37821
36709
|
child = ancestor;
|
|
37822
36710
|
ancestor = ancestor.parent ?? null;
|
|
37823
36711
|
}
|
|
@@ -37839,7 +36727,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
37839
36727
|
if (!isNodeOfType(stripParenExpression(node.callee.object), "ThisExpression")) return;
|
|
37840
36728
|
if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
|
|
37841
36729
|
if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
37842
|
-
if (isInsideDiffGuard(node
|
|
36730
|
+
if (isInsideDiffGuard(node)) return;
|
|
37843
36731
|
context.report({
|
|
37844
36732
|
node: node.callee,
|
|
37845
36733
|
message: MESSAGE$27
|
|
@@ -37909,7 +36797,7 @@ const noDirectMutationState = defineRule({
|
|
|
37909
36797
|
const isSetterIdentifier = (name) => SETTER_PATTERN.test(name);
|
|
37910
36798
|
//#endregion
|
|
37911
36799
|
//#region src/plugin/rules/state-and-effects/utils/collect-use-state-bindings.ts
|
|
37912
|
-
const collectUseStateBindings = (componentBody
|
|
36800
|
+
const collectUseStateBindings = (componentBody) => {
|
|
37913
36801
|
const bindings = [];
|
|
37914
36802
|
if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
|
|
37915
36803
|
for (const statement of componentBody.body ?? []) {
|
|
@@ -37922,12 +36810,7 @@ const collectUseStateBindings = (componentBody, scopes) => {
|
|
|
37922
36810
|
const setterElement = elements[1];
|
|
37923
36811
|
if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
|
|
37924
36812
|
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
37925
|
-
if (!(
|
|
37926
|
-
allowGlobalReactNamespace: true,
|
|
37927
|
-
allowUnboundBareCalls: true,
|
|
37928
|
-
resolveConditionalAliases: true,
|
|
37929
|
-
resolveNamedAliases: true
|
|
37930
|
-
}) : isHookCall$2(declarator.init, "useState"))) continue;
|
|
36813
|
+
if (!isHookCall$2(declarator.init, "useState")) continue;
|
|
37931
36814
|
bindings.push({
|
|
37932
36815
|
valueName: valueElement.name,
|
|
37933
36816
|
setterName: setterElement.name,
|
|
@@ -38654,36 +37537,25 @@ const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSub
|
|
|
38654
37537
|
};
|
|
38655
37538
|
//#endregion
|
|
38656
37539
|
//#region src/plugin/rules/state-and-effects/no-effect-chain.ts
|
|
38657
|
-
const findTopLevelEffectCalls = (componentBody
|
|
37540
|
+
const findTopLevelEffectCalls = (componentBody) => {
|
|
38658
37541
|
const effectCalls = [];
|
|
38659
37542
|
if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
|
|
38660
37543
|
for (const statement of componentBody.body ?? []) {
|
|
38661
37544
|
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
38662
37545
|
const expression = unwrapDiscardedExpression(statement);
|
|
38663
37546
|
if (!isNodeOfType(expression, "CallExpression")) continue;
|
|
38664
|
-
if (!
|
|
38665
|
-
allowGlobalReactNamespace: true,
|
|
38666
|
-
allowUnboundBareCalls: true,
|
|
38667
|
-
resolveConditionalAliases: true,
|
|
38668
|
-
resolveNamedAliases: true
|
|
38669
|
-
})) continue;
|
|
37547
|
+
if (!isHookCall$2(expression, EFFECT_HOOK_NAMES$1)) continue;
|
|
38670
37548
|
effectCalls.push(expression);
|
|
38671
37549
|
}
|
|
38672
37550
|
return effectCalls;
|
|
38673
37551
|
};
|
|
38674
|
-
const
|
|
38675
|
-
const
|
|
38676
|
-
if (!isNodeOfType(effectNode, "CallExpression")) return
|
|
37552
|
+
const collectDepIdentifierNames = (effectNode) => {
|
|
37553
|
+
const depNames = /* @__PURE__ */ new Set();
|
|
37554
|
+
if (!isNodeOfType(effectNode, "CallExpression")) return depNames;
|
|
38677
37555
|
const depsNode = effectNode.arguments?.[1];
|
|
38678
|
-
if (!isNodeOfType(depsNode, "ArrayExpression")) return
|
|
38679
|
-
for (const element of depsNode.elements ?? [])
|
|
38680
|
-
|
|
38681
|
-
const rootIdentifier = getRootIdentifier$1(element);
|
|
38682
|
-
if (!isNodeOfType(rootIdentifier, "Identifier")) continue;
|
|
38683
|
-
const symbol = resolveConstIdentifierAlias(rootIdentifier, scopes, true);
|
|
38684
|
-
if (symbol && stateSymbolIds.has(symbol.id)) dependencyStateSymbolIds.add(symbol.id);
|
|
38685
|
-
}
|
|
38686
|
-
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;
|
|
38687
37559
|
};
|
|
38688
37560
|
const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
|
|
38689
37561
|
const analysisFunctions = new Set([effectCallback]);
|
|
@@ -38789,13 +37661,12 @@ const readStaticSetterValue = (setterCall, scopes) => {
|
|
|
38789
37661
|
if (updater) return readStaticUpdaterReturnValue(updater, scopes);
|
|
38790
37662
|
return readStaticEffectValue(argument, scopes, null, null);
|
|
38791
37663
|
};
|
|
38792
|
-
const collectStateWritesInEffect = (analysisFunctions,
|
|
37664
|
+
const collectStateWritesInEffect = (analysisFunctions, setterToStateName, scopes) => {
|
|
38793
37665
|
const stateWrites = /* @__PURE__ */ new Map();
|
|
38794
37666
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
38795
37667
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
38796
37668
|
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
38797
|
-
const
|
|
38798
|
-
const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
|
|
37669
|
+
const stateName = setterToStateName.get(child.callee.name);
|
|
38799
37670
|
if (!stateName) return;
|
|
38800
37671
|
const writeInfo = stateWrites.get(stateName) ?? {
|
|
38801
37672
|
values: /* @__PURE__ */ new Set(),
|
|
@@ -38881,12 +37752,11 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
|
|
|
38881
37752
|
"keys",
|
|
38882
37753
|
"values"
|
|
38883
37754
|
]);
|
|
38884
|
-
const isFunctionShapedReturn = (returnedValue, setterToStateName,
|
|
37755
|
+
const isFunctionShapedReturn = (returnedValue, setterToStateName, isExplicitReturnStatement) => {
|
|
38885
37756
|
if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
|
|
38886
37757
|
if (isNodeOfType(returnedValue, "CallExpression")) {
|
|
38887
37758
|
if (isNodeOfType(returnedValue.callee, "Identifier")) {
|
|
38888
|
-
|
|
38889
|
-
if (setterToStateName.has(returnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
|
|
37759
|
+
if (setterToStateName.has(returnedValue.callee.name)) return false;
|
|
38890
37760
|
if (isSetterIdentifier(returnedValue.callee.name)) return true;
|
|
38891
37761
|
}
|
|
38892
37762
|
return isCleanupReturn(returnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
|
|
@@ -39053,11 +37923,11 @@ const isExternalSyncNode = (node) => {
|
|
|
39053
37923
|
const receiverRootName = getRootIdentifierName(node.callee.object);
|
|
39054
37924
|
return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
|
|
39055
37925
|
};
|
|
39056
|
-
const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName,
|
|
37926
|
+
const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, scopes, allowCommittedDomSync) => {
|
|
39057
37927
|
if (!isFunctionLike$1(effectCallback)) return false;
|
|
39058
37928
|
if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
|
|
39059
|
-
if (isFunctionShapedReturn(effectCallback.body, setterToStateName,
|
|
39060
|
-
} 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;
|
|
39061
37931
|
let didFindExternalCall = false;
|
|
39062
37932
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
39063
37933
|
if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
|
|
@@ -39073,11 +37943,10 @@ const noEffectChain = defineRule({
|
|
|
39073
37943
|
create: (context) => {
|
|
39074
37944
|
const checkComponent = (componentBody) => {
|
|
39075
37945
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
39076
|
-
const useStateBindings = collectUseStateBindings(componentBody
|
|
37946
|
+
const useStateBindings = collectUseStateBindings(componentBody);
|
|
39077
37947
|
if (useStateBindings.length === 0) return;
|
|
39078
37948
|
const setterToStateName = /* @__PURE__ */ new Map();
|
|
39079
37949
|
const stateSymbolIds = /* @__PURE__ */ new Map();
|
|
39080
|
-
const setterSymbolIdToStateName = /* @__PURE__ */ new Map();
|
|
39081
37950
|
for (const binding of useStateBindings) {
|
|
39082
37951
|
setterToStateName.set(binding.setterName, binding.valueName);
|
|
39083
37952
|
if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
|
|
@@ -39086,27 +37955,21 @@ const noEffectChain = defineRule({
|
|
|
39086
37955
|
const stateSymbol = context.scopes.symbolFor(stateIdentifier);
|
|
39087
37956
|
if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
|
|
39088
37957
|
}
|
|
39089
|
-
const setterIdentifier = binding.declarator.id.elements[1];
|
|
39090
|
-
if (isNodeOfType(setterIdentifier, "Identifier")) {
|
|
39091
|
-
const setterSymbol = context.scopes.symbolFor(setterIdentifier);
|
|
39092
|
-
if (setterSymbol) setterSymbolIdToStateName.set(setterSymbol.id, binding.valueName);
|
|
39093
|
-
}
|
|
39094
37958
|
}
|
|
39095
37959
|
const storageSetterNames = collectStorageHookSetterNames(componentBody);
|
|
39096
|
-
const stateSymbolIdSet = new Set(stateSymbolIds.values());
|
|
39097
37960
|
const effectInfos = [];
|
|
39098
|
-
for (const effectCall of findTopLevelEffectCalls(componentBody
|
|
37961
|
+
for (const effectCall of findTopLevelEffectCalls(componentBody)) {
|
|
39099
37962
|
const callback = getEffectCallback(effectCall, context.scopes);
|
|
39100
37963
|
if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
|
|
39101
37964
|
const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
|
|
39102
|
-
const stateWrites = collectStateWritesInEffect(analysisFunctions,
|
|
37965
|
+
const stateWrites = collectStateWritesInEffect(analysisFunctions, setterToStateName, context.scopes);
|
|
39103
37966
|
const writtenStateNames = new Set(stateWrites.keys());
|
|
39104
37967
|
effectInfos.push({
|
|
39105
37968
|
node: effectCall,
|
|
39106
|
-
|
|
37969
|
+
depNames: collectDepIdentifierNames(effectCall),
|
|
39107
37970
|
stateWrites,
|
|
39108
37971
|
analysisFunctions,
|
|
39109
|
-
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)
|
|
39110
37973
|
});
|
|
39111
37974
|
}
|
|
39112
37975
|
if (effectInfos.length < 2) return;
|
|
@@ -39117,11 +37980,10 @@ const noEffectChain = defineRule({
|
|
|
39117
37980
|
for (const readerEffect of effectInfos) {
|
|
39118
37981
|
if (readerEffect === writerEffect) continue;
|
|
39119
37982
|
if (readerEffect.isExternalSync) continue;
|
|
39120
|
-
if (readerEffect.
|
|
37983
|
+
if (readerEffect.depNames.size === 0) continue;
|
|
39121
37984
|
let chainedStateName = null;
|
|
39122
37985
|
for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
|
|
39123
|
-
|
|
39124
|
-
if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
|
|
37986
|
+
if (!readerEffect.depNames.has(writtenName)) continue;
|
|
39125
37987
|
if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
|
|
39126
37988
|
chainedStateName = writtenName;
|
|
39127
37989
|
break;
|
|
@@ -40937,17 +39799,6 @@ const DOM_MEASUREMENT_NAMES = new Set([
|
|
|
40937
39799
|
"scrollHeight"
|
|
40938
39800
|
]);
|
|
40939
39801
|
const MEASUREMENT_HELPER_CALLEE_PATTERN = /^(?:get|measure|read)\w*(?:Width|Height|Rect|Rects|Size|Bounds|Position)$/;
|
|
40940
|
-
const IMPERATIVE_DOM_MUTATION_NAMES = new Set([
|
|
40941
|
-
"blur",
|
|
40942
|
-
"focus",
|
|
40943
|
-
"restoreSelection",
|
|
40944
|
-
"scroll",
|
|
40945
|
-
"scrollBy",
|
|
40946
|
-
"scrollIntoView",
|
|
40947
|
-
"scrollTo",
|
|
40948
|
-
"setRangeText",
|
|
40949
|
-
"setSelectionRange"
|
|
40950
|
-
]);
|
|
40951
39802
|
const subtreeReadsDomMeasurement = (root) => {
|
|
40952
39803
|
if (!root) return false;
|
|
40953
39804
|
let found = false;
|
|
@@ -40966,66 +39817,29 @@ const subtreeReadsDomMeasurement = (root) => {
|
|
|
40966
39817
|
});
|
|
40967
39818
|
return found;
|
|
40968
39819
|
};
|
|
40969
|
-
const
|
|
39820
|
+
const collectMeasuringFunctionNames = (program) => {
|
|
40970
39821
|
const names = /* @__PURE__ */ new Set();
|
|
40971
39822
|
walkAst(program, (child) => {
|
|
40972
39823
|
if (isNodeOfType(child, "FunctionDeclaration")) {
|
|
40973
|
-
if (child.id && isNodeOfType(child.id, "Identifier") &&
|
|
39824
|
+
if (child.id && isNodeOfType(child.id, "Identifier") && subtreeReadsDomMeasurement(child.body)) names.add(child.id.name);
|
|
40974
39825
|
return;
|
|
40975
39826
|
}
|
|
40976
39827
|
if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier")) return;
|
|
40977
39828
|
let functionValue = child.init;
|
|
40978
39829
|
if (functionValue && isNodeOfType(functionValue, "CallExpression") && isNodeOfType(functionValue.callee, "Identifier") && /^use[A-Z]/.test(functionValue.callee.name)) functionValue = functionValue.arguments?.[0];
|
|
40979
|
-
if (functionValue && isFunctionLike$1(functionValue) &&
|
|
39830
|
+
if (functionValue && isFunctionLike$1(functionValue) && subtreeReadsDomMeasurement(functionValue.body)) names.add(child.id.name);
|
|
40980
39831
|
});
|
|
40981
39832
|
return names;
|
|
40982
39833
|
};
|
|
40983
|
-
const
|
|
40984
|
-
|
|
40985
|
-
if (!root || isFunctionLike$1(root)) return false;
|
|
40986
|
-
let found = false;
|
|
40987
|
-
walkAst(root, (child) => {
|
|
40988
|
-
if (found) return false;
|
|
40989
|
-
if (child !== root && isFunctionLike$1(child)) return false;
|
|
40990
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
40991
|
-
const callee = stripParenExpression(child.callee);
|
|
40992
|
-
const propertyName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
|
|
40993
|
-
if (propertyName !== null && IMPERATIVE_DOM_MUTATION_NAMES.has(propertyName)) {
|
|
40994
|
-
found = true;
|
|
40995
|
-
return false;
|
|
40996
|
-
}
|
|
40997
|
-
});
|
|
40998
|
-
return found;
|
|
40999
|
-
};
|
|
41000
|
-
const collectImperativeDomFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeMutatesDomImperatively);
|
|
41001
|
-
const callsAnyName = (root, names, shouldSkipNestedFunctions = false) => {
|
|
41002
|
-
if (!root || names.size === 0 || shouldSkipNestedFunctions && isFunctionLike$1(root)) return false;
|
|
39834
|
+
const callsAnyName = (root, names) => {
|
|
39835
|
+
if (!root || names.size === 0) return false;
|
|
41003
39836
|
let found = false;
|
|
41004
39837
|
walkAst(root, (child) => {
|
|
41005
39838
|
if (found) return false;
|
|
41006
|
-
if (shouldSkipNestedFunctions && child !== root && isFunctionLike$1(child)) return false;
|
|
41007
39839
|
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && names.has(child.callee.name)) found = true;
|
|
41008
39840
|
});
|
|
41009
39841
|
return found;
|
|
41010
39842
|
};
|
|
41011
|
-
const isFollowedByImperativeDomMutation = (call, imperativeDomFunctionNames) => {
|
|
41012
|
-
let statement = call;
|
|
41013
|
-
let parent = statement.parent;
|
|
41014
|
-
while (parent) {
|
|
41015
|
-
const statements = isNodeOfType(parent, "BlockStatement") || isNodeOfType(parent, "Program") || isNodeOfType(parent, "StaticBlock") ? parent.body : isNodeOfType(parent, "SwitchCase") ? parent.consequent : null;
|
|
41016
|
-
if (statements) {
|
|
41017
|
-
const statementIndex = statements.findIndex((siblingStatement) => siblingStatement === statement);
|
|
41018
|
-
if (statementIndex >= 0) {
|
|
41019
|
-
const nextStatement = statements[statementIndex + 1];
|
|
41020
|
-
return subtreeMutatesDomImperatively(nextStatement) || callsAnyName(nextStatement, imperativeDomFunctionNames, true);
|
|
41021
|
-
}
|
|
41022
|
-
}
|
|
41023
|
-
if (isFunctionLike$1(parent) || parent.type.endsWith("Statement") && !isNodeOfType(parent, "ExpressionStatement")) return false;
|
|
41024
|
-
statement = parent;
|
|
41025
|
-
parent = parent.parent;
|
|
41026
|
-
}
|
|
41027
|
-
return false;
|
|
41028
|
-
};
|
|
41029
39843
|
const isInsideStartViewTransition = (node) => {
|
|
41030
39844
|
let cursor = node.parent;
|
|
41031
39845
|
while (cursor) {
|
|
@@ -41066,12 +39880,11 @@ const importsImperativeDomLibrary = (program) => {
|
|
|
41066
39880
|
};
|
|
41067
39881
|
const hasExemptFlushSyncCall = (program, localName) => {
|
|
41068
39882
|
const measuringFunctionNames = collectMeasuringFunctionNames(program);
|
|
41069
|
-
const imperativeDomFunctionNames = collectImperativeDomFunctionNames(program);
|
|
41070
39883
|
let exempt = false;
|
|
41071
39884
|
walkAst(program, (child) => {
|
|
41072
39885
|
if (exempt) return false;
|
|
41073
39886
|
if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "Identifier") || child.callee.name !== localName) return;
|
|
41074
|
-
if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames)
|
|
39887
|
+
if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames)) {
|
|
41075
39888
|
exempt = true;
|
|
41076
39889
|
return false;
|
|
41077
39890
|
}
|
|
@@ -41636,223 +40449,41 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
|
|
|
41636
40449
|
if (leftResult === false && rightResult === false) return false;
|
|
41637
40450
|
return null;
|
|
41638
40451
|
};
|
|
41639
|
-
const readHydrationConditionResult = (expression, context, runtime
|
|
40452
|
+
const readHydrationConditionResult = (expression, context, runtime) => {
|
|
41640
40453
|
const unwrappedExpression = stripParenExpression(expression);
|
|
41641
40454
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
41642
40455
|
if (predicateMatch) return predicateMatch[`${runtime}Result`];
|
|
41643
40456
|
const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
|
|
41644
40457
|
if (staticResult !== null) return staticResult;
|
|
41645
|
-
const expressionSymbol = isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression) : null;
|
|
41646
|
-
const parameterValue = expressionSymbol ? state.parameterValuesBySymbolId.get(expressionSymbol.id) : null;
|
|
41647
|
-
if (expressionSymbol && parameterValue && !state.visitedSymbolIds.has(expressionSymbol.id)) {
|
|
41648
|
-
state.visitedSymbolIds.add(expressionSymbol.id);
|
|
41649
|
-
const result = readHydrationConditionResult(parameterValue, context, runtime, state);
|
|
41650
|
-
state.visitedSymbolIds.delete(expressionSymbol.id);
|
|
41651
|
-
return result;
|
|
41652
|
-
}
|
|
41653
|
-
if (expressionSymbol && expressionSymbol.kind === "const" && expressionSymbol.initializer && expressionSymbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(expressionSymbol.id)) {
|
|
41654
|
-
state.visitedSymbolIds.add(expressionSymbol.id);
|
|
41655
|
-
const result = readHydrationConditionResult(expressionSymbol.initializer, context, runtime, state);
|
|
41656
|
-
state.visitedSymbolIds.delete(expressionSymbol.id);
|
|
41657
|
-
return result;
|
|
41658
|
-
}
|
|
41659
|
-
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
41660
|
-
const callArguments = unwrappedExpression.arguments ?? [];
|
|
41661
|
-
if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
|
|
41662
|
-
allowGlobalReactNamespace: true,
|
|
41663
|
-
resolveNamedAliases: true
|
|
41664
|
-
})) {
|
|
41665
|
-
const callbackArgument = callArguments[0];
|
|
41666
|
-
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
|
|
41667
|
-
const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
41668
|
-
return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? readHydrationFunctionResult(callbackFunction, context, runtime, state) : null;
|
|
41669
|
-
}
|
|
41670
|
-
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
41671
|
-
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);
|
|
41672
|
-
const helperFunction = resolveExactLocalFunction(callee, context.scopes);
|
|
41673
|
-
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;
|
|
41674
|
-
const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
|
|
41675
|
-
for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
|
|
41676
|
-
const parameter = helperFunction.params[parameterIndex];
|
|
41677
|
-
const argument = callArguments[parameterIndex];
|
|
41678
|
-
if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
|
|
41679
|
-
const parameterSymbol = context.scopes.symbolFor(parameter);
|
|
41680
|
-
if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
|
|
41681
|
-
}
|
|
41682
|
-
return readHydrationFunctionResult(helperFunction, context, runtime, {
|
|
41683
|
-
...state,
|
|
41684
|
-
parameterValuesBySymbolId
|
|
41685
|
-
});
|
|
41686
|
-
}
|
|
41687
40458
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
|
|
41688
|
-
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime
|
|
40459
|
+
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
|
|
41689
40460
|
return argumentResult === null ? null : !argumentResult;
|
|
41690
40461
|
}
|
|
41691
40462
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
41692
|
-
return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime
|
|
41693
|
-
};
|
|
41694
|
-
const readHydrationStatementResult = (statement, context, runtime, state) => {
|
|
41695
|
-
if (isNodeOfType(statement, "ReturnStatement")) return {
|
|
41696
|
-
didReturn: true,
|
|
41697
|
-
value: statement.argument ? readHydrationConditionResult(statement.argument, context, runtime, state) : null
|
|
41698
|
-
};
|
|
41699
|
-
if (isNodeOfType(statement, "BlockStatement")) {
|
|
41700
|
-
for (const childStatement of statement.body) {
|
|
41701
|
-
const result = readHydrationStatementResult(childStatement, context, runtime, state);
|
|
41702
|
-
if (result.didReturn) return result;
|
|
41703
|
-
if (statementAlwaysExits(childStatement)) break;
|
|
41704
|
-
}
|
|
41705
|
-
return {
|
|
41706
|
-
didReturn: false,
|
|
41707
|
-
value: null
|
|
41708
|
-
};
|
|
41709
|
-
}
|
|
41710
|
-
if (!isNodeOfType(statement, "IfStatement")) return {
|
|
41711
|
-
didReturn: false,
|
|
41712
|
-
value: null
|
|
41713
|
-
};
|
|
41714
|
-
const conditionResult = readHydrationConditionResult(statement.test, context, runtime, state);
|
|
41715
|
-
if (conditionResult !== null) {
|
|
41716
|
-
const selectedBranch = conditionResult ? statement.consequent : statement.alternate;
|
|
41717
|
-
return selectedBranch ? readHydrationStatementResult(selectedBranch, context, runtime, state) : {
|
|
41718
|
-
didReturn: false,
|
|
41719
|
-
value: null
|
|
41720
|
-
};
|
|
41721
|
-
}
|
|
41722
|
-
const consequentResult = readHydrationStatementResult(statement.consequent, context, runtime, state);
|
|
41723
|
-
const alternateResult = statement.alternate ? readHydrationStatementResult(statement.alternate, context, runtime, state) : {
|
|
41724
|
-
didReturn: false,
|
|
41725
|
-
value: null
|
|
41726
|
-
};
|
|
41727
|
-
return consequentResult.didReturn && alternateResult.didReturn && consequentResult.value !== null && consequentResult.value === alternateResult.value ? consequentResult : {
|
|
41728
|
-
didReturn: consequentResult.didReturn || alternateResult.didReturn,
|
|
41729
|
-
value: null
|
|
41730
|
-
};
|
|
41731
|
-
};
|
|
41732
|
-
const readHydrationFunctionResult = (functionNode, context, runtime, state) => {
|
|
41733
|
-
if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
|
|
41734
|
-
state.visitedFunctionNodes.add(functionNode);
|
|
41735
|
-
const result = isNodeOfType(functionNode.body, "BlockStatement") ? readHydrationStatementResult(functionNode.body, context, runtime, state).value : readHydrationConditionResult(functionNode.body, context, runtime, state);
|
|
41736
|
-
state.visitedFunctionNodes.delete(functionNode);
|
|
41737
|
-
return result;
|
|
40463
|
+
return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
|
|
41738
40464
|
};
|
|
41739
|
-
const
|
|
41740
|
-
const left = stripParenExpression(leftExpression);
|
|
41741
|
-
const right = stripParenExpression(rightExpression);
|
|
41742
|
-
if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
|
|
41743
|
-
const leftSymbol = scopes.symbolFor(left);
|
|
41744
|
-
const rightSymbol = scopes.symbolFor(right);
|
|
41745
|
-
return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
|
|
41746
|
-
}
|
|
41747
|
-
if (isNodeOfType(left, "MemberExpression") && isNodeOfType(right, "MemberExpression")) return doEquivalentExpressionBindingsMatch(left.object, right.object, scopes) && (!left.computed || doEquivalentExpressionBindingsMatch(left.property, right.property, scopes));
|
|
41748
|
-
if (isNodeOfType(left, "CallExpression") && isNodeOfType(right, "CallExpression")) {
|
|
41749
|
-
const rightArguments = right.arguments ?? [];
|
|
41750
|
-
return doEquivalentExpressionBindingsMatch(left.callee, right.callee, scopes) && (left.arguments ?? []).every((argument, index) => {
|
|
41751
|
-
const rightArgument = rightArguments[index];
|
|
41752
|
-
return Boolean(rightArgument && doEquivalentExpressionBindingsMatch(argument, rightArgument, scopes));
|
|
41753
|
-
});
|
|
41754
|
-
}
|
|
41755
|
-
return true;
|
|
41756
|
-
};
|
|
41757
|
-
const areHelperReturnValuesEquivalent = (leftValue, rightValue, context) => {
|
|
41758
|
-
if (areExpressionsStructurallyEqual(leftValue, rightValue)) return doEquivalentExpressionBindingsMatch(leftValue, rightValue, context.scopes);
|
|
41759
|
-
const leftBoolean = readInitialStateBoolean(leftValue, context.scopes);
|
|
41760
|
-
const rightBoolean = readInitialStateBoolean(rightValue, context.scopes);
|
|
41761
|
-
return leftBoolean !== null && rightBoolean !== null && leftBoolean === rightBoolean;
|
|
41762
|
-
};
|
|
41763
|
-
const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
|
|
41764
|
-
const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
|
|
41765
|
-
return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
|
|
41766
|
-
};
|
|
41767
|
-
const matchHydrationConditionInternal = (expression, context, state) => {
|
|
40465
|
+
const matchHydrationCondition = (expression, context) => {
|
|
41768
40466
|
const unwrappedExpression = stripParenExpression(expression);
|
|
41769
40467
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
41770
40468
|
if (predicateMatch) return {
|
|
41771
40469
|
predicateMatch,
|
|
41772
40470
|
predicateNode: unwrappedExpression
|
|
41773
40471
|
};
|
|
41774
|
-
if (isNodeOfType(unwrappedExpression, "
|
|
41775
|
-
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
41776
|
-
const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
|
|
41777
|
-
if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
|
|
41778
|
-
state.visitedSymbolIds.add(symbol.id);
|
|
41779
|
-
const match = matchHydrationConditionInternal(parameterValue, context, state);
|
|
41780
|
-
state.visitedSymbolIds.delete(symbol.id);
|
|
41781
|
-
return match;
|
|
41782
|
-
}
|
|
41783
|
-
if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
|
|
41784
|
-
state.visitedSymbolIds.add(symbol.id);
|
|
41785
|
-
const match = matchHydrationConditionInternal(symbol.initializer, context, state);
|
|
41786
|
-
state.visitedSymbolIds.delete(symbol.id);
|
|
41787
|
-
return match;
|
|
41788
|
-
}
|
|
41789
|
-
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
41790
|
-
const callArguments = unwrappedExpression.arguments ?? [];
|
|
41791
|
-
if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
|
|
41792
|
-
allowGlobalReactNamespace: true,
|
|
41793
|
-
resolveNamedAliases: true
|
|
41794
|
-
})) {
|
|
41795
|
-
const callbackArgument = callArguments[0];
|
|
41796
|
-
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
|
|
41797
|
-
const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
41798
|
-
return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionResult(callbackFunction, context, state) : null;
|
|
41799
|
-
}
|
|
41800
|
-
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
41801
|
-
if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return matchHydrationConditionInternal(callArguments[0], context, state);
|
|
41802
|
-
const helperFunction = resolveExactLocalFunction(callee, context.scopes);
|
|
41803
|
-
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;
|
|
41804
|
-
const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
|
|
41805
|
-
for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
|
|
41806
|
-
const parameter = helperFunction.params[parameterIndex];
|
|
41807
|
-
const argument = callArguments[parameterIndex];
|
|
41808
|
-
if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
|
|
41809
|
-
const parameterSymbol = context.scopes.symbolFor(parameter);
|
|
41810
|
-
if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
|
|
41811
|
-
}
|
|
41812
|
-
return matchHydrationFunctionResult(helperFunction, context, {
|
|
41813
|
-
...state,
|
|
41814
|
-
parameterValuesBySymbolId
|
|
41815
|
-
});
|
|
41816
|
-
}
|
|
41817
|
-
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
|
|
40472
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
|
|
41818
40473
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
41819
|
-
const leftMatch =
|
|
41820
|
-
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
|
+
}
|
|
41821
40481
|
const nestedMatch = leftMatch ?? rightMatch;
|
|
41822
40482
|
if (!nestedMatch) return null;
|
|
41823
|
-
const
|
|
41824
|
-
|
|
41825
|
-
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;
|
|
41826
40486
|
};
|
|
41827
|
-
const matchHydrationReturningStatement = (statement, context, state) => {
|
|
41828
|
-
if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? matchHydrationConditionInternal(statement.argument, context, state) : null;
|
|
41829
|
-
if (isNodeOfType(statement, "IfStatement")) {
|
|
41830
|
-
const conditionMatch = matchHydrationConditionInternal(statement.test, context, state);
|
|
41831
|
-
const consequentValues = getReturnedValues(statement.consequent);
|
|
41832
|
-
const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
|
|
41833
|
-
if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
|
|
41834
|
-
return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
|
|
41835
|
-
}
|
|
41836
|
-
if (!isNodeOfType(statement, "BlockStatement")) return null;
|
|
41837
|
-
for (const childStatement of statement.body) {
|
|
41838
|
-
const match = matchHydrationReturningStatement(childStatement, context, state);
|
|
41839
|
-
if (match) return match;
|
|
41840
|
-
if (statementAlwaysExits(childStatement)) break;
|
|
41841
|
-
}
|
|
41842
|
-
return null;
|
|
41843
|
-
};
|
|
41844
|
-
const matchHydrationFunctionResult = (functionNode, context, state) => {
|
|
41845
|
-
if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
|
|
41846
|
-
state.visitedFunctionNodes.add(functionNode);
|
|
41847
|
-
const match = isNodeOfType(functionNode.body, "BlockStatement") ? matchHydrationReturningStatement(functionNode.body, context, state) : matchHydrationConditionInternal(functionNode.body, context, state);
|
|
41848
|
-
state.visitedFunctionNodes.delete(functionNode);
|
|
41849
|
-
return match;
|
|
41850
|
-
};
|
|
41851
|
-
const matchHydrationCondition = (expression, context) => matchHydrationConditionInternal(expression, context, {
|
|
41852
|
-
parameterValuesBySymbolId: /* @__PURE__ */ new Map(),
|
|
41853
|
-
visitedFunctionNodes: /* @__PURE__ */ new Set(),
|
|
41854
|
-
visitedSymbolIds: /* @__PURE__ */ new Set()
|
|
41855
|
-
});
|
|
41856
40487
|
const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
|
|
41857
40488
|
const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
|
|
41858
40489
|
if (!leftNode || !rightNode) return leftNode === rightNode;
|
|
@@ -41995,17 +40626,17 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
41995
40626
|
const { predicateMatch, predicateNode } = conditionMatch;
|
|
41996
40627
|
if (reportedNodes.has(predicateNode)) return;
|
|
41997
40628
|
if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
|
|
41998
|
-
const componentOrHookNode = findRenderPhaseComponentOrHook(
|
|
40629
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(predicateNode, context.scopes);
|
|
41999
40630
|
if (!componentOrHookNode) return;
|
|
42000
40631
|
if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
|
|
42001
|
-
if (requiresRenderedContext && !isInRenderedOutput(
|
|
40632
|
+
if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
|
|
42002
40633
|
if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
|
|
42003
|
-
const attribute = findEnclosingJsxAttribute(
|
|
40634
|
+
const attribute = findEnclosingJsxAttribute(predicateNode);
|
|
42004
40635
|
if (!attribute || isEventHandlerAttribute(attribute)) return;
|
|
42005
40636
|
}
|
|
42006
|
-
if (fileIsEmailTemplate || isGatedByFalsyInitialState(
|
|
42007
|
-
if (isAfterClientOnlyEarlyReturn(
|
|
42008
|
-
const openingElement = findEnclosingJsxOpeningElement(
|
|
40637
|
+
if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
|
|
40638
|
+
if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
|
|
40639
|
+
const openingElement = findEnclosingJsxOpeningElement(predicateNode);
|
|
42009
40640
|
if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
|
|
42010
40641
|
if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
|
|
42011
40642
|
if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
|
|
@@ -42387,7 +41018,7 @@ const noInitializeState = defineRule({
|
|
|
42387
41018
|
if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
|
|
42388
41019
|
const analysis = getProgramAnalysis(node);
|
|
42389
41020
|
if (!analysis) return;
|
|
42390
|
-
for (const fact of collectEffectStateWriteFacts(analysis,
|
|
41021
|
+
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
42391
41022
|
if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
|
|
42392
41023
|
const stateName = getStateName(fact.stateDeclarator);
|
|
42393
41024
|
context.report({
|
|
@@ -42745,8 +41376,7 @@ const noJsxElementType = defineRule({
|
|
|
42745
41376
|
create: (context) => {
|
|
42746
41377
|
let isJsxImported = false;
|
|
42747
41378
|
const flaggedAnnotations = [];
|
|
42748
|
-
const
|
|
42749
|
-
if (!(isNodeOfType(functionNode, "TSDeclareFunction") ? Boolean(functionNode.id && isReactComponentName(functionNode.id.name)) : isComponentFunction$1(functionNode))) return;
|
|
41379
|
+
const checkReturnType = (returnType) => {
|
|
42750
41380
|
const typeAnnotation = extractReturnTypeAnnotation(returnType);
|
|
42751
41381
|
if (!typeAnnotation) return;
|
|
42752
41382
|
if (isJsxElementTypeReference(typeAnnotation)) flaggedAnnotations.push(typeAnnotation);
|
|
@@ -42756,16 +41386,19 @@ const noJsxElementType = defineRule({
|
|
|
42756
41386
|
if (isJsxImportBinding(node)) isJsxImported = true;
|
|
42757
41387
|
},
|
|
42758
41388
|
FunctionDeclaration(node) {
|
|
42759
|
-
|
|
41389
|
+
checkReturnType(node.returnType);
|
|
42760
41390
|
},
|
|
42761
41391
|
ArrowFunctionExpression(node) {
|
|
42762
|
-
|
|
41392
|
+
checkReturnType(node.returnType);
|
|
42763
41393
|
},
|
|
42764
41394
|
FunctionExpression(node) {
|
|
42765
|
-
|
|
41395
|
+
checkReturnType(node.returnType);
|
|
42766
41396
|
},
|
|
42767
41397
|
TSDeclareFunction(node) {
|
|
42768
|
-
|
|
41398
|
+
checkReturnType(node.returnType);
|
|
41399
|
+
},
|
|
41400
|
+
TSMethodSignature(node) {
|
|
41401
|
+
checkReturnType(node.returnType);
|
|
42769
41402
|
},
|
|
42770
41403
|
"Program:exit"() {
|
|
42771
41404
|
if (isJsxImported) return;
|
|
@@ -45792,114 +44425,6 @@ const DATA_SINK_METHOD_NAMES = new Set([
|
|
|
45792
44425
|
"deserialize"
|
|
45793
44426
|
]);
|
|
45794
44427
|
//#endregion
|
|
45795
|
-
//#region src/plugin/utils/get-transparent-react-callback-wrapper-argument.ts
|
|
45796
|
-
const getTransparentReactCallbackWrapperArgument = (initializer, resultSymbol, scopes) => {
|
|
45797
|
-
const callExpression = stripParenExpression(initializer);
|
|
45798
|
-
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
45799
|
-
const callbackArgument = callExpression.arguments[0];
|
|
45800
|
-
if (!callbackArgument) return null;
|
|
45801
|
-
if (resultSymbol && symbolHasReactUseEffectEventOrigin(resultSymbol, scopes)) return callbackArgument;
|
|
45802
|
-
return isReactApiCall(callExpression, "useCallback", scopes, {
|
|
45803
|
-
allowGlobalReactNamespace: true,
|
|
45804
|
-
allowUnboundBareCalls: true
|
|
45805
|
-
}) ? callbackArgument : null;
|
|
45806
|
-
};
|
|
45807
|
-
//#endregion
|
|
45808
|
-
//#region src/plugin/rules/state-and-effects/utils/resolve-parent-callback-provenance.ts
|
|
45809
|
-
const getDeclarationKind$1 = (declarator) => {
|
|
45810
|
-
const declaration = declarator.parent;
|
|
45811
|
-
return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
|
|
45812
|
-
};
|
|
45813
|
-
const hasMutableBindingWrite$2 = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
45814
|
-
const mergeRequiredBranches = (leftNames, rightNames) => {
|
|
45815
|
-
if (!leftNames || !rightNames) return null;
|
|
45816
|
-
return new Set([...leftNames, ...rightNames]);
|
|
45817
|
-
};
|
|
45818
|
-
const getPropReferenceName = (analysis, identifier) => {
|
|
45819
|
-
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
45820
|
-
const reference = getRef(analysis, identifier);
|
|
45821
|
-
if (!reference || !isProp(analysis, reference) || isWholePropsObjectReference(analysis, reference)) return null;
|
|
45822
|
-
const bindingIdentifier = (reference.resolved?.defs.find((definition) => definition.type === "Parameter"))?.name;
|
|
45823
|
-
return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? identifier.name;
|
|
45824
|
-
};
|
|
45825
|
-
const getSingleConstDeclarator = (reference) => {
|
|
45826
|
-
if (!reference.resolved || hasMutableBindingWrite$2(reference)) return null;
|
|
45827
|
-
const declarators = reference.resolved.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
45828
|
-
if (declarators.length !== 1) return null;
|
|
45829
|
-
const declarator = declarators[0];
|
|
45830
|
-
if (!declarator || getDeclarationKind$1(declarator) !== "const") return null;
|
|
45831
|
-
return declarator;
|
|
45832
|
-
};
|
|
45833
|
-
const resolveParentCallbackPropNames = (analysis, expression, scopes, visitedReferences, allowFunctionForwarder = false) => {
|
|
45834
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
45835
|
-
if (isFunctionLike$1(unwrappedExpression)) {
|
|
45836
|
-
if (!allowFunctionForwarder || Boolean(unwrappedExpression.async)) return null;
|
|
45837
|
-
const callbackNames = /* @__PURE__ */ new Set();
|
|
45838
|
-
walkInsideStatementBlocks(unwrappedExpression.body, (child) => {
|
|
45839
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
45840
|
-
const resolvedNames = resolveParentCallbackPropNames(analysis, child.callee, scopes, new Set(visitedReferences), false);
|
|
45841
|
-
if (!resolvedNames) return;
|
|
45842
|
-
for (const resolvedName of resolvedNames) callbackNames.add(resolvedName);
|
|
45843
|
-
});
|
|
45844
|
-
return callbackNames.size > 0 ? callbackNames : null;
|
|
45845
|
-
}
|
|
45846
|
-
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return mergeRequiredBranches(resolveParentCallbackPropNames(analysis, unwrappedExpression.consequent, scopes, new Set(visitedReferences), false), resolveParentCallbackPropNames(analysis, unwrappedExpression.alternate, scopes, new Set(visitedReferences), false));
|
|
45847
|
-
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return mergeRequiredBranches(resolveParentCallbackPropNames(analysis, unwrappedExpression.left, scopes, new Set(visitedReferences), false), resolveParentCallbackPropNames(analysis, unwrappedExpression.right, scopes, new Set(visitedReferences)));
|
|
45848
|
-
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
45849
|
-
const propName = getPropReferenceName(analysis, unwrappedExpression);
|
|
45850
|
-
if (propName) return new Set([propName]);
|
|
45851
|
-
const reference = getRef(analysis, unwrappedExpression);
|
|
45852
|
-
if (!reference?.resolved || visitedReferences.has(reference.resolved)) return null;
|
|
45853
|
-
const declarator = getSingleConstDeclarator(reference);
|
|
45854
|
-
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return null;
|
|
45855
|
-
visitedReferences.add(reference.resolved);
|
|
45856
|
-
const wrappedArgument = getTransparentReactCallbackWrapperArgument(declarator.init, scopes.symbolFor(unwrappedExpression), scopes);
|
|
45857
|
-
const allowsFunctionForwarder = Boolean(wrappedArgument && !isReactApiCall(declarator.init, "useCallback", scopes, {
|
|
45858
|
-
allowGlobalReactNamespace: true,
|
|
45859
|
-
allowUnboundBareCalls: true
|
|
45860
|
-
}));
|
|
45861
|
-
return resolveParentCallbackPropNames(analysis, wrappedArgument ?? declarator.init, scopes, visitedReferences, allowsFunctionForwarder);
|
|
45862
|
-
}
|
|
45863
|
-
if (!isNodeOfType(unwrappedExpression, "MemberExpression")) return null;
|
|
45864
|
-
const propertyName = getStaticMemberPropertyName(unwrappedExpression);
|
|
45865
|
-
if (!propertyName) return null;
|
|
45866
|
-
const receiver = stripParenExpression(unwrappedExpression.object);
|
|
45867
|
-
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
45868
|
-
const receiverReference = getRef(analysis, receiver);
|
|
45869
|
-
if (!receiverReference?.resolved || visitedReferences.has(receiverReference.resolved)) return null;
|
|
45870
|
-
if (isWholePropsObjectReference(analysis, receiverReference)) return new Set([propertyName]);
|
|
45871
|
-
const declarator = getSingleConstDeclarator(receiverReference);
|
|
45872
|
-
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return null;
|
|
45873
|
-
visitedReferences.add(receiverReference.resolved);
|
|
45874
|
-
const initializer = stripParenExpression(declarator.init);
|
|
45875
|
-
if (propertyName === "current" && isNodeOfType(initializer, "CallExpression")) {
|
|
45876
|
-
if (!isReactApiCall(initializer, "useRef", scopes, {
|
|
45877
|
-
allowGlobalReactNamespace: true,
|
|
45878
|
-
allowUnboundBareCalls: true
|
|
45879
|
-
})) return null;
|
|
45880
|
-
const callbackArgument = initializer.arguments[0];
|
|
45881
|
-
if (!callbackArgument) return null;
|
|
45882
|
-
let callbackNames = resolveParentCallbackPropNames(analysis, callbackArgument, scopes, new Set(visitedReferences), false);
|
|
45883
|
-
if (!callbackNames) return null;
|
|
45884
|
-
for (const candidateReference of receiverReference.resolved.references) {
|
|
45885
|
-
const candidateIdentifier = candidateReference.identifier;
|
|
45886
|
-
const candidateMember = candidateIdentifier.parent;
|
|
45887
|
-
if (!candidateMember || !isNodeOfType(candidateMember, "MemberExpression") || candidateMember.object !== candidateIdentifier || getStaticMemberPropertyName(candidateMember) !== "current") continue;
|
|
45888
|
-
const assignment = candidateMember.parent;
|
|
45889
|
-
if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.left !== candidateMember) continue;
|
|
45890
|
-
if (assignment.operator !== "=") return null;
|
|
45891
|
-
callbackNames = mergeRequiredBranches(callbackNames, resolveParentCallbackPropNames(analysis, assignment.right, scopes, new Set(visitedReferences), false));
|
|
45892
|
-
if (!callbackNames) return null;
|
|
45893
|
-
}
|
|
45894
|
-
return callbackNames;
|
|
45895
|
-
}
|
|
45896
|
-
if (!isNodeOfType(initializer, "ObjectExpression")) return null;
|
|
45897
|
-
const property = initializer.properties.find((candidateProperty) => isNodeOfType(candidateProperty, "Property") && getStaticPropertyKeyName(candidateProperty, { allowComputedString: true }) === propertyName);
|
|
45898
|
-
if (!property || !isNodeOfType(property, "Property")) return null;
|
|
45899
|
-
return resolveParentCallbackPropNames(analysis, property.value, scopes, visitedReferences, false);
|
|
45900
|
-
};
|
|
45901
|
-
const getParentCallbackPropNames = ({ analysis, expression, scopes }) => resolveParentCallbackPropNames(analysis, expression, scopes, /* @__PURE__ */ new Set(), false);
|
|
45902
|
-
//#endregion
|
|
45903
44428
|
//#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
|
|
45904
44429
|
const isUseStateIdentifier = (identifier) => {
|
|
45905
44430
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
@@ -45928,18 +44453,14 @@ const FUNCTION_WRAPPER_HOOK_NAMES$1 = new Set([
|
|
|
45928
44453
|
"useStableCallback",
|
|
45929
44454
|
"useCallbackRef"
|
|
45930
44455
|
]);
|
|
45931
|
-
const getWrapperHookWrappedFunction = (initializer
|
|
44456
|
+
const getWrapperHookWrappedFunction = (initializer) => {
|
|
45932
44457
|
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
45933
|
-
const transparentReactArgument = getTransparentReactCallbackWrapperArgument(initializer, resultSymbol, scopes);
|
|
45934
|
-
if (transparentReactArgument) return transparentReactArgument;
|
|
45935
44458
|
const callee = initializer.callee;
|
|
45936
44459
|
const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") ? callee.property.name : null;
|
|
45937
44460
|
if (!calleeName || !FUNCTION_WRAPPER_HOOK_NAMES$1.has(calleeName)) return null;
|
|
45938
44461
|
const wrapped = initializer.arguments?.[0];
|
|
45939
|
-
if (!wrapped) return null;
|
|
45940
|
-
|
|
45941
|
-
if (isFunctionLike$1(wrapped)) return wrapped;
|
|
45942
|
-
return null;
|
|
44462
|
+
if (!wrapped || !isFunctionLike$1(wrapped)) return null;
|
|
44463
|
+
return wrapped;
|
|
45943
44464
|
};
|
|
45944
44465
|
const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
|
|
45945
44466
|
const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
|
|
@@ -45949,29 +44470,16 @@ const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstre
|
|
|
45949
44470
|
const innerParent = innerIdentifier.parent;
|
|
45950
44471
|
return Boolean(innerParent && isNodeOfType(innerParent, "CallExpression") && innerParent.callee === innerIdentifier);
|
|
45951
44472
|
});
|
|
45952
|
-
const isDirectParentCallbackRef = (analysis, ref
|
|
44473
|
+
const isDirectParentCallbackRef = (analysis, ref) => {
|
|
45953
44474
|
if (isProp(analysis, ref)) return true;
|
|
45954
|
-
if (hasMutableBindingWrite$1(ref)) {
|
|
45955
|
-
if (!(ref.resolved?.references.filter((candidateReference) => candidateReference.isWrite() && !candidateReference.init) ?? []).every((candidateReference) => {
|
|
45956
|
-
const candidateIdentifier = candidateReference.identifier;
|
|
45957
|
-
const assignment = candidateIdentifier.parent;
|
|
45958
|
-
if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== candidateIdentifier) return false;
|
|
45959
|
-
const assignedReferences = getDownstreamRefs(analysis, assignment.right);
|
|
45960
|
-
return assignedReferences.length > 0 && assignedReferences.every((assignedReference) => isProp(analysis, assignedReference));
|
|
45961
|
-
})) return false;
|
|
45962
|
-
}
|
|
45963
44475
|
return Boolean(ref.resolved?.defs.some((def) => {
|
|
45964
44476
|
const node = def.node;
|
|
45965
44477
|
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
45966
44478
|
const initializer = unwrapChainExpression(node.init);
|
|
45967
|
-
const wrappedFunction = getWrapperHookWrappedFunction(initializer
|
|
44479
|
+
const wrappedFunction = getWrapperHookWrappedFunction(initializer);
|
|
45968
44480
|
if (wrappedFunction) {
|
|
45969
44481
|
if (wrappedFunction.async) return false;
|
|
45970
|
-
|
|
45971
|
-
const directName = getParentCallbackPropName(analysis, wrappedFunction);
|
|
45972
|
-
const downstreamReferences = getDownstreamRefs(analysis, wrappedFunction);
|
|
45973
|
-
if (directName !== null) return true;
|
|
45974
|
-
return downstreamReferences.some((wrappedReference) => !hasMutableBindingWrite$1(wrappedReference) && getUpstreamRefs(analysis, wrappedReference).some((upstreamReference) => isProp(analysis, upstreamReference)));
|
|
44482
|
+
return wrappedFunctionNotifiesParent(analysis, wrappedFunction);
|
|
45975
44483
|
}
|
|
45976
44484
|
if (!isNodeOfType(initializer, "Identifier") && !isNodeOfType(initializer, "MemberExpression")) return false;
|
|
45977
44485
|
return getDownstreamRefs(analysis, initializer).some((initializerRef) => getUpstreamRefs(analysis, initializerRef).some((upstreamRef) => isProp(analysis, upstreamRef)));
|
|
@@ -45981,7 +44489,7 @@ const getDeclarationKind = (declarator) => {
|
|
|
45981
44489
|
const declaration = declarator.parent;
|
|
45982
44490
|
return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
|
|
45983
44491
|
};
|
|
45984
|
-
const hasMutableBindingWrite
|
|
44492
|
+
const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
45985
44493
|
const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
|
|
45986
44494
|
const unwrappedExpression = stripParenExpression(expression);
|
|
45987
44495
|
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
@@ -45993,7 +44501,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
45993
44501
|
const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
|
|
45994
44502
|
return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
|
|
45995
44503
|
}
|
|
45996
|
-
if (hasMutableBindingWrite
|
|
44504
|
+
if (hasMutableBindingWrite(callbackReference)) return null;
|
|
45997
44505
|
const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
45998
44506
|
if (definitions.length !== 1) return null;
|
|
45999
44507
|
const declarator = definitions[0];
|
|
@@ -46059,7 +44567,7 @@ const getRefAliasDeclarator = (identifier) => {
|
|
|
46059
44567
|
const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
|
|
46060
44568
|
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
46061
44569
|
const receiverReference = getRef(analysis, receiver);
|
|
46062
|
-
if (!receiverReference?.resolved || hasMutableBindingWrite
|
|
44570
|
+
if (!receiverReference?.resolved || hasMutableBindingWrite(receiverReference)) return null;
|
|
46063
44571
|
const variables = /* @__PURE__ */ new Set();
|
|
46064
44572
|
let currentVariable = receiverReference.resolved;
|
|
46065
44573
|
let refCall = null;
|
|
@@ -46075,7 +44583,7 @@ const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
|
|
|
46075
44583
|
}
|
|
46076
44584
|
if (getDeclarationKind(declarator) !== "const" || !isNodeOfType(stripParenExpression(declarator.init), "Identifier")) return null;
|
|
46077
44585
|
const upstreamReference = getRef(analysis, stripParenExpression(declarator.init));
|
|
46078
|
-
if (!upstreamReference?.resolved || hasMutableBindingWrite
|
|
44586
|
+
if (!upstreamReference?.resolved || hasMutableBindingWrite(upstreamReference)) return null;
|
|
46079
44587
|
currentVariable = upstreamReference.resolved;
|
|
46080
44588
|
}
|
|
46081
44589
|
if (!refCall) return null;
|
|
@@ -46150,7 +44658,7 @@ const isParentPropsContextMerge = (analysis, expression) => {
|
|
|
46150
44658
|
while (isNodeOfType(currentExpression, "Identifier")) {
|
|
46151
44659
|
const currentReference = getRef(analysis, currentExpression);
|
|
46152
44660
|
const currentVariable = currentReference?.resolved;
|
|
46153
|
-
if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite
|
|
44661
|
+
if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return false;
|
|
46154
44662
|
visitedVariables.add(currentVariable);
|
|
46155
44663
|
const definitions = currentVariable.defs.filter((definition) => isNodeOfType(definition.node, "VariableDeclarator"));
|
|
46156
44664
|
if (definitions.length !== 1) return false;
|
|
@@ -46164,11 +44672,11 @@ const isParentPropsContextMerge = (analysis, expression) => {
|
|
|
46164
44672
|
const propsExpression = stripParenExpression(propsSpread.argument);
|
|
46165
44673
|
if (!isNodeOfType(propsExpression, "Identifier")) return false;
|
|
46166
44674
|
const propsReference = getRef(analysis, propsExpression);
|
|
46167
|
-
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;
|
|
46168
44676
|
const contextExpression = stripParenExpression(contextSpread.argument);
|
|
46169
44677
|
if (!isNodeOfType(contextExpression, "Identifier")) return false;
|
|
46170
44678
|
const contextReference = getRef(analysis, contextExpression);
|
|
46171
|
-
if (!contextReference?.resolved || hasMutableBindingWrite
|
|
44679
|
+
if (!contextReference?.resolved || hasMutableBindingWrite(contextReference) || contextReference.resolved.references.some((candidateReference) => !candidateReference.init && candidateReference !== contextReference)) return false;
|
|
46172
44680
|
const contextInitializer = contextReference.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
46173
44681
|
if (!contextInitializer || !isNodeOfType(contextInitializer, "VariableDeclarator") || getDeclarationKind(contextInitializer) !== "const" || !contextInitializer.init || !isNodeOfType(contextInitializer.init, "CallExpression")) return false;
|
|
46174
44682
|
const contextHook = stripParenExpression(contextInitializer.init.callee);
|
|
@@ -46182,7 +44690,7 @@ const getImmutableParentCallbackPropName = (analysis, expression) => {
|
|
|
46182
44690
|
while (isNodeOfType(currentExpression, "Identifier")) {
|
|
46183
44691
|
const currentReference = getRef(analysis, currentExpression);
|
|
46184
44692
|
const currentVariable = currentReference?.resolved;
|
|
46185
|
-
if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite
|
|
44693
|
+
if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return null;
|
|
46186
44694
|
visitedVariables.add(currentVariable);
|
|
46187
44695
|
const definition = currentVariable.defs.length === 1 ? currentVariable.defs[0] : null;
|
|
46188
44696
|
const bindingIdentifier = definition?.name;
|
|
@@ -46251,7 +44759,7 @@ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) =>
|
|
|
46251
44759
|
while (isNodeOfType(currentExpression, "Identifier")) {
|
|
46252
44760
|
const callbackReference = getRef(analysis, currentExpression);
|
|
46253
44761
|
const callbackVariable = callbackReference?.resolved;
|
|
46254
|
-
if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite
|
|
44762
|
+
if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite(callbackReference)) return null;
|
|
46255
44763
|
visitedVariables.add(callbackVariable);
|
|
46256
44764
|
const definition = callbackVariable.defs.length === 1 ? callbackVariable.defs[0] : null;
|
|
46257
44765
|
const declarator = definition?.node;
|
|
@@ -46271,11 +44779,10 @@ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) =>
|
|
|
46271
44779
|
if (!propertyName || !COMMAND_PROP_NAME_PATTERN.test(propertyName)) return null;
|
|
46272
44780
|
return refCurrentObjectPreservesCallbackProperty(analysis, currentExpression.object, propertyName, isReactUseRefCall) ? propertyName : null;
|
|
46273
44781
|
};
|
|
46274
|
-
const isWrapperHookCallbackRef = (analysis, ref
|
|
44782
|
+
const isWrapperHookCallbackRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
|
|
46275
44783
|
const node = def.node;
|
|
46276
44784
|
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
46277
|
-
|
|
46278
|
-
return getWrapperHookWrappedFunction(unwrapChainExpression(node.init), resultSymbol, scopes) !== null;
|
|
44785
|
+
return getWrapperHookWrappedFunction(unwrapChainExpression(node.init)) !== null;
|
|
46279
44786
|
}));
|
|
46280
44787
|
const isHandlerBagArgument = (analysis, argument) => {
|
|
46281
44788
|
if (!isNodeOfType(argument, "ObjectExpression")) return false;
|
|
@@ -46294,26 +44801,14 @@ const isHandlerBagArgument = (analysis, argument) => {
|
|
|
46294
44801
|
};
|
|
46295
44802
|
const getFunctionalUpdaterDataRefs = (analysis, updater) => getDownstreamRefs(analysis, updater).filter((updaterRef) => !updaterRef.resolved?.defs.some((def) => def.type === "Parameter" && def.node === updater));
|
|
46296
44803
|
const HOOK_NAME_PATTERN$1 = /^use[A-Z0-9]/;
|
|
46297
|
-
const EXTERNAL_SUBSCRIPTION_HOOK_NAMES
|
|
44804
|
+
const EXTERNAL_SUBSCRIPTION_HOOK_NAMES = new Set([
|
|
46298
44805
|
"useIntersectionObserver",
|
|
46299
44806
|
"useMatchMedia",
|
|
46300
|
-
"useMediaJobProgress",
|
|
46301
44807
|
"useMediaQuery",
|
|
46302
|
-
"useMediaQueryState",
|
|
46303
44808
|
"useResizeObserver",
|
|
46304
44809
|
"useVisibility",
|
|
46305
44810
|
"useWindowSize"
|
|
46306
44811
|
]);
|
|
46307
|
-
const isCallbackPropReference = (analysis, ref) => {
|
|
46308
|
-
if (!isProp(analysis, ref)) return false;
|
|
46309
|
-
const identifier = ref.identifier;
|
|
46310
|
-
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
46311
|
-
if (!isWholePropsObjectReference(analysis, ref)) return HANDLER_NAMED_PROP_PATTERN.test(identifier.name);
|
|
46312
|
-
const member = identifier.parent;
|
|
46313
|
-
if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier) return false;
|
|
46314
|
-
const propertyName = getStaticMemberPropertyName(member);
|
|
46315
|
-
return Boolean(propertyName && HANDLER_NAMED_PROP_PATTERN.test(propertyName));
|
|
46316
|
-
};
|
|
46317
44812
|
const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
|
|
46318
44813
|
const node = def.node;
|
|
46319
44814
|
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
@@ -46321,7 +44816,7 @@ const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs
|
|
|
46321
44816
|
if (!isNodeOfType(init, "CallExpression")) return false;
|
|
46322
44817
|
const callee = init.callee;
|
|
46323
44818
|
if (!isNodeOfType(callee, "Identifier") || !HOOK_NAME_PATTERN$1.test(callee.name)) return false;
|
|
46324
|
-
return (init.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) =>
|
|
44819
|
+
return (init.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isProp(analysis, downstreamRef)));
|
|
46325
44820
|
}));
|
|
46326
44821
|
const isParentWiredHookResultArgument = (analysis, argument) => {
|
|
46327
44822
|
if (!isNodeOfType(argument, "Identifier")) return false;
|
|
@@ -46334,40 +44829,19 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
|
|
|
46334
44829
|
if (!isNodeOfType(identifier, "Identifier") || !HOOK_NAME_PATTERN$1.test(identifier.name)) return false;
|
|
46335
44830
|
const parent = identifier.parent;
|
|
46336
44831
|
if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
|
|
46337
|
-
return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) =>
|
|
44832
|
+
return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isProp(analysis, downstreamRef)));
|
|
46338
44833
|
};
|
|
46339
|
-
const
|
|
46340
|
-
let hookFunction = resolveToFunction(ref);
|
|
46341
|
-
if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
|
|
46342
|
-
const definitionNode = definition.node;
|
|
46343
|
-
if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
|
|
46344
|
-
const initializer = stripParenExpression(definitionNode.init);
|
|
46345
|
-
if (!isNodeOfType(initializer, "CallExpression")) continue;
|
|
46346
|
-
const callee = stripParenExpression(initializer.callee);
|
|
46347
|
-
if (!isNodeOfType(callee, "Identifier")) continue;
|
|
46348
|
-
const calleeReference = getRef(analysis, callee);
|
|
46349
|
-
if (!calleeReference) continue;
|
|
46350
|
-
hookFunction = resolveToFunction(calleeReference);
|
|
46351
|
-
if (hookFunction) break;
|
|
46352
|
-
}
|
|
46353
|
-
if (!hookFunction) return null;
|
|
46354
|
-
const returnedReferences = collectFunctionReturnStatements(hookFunction).flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
|
|
46355
|
-
if (returnedReferences.length === 0) return null;
|
|
46356
|
-
return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
|
|
46357
|
-
};
|
|
46358
|
-
const isExternalSubscriptionHookRef = (analysis, ref) => {
|
|
44834
|
+
const isExternalSubscriptionHookRef = (ref) => {
|
|
46359
44835
|
const identifier = ref.identifier;
|
|
46360
44836
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
46361
|
-
|
|
46362
|
-
if (localHookProof !== null) return localHookProof;
|
|
46363
|
-
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;
|
|
46364
44838
|
return Boolean(ref.resolved?.defs.some((def) => {
|
|
46365
44839
|
const node = def.node;
|
|
46366
44840
|
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
46367
44841
|
const initializer = stripParenExpression(node.init);
|
|
46368
44842
|
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
46369
44843
|
const callee = stripParenExpression(initializer.callee);
|
|
46370
|
-
return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES
|
|
44844
|
+
return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(callee.name);
|
|
46371
44845
|
}));
|
|
46372
44846
|
};
|
|
46373
44847
|
const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
|
|
@@ -46403,22 +44877,16 @@ const noPassDataToParent = defineRule({
|
|
|
46403
44877
|
const callExpr = getCallExpr(ref);
|
|
46404
44878
|
if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
|
|
46405
44879
|
const callbackRefProvenance = getCallbackRefProvenance(analysis, node, callExpr, isReactUseRefCall, isReactUseEffectCall);
|
|
44880
|
+
if (isRefCall(analysis, ref) && !callbackRefProvenance) continue;
|
|
46406
44881
|
if (!isSynchronous(ref.identifier, effectFn)) continue;
|
|
46407
44882
|
const calleeNode = unwrapChainExpression(callExpr.callee);
|
|
46408
44883
|
const identifier = ref.identifier;
|
|
46409
|
-
|
|
46410
|
-
|
|
46411
|
-
expression: calleeNode,
|
|
46412
|
-
scopes: context.scopes
|
|
46413
|
-
});
|
|
46414
|
-
const callbackPropNames = callbackRefProvenance?.callbackPropNames ?? resolvedCallbackPropNames;
|
|
46415
|
-
if (isRefCall(analysis, ref) && !callbackPropNames) continue;
|
|
46416
|
-
if (callbackPropNames) {
|
|
46417
|
-
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;
|
|
46418
44886
|
} else if (calleeNode === identifier) {
|
|
46419
44887
|
const callbackPropName = getCommandCallbackPropName(analysis, identifier, isReactUseRefCall);
|
|
46420
44888
|
if (callbackPropName && COMMAND_PROP_NAME_PATTERN.test(callbackPropName)) continue;
|
|
46421
|
-
if (!isDirectParentCallbackRef(analysis, ref
|
|
44889
|
+
if (!isDirectParentCallbackRef(analysis, ref)) continue;
|
|
46422
44890
|
if (isNodeOfType(identifier, "Identifier") && COMMAND_PROP_NAME_PATTERN.test(identifier.name)) continue;
|
|
46423
44891
|
} else if (isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === identifier) {
|
|
46424
44892
|
if (!isWholePropsObjectReference(analysis, ref)) continue;
|
|
@@ -46426,10 +44894,10 @@ const noPassDataToParent = defineRule({
|
|
|
46426
44894
|
} else continue;
|
|
46427
44895
|
const methodName = getCallMethodName(calleeNode);
|
|
46428
44896
|
const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
|
|
46429
|
-
if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead
|
|
44897
|
+
if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
|
|
46430
44898
|
if (methodName && COMMAND_PROP_NAME_PATTERN.test(methodName)) continue;
|
|
46431
|
-
if (!
|
|
46432
|
-
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) ?? ""));
|
|
46433
44901
|
const isLeafRef = (argRef) => getUpstreamRefs(analysis, argRef).length === 1;
|
|
46434
44902
|
const argsUpstreamRefs = (callExpr.arguments ?? []).flatMap((argument) => {
|
|
46435
44903
|
if (isFunctionLike$1(argument)) {
|
|
@@ -46443,11 +44911,11 @@ const noPassDataToParent = defineRule({
|
|
|
46443
44911
|
if (argumentRef && resolveToFunction(argumentRef)) return [];
|
|
46444
44912
|
}
|
|
46445
44913
|
return getDownstreamRefs(analysis, argument);
|
|
46446
|
-
}).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef)
|
|
46447
|
-
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));
|
|
46448
44916
|
if (!argsUpstreamRefs.some((argRef) => {
|
|
46449
44917
|
if (isUseStateIdentifier(argRef.identifier)) return false;
|
|
46450
|
-
if (isExternalSubscriptionHookRef(
|
|
44918
|
+
if (isExternalSubscriptionHookRef(argRef)) return false;
|
|
46451
44919
|
if (isProp(analysis, argRef)) return false;
|
|
46452
44920
|
if (isUseRefIdentifier(argRef.identifier)) return false;
|
|
46453
44921
|
if (isRefCurrent(argRef)) return false;
|
|
@@ -46482,47 +44950,9 @@ const isCallResultConsumedAsArgument = (callExpression) => {
|
|
|
46482
44950
|
return false;
|
|
46483
44951
|
};
|
|
46484
44952
|
//#endregion
|
|
46485
|
-
//#region src/plugin/rules/state-and-effects/utils/is-custom-hook-state-result-reference.ts
|
|
46486
|
-
const NON_STATE_CUSTOM_HOOK_NAMES = new Set([
|
|
46487
|
-
"useCallbackRef",
|
|
46488
|
-
"useEffectEvent",
|
|
46489
|
-
"useEvent",
|
|
46490
|
-
"useEventCallback",
|
|
46491
|
-
"useLatest",
|
|
46492
|
-
"useMemoizedFn",
|
|
46493
|
-
"useStableCallback"
|
|
46494
|
-
]);
|
|
46495
|
-
const EXTERNAL_SUBSCRIPTION_HOOK_NAMES = new Set([
|
|
46496
|
-
"useIntersectionObserver",
|
|
46497
|
-
"useMatchMedia",
|
|
46498
|
-
"useMediaJobProgress",
|
|
46499
|
-
"useMediaQuery",
|
|
46500
|
-
"useResizeObserver",
|
|
46501
|
-
"useVisibility",
|
|
46502
|
-
"useWindowSize"
|
|
46503
|
-
]);
|
|
46504
|
-
const getHookCalleeName = (initializer) => {
|
|
46505
|
-
const unwrappedInitializer = stripParenExpression(initializer);
|
|
46506
|
-
if (!isNodeOfType(unwrappedInitializer, "CallExpression")) return null;
|
|
46507
|
-
const callee = stripParenExpression(unwrappedInitializer.callee);
|
|
46508
|
-
if (isNodeOfType(callee, "Identifier")) return callee.name;
|
|
46509
|
-
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
|
|
46510
|
-
return null;
|
|
46511
|
-
};
|
|
46512
|
-
const isCustomHookStateResultReference = (analysis, reference) => Boolean(reference.resolved?.defs.some((definition) => {
|
|
46513
|
-
const declarator = definition.node;
|
|
46514
|
-
if (!isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return false;
|
|
46515
|
-
const calleeName = getHookCalleeName(declarator.init);
|
|
46516
|
-
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;
|
|
46517
|
-
const initializer = stripParenExpression(declarator.init);
|
|
46518
|
-
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
46519
|
-
return initializer.arguments.some((argument) => getDownstreamRefs(analysis, argument).some((argumentReference) => isProp(analysis, argumentReference)));
|
|
46520
|
-
}));
|
|
46521
|
-
//#endregion
|
|
46522
44953
|
//#region src/plugin/rules/state-and-effects/no-pass-live-state-to-parent.ts
|
|
46523
44954
|
const SETTER_NAMED_CALLBACK_PATTERN = /^set[A-Z]/;
|
|
46524
44955
|
const DATA_FETCHING_CALLBACK_PATTERN = /^(fetch|refetch|load|query|request)([A-Z_]|$)/;
|
|
46525
|
-
const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
46526
44956
|
const getCallCalleeName = (callExpr) => {
|
|
46527
44957
|
if (!isNodeOfType(callExpr, "CallExpression")) return null;
|
|
46528
44958
|
const callee = callExpr.callee;
|
|
@@ -46567,10 +44997,6 @@ const collectUpstreamStateRefs = (analysis, ref, stateRefs, visited) => {
|
|
|
46567
44997
|
stateRefs.push(ref);
|
|
46568
44998
|
return;
|
|
46569
44999
|
}
|
|
46570
|
-
if (isCustomHookStateResultReference(analysis, ref)) {
|
|
46571
|
-
stateRefs.push(ref);
|
|
46572
|
-
return;
|
|
46573
|
-
}
|
|
46574
45000
|
for (const def of ref.resolved?.defs ?? []) {
|
|
46575
45001
|
if (def.type === "ImportBinding" || def.type === "Parameter") continue;
|
|
46576
45002
|
const defNode = def.node;
|
|
@@ -46600,32 +45026,6 @@ const collectPropCallbackBoundStateRefs = (analysis, ref, isPropCallbackRef) =>
|
|
|
46600
45026
|
}
|
|
46601
45027
|
return stateRefs;
|
|
46602
45028
|
};
|
|
46603
|
-
const collectDirectCallStateRefs = (analysis, callExpression) => {
|
|
46604
|
-
const stateReferences = [];
|
|
46605
|
-
for (const argument of callExpression.arguments) {
|
|
46606
|
-
if (isFunctionLike$1(argument)) continue;
|
|
46607
|
-
for (const argumentReference of getDownstreamRefs(analysis, argument)) {
|
|
46608
|
-
if (resolveToFunction(argumentReference)) continue;
|
|
46609
|
-
collectUpstreamStateRefs(analysis, argumentReference, stateReferences, /* @__PURE__ */ new Set());
|
|
46610
|
-
}
|
|
46611
|
-
}
|
|
46612
|
-
return stateReferences;
|
|
46613
|
-
};
|
|
46614
|
-
const getTransparentWrapperPropReference = (analysis, reference, context) => {
|
|
46615
|
-
for (const definition of reference.resolved?.defs ?? []) {
|
|
46616
|
-
const declarator = definition.node;
|
|
46617
|
-
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
|
|
46618
|
-
const resultSymbol = context.scopes.symbolFor(declarator.id);
|
|
46619
|
-
const callbackArgument = getTransparentReactCallbackWrapperArgument(declarator.init, resultSymbol, context.scopes);
|
|
46620
|
-
if (!callbackArgument) continue;
|
|
46621
|
-
const callbackReferences = getDownstreamRefs(analysis, callbackArgument);
|
|
46622
|
-
const callbackReference = callbackReferences.find((candidateReference) => isPropCallbackInvocationRef(analysis, candidateReference));
|
|
46623
|
-
if (callbackReference) return callbackReference;
|
|
46624
|
-
const propReference = callbackReferences.find((candidateReference) => isProp(analysis, candidateReference) && !candidateReference.resolved?.references.some((candidateUsage) => candidateUsage.isWrite() && !candidateUsage.init));
|
|
46625
|
-
if (propReference) return propReference;
|
|
46626
|
-
}
|
|
46627
|
-
return null;
|
|
46628
|
-
};
|
|
46629
45029
|
const isSetterNamedCallbackReceivingData = (callbackRef) => {
|
|
46630
45030
|
const callExpr = getCallExpr(callbackRef);
|
|
46631
45031
|
if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) return false;
|
|
@@ -46661,16 +45061,6 @@ const resolvesToLocalHookReturnBinding = (ref) => Boolean(ref?.resolved?.defs?.s
|
|
|
46661
45061
|
const calleeName = getInitializerCalleeName(node.init);
|
|
46662
45062
|
return calleeName !== null && isReactHookName(calleeName) && !FUNCTION_WRAPPER_HOOK_NAMES.has(calleeName);
|
|
46663
45063
|
}));
|
|
46664
|
-
const getDirectLocalEffectHelper = (callExpression, effectFunction, context) => {
|
|
46665
|
-
const helperFunction = resolveExactLocalFunction(callExpression.callee, context.scopes);
|
|
46666
|
-
if (!helperFunction) return null;
|
|
46667
|
-
let ancestor = callExpression.parent;
|
|
46668
|
-
while (ancestor && ancestor !== effectFunction) {
|
|
46669
|
-
if (isFunctionLike$1(ancestor)) return null;
|
|
46670
|
-
ancestor = ancestor.parent;
|
|
46671
|
-
}
|
|
46672
|
-
return ancestor === effectFunction ? helperFunction : null;
|
|
46673
|
-
};
|
|
46674
45064
|
const noPassLiveStateToParent = defineRule({
|
|
46675
45065
|
id: "no-pass-live-state-to-parent",
|
|
46676
45066
|
title: "Live state pushed to parent via effect",
|
|
@@ -46685,32 +45075,20 @@ const noPassLiveStateToParent = defineRule({
|
|
|
46685
45075
|
if (!effectFnRefs) return;
|
|
46686
45076
|
const effectFn = getEffectFn(analysis, node);
|
|
46687
45077
|
if (!effectFn) return;
|
|
46688
|
-
const effectFunctionBody = isNodeOfType(effectFn, "ArrowFunctionExpression") || isNodeOfType(effectFn, "FunctionExpression") || isNodeOfType(effectFn, "FunctionDeclaration") ? effectFn.body : null;
|
|
46689
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;
|
|
46690
45083
|
const callExpr = getCallExpr(ref);
|
|
46691
|
-
if (!callExpr
|
|
46692
|
-
const directLocalEffectHelper = getDirectLocalEffectHelper(callExpr, effectFn, context);
|
|
46693
|
-
const callGraphReferences = directLocalEffectHelper ? [ref, ...getDownstreamRefs(analysis, directLocalEffectHelper)] : [ref];
|
|
46694
|
-
const resolvedCallbackPropNames = getParentCallbackPropNames({
|
|
46695
|
-
analysis,
|
|
46696
|
-
expression: callExpr.callee,
|
|
46697
|
-
scopes: context.scopes
|
|
46698
|
-
});
|
|
46699
|
-
const callExpressionRoot = findTransparentExpressionRoot(callExpr);
|
|
46700
|
-
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;
|
|
46701
|
-
if (!notificationCallbackPropNames && hasMutableBindingWrite(ref)) continue;
|
|
46702
|
-
const propCallbackRefs = callGraphReferences.flatMap((callGraphReference) => getEventualCallRefsTo(analysis, callGraphReference, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef)));
|
|
46703
|
-
const transparentPropReference = propCallbackRefs.length === 0 ? getTransparentWrapperPropReference(analysis, ref, context) : null;
|
|
46704
|
-
if (propCallbackRefs.length === 0 && !transparentPropReference && !notificationCallbackPropNames) continue;
|
|
46705
|
-
if (!notificationCallbackPropNames && resolvesToLocalHookReturnBinding(ref)) continue;
|
|
46706
|
-
if (!isSynchronous(ref.identifier, effectFn) && !directLocalEffectHelper) continue;
|
|
45084
|
+
if (!callExpr) continue;
|
|
46707
45085
|
if (isCallResultConsumedAsArgument(callExpr)) continue;
|
|
46708
45086
|
const calleeNode = callExpr.callee;
|
|
46709
45087
|
const methodName = calleeNode ? getCallMethodName(calleeNode) : null;
|
|
46710
45088
|
const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && calleeNode && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
|
|
46711
|
-
if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead
|
|
46712
|
-
if (
|
|
46713
|
-
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));
|
|
46714
45092
|
const handsSetterNamedCallbackData = propCallbackRefs.some(isSetterNamedCallbackReceivingData);
|
|
46715
45093
|
if (stateArgRefs.length === 0 && !handsSetterNamedCallbackData) continue;
|
|
46716
45094
|
context.report({
|
|
@@ -47103,7 +45481,6 @@ const isStateLikeDependency = (analysis, element, isPropName) => {
|
|
|
47103
45481
|
if (!analysis) return true;
|
|
47104
45482
|
const reference = getRef(analysis, element);
|
|
47105
45483
|
if (!reference) return true;
|
|
47106
|
-
if (isCustomHookStateResultReference(analysis, reference)) return true;
|
|
47107
45484
|
const upstreamReferences = getUpstreamRefs(analysis, reference);
|
|
47108
45485
|
if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
|
|
47109
45486
|
return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
|
|
@@ -47120,22 +45497,6 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
|
|
|
47120
45497
|
if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
|
|
47121
45498
|
return isPropName(callbackArgument.name) ? callbackArgument.name : null;
|
|
47122
45499
|
};
|
|
47123
|
-
const getTransparentWrappedPropCallbackName = (callExpression, context, isPropName) => {
|
|
47124
|
-
const callee = stripParenExpression(callExpression.callee);
|
|
47125
|
-
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
47126
|
-
const binding = findVariableInitializer(callExpression, callee.name);
|
|
47127
|
-
if (!binding?.initializer) return null;
|
|
47128
|
-
const resultSymbol = context.scopes.symbolFor(callee);
|
|
47129
|
-
const callbackArgument = getTransparentReactCallbackWrapperArgument(binding.initializer, resultSymbol, context.scopes);
|
|
47130
|
-
if (!callbackArgument) return null;
|
|
47131
|
-
const callbackSource = stripParenExpression(callbackArgument);
|
|
47132
|
-
if (isNodeOfType(callbackSource, "Identifier")) return isPropName(callbackSource.name, callbackSource) ? callbackSource.name : null;
|
|
47133
|
-
if (!isNodeOfType(callbackSource, "MemberExpression")) return null;
|
|
47134
|
-
const receiver = stripParenExpression(callbackSource.object);
|
|
47135
|
-
const propertyName = getStaticPropertyName(callbackSource);
|
|
47136
|
-
if (!isNodeOfType(receiver, "Identifier") || !propertyName) return null;
|
|
47137
|
-
return isPropName(receiver.name, receiver) ? propertyName : null;
|
|
47138
|
-
};
|
|
47139
45500
|
const noPropCallbackInEffect = defineRule({
|
|
47140
45501
|
id: "no-prop-callback-in-effect",
|
|
47141
45502
|
title: "Parent kept in sync with a callback effect",
|
|
@@ -47169,16 +45530,9 @@ const noPropCallbackInEffect = defineRule({
|
|
|
47169
45530
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
47170
45531
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
47171
45532
|
const directCallee = stripParenExpression(child.callee);
|
|
47172
|
-
const
|
|
47173
|
-
analysis,
|
|
47174
|
-
expression: directCallee,
|
|
47175
|
-
scopes: context.scopes
|
|
47176
|
-
}) : null;
|
|
47177
|
-
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);
|
|
47178
45534
|
if (!calleeName) return;
|
|
47179
|
-
|
|
47180
|
-
const isDirectEffectReturn = isNodeOfType(callExpressionRoot.parent, "ReturnStatement") && callExpressionRoot.parent.parent === callback.body;
|
|
47181
|
-
if (!isResultDiscardedCall(child) && !isDirectEffectReturn) return;
|
|
45535
|
+
if (!isResultDiscardedCall(child)) return;
|
|
47182
45536
|
if (reportedNodes.has(child)) return;
|
|
47183
45537
|
reportedNodes.add(child);
|
|
47184
45538
|
context.report({
|
|
@@ -48011,69 +46365,6 @@ const noRedundantShouldComponentUpdate = defineRule({
|
|
|
48011
46365
|
}
|
|
48012
46366
|
});
|
|
48013
46367
|
//#endregion
|
|
48014
|
-
//#region src/plugin/rules/correctness/no-ref-callback-cleanup-before-react-19.ts
|
|
48015
|
-
const resolveFunctionExpressions = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
48016
|
-
const expression = stripParenExpression(rawExpression);
|
|
48017
|
-
if (isFunctionLike$1(expression)) return expression.async || expression.generator ? [] : [expression];
|
|
48018
|
-
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
48019
|
-
if (isNodeOfType(expression.test, "Literal")) return resolveFunctionExpressions(expression.test.value ? expression.consequent : expression.alternate, scopes, visitedSymbolIds);
|
|
48020
|
-
return [...resolveFunctionExpressions(expression.consequent, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.alternate, scopes, visitedSymbolIds)];
|
|
48021
|
-
}
|
|
48022
|
-
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
48023
|
-
if (isNodeOfType(expression.left, "Literal")) {
|
|
48024
|
-
const isLeftTruthy = Boolean(expression.left.value);
|
|
48025
|
-
if (expression.operator === "&&" && !isLeftTruthy) return [];
|
|
48026
|
-
if (expression.operator === "||" && isLeftTruthy) return [];
|
|
48027
|
-
if (expression.operator === "??" && expression.left.value !== null) return [];
|
|
48028
|
-
}
|
|
48029
|
-
if (expression.operator === "&&") return resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds);
|
|
48030
|
-
return [...resolveFunctionExpressions(expression.left, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds)];
|
|
48031
|
-
}
|
|
48032
|
-
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
48033
|
-
const finalExpression = expression.expressions.at(-1);
|
|
48034
|
-
return finalExpression ? resolveFunctionExpressions(finalExpression, scopes, visitedSymbolIds) : [];
|
|
48035
|
-
}
|
|
48036
|
-
if (isNodeOfType(expression, "CallExpression")) {
|
|
48037
|
-
if (!isReactApiCall(expression, "useCallback", scopes)) return [];
|
|
48038
|
-
const callback = expression.arguments[0];
|
|
48039
|
-
return callback && !isNodeOfType(callback, "SpreadElement") ? resolveFunctionExpressions(callback, scopes, visitedSymbolIds) : [];
|
|
48040
|
-
}
|
|
48041
|
-
if (!isNodeOfType(expression, "Identifier")) return [];
|
|
48042
|
-
const symbol = scopes.symbolFor(expression);
|
|
48043
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return [];
|
|
48044
|
-
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]));
|
|
48045
|
-
const initializer = getDirectConstInitializer(symbol);
|
|
48046
|
-
if (!initializer) return [];
|
|
48047
|
-
return resolveFunctionExpressions(initializer, scopes, new Set([...visitedSymbolIds, symbol.id]));
|
|
48048
|
-
};
|
|
48049
|
-
const functionReturnsCleanupFunction = (functionExpression, scopes) => {
|
|
48050
|
-
if (!isFunctionLike$1(functionExpression)) return false;
|
|
48051
|
-
if (!isNodeOfType(functionExpression.body, "BlockStatement")) return resolveFunctionExpressions(functionExpression.body, scopes).length > 0;
|
|
48052
|
-
return collectFunctionReturnStatements(functionExpression).some((returnStatement) => Boolean(returnStatement.argument && resolveFunctionExpressions(returnStatement.argument, scopes).length > 0));
|
|
48053
|
-
};
|
|
48054
|
-
const callbackReturnsCleanupFunction = (callback, scopes) => {
|
|
48055
|
-
return resolveFunctionExpressions(callback, scopes).some((functionExpression) => functionReturnsCleanupFunction(functionExpression, scopes));
|
|
48056
|
-
};
|
|
48057
|
-
const noRefCallbackCleanupBeforeReact19 = defineRule({
|
|
48058
|
-
id: "no-ref-callback-cleanup-before-react-19",
|
|
48059
|
-
title: "Ref cleanup requires React 19",
|
|
48060
|
-
requires: ["react:18"],
|
|
48061
|
-
disabledWhen: ["react:19"],
|
|
48062
|
-
severity: "warn",
|
|
48063
|
-
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.",
|
|
48064
|
-
create: (context) => ({ JSXAttribute(node) {
|
|
48065
|
-
if (getJsxAttributeName(node.name) !== "ref") return;
|
|
48066
|
-
if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
|
|
48067
|
-
const callback = node.value.expression;
|
|
48068
|
-
if (!callback || isNodeOfType(callback, "JSXEmptyExpression")) return;
|
|
48069
|
-
if (!callbackReturnsCleanupFunction(callback, context.scopes)) return;
|
|
48070
|
-
context.report({
|
|
48071
|
-
node,
|
|
48072
|
-
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."
|
|
48073
|
-
});
|
|
48074
|
-
} })
|
|
48075
|
-
});
|
|
48076
|
-
//#endregion
|
|
48077
46368
|
//#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
|
|
48078
46369
|
const REPEATED_ANCESTOR_TYPES = new Set([
|
|
48079
46370
|
"DoWhileStatement",
|
|
@@ -54834,6 +53125,12 @@ const isInsideEs6Component$1 = (methodDefinition) => {
|
|
|
54834
53125
|
if (!owningClass) return false;
|
|
54835
53126
|
return isPreactOrReactComponentClass(owningClass);
|
|
54836
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
|
+
};
|
|
54837
53134
|
const preactNoRenderArguments = defineRule({
|
|
54838
53135
|
id: "preact-no-render-arguments",
|
|
54839
53136
|
title: "render() reads props from arguments",
|
|
@@ -58035,39 +56332,8 @@ const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node,
|
|
|
58035
56332
|
destructureIndex: 1
|
|
58036
56333
|
});
|
|
58037
56334
|
//#endregion
|
|
58038
|
-
//#region src/plugin/utils/unwrap-return-expression.ts
|
|
58039
|
-
const unwrapReturnExpression = (node) => isNodeOfType(node, "ReturnStatement") && node.argument ? node.argument : node;
|
|
58040
|
-
//#endregion
|
|
58041
56335
|
//#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
|
|
58042
56336
|
const USE_EFFECT_ONLY = new Set(["useEffect"]);
|
|
58043
|
-
const USE_CALLBACK_ONLY = new Set(["useCallback"]);
|
|
58044
|
-
const USE_STATE_ONLY = new Set(["useState"]);
|
|
58045
|
-
const REACT_API_CALL_OPTIONS = {
|
|
58046
|
-
allowGlobalReactNamespace: true,
|
|
58047
|
-
allowUnboundBareCalls: true,
|
|
58048
|
-
resolveNamedAliases: true
|
|
58049
|
-
};
|
|
58050
|
-
const expressionReadsDerivedSymbol = (context, expression, stateDerivedSymbolIds) => {
|
|
58051
|
-
let readsDerivedSymbol = false;
|
|
58052
|
-
walkAst(expression, (node) => {
|
|
58053
|
-
if (readsDerivedSymbol) return false;
|
|
58054
|
-
if (node !== expression && isFunctionLike$1(node)) return false;
|
|
58055
|
-
if (isNodeOfType(node, "Identifier") && stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) readsDerivedSymbol = true;
|
|
58056
|
-
});
|
|
58057
|
-
return readsDerivedSymbol;
|
|
58058
|
-
};
|
|
58059
|
-
const getStaticObjectPropertyName = (property) => {
|
|
58060
|
-
if (!isNodeOfType(property, "Property") || property.computed || property.method || property.kind !== "init") return null;
|
|
58061
|
-
if (isNodeOfType(property.key, "Identifier")) return property.key.name;
|
|
58062
|
-
if (isNodeOfType(property.key, "Literal") && (typeof property.key.value === "string" || typeof property.key.value === "number")) return String(property.key.value);
|
|
58063
|
-
return null;
|
|
58064
|
-
};
|
|
58065
|
-
const isNonVisibleJsxSpreadProperty = (propertyName) => propertyName === "id" || propertyName.startsWith("aria-") || /^on[A-Z]/.test(propertyName);
|
|
58066
|
-
const isTransparentAssignmentTarget = (identifier) => {
|
|
58067
|
-
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
58068
|
-
const parent = expressionRoot.parent;
|
|
58069
|
-
return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expressionRoot || isNodeOfType(parent, "UpdateExpression") && parent.argument === expressionRoot || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expressionRoot);
|
|
58070
|
-
};
|
|
58071
56337
|
const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
|
|
58072
56338
|
let readsCurrent = false;
|
|
58073
56339
|
walkAst(argument, (child) => {
|
|
@@ -58119,166 +56385,6 @@ const isStateUsedOnlyInIdOrAriaAttributes = (setterCall, setterName) => {
|
|
|
58119
56385
|
});
|
|
58120
56386
|
return referenceCount > 0 && !nonAriaReferenceFound;
|
|
58121
56387
|
};
|
|
58122
|
-
const isGlobalWindowMember = (context, node, propertyName) => {
|
|
58123
|
-
const member = stripParenExpression(node);
|
|
58124
|
-
if (!isNodeOfType(member, "MemberExpression") || member.computed) return false;
|
|
58125
|
-
const receiver = stripParenExpression(member.object);
|
|
58126
|
-
return isNodeOfType(receiver, "Identifier") && receiver.name === "window" && context.scopes.isGlobalReference(receiver) && isNodeOfType(member.property, "Identifier") && member.property.name === propertyName;
|
|
58127
|
-
};
|
|
58128
|
-
const getDirectWindowWidthSetter = (context, statement) => {
|
|
58129
|
-
const call = unwrapDiscardedExpression(statement);
|
|
58130
|
-
if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 1) return null;
|
|
58131
|
-
if (!isNodeOfType(call.callee, "Identifier") || !isSetterCall(call)) return null;
|
|
58132
|
-
const argument = call.arguments[0];
|
|
58133
|
-
return isGlobalWindowMember(context, argument, "innerWidth") ? call : null;
|
|
58134
|
-
};
|
|
58135
|
-
const getResizeListenerHandler = (context, statement, methodName) => {
|
|
58136
|
-
const call = unwrapDiscardedExpression(statement);
|
|
58137
|
-
if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 2) return null;
|
|
58138
|
-
if (!isGlobalWindowMember(context, call.callee, methodName)) return null;
|
|
58139
|
-
const eventName = call.arguments[0];
|
|
58140
|
-
const handler = call.arguments[1];
|
|
58141
|
-
if (!isNodeOfType(eventName, "Literal") || eventName.value !== "resize") return null;
|
|
58142
|
-
return isNodeOfType(handler, "Identifier") ? handler : null;
|
|
58143
|
-
};
|
|
58144
|
-
const getCleanupResizeHandler = (context, statement) => {
|
|
58145
|
-
if (!isNodeOfType(statement, "ReturnStatement") || !isFunctionLike$1(statement.argument)) return null;
|
|
58146
|
-
const cleanupStatements = getCallbackStatements(statement.argument);
|
|
58147
|
-
if (cleanupStatements.length !== 1) return null;
|
|
58148
|
-
return getResizeListenerHandler(context, unwrapReturnExpression(cleanupStatements[0]), "removeEventListener");
|
|
58149
|
-
};
|
|
58150
|
-
const findExactViewportState = (context, componentFunction, setterCall) => {
|
|
58151
|
-
if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
|
|
58152
|
-
const componentBody = componentFunction.body;
|
|
58153
|
-
if (!isNodeOfType(setterCall.callee, "Identifier")) return null;
|
|
58154
|
-
const setterSymbol = context.scopes.symbolFor(setterCall.callee);
|
|
58155
|
-
if (!setterSymbol || setterSymbol.kind !== "const" || !isNodeOfType(setterSymbol.declarationNode, "VariableDeclarator")) return null;
|
|
58156
|
-
const declarator = setterSymbol.declarationNode;
|
|
58157
|
-
if (!isNodeOfType(declarator.id, "ArrayPattern")) return null;
|
|
58158
|
-
const stateIdentifier = declarator.id.elements?.[0];
|
|
58159
|
-
const setterIdentifier = declarator.id.elements?.[1];
|
|
58160
|
-
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;
|
|
58161
|
-
const initializer = declarator.init.arguments?.[0];
|
|
58162
|
-
if (!isNodeOfType(initializer, "Literal") || initializer.value !== 0) return null;
|
|
58163
|
-
const stateSymbol = context.scopes.symbolFor(stateIdentifier);
|
|
58164
|
-
if (!stateSymbol) return null;
|
|
58165
|
-
const stateDerivedSymbolIds = new Set([stateSymbol.id]);
|
|
58166
|
-
let didAddDerivedSymbol = true;
|
|
58167
|
-
while (didAddDerivedSymbol) {
|
|
58168
|
-
didAddDerivedSymbol = false;
|
|
58169
|
-
for (const statement of componentBody.body ?? []) {
|
|
58170
|
-
if (!isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
58171
|
-
for (const candidateDeclarator of statement.declarations ?? []) {
|
|
58172
|
-
if (!isNodeOfType(candidateDeclarator.id, "Identifier") || !candidateDeclarator.init) continue;
|
|
58173
|
-
const candidateInitializer = stripParenExpression(candidateDeclarator.init);
|
|
58174
|
-
if (isFunctionLike$1(candidateInitializer) || isNodeOfType(candidateInitializer, "CallExpression") && isReactApiCall(candidateInitializer, USE_CALLBACK_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) continue;
|
|
58175
|
-
if (!expressionReadsDerivedSymbol(context, candidateInitializer, stateDerivedSymbolIds)) continue;
|
|
58176
|
-
const candidateSymbol = context.scopes.symbolFor(candidateDeclarator.id);
|
|
58177
|
-
if (candidateSymbol?.kind === "const" && candidateSymbol.references.every((reference) => reference.flag === "read" && !isTransparentAssignmentTarget(reference.identifier)) && !stateDerivedSymbolIds.has(candidateSymbol.id)) {
|
|
58178
|
-
stateDerivedSymbolIds.add(candidateSymbol.id);
|
|
58179
|
-
didAddDerivedSymbol = true;
|
|
58180
|
-
}
|
|
58181
|
-
}
|
|
58182
|
-
}
|
|
58183
|
-
}
|
|
58184
|
-
const staticSpreadVisibilityBySymbolId = /* @__PURE__ */ new Map();
|
|
58185
|
-
const hasOnlyStaticObjectReferences = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
58186
|
-
const symbol = context.scopes.symbolFor(identifier);
|
|
58187
|
-
if (!symbol) return false;
|
|
58188
|
-
if (visitedSymbolIds.has(symbol.id)) return true;
|
|
58189
|
-
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
58190
|
-
nextVisitedSymbolIds.add(symbol.id);
|
|
58191
|
-
let hasUnknownReference = false;
|
|
58192
|
-
walkAst(componentBody, (node) => {
|
|
58193
|
-
if (hasUnknownReference || !isNodeOfType(node, "Identifier") || context.scopes.symbolFor(node)?.id !== symbol.id || node === symbol.bindingIdentifier) return;
|
|
58194
|
-
const referenceRoot = findTransparentExpressionRoot(node);
|
|
58195
|
-
const parent = referenceRoot.parent;
|
|
58196
|
-
if (isNodeOfType(parent, "JSXSpreadAttribute") && parent.argument === referenceRoot) return;
|
|
58197
|
-
if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier") && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const" && hasOnlyStaticObjectReferences(parent.id, nextVisitedSymbolIds)) return;
|
|
58198
|
-
hasUnknownReference = true;
|
|
58199
|
-
return false;
|
|
58200
|
-
});
|
|
58201
|
-
return !hasUnknownReference;
|
|
58202
|
-
};
|
|
58203
|
-
const classifyStaticSpreadObject = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
58204
|
-
const symbol = context.scopes.symbolFor(identifier);
|
|
58205
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return "unknown";
|
|
58206
|
-
const cachedVisibility = staticSpreadVisibilityBySymbolId.get(symbol.id);
|
|
58207
|
-
if (cachedVisibility) return cachedVisibility;
|
|
58208
|
-
if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || symbol.declarationNode.id !== symbol.bindingIdentifier || !symbol.declarationNode.init) return "unknown";
|
|
58209
|
-
if (!hasOnlyStaticObjectReferences(identifier)) return "unknown";
|
|
58210
|
-
const initializer = stripParenExpression(symbol.declarationNode.init);
|
|
58211
|
-
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
58212
|
-
nextVisitedSymbolIds.add(symbol.id);
|
|
58213
|
-
if (isNodeOfType(initializer, "Identifier")) {
|
|
58214
|
-
const visibility = classifyStaticSpreadObject(initializer, nextVisitedSymbolIds);
|
|
58215
|
-
staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
|
|
58216
|
-
return visibility;
|
|
58217
|
-
}
|
|
58218
|
-
if (!isNodeOfType(initializer, "ObjectExpression")) return "unknown";
|
|
58219
|
-
let visibility = "non-visible";
|
|
58220
|
-
for (const property of initializer.properties ?? []) {
|
|
58221
|
-
const propertyName = getStaticObjectPropertyName(property);
|
|
58222
|
-
if (!isNodeOfType(property, "Property") || !propertyName) {
|
|
58223
|
-
visibility = "unknown";
|
|
58224
|
-
break;
|
|
58225
|
-
}
|
|
58226
|
-
if (expressionReadsDerivedSymbol(context, property.value, stateDerivedSymbolIds) && !isNonVisibleJsxSpreadProperty(propertyName)) visibility = "visible";
|
|
58227
|
-
}
|
|
58228
|
-
staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
|
|
58229
|
-
return visibility;
|
|
58230
|
-
};
|
|
58231
|
-
let hasNonAriaReference = false;
|
|
58232
|
-
walkAst(componentBody, (node) => {
|
|
58233
|
-
if (hasNonAriaReference) return false;
|
|
58234
|
-
if (!isNodeOfType(node, "Identifier") || !stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) return;
|
|
58235
|
-
if (findEnclosingFunction$1(node) !== componentFunction) return;
|
|
58236
|
-
const parent = node.parent;
|
|
58237
|
-
if (parent && (isNodeOfType(parent, "MemberExpression") && parent.property === node && !parent.computed || isNodeOfType(parent, "Property") && parent.key === node && !parent.computed)) return;
|
|
58238
|
-
let cursor = parent;
|
|
58239
|
-
while (cursor && cursor !== componentBody) {
|
|
58240
|
-
if (isFunctionLike$1(cursor)) return;
|
|
58241
|
-
if (isNodeOfType(cursor, "JSXSpreadAttribute")) {
|
|
58242
|
-
if (isNodeOfType(node, "Identifier") && classifyStaticSpreadObject(node) === "visible") hasNonAriaReference = true;
|
|
58243
|
-
return;
|
|
58244
|
-
}
|
|
58245
|
-
if (isNodeOfType(cursor, "JSXAttribute")) {
|
|
58246
|
-
if (isEventHandlerAttribute(cursor)) return;
|
|
58247
|
-
if (!isInsideIdOrAriaAttribute(node)) hasNonAriaReference = true;
|
|
58248
|
-
return;
|
|
58249
|
-
}
|
|
58250
|
-
if (isNodeOfType(cursor, "ReturnStatement")) {
|
|
58251
|
-
hasNonAriaReference = true;
|
|
58252
|
-
return;
|
|
58253
|
-
}
|
|
58254
|
-
cursor = cursor.parent;
|
|
58255
|
-
}
|
|
58256
|
-
});
|
|
58257
|
-
return hasNonAriaReference ? stateIdentifier.name : null;
|
|
58258
|
-
};
|
|
58259
|
-
const isExactViewportSubscriptionEffect = (context, effectCall, callback) => {
|
|
58260
|
-
if (!isReactApiCall(effectCall, USE_EFFECT_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return false;
|
|
58261
|
-
if (!isFunctionLike$1(callback) || callback.async || !isNodeOfType(callback.body, "BlockStatement")) return false;
|
|
58262
|
-
const statements = getCallbackStatements(callback);
|
|
58263
|
-
if (statements.length !== 4) return false;
|
|
58264
|
-
const handlerDeclaration = statements[0];
|
|
58265
|
-
if (!isNodeOfType(handlerDeclaration, "VariableDeclaration") || handlerDeclaration.kind !== "const" || handlerDeclaration.declarations?.length !== 1) return false;
|
|
58266
|
-
const handlerDeclarator = handlerDeclaration.declarations[0];
|
|
58267
|
-
if (!isNodeOfType(handlerDeclarator.id, "Identifier") || !isFunctionLike$1(handlerDeclarator.init)) return false;
|
|
58268
|
-
const handlerStatements = getCallbackStatements(handlerDeclarator.init);
|
|
58269
|
-
if (handlerStatements.length !== 1) return false;
|
|
58270
|
-
const handlerSetter = getDirectWindowWidthSetter(context, unwrapReturnExpression(handlerStatements[0]));
|
|
58271
|
-
const subscribedHandler = getResizeListenerHandler(context, statements[1], "addEventListener");
|
|
58272
|
-
const immediateSetter = getDirectWindowWidthSetter(context, statements[2]);
|
|
58273
|
-
const cleanupHandler = getCleanupResizeHandler(context, statements[3]);
|
|
58274
|
-
if (!handlerSetter || !subscribedHandler || !immediateSetter || !cleanupHandler) return false;
|
|
58275
|
-
const handlerSymbol = context.scopes.symbolFor(handlerDeclarator.id);
|
|
58276
|
-
if (!handlerSymbol || context.scopes.symbolFor(subscribedHandler) !== handlerSymbol || context.scopes.symbolFor(cleanupHandler) !== handlerSymbol) return false;
|
|
58277
|
-
if (!isNodeOfType(handlerSetter.callee, "Identifier") || !isNodeOfType(immediateSetter.callee, "Identifier") || context.scopes.symbolFor(handlerSetter.callee) !== context.scopes.symbolFor(immediateSetter.callee)) return false;
|
|
58278
|
-
const componentFunction = findEnclosingFunction$1(effectCall);
|
|
58279
|
-
if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return false;
|
|
58280
|
-
return findExactViewportState(context, componentFunction, immediateSetter) !== null;
|
|
58281
|
-
};
|
|
58282
56388
|
const renderingHydrationNoFlicker = defineRule({
|
|
58283
56389
|
id: "rendering-hydration-no-flicker",
|
|
58284
56390
|
title: "useEffect setState flashes on mount",
|
|
@@ -58291,14 +56397,7 @@ const renderingHydrationNoFlicker = defineRule({
|
|
|
58291
56397
|
if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
|
|
58292
56398
|
const callback = getEffectCallback(node);
|
|
58293
56399
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
58294
|
-
|
|
58295
|
-
context.report({
|
|
58296
|
-
node,
|
|
58297
|
-
message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
|
|
58298
|
-
});
|
|
58299
|
-
return;
|
|
58300
|
-
}
|
|
58301
|
-
const bodyStatements = getCallbackStatements(callback);
|
|
56400
|
+
const bodyStatements = (isNodeOfType(callback.body, "BlockStatement") ? callback.body.body ?? [] : [callback.body]).filter((statement) => !isNoOpStatement(statement));
|
|
58302
56401
|
if (bodyStatements.length !== 1) return;
|
|
58303
56402
|
const soleStatement = bodyStatements[0];
|
|
58304
56403
|
if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
|
|
@@ -58461,125 +56560,6 @@ const ASYNC_DATA_CALLEE_NAMES = new Set([
|
|
|
58461
56560
|
const RESOURCE_LOAD_EVENT_ATTRIBUTE_PATTERN = /^on(?:Load|Error|Abort|Progress|CanPlay|Stalled|Suspend|Waiting|Ended)/;
|
|
58462
56561
|
const JSX_EVENT_HANDLER_ATTRIBUTE_PATTERN = /^on[A-Z]/;
|
|
58463
56562
|
const REDUX_DISPATCH_HOOK_PATTERN = /^use\w*Dispatch$/;
|
|
58464
|
-
const FILE_READER_READ_METHOD_NAMES = new Set([
|
|
58465
|
-
"readAsArrayBuffer",
|
|
58466
|
-
"readAsBinaryString",
|
|
58467
|
-
"readAsDataURL",
|
|
58468
|
-
"readAsText"
|
|
58469
|
-
]);
|
|
58470
|
-
const isGlobalFileReaderConstruction = (expression, context) => {
|
|
58471
|
-
if (!expression) return false;
|
|
58472
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
58473
|
-
if (!isNodeOfType(unwrappedExpression, "NewExpression") || !isNodeOfType(unwrappedExpression.callee, "Identifier")) return false;
|
|
58474
|
-
return unwrappedExpression.callee.name === "FileReader" && context.scopes.isGlobalReference(unwrappedExpression.callee);
|
|
58475
|
-
};
|
|
58476
|
-
const getFileReaderOriginStartBefore = (readerSymbol, readCall, context) => {
|
|
58477
|
-
const readFunction = findEnclosingFunction$1(readCall);
|
|
58478
|
-
let latestValue = null;
|
|
58479
|
-
let latestStart = null;
|
|
58480
|
-
if (readerSymbol.initializer && findEnclosingFunction$1(readerSymbol.declarationNode) === readFunction && readerSymbol.declarationNode.range[0] < readCall.range[0]) {
|
|
58481
|
-
latestValue = readerSymbol.initializer;
|
|
58482
|
-
latestStart = readerSymbol.declarationNode.range[0];
|
|
58483
|
-
}
|
|
58484
|
-
for (const reference of readerSymbol.references) {
|
|
58485
|
-
if (reference.flag === "read" || reference.identifier.range[0] >= readCall.range[0] || latestStart !== null && reference.identifier.range[0] <= latestStart || findEnclosingFunction$1(reference.identifier) !== readFunction) continue;
|
|
58486
|
-
const assignment = reference.identifier.parent;
|
|
58487
|
-
if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== reference.identifier) continue;
|
|
58488
|
-
latestValue = assignment.right;
|
|
58489
|
-
latestStart = reference.identifier.range[0];
|
|
58490
|
-
}
|
|
58491
|
-
return isGlobalFileReaderConstruction(latestValue, context) ? latestStart : null;
|
|
58492
|
-
};
|
|
58493
|
-
const resolveLoadingCompletionFunction = (expression, context) => {
|
|
58494
|
-
const directFunction = resolveExactLocalFunction(expression, context.scopes);
|
|
58495
|
-
if (directFunction) return directFunction;
|
|
58496
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
58497
|
-
if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
|
|
58498
|
-
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
58499
|
-
const initializer = symbol ? getDirectUnreassignedInitializer(symbol) : null;
|
|
58500
|
-
if (!initializer || !isNodeOfType(initializer, "CallExpression") || !isReactApiCall(initializer, "useCallback", context.scopes)) return null;
|
|
58501
|
-
const callback = initializer.arguments?.[0];
|
|
58502
|
-
return callback && isFunctionLike$1(callback) ? callback : null;
|
|
58503
|
-
};
|
|
58504
|
-
const isSetterBooleanCall = (node, setterSymbol, value, context) => {
|
|
58505
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
58506
|
-
const callee = stripParenExpression(node.callee);
|
|
58507
|
-
const argument = node.arguments?.[0];
|
|
58508
|
-
const unwrappedArgument = argument ? stripParenExpression(argument) : null;
|
|
58509
|
-
return Boolean(isNodeOfType(callee, "Identifier") && context.scopes.symbolFor(callee) === setterSymbol && unwrappedArgument && isNodeOfType(unwrappedArgument, "Literal") && unwrappedArgument.value === value);
|
|
58510
|
-
};
|
|
58511
|
-
const functionClearsLoadingState = (functionNode, setterSymbol, context, visitedFunctions) => {
|
|
58512
|
-
if (visitedFunctions.has(functionNode) || !isFunctionLike$1(functionNode)) return false;
|
|
58513
|
-
visitedFunctions.add(functionNode);
|
|
58514
|
-
let didClearLoadingState = false;
|
|
58515
|
-
walkAst(functionNode.body, (child) => {
|
|
58516
|
-
if (didClearLoadingState) return false;
|
|
58517
|
-
if (child !== functionNode.body && isFunctionLike$1(child)) return false;
|
|
58518
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
58519
|
-
if (isSetterBooleanCall(child, setterSymbol, false, context)) {
|
|
58520
|
-
didClearLoadingState = true;
|
|
58521
|
-
return false;
|
|
58522
|
-
}
|
|
58523
|
-
const helperFunction = resolveLoadingCompletionFunction(child.callee, context);
|
|
58524
|
-
if (helperFunction && functionClearsLoadingState(helperFunction, setterSymbol, context, visitedFunctions)) {
|
|
58525
|
-
didClearLoadingState = true;
|
|
58526
|
-
return false;
|
|
58527
|
-
}
|
|
58528
|
-
});
|
|
58529
|
-
return didClearLoadingState;
|
|
58530
|
-
};
|
|
58531
|
-
const getLatestFileReaderCallbackBefore = (readCall, readerSymbol, propertyName, originStart, context) => {
|
|
58532
|
-
const readFunction = findEnclosingFunction$1(readCall);
|
|
58533
|
-
if (!readFunction || !isFunctionLike$1(readFunction)) return null;
|
|
58534
|
-
let callback = null;
|
|
58535
|
-
let callbackStart = originStart;
|
|
58536
|
-
walkAst(readFunction.body, (child) => {
|
|
58537
|
-
if (child !== readFunction.body && isFunctionLike$1(child)) return false;
|
|
58538
|
-
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;
|
|
58539
|
-
const receiver = stripParenExpression(child.left.object);
|
|
58540
|
-
if (!isNodeOfType(receiver, "Identifier") || context.scopes.symbolFor(receiver) !== readerSymbol) return;
|
|
58541
|
-
callback = child.right;
|
|
58542
|
-
callbackStart = child.range[0];
|
|
58543
|
-
});
|
|
58544
|
-
return callback;
|
|
58545
|
-
};
|
|
58546
|
-
const setterStartsLoadingBefore = (readCall, setterSymbol, context) => {
|
|
58547
|
-
const readFunction = findEnclosingFunction$1(readCall);
|
|
58548
|
-
if (!readFunction || !isFunctionLike$1(readFunction)) return false;
|
|
58549
|
-
let didStartLoading = false;
|
|
58550
|
-
walkAst(readFunction.body, (child) => {
|
|
58551
|
-
if (didStartLoading) return false;
|
|
58552
|
-
if (child !== readFunction.body && isFunctionLike$1(child)) return false;
|
|
58553
|
-
if (!isNodeOfType(child, "CallExpression") || child.range[0] >= readCall.range[0]) return;
|
|
58554
|
-
if (isSetterBooleanCall(child, setterSymbol, true, context)) {
|
|
58555
|
-
didStartLoading = true;
|
|
58556
|
-
return false;
|
|
58557
|
-
}
|
|
58558
|
-
});
|
|
58559
|
-
return didStartLoading;
|
|
58560
|
-
};
|
|
58561
|
-
const setterTracksFileReader = (functionBody, setterSymbol, context) => {
|
|
58562
|
-
let didFindFileReaderLifecycle = false;
|
|
58563
|
-
walkAst(functionBody, (child) => {
|
|
58564
|
-
if (didFindFileReaderLifecycle) return false;
|
|
58565
|
-
if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || !FILE_READER_READ_METHOD_NAMES.has(getStaticPropertyName(child.callee) ?? "")) return;
|
|
58566
|
-
const receiver = stripParenExpression(child.callee.object);
|
|
58567
|
-
if (!isNodeOfType(receiver, "Identifier")) return;
|
|
58568
|
-
const readerSymbol = context.scopes.symbolFor(receiver);
|
|
58569
|
-
if (!readerSymbol) return;
|
|
58570
|
-
const originStart = getFileReaderOriginStartBefore(readerSymbol, child, context);
|
|
58571
|
-
if (originStart === null || !setterStartsLoadingBefore(child, setterSymbol, context)) return;
|
|
58572
|
-
const loadCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onload", originStart, context);
|
|
58573
|
-
const errorCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onerror", originStart, context);
|
|
58574
|
-
const loadFunction = loadCallback ? resolveLoadingCompletionFunction(loadCallback, context) : null;
|
|
58575
|
-
const errorFunction = errorCallback ? resolveLoadingCompletionFunction(errorCallback, context) : null;
|
|
58576
|
-
if (loadFunction && errorFunction && functionClearsLoadingState(loadFunction, setterSymbol, context, /* @__PURE__ */ new Set()) && functionClearsLoadingState(errorFunction, setterSymbol, context, /* @__PURE__ */ new Set())) {
|
|
58577
|
-
didFindFileReaderLifecycle = true;
|
|
58578
|
-
return false;
|
|
58579
|
-
}
|
|
58580
|
-
});
|
|
58581
|
-
return didFindFileReaderLifecycle;
|
|
58582
|
-
};
|
|
58583
56563
|
const hasAsyncLoadingWork = (fnBody, setterName) => {
|
|
58584
56564
|
let found = false;
|
|
58585
56565
|
walkAst(fnBody, (child) => {
|
|
@@ -58753,8 +56733,6 @@ const renderingUsetransitionLoading = defineRule({
|
|
|
58753
56733
|
const fnBody = enclosingFunctionBody(node);
|
|
58754
56734
|
if (fnBody && hasAsyncLoadingWork(fnBody, setterName)) return;
|
|
58755
56735
|
if (fnBody && setterName) {
|
|
58756
|
-
const setterSymbol = isNodeOfType(secondBinding, "Identifier") ? context.scopes.symbolFor(secondBinding) : null;
|
|
58757
|
-
if (setterSymbol && setterTracksFileReader(fnBody, setterSymbol, context)) return;
|
|
58758
56736
|
if (setterEscapes(fnBody, setterName, node)) return;
|
|
58759
56737
|
if (setterCalledAlongsideAsyncSignal(fnBody, setterName)) return;
|
|
58760
56738
|
if (setterCalledInEventListenerHandler(fnBody, setterName)) return;
|
|
@@ -68251,6 +66229,14 @@ const isStateKey = (key) => {
|
|
|
68251
66229
|
if (isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value === "state";
|
|
68252
66230
|
return false;
|
|
68253
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
|
+
};
|
|
68254
66240
|
const isInConstructor = (node) => {
|
|
68255
66241
|
let ancestor = node.parent;
|
|
68256
66242
|
while (ancestor) {
|
|
@@ -72962,17 +70948,6 @@ const reactDoctorRules = [
|
|
|
72962
70948
|
requires: [...new Set(["react", ...noRedundantShouldComponentUpdate.requires ?? []])]
|
|
72963
70949
|
}
|
|
72964
70950
|
},
|
|
72965
|
-
{
|
|
72966
|
-
key: "react-doctor/no-ref-callback-cleanup-before-react-19",
|
|
72967
|
-
id: "no-ref-callback-cleanup-before-react-19",
|
|
72968
|
-
source: "react-doctor",
|
|
72969
|
-
originallyExternal: false,
|
|
72970
|
-
rule: {
|
|
72971
|
-
...noRefCallbackCleanupBeforeReact19,
|
|
72972
|
-
framework: "global",
|
|
72973
|
-
category: "Bugs"
|
|
72974
|
-
}
|
|
72975
|
-
},
|
|
72976
70951
|
{
|
|
72977
70952
|
key: "react-doctor/no-ref-current-in-render",
|
|
72978
70953
|
id: "no-ref-current-in-render",
|