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.
Files changed (3) hide show
  1. package/dist/index.d.ts +46 -0
  2. package/dist/index.js +1482 -177
  3. 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 isWebStorageObject = (node) => {
6765
- if (isDirectWebStorageObject(node)) return true;
6766
- if (!isNodeOfType(node, "Identifier")) return false;
6767
- const binding = findVariableInitializer(node, node.name);
6768
- return binding?.initializer ? isDirectWebStorageObject(binding.initializer) : false;
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
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
6797
- if (!isNodeOfType(callee.property, "Identifier") || callee.property.name !== "setItem") return;
6798
- if (!isWebStorageObject(stripParenExpression(callee.object))) return;
6799
- const keyArgument = node.arguments?.[0];
6800
- if (!keyArgument) return;
6801
- const keyString = resolveStaticKeyString(keyArgument);
6802
- if (keyString === null || !isAuthCredentialKey(keyString)) return;
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
- return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey;
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.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
30108
- const namespaceReference = getRef(analysis, callee.object);
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 parent = node.parent;
30118
- if (parent && isNodeOfType(parent, "MemberExpression") && isNodeOfType(parent.object, "Identifier") && parent.object.name === "React" && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
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 hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
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
- let qualifies = false;
36697
- walkAst(test, (node) => {
36698
- if (qualifies) return false;
36699
- if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
36700
- const leftFieldName = getThisStateFieldName(node.left);
36701
- const rightFieldName = getThisStateFieldName(node.right);
36702
- const fieldName = leftFieldName ?? rightFieldName;
36703
- const comparedValue = leftFieldName ? node.right : node.left;
36704
- if (!fieldName || !leftFieldName && !rightFieldName) return;
36705
- const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
36706
- if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
36707
- if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
36708
- qualifies = true;
36709
- return false;
36710
- });
36711
- return qualifies;
36712
- };
36713
- const isDiffGuardTest = (test, paramNames, derivedNames) => {
36714
- if (referencesAnyName(test, paramNames)) return true;
36715
- let qualifies = false;
36716
- walkAst(test, (node) => {
36717
- if (qualifies) return false;
36718
- if (!isNodeOfType(node, "BinaryExpression")) return;
36719
- if (!EQUALITY_OPERATORS.has(node.operator)) return;
36720
- if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames) && (referencesAnyName(node.left, derivedNames) || referencesAnyName(node.right, derivedNames))) {
36721
- qualifies = true;
36722
- return false;
36723
- }
36724
- });
36725
- return qualifies;
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 isInsideDiffGuard = (setStateCall) => {
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
- for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
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
- const guardTest = isNodeOfType(ancestor, "IfStatement") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "ConditionalExpression") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right && ancestor.left || null;
36738
- if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers))) return true;
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 matchHydrationCondition = (expression, context) => {
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, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
40503
- if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
40504
- const leftMatch = matchHydrationCondition(unwrappedExpression.left, context);
40505
- const rightMatch = matchHydrationCondition(unwrappedExpression.right, context);
40506
- if (leftMatch && rightMatch) {
40507
- const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client");
40508
- const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server");
40509
- return clientResult !== null && serverResult !== null && clientResult !== serverResult ? leftMatch : null;
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 otherResult = readInitialStateBoolean(leftMatch ? unwrappedExpression.right : unwrappedExpression.left, context.scopes);
40514
- if (unwrappedExpression.operator === "&&" && otherResult === false || unwrappedExpression.operator === "||" && otherResult === true) return null;
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(predicateNode, context.scopes);
41451
+ const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes);
40660
41452
  if (!componentOrHookNode) return;
40661
41453
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
40662
- if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
41454
+ if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes)) return;
40663
41455
  if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
40664
- const attribute = findEnclosingJsxAttribute(predicateNode);
41456
+ const attribute = findEnclosingJsxAttribute(conditionNode);
40665
41457
  if (!attribute || isEventHandlerAttribute(attribute)) return;
40666
41458
  }
40667
- if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
40668
- if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
40669
- const openingElement = findEnclosingJsxOpeningElement(predicateNode);
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 checkReturnType = (returnType) => {
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
- checkReturnType(node.returnType);
42212
+ collectComponentReturnType(node, node.returnType);
41420
42213
  },
41421
42214
  ArrowFunctionExpression(node) {
41422
- checkReturnType(node.returnType);
42215
+ collectComponentReturnType(node, node.returnType);
41423
42216
  },
41424
42217
  FunctionExpression(node) {
41425
- checkReturnType(node.returnType);
42218
+ collectComponentReturnType(node, node.returnType);
41426
42219
  },
41427
42220
  TSDeclareFunction(node) {
41428
- checkReturnType(node.returnType);
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 || !isFunctionLike$1(wrapped)) return null;
44493
- return wrapped;
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
- return getWrapperHookWrappedFunction(unwrapChainExpression(node.init)) !== null;
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) => isProp(analysis, 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) => isProp(analysis, 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
- if (callbackRefProvenance) {
44915
- if ([...callbackRefProvenance.callbackPropNames].some((callbackPropName) => COMMAND_PROP_NAME_PATTERN.test(callbackPropName))) continue;
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 (!callbackRefProvenance && isNamespacedApiCallee(calleeNode)) continue;
44930
- const isSetterNamedCallee = callbackRefProvenance ? [...callbackRefProvenance.callbackPropNames].every((callbackPropName) => SETTER_NAMED_PROP_PATTERN.test(callbackPropName)) : Boolean((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) && SETTER_NAMED_PROP_PATTERN.test((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) ?? ""));
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, ref, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef));
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 calleeName = isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName);
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
- if (!isResultDiscardedCall(child)) return;
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
- const bodyStatements = (isNodeOfType(callback.body, "BlockStatement") ? callback.body.body ?? [] : [callback.body]).filter((statement) => !isNoOpStatement(statement));
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",