oxlint-plugin-react-doctor 0.7.9-dev.71892af → 0.7.9-dev.842a55f

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