oxlint-plugin-react-doctor 0.7.9-dev.a16e452 → 0.7.9-dev.a6e4fb5
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 +46 -0
- package/dist/index.js +1482 -177
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6736,6 +6736,28 @@ const asyncParallel = defineRule({
|
|
|
6736
6736
|
}
|
|
6737
6737
|
});
|
|
6738
6738
|
//#endregion
|
|
6739
|
+
//#region src/plugin/utils/collect-function-return-statements.ts
|
|
6740
|
+
const collectFunctionReturnStatements = (functionNode) => {
|
|
6741
|
+
if (!isFunctionLike$1(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return [];
|
|
6742
|
+
const returnStatements = [];
|
|
6743
|
+
walkAst(functionNode.body, (node) => {
|
|
6744
|
+
if (node !== functionNode.body && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
6745
|
+
if (isNodeOfType(node, "ReturnStatement")) returnStatements.push(node);
|
|
6746
|
+
});
|
|
6747
|
+
return returnStatements;
|
|
6748
|
+
};
|
|
6749
|
+
//#endregion
|
|
6750
|
+
//#region src/plugin/utils/is-nullish-expression.ts
|
|
6751
|
+
const isNullishExpression = (expression) => isNodeOfType(expression, "Literal") && expression.value === null || isNodeOfType(expression, "Identifier") && expression.name === "undefined" || isNodeOfType(expression, "UnaryExpression") && expression.operator === "void";
|
|
6752
|
+
//#endregion
|
|
6753
|
+
//#region src/plugin/utils/strip-this-parameter.ts
|
|
6754
|
+
const stripThisParameter = (parameters) => {
|
|
6755
|
+
const firstParameter = parameters[0];
|
|
6756
|
+
if (!firstParameter) return parameters;
|
|
6757
|
+
if (isNodeOfType(firstParameter, "Identifier") && firstParameter.name === "this") return parameters.slice(1);
|
|
6758
|
+
return parameters;
|
|
6759
|
+
};
|
|
6760
|
+
//#endregion
|
|
6739
6761
|
//#region src/plugin/rules/security/auth-token-in-web-storage.ts
|
|
6740
6762
|
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.";
|
|
6741
6763
|
const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
|
|
@@ -6747,12 +6769,8 @@ const STORAGE_GLOBALS = new Set([
|
|
|
6747
6769
|
const SENSITIVE_KEY_PATTERN = /token|jwt|secret|password|passwd|credential|api[-_]?key|bearer|private[-_]?key/i;
|
|
6748
6770
|
const NON_AUTH_TOKEN_PATTERN = /csrf|xsrf|device|fcm|apns|push|design|tokeniz|syntax|css|theme|color/i;
|
|
6749
6771
|
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;
|
|
6752
6772
|
const isAuthCredentialKey = (key) => {
|
|
6753
|
-
if (PRODUCT_API_KEY_RECORDS_PATTERN.test(key)) return false;
|
|
6754
6773
|
if (!SENSITIVE_KEY_PATTERN.test(key)) return false;
|
|
6755
|
-
if (PRODUCT_API_KEY_COLLECTION_PATTERN.test(key)) return false;
|
|
6756
6774
|
if (NON_AUTH_TOKEN_PATTERN.test(key) && !STRONG_AUTH_KEY_PATTERN.test(key)) return false;
|
|
6757
6775
|
return true;
|
|
6758
6776
|
};
|
|
@@ -6761,11 +6779,63 @@ const isDirectWebStorageObject = (node) => {
|
|
|
6761
6779
|
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);
|
|
6762
6780
|
return false;
|
|
6763
6781
|
};
|
|
6764
|
-
const
|
|
6765
|
-
if (
|
|
6766
|
-
|
|
6767
|
-
const binding = findVariableInitializer(
|
|
6768
|
-
|
|
6782
|
+
const immutableInitializer = (identifier, visitedIdentifiers = /* @__PURE__ */ new Set()) => {
|
|
6783
|
+
if (visitedIdentifiers.has(identifier)) return null;
|
|
6784
|
+
visitedIdentifiers.add(identifier);
|
|
6785
|
+
const binding = findVariableInitializer(identifier, identifier.name);
|
|
6786
|
+
if (!binding?.initializer) return null;
|
|
6787
|
+
if (isNodeOfType(binding.initializer, "FunctionDeclaration")) return binding.initializer;
|
|
6788
|
+
const declarator = binding.bindingIdentifier.parent;
|
|
6789
|
+
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return null;
|
|
6790
|
+
const declaration = declarator.parent;
|
|
6791
|
+
if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) return null;
|
|
6792
|
+
if (declaration.kind !== "const") return null;
|
|
6793
|
+
const initializer = stripParenExpression(binding.initializer);
|
|
6794
|
+
if (!isNodeOfType(initializer, "Identifier")) return initializer;
|
|
6795
|
+
return immutableInitializer(initializer, visitedIdentifiers) ?? initializer;
|
|
6796
|
+
};
|
|
6797
|
+
const isWebStorageFactoryResult = (node, visitedNodes) => {
|
|
6798
|
+
const expression = stripParenExpression(node);
|
|
6799
|
+
if (isWebStorageObject(expression, new Set(visitedNodes))) return true;
|
|
6800
|
+
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
6801
|
+
const consequent = stripParenExpression(expression.consequent);
|
|
6802
|
+
const alternate = stripParenExpression(expression.alternate);
|
|
6803
|
+
return (isNullishExpression(consequent) || isWebStorageFactoryResult(consequent, visitedNodes)) && (isNullishExpression(alternate) || isWebStorageFactoryResult(alternate, visitedNodes)) && (!isNullishExpression(consequent) || !isNullishExpression(alternate));
|
|
6804
|
+
}
|
|
6805
|
+
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
6806
|
+
if (expression.operator === "&&") return isWebStorageFactoryResult(expression.right, visitedNodes);
|
|
6807
|
+
const left = stripParenExpression(expression.left);
|
|
6808
|
+
const right = stripParenExpression(expression.right);
|
|
6809
|
+
const isLeftStorage = isWebStorageFactoryResult(left, visitedNodes);
|
|
6810
|
+
const isRightStorage = isWebStorageFactoryResult(right, visitedNodes);
|
|
6811
|
+
return (isLeftStorage || isNullishExpression(left)) && (isRightStorage || isNullishExpression(right)) && (isLeftStorage || isRightStorage);
|
|
6812
|
+
}
|
|
6813
|
+
return false;
|
|
6814
|
+
};
|
|
6815
|
+
const isWebStorageObject = (node, visitedNodes = /* @__PURE__ */ new Set()) => {
|
|
6816
|
+
const expression = stripParenExpression(node);
|
|
6817
|
+
if (visitedNodes.has(expression)) return false;
|
|
6818
|
+
visitedNodes.add(expression);
|
|
6819
|
+
if (isDirectWebStorageObject(expression)) return true;
|
|
6820
|
+
if (isNodeOfType(expression, "Identifier")) {
|
|
6821
|
+
const initializer = immutableInitializer(expression);
|
|
6822
|
+
return initializer ? isWebStorageObject(initializer, new Set(visitedNodes)) : false;
|
|
6823
|
+
}
|
|
6824
|
+
if (!isNodeOfType(expression, "CallExpression")) return false;
|
|
6825
|
+
const callee = stripParenExpression(expression.callee);
|
|
6826
|
+
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
6827
|
+
const factory = immutableInitializer(callee);
|
|
6828
|
+
if (!isFunctionLike$1(factory)) return false;
|
|
6829
|
+
if (isNodeOfType(factory, "ArrowFunctionExpression") && !isNodeOfType(factory.body, "BlockStatement")) return isWebStorageFactoryResult(factory.body, visitedNodes);
|
|
6830
|
+
let didReturnWebStorage = false;
|
|
6831
|
+
for (const returnStatement of collectFunctionReturnStatements(factory)) {
|
|
6832
|
+
if (!returnStatement.argument) continue;
|
|
6833
|
+
const strippedReturn = stripParenExpression(returnStatement.argument);
|
|
6834
|
+
if (isNullishExpression(strippedReturn)) continue;
|
|
6835
|
+
if (!isWebStorageFactoryResult(strippedReturn, visitedNodes)) return false;
|
|
6836
|
+
didReturnWebStorage = true;
|
|
6837
|
+
}
|
|
6838
|
+
return didReturnWebStorage;
|
|
6769
6839
|
};
|
|
6770
6840
|
const resolveStaticKeyString = (node) => {
|
|
6771
6841
|
if (isNodeOfType(node, "Literal") && typeof node.value === "string") return node.value;
|
|
@@ -6785,6 +6855,56 @@ const staticMemberName = (member) => {
|
|
|
6785
6855
|
if (member.computed && isNodeOfType(member.property, "Literal") && typeof member.property.value === "string") return member.property.value;
|
|
6786
6856
|
return null;
|
|
6787
6857
|
};
|
|
6858
|
+
const parameterIndex = (expression, parameterSymbolIds, scopes, canUnwrapSerialization, visitedNodes = /* @__PURE__ */ new Set()) => {
|
|
6859
|
+
const strippedExpression = stripParenExpression(expression);
|
|
6860
|
+
if (visitedNodes.has(strippedExpression)) return null;
|
|
6861
|
+
visitedNodes.add(strippedExpression);
|
|
6862
|
+
if (isNodeOfType(strippedExpression, "Identifier")) {
|
|
6863
|
+
const directSymbolId = scopes.symbolFor(strippedExpression)?.id;
|
|
6864
|
+
const directIndex = directSymbolId === void 0 ? -1 : parameterSymbolIds.indexOf(directSymbolId);
|
|
6865
|
+
if (directIndex !== -1) return directIndex;
|
|
6866
|
+
const initializer = immutableInitializer(strippedExpression);
|
|
6867
|
+
return initializer ? parameterIndex(initializer, parameterSymbolIds, scopes, canUnwrapSerialization, visitedNodes) : null;
|
|
6868
|
+
}
|
|
6869
|
+
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") {
|
|
6870
|
+
const serializedArgument = strippedExpression.arguments[0];
|
|
6871
|
+
return serializedArgument ? parameterIndex(serializedArgument, parameterSymbolIds, scopes, true, visitedNodes) : null;
|
|
6872
|
+
}
|
|
6873
|
+
return null;
|
|
6874
|
+
};
|
|
6875
|
+
const storageHelperSinkCache = /* @__PURE__ */ new WeakMap();
|
|
6876
|
+
const findStorageHelperSinks = (functionNode, scopes) => {
|
|
6877
|
+
const cachedSinks = storageHelperSinkCache.get(functionNode);
|
|
6878
|
+
if (cachedSinks) return cachedSinks;
|
|
6879
|
+
if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) {
|
|
6880
|
+
storageHelperSinkCache.set(functionNode, []);
|
|
6881
|
+
return [];
|
|
6882
|
+
}
|
|
6883
|
+
const parameterSymbolIds = stripThisParameter(functionNode.params).map((parameter) => {
|
|
6884
|
+
const strippedParameter = stripParenExpression(parameter);
|
|
6885
|
+
const identifier = isNodeOfType(strippedParameter, "Identifier") ? strippedParameter : isNodeOfType(strippedParameter, "AssignmentPattern") && isNodeOfType(strippedParameter.left, "Identifier") ? strippedParameter.left : null;
|
|
6886
|
+
return identifier ? scopes.symbolFor(identifier)?.id ?? null : null;
|
|
6887
|
+
});
|
|
6888
|
+
const helperSinks = [];
|
|
6889
|
+
walkAst(functionNode.body, (child) => {
|
|
6890
|
+
if (child !== functionNode.body && isFunctionLike$1(child)) return false;
|
|
6891
|
+
if (!isNodeOfType(child, "CallExpression")) return;
|
|
6892
|
+
const callee = stripParenExpression(child.callee);
|
|
6893
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "setItem" || !isWebStorageObject(callee.object)) return;
|
|
6894
|
+
const keyExpression = child.arguments[0];
|
|
6895
|
+
const valueExpression = child.arguments[1];
|
|
6896
|
+
if (!keyExpression || !valueExpression) return;
|
|
6897
|
+
const keyParameterIndex = parameterIndex(keyExpression, parameterSymbolIds, scopes, false);
|
|
6898
|
+
const valueParameterIndex = parameterIndex(valueExpression, parameterSymbolIds, scopes, true);
|
|
6899
|
+
if (keyParameterIndex === null || valueParameterIndex === null) return;
|
|
6900
|
+
helperSinks.push({
|
|
6901
|
+
keyParameterIndex,
|
|
6902
|
+
valueParameterIndex
|
|
6903
|
+
});
|
|
6904
|
+
});
|
|
6905
|
+
storageHelperSinkCache.set(functionNode, helperSinks);
|
|
6906
|
+
return helperSinks;
|
|
6907
|
+
};
|
|
6788
6908
|
const authTokenInWebStorage = defineRule({
|
|
6789
6909
|
id: "auth-token-in-web-storage",
|
|
6790
6910
|
title: "Auth token in web storage",
|
|
@@ -6792,14 +6912,23 @@ const authTokenInWebStorage = defineRule({
|
|
|
6792
6912
|
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.",
|
|
6793
6913
|
create: skipNonProductionFiles((context) => ({
|
|
6794
6914
|
CallExpression(node) {
|
|
6795
|
-
const callee = node.callee;
|
|
6796
|
-
|
|
6797
|
-
if (!isNodeOfType(callee.property, "Identifier")
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
if (
|
|
6801
|
-
|
|
6802
|
-
|
|
6915
|
+
const callee = stripParenExpression(node.callee);
|
|
6916
|
+
const keyArguments = [];
|
|
6917
|
+
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "setItem" && isWebStorageObject(stripParenExpression(callee.object))) {
|
|
6918
|
+
const keyArgument = node.arguments[0];
|
|
6919
|
+
if (keyArgument) keyArguments.push(keyArgument);
|
|
6920
|
+
} else if (isNodeOfType(callee, "Identifier")) {
|
|
6921
|
+
const helperFunction = immutableInitializer(callee);
|
|
6922
|
+
const helperSinks = helperFunction ? findStorageHelperSinks(helperFunction, context.scopes) : [];
|
|
6923
|
+
for (const helperSink of helperSinks) {
|
|
6924
|
+
const keyArgument = node.arguments[helperSink.keyParameterIndex];
|
|
6925
|
+
if (keyArgument && node.arguments[helperSink.valueParameterIndex]) keyArguments.push(keyArgument);
|
|
6926
|
+
}
|
|
6927
|
+
}
|
|
6928
|
+
if (!keyArguments.some((keyArgument) => {
|
|
6929
|
+
const keyString = resolveStaticKeyString(keyArgument);
|
|
6930
|
+
return keyString !== null && isAuthCredentialKey(keyString);
|
|
6931
|
+
})) return;
|
|
6803
6932
|
context.report({
|
|
6804
6933
|
node,
|
|
6805
6934
|
message: MESSAGE$60
|
|
@@ -7047,9 +7176,6 @@ const isCreateElementCall = (node) => {
|
|
|
7047
7176
|
return false;
|
|
7048
7177
|
};
|
|
7049
7178
|
//#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
|
|
7053
7179
|
//#region src/plugin/rules/react-builtins/button-has-type.ts
|
|
7054
7180
|
const MISSING_MESSAGE$2 = "Your users can submit the form by accident because a `<button>` with no `type` defaults to submit.";
|
|
7055
7181
|
const INVALID_MESSAGE = "This button has an invalid `type`, so the browser may treat it like a submit button.";
|
|
@@ -7511,6 +7637,7 @@ const areExpressionsStructurallyEqual = (a, b) => {
|
|
|
7511
7637
|
if (a.type !== b.type) return false;
|
|
7512
7638
|
if (isNodeOfType(a, "ThisExpression")) return true;
|
|
7513
7639
|
if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
|
|
7640
|
+
if (isNodeOfType(a, "PrivateIdentifier") && isNodeOfType(b, "PrivateIdentifier")) return a.name === b.name;
|
|
7514
7641
|
if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
|
|
7515
7642
|
if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
|
|
7516
7643
|
if (a.computed !== b.computed) return false;
|
|
@@ -8566,17 +8693,6 @@ const createMethodMutationAnalysis = (context) => {
|
|
|
8566
8693
|
//#region src/plugin/utils/is-member-property.ts
|
|
8567
8694
|
const isMemberProperty = (node, propertyName) => Boolean(node && isNodeOfType(node, "MemberExpression") && isNodeOfType(node.property, "Identifier") && node.property.name === propertyName);
|
|
8568
8695
|
//#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
|
|
8580
8696
|
//#region src/plugin/utils/statement-always-exits.ts
|
|
8581
8697
|
const statementAlwaysExits = (statement) => {
|
|
8582
8698
|
if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement")) return true;
|
|
@@ -8843,7 +8959,7 @@ const isReactNamespaceImport = (identifier, scopes) => {
|
|
|
8843
8959
|
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
8844
8960
|
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
8845
8961
|
};
|
|
8846
|
-
const isReactNamespaceReceiver = (receiver, scopes, options) => {
|
|
8962
|
+
const isReactNamespaceReceiver$1 = (receiver, scopes, options) => {
|
|
8847
8963
|
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
8848
8964
|
if (isReactNamespaceImport(receiver, scopes)) return true;
|
|
8849
8965
|
return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
|
|
@@ -8856,7 +8972,7 @@ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) =>
|
|
|
8856
8972
|
for (const property of pattern.properties) {
|
|
8857
8973
|
if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
|
|
8858
8974
|
const propertyName = getStaticPropertyKeyName(property);
|
|
8859
|
-
return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver(stripParenExpression(symbol.initializer), scopes, options));
|
|
8975
|
+
return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver$1(stripParenExpression(symbol.initializer), scopes, options));
|
|
8860
8976
|
}
|
|
8861
8977
|
return false;
|
|
8862
8978
|
};
|
|
@@ -8880,7 +8996,7 @@ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds
|
|
|
8880
8996
|
return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
|
|
8881
8997
|
}
|
|
8882
8998
|
if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
|
|
8883
|
-
return isReactNamespaceReceiver(stripParenExpression(callee.object), scopes, options);
|
|
8999
|
+
return isReactNamespaceReceiver$1(stripParenExpression(callee.object), scopes, options);
|
|
8884
9000
|
};
|
|
8885
9001
|
//#endregion
|
|
8886
9002
|
//#region src/plugin/utils/is-proven-browser-api-receiver.ts
|
|
@@ -14831,7 +14947,8 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
14831
14947
|
const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
|
|
14832
14948
|
if (!releaseHandler) return releaseVerbName === "off";
|
|
14833
14949
|
const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
|
|
14834
|
-
|
|
14950
|
+
const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignature ? 0 : 1] : null;
|
|
14951
|
+
return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
|
|
14835
14952
|
}
|
|
14836
14953
|
if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
|
|
14837
14954
|
return true;
|
|
@@ -14856,6 +14973,25 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
|
|
|
14856
14973
|
if (!symbol) return false;
|
|
14857
14974
|
return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
|
|
14858
14975
|
};
|
|
14976
|
+
const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
|
|
14977
|
+
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;
|
|
14978
|
+
const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
|
|
14979
|
+
const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
|
|
14980
|
+
if (!isNodeOfType(releaseCall, "CallExpression")) return false;
|
|
14981
|
+
const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
|
|
14982
|
+
if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
|
|
14983
|
+
const ownerFunction = findEnclosingFunction$1(releaseFunction);
|
|
14984
|
+
if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
|
|
14985
|
+
const triggerRegistrations = [];
|
|
14986
|
+
walkAst(ownerFunction.body, (child) => {
|
|
14987
|
+
if (child !== ownerFunction.body && isFunctionLike$1(child)) return false;
|
|
14988
|
+
if (!isNodeOfType(child, "CallExpression")) return;
|
|
14989
|
+
const registrationDetails = getCallRegistrationDetails(child, context);
|
|
14990
|
+
if (registrationDetails.registrationVerbName === "addEventListener" && registrationDetails.receiverKey === usage.receiverKey && resolveStableValue(child.arguments?.[1], context) === releaseFunction) triggerRegistrations.push(child);
|
|
14991
|
+
});
|
|
14992
|
+
if (triggerRegistrations.some((triggerRegistration) => triggerRegistration === usage.node)) return true;
|
|
14993
|
+
return doMatchingNodesCoverEveryPathAfterUsage(usage.node, triggerRegistrations, context) || doMatchingNodesCoverEveryPathBeforeUsage(usage.node, triggerRegistrations, ownerFunction, context);
|
|
14994
|
+
};
|
|
14859
14995
|
const isReleaseReachableForUsage = (releaseNode, usage, context) => {
|
|
14860
14996
|
if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
|
|
14861
14997
|
const releaseFunction = findEnclosingFunction$1(releaseNode);
|
|
@@ -14863,6 +14999,7 @@ const isReleaseReachableForUsage = (releaseNode, usage, context) => {
|
|
|
14863
14999
|
if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
|
|
14864
15000
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
14865
15001
|
if (usageFunction && isFunctionLike$1(usageFunction) && getAssignedReactRefSymbol(usageFunction, context) && isCleanupFunctionReferencedByReturn(usageFunction, releaseFunction, context)) return isReactRefCallbackCleanupOwnedByEffect(usageFunction, releaseFunction, usage, context);
|
|
15002
|
+
if (isSelfReleasingListenerRelease(releaseNode, releaseFunction, usage, context)) return true;
|
|
14866
15003
|
return isPotentiallyReachableFunction(releaseFunction, context);
|
|
14867
15004
|
};
|
|
14868
15005
|
const fileContainsReleaseForUsage = (usage, context) => {
|
|
@@ -16996,7 +17133,7 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allow
|
|
|
16996
17133
|
keys.add(depKey);
|
|
16997
17134
|
continue;
|
|
16998
17135
|
}
|
|
16999
|
-
const identitySourceKeys = resolveReactiveIdentitySourceKeys(symbol, scopes);
|
|
17136
|
+
const identitySourceKeys = resolvePureCalledFunctionSourceKeys(reference, symbol, scopes) ?? resolveRenderDerivedMutableSourceKeys(reference, symbol, scopes) ?? resolveReactiveIdentitySourceKeys(symbol, scopes);
|
|
17000
17137
|
if (identitySourceKeys) {
|
|
17001
17138
|
if (identitySourceKeys.size === 0) stableCapturedNames.add(depKey);
|
|
17002
17139
|
for (const identitySourceKey of identitySourceKeys) keys.add(identitySourceKey);
|
|
@@ -17079,6 +17216,161 @@ const resolveReactiveIdentitySourceKeys = (symbol, scopes) => {
|
|
|
17079
17216
|
if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier || symbol.references.some((reference) => reference.flag !== "read")) return null;
|
|
17080
17217
|
return resolveIdentitySourceKeysFromExpression(symbol.initializer, scopes, new Set([symbol.id]));
|
|
17081
17218
|
};
|
|
17219
|
+
const isPureDerivedExpression = (expression) => {
|
|
17220
|
+
const candidate = unwrapExpression$3(expression);
|
|
17221
|
+
if (isNodeOfType(candidate, "Literal") || isNodeOfType(candidate, "Identifier")) return true;
|
|
17222
|
+
if (isNodeOfType(candidate, "MemberExpression")) return isPureDerivedExpression(candidate.object) && (!candidate.computed || isPureDerivedExpression(candidate.property));
|
|
17223
|
+
if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return isPureDerivedExpression(candidate.left) && isPureDerivedExpression(candidate.right);
|
|
17224
|
+
if (isNodeOfType(candidate, "UnaryExpression")) return candidate.operator !== "delete" && isPureDerivedExpression(candidate.argument);
|
|
17225
|
+
if (isNodeOfType(candidate, "ConditionalExpression")) return isPureDerivedExpression(candidate.test) && isPureDerivedExpression(candidate.consequent) && isPureDerivedExpression(candidate.alternate);
|
|
17226
|
+
if (isNodeOfType(candidate, "TemplateLiteral")) return candidate.expressions.every((nestedExpression) => isPureDerivedExpression(nestedExpression));
|
|
17227
|
+
return false;
|
|
17228
|
+
};
|
|
17229
|
+
const isPureDerivedStatement = (statement) => {
|
|
17230
|
+
if (isNodeOfType(statement, "BlockStatement")) return statement.body.every((nestedStatement) => isPureDerivedStatement(nestedStatement));
|
|
17231
|
+
if (isNodeOfType(statement, "ReturnStatement")) return !statement.argument || isPureDerivedExpression(statement.argument);
|
|
17232
|
+
if (isNodeOfType(statement, "IfStatement")) return isPureDerivedExpression(statement.test) && isPureDerivedStatement(statement.consequent) && (!statement.alternate || isPureDerivedStatement(statement.alternate));
|
|
17233
|
+
return false;
|
|
17234
|
+
};
|
|
17235
|
+
const isPureDerivedFunction = (functionNode) => {
|
|
17236
|
+
if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return false;
|
|
17237
|
+
if (functionNode.async || functionNode.generator) return false;
|
|
17238
|
+
return isNodeOfType(functionNode.body, "BlockStatement") ? isPureDerivedStatement(functionNode.body) : isPureDerivedExpression(functionNode.body);
|
|
17239
|
+
};
|
|
17240
|
+
const resolvePureCalledFunctionSourceKeys = (reference, symbol, scopes) => {
|
|
17241
|
+
if (symbol.references.some((symbolReference) => symbolReference.flag !== "read")) return null;
|
|
17242
|
+
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
17243
|
+
const callExpression = referenceRoot.parent;
|
|
17244
|
+
if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== referenceRoot) return null;
|
|
17245
|
+
const functionNode = getFunctionValueNode(symbol);
|
|
17246
|
+
if (!functionNode || !isPureDerivedFunction(functionNode)) return null;
|
|
17247
|
+
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17248
|
+
for (const capturedReference of closureCaptures(functionNode, scopes)) {
|
|
17249
|
+
const capturedSymbol = capturedReference.resolvedSymbol;
|
|
17250
|
+
if (!capturedSymbol || capturedSymbol.id === symbol.id) continue;
|
|
17251
|
+
if (isOutsideAllFunctions(capturedSymbol) || symbolHasStableValue(capturedSymbol, scopes)) continue;
|
|
17252
|
+
const capturedKey = computeDepKey(capturedReference);
|
|
17253
|
+
if (!capturedKey) return null;
|
|
17254
|
+
if (capturedKey === capturedSymbol.name) {
|
|
17255
|
+
const nestedSourceKeys = resolveReactiveIdentitySourceKeys(capturedSymbol, scopes);
|
|
17256
|
+
if (nestedSourceKeys) {
|
|
17257
|
+
for (const nestedSourceKey of nestedSourceKeys) sourceKeys.add(nestedSourceKey);
|
|
17258
|
+
continue;
|
|
17259
|
+
}
|
|
17260
|
+
}
|
|
17261
|
+
sourceKeys.add(capturedKey);
|
|
17262
|
+
}
|
|
17263
|
+
return sourceKeys.size > 0 ? sourceKeys : null;
|
|
17264
|
+
};
|
|
17265
|
+
const mergeDerivedExpressionSourceKeys = (expressions, scopes, visitedSymbolIds) => {
|
|
17266
|
+
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17267
|
+
for (const expression of expressions) {
|
|
17268
|
+
const expressionSourceKeys = resolveDerivedExpressionSourceKeys(expression, scopes, visitedSymbolIds);
|
|
17269
|
+
if (!expressionSourceKeys) return null;
|
|
17270
|
+
for (const expressionSourceKey of expressionSourceKeys) sourceKeys.add(expressionSourceKey);
|
|
17271
|
+
}
|
|
17272
|
+
return sourceKeys;
|
|
17273
|
+
};
|
|
17274
|
+
const resolveDerivedExpressionSourceKeys = (expression, scopes, visitedSymbolIds) => {
|
|
17275
|
+
const candidate = unwrapExpression$3(expression);
|
|
17276
|
+
if (isNodeOfType(candidate, "Literal")) return /* @__PURE__ */ new Set();
|
|
17277
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
17278
|
+
if (scopes.isGlobalReference(candidate)) return /* @__PURE__ */ new Set();
|
|
17279
|
+
const sourceSymbol = scopes.symbolFor(candidate);
|
|
17280
|
+
if (!sourceSymbol) return null;
|
|
17281
|
+
if (isOutsideAllFunctions(sourceSymbol) || symbolHasStableValue(sourceSymbol, scopes)) return /* @__PURE__ */ new Set();
|
|
17282
|
+
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)) {
|
|
17283
|
+
visitedSymbolIds.add(sourceSymbol.id);
|
|
17284
|
+
const sourceKeys = resolveDerivedExpressionSourceKeys(sourceSymbol.initializer, scopes, visitedSymbolIds);
|
|
17285
|
+
visitedSymbolIds.delete(sourceSymbol.id);
|
|
17286
|
+
if (sourceKeys) return sourceKeys;
|
|
17287
|
+
}
|
|
17288
|
+
return new Set([sourceSymbol.name]);
|
|
17289
|
+
}
|
|
17290
|
+
if (isNodeOfType(candidate, "MemberExpression")) {
|
|
17291
|
+
if (hasComputedMemberExpression(candidate)) return null;
|
|
17292
|
+
const sourceKey = stringifyMemberChain(candidate);
|
|
17293
|
+
const rootIdentifier = getMemberRootIdentifier(candidate);
|
|
17294
|
+
const rootSymbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
|
|
17295
|
+
if (!sourceKey || !rootSymbol) return null;
|
|
17296
|
+
if (isOutsideAllFunctions(rootSymbol) || symbolHasStableValue(rootSymbol, scopes)) return /* @__PURE__ */ new Set();
|
|
17297
|
+
return new Set([sourceKey]);
|
|
17298
|
+
}
|
|
17299
|
+
if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return mergeDerivedExpressionSourceKeys([candidate.left, candidate.right], scopes, visitedSymbolIds);
|
|
17300
|
+
if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator !== "delete") return resolveDerivedExpressionSourceKeys(candidate.argument, scopes, visitedSymbolIds);
|
|
17301
|
+
if (isNodeOfType(candidate, "ConditionalExpression")) return mergeDerivedExpressionSourceKeys([
|
|
17302
|
+
candidate.test,
|
|
17303
|
+
candidate.consequent,
|
|
17304
|
+
candidate.alternate
|
|
17305
|
+
], scopes, visitedSymbolIds);
|
|
17306
|
+
if (isNodeOfType(candidate, "TemplateLiteral")) return mergeDerivedExpressionSourceKeys(candidate.expressions, scopes, visitedSymbolIds);
|
|
17307
|
+
if (isNodeOfType(candidate, "NewExpression")) {
|
|
17308
|
+
const callee = unwrapExpression$3(candidate.callee);
|
|
17309
|
+
if (!isNodeOfType(callee, "Identifier") || callee.name !== "Error" || !scopes.isGlobalReference(callee)) return null;
|
|
17310
|
+
const argumentsToAnalyze = [];
|
|
17311
|
+
for (const argument of candidate.arguments) {
|
|
17312
|
+
if (!isAstNode(argument) || isNodeOfType(argument, "SpreadElement")) return null;
|
|
17313
|
+
argumentsToAnalyze.push(argument);
|
|
17314
|
+
}
|
|
17315
|
+
return mergeDerivedExpressionSourceKeys(argumentsToAnalyze, scopes, visitedSymbolIds);
|
|
17316
|
+
}
|
|
17317
|
+
return null;
|
|
17318
|
+
};
|
|
17319
|
+
const resolveWriteControlSourceKeys = (assignment, boundaryFunction, scopes) => {
|
|
17320
|
+
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17321
|
+
let currentNode = assignment;
|
|
17322
|
+
while (currentNode.parent && currentNode.parent !== boundaryFunction) {
|
|
17323
|
+
const parentNode = currentNode.parent;
|
|
17324
|
+
if (isNodeOfType(parentNode, "IfStatement")) {
|
|
17325
|
+
if (parentNode.test === currentNode) return null;
|
|
17326
|
+
const testSourceKeys = resolveDerivedExpressionSourceKeys(parentNode.test, scopes, /* @__PURE__ */ new Set());
|
|
17327
|
+
if (!testSourceKeys) return null;
|
|
17328
|
+
for (const testSourceKey of testSourceKeys) sourceKeys.add(testSourceKey);
|
|
17329
|
+
} else if (!isNodeOfType(parentNode, "ExpressionStatement") && !isNodeOfType(parentNode, "BlockStatement")) return null;
|
|
17330
|
+
currentNode = parentNode;
|
|
17331
|
+
}
|
|
17332
|
+
return currentNode.parent === boundaryFunction ? sourceKeys : null;
|
|
17333
|
+
};
|
|
17334
|
+
const isReadOnlyInitialStateUse = (referenceNode, scopes) => {
|
|
17335
|
+
const referenceRoot = findTransparentExpressionRoot(referenceNode);
|
|
17336
|
+
const callExpression = referenceRoot.parent;
|
|
17337
|
+
return isNodeOfType(callExpression, "CallExpression") && callExpression.arguments.some((argument) => argument === referenceRoot) && isReactApiCall(callExpression, "useState", scopes, {
|
|
17338
|
+
allowGlobalReactNamespace: true,
|
|
17339
|
+
allowUnboundBareCalls: true,
|
|
17340
|
+
resolveNamedAliases: true
|
|
17341
|
+
});
|
|
17342
|
+
};
|
|
17343
|
+
const resolveRenderDerivedMutableSourceKeys = (capturedReference, symbol, scopes) => {
|
|
17344
|
+
if (symbol.kind !== "let" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
17345
|
+
const boundaryFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
17346
|
+
if (!boundaryFunction) return null;
|
|
17347
|
+
const capturingFunction = findEnclosingFunction$1(capturedReference.identifier);
|
|
17348
|
+
if (!capturingFunction || capturingFunction === boundaryFunction) return null;
|
|
17349
|
+
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17350
|
+
if (symbol.initializer) {
|
|
17351
|
+
const initializerSourceKeys = resolveDerivedExpressionSourceKeys(symbol.initializer, scopes, new Set([symbol.id]));
|
|
17352
|
+
if (!initializerSourceKeys) return null;
|
|
17353
|
+
for (const initializerSourceKey of initializerSourceKeys) sourceKeys.add(initializerSourceKey);
|
|
17354
|
+
}
|
|
17355
|
+
let writeCount = 0;
|
|
17356
|
+
for (const symbolReference of symbol.references) {
|
|
17357
|
+
if (symbolReference.flag === "read") {
|
|
17358
|
+
if (findEnclosingFunction$1(symbolReference.identifier) !== capturingFunction && !isReadOnlyInitialStateUse(symbolReference.identifier, scopes)) return null;
|
|
17359
|
+
continue;
|
|
17360
|
+
}
|
|
17361
|
+
if (symbolReference.flag !== "write") return null;
|
|
17362
|
+
const referenceRoot = findTransparentExpressionRoot(symbolReference.identifier);
|
|
17363
|
+
const assignment = referenceRoot.parent;
|
|
17364
|
+
if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== referenceRoot || findEnclosingFunction$1(referenceRoot) !== boundaryFunction) return null;
|
|
17365
|
+
const assignmentSourceKeys = resolveDerivedExpressionSourceKeys(assignment.right, scopes, new Set([symbol.id]));
|
|
17366
|
+
const controlSourceKeys = resolveWriteControlSourceKeys(assignment, boundaryFunction, scopes);
|
|
17367
|
+
if (!assignmentSourceKeys || !controlSourceKeys) return null;
|
|
17368
|
+
for (const assignmentSourceKey of assignmentSourceKeys) sourceKeys.add(assignmentSourceKey);
|
|
17369
|
+
for (const controlSourceKey of controlSourceKeys) sourceKeys.add(controlSourceKey);
|
|
17370
|
+
writeCount += 1;
|
|
17371
|
+
}
|
|
17372
|
+
return writeCount > 0 && sourceKeys.size > 0 ? sourceKeys : null;
|
|
17373
|
+
};
|
|
17082
17374
|
const isUseCallbackResultDep = (node, scopes) => {
|
|
17083
17375
|
const rootSymbol = getRootSymbol(node, scopes);
|
|
17084
17376
|
const initializer = rootSymbol?.initializer ? unwrapExpression$3(rootSymbol.initializer) : null;
|
|
@@ -29306,7 +29598,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
29306
29598
|
//#endregion
|
|
29307
29599
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
29308
29600
|
const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
29309
|
-
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
29601
|
+
const isUndefinedIdentifier$1 = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
29310
29602
|
const noAccessKey = defineRule({
|
|
29311
29603
|
id: "no-access-key",
|
|
29312
29604
|
title: "accessKey attribute used",
|
|
@@ -29331,7 +29623,7 @@ const noAccessKey = defineRule({
|
|
|
29331
29623
|
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
29332
29624
|
const expression = attributeValue.expression;
|
|
29333
29625
|
if (!expression || expression.type === "JSXEmptyExpression") return;
|
|
29334
|
-
if (isUndefinedIdentifier(expression)) return;
|
|
29626
|
+
if (isUndefinedIdentifier$1(expression)) return;
|
|
29335
29627
|
context.report({
|
|
29336
29628
|
node: accessKey,
|
|
29337
29629
|
message: MESSAGE$39
|
|
@@ -30096,6 +30388,12 @@ const isReactNamespaceImportReference = (ref) => Boolean(ref?.resolved?.defs.som
|
|
|
30096
30388
|
const importDeclaration = declarationNode.parent;
|
|
30097
30389
|
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
|
|
30098
30390
|
}));
|
|
30391
|
+
const isReactNamespaceReceiver = (analysis, node) => {
|
|
30392
|
+
const receiver = stripParenExpression(node);
|
|
30393
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
30394
|
+
const namespaceReference = getRef(analysis, receiver);
|
|
30395
|
+
return namespaceReference?.resolved ? isReactNamespaceImportReference(namespaceReference) : receiver.name === "React";
|
|
30396
|
+
};
|
|
30099
30397
|
const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
|
|
30100
30398
|
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return false;
|
|
30101
30399
|
const callee = stripParenExpression(declarator.init.callee);
|
|
@@ -30104,24 +30402,20 @@ const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
|
|
|
30104
30402
|
if (!reference?.resolved) return callee.name === hookName;
|
|
30105
30403
|
return isReactNamedImportReference(reference, hookName);
|
|
30106
30404
|
}
|
|
30107
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.
|
|
30108
|
-
|
|
30109
|
-
if (!namespaceReference?.resolved) return callee.object.name === "React";
|
|
30110
|
-
return isReactNamespaceImportReference(namespaceReference);
|
|
30405
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
|
|
30406
|
+
return isReactNamespaceReceiver(analysis, callee.object);
|
|
30111
30407
|
};
|
|
30112
30408
|
const isHookCallee$1 = (analysis, node, hookName) => {
|
|
30113
30409
|
if (!node) return false;
|
|
30114
30410
|
if (isNodeOfType(node, "Identifier")) {
|
|
30115
30411
|
if (node.name === hookName) return true;
|
|
30116
30412
|
if (isReactNamedImportReference(getRef(analysis, node), hookName)) return true;
|
|
30117
|
-
const
|
|
30118
|
-
|
|
30413
|
+
const receiverRoot = findTransparentExpressionRoot(node);
|
|
30414
|
+
const parent = receiverRoot.parent;
|
|
30415
|
+
if (parent && isNodeOfType(parent, "MemberExpression") && parent.object === receiverRoot && isReactNamespaceReceiver(analysis, node) && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
|
|
30119
30416
|
return false;
|
|
30120
30417
|
}
|
|
30121
|
-
if (isNodeOfType(node, "MemberExpression"))
|
|
30122
|
-
const receiver = stripParenExpression(node.object);
|
|
30123
|
-
return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
|
|
30124
|
-
}
|
|
30418
|
+
if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
|
|
30125
30419
|
return false;
|
|
30126
30420
|
};
|
|
30127
30421
|
const isUseEffect = (node) => {
|
|
@@ -30545,7 +30839,88 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
|
|
|
30545
30839
|
if (HANDLER_BINDING_NAME_PATTERN.test(bindingName)) return true;
|
|
30546
30840
|
return isSetterWiredToJsxHandler(componentFunction, bindingName);
|
|
30547
30841
|
};
|
|
30548
|
-
const
|
|
30842
|
+
const isSynchronousFunction = (functionNode) => {
|
|
30843
|
+
const functionMetadata = functionNode;
|
|
30844
|
+
return functionMetadata.async !== true && functionMetadata.generator !== true;
|
|
30845
|
+
};
|
|
30846
|
+
const findBindingVariable = (analysis, bindingIdentifier) => {
|
|
30847
|
+
for (const scope of analysis.scopeManager.scopes) for (const variable of scope.variables) if (variable.identifiers.includes(bindingIdentifier)) return variable;
|
|
30848
|
+
return null;
|
|
30849
|
+
};
|
|
30850
|
+
const getImmutableFunctionVariable = (analysis, componentFunction, functionNode) => {
|
|
30851
|
+
if (!isSynchronousFunction(functionNode) || !isAstDescendant(functionNode, componentFunction)) return null;
|
|
30852
|
+
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
30853
|
+
if (!bindingIdentifier) return null;
|
|
30854
|
+
const variable = findBindingVariable(analysis, bindingIdentifier);
|
|
30855
|
+
if (!variable || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && !reference.init)) return null;
|
|
30856
|
+
const definition = variable.defs[0];
|
|
30857
|
+
if (definition.type === "FunctionName") return definition.node === functionNode ? variable : null;
|
|
30858
|
+
if (definition.type !== "Variable") return null;
|
|
30859
|
+
const declarator = definition.node;
|
|
30860
|
+
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return null;
|
|
30861
|
+
if (declarator.init === functionNode) return variable;
|
|
30862
|
+
if (isNodeOfType(declarator.init, "CallExpression") && declarator.init.arguments?.[0] === functionNode && isGenuineReactHookDeclarator(analysis, declarator, "useCallback")) return variable;
|
|
30863
|
+
return null;
|
|
30864
|
+
};
|
|
30865
|
+
const getJsxEventValueAttribute = (identifier) => {
|
|
30866
|
+
const expression = findTransparentExpressionRoot(identifier);
|
|
30867
|
+
const expressionContainer = expression.parent;
|
|
30868
|
+
if (!isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== expression) return null;
|
|
30869
|
+
const attribute = expressionContainer.parent;
|
|
30870
|
+
if (!isNodeOfType(attribute, "JSXAttribute")) return null;
|
|
30871
|
+
const attributeName = getJsxAttributeName(attribute.name);
|
|
30872
|
+
return attributeName && isEventHandlerName(attributeName) ? attribute : null;
|
|
30873
|
+
};
|
|
30874
|
+
const getInlineJsxEventCallbackAttribute = (callExpression) => {
|
|
30875
|
+
const callbackFunction = findEnclosingFunction$1(callExpression);
|
|
30876
|
+
if (!callbackFunction || !isSynchronousFunction(callbackFunction)) return null;
|
|
30877
|
+
return getJsxEventValueAttribute(callbackFunction);
|
|
30878
|
+
};
|
|
30879
|
+
const isReactHookDependencyReference = (identifier) => {
|
|
30880
|
+
const expression = findTransparentExpressionRoot(identifier);
|
|
30881
|
+
const dependencyArray = expression.parent;
|
|
30882
|
+
if (!isNodeOfType(dependencyArray, "ArrayExpression") || !(dependencyArray.elements ?? []).includes(expression)) return false;
|
|
30883
|
+
const hookCall = dependencyArray.parent;
|
|
30884
|
+
if (!isNodeOfType(hookCall, "CallExpression") || hookCall.arguments?.[1] !== dependencyArray) return false;
|
|
30885
|
+
const callee = hookCall.callee;
|
|
30886
|
+
if (isNodeOfType(callee, "Identifier")) return /^use[A-Z0-9]/.test(callee.name);
|
|
30887
|
+
return Boolean(isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && /^use[A-Z0-9]/.test(callee.property.name));
|
|
30888
|
+
};
|
|
30889
|
+
const hasReachableJsxEventCallPath = (analysis, context, componentFunction, functionVariable, visitedVariables) => {
|
|
30890
|
+
if (visitedVariables.has(functionVariable)) return false;
|
|
30891
|
+
const nextVisitedVariables = new Set(visitedVariables).add(functionVariable);
|
|
30892
|
+
const callExpressions = [];
|
|
30893
|
+
let hasDirectJsxEventReference = false;
|
|
30894
|
+
for (const reference of functionVariable.references) {
|
|
30895
|
+
if (reference.init) continue;
|
|
30896
|
+
const identifier = reference.identifier;
|
|
30897
|
+
if (reference.isWrite()) return false;
|
|
30898
|
+
const jsxEventValueAttribute = getJsxEventValueAttribute(identifier);
|
|
30899
|
+
if (jsxEventValueAttribute) {
|
|
30900
|
+
if (isNodeReachableWithinFunction(jsxEventValueAttribute, context)) hasDirectJsxEventReference = true;
|
|
30901
|
+
continue;
|
|
30902
|
+
}
|
|
30903
|
+
if (isReactHookDependencyReference(identifier)) continue;
|
|
30904
|
+
const callExpression = getCallExpr(reference);
|
|
30905
|
+
if (!callExpression) return false;
|
|
30906
|
+
const jsxEventCallbackAttribute = getInlineJsxEventCallbackAttribute(callExpression);
|
|
30907
|
+
if (jsxEventCallbackAttribute) {
|
|
30908
|
+
if (isNodeReachableWithinFunction(callExpression, context) && isNodeReachableWithinFunction(jsxEventCallbackAttribute, context)) hasDirectJsxEventReference = true;
|
|
30909
|
+
continue;
|
|
30910
|
+
}
|
|
30911
|
+
callExpressions.push(callExpression);
|
|
30912
|
+
}
|
|
30913
|
+
if (hasDirectJsxEventReference) return true;
|
|
30914
|
+
for (const callExpression of callExpressions) {
|
|
30915
|
+
if (!isNodeReachableWithinFunction(callExpression, context)) continue;
|
|
30916
|
+
const callerFunction = findEnclosingFunction$1(callExpression);
|
|
30917
|
+
if (!callerFunction || callerFunction === componentFunction) continue;
|
|
30918
|
+
const callerVariable = getImmutableFunctionVariable(analysis, componentFunction, callerFunction);
|
|
30919
|
+
if (callerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, callerVariable, nextVisitedVariables)) return true;
|
|
30920
|
+
}
|
|
30921
|
+
return false;
|
|
30922
|
+
};
|
|
30923
|
+
const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, includeDeferredWriters = false) => {
|
|
30549
30924
|
if (!setterRef.resolved) return false;
|
|
30550
30925
|
const componentFunction = findEnclosingFunction$1(effectNode);
|
|
30551
30926
|
if (!componentFunction) return false;
|
|
@@ -30554,6 +30929,11 @@ const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters
|
|
|
30554
30929
|
const identifier = reference.identifier;
|
|
30555
30930
|
if (isAstDescendant(identifier, effectNode)) continue;
|
|
30556
30931
|
if (isIndependentWriterIdentifier(componentFunction, identifier, includeDeferredWriters)) return true;
|
|
30932
|
+
if (!isNodeReachableWithinFunction(identifier, context)) continue;
|
|
30933
|
+
const writerFunction = findEnclosingFunction$1(identifier);
|
|
30934
|
+
if (!writerFunction || writerFunction === componentFunction) continue;
|
|
30935
|
+
const writerVariable = getImmutableFunctionVariable(analysis, componentFunction, writerFunction);
|
|
30936
|
+
if (writerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, writerVariable, /* @__PURE__ */ new Set())) return true;
|
|
30557
30937
|
}
|
|
30558
30938
|
return false;
|
|
30559
30939
|
};
|
|
@@ -31443,7 +31823,7 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
|
|
|
31443
31823
|
}
|
|
31444
31824
|
return false;
|
|
31445
31825
|
};
|
|
31446
|
-
const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
|
|
31826
|
+
const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFilename) => {
|
|
31447
31827
|
const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
|
|
31448
31828
|
if (frames.length === 0) return [];
|
|
31449
31829
|
const effectHasCleanup = hasCleanup(analysis, effectNode);
|
|
@@ -31473,7 +31853,7 @@ const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) =>
|
|
|
31473
31853
|
for (const returnedExpression of returnedExpressions) mergeEvidence(valueEvidence, collectValueEvidence(analysis, returnedExpression, updaterFrame, remainingValueCallFrames));
|
|
31474
31854
|
} else valueEvidence = collectValueEvidence(analysis, writtenValue, frame, remainingValueCallFrames);
|
|
31475
31855
|
const sourceReferences = [...valueEvidence.sourceReferences].filter((sourceReference) => getUseStateDecl(analysis, sourceReference) !== stateDeclarator);
|
|
31476
|
-
const hasIndependentWriter = hasUserInputSetterWriter(setterReference, effectNode, true);
|
|
31856
|
+
const hasIndependentWriter = hasUserInputSetterWriter(analysis, context, setterReference, effectNode, true);
|
|
31477
31857
|
const doesMatchStateInitializer = matchesStateInitializer(analysis, callExpression, stateDeclarator);
|
|
31478
31858
|
if (effectHasCleanup && (frame.isDeferred || valueEvidence.hasUnknownSource || valueEvidence.hasDeferredIntroducedValue || valueEvidence.readsExternalValue)) cleanupManagedStateDeclarators.add(stateDeclarator);
|
|
31479
31859
|
const isRenderKnownCopy = sourceReferences.length > 0 && !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue && !hasIndependentWriter;
|
|
@@ -31514,7 +31894,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
31514
31894
|
const dependencyReferences = getEffectDepsRefs(analysis, node);
|
|
31515
31895
|
if (!dependencyReferences) return;
|
|
31516
31896
|
if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
|
|
31517
|
-
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
31897
|
+
for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
|
|
31518
31898
|
if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
|
|
31519
31899
|
context.report({
|
|
31520
31900
|
node: fact.callExpression,
|
|
@@ -36088,7 +36468,7 @@ const noDerivedState = defineRule({
|
|
|
36088
36468
|
if (!isUseEffect(node)) return;
|
|
36089
36469
|
const analysis = getProgramAnalysis(node);
|
|
36090
36470
|
if (!analysis) return;
|
|
36091
|
-
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
36471
|
+
for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
|
|
36092
36472
|
if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
|
|
36093
36473
|
reportStateWrite(fact.callExpression, fact.stateDeclarator);
|
|
36094
36474
|
}
|
|
@@ -36108,7 +36488,7 @@ const noDerivedStateEffect = defineRule({
|
|
|
36108
36488
|
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
36109
36489
|
const analysis = getProgramAnalysis(node);
|
|
36110
36490
|
if (!analysis) return;
|
|
36111
|
-
if (!collectEffectStateWriteFacts(analysis, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
|
|
36491
|
+
if (!collectEffectStateWriteFacts(analysis, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
|
|
36112
36492
|
context.report({
|
|
36113
36493
|
node,
|
|
36114
36494
|
message: "You pay an extra render for state you can derive from other values."
|
|
@@ -36584,9 +36964,20 @@ const noDidMountSetState = defineRule({
|
|
|
36584
36964
|
}
|
|
36585
36965
|
});
|
|
36586
36966
|
//#endregion
|
|
36967
|
+
//#region src/plugin/utils/find-enclosing-class.ts
|
|
36968
|
+
const findEnclosingClass = (node) => {
|
|
36969
|
+
let ancestor = node.parent;
|
|
36970
|
+
while (ancestor) {
|
|
36971
|
+
if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
|
|
36972
|
+
ancestor = ancestor.parent ?? null;
|
|
36973
|
+
}
|
|
36974
|
+
return null;
|
|
36975
|
+
};
|
|
36976
|
+
//#endregion
|
|
36587
36977
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
36588
36978
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
36589
36979
|
const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
36980
|
+
const DIFFERENCE_OPERATORS = new Set(["!=", "!=="]);
|
|
36590
36981
|
const EQUALITY_OPERATORS = new Set([
|
|
36591
36982
|
"==",
|
|
36592
36983
|
"===",
|
|
@@ -36598,6 +36989,8 @@ const FUNCTION_NODE_TYPES = new Set([
|
|
|
36598
36989
|
"FunctionExpression",
|
|
36599
36990
|
"ArrowFunctionExpression"
|
|
36600
36991
|
]);
|
|
36992
|
+
const CLASS_NODE_TYPES = new Set(["ClassDeclaration", "ClassExpression"]);
|
|
36993
|
+
const callbackRefFieldNamesByClass = /* @__PURE__ */ new WeakMap();
|
|
36601
36994
|
const isLifecycleMethodFunction = (node) => {
|
|
36602
36995
|
if (!FUNCTION_NODE_TYPES.has(node.type)) return false;
|
|
36603
36996
|
const parent = node.parent;
|
|
@@ -36653,6 +37046,187 @@ const getStaticMemberName = (node) => {
|
|
|
36653
37046
|
if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
|
|
36654
37047
|
return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
|
|
36655
37048
|
};
|
|
37049
|
+
const getMemberIdentity = (property) => {
|
|
37050
|
+
const propertyName = getPropertyKeyName$2(property);
|
|
37051
|
+
if (propertyName !== void 0) return isNodeOfType(property, "PrivateIdentifier") ? `#${propertyName}` : propertyName;
|
|
37052
|
+
return isNodeOfType(property, "Literal") && typeof property.value === "string" ? property.value : null;
|
|
37053
|
+
};
|
|
37054
|
+
const collectPreviousSourcePaths = (pattern, domain, members, previousSourcePaths) => {
|
|
37055
|
+
if (!pattern) return;
|
|
37056
|
+
const unwrappedPattern = stripParenExpression(pattern);
|
|
37057
|
+
if (isNodeOfType(unwrappedPattern, "Identifier")) {
|
|
37058
|
+
previousSourcePaths.set(unwrappedPattern.name, {
|
|
37059
|
+
domain,
|
|
37060
|
+
members: [...members],
|
|
37061
|
+
source: "previous"
|
|
37062
|
+
});
|
|
37063
|
+
return;
|
|
37064
|
+
}
|
|
37065
|
+
if (isNodeOfType(unwrappedPattern, "AssignmentPattern")) {
|
|
37066
|
+
collectPreviousSourcePaths(unwrappedPattern.left, domain, members, previousSourcePaths);
|
|
37067
|
+
return;
|
|
37068
|
+
}
|
|
37069
|
+
if (!isNodeOfType(unwrappedPattern, "ObjectPattern")) return;
|
|
37070
|
+
for (const property of unwrappedPattern.properties) {
|
|
37071
|
+
if (!isNodeOfType(property, "Property")) continue;
|
|
37072
|
+
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
37073
|
+
if (!propertyName) continue;
|
|
37074
|
+
collectPreviousSourcePaths(property.value, domain, [...members, propertyName], previousSourcePaths);
|
|
37075
|
+
}
|
|
37076
|
+
};
|
|
37077
|
+
const getStateSourcePath = (node, previousSourcePaths) => {
|
|
37078
|
+
let currentNode = stripParenExpression(node);
|
|
37079
|
+
const members = [];
|
|
37080
|
+
while (isNodeOfType(currentNode, "MemberExpression")) {
|
|
37081
|
+
const memberName = getStaticMemberName(currentNode);
|
|
37082
|
+
if (!memberName) return null;
|
|
37083
|
+
members.unshift(memberName);
|
|
37084
|
+
currentNode = stripParenExpression(currentNode.object);
|
|
37085
|
+
}
|
|
37086
|
+
if (isNodeOfType(currentNode, "ThisExpression")) {
|
|
37087
|
+
const [domain, ...pathMembers] = members;
|
|
37088
|
+
if (domain !== "props" && domain !== "state") return null;
|
|
37089
|
+
return {
|
|
37090
|
+
domain,
|
|
37091
|
+
members: pathMembers,
|
|
37092
|
+
source: "current"
|
|
37093
|
+
};
|
|
37094
|
+
}
|
|
37095
|
+
if (!isNodeOfType(currentNode, "Identifier")) return null;
|
|
37096
|
+
const previousSourcePath = previousSourcePaths.get(currentNode.name);
|
|
37097
|
+
return previousSourcePath ? {
|
|
37098
|
+
...previousSourcePath,
|
|
37099
|
+
members: [...previousSourcePath.members, ...members]
|
|
37100
|
+
} : null;
|
|
37101
|
+
};
|
|
37102
|
+
const haveMatchingStateSourcePaths = (left, right) => left.domain === right.domain && left.members.length === right.members.length && left.members.every((member, index) => member === right.members[index]);
|
|
37103
|
+
const collectConjunctiveStateSourceComparisons = (test, previousSourcePaths, comparisons) => {
|
|
37104
|
+
const expression = stripParenExpression(test);
|
|
37105
|
+
if (isNodeOfType(expression, "LogicalExpression") && expression.operator === "&&") {
|
|
37106
|
+
collectConjunctiveStateSourceComparisons(expression.left, previousSourcePaths, comparisons);
|
|
37107
|
+
collectConjunctiveStateSourceComparisons(expression.right, previousSourcePaths, comparisons);
|
|
37108
|
+
return;
|
|
37109
|
+
}
|
|
37110
|
+
if (!isNodeOfType(expression, "BinaryExpression") || !EQUALITY_OPERATORS.has(expression.operator)) return;
|
|
37111
|
+
const leftPath = getStateSourcePath(expression.left, previousSourcePaths);
|
|
37112
|
+
const rightPath = getStateSourcePath(expression.right, previousSourcePaths);
|
|
37113
|
+
if (Boolean(leftPath) === Boolean(rightPath)) return;
|
|
37114
|
+
const path = leftPath ?? rightPath;
|
|
37115
|
+
if (!path) return;
|
|
37116
|
+
comparisons.push({
|
|
37117
|
+
comparedValue: leftPath ? expression.right : expression.left,
|
|
37118
|
+
isDifference: DIFFERENCE_OPERATORS.has(expression.operator),
|
|
37119
|
+
path
|
|
37120
|
+
});
|
|
37121
|
+
};
|
|
37122
|
+
const isHistoricalToCurrentTransitionGuard = (test, previousSourcePaths) => {
|
|
37123
|
+
const expression = stripParenExpression(test);
|
|
37124
|
+
if (isNodeOfType(expression, "LogicalExpression") && expression.operator === "||") return isHistoricalToCurrentTransitionGuard(expression.left, previousSourcePaths) && isHistoricalToCurrentTransitionGuard(expression.right, previousSourcePaths);
|
|
37125
|
+
const comparisons = [];
|
|
37126
|
+
collectConjunctiveStateSourceComparisons(expression, previousSourcePaths, comparisons);
|
|
37127
|
+
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)));
|
|
37128
|
+
};
|
|
37129
|
+
const getThisFieldName = (node) => {
|
|
37130
|
+
const unwrappedNode = stripParenExpression(node);
|
|
37131
|
+
if (!isNodeOfType(unwrappedNode, "MemberExpression") || unwrappedNode.computed === true || !isNodeOfType(stripParenExpression(unwrappedNode.object), "ThisExpression")) return null;
|
|
37132
|
+
return getMemberIdentity(unwrappedNode.property);
|
|
37133
|
+
};
|
|
37134
|
+
const isUndefinedIdentifier = (node) => {
|
|
37135
|
+
const unwrappedNode = stripParenExpression(node);
|
|
37136
|
+
return isNodeOfType(unwrappedNode, "Identifier") && unwrappedNode.name === "undefined";
|
|
37137
|
+
};
|
|
37138
|
+
const isDirectRefParameterValue = (node, parameterSymbolId, scopes) => {
|
|
37139
|
+
const unwrappedNode = stripParenExpression(node);
|
|
37140
|
+
if (isNodeOfType(unwrappedNode, "Identifier")) return scopes.symbolFor(unwrappedNode)?.id === parameterSymbolId;
|
|
37141
|
+
if (!isNodeOfType(unwrappedNode, "LogicalExpression") || unwrappedNode.operator !== "??") return false;
|
|
37142
|
+
const left = stripParenExpression(unwrappedNode.left);
|
|
37143
|
+
return isNodeOfType(left, "Identifier") && scopes.symbolFor(left)?.id === parameterSymbolId && isUndefinedIdentifier(unwrappedNode.right);
|
|
37144
|
+
};
|
|
37145
|
+
const getCallbackRefAssignedFields = (callback, scopes) => {
|
|
37146
|
+
const firstParameter = (callback.params ?? [])[0];
|
|
37147
|
+
if (!firstParameter) return /* @__PURE__ */ new Set();
|
|
37148
|
+
const parameterIdentifier = isNodeOfType(firstParameter, "AssignmentPattern") ? firstParameter.left : firstParameter;
|
|
37149
|
+
if (!isNodeOfType(parameterIdentifier, "Identifier")) return /* @__PURE__ */ new Set();
|
|
37150
|
+
const parameterSymbolId = scopes.symbolFor(parameterIdentifier)?.id;
|
|
37151
|
+
if (parameterSymbolId === void 0) return /* @__PURE__ */ new Set();
|
|
37152
|
+
const body = callback.body;
|
|
37153
|
+
if (!body) return /* @__PURE__ */ new Set();
|
|
37154
|
+
const assignedFieldNames = /* @__PURE__ */ new Set();
|
|
37155
|
+
walkAst(body, (node) => {
|
|
37156
|
+
if (node !== body && (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node) || CLASS_NODE_TYPES.has(node.type))) return false;
|
|
37157
|
+
const assignmentTarget = isNodeOfType(node, "AssignmentExpression") && node.left || isNodeOfType(node, "UpdateExpression") && node.argument || isNodeOfType(node, "UnaryExpression") && node.operator === "delete" && node.argument || null;
|
|
37158
|
+
if (!assignmentTarget) return;
|
|
37159
|
+
const fieldName = getThisFieldName(assignmentTarget);
|
|
37160
|
+
if (!fieldName) return;
|
|
37161
|
+
if (isNodeOfType(node, "AssignmentExpression") && node.operator === "=" && isDirectRefParameterValue(node.right, parameterSymbolId, scopes)) {
|
|
37162
|
+
assignedFieldNames.add(fieldName);
|
|
37163
|
+
return;
|
|
37164
|
+
}
|
|
37165
|
+
assignedFieldNames.delete(fieldName);
|
|
37166
|
+
});
|
|
37167
|
+
return assignedFieldNames;
|
|
37168
|
+
};
|
|
37169
|
+
const getClassMemberCallback = (classNode, memberName) => {
|
|
37170
|
+
const classBody = classNode.body?.body ?? [];
|
|
37171
|
+
for (const member of classBody) {
|
|
37172
|
+
if (!isNodeOfType(member, "MethodDefinition") && !isNodeOfType(member, "PropertyDefinition")) continue;
|
|
37173
|
+
if (member.static === true) continue;
|
|
37174
|
+
const key = member.key;
|
|
37175
|
+
if (getMemberIdentity(key) !== memberName) continue;
|
|
37176
|
+
const value = member.value;
|
|
37177
|
+
return value && FUNCTION_NODE_TYPES.has(value.type) ? value : null;
|
|
37178
|
+
}
|
|
37179
|
+
return null;
|
|
37180
|
+
};
|
|
37181
|
+
const collectCallbackRefFieldsFromExpression = (expression, classNode, fieldNames, scopes) => {
|
|
37182
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
37183
|
+
if (FUNCTION_NODE_TYPES.has(unwrappedExpression.type)) {
|
|
37184
|
+
for (const fieldName of getCallbackRefAssignedFields(unwrappedExpression, scopes)) fieldNames.add(fieldName);
|
|
37185
|
+
return;
|
|
37186
|
+
}
|
|
37187
|
+
const handlerName = getThisFieldName(unwrappedExpression);
|
|
37188
|
+
if (handlerName) {
|
|
37189
|
+
const callback = getClassMemberCallback(classNode, handlerName);
|
|
37190
|
+
if (callback) for (const fieldName of getCallbackRefAssignedFields(callback, scopes)) fieldNames.add(fieldName);
|
|
37191
|
+
return;
|
|
37192
|
+
}
|
|
37193
|
+
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
|
|
37194
|
+
collectCallbackRefFieldsFromExpression(unwrappedExpression.consequent, classNode, fieldNames, scopes);
|
|
37195
|
+
collectCallbackRefFieldsFromExpression(unwrappedExpression.alternate, classNode, fieldNames, scopes);
|
|
37196
|
+
return;
|
|
37197
|
+
}
|
|
37198
|
+
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
37199
|
+
if (unwrappedExpression.operator !== "&&") collectCallbackRefFieldsFromExpression(unwrappedExpression.left, classNode, fieldNames, scopes);
|
|
37200
|
+
collectCallbackRefFieldsFromExpression(unwrappedExpression.right, classNode, fieldNames, scopes);
|
|
37201
|
+
}
|
|
37202
|
+
};
|
|
37203
|
+
const getCallbackRefFieldNames = (classNode, scopes) => {
|
|
37204
|
+
if (!classNode) return /* @__PURE__ */ new Set();
|
|
37205
|
+
const cachedFieldNames = callbackRefFieldNamesByClass.get(classNode);
|
|
37206
|
+
if (cachedFieldNames) return cachedFieldNames;
|
|
37207
|
+
const fieldNames = /* @__PURE__ */ new Set();
|
|
37208
|
+
const classBody = classNode.body;
|
|
37209
|
+
if (classBody) walkAst(classBody, (node) => {
|
|
37210
|
+
if (node !== classBody && CLASS_NODE_TYPES.has(node.type)) return false;
|
|
37211
|
+
if (!isNodeOfType(node, "JSXAttribute") || !isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "ref" || !node.value || !isNodeOfType(node.value, "JSXExpressionContainer") || !node.value.expression) return;
|
|
37212
|
+
collectCallbackRefFieldsFromExpression(node.value.expression, classNode, fieldNames, scopes);
|
|
37213
|
+
});
|
|
37214
|
+
callbackRefFieldNamesByClass.set(classNode, fieldNames);
|
|
37215
|
+
return fieldNames;
|
|
37216
|
+
};
|
|
37217
|
+
const collectLifecycleWrittenFieldNames = (lifecycleFunction) => {
|
|
37218
|
+
const fieldNames = /* @__PURE__ */ new Set();
|
|
37219
|
+
const body = lifecycleFunction.body;
|
|
37220
|
+
if (!body) return fieldNames;
|
|
37221
|
+
walkAst(body, (node) => {
|
|
37222
|
+
if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
|
|
37223
|
+
const target = isNodeOfType(node, "AssignmentExpression") && node.left || isNodeOfType(node, "UpdateExpression") && node.argument || null;
|
|
37224
|
+
if (!target) return;
|
|
37225
|
+
const fieldName = getThisFieldName(target);
|
|
37226
|
+
if (fieldName) fieldNames.add(fieldName);
|
|
37227
|
+
});
|
|
37228
|
+
return fieldNames;
|
|
37229
|
+
};
|
|
36656
37230
|
const getThisStateFieldName = (node) => {
|
|
36657
37231
|
const unwrappedNode = stripParenExpression(node);
|
|
36658
37232
|
if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
|
|
@@ -36670,15 +37244,17 @@ const collectLocalInitializers = (lifecycleFunction) => {
|
|
|
36670
37244
|
});
|
|
36671
37245
|
return initializers;
|
|
36672
37246
|
};
|
|
36673
|
-
const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
|
|
37247
|
+
const derivesFromPostMountValue = (node, localInitializers, callbackRefFieldNames, visitedNames = /* @__PURE__ */ new Set()) => {
|
|
36674
37248
|
if (readsPostMountValue(node)) return true;
|
|
37249
|
+
const fieldName = getThisFieldName(node);
|
|
37250
|
+
if (fieldName && callbackRefFieldNames.has(fieldName)) return true;
|
|
36675
37251
|
const referencedNames = /* @__PURE__ */ new Set();
|
|
36676
37252
|
collectReferenceIdentifierNames(node, referencedNames);
|
|
36677
37253
|
for (const referencedName of referencedNames) {
|
|
36678
37254
|
if (visitedNames.has(referencedName)) continue;
|
|
36679
37255
|
const initializer = localInitializers.get(referencedName);
|
|
36680
37256
|
if (!initializer) continue;
|
|
36681
|
-
if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
|
|
37257
|
+
if (derivesFromPostMountValue(initializer, localInitializers, callbackRefFieldNames, new Set([...visitedNames, referencedName]))) return true;
|
|
36682
37258
|
}
|
|
36683
37259
|
return false;
|
|
36684
37260
|
};
|
|
@@ -36692,50 +37268,84 @@ const getSetStateFieldValue = (setStateCall, fieldName) => {
|
|
|
36692
37268
|
}
|
|
36693
37269
|
return null;
|
|
36694
37270
|
};
|
|
36695
|
-
const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
|
|
36696
|
-
|
|
36697
|
-
|
|
36698
|
-
if (
|
|
36699
|
-
|
|
36700
|
-
const
|
|
36701
|
-
|
|
36702
|
-
|
|
36703
|
-
|
|
36704
|
-
|
|
36705
|
-
|
|
36706
|
-
|
|
36707
|
-
|
|
36708
|
-
|
|
36709
|
-
|
|
36710
|
-
|
|
36711
|
-
return
|
|
36712
|
-
};
|
|
36713
|
-
const
|
|
36714
|
-
|
|
36715
|
-
|
|
36716
|
-
|
|
36717
|
-
|
|
36718
|
-
|
|
36719
|
-
|
|
36720
|
-
|
|
36721
|
-
|
|
36722
|
-
|
|
36723
|
-
|
|
36724
|
-
|
|
36725
|
-
|
|
37271
|
+
const isConvergentPostMountGuard = (test, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch) => {
|
|
37272
|
+
const expression = stripParenExpression(test);
|
|
37273
|
+
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
37274
|
+
if (expression.operator !== "&&" && expression.operator !== "||") return false;
|
|
37275
|
+
const leftIsConvergent = isConvergentPostMountGuard(expression.left, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch);
|
|
37276
|
+
const rightIsConvergent = isConvergentPostMountGuard(expression.right, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch);
|
|
37277
|
+
return isTruthfulBranch && expression.operator === "||" || !isTruthfulBranch && expression.operator === "&&" ? leftIsConvergent && rightIsConvergent : leftIsConvergent || rightIsConvergent;
|
|
37278
|
+
}
|
|
37279
|
+
if (!isNodeOfType(expression, "BinaryExpression") || !(isTruthfulBranch ? DIFFERENCE_OPERATORS.has(expression.operator) : EQUALITY_OPERATORS.has(expression.operator) && !DIFFERENCE_OPERATORS.has(expression.operator))) return false;
|
|
37280
|
+
const leftFieldName = getThisStateFieldName(expression.left);
|
|
37281
|
+
const rightFieldName = getThisStateFieldName(expression.right);
|
|
37282
|
+
const fieldName = leftFieldName ?? rightFieldName;
|
|
37283
|
+
const comparedValue = leftFieldName ? expression.right : expression.left;
|
|
37284
|
+
if (!fieldName) return false;
|
|
37285
|
+
const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
|
|
37286
|
+
if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return false;
|
|
37287
|
+
return isUndefinedIdentifier(comparedValue) || derivesFromPostMountValue(comparedValue, localInitializers, callbackRefFieldNames);
|
|
37288
|
+
};
|
|
37289
|
+
const containsPositiveStateFieldTest = (test, fieldName) => {
|
|
37290
|
+
const unwrappedTest = stripParenExpression(test);
|
|
37291
|
+
if (getThisStateFieldName(unwrappedTest) === fieldName) return true;
|
|
37292
|
+
return isNodeOfType(unwrappedTest, "LogicalExpression") && unwrappedTest.operator === "&&" && (containsPositiveStateFieldTest(unwrappedTest.left, fieldName) || containsPositiveStateFieldTest(unwrappedTest.right, fieldName));
|
|
37293
|
+
};
|
|
37294
|
+
const isConvergentUndefinedClearGuard = (test, setStateCall) => {
|
|
37295
|
+
if (!isNodeOfType(setStateCall, "CallExpression")) return false;
|
|
37296
|
+
const argument = setStateCall.arguments?.[0];
|
|
37297
|
+
if (!argument || !isNodeOfType(argument, "ObjectExpression")) return false;
|
|
37298
|
+
for (const property of argument.properties ?? []) {
|
|
37299
|
+
if (!isNodeOfType(property, "Property") || property.computed === true || !isUndefinedIdentifier(property.value)) continue;
|
|
37300
|
+
const fieldName = isNodeOfType(property.key, "Identifier") && property.key.name || isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" && property.key.value || null;
|
|
37301
|
+
if (fieldName && containsPositiveStateFieldTest(test, fieldName)) return true;
|
|
37302
|
+
}
|
|
37303
|
+
return false;
|
|
36726
37304
|
};
|
|
36727
|
-
const
|
|
37305
|
+
const isDiffGuardTest = (test, paramNames, derivedNames, isTruthfulBranch) => {
|
|
37306
|
+
const expression = stripParenExpression(test);
|
|
37307
|
+
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
37308
|
+
if (expression.operator !== "&&" && expression.operator !== "||") return false;
|
|
37309
|
+
const leftIsDiffGuard = isDiffGuardTest(expression.left, paramNames, derivedNames, isTruthfulBranch);
|
|
37310
|
+
const rightIsDiffGuard = isDiffGuardTest(expression.right, paramNames, derivedNames, isTruthfulBranch);
|
|
37311
|
+
return isTruthfulBranch && expression.operator === "||" || !isTruthfulBranch && expression.operator === "&&" ? leftIsDiffGuard && rightIsDiffGuard : leftIsDiffGuard || rightIsDiffGuard;
|
|
37312
|
+
}
|
|
37313
|
+
if (!isNodeOfType(expression, "BinaryExpression") || !(isTruthfulBranch ? DIFFERENCE_OPERATORS.has(expression.operator) : EQUALITY_OPERATORS.has(expression.operator) && !DIFFERENCE_OPERATORS.has(expression.operator))) return false;
|
|
37314
|
+
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));
|
|
37315
|
+
};
|
|
37316
|
+
const isInsideDiffGuard = (setStateCall, scopes) => {
|
|
36728
37317
|
const lifecycleFunction = findEnclosingLifecycleFunction(setStateCall);
|
|
36729
37318
|
if (!lifecycleFunction) return false;
|
|
36730
37319
|
const paramNames = /* @__PURE__ */ new Set();
|
|
36731
|
-
|
|
37320
|
+
const parameters = lifecycleFunction.params ?? [];
|
|
37321
|
+
for (const param of parameters) collectPatternNames(param, paramNames);
|
|
37322
|
+
const previousSourcePaths = /* @__PURE__ */ new Map();
|
|
37323
|
+
const [previousPropsParameter, previousStateParameter] = parameters;
|
|
37324
|
+
collectPreviousSourcePaths(previousPropsParameter, "props", [], previousSourcePaths);
|
|
37325
|
+
collectPreviousSourcePaths(previousStateParameter, "state", [], previousSourcePaths);
|
|
36732
37326
|
const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
|
|
36733
37327
|
const localInitializers = collectLocalInitializers(lifecycleFunction);
|
|
37328
|
+
const lifecycleWrittenFieldNames = collectLifecycleWrittenFieldNames(lifecycleFunction);
|
|
37329
|
+
const callbackRefFieldNames = new Set([...getCallbackRefFieldNames(findEnclosingClass(lifecycleFunction), scopes)].filter((fieldName) => !lifecycleWrittenFieldNames.has(fieldName)));
|
|
36734
37330
|
let child = setStateCall;
|
|
36735
37331
|
let ancestor = setStateCall.parent;
|
|
36736
37332
|
while (ancestor && ancestor !== lifecycleFunction) {
|
|
36737
|
-
|
|
36738
|
-
|
|
37333
|
+
let guardTest = null;
|
|
37334
|
+
let isTruthfulBranch = true;
|
|
37335
|
+
if (isNodeOfType(ancestor, "IfStatement")) {
|
|
37336
|
+
if (child === ancestor.consequent) guardTest = ancestor.test;
|
|
37337
|
+
else if (child === ancestor.alternate) {
|
|
37338
|
+
guardTest = ancestor.test;
|
|
37339
|
+
isTruthfulBranch = false;
|
|
37340
|
+
}
|
|
37341
|
+
} else if (isNodeOfType(ancestor, "ConditionalExpression")) {
|
|
37342
|
+
if (child === ancestor.consequent) guardTest = ancestor.test;
|
|
37343
|
+
else if (child === ancestor.alternate) {
|
|
37344
|
+
guardTest = ancestor.test;
|
|
37345
|
+
isTruthfulBranch = false;
|
|
37346
|
+
}
|
|
37347
|
+
} else if (isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right) guardTest = ancestor.left;
|
|
37348
|
+
if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames, isTruthfulBranch) || isTruthfulBranch && isHistoricalToCurrentTransitionGuard(guardTest, previousSourcePaths) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch) || isTruthfulBranch && isConvergentUndefinedClearGuard(guardTest, setStateCall))) return true;
|
|
36739
37349
|
child = ancestor;
|
|
36740
37350
|
ancestor = ancestor.parent ?? null;
|
|
36741
37351
|
}
|
|
@@ -36757,7 +37367,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
36757
37367
|
if (!isNodeOfType(stripParenExpression(node.callee.object), "ThisExpression")) return;
|
|
36758
37368
|
if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
|
|
36759
37369
|
if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
36760
|
-
if (isInsideDiffGuard(node)) return;
|
|
37370
|
+
if (isInsideDiffGuard(node, context.scopes)) return;
|
|
36761
37371
|
context.report({
|
|
36762
37372
|
node: node.callee,
|
|
36763
37373
|
message: MESSAGE$27
|
|
@@ -40479,41 +41089,223 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
|
|
|
40479
41089
|
if (leftResult === false && rightResult === false) return false;
|
|
40480
41090
|
return null;
|
|
40481
41091
|
};
|
|
40482
|
-
const readHydrationConditionResult = (expression, context, runtime) => {
|
|
41092
|
+
const readHydrationConditionResult = (expression, context, runtime, state) => {
|
|
40483
41093
|
const unwrappedExpression = stripParenExpression(expression);
|
|
40484
41094
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
40485
41095
|
if (predicateMatch) return predicateMatch[`${runtime}Result`];
|
|
40486
41096
|
const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
|
|
40487
41097
|
if (staticResult !== null) return staticResult;
|
|
41098
|
+
const expressionSymbol = isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression) : null;
|
|
41099
|
+
const parameterValue = expressionSymbol ? state.parameterValuesBySymbolId.get(expressionSymbol.id) : null;
|
|
41100
|
+
if (expressionSymbol && parameterValue && !state.visitedSymbolIds.has(expressionSymbol.id)) {
|
|
41101
|
+
state.visitedSymbolIds.add(expressionSymbol.id);
|
|
41102
|
+
const result = readHydrationConditionResult(parameterValue, context, runtime, state);
|
|
41103
|
+
state.visitedSymbolIds.delete(expressionSymbol.id);
|
|
41104
|
+
return result;
|
|
41105
|
+
}
|
|
41106
|
+
if (expressionSymbol && expressionSymbol.kind === "const" && expressionSymbol.initializer && expressionSymbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(expressionSymbol.id)) {
|
|
41107
|
+
state.visitedSymbolIds.add(expressionSymbol.id);
|
|
41108
|
+
const result = readHydrationConditionResult(expressionSymbol.initializer, context, runtime, state);
|
|
41109
|
+
state.visitedSymbolIds.delete(expressionSymbol.id);
|
|
41110
|
+
return result;
|
|
41111
|
+
}
|
|
41112
|
+
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
41113
|
+
const callArguments = unwrappedExpression.arguments ?? [];
|
|
41114
|
+
if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
|
|
41115
|
+
allowGlobalReactNamespace: true,
|
|
41116
|
+
resolveNamedAliases: true
|
|
41117
|
+
})) {
|
|
41118
|
+
const callbackArgument = callArguments[0];
|
|
41119
|
+
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
|
|
41120
|
+
const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
41121
|
+
return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? readHydrationFunctionResult(callbackFunction, context, runtime, state) : null;
|
|
41122
|
+
}
|
|
41123
|
+
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
41124
|
+
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);
|
|
41125
|
+
const helperFunction = resolveExactLocalFunction(callee, context.scopes);
|
|
41126
|
+
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;
|
|
41127
|
+
const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
|
|
41128
|
+
for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
|
|
41129
|
+
const parameter = helperFunction.params[parameterIndex];
|
|
41130
|
+
const argument = callArguments[parameterIndex];
|
|
41131
|
+
if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
|
|
41132
|
+
const parameterSymbol = context.scopes.symbolFor(parameter);
|
|
41133
|
+
if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
|
|
41134
|
+
}
|
|
41135
|
+
return readHydrationFunctionResult(helperFunction, context, runtime, {
|
|
41136
|
+
...state,
|
|
41137
|
+
parameterValuesBySymbolId
|
|
41138
|
+
});
|
|
41139
|
+
}
|
|
40488
41140
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
|
|
40489
|
-
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
|
|
41141
|
+
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
|
|
40490
41142
|
return argumentResult === null ? null : !argumentResult;
|
|
40491
41143
|
}
|
|
40492
41144
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
40493
|
-
return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
|
|
41145
|
+
return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime, state), readHydrationConditionResult(unwrappedExpression.right, context, runtime, state));
|
|
40494
41146
|
};
|
|
40495
|
-
const
|
|
41147
|
+
const readHydrationStatementResult = (statement, context, runtime, state) => {
|
|
41148
|
+
if (isNodeOfType(statement, "ReturnStatement")) return {
|
|
41149
|
+
didReturn: true,
|
|
41150
|
+
value: statement.argument ? readHydrationConditionResult(statement.argument, context, runtime, state) : null
|
|
41151
|
+
};
|
|
41152
|
+
if (isNodeOfType(statement, "BlockStatement")) {
|
|
41153
|
+
for (const childStatement of statement.body) {
|
|
41154
|
+
const result = readHydrationStatementResult(childStatement, context, runtime, state);
|
|
41155
|
+
if (result.didReturn) return result;
|
|
41156
|
+
if (statementAlwaysExits(childStatement)) break;
|
|
41157
|
+
}
|
|
41158
|
+
return {
|
|
41159
|
+
didReturn: false,
|
|
41160
|
+
value: null
|
|
41161
|
+
};
|
|
41162
|
+
}
|
|
41163
|
+
if (!isNodeOfType(statement, "IfStatement")) return {
|
|
41164
|
+
didReturn: false,
|
|
41165
|
+
value: null
|
|
41166
|
+
};
|
|
41167
|
+
const conditionResult = readHydrationConditionResult(statement.test, context, runtime, state);
|
|
41168
|
+
if (conditionResult !== null) {
|
|
41169
|
+
const selectedBranch = conditionResult ? statement.consequent : statement.alternate;
|
|
41170
|
+
return selectedBranch ? readHydrationStatementResult(selectedBranch, context, runtime, state) : {
|
|
41171
|
+
didReturn: false,
|
|
41172
|
+
value: null
|
|
41173
|
+
};
|
|
41174
|
+
}
|
|
41175
|
+
const consequentResult = readHydrationStatementResult(statement.consequent, context, runtime, state);
|
|
41176
|
+
const alternateResult = statement.alternate ? readHydrationStatementResult(statement.alternate, context, runtime, state) : {
|
|
41177
|
+
didReturn: false,
|
|
41178
|
+
value: null
|
|
41179
|
+
};
|
|
41180
|
+
return consequentResult.didReturn && alternateResult.didReturn && consequentResult.value !== null && consequentResult.value === alternateResult.value ? consequentResult : {
|
|
41181
|
+
didReturn: consequentResult.didReturn || alternateResult.didReturn,
|
|
41182
|
+
value: null
|
|
41183
|
+
};
|
|
41184
|
+
};
|
|
41185
|
+
const readHydrationFunctionResult = (functionNode, context, runtime, state) => {
|
|
41186
|
+
if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
|
|
41187
|
+
state.visitedFunctionNodes.add(functionNode);
|
|
41188
|
+
const result = isNodeOfType(functionNode.body, "BlockStatement") ? readHydrationStatementResult(functionNode.body, context, runtime, state).value : readHydrationConditionResult(functionNode.body, context, runtime, state);
|
|
41189
|
+
state.visitedFunctionNodes.delete(functionNode);
|
|
41190
|
+
return result;
|
|
41191
|
+
};
|
|
41192
|
+
const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, scopes) => {
|
|
41193
|
+
const left = stripParenExpression(leftExpression);
|
|
41194
|
+
const right = stripParenExpression(rightExpression);
|
|
41195
|
+
if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
|
|
41196
|
+
const leftSymbol = scopes.symbolFor(left);
|
|
41197
|
+
const rightSymbol = scopes.symbolFor(right);
|
|
41198
|
+
return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
|
|
41199
|
+
}
|
|
41200
|
+
if (isNodeOfType(left, "MemberExpression") && isNodeOfType(right, "MemberExpression")) return doEquivalentExpressionBindingsMatch(left.object, right.object, scopes) && (!left.computed || doEquivalentExpressionBindingsMatch(left.property, right.property, scopes));
|
|
41201
|
+
if (isNodeOfType(left, "CallExpression") && isNodeOfType(right, "CallExpression")) {
|
|
41202
|
+
const rightArguments = right.arguments ?? [];
|
|
41203
|
+
return doEquivalentExpressionBindingsMatch(left.callee, right.callee, scopes) && (left.arguments ?? []).every((argument, index) => {
|
|
41204
|
+
const rightArgument = rightArguments[index];
|
|
41205
|
+
return Boolean(rightArgument && doEquivalentExpressionBindingsMatch(argument, rightArgument, scopes));
|
|
41206
|
+
});
|
|
41207
|
+
}
|
|
41208
|
+
return true;
|
|
41209
|
+
};
|
|
41210
|
+
const areHelperReturnValuesEquivalent = (leftValue, rightValue, context) => {
|
|
41211
|
+
if (areExpressionsStructurallyEqual(leftValue, rightValue)) return doEquivalentExpressionBindingsMatch(leftValue, rightValue, context.scopes);
|
|
41212
|
+
const leftBoolean = readInitialStateBoolean(leftValue, context.scopes);
|
|
41213
|
+
const rightBoolean = readInitialStateBoolean(rightValue, context.scopes);
|
|
41214
|
+
return leftBoolean !== null && rightBoolean !== null && leftBoolean === rightBoolean;
|
|
41215
|
+
};
|
|
41216
|
+
const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
|
|
41217
|
+
const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
|
|
41218
|
+
return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
|
|
41219
|
+
};
|
|
41220
|
+
const matchHydrationConditionInternal = (expression, context, state) => {
|
|
40496
41221
|
const unwrappedExpression = stripParenExpression(expression);
|
|
40497
41222
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
40498
41223
|
if (predicateMatch) return {
|
|
40499
41224
|
predicateMatch,
|
|
40500
41225
|
predicateNode: unwrappedExpression
|
|
40501
41226
|
};
|
|
40502
|
-
if (isNodeOfType(unwrappedExpression, "
|
|
40503
|
-
|
|
40504
|
-
|
|
40505
|
-
|
|
40506
|
-
|
|
40507
|
-
|
|
40508
|
-
|
|
40509
|
-
|
|
41227
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
41228
|
+
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
41229
|
+
const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
|
|
41230
|
+
if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
|
|
41231
|
+
state.visitedSymbolIds.add(symbol.id);
|
|
41232
|
+
const match = matchHydrationConditionInternal(parameterValue, context, state);
|
|
41233
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
41234
|
+
return match;
|
|
41235
|
+
}
|
|
41236
|
+
if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
|
|
41237
|
+
state.visitedSymbolIds.add(symbol.id);
|
|
41238
|
+
const match = matchHydrationConditionInternal(symbol.initializer, context, state);
|
|
41239
|
+
state.visitedSymbolIds.delete(symbol.id);
|
|
41240
|
+
return match;
|
|
41241
|
+
}
|
|
41242
|
+
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
41243
|
+
const callArguments = unwrappedExpression.arguments ?? [];
|
|
41244
|
+
if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
|
|
41245
|
+
allowGlobalReactNamespace: true,
|
|
41246
|
+
resolveNamedAliases: true
|
|
41247
|
+
})) {
|
|
41248
|
+
const callbackArgument = callArguments[0];
|
|
41249
|
+
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
|
|
41250
|
+
const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
41251
|
+
return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionResult(callbackFunction, context, state) : null;
|
|
41252
|
+
}
|
|
41253
|
+
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
41254
|
+
if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return matchHydrationConditionInternal(callArguments[0], context, state);
|
|
41255
|
+
const helperFunction = resolveExactLocalFunction(callee, context.scopes);
|
|
41256
|
+
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;
|
|
41257
|
+
const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
|
|
41258
|
+
for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
|
|
41259
|
+
const parameter = helperFunction.params[parameterIndex];
|
|
41260
|
+
const argument = callArguments[parameterIndex];
|
|
41261
|
+
if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
|
|
41262
|
+
const parameterSymbol = context.scopes.symbolFor(parameter);
|
|
41263
|
+
if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
|
|
41264
|
+
}
|
|
41265
|
+
return matchHydrationFunctionResult(helperFunction, context, {
|
|
41266
|
+
...state,
|
|
41267
|
+
parameterValuesBySymbolId
|
|
41268
|
+
});
|
|
40510
41269
|
}
|
|
41270
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
|
|
41271
|
+
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
41272
|
+
const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
|
|
41273
|
+
const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
|
|
40511
41274
|
const nestedMatch = leftMatch ?? rightMatch;
|
|
40512
41275
|
if (!nestedMatch) return null;
|
|
40513
|
-
const
|
|
40514
|
-
|
|
40515
|
-
return nestedMatch;
|
|
41276
|
+
const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client", state);
|
|
41277
|
+
const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server", state);
|
|
41278
|
+
return clientResult !== null && serverResult !== null && clientResult === serverResult ? null : nestedMatch;
|
|
41279
|
+
};
|
|
41280
|
+
const matchHydrationReturningStatement = (statement, context, state) => {
|
|
41281
|
+
if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? matchHydrationConditionInternal(statement.argument, context, state) : null;
|
|
41282
|
+
if (isNodeOfType(statement, "IfStatement")) {
|
|
41283
|
+
const conditionMatch = matchHydrationConditionInternal(statement.test, context, state);
|
|
41284
|
+
const consequentValues = getReturnedValues(statement.consequent);
|
|
41285
|
+
const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
|
|
41286
|
+
if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
|
|
41287
|
+
return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
|
|
41288
|
+
}
|
|
41289
|
+
if (!isNodeOfType(statement, "BlockStatement")) return null;
|
|
41290
|
+
for (const childStatement of statement.body) {
|
|
41291
|
+
const match = matchHydrationReturningStatement(childStatement, context, state);
|
|
41292
|
+
if (match) return match;
|
|
41293
|
+
if (statementAlwaysExits(childStatement)) break;
|
|
41294
|
+
}
|
|
41295
|
+
return null;
|
|
40516
41296
|
};
|
|
41297
|
+
const matchHydrationFunctionResult = (functionNode, context, state) => {
|
|
41298
|
+
if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
|
|
41299
|
+
state.visitedFunctionNodes.add(functionNode);
|
|
41300
|
+
const match = isNodeOfType(functionNode.body, "BlockStatement") ? matchHydrationReturningStatement(functionNode.body, context, state) : matchHydrationConditionInternal(functionNode.body, context, state);
|
|
41301
|
+
state.visitedFunctionNodes.delete(functionNode);
|
|
41302
|
+
return match;
|
|
41303
|
+
};
|
|
41304
|
+
const matchHydrationCondition = (expression, context) => matchHydrationConditionInternal(expression, context, {
|
|
41305
|
+
parameterValuesBySymbolId: /* @__PURE__ */ new Map(),
|
|
41306
|
+
visitedFunctionNodes: /* @__PURE__ */ new Set(),
|
|
41307
|
+
visitedSymbolIds: /* @__PURE__ */ new Set()
|
|
41308
|
+
});
|
|
40517
41309
|
const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
|
|
40518
41310
|
const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
|
|
40519
41311
|
if (!leftNode || !rightNode) return leftNode === rightNode;
|
|
@@ -40656,17 +41448,17 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
40656
41448
|
const { predicateMatch, predicateNode } = conditionMatch;
|
|
40657
41449
|
if (reportedNodes.has(predicateNode)) return;
|
|
40658
41450
|
if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
|
|
40659
|
-
const componentOrHookNode = findRenderPhaseComponentOrHook(
|
|
41451
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes);
|
|
40660
41452
|
if (!componentOrHookNode) return;
|
|
40661
41453
|
if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
|
|
40662
|
-
if (requiresRenderedContext && !isInRenderedOutput(
|
|
41454
|
+
if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes)) return;
|
|
40663
41455
|
if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
|
|
40664
|
-
const attribute = findEnclosingJsxAttribute(
|
|
41456
|
+
const attribute = findEnclosingJsxAttribute(conditionNode);
|
|
40665
41457
|
if (!attribute || isEventHandlerAttribute(attribute)) return;
|
|
40666
41458
|
}
|
|
40667
|
-
if (fileIsEmailTemplate || isGatedByFalsyInitialState(
|
|
40668
|
-
if (isAfterClientOnlyEarlyReturn(
|
|
40669
|
-
const openingElement = findEnclosingJsxOpeningElement(
|
|
41459
|
+
if (fileIsEmailTemplate || isGatedByFalsyInitialState(conditionNode, context.scopes)) return;
|
|
41460
|
+
if (isAfterClientOnlyEarlyReturn(conditionNode, componentOrHookNode, context.scopes)) return;
|
|
41461
|
+
const openingElement = findEnclosingJsxOpeningElement(conditionNode);
|
|
40670
41462
|
if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
|
|
40671
41463
|
if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
|
|
40672
41464
|
if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
|
|
@@ -41048,7 +41840,7 @@ const noInitializeState = defineRule({
|
|
|
41048
41840
|
if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
|
|
41049
41841
|
const analysis = getProgramAnalysis(node);
|
|
41050
41842
|
if (!analysis) return;
|
|
41051
|
-
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
41843
|
+
for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
|
|
41052
41844
|
if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
|
|
41053
41845
|
const stateName = getStateName(fact.stateDeclarator);
|
|
41054
41846
|
context.report({
|
|
@@ -41406,7 +42198,8 @@ const noJsxElementType = defineRule({
|
|
|
41406
42198
|
create: (context) => {
|
|
41407
42199
|
let isJsxImported = false;
|
|
41408
42200
|
const flaggedAnnotations = [];
|
|
41409
|
-
const
|
|
42201
|
+
const collectComponentReturnType = (functionNode, returnType) => {
|
|
42202
|
+
if (!(isNodeOfType(functionNode, "TSDeclareFunction") ? Boolean(functionNode.id && isReactComponentName(functionNode.id.name)) : isComponentFunction$1(functionNode))) return;
|
|
41410
42203
|
const typeAnnotation = extractReturnTypeAnnotation(returnType);
|
|
41411
42204
|
if (!typeAnnotation) return;
|
|
41412
42205
|
if (isJsxElementTypeReference(typeAnnotation)) flaggedAnnotations.push(typeAnnotation);
|
|
@@ -41416,19 +42209,16 @@ const noJsxElementType = defineRule({
|
|
|
41416
42209
|
if (isJsxImportBinding(node)) isJsxImported = true;
|
|
41417
42210
|
},
|
|
41418
42211
|
FunctionDeclaration(node) {
|
|
41419
|
-
|
|
42212
|
+
collectComponentReturnType(node, node.returnType);
|
|
41420
42213
|
},
|
|
41421
42214
|
ArrowFunctionExpression(node) {
|
|
41422
|
-
|
|
42215
|
+
collectComponentReturnType(node, node.returnType);
|
|
41423
42216
|
},
|
|
41424
42217
|
FunctionExpression(node) {
|
|
41425
|
-
|
|
42218
|
+
collectComponentReturnType(node, node.returnType);
|
|
41426
42219
|
},
|
|
41427
42220
|
TSDeclareFunction(node) {
|
|
41428
|
-
|
|
41429
|
-
},
|
|
41430
|
-
TSMethodSignature(node) {
|
|
41431
|
-
checkReturnType(node.returnType);
|
|
42221
|
+
collectComponentReturnType(node, node.returnType);
|
|
41432
42222
|
},
|
|
41433
42223
|
"Program:exit"() {
|
|
41434
42224
|
if (isJsxImported) return;
|
|
@@ -44455,6 +45245,114 @@ const DATA_SINK_METHOD_NAMES = new Set([
|
|
|
44455
45245
|
"deserialize"
|
|
44456
45246
|
]);
|
|
44457
45247
|
//#endregion
|
|
45248
|
+
//#region src/plugin/utils/get-transparent-react-callback-wrapper-argument.ts
|
|
45249
|
+
const getTransparentReactCallbackWrapperArgument = (initializer, resultSymbol, scopes) => {
|
|
45250
|
+
const callExpression = stripParenExpression(initializer);
|
|
45251
|
+
if (!isNodeOfType(callExpression, "CallExpression")) return null;
|
|
45252
|
+
const callbackArgument = callExpression.arguments[0];
|
|
45253
|
+
if (!callbackArgument) return null;
|
|
45254
|
+
if (resultSymbol && symbolHasReactUseEffectEventOrigin(resultSymbol, scopes)) return callbackArgument;
|
|
45255
|
+
return isReactApiCall(callExpression, "useCallback", scopes, {
|
|
45256
|
+
allowGlobalReactNamespace: true,
|
|
45257
|
+
allowUnboundBareCalls: true
|
|
45258
|
+
}) ? callbackArgument : null;
|
|
45259
|
+
};
|
|
45260
|
+
//#endregion
|
|
45261
|
+
//#region src/plugin/rules/state-and-effects/utils/resolve-parent-callback-provenance.ts
|
|
45262
|
+
const getDeclarationKind$1 = (declarator) => {
|
|
45263
|
+
const declaration = declarator.parent;
|
|
45264
|
+
return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
|
|
45265
|
+
};
|
|
45266
|
+
const hasMutableBindingWrite$2 = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
45267
|
+
const mergeRequiredBranches = (leftNames, rightNames) => {
|
|
45268
|
+
if (!leftNames || !rightNames) return null;
|
|
45269
|
+
return new Set([...leftNames, ...rightNames]);
|
|
45270
|
+
};
|
|
45271
|
+
const getPropReferenceName = (analysis, identifier) => {
|
|
45272
|
+
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
45273
|
+
const reference = getRef(analysis, identifier);
|
|
45274
|
+
if (!reference || !isProp(analysis, reference) || isWholePropsObjectReference(analysis, reference)) return null;
|
|
45275
|
+
const bindingIdentifier = (reference.resolved?.defs.find((definition) => definition.type === "Parameter"))?.name;
|
|
45276
|
+
return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? identifier.name;
|
|
45277
|
+
};
|
|
45278
|
+
const getSingleConstDeclarator = (reference) => {
|
|
45279
|
+
if (!reference.resolved || hasMutableBindingWrite$2(reference)) return null;
|
|
45280
|
+
const declarators = reference.resolved.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
45281
|
+
if (declarators.length !== 1) return null;
|
|
45282
|
+
const declarator = declarators[0];
|
|
45283
|
+
if (!declarator || getDeclarationKind$1(declarator) !== "const") return null;
|
|
45284
|
+
return declarator;
|
|
45285
|
+
};
|
|
45286
|
+
const resolveParentCallbackPropNames = (analysis, expression, scopes, visitedReferences, allowFunctionForwarder = false) => {
|
|
45287
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
45288
|
+
if (isFunctionLike$1(unwrappedExpression)) {
|
|
45289
|
+
if (!allowFunctionForwarder || Boolean(unwrappedExpression.async)) return null;
|
|
45290
|
+
const callbackNames = /* @__PURE__ */ new Set();
|
|
45291
|
+
walkInsideStatementBlocks(unwrappedExpression.body, (child) => {
|
|
45292
|
+
if (!isNodeOfType(child, "CallExpression")) return;
|
|
45293
|
+
const resolvedNames = resolveParentCallbackPropNames(analysis, child.callee, scopes, new Set(visitedReferences), false);
|
|
45294
|
+
if (!resolvedNames) return;
|
|
45295
|
+
for (const resolvedName of resolvedNames) callbackNames.add(resolvedName);
|
|
45296
|
+
});
|
|
45297
|
+
return callbackNames.size > 0 ? callbackNames : null;
|
|
45298
|
+
}
|
|
45299
|
+
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return mergeRequiredBranches(resolveParentCallbackPropNames(analysis, unwrappedExpression.consequent, scopes, new Set(visitedReferences), false), resolveParentCallbackPropNames(analysis, unwrappedExpression.alternate, scopes, new Set(visitedReferences), false));
|
|
45300
|
+
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return mergeRequiredBranches(resolveParentCallbackPropNames(analysis, unwrappedExpression.left, scopes, new Set(visitedReferences), false), resolveParentCallbackPropNames(analysis, unwrappedExpression.right, scopes, new Set(visitedReferences)));
|
|
45301
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
45302
|
+
const propName = getPropReferenceName(analysis, unwrappedExpression);
|
|
45303
|
+
if (propName) return new Set([propName]);
|
|
45304
|
+
const reference = getRef(analysis, unwrappedExpression);
|
|
45305
|
+
if (!reference?.resolved || visitedReferences.has(reference.resolved)) return null;
|
|
45306
|
+
const declarator = getSingleConstDeclarator(reference);
|
|
45307
|
+
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return null;
|
|
45308
|
+
visitedReferences.add(reference.resolved);
|
|
45309
|
+
const wrappedArgument = getTransparentReactCallbackWrapperArgument(declarator.init, scopes.symbolFor(unwrappedExpression), scopes);
|
|
45310
|
+
const allowsFunctionForwarder = Boolean(wrappedArgument && !isReactApiCall(declarator.init, "useCallback", scopes, {
|
|
45311
|
+
allowGlobalReactNamespace: true,
|
|
45312
|
+
allowUnboundBareCalls: true
|
|
45313
|
+
}));
|
|
45314
|
+
return resolveParentCallbackPropNames(analysis, wrappedArgument ?? declarator.init, scopes, visitedReferences, allowsFunctionForwarder);
|
|
45315
|
+
}
|
|
45316
|
+
if (!isNodeOfType(unwrappedExpression, "MemberExpression")) return null;
|
|
45317
|
+
const propertyName = getStaticMemberPropertyName(unwrappedExpression);
|
|
45318
|
+
if (!propertyName) return null;
|
|
45319
|
+
const receiver = stripParenExpression(unwrappedExpression.object);
|
|
45320
|
+
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
45321
|
+
const receiverReference = getRef(analysis, receiver);
|
|
45322
|
+
if (!receiverReference?.resolved || visitedReferences.has(receiverReference.resolved)) return null;
|
|
45323
|
+
if (isWholePropsObjectReference(analysis, receiverReference)) return new Set([propertyName]);
|
|
45324
|
+
const declarator = getSingleConstDeclarator(receiverReference);
|
|
45325
|
+
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return null;
|
|
45326
|
+
visitedReferences.add(receiverReference.resolved);
|
|
45327
|
+
const initializer = stripParenExpression(declarator.init);
|
|
45328
|
+
if (propertyName === "current" && isNodeOfType(initializer, "CallExpression")) {
|
|
45329
|
+
if (!isReactApiCall(initializer, "useRef", scopes, {
|
|
45330
|
+
allowGlobalReactNamespace: true,
|
|
45331
|
+
allowUnboundBareCalls: true
|
|
45332
|
+
})) return null;
|
|
45333
|
+
const callbackArgument = initializer.arguments[0];
|
|
45334
|
+
if (!callbackArgument) return null;
|
|
45335
|
+
let callbackNames = resolveParentCallbackPropNames(analysis, callbackArgument, scopes, new Set(visitedReferences), false);
|
|
45336
|
+
if (!callbackNames) return null;
|
|
45337
|
+
for (const candidateReference of receiverReference.resolved.references) {
|
|
45338
|
+
const candidateIdentifier = candidateReference.identifier;
|
|
45339
|
+
const candidateMember = candidateIdentifier.parent;
|
|
45340
|
+
if (!candidateMember || !isNodeOfType(candidateMember, "MemberExpression") || candidateMember.object !== candidateIdentifier || getStaticMemberPropertyName(candidateMember) !== "current") continue;
|
|
45341
|
+
const assignment = candidateMember.parent;
|
|
45342
|
+
if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.left !== candidateMember) continue;
|
|
45343
|
+
if (assignment.operator !== "=") return null;
|
|
45344
|
+
callbackNames = mergeRequiredBranches(callbackNames, resolveParentCallbackPropNames(analysis, assignment.right, scopes, new Set(visitedReferences), false));
|
|
45345
|
+
if (!callbackNames) return null;
|
|
45346
|
+
}
|
|
45347
|
+
return callbackNames;
|
|
45348
|
+
}
|
|
45349
|
+
if (!isNodeOfType(initializer, "ObjectExpression")) return null;
|
|
45350
|
+
const property = initializer.properties.find((candidateProperty) => isNodeOfType(candidateProperty, "Property") && getStaticPropertyKeyName(candidateProperty, { allowComputedString: true }) === propertyName);
|
|
45351
|
+
if (!property || !isNodeOfType(property, "Property")) return null;
|
|
45352
|
+
return resolveParentCallbackPropNames(analysis, property.value, scopes, visitedReferences, false);
|
|
45353
|
+
};
|
|
45354
|
+
const getParentCallbackPropNames = ({ analysis, expression, scopes }) => resolveParentCallbackPropNames(analysis, expression, scopes, /* @__PURE__ */ new Set(), false);
|
|
45355
|
+
//#endregion
|
|
44458
45356
|
//#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
|
|
44459
45357
|
const isUseStateIdentifier = (identifier) => {
|
|
44460
45358
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
@@ -44483,14 +45381,18 @@ const FUNCTION_WRAPPER_HOOK_NAMES$1 = new Set([
|
|
|
44483
45381
|
"useStableCallback",
|
|
44484
45382
|
"useCallbackRef"
|
|
44485
45383
|
]);
|
|
44486
|
-
const getWrapperHookWrappedFunction = (initializer) => {
|
|
45384
|
+
const getWrapperHookWrappedFunction = (initializer, resultSymbol, scopes) => {
|
|
44487
45385
|
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
45386
|
+
const transparentReactArgument = getTransparentReactCallbackWrapperArgument(initializer, resultSymbol, scopes);
|
|
45387
|
+
if (transparentReactArgument) return transparentReactArgument;
|
|
44488
45388
|
const callee = initializer.callee;
|
|
44489
45389
|
const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") ? callee.property.name : null;
|
|
44490
45390
|
if (!calleeName || !FUNCTION_WRAPPER_HOOK_NAMES$1.has(calleeName)) return null;
|
|
44491
45391
|
const wrapped = initializer.arguments?.[0];
|
|
44492
|
-
if (!wrapped
|
|
44493
|
-
return
|
|
45392
|
+
if (!wrapped) return null;
|
|
45393
|
+
if (calleeName === "useEffectEvent") return null;
|
|
45394
|
+
if (isFunctionLike$1(wrapped)) return wrapped;
|
|
45395
|
+
return null;
|
|
44494
45396
|
};
|
|
44495
45397
|
const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
|
|
44496
45398
|
const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
|
|
@@ -44500,16 +45402,29 @@ const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstre
|
|
|
44500
45402
|
const innerParent = innerIdentifier.parent;
|
|
44501
45403
|
return Boolean(innerParent && isNodeOfType(innerParent, "CallExpression") && innerParent.callee === innerIdentifier);
|
|
44502
45404
|
});
|
|
44503
|
-
const isDirectParentCallbackRef = (analysis, ref) => {
|
|
45405
|
+
const isDirectParentCallbackRef = (analysis, ref, scopes) => {
|
|
44504
45406
|
if (isProp(analysis, ref)) return true;
|
|
45407
|
+
if (hasMutableBindingWrite$1(ref)) {
|
|
45408
|
+
if (!(ref.resolved?.references.filter((candidateReference) => candidateReference.isWrite() && !candidateReference.init) ?? []).every((candidateReference) => {
|
|
45409
|
+
const candidateIdentifier = candidateReference.identifier;
|
|
45410
|
+
const assignment = candidateIdentifier.parent;
|
|
45411
|
+
if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== candidateIdentifier) return false;
|
|
45412
|
+
const assignedReferences = getDownstreamRefs(analysis, assignment.right);
|
|
45413
|
+
return assignedReferences.length > 0 && assignedReferences.every((assignedReference) => isProp(analysis, assignedReference));
|
|
45414
|
+
})) return false;
|
|
45415
|
+
}
|
|
44505
45416
|
return Boolean(ref.resolved?.defs.some((def) => {
|
|
44506
45417
|
const node = def.node;
|
|
44507
45418
|
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
44508
45419
|
const initializer = unwrapChainExpression(node.init);
|
|
44509
|
-
const wrappedFunction = getWrapperHookWrappedFunction(initializer);
|
|
45420
|
+
const wrappedFunction = getWrapperHookWrappedFunction(initializer, isNodeOfType(node.id, "Identifier") ? scopes.symbolFor(node.id) ?? null : null, scopes);
|
|
44510
45421
|
if (wrappedFunction) {
|
|
44511
45422
|
if (wrappedFunction.async) return false;
|
|
44512
|
-
return wrappedFunctionNotifiesParent(analysis, wrappedFunction);
|
|
45423
|
+
if (isFunctionLike$1(wrappedFunction)) return wrappedFunctionNotifiesParent(analysis, wrappedFunction);
|
|
45424
|
+
const directName = getParentCallbackPropName(analysis, wrappedFunction);
|
|
45425
|
+
const downstreamReferences = getDownstreamRefs(analysis, wrappedFunction);
|
|
45426
|
+
if (directName !== null) return true;
|
|
45427
|
+
return downstreamReferences.some((wrappedReference) => !hasMutableBindingWrite$1(wrappedReference) && getUpstreamRefs(analysis, wrappedReference).some((upstreamReference) => isProp(analysis, upstreamReference)));
|
|
44513
45428
|
}
|
|
44514
45429
|
if (!isNodeOfType(initializer, "Identifier") && !isNodeOfType(initializer, "MemberExpression")) return false;
|
|
44515
45430
|
return getDownstreamRefs(analysis, initializer).some((initializerRef) => getUpstreamRefs(analysis, initializerRef).some((upstreamRef) => isProp(analysis, upstreamRef)));
|
|
@@ -44519,7 +45434,7 @@ const getDeclarationKind = (declarator) => {
|
|
|
44519
45434
|
const declaration = declarator.parent;
|
|
44520
45435
|
return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
|
|
44521
45436
|
};
|
|
44522
|
-
const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
45437
|
+
const hasMutableBindingWrite$1 = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
44523
45438
|
const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
|
|
44524
45439
|
const unwrappedExpression = stripParenExpression(expression);
|
|
44525
45440
|
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
@@ -44531,7 +45446,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
|
|
|
44531
45446
|
const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
|
|
44532
45447
|
return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
|
|
44533
45448
|
}
|
|
44534
|
-
if (hasMutableBindingWrite(callbackReference)) return null;
|
|
45449
|
+
if (hasMutableBindingWrite$1(callbackReference)) return null;
|
|
44535
45450
|
const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
44536
45451
|
if (definitions.length !== 1) return null;
|
|
44537
45452
|
const declarator = definitions[0];
|
|
@@ -44597,7 +45512,7 @@ const getRefAliasDeclarator = (identifier) => {
|
|
|
44597
45512
|
const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
|
|
44598
45513
|
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
44599
45514
|
const receiverReference = getRef(analysis, receiver);
|
|
44600
|
-
if (!receiverReference?.resolved || hasMutableBindingWrite(receiverReference)) return null;
|
|
45515
|
+
if (!receiverReference?.resolved || hasMutableBindingWrite$1(receiverReference)) return null;
|
|
44601
45516
|
const variables = /* @__PURE__ */ new Set();
|
|
44602
45517
|
let currentVariable = receiverReference.resolved;
|
|
44603
45518
|
let refCall = null;
|
|
@@ -44613,7 +45528,7 @@ const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
|
|
|
44613
45528
|
}
|
|
44614
45529
|
if (getDeclarationKind(declarator) !== "const" || !isNodeOfType(stripParenExpression(declarator.init), "Identifier")) return null;
|
|
44615
45530
|
const upstreamReference = getRef(analysis, stripParenExpression(declarator.init));
|
|
44616
|
-
if (!upstreamReference?.resolved || hasMutableBindingWrite(upstreamReference)) return null;
|
|
45531
|
+
if (!upstreamReference?.resolved || hasMutableBindingWrite$1(upstreamReference)) return null;
|
|
44617
45532
|
currentVariable = upstreamReference.resolved;
|
|
44618
45533
|
}
|
|
44619
45534
|
if (!refCall) return null;
|
|
@@ -44688,7 +45603,7 @@ const isParentPropsContextMerge = (analysis, expression) => {
|
|
|
44688
45603
|
while (isNodeOfType(currentExpression, "Identifier")) {
|
|
44689
45604
|
const currentReference = getRef(analysis, currentExpression);
|
|
44690
45605
|
const currentVariable = currentReference?.resolved;
|
|
44691
|
-
if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return false;
|
|
45606
|
+
if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite$1(currentReference)) return false;
|
|
44692
45607
|
visitedVariables.add(currentVariable);
|
|
44693
45608
|
const definitions = currentVariable.defs.filter((definition) => isNodeOfType(definition.node, "VariableDeclarator"));
|
|
44694
45609
|
if (definitions.length !== 1) return false;
|
|
@@ -44702,11 +45617,11 @@ const isParentPropsContextMerge = (analysis, expression) => {
|
|
|
44702
45617
|
const propsExpression = stripParenExpression(propsSpread.argument);
|
|
44703
45618
|
if (!isNodeOfType(propsExpression, "Identifier")) return false;
|
|
44704
45619
|
const propsReference = getRef(analysis, propsExpression);
|
|
44705
|
-
if (!propsReference?.resolved || !isWholePropsObjectReference(analysis, propsReference) || hasMutableBindingWrite(propsReference) || propsReference.resolved.references.some((candidateReference) => candidateReference !== propsReference)) return false;
|
|
45620
|
+
if (!propsReference?.resolved || !isWholePropsObjectReference(analysis, propsReference) || hasMutableBindingWrite$1(propsReference) || propsReference.resolved.references.some((candidateReference) => candidateReference !== propsReference)) return false;
|
|
44706
45621
|
const contextExpression = stripParenExpression(contextSpread.argument);
|
|
44707
45622
|
if (!isNodeOfType(contextExpression, "Identifier")) return false;
|
|
44708
45623
|
const contextReference = getRef(analysis, contextExpression);
|
|
44709
|
-
if (!contextReference?.resolved || hasMutableBindingWrite(contextReference) || contextReference.resolved.references.some((candidateReference) => !candidateReference.init && candidateReference !== contextReference)) return false;
|
|
45624
|
+
if (!contextReference?.resolved || hasMutableBindingWrite$1(contextReference) || contextReference.resolved.references.some((candidateReference) => !candidateReference.init && candidateReference !== contextReference)) return false;
|
|
44710
45625
|
const contextInitializer = contextReference.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
|
|
44711
45626
|
if (!contextInitializer || !isNodeOfType(contextInitializer, "VariableDeclarator") || getDeclarationKind(contextInitializer) !== "const" || !contextInitializer.init || !isNodeOfType(contextInitializer.init, "CallExpression")) return false;
|
|
44712
45627
|
const contextHook = stripParenExpression(contextInitializer.init.callee);
|
|
@@ -44720,7 +45635,7 @@ const getImmutableParentCallbackPropName = (analysis, expression) => {
|
|
|
44720
45635
|
while (isNodeOfType(currentExpression, "Identifier")) {
|
|
44721
45636
|
const currentReference = getRef(analysis, currentExpression);
|
|
44722
45637
|
const currentVariable = currentReference?.resolved;
|
|
44723
|
-
if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return null;
|
|
45638
|
+
if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite$1(currentReference)) return null;
|
|
44724
45639
|
visitedVariables.add(currentVariable);
|
|
44725
45640
|
const definition = currentVariable.defs.length === 1 ? currentVariable.defs[0] : null;
|
|
44726
45641
|
const bindingIdentifier = definition?.name;
|
|
@@ -44789,7 +45704,7 @@ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) =>
|
|
|
44789
45704
|
while (isNodeOfType(currentExpression, "Identifier")) {
|
|
44790
45705
|
const callbackReference = getRef(analysis, currentExpression);
|
|
44791
45706
|
const callbackVariable = callbackReference?.resolved;
|
|
44792
|
-
if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite(callbackReference)) return null;
|
|
45707
|
+
if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite$1(callbackReference)) return null;
|
|
44793
45708
|
visitedVariables.add(callbackVariable);
|
|
44794
45709
|
const definition = callbackVariable.defs.length === 1 ? callbackVariable.defs[0] : null;
|
|
44795
45710
|
const declarator = definition?.node;
|
|
@@ -44809,10 +45724,11 @@ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) =>
|
|
|
44809
45724
|
if (!propertyName || !COMMAND_PROP_NAME_PATTERN.test(propertyName)) return null;
|
|
44810
45725
|
return refCurrentObjectPreservesCallbackProperty(analysis, currentExpression.object, propertyName, isReactUseRefCall) ? propertyName : null;
|
|
44811
45726
|
};
|
|
44812
|
-
const isWrapperHookCallbackRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
|
|
45727
|
+
const isWrapperHookCallbackRef = (analysis, ref, scopes) => Boolean(ref.resolved?.defs.some((def) => {
|
|
44813
45728
|
const node = def.node;
|
|
44814
45729
|
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
44815
|
-
|
|
45730
|
+
const resultSymbol = isNodeOfType(node.id, "Identifier") ? scopes.symbolFor(node.id) ?? null : null;
|
|
45731
|
+
return getWrapperHookWrappedFunction(unwrapChainExpression(node.init), resultSymbol, scopes) !== null;
|
|
44816
45732
|
}));
|
|
44817
45733
|
const isHandlerBagArgument = (analysis, argument) => {
|
|
44818
45734
|
if (!isNodeOfType(argument, "ObjectExpression")) return false;
|
|
@@ -44831,14 +45747,25 @@ const isHandlerBagArgument = (analysis, argument) => {
|
|
|
44831
45747
|
};
|
|
44832
45748
|
const getFunctionalUpdaterDataRefs = (analysis, updater) => getDownstreamRefs(analysis, updater).filter((updaterRef) => !updaterRef.resolved?.defs.some((def) => def.type === "Parameter" && def.node === updater));
|
|
44833
45749
|
const HOOK_NAME_PATTERN$1 = /^use[A-Z0-9]/;
|
|
44834
|
-
const EXTERNAL_SUBSCRIPTION_HOOK_NAMES = new Set([
|
|
45750
|
+
const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
|
|
44835
45751
|
"useIntersectionObserver",
|
|
44836
45752
|
"useMatchMedia",
|
|
45753
|
+
"useMediaJobProgress",
|
|
44837
45754
|
"useMediaQuery",
|
|
44838
45755
|
"useResizeObserver",
|
|
44839
45756
|
"useVisibility",
|
|
44840
45757
|
"useWindowSize"
|
|
44841
45758
|
]);
|
|
45759
|
+
const isCallbackPropReference = (analysis, ref) => {
|
|
45760
|
+
if (!isProp(analysis, ref)) return false;
|
|
45761
|
+
const identifier = ref.identifier;
|
|
45762
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
45763
|
+
if (!isWholePropsObjectReference(analysis, ref)) return HANDLER_NAMED_PROP_PATTERN.test(identifier.name);
|
|
45764
|
+
const member = identifier.parent;
|
|
45765
|
+
if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier) return false;
|
|
45766
|
+
const propertyName = getStaticMemberPropertyName(member);
|
|
45767
|
+
return Boolean(propertyName && HANDLER_NAMED_PROP_PATTERN.test(propertyName));
|
|
45768
|
+
};
|
|
44842
45769
|
const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
|
|
44843
45770
|
const node = def.node;
|
|
44844
45771
|
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
@@ -44846,7 +45773,7 @@ const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs
|
|
|
44846
45773
|
if (!isNodeOfType(init, "CallExpression")) return false;
|
|
44847
45774
|
const callee = init.callee;
|
|
44848
45775
|
if (!isNodeOfType(callee, "Identifier") || !HOOK_NAME_PATTERN$1.test(callee.name)) return false;
|
|
44849
|
-
return (init.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) =>
|
|
45776
|
+
return (init.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
|
|
44850
45777
|
}));
|
|
44851
45778
|
const isParentWiredHookResultArgument = (analysis, argument) => {
|
|
44852
45779
|
if (!isNodeOfType(argument, "Identifier")) return false;
|
|
@@ -44859,19 +45786,19 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
|
|
|
44859
45786
|
if (!isNodeOfType(identifier, "Identifier") || !HOOK_NAME_PATTERN$1.test(identifier.name)) return false;
|
|
44860
45787
|
const parent = identifier.parent;
|
|
44861
45788
|
if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
|
|
44862
|
-
return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) =>
|
|
45789
|
+
return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
|
|
44863
45790
|
};
|
|
44864
45791
|
const isExternalSubscriptionHookRef = (ref) => {
|
|
44865
45792
|
const identifier = ref.identifier;
|
|
44866
45793
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
44867
|
-
if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(identifier.name) && isCalleePosition(identifier)) return true;
|
|
45794
|
+
if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
|
|
44868
45795
|
return Boolean(ref.resolved?.defs.some((def) => {
|
|
44869
45796
|
const node = def.node;
|
|
44870
45797
|
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
44871
45798
|
const initializer = stripParenExpression(node.init);
|
|
44872
45799
|
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
44873
45800
|
const callee = stripParenExpression(initializer.callee);
|
|
44874
|
-
return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(callee.name);
|
|
45801
|
+
return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name);
|
|
44875
45802
|
}));
|
|
44876
45803
|
};
|
|
44877
45804
|
const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
|
|
@@ -44907,16 +45834,22 @@ const noPassDataToParent = defineRule({
|
|
|
44907
45834
|
const callExpr = getCallExpr(ref);
|
|
44908
45835
|
if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
|
|
44909
45836
|
const callbackRefProvenance = getCallbackRefProvenance(analysis, node, callExpr, isReactUseRefCall, isReactUseEffectCall);
|
|
44910
|
-
if (isRefCall(analysis, ref) && !callbackRefProvenance) continue;
|
|
44911
45837
|
if (!isSynchronous(ref.identifier, effectFn)) continue;
|
|
44912
45838
|
const calleeNode = unwrapChainExpression(callExpr.callee);
|
|
44913
45839
|
const identifier = ref.identifier;
|
|
44914
|
-
|
|
44915
|
-
|
|
45840
|
+
const resolvedCallbackPropNames = isNodeOfType(calleeNode, "MemberExpression") && getStaticMemberPropertyName(calleeNode) === "current" ? null : getParentCallbackPropNames({
|
|
45841
|
+
analysis,
|
|
45842
|
+
expression: calleeNode,
|
|
45843
|
+
scopes: context.scopes
|
|
45844
|
+
});
|
|
45845
|
+
const callbackPropNames = callbackRefProvenance?.callbackPropNames ?? resolvedCallbackPropNames;
|
|
45846
|
+
if (isRefCall(analysis, ref) && !callbackPropNames) continue;
|
|
45847
|
+
if (callbackPropNames) {
|
|
45848
|
+
if ([...callbackPropNames].some((callbackPropName) => COMMAND_PROP_NAME_PATTERN.test(callbackPropName))) continue;
|
|
44916
45849
|
} else if (calleeNode === identifier) {
|
|
44917
45850
|
const callbackPropName = getCommandCallbackPropName(analysis, identifier, isReactUseRefCall);
|
|
44918
45851
|
if (callbackPropName && COMMAND_PROP_NAME_PATTERN.test(callbackPropName)) continue;
|
|
44919
|
-
if (!isDirectParentCallbackRef(analysis, ref)) continue;
|
|
45852
|
+
if (!isDirectParentCallbackRef(analysis, ref, context.scopes)) continue;
|
|
44920
45853
|
if (isNodeOfType(identifier, "Identifier") && COMMAND_PROP_NAME_PATTERN.test(identifier.name)) continue;
|
|
44921
45854
|
} else if (isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === identifier) {
|
|
44922
45855
|
if (!isWholePropsObjectReference(analysis, ref)) continue;
|
|
@@ -44924,10 +45857,10 @@ const noPassDataToParent = defineRule({
|
|
|
44924
45857
|
} else continue;
|
|
44925
45858
|
const methodName = getCallMethodName(calleeNode);
|
|
44926
45859
|
const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
|
|
44927
|
-
if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
|
|
45860
|
+
if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead && !callbackPropNames) continue;
|
|
44928
45861
|
if (methodName && COMMAND_PROP_NAME_PATTERN.test(methodName)) continue;
|
|
44929
|
-
if (!
|
|
44930
|
-
const isSetterNamedCallee =
|
|
45862
|
+
if (!callbackPropNames && isNamespacedApiCallee(calleeNode)) continue;
|
|
45863
|
+
const isSetterNamedCallee = callbackPropNames ? [...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) ?? ""));
|
|
44931
45864
|
const isLeafRef = (argRef) => getUpstreamRefs(analysis, argRef).length === 1;
|
|
44932
45865
|
const argsUpstreamRefs = (callExpr.arguments ?? []).flatMap((argument) => {
|
|
44933
45866
|
if (isFunctionLike$1(argument)) {
|
|
@@ -44942,7 +45875,7 @@ const noPassDataToParent = defineRule({
|
|
|
44942
45875
|
}
|
|
44943
45876
|
return getDownstreamRefs(analysis, argument);
|
|
44944
45877
|
}).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
|
|
44945
|
-
if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
|
|
45878
|
+
if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
|
|
44946
45879
|
if (!argsUpstreamRefs.some((argRef) => {
|
|
44947
45880
|
if (isUseStateIdentifier(argRef.identifier)) return false;
|
|
44948
45881
|
if (isExternalSubscriptionHookRef(argRef)) return false;
|
|
@@ -44980,9 +45913,47 @@ const isCallResultConsumedAsArgument = (callExpression) => {
|
|
|
44980
45913
|
return false;
|
|
44981
45914
|
};
|
|
44982
45915
|
//#endregion
|
|
45916
|
+
//#region src/plugin/rules/state-and-effects/utils/is-custom-hook-state-result-reference.ts
|
|
45917
|
+
const NON_STATE_CUSTOM_HOOK_NAMES = new Set([
|
|
45918
|
+
"useCallbackRef",
|
|
45919
|
+
"useEffectEvent",
|
|
45920
|
+
"useEvent",
|
|
45921
|
+
"useEventCallback",
|
|
45922
|
+
"useLatest",
|
|
45923
|
+
"useMemoizedFn",
|
|
45924
|
+
"useStableCallback"
|
|
45925
|
+
]);
|
|
45926
|
+
const EXTERNAL_SUBSCRIPTION_HOOK_NAMES = new Set([
|
|
45927
|
+
"useIntersectionObserver",
|
|
45928
|
+
"useMatchMedia",
|
|
45929
|
+
"useMediaJobProgress",
|
|
45930
|
+
"useMediaQuery",
|
|
45931
|
+
"useResizeObserver",
|
|
45932
|
+
"useVisibility",
|
|
45933
|
+
"useWindowSize"
|
|
45934
|
+
]);
|
|
45935
|
+
const getHookCalleeName = (initializer) => {
|
|
45936
|
+
const unwrappedInitializer = stripParenExpression(initializer);
|
|
45937
|
+
if (!isNodeOfType(unwrappedInitializer, "CallExpression")) return null;
|
|
45938
|
+
const callee = stripParenExpression(unwrappedInitializer.callee);
|
|
45939
|
+
if (isNodeOfType(callee, "Identifier")) return callee.name;
|
|
45940
|
+
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
|
|
45941
|
+
return null;
|
|
45942
|
+
};
|
|
45943
|
+
const isCustomHookStateResultReference = (analysis, reference) => Boolean(reference.resolved?.defs.some((definition) => {
|
|
45944
|
+
const declarator = definition.node;
|
|
45945
|
+
if (!isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return false;
|
|
45946
|
+
const calleeName = getHookCalleeName(declarator.init);
|
|
45947
|
+
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;
|
|
45948
|
+
const initializer = stripParenExpression(declarator.init);
|
|
45949
|
+
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
45950
|
+
return initializer.arguments.some((argument) => getDownstreamRefs(analysis, argument).some((argumentReference) => isProp(analysis, argumentReference)));
|
|
45951
|
+
}));
|
|
45952
|
+
//#endregion
|
|
44983
45953
|
//#region src/plugin/rules/state-and-effects/no-pass-live-state-to-parent.ts
|
|
44984
45954
|
const SETTER_NAMED_CALLBACK_PATTERN = /^set[A-Z]/;
|
|
44985
45955
|
const DATA_FETCHING_CALLBACK_PATTERN = /^(fetch|refetch|load|query|request)([A-Z_]|$)/;
|
|
45956
|
+
const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
|
|
44986
45957
|
const getCallCalleeName = (callExpr) => {
|
|
44987
45958
|
if (!isNodeOfType(callExpr, "CallExpression")) return null;
|
|
44988
45959
|
const callee = callExpr.callee;
|
|
@@ -45027,6 +45998,10 @@ const collectUpstreamStateRefs = (analysis, ref, stateRefs, visited) => {
|
|
|
45027
45998
|
stateRefs.push(ref);
|
|
45028
45999
|
return;
|
|
45029
46000
|
}
|
|
46001
|
+
if (isCustomHookStateResultReference(analysis, ref)) {
|
|
46002
|
+
stateRefs.push(ref);
|
|
46003
|
+
return;
|
|
46004
|
+
}
|
|
45030
46005
|
for (const def of ref.resolved?.defs ?? []) {
|
|
45031
46006
|
if (def.type === "ImportBinding" || def.type === "Parameter") continue;
|
|
45032
46007
|
const defNode = def.node;
|
|
@@ -45056,6 +46031,32 @@ const collectPropCallbackBoundStateRefs = (analysis, ref, isPropCallbackRef) =>
|
|
|
45056
46031
|
}
|
|
45057
46032
|
return stateRefs;
|
|
45058
46033
|
};
|
|
46034
|
+
const collectDirectCallStateRefs = (analysis, callExpression) => {
|
|
46035
|
+
const stateReferences = [];
|
|
46036
|
+
for (const argument of callExpression.arguments) {
|
|
46037
|
+
if (isFunctionLike$1(argument)) continue;
|
|
46038
|
+
for (const argumentReference of getDownstreamRefs(analysis, argument)) {
|
|
46039
|
+
if (resolveToFunction(argumentReference)) continue;
|
|
46040
|
+
collectUpstreamStateRefs(analysis, argumentReference, stateReferences, /* @__PURE__ */ new Set());
|
|
46041
|
+
}
|
|
46042
|
+
}
|
|
46043
|
+
return stateReferences;
|
|
46044
|
+
};
|
|
46045
|
+
const getTransparentWrapperPropReference = (analysis, reference, context) => {
|
|
46046
|
+
for (const definition of reference.resolved?.defs ?? []) {
|
|
46047
|
+
const declarator = definition.node;
|
|
46048
|
+
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
|
|
46049
|
+
const resultSymbol = context.scopes.symbolFor(declarator.id);
|
|
46050
|
+
const callbackArgument = getTransparentReactCallbackWrapperArgument(declarator.init, resultSymbol, context.scopes);
|
|
46051
|
+
if (!callbackArgument) continue;
|
|
46052
|
+
const callbackReferences = getDownstreamRefs(analysis, callbackArgument);
|
|
46053
|
+
const callbackReference = callbackReferences.find((candidateReference) => isPropCallbackInvocationRef(analysis, candidateReference));
|
|
46054
|
+
if (callbackReference) return callbackReference;
|
|
46055
|
+
const propReference = callbackReferences.find((candidateReference) => isProp(analysis, candidateReference) && !candidateReference.resolved?.references.some((candidateUsage) => candidateUsage.isWrite() && !candidateUsage.init));
|
|
46056
|
+
if (propReference) return propReference;
|
|
46057
|
+
}
|
|
46058
|
+
return null;
|
|
46059
|
+
};
|
|
45059
46060
|
const isSetterNamedCallbackReceivingData = (callbackRef) => {
|
|
45060
46061
|
const callExpr = getCallExpr(callbackRef);
|
|
45061
46062
|
if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) return false;
|
|
@@ -45091,6 +46092,16 @@ const resolvesToLocalHookReturnBinding = (ref) => Boolean(ref?.resolved?.defs?.s
|
|
|
45091
46092
|
const calleeName = getInitializerCalleeName(node.init);
|
|
45092
46093
|
return calleeName !== null && isReactHookName(calleeName) && !FUNCTION_WRAPPER_HOOK_NAMES.has(calleeName);
|
|
45093
46094
|
}));
|
|
46095
|
+
const getDirectLocalEffectHelper = (callExpression, effectFunction, context) => {
|
|
46096
|
+
const helperFunction = resolveExactLocalFunction(callExpression.callee, context.scopes);
|
|
46097
|
+
if (!helperFunction) return null;
|
|
46098
|
+
let ancestor = callExpression.parent;
|
|
46099
|
+
while (ancestor && ancestor !== effectFunction) {
|
|
46100
|
+
if (isFunctionLike$1(ancestor)) return null;
|
|
46101
|
+
ancestor = ancestor.parent;
|
|
46102
|
+
}
|
|
46103
|
+
return ancestor === effectFunction ? helperFunction : null;
|
|
46104
|
+
};
|
|
45094
46105
|
const noPassLiveStateToParent = defineRule({
|
|
45095
46106
|
id: "no-pass-live-state-to-parent",
|
|
45096
46107
|
title: "Live state pushed to parent via effect",
|
|
@@ -45105,20 +46116,32 @@ const noPassLiveStateToParent = defineRule({
|
|
|
45105
46116
|
if (!effectFnRefs) return;
|
|
45106
46117
|
const effectFn = getEffectFn(analysis, node);
|
|
45107
46118
|
if (!effectFn) return;
|
|
46119
|
+
const effectFunctionBody = isNodeOfType(effectFn, "ArrowFunctionExpression") || isNodeOfType(effectFn, "FunctionExpression") || isNodeOfType(effectFn, "FunctionDeclaration") ? effectFn.body : null;
|
|
45108
46120
|
for (const ref of effectFnRefs) {
|
|
45109
|
-
const propCallbackRefs = getEventualCallRefsTo(analysis, ref, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef));
|
|
45110
|
-
if (propCallbackRefs.length === 0) continue;
|
|
45111
|
-
if (resolvesToLocalHookReturnBinding(ref)) continue;
|
|
45112
|
-
if (!isSynchronous(ref.identifier, effectFn)) continue;
|
|
45113
46121
|
const callExpr = getCallExpr(ref);
|
|
45114
|
-
if (!callExpr) continue;
|
|
46122
|
+
if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
|
|
46123
|
+
const directLocalEffectHelper = getDirectLocalEffectHelper(callExpr, effectFn, context);
|
|
46124
|
+
const callGraphReferences = directLocalEffectHelper ? [ref, ...getDownstreamRefs(analysis, directLocalEffectHelper)] : [ref];
|
|
46125
|
+
const resolvedCallbackPropNames = getParentCallbackPropNames({
|
|
46126
|
+
analysis,
|
|
46127
|
+
expression: callExpr.callee,
|
|
46128
|
+
scopes: context.scopes
|
|
46129
|
+
});
|
|
46130
|
+
const callExpressionRoot = findTransparentExpressionRoot(callExpr);
|
|
46131
|
+
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;
|
|
46132
|
+
if (!notificationCallbackPropNames && hasMutableBindingWrite(ref)) continue;
|
|
46133
|
+
const propCallbackRefs = callGraphReferences.flatMap((callGraphReference) => getEventualCallRefsTo(analysis, callGraphReference, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef)));
|
|
46134
|
+
const transparentPropReference = propCallbackRefs.length === 0 ? getTransparentWrapperPropReference(analysis, ref, context) : null;
|
|
46135
|
+
if (propCallbackRefs.length === 0 && !transparentPropReference && !notificationCallbackPropNames) continue;
|
|
46136
|
+
if (!notificationCallbackPropNames && resolvesToLocalHookReturnBinding(ref)) continue;
|
|
46137
|
+
if (!isSynchronous(ref.identifier, effectFn) && !directLocalEffectHelper) continue;
|
|
45115
46138
|
if (isCallResultConsumedAsArgument(callExpr)) continue;
|
|
45116
46139
|
const calleeNode = callExpr.callee;
|
|
45117
46140
|
const methodName = calleeNode ? getCallMethodName(calleeNode) : null;
|
|
45118
46141
|
const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && calleeNode && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
|
|
45119
|
-
if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
|
|
45120
|
-
if (calleeNode && isNamespacedApiCallee(calleeNode)) continue;
|
|
45121
|
-
const stateArgRefs = collectPropCallbackBoundStateRefs(analysis,
|
|
46142
|
+
if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead && !notificationCallbackPropNames) continue;
|
|
46143
|
+
if (!notificationCallbackPropNames && calleeNode && isNamespacedApiCallee(calleeNode)) continue;
|
|
46144
|
+
const stateArgRefs = transparentPropReference || notificationCallbackPropNames ? collectDirectCallStateRefs(analysis, callExpr) : callGraphReferences.flatMap((callGraphReference) => collectPropCallbackBoundStateRefs(analysis, callGraphReference, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef)));
|
|
45122
46145
|
const handsSetterNamedCallbackData = propCallbackRefs.some(isSetterNamedCallbackReceivingData);
|
|
45123
46146
|
if (stateArgRefs.length === 0 && !handsSetterNamedCallbackData) continue;
|
|
45124
46147
|
context.report({
|
|
@@ -45511,6 +46534,7 @@ const isStateLikeDependency = (analysis, element, isPropName) => {
|
|
|
45511
46534
|
if (!analysis) return true;
|
|
45512
46535
|
const reference = getRef(analysis, element);
|
|
45513
46536
|
if (!reference) return true;
|
|
46537
|
+
if (isCustomHookStateResultReference(analysis, reference)) return true;
|
|
45514
46538
|
const upstreamReferences = getUpstreamRefs(analysis, reference);
|
|
45515
46539
|
if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
|
|
45516
46540
|
return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
|
|
@@ -45527,6 +46551,22 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
|
|
|
45527
46551
|
if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
|
|
45528
46552
|
return isPropName(callbackArgument.name) ? callbackArgument.name : null;
|
|
45529
46553
|
};
|
|
46554
|
+
const getTransparentWrappedPropCallbackName = (callExpression, context, isPropName) => {
|
|
46555
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
46556
|
+
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
46557
|
+
const binding = findVariableInitializer(callExpression, callee.name);
|
|
46558
|
+
if (!binding?.initializer) return null;
|
|
46559
|
+
const resultSymbol = context.scopes.symbolFor(callee);
|
|
46560
|
+
const callbackArgument = getTransparentReactCallbackWrapperArgument(binding.initializer, resultSymbol, context.scopes);
|
|
46561
|
+
if (!callbackArgument) return null;
|
|
46562
|
+
const callbackSource = stripParenExpression(callbackArgument);
|
|
46563
|
+
if (isNodeOfType(callbackSource, "Identifier")) return isPropName(callbackSource.name, callbackSource) ? callbackSource.name : null;
|
|
46564
|
+
if (!isNodeOfType(callbackSource, "MemberExpression")) return null;
|
|
46565
|
+
const receiver = stripParenExpression(callbackSource.object);
|
|
46566
|
+
const propertyName = getStaticPropertyName(callbackSource);
|
|
46567
|
+
if (!isNodeOfType(receiver, "Identifier") || !propertyName) return null;
|
|
46568
|
+
return isPropName(receiver.name, receiver) ? propertyName : null;
|
|
46569
|
+
};
|
|
45530
46570
|
const noPropCallbackInEffect = defineRule({
|
|
45531
46571
|
id: "no-prop-callback-in-effect",
|
|
45532
46572
|
title: "Parent kept in sync with a callback effect",
|
|
@@ -45560,9 +46600,16 @@ const noPropCallbackInEffect = defineRule({
|
|
|
45560
46600
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
45561
46601
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
45562
46602
|
const directCallee = stripParenExpression(child.callee);
|
|
45563
|
-
const
|
|
46603
|
+
const resolvedCallbackPropNames = analysis && propStackTracker.getCurrentPropNames().size > 0 ? getParentCallbackPropNames({
|
|
46604
|
+
analysis,
|
|
46605
|
+
expression: directCallee,
|
|
46606
|
+
scopes: context.scopes
|
|
46607
|
+
}) : null;
|
|
46608
|
+
const calleeName = resolvedCallbackPropNames && [...resolvedCallbackPropNames][0] || isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName) || getTransparentWrappedPropCallbackName(child, context, propStackTracker.isPropName);
|
|
45564
46609
|
if (!calleeName) return;
|
|
45565
|
-
|
|
46610
|
+
const callExpressionRoot = findTransparentExpressionRoot(child);
|
|
46611
|
+
const isDirectEffectReturn = isNodeOfType(callExpressionRoot.parent, "ReturnStatement") && callExpressionRoot.parent.parent === callback.body;
|
|
46612
|
+
if (!isResultDiscardedCall(child) && !isDirectEffectReturn) return;
|
|
45566
46613
|
if (reportedNodes.has(child)) return;
|
|
45567
46614
|
reportedNodes.add(child);
|
|
45568
46615
|
context.report({
|
|
@@ -46395,6 +47442,69 @@ const noRedundantShouldComponentUpdate = defineRule({
|
|
|
46395
47442
|
}
|
|
46396
47443
|
});
|
|
46397
47444
|
//#endregion
|
|
47445
|
+
//#region src/plugin/rules/correctness/no-ref-callback-cleanup-before-react-19.ts
|
|
47446
|
+
const resolveFunctionExpressions = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
47447
|
+
const expression = stripParenExpression(rawExpression);
|
|
47448
|
+
if (isFunctionLike$1(expression)) return expression.async || expression.generator ? [] : [expression];
|
|
47449
|
+
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
47450
|
+
if (isNodeOfType(expression.test, "Literal")) return resolveFunctionExpressions(expression.test.value ? expression.consequent : expression.alternate, scopes, visitedSymbolIds);
|
|
47451
|
+
return [...resolveFunctionExpressions(expression.consequent, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.alternate, scopes, visitedSymbolIds)];
|
|
47452
|
+
}
|
|
47453
|
+
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
47454
|
+
if (isNodeOfType(expression.left, "Literal")) {
|
|
47455
|
+
const isLeftTruthy = Boolean(expression.left.value);
|
|
47456
|
+
if (expression.operator === "&&" && !isLeftTruthy) return [];
|
|
47457
|
+
if (expression.operator === "||" && isLeftTruthy) return [];
|
|
47458
|
+
if (expression.operator === "??" && expression.left.value !== null) return [];
|
|
47459
|
+
}
|
|
47460
|
+
if (expression.operator === "&&") return resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds);
|
|
47461
|
+
return [...resolveFunctionExpressions(expression.left, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds)];
|
|
47462
|
+
}
|
|
47463
|
+
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
47464
|
+
const finalExpression = expression.expressions.at(-1);
|
|
47465
|
+
return finalExpression ? resolveFunctionExpressions(finalExpression, scopes, visitedSymbolIds) : [];
|
|
47466
|
+
}
|
|
47467
|
+
if (isNodeOfType(expression, "CallExpression")) {
|
|
47468
|
+
if (!isReactApiCall(expression, "useCallback", scopes)) return [];
|
|
47469
|
+
const callback = expression.arguments[0];
|
|
47470
|
+
return callback && !isNodeOfType(callback, "SpreadElement") ? resolveFunctionExpressions(callback, scopes, visitedSymbolIds) : [];
|
|
47471
|
+
}
|
|
47472
|
+
if (!isNodeOfType(expression, "Identifier")) return [];
|
|
47473
|
+
const symbol = scopes.symbolFor(expression);
|
|
47474
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return [];
|
|
47475
|
+
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]));
|
|
47476
|
+
const initializer = getDirectConstInitializer(symbol);
|
|
47477
|
+
if (!initializer) return [];
|
|
47478
|
+
return resolveFunctionExpressions(initializer, scopes, new Set([...visitedSymbolIds, symbol.id]));
|
|
47479
|
+
};
|
|
47480
|
+
const functionReturnsCleanupFunction = (functionExpression, scopes) => {
|
|
47481
|
+
if (!isFunctionLike$1(functionExpression)) return false;
|
|
47482
|
+
if (!isNodeOfType(functionExpression.body, "BlockStatement")) return resolveFunctionExpressions(functionExpression.body, scopes).length > 0;
|
|
47483
|
+
return collectFunctionReturnStatements(functionExpression).some((returnStatement) => Boolean(returnStatement.argument && resolveFunctionExpressions(returnStatement.argument, scopes).length > 0));
|
|
47484
|
+
};
|
|
47485
|
+
const callbackReturnsCleanupFunction = (callback, scopes) => {
|
|
47486
|
+
return resolveFunctionExpressions(callback, scopes).some((functionExpression) => functionReturnsCleanupFunction(functionExpression, scopes));
|
|
47487
|
+
};
|
|
47488
|
+
const noRefCallbackCleanupBeforeReact19 = defineRule({
|
|
47489
|
+
id: "no-ref-callback-cleanup-before-react-19",
|
|
47490
|
+
title: "Ref cleanup requires React 19",
|
|
47491
|
+
requires: ["react:18"],
|
|
47492
|
+
disabledWhen: ["react:19"],
|
|
47493
|
+
severity: "warn",
|
|
47494
|
+
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.",
|
|
47495
|
+
create: (context) => ({ JSXAttribute(node) {
|
|
47496
|
+
if (getJsxAttributeName(node.name) !== "ref") return;
|
|
47497
|
+
if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
|
|
47498
|
+
const callback = node.value.expression;
|
|
47499
|
+
if (!callback || isNodeOfType(callback, "JSXEmptyExpression")) return;
|
|
47500
|
+
if (!callbackReturnsCleanupFunction(callback, context.scopes)) return;
|
|
47501
|
+
context.report({
|
|
47502
|
+
node,
|
|
47503
|
+
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."
|
|
47504
|
+
});
|
|
47505
|
+
} })
|
|
47506
|
+
});
|
|
47507
|
+
//#endregion
|
|
46398
47508
|
//#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
|
|
46399
47509
|
const REPEATED_ANCESTOR_TYPES = new Set([
|
|
46400
47510
|
"DoWhileStatement",
|
|
@@ -53155,12 +54265,6 @@ const isInsideEs6Component$1 = (methodDefinition) => {
|
|
|
53155
54265
|
if (!owningClass) return false;
|
|
53156
54266
|
return isPreactOrReactComponentClass(owningClass);
|
|
53157
54267
|
};
|
|
53158
|
-
const stripThisParameter = (params) => {
|
|
53159
|
-
const first = params[0];
|
|
53160
|
-
if (!first) return params;
|
|
53161
|
-
if (isNodeOfType(first, "Identifier") && first.name === "this") return params.slice(1);
|
|
53162
|
-
return params;
|
|
53163
|
-
};
|
|
53164
54268
|
const preactNoRenderArguments = defineRule({
|
|
53165
54269
|
id: "preact-no-render-arguments",
|
|
53166
54270
|
title: "render() reads props from arguments",
|
|
@@ -56362,8 +57466,39 @@ const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node,
|
|
|
56362
57466
|
destructureIndex: 1
|
|
56363
57467
|
});
|
|
56364
57468
|
//#endregion
|
|
57469
|
+
//#region src/plugin/utils/unwrap-return-expression.ts
|
|
57470
|
+
const unwrapReturnExpression = (node) => isNodeOfType(node, "ReturnStatement") && node.argument ? node.argument : node;
|
|
57471
|
+
//#endregion
|
|
56365
57472
|
//#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
|
|
56366
57473
|
const USE_EFFECT_ONLY = new Set(["useEffect"]);
|
|
57474
|
+
const USE_CALLBACK_ONLY = new Set(["useCallback"]);
|
|
57475
|
+
const USE_STATE_ONLY = new Set(["useState"]);
|
|
57476
|
+
const REACT_API_CALL_OPTIONS = {
|
|
57477
|
+
allowGlobalReactNamespace: true,
|
|
57478
|
+
allowUnboundBareCalls: true,
|
|
57479
|
+
resolveNamedAliases: true
|
|
57480
|
+
};
|
|
57481
|
+
const expressionReadsDerivedSymbol = (context, expression, stateDerivedSymbolIds) => {
|
|
57482
|
+
let readsDerivedSymbol = false;
|
|
57483
|
+
walkAst(expression, (node) => {
|
|
57484
|
+
if (readsDerivedSymbol) return false;
|
|
57485
|
+
if (node !== expression && isFunctionLike$1(node)) return false;
|
|
57486
|
+
if (isNodeOfType(node, "Identifier") && stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) readsDerivedSymbol = true;
|
|
57487
|
+
});
|
|
57488
|
+
return readsDerivedSymbol;
|
|
57489
|
+
};
|
|
57490
|
+
const getStaticObjectPropertyName = (property) => {
|
|
57491
|
+
if (!isNodeOfType(property, "Property") || property.computed || property.method || property.kind !== "init") return null;
|
|
57492
|
+
if (isNodeOfType(property.key, "Identifier")) return property.key.name;
|
|
57493
|
+
if (isNodeOfType(property.key, "Literal") && (typeof property.key.value === "string" || typeof property.key.value === "number")) return String(property.key.value);
|
|
57494
|
+
return null;
|
|
57495
|
+
};
|
|
57496
|
+
const isNonVisibleJsxSpreadProperty = (propertyName) => propertyName === "id" || propertyName.startsWith("aria-") || /^on[A-Z]/.test(propertyName);
|
|
57497
|
+
const isTransparentAssignmentTarget = (identifier) => {
|
|
57498
|
+
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
57499
|
+
const parent = expressionRoot.parent;
|
|
57500
|
+
return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expressionRoot || isNodeOfType(parent, "UpdateExpression") && parent.argument === expressionRoot || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expressionRoot);
|
|
57501
|
+
};
|
|
56367
57502
|
const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
|
|
56368
57503
|
let readsCurrent = false;
|
|
56369
57504
|
walkAst(argument, (child) => {
|
|
@@ -56415,6 +57550,166 @@ const isStateUsedOnlyInIdOrAriaAttributes = (setterCall, setterName) => {
|
|
|
56415
57550
|
});
|
|
56416
57551
|
return referenceCount > 0 && !nonAriaReferenceFound;
|
|
56417
57552
|
};
|
|
57553
|
+
const isGlobalWindowMember = (context, node, propertyName) => {
|
|
57554
|
+
const member = stripParenExpression(node);
|
|
57555
|
+
if (!isNodeOfType(member, "MemberExpression") || member.computed) return false;
|
|
57556
|
+
const receiver = stripParenExpression(member.object);
|
|
57557
|
+
return isNodeOfType(receiver, "Identifier") && receiver.name === "window" && context.scopes.isGlobalReference(receiver) && isNodeOfType(member.property, "Identifier") && member.property.name === propertyName;
|
|
57558
|
+
};
|
|
57559
|
+
const getDirectWindowWidthSetter = (context, statement) => {
|
|
57560
|
+
const call = unwrapDiscardedExpression(statement);
|
|
57561
|
+
if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 1) return null;
|
|
57562
|
+
if (!isNodeOfType(call.callee, "Identifier") || !isSetterCall(call)) return null;
|
|
57563
|
+
const argument = call.arguments[0];
|
|
57564
|
+
return isGlobalWindowMember(context, argument, "innerWidth") ? call : null;
|
|
57565
|
+
};
|
|
57566
|
+
const getResizeListenerHandler = (context, statement, methodName) => {
|
|
57567
|
+
const call = unwrapDiscardedExpression(statement);
|
|
57568
|
+
if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 2) return null;
|
|
57569
|
+
if (!isGlobalWindowMember(context, call.callee, methodName)) return null;
|
|
57570
|
+
const eventName = call.arguments[0];
|
|
57571
|
+
const handler = call.arguments[1];
|
|
57572
|
+
if (!isNodeOfType(eventName, "Literal") || eventName.value !== "resize") return null;
|
|
57573
|
+
return isNodeOfType(handler, "Identifier") ? handler : null;
|
|
57574
|
+
};
|
|
57575
|
+
const getCleanupResizeHandler = (context, statement) => {
|
|
57576
|
+
if (!isNodeOfType(statement, "ReturnStatement") || !isFunctionLike$1(statement.argument)) return null;
|
|
57577
|
+
const cleanupStatements = getCallbackStatements(statement.argument);
|
|
57578
|
+
if (cleanupStatements.length !== 1) return null;
|
|
57579
|
+
return getResizeListenerHandler(context, unwrapReturnExpression(cleanupStatements[0]), "removeEventListener");
|
|
57580
|
+
};
|
|
57581
|
+
const findExactViewportState = (context, componentFunction, setterCall) => {
|
|
57582
|
+
if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
|
|
57583
|
+
const componentBody = componentFunction.body;
|
|
57584
|
+
if (!isNodeOfType(setterCall.callee, "Identifier")) return null;
|
|
57585
|
+
const setterSymbol = context.scopes.symbolFor(setterCall.callee);
|
|
57586
|
+
if (!setterSymbol || setterSymbol.kind !== "const" || !isNodeOfType(setterSymbol.declarationNode, "VariableDeclarator")) return null;
|
|
57587
|
+
const declarator = setterSymbol.declarationNode;
|
|
57588
|
+
if (!isNodeOfType(declarator.id, "ArrayPattern")) return null;
|
|
57589
|
+
const stateIdentifier = declarator.id.elements?.[0];
|
|
57590
|
+
const setterIdentifier = declarator.id.elements?.[1];
|
|
57591
|
+
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;
|
|
57592
|
+
const initializer = declarator.init.arguments?.[0];
|
|
57593
|
+
if (!isNodeOfType(initializer, "Literal") || initializer.value !== 0) return null;
|
|
57594
|
+
const stateSymbol = context.scopes.symbolFor(stateIdentifier);
|
|
57595
|
+
if (!stateSymbol) return null;
|
|
57596
|
+
const stateDerivedSymbolIds = new Set([stateSymbol.id]);
|
|
57597
|
+
let didAddDerivedSymbol = true;
|
|
57598
|
+
while (didAddDerivedSymbol) {
|
|
57599
|
+
didAddDerivedSymbol = false;
|
|
57600
|
+
for (const statement of componentBody.body ?? []) {
|
|
57601
|
+
if (!isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
57602
|
+
for (const candidateDeclarator of statement.declarations ?? []) {
|
|
57603
|
+
if (!isNodeOfType(candidateDeclarator.id, "Identifier") || !candidateDeclarator.init) continue;
|
|
57604
|
+
const candidateInitializer = stripParenExpression(candidateDeclarator.init);
|
|
57605
|
+
if (isFunctionLike$1(candidateInitializer) || isNodeOfType(candidateInitializer, "CallExpression") && isReactApiCall(candidateInitializer, USE_CALLBACK_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) continue;
|
|
57606
|
+
if (!expressionReadsDerivedSymbol(context, candidateInitializer, stateDerivedSymbolIds)) continue;
|
|
57607
|
+
const candidateSymbol = context.scopes.symbolFor(candidateDeclarator.id);
|
|
57608
|
+
if (candidateSymbol?.kind === "const" && candidateSymbol.references.every((reference) => reference.flag === "read" && !isTransparentAssignmentTarget(reference.identifier)) && !stateDerivedSymbolIds.has(candidateSymbol.id)) {
|
|
57609
|
+
stateDerivedSymbolIds.add(candidateSymbol.id);
|
|
57610
|
+
didAddDerivedSymbol = true;
|
|
57611
|
+
}
|
|
57612
|
+
}
|
|
57613
|
+
}
|
|
57614
|
+
}
|
|
57615
|
+
const staticSpreadVisibilityBySymbolId = /* @__PURE__ */ new Map();
|
|
57616
|
+
const hasOnlyStaticObjectReferences = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
57617
|
+
const symbol = context.scopes.symbolFor(identifier);
|
|
57618
|
+
if (!symbol) return false;
|
|
57619
|
+
if (visitedSymbolIds.has(symbol.id)) return true;
|
|
57620
|
+
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
57621
|
+
nextVisitedSymbolIds.add(symbol.id);
|
|
57622
|
+
let hasUnknownReference = false;
|
|
57623
|
+
walkAst(componentBody, (node) => {
|
|
57624
|
+
if (hasUnknownReference || !isNodeOfType(node, "Identifier") || context.scopes.symbolFor(node)?.id !== symbol.id || node === symbol.bindingIdentifier) return;
|
|
57625
|
+
const referenceRoot = findTransparentExpressionRoot(node);
|
|
57626
|
+
const parent = referenceRoot.parent;
|
|
57627
|
+
if (isNodeOfType(parent, "JSXSpreadAttribute") && parent.argument === referenceRoot) return;
|
|
57628
|
+
if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier") && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const" && hasOnlyStaticObjectReferences(parent.id, nextVisitedSymbolIds)) return;
|
|
57629
|
+
hasUnknownReference = true;
|
|
57630
|
+
return false;
|
|
57631
|
+
});
|
|
57632
|
+
return !hasUnknownReference;
|
|
57633
|
+
};
|
|
57634
|
+
const classifyStaticSpreadObject = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
57635
|
+
const symbol = context.scopes.symbolFor(identifier);
|
|
57636
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return "unknown";
|
|
57637
|
+
const cachedVisibility = staticSpreadVisibilityBySymbolId.get(symbol.id);
|
|
57638
|
+
if (cachedVisibility) return cachedVisibility;
|
|
57639
|
+
if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || symbol.declarationNode.id !== symbol.bindingIdentifier || !symbol.declarationNode.init) return "unknown";
|
|
57640
|
+
if (!hasOnlyStaticObjectReferences(identifier)) return "unknown";
|
|
57641
|
+
const initializer = stripParenExpression(symbol.declarationNode.init);
|
|
57642
|
+
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
57643
|
+
nextVisitedSymbolIds.add(symbol.id);
|
|
57644
|
+
if (isNodeOfType(initializer, "Identifier")) {
|
|
57645
|
+
const visibility = classifyStaticSpreadObject(initializer, nextVisitedSymbolIds);
|
|
57646
|
+
staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
|
|
57647
|
+
return visibility;
|
|
57648
|
+
}
|
|
57649
|
+
if (!isNodeOfType(initializer, "ObjectExpression")) return "unknown";
|
|
57650
|
+
let visibility = "non-visible";
|
|
57651
|
+
for (const property of initializer.properties ?? []) {
|
|
57652
|
+
const propertyName = getStaticObjectPropertyName(property);
|
|
57653
|
+
if (!isNodeOfType(property, "Property") || !propertyName) {
|
|
57654
|
+
visibility = "unknown";
|
|
57655
|
+
break;
|
|
57656
|
+
}
|
|
57657
|
+
if (expressionReadsDerivedSymbol(context, property.value, stateDerivedSymbolIds) && !isNonVisibleJsxSpreadProperty(propertyName)) visibility = "visible";
|
|
57658
|
+
}
|
|
57659
|
+
staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
|
|
57660
|
+
return visibility;
|
|
57661
|
+
};
|
|
57662
|
+
let hasNonAriaReference = false;
|
|
57663
|
+
walkAst(componentBody, (node) => {
|
|
57664
|
+
if (hasNonAriaReference) return false;
|
|
57665
|
+
if (!isNodeOfType(node, "Identifier") || !stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) return;
|
|
57666
|
+
if (findEnclosingFunction$1(node) !== componentFunction) return;
|
|
57667
|
+
const parent = node.parent;
|
|
57668
|
+
if (parent && (isNodeOfType(parent, "MemberExpression") && parent.property === node && !parent.computed || isNodeOfType(parent, "Property") && parent.key === node && !parent.computed)) return;
|
|
57669
|
+
let cursor = parent;
|
|
57670
|
+
while (cursor && cursor !== componentBody) {
|
|
57671
|
+
if (isFunctionLike$1(cursor)) return;
|
|
57672
|
+
if (isNodeOfType(cursor, "JSXSpreadAttribute")) {
|
|
57673
|
+
if (isNodeOfType(node, "Identifier") && classifyStaticSpreadObject(node) === "visible") hasNonAriaReference = true;
|
|
57674
|
+
return;
|
|
57675
|
+
}
|
|
57676
|
+
if (isNodeOfType(cursor, "JSXAttribute")) {
|
|
57677
|
+
if (isEventHandlerAttribute(cursor)) return;
|
|
57678
|
+
if (!isInsideIdOrAriaAttribute(node)) hasNonAriaReference = true;
|
|
57679
|
+
return;
|
|
57680
|
+
}
|
|
57681
|
+
if (isNodeOfType(cursor, "ReturnStatement")) {
|
|
57682
|
+
hasNonAriaReference = true;
|
|
57683
|
+
return;
|
|
57684
|
+
}
|
|
57685
|
+
cursor = cursor.parent;
|
|
57686
|
+
}
|
|
57687
|
+
});
|
|
57688
|
+
return hasNonAriaReference ? stateIdentifier.name : null;
|
|
57689
|
+
};
|
|
57690
|
+
const isExactViewportSubscriptionEffect = (context, effectCall, callback) => {
|
|
57691
|
+
if (!isReactApiCall(effectCall, USE_EFFECT_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return false;
|
|
57692
|
+
if (!isFunctionLike$1(callback) || callback.async || !isNodeOfType(callback.body, "BlockStatement")) return false;
|
|
57693
|
+
const statements = getCallbackStatements(callback);
|
|
57694
|
+
if (statements.length !== 4) return false;
|
|
57695
|
+
const handlerDeclaration = statements[0];
|
|
57696
|
+
if (!isNodeOfType(handlerDeclaration, "VariableDeclaration") || handlerDeclaration.kind !== "const" || handlerDeclaration.declarations?.length !== 1) return false;
|
|
57697
|
+
const handlerDeclarator = handlerDeclaration.declarations[0];
|
|
57698
|
+
if (!isNodeOfType(handlerDeclarator.id, "Identifier") || !isFunctionLike$1(handlerDeclarator.init)) return false;
|
|
57699
|
+
const handlerStatements = getCallbackStatements(handlerDeclarator.init);
|
|
57700
|
+
if (handlerStatements.length !== 1) return false;
|
|
57701
|
+
const handlerSetter = getDirectWindowWidthSetter(context, unwrapReturnExpression(handlerStatements[0]));
|
|
57702
|
+
const subscribedHandler = getResizeListenerHandler(context, statements[1], "addEventListener");
|
|
57703
|
+
const immediateSetter = getDirectWindowWidthSetter(context, statements[2]);
|
|
57704
|
+
const cleanupHandler = getCleanupResizeHandler(context, statements[3]);
|
|
57705
|
+
if (!handlerSetter || !subscribedHandler || !immediateSetter || !cleanupHandler) return false;
|
|
57706
|
+
const handlerSymbol = context.scopes.symbolFor(handlerDeclarator.id);
|
|
57707
|
+
if (!handlerSymbol || context.scopes.symbolFor(subscribedHandler) !== handlerSymbol || context.scopes.symbolFor(cleanupHandler) !== handlerSymbol) return false;
|
|
57708
|
+
if (!isNodeOfType(handlerSetter.callee, "Identifier") || !isNodeOfType(immediateSetter.callee, "Identifier") || context.scopes.symbolFor(handlerSetter.callee) !== context.scopes.symbolFor(immediateSetter.callee)) return false;
|
|
57709
|
+
const componentFunction = findEnclosingFunction$1(effectCall);
|
|
57710
|
+
if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return false;
|
|
57711
|
+
return findExactViewportState(context, componentFunction, immediateSetter) !== null;
|
|
57712
|
+
};
|
|
56418
57713
|
const renderingHydrationNoFlicker = defineRule({
|
|
56419
57714
|
id: "rendering-hydration-no-flicker",
|
|
56420
57715
|
title: "useEffect setState flashes on mount",
|
|
@@ -56427,7 +57722,14 @@ const renderingHydrationNoFlicker = defineRule({
|
|
|
56427
57722
|
if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
|
|
56428
57723
|
const callback = getEffectCallback(node);
|
|
56429
57724
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
56430
|
-
|
|
57725
|
+
if (isExactViewportSubscriptionEffect(context, node, callback)) {
|
|
57726
|
+
context.report({
|
|
57727
|
+
node,
|
|
57728
|
+
message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
|
|
57729
|
+
});
|
|
57730
|
+
return;
|
|
57731
|
+
}
|
|
57732
|
+
const bodyStatements = getCallbackStatements(callback);
|
|
56431
57733
|
if (bodyStatements.length !== 1) return;
|
|
56432
57734
|
const soleStatement = bodyStatements[0];
|
|
56433
57735
|
if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
|
|
@@ -66259,14 +67561,6 @@ const isStateKey = (key) => {
|
|
|
66259
67561
|
if (isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value === "state";
|
|
66260
67562
|
return false;
|
|
66261
67563
|
};
|
|
66262
|
-
const findEnclosingClass = (node) => {
|
|
66263
|
-
let ancestor = node.parent;
|
|
66264
|
-
while (ancestor) {
|
|
66265
|
-
if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
|
|
66266
|
-
ancestor = ancestor.parent ?? null;
|
|
66267
|
-
}
|
|
66268
|
-
return null;
|
|
66269
|
-
};
|
|
66270
67564
|
const isInConstructor = (node) => {
|
|
66271
67565
|
let ancestor = node.parent;
|
|
66272
67566
|
while (ancestor) {
|
|
@@ -70978,6 +72272,17 @@ const reactDoctorRules = [
|
|
|
70978
72272
|
requires: [...new Set(["react", ...noRedundantShouldComponentUpdate.requires ?? []])]
|
|
70979
72273
|
}
|
|
70980
72274
|
},
|
|
72275
|
+
{
|
|
72276
|
+
key: "react-doctor/no-ref-callback-cleanup-before-react-19",
|
|
72277
|
+
id: "no-ref-callback-cleanup-before-react-19",
|
|
72278
|
+
source: "react-doctor",
|
|
72279
|
+
originallyExternal: false,
|
|
72280
|
+
rule: {
|
|
72281
|
+
...noRefCallbackCleanupBeforeReact19,
|
|
72282
|
+
framework: "global",
|
|
72283
|
+
category: "Bugs"
|
|
72284
|
+
}
|
|
72285
|
+
},
|
|
70981
72286
|
{
|
|
70982
72287
|
key: "react-doctor/no-ref-current-in-render",
|
|
70983
72288
|
id: "no-ref-current-in-render",
|