oxlint-plugin-react-doctor 0.7.9-dev.2ba83c3 → 0.7.9-dev.4f3848b

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 +1322 -120
  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;
@@ -8959,7 +8960,7 @@ const isReactNamespaceImport = (identifier, scopes) => {
8959
8960
  if (!symbol || !isImportedFromReact(symbol)) return false;
8960
8961
  return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
8961
8962
  };
8962
- const isReactNamespaceReceiver = (receiver, scopes, options) => {
8963
+ const isReactNamespaceReceiver$1 = (receiver, scopes, options) => {
8963
8964
  if (!isNodeOfType(receiver, "Identifier")) return false;
8964
8965
  if (isReactNamespaceImport(receiver, scopes)) return true;
8965
8966
  return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
@@ -8972,7 +8973,7 @@ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) =>
8972
8973
  for (const property of pattern.properties) {
8973
8974
  if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
8974
8975
  const propertyName = getStaticPropertyKeyName(property);
8975
- 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));
8976
8977
  }
8977
8978
  return false;
8978
8979
  };
@@ -8996,7 +8997,7 @@ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds
8996
8997
  return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
8997
8998
  }
8998
8999
  if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
8999
- return isReactNamespaceReceiver(stripParenExpression(callee.object), scopes, options);
9000
+ return isReactNamespaceReceiver$1(stripParenExpression(callee.object), scopes, options);
9000
9001
  };
9001
9002
  //#endregion
9002
9003
  //#region src/plugin/utils/is-proven-browser-api-receiver.ts
@@ -13652,7 +13653,7 @@ const getPromiseChainCallForCallback = (candidate) => {
13652
13653
  if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
13653
13654
  return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
13654
13655
  };
13655
- const collectEffectInvokedFunctions = (effectCallback) => {
13656
+ const collectInvokedFunctions = (effectCallback, includePromiseCallbacks) => {
13656
13657
  const invokedFunctions = new Set([effectCallback]);
13657
13658
  const localFunctionBindings = /* @__PURE__ */ new Map();
13658
13659
  const calledBindingNames = /* @__PURE__ */ new Set();
@@ -13686,12 +13687,14 @@ const collectEffectInvokedFunctions = (effectCallback) => {
13686
13687
  calledBindingNames.add(callee.name);
13687
13688
  return;
13688
13689
  }
13689
- if (isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
13690
+ if (includePromiseCallbacks && isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
13690
13691
  });
13691
13692
  for (const calledName of calledBindingNames) enqueue(localFunctionBindings.get(calledName));
13692
13693
  }
13693
13694
  return invokedFunctions;
13694
13695
  };
13696
+ const collectEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, true);
13697
+ const collectSynchronouslyEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, false);
13695
13698
  //#endregion
13696
13699
  //#region src/plugin/utils/is-react-hook-name.ts
13697
13700
  const isReactHookName = (name) => {
@@ -13850,15 +13853,19 @@ const resolveReactRefSymbol = (memberExpression, scopes) => {
13850
13853
  if (!isNodeOfType(initializer, "CallExpression")) return null;
13851
13854
  return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
13852
13855
  };
13853
- const hasReactRefCurrentOrigin = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13856
+ const resolveReactRefCurrentOriginSymbol = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13854
13857
  const expression = stripParenExpression(node);
13855
- if (resolveReactRefSymbol(expression, scopes)) return true;
13856
- if (!isNodeOfType(expression, "Identifier")) return false;
13857
- const symbol = resolveConstIdentifierAlias(expression, scopes);
13858
- 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;
13859
13865
  visitedSymbolIds.add(symbol.id);
13860
- return hasReactRefCurrentOrigin(symbol.initializer, scopes, visitedSymbolIds);
13866
+ return resolveReactRefCurrentOriginSymbol(initializer, scopes, visitedSymbolIds);
13861
13867
  };
13868
+ const hasReactRefCurrentOrigin = (node, scopes) => resolveReactRefCurrentOriginSymbol(node, scopes) !== null;
13862
13869
  //#endregion
13863
13870
  //#region src/plugin/utils/walk-inside-statement-blocks.ts
13864
13871
  const walkInsideStatementBlocks = (node, visitor) => {
@@ -14340,6 +14347,39 @@ const findContainingCollectionKey = (resourceNode, context) => {
14340
14347
  }
14341
14348
  return null;
14342
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
+ };
14343
14383
  const isWithinAssignmentTarget = (identifier) => {
14344
14384
  let currentNode = identifier;
14345
14385
  let parentNode = currentNode.parent;
@@ -14398,6 +14438,14 @@ const isSynchronousIteratorCallback = (functionNode) => {
14398
14438
  if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments?.[1] === functionNode;
14399
14439
  return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments?.[0] === functionNode;
14400
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
+ };
14401
14449
  const findDirectCallForReference = (identifier) => {
14402
14450
  const expressionRoot = findTransparentExpressionRoot(identifier);
14403
14451
  const callNode = expressionRoot.parent;
@@ -14412,7 +14460,7 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
14412
14460
  const callNode = findDirectCallForReference(reference.identifier);
14413
14461
  return callNode ? [callNode] : [];
14414
14462
  });
14415
- if (invocationCalls.length !== 1) return null;
14463
+ if (invocationCalls.length !== 1 || symbol.references.length !== 1) return null;
14416
14464
  const invocationCall = invocationCalls[0];
14417
14465
  return findEnclosingFunction$1(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
14418
14466
  };
@@ -14446,7 +14494,15 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
14446
14494
  if (cleanupChild !== cleanupFunction.body && isFunctionLike$1(cleanupChild) && !isSynchronousIteratorCallback(cleanupChild)) return false;
14447
14495
  const cleanupCall = isNodeOfType(cleanupChild, "ChainExpression") ? cleanupChild.expression : cleanupChild;
14448
14496
  if (doesReleaseCallMatchUsage(cleanupChild, usage, context)) {
14449
- 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;
14450
14506
  if (!cleanupForOfStatement) {
14451
14507
  didCleanupFunctionMatch = true;
14452
14508
  return false;
@@ -14490,28 +14546,28 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
14490
14546
  });
14491
14547
  return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
14492
14548
  };
14493
- const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => {
14494
- if (usage.handleKey === null) return null;
14495
- const doesTestRequireLiveHandle = (test) => {
14496
- if (resolveExpressionKey(test, context) === usage.handleKey) return true;
14497
- const unwrappedTest = stripParenExpression(test);
14498
- if (!isNodeOfType(unwrappedTest, "BinaryExpression") || unwrappedTest.operator !== "!=" && unwrappedTest.operator !== "!==") return false;
14499
- const isNullishOperand = (operand) => {
14500
- const unwrappedOperand = stripParenExpression(operand);
14501
- return isNodeOfType(unwrappedOperand, "Literal") && unwrappedOperand.value === null || isNodeOfType(unwrappedOperand, "Identifier") && unwrappedOperand.name === "undefined" && context.scopes.isGlobalReference(unwrappedOperand);
14502
- };
14503
- 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);
14504
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) => {
14505
14560
  let ancestor = releaseCall.parent;
14506
14561
  while (ancestor && ancestor !== owner) {
14507
14562
  if (isNodeOfType(ancestor, "IfStatement")) {
14508
- 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;
14509
14564
  return ancestor;
14510
14565
  }
14511
14566
  ancestor = ancestor.parent;
14512
14567
  }
14513
14568
  return null;
14514
14569
  };
14570
+ const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => usage.handleKey === null ? null : findLiveExpressionGuardForRelease(releaseCall, owner, usage.handleKey, context);
14515
14571
  const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
14516
14572
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
14517
14573
  const functionCfg = context.cfg.cfgFor(callback);
@@ -14699,7 +14755,108 @@ const hasPotentialInterruptionAfterGuard = (callback, guardState, usageNode, con
14699
14755
  });
14700
14756
  return hasPotentialInterruption;
14701
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
+ };
14702
14858
  const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
14859
+ if (hasGuardedRefOwnedNestedCleanup(callback, usage, cleanupReturns, context)) return true;
14703
14860
  const usageFunction = findEnclosingFunction$1(usage.node);
14704
14861
  const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
14705
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;
@@ -14870,7 +15027,7 @@ const getReleaseVerbName = (node) => {
14870
15027
  const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) => {
14871
15028
  const releaseFunction = findEnclosingFunction$1(releaseReceiver);
14872
15029
  const usageFunction = findEnclosingFunction$1(usage.node);
14873
- if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction) || !hasReactRefCurrentOrigin(releaseReceiver, context.scopes)) return false;
15030
+ if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction) || !resolveReactRefCurrentOriginSymbol(releaseReceiver, context.scopes)) return false;
14874
15031
  const controllerKey = getListenerAbortControllerKey(usage, context);
14875
15032
  const refCurrentKey = resolveExpressionKey(releaseReceiver, context);
14876
15033
  if (controllerKey === null || refCurrentKey === null) return false;
@@ -14890,6 +15047,62 @@ const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) =>
14890
15047
  const safeOwnershipAssignments = ownershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, previousAbortCalls, usageFunction, context));
14891
15048
  return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
14892
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
+ };
14893
15106
  const doesReleaseCallMatchUsage = (node, usage, context) => {
14894
15107
  const callNode = isNodeOfType(node, "ChainExpression") ? node.expression : node;
14895
15108
  if (!isNodeOfType(callNode, "CallExpression")) return false;
@@ -14906,14 +15119,21 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
14906
15119
  if (!releaseVerbName) return false;
14907
15120
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
14908
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;
14909
15129
  if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
14910
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;
14911
15131
  if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
14912
15132
  if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
14913
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;
14914
15135
  const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
14915
15136
  if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
14916
- const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
14917
15137
  const usageEventArgument = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[0] : null;
14918
15138
  const releaseEventArgument = callNode.arguments?.[0];
14919
15139
  if (isAssignmentFormForOfIteratorReference(usageEventArgument, context) || isAssignmentFormForOfIteratorReference(releaseEventArgument, context)) return false;
@@ -14947,7 +15167,8 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
14947
15167
  const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
14948
15168
  if (!releaseHandler) return releaseVerbName === "off";
14949
15169
  const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
14950
- return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey;
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);
14951
15172
  }
14952
15173
  if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
14953
15174
  return true;
@@ -14972,13 +15193,165 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
14972
15193
  if (!symbol) return false;
14973
15194
  return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
14974
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
+ };
14975
15346
  const isReleaseReachableForUsage = (releaseNode, usage, context) => {
14976
15347
  if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
14977
15348
  const releaseFunction = findEnclosingFunction$1(releaseNode);
14978
15349
  if (!releaseFunction) return true;
14979
15350
  if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
15351
+ if (isRetainedDisposerRefRelease(releaseNode, usage, context)) return true;
14980
15352
  const usageFunction = findEnclosingFunction$1(usage.node);
14981
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;
14982
15355
  return isPotentiallyReachableFunction(releaseFunction, context);
14983
15356
  };
14984
15357
  const fileContainsReleaseForUsage = (usage, context) => {
@@ -17112,7 +17485,7 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allow
17112
17485
  keys.add(depKey);
17113
17486
  continue;
17114
17487
  }
17115
- const identitySourceKeys = resolveReactiveIdentitySourceKeys(symbol, scopes);
17488
+ const identitySourceKeys = resolvePureCalledFunctionSourceKeys(reference, symbol, scopes) ?? resolveRenderDerivedMutableSourceKeys(reference, symbol, scopes) ?? resolveReactiveIdentitySourceKeys(symbol, scopes);
17116
17489
  if (identitySourceKeys) {
17117
17490
  if (identitySourceKeys.size === 0) stableCapturedNames.add(depKey);
17118
17491
  for (const identitySourceKey of identitySourceKeys) keys.add(identitySourceKey);
@@ -17195,6 +17568,161 @@ const resolveReactiveIdentitySourceKeys = (symbol, scopes) => {
17195
17568
  if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier || symbol.references.some((reference) => reference.flag !== "read")) return null;
17196
17569
  return resolveIdentitySourceKeysFromExpression(symbol.initializer, scopes, new Set([symbol.id]));
17197
17570
  };
17571
+ const isPureDerivedExpression = (expression) => {
17572
+ const candidate = unwrapExpression$3(expression);
17573
+ if (isNodeOfType(candidate, "Literal") || isNodeOfType(candidate, "Identifier")) return true;
17574
+ if (isNodeOfType(candidate, "MemberExpression")) return isPureDerivedExpression(candidate.object) && (!candidate.computed || isPureDerivedExpression(candidate.property));
17575
+ if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return isPureDerivedExpression(candidate.left) && isPureDerivedExpression(candidate.right);
17576
+ if (isNodeOfType(candidate, "UnaryExpression")) return candidate.operator !== "delete" && isPureDerivedExpression(candidate.argument);
17577
+ if (isNodeOfType(candidate, "ConditionalExpression")) return isPureDerivedExpression(candidate.test) && isPureDerivedExpression(candidate.consequent) && isPureDerivedExpression(candidate.alternate);
17578
+ if (isNodeOfType(candidate, "TemplateLiteral")) return candidate.expressions.every((nestedExpression) => isPureDerivedExpression(nestedExpression));
17579
+ return false;
17580
+ };
17581
+ const isPureDerivedStatement = (statement) => {
17582
+ if (isNodeOfType(statement, "BlockStatement")) return statement.body.every((nestedStatement) => isPureDerivedStatement(nestedStatement));
17583
+ if (isNodeOfType(statement, "ReturnStatement")) return !statement.argument || isPureDerivedExpression(statement.argument);
17584
+ if (isNodeOfType(statement, "IfStatement")) return isPureDerivedExpression(statement.test) && isPureDerivedStatement(statement.consequent) && (!statement.alternate || isPureDerivedStatement(statement.alternate));
17585
+ return false;
17586
+ };
17587
+ const isPureDerivedFunction = (functionNode) => {
17588
+ if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return false;
17589
+ if (functionNode.async || functionNode.generator) return false;
17590
+ return isNodeOfType(functionNode.body, "BlockStatement") ? isPureDerivedStatement(functionNode.body) : isPureDerivedExpression(functionNode.body);
17591
+ };
17592
+ const resolvePureCalledFunctionSourceKeys = (reference, symbol, scopes) => {
17593
+ if (symbol.references.some((symbolReference) => symbolReference.flag !== "read")) return null;
17594
+ const referenceRoot = findTransparentExpressionRoot(reference.identifier);
17595
+ const callExpression = referenceRoot.parent;
17596
+ if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== referenceRoot) return null;
17597
+ const functionNode = getFunctionValueNode(symbol);
17598
+ if (!functionNode || !isPureDerivedFunction(functionNode)) return null;
17599
+ const sourceKeys = /* @__PURE__ */ new Set();
17600
+ for (const capturedReference of closureCaptures(functionNode, scopes)) {
17601
+ const capturedSymbol = capturedReference.resolvedSymbol;
17602
+ if (!capturedSymbol || capturedSymbol.id === symbol.id) continue;
17603
+ if (isOutsideAllFunctions(capturedSymbol) || symbolHasStableValue(capturedSymbol, scopes)) continue;
17604
+ const capturedKey = computeDepKey(capturedReference);
17605
+ if (!capturedKey) return null;
17606
+ if (capturedKey === capturedSymbol.name) {
17607
+ const nestedSourceKeys = resolveReactiveIdentitySourceKeys(capturedSymbol, scopes);
17608
+ if (nestedSourceKeys) {
17609
+ for (const nestedSourceKey of nestedSourceKeys) sourceKeys.add(nestedSourceKey);
17610
+ continue;
17611
+ }
17612
+ }
17613
+ sourceKeys.add(capturedKey);
17614
+ }
17615
+ return sourceKeys.size > 0 ? sourceKeys : null;
17616
+ };
17617
+ const mergeDerivedExpressionSourceKeys = (expressions, scopes, visitedSymbolIds) => {
17618
+ const sourceKeys = /* @__PURE__ */ new Set();
17619
+ for (const expression of expressions) {
17620
+ const expressionSourceKeys = resolveDerivedExpressionSourceKeys(expression, scopes, visitedSymbolIds);
17621
+ if (!expressionSourceKeys) return null;
17622
+ for (const expressionSourceKey of expressionSourceKeys) sourceKeys.add(expressionSourceKey);
17623
+ }
17624
+ return sourceKeys;
17625
+ };
17626
+ const resolveDerivedExpressionSourceKeys = (expression, scopes, visitedSymbolIds) => {
17627
+ const candidate = unwrapExpression$3(expression);
17628
+ if (isNodeOfType(candidate, "Literal")) return /* @__PURE__ */ new Set();
17629
+ if (isNodeOfType(candidate, "Identifier")) {
17630
+ if (scopes.isGlobalReference(candidate)) return /* @__PURE__ */ new Set();
17631
+ const sourceSymbol = scopes.symbolFor(candidate);
17632
+ if (!sourceSymbol) return null;
17633
+ if (isOutsideAllFunctions(sourceSymbol) || symbolHasStableValue(sourceSymbol, scopes)) return /* @__PURE__ */ new Set();
17634
+ 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)) {
17635
+ visitedSymbolIds.add(sourceSymbol.id);
17636
+ const sourceKeys = resolveDerivedExpressionSourceKeys(sourceSymbol.initializer, scopes, visitedSymbolIds);
17637
+ visitedSymbolIds.delete(sourceSymbol.id);
17638
+ if (sourceKeys) return sourceKeys;
17639
+ }
17640
+ return new Set([sourceSymbol.name]);
17641
+ }
17642
+ if (isNodeOfType(candidate, "MemberExpression")) {
17643
+ if (hasComputedMemberExpression(candidate)) return null;
17644
+ const sourceKey = stringifyMemberChain(candidate);
17645
+ const rootIdentifier = getMemberRootIdentifier(candidate);
17646
+ const rootSymbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
17647
+ if (!sourceKey || !rootSymbol) return null;
17648
+ if (isOutsideAllFunctions(rootSymbol) || symbolHasStableValue(rootSymbol, scopes)) return /* @__PURE__ */ new Set();
17649
+ return new Set([sourceKey]);
17650
+ }
17651
+ if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return mergeDerivedExpressionSourceKeys([candidate.left, candidate.right], scopes, visitedSymbolIds);
17652
+ if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator !== "delete") return resolveDerivedExpressionSourceKeys(candidate.argument, scopes, visitedSymbolIds);
17653
+ if (isNodeOfType(candidate, "ConditionalExpression")) return mergeDerivedExpressionSourceKeys([
17654
+ candidate.test,
17655
+ candidate.consequent,
17656
+ candidate.alternate
17657
+ ], scopes, visitedSymbolIds);
17658
+ if (isNodeOfType(candidate, "TemplateLiteral")) return mergeDerivedExpressionSourceKeys(candidate.expressions, scopes, visitedSymbolIds);
17659
+ if (isNodeOfType(candidate, "NewExpression")) {
17660
+ const callee = unwrapExpression$3(candidate.callee);
17661
+ if (!isNodeOfType(callee, "Identifier") || callee.name !== "Error" || !scopes.isGlobalReference(callee)) return null;
17662
+ const argumentsToAnalyze = [];
17663
+ for (const argument of candidate.arguments) {
17664
+ if (!isAstNode(argument) || isNodeOfType(argument, "SpreadElement")) return null;
17665
+ argumentsToAnalyze.push(argument);
17666
+ }
17667
+ return mergeDerivedExpressionSourceKeys(argumentsToAnalyze, scopes, visitedSymbolIds);
17668
+ }
17669
+ return null;
17670
+ };
17671
+ const resolveWriteControlSourceKeys = (assignment, boundaryFunction, scopes) => {
17672
+ const sourceKeys = /* @__PURE__ */ new Set();
17673
+ let currentNode = assignment;
17674
+ while (currentNode.parent && currentNode.parent !== boundaryFunction) {
17675
+ const parentNode = currentNode.parent;
17676
+ if (isNodeOfType(parentNode, "IfStatement")) {
17677
+ if (parentNode.test === currentNode) return null;
17678
+ const testSourceKeys = resolveDerivedExpressionSourceKeys(parentNode.test, scopes, /* @__PURE__ */ new Set());
17679
+ if (!testSourceKeys) return null;
17680
+ for (const testSourceKey of testSourceKeys) sourceKeys.add(testSourceKey);
17681
+ } else if (!isNodeOfType(parentNode, "ExpressionStatement") && !isNodeOfType(parentNode, "BlockStatement")) return null;
17682
+ currentNode = parentNode;
17683
+ }
17684
+ return currentNode.parent === boundaryFunction ? sourceKeys : null;
17685
+ };
17686
+ const isReadOnlyInitialStateUse = (referenceNode, scopes) => {
17687
+ const referenceRoot = findTransparentExpressionRoot(referenceNode);
17688
+ const callExpression = referenceRoot.parent;
17689
+ return isNodeOfType(callExpression, "CallExpression") && callExpression.arguments.some((argument) => argument === referenceRoot) && isReactApiCall(callExpression, "useState", scopes, {
17690
+ allowGlobalReactNamespace: true,
17691
+ allowUnboundBareCalls: true,
17692
+ resolveNamedAliases: true
17693
+ });
17694
+ };
17695
+ const resolveRenderDerivedMutableSourceKeys = (capturedReference, symbol, scopes) => {
17696
+ if (symbol.kind !== "let" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
17697
+ const boundaryFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
17698
+ if (!boundaryFunction) return null;
17699
+ const capturingFunction = findEnclosingFunction$1(capturedReference.identifier);
17700
+ if (!capturingFunction || capturingFunction === boundaryFunction) return null;
17701
+ const sourceKeys = /* @__PURE__ */ new Set();
17702
+ if (symbol.initializer) {
17703
+ const initializerSourceKeys = resolveDerivedExpressionSourceKeys(symbol.initializer, scopes, new Set([symbol.id]));
17704
+ if (!initializerSourceKeys) return null;
17705
+ for (const initializerSourceKey of initializerSourceKeys) sourceKeys.add(initializerSourceKey);
17706
+ }
17707
+ let writeCount = 0;
17708
+ for (const symbolReference of symbol.references) {
17709
+ if (symbolReference.flag === "read") {
17710
+ if (findEnclosingFunction$1(symbolReference.identifier) !== capturingFunction && !isReadOnlyInitialStateUse(symbolReference.identifier, scopes)) return null;
17711
+ continue;
17712
+ }
17713
+ if (symbolReference.flag !== "write") return null;
17714
+ const referenceRoot = findTransparentExpressionRoot(symbolReference.identifier);
17715
+ const assignment = referenceRoot.parent;
17716
+ if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== referenceRoot || findEnclosingFunction$1(referenceRoot) !== boundaryFunction) return null;
17717
+ const assignmentSourceKeys = resolveDerivedExpressionSourceKeys(assignment.right, scopes, new Set([symbol.id]));
17718
+ const controlSourceKeys = resolveWriteControlSourceKeys(assignment, boundaryFunction, scopes);
17719
+ if (!assignmentSourceKeys || !controlSourceKeys) return null;
17720
+ for (const assignmentSourceKey of assignmentSourceKeys) sourceKeys.add(assignmentSourceKey);
17721
+ for (const controlSourceKey of controlSourceKeys) sourceKeys.add(controlSourceKey);
17722
+ writeCount += 1;
17723
+ }
17724
+ return writeCount > 0 && sourceKeys.size > 0 ? sourceKeys : null;
17725
+ };
17198
17726
  const isUseCallbackResultDep = (node, scopes) => {
17199
17727
  const rootSymbol = getRootSymbol(node, scopes);
17200
17728
  const initializer = rootSymbol?.initializer ? unwrapExpression$3(rootSymbol.initializer) : null;
@@ -30212,6 +30740,12 @@ const isReactNamespaceImportReference = (ref) => Boolean(ref?.resolved?.defs.som
30212
30740
  const importDeclaration = declarationNode.parent;
30213
30741
  return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
30214
30742
  }));
30743
+ const isReactNamespaceReceiver = (analysis, node) => {
30744
+ const receiver = stripParenExpression(node);
30745
+ if (!isNodeOfType(receiver, "Identifier")) return false;
30746
+ const namespaceReference = getRef(analysis, receiver);
30747
+ return namespaceReference?.resolved ? isReactNamespaceImportReference(namespaceReference) : receiver.name === "React";
30748
+ };
30215
30749
  const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
30216
30750
  if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return false;
30217
30751
  const callee = stripParenExpression(declarator.init.callee);
@@ -30220,24 +30754,20 @@ const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
30220
30754
  if (!reference?.resolved) return callee.name === hookName;
30221
30755
  return isReactNamedImportReference(reference, hookName);
30222
30756
  }
30223
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
30224
- const namespaceReference = getRef(analysis, callee.object);
30225
- if (!namespaceReference?.resolved) return callee.object.name === "React";
30226
- return isReactNamespaceImportReference(namespaceReference);
30757
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
30758
+ return isReactNamespaceReceiver(analysis, callee.object);
30227
30759
  };
30228
30760
  const isHookCallee$1 = (analysis, node, hookName) => {
30229
30761
  if (!node) return false;
30230
30762
  if (isNodeOfType(node, "Identifier")) {
30231
30763
  if (node.name === hookName) return true;
30232
30764
  if (isReactNamedImportReference(getRef(analysis, node), hookName)) return true;
30233
- const parent = node.parent;
30234
- if (parent && isNodeOfType(parent, "MemberExpression") && isNodeOfType(parent.object, "Identifier") && parent.object.name === "React" && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
30765
+ const receiverRoot = findTransparentExpressionRoot(node);
30766
+ const parent = receiverRoot.parent;
30767
+ if (parent && isNodeOfType(parent, "MemberExpression") && parent.object === receiverRoot && isReactNamespaceReceiver(analysis, node) && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
30235
30768
  return false;
30236
30769
  }
30237
- if (isNodeOfType(node, "MemberExpression")) {
30238
- const receiver = stripParenExpression(node.object);
30239
- return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
30240
- }
30770
+ if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
30241
30771
  return false;
30242
30772
  };
30243
30773
  const isUseEffect = (node) => {
@@ -30345,7 +30875,27 @@ const isRefCurrent = (ref) => {
30345
30875
  if (!isNodeOfType(parent.property, "Identifier")) return false;
30346
30876
  return parent.property.name === "current";
30347
30877
  };
30348
- const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isStateSetter(analysis, innerRef));
30878
+ const resolveStateSetterReference = (analysis, ref) => {
30879
+ const visitedReferences = /* @__PURE__ */ new Set();
30880
+ let currentReference = ref;
30881
+ while (currentReference && !visitedReferences.has(currentReference)) {
30882
+ if (isStateSetter(analysis, currentReference)) return currentReference;
30883
+ visitedReferences.add(currentReference);
30884
+ const definitions = currentReference.resolved?.defs ?? [];
30885
+ if (definitions.length !== 1) return null;
30886
+ const definitionNode = definitions[0].node;
30887
+ if (!isNodeOfType(definitionNode, "VariableDeclarator")) return null;
30888
+ if (!isNodeOfType(definitionNode.id, "Identifier")) return null;
30889
+ const declaration = definitionNode.parent;
30890
+ if (!isNodeOfType(declaration, "VariableDeclaration") || declaration.kind !== "const") return null;
30891
+ if (!definitionNode.init) return null;
30892
+ const initializer = stripParenExpression(definitionNode.init);
30893
+ if (!isNodeOfType(initializer, "Identifier")) return null;
30894
+ currentReference = getRef(analysis, initializer);
30895
+ }
30896
+ return null;
30897
+ };
30898
+ const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => resolveStateSetterReference(analysis, innerRef) !== null);
30349
30899
  const isSyncStateSetterCall = (analysis, ref, effectFn) => isStateSetterCall(analysis, ref) && isSynchronous(ref.identifier, effectFn) && !resolvesToAsyncFunction(ref);
30350
30900
  const HANDLER_NAMED_METHOD_PATTERN = /^(on|handle)[A-Z]/;
30351
30901
  const SYNCHRONOUS_CALLBACK_ARGUMENT_INDEX_BY_METHOD = new Map([
@@ -30496,9 +31046,11 @@ const isPropCallbackInvocationRef = (analysis, ref, options = {}) => {
30496
31046
  };
30497
31047
  const isRefCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isRefCurrent(innerRef) || isRef(analysis, innerRef));
30498
31048
  const getUseStateDecl = (analysis, ref) => {
30499
- let node = getUpstreamRefs(analysis, ref).find((upRef) => isHookCallee$1(analysis, upRef.identifier, "useState"))?.identifier;
30500
- while (node && !isNodeOfType(node, "VariableDeclarator")) node = node.parent;
30501
- return node ?? null;
31049
+ const definition = getUpstreamRefs(analysis, ref).find((upstreamReference) => isState(analysis, upstreamReference) || isStateSetter(analysis, upstreamReference))?.resolved?.defs.find((candidateDefinition) => {
31050
+ const definitionNode = candidateDefinition.node;
31051
+ return isNodeOfType(definitionNode, "VariableDeclarator") && isNodeOfType(definitionNode.init, "CallExpression") && isHookCallee$1(analysis, definitionNode.init.callee, "useState");
31052
+ });
31053
+ return definition ? definition.node : null;
30502
31054
  };
30503
31055
  const isCleanupReturnArgument = (analysis, node) => {
30504
31056
  if (isFunctionLike$1(node)) return true;
@@ -30661,7 +31213,88 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
30661
31213
  if (HANDLER_BINDING_NAME_PATTERN.test(bindingName)) return true;
30662
31214
  return isSetterWiredToJsxHandler(componentFunction, bindingName);
30663
31215
  };
30664
- const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
31216
+ const isSynchronousFunction = (functionNode) => {
31217
+ const functionMetadata = functionNode;
31218
+ return functionMetadata.async !== true && functionMetadata.generator !== true;
31219
+ };
31220
+ const findBindingVariable = (analysis, bindingIdentifier) => {
31221
+ for (const scope of analysis.scopeManager.scopes) for (const variable of scope.variables) if (variable.identifiers.includes(bindingIdentifier)) return variable;
31222
+ return null;
31223
+ };
31224
+ const getImmutableFunctionVariable = (analysis, componentFunction, functionNode) => {
31225
+ if (!isSynchronousFunction(functionNode) || !isAstDescendant(functionNode, componentFunction)) return null;
31226
+ const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
31227
+ if (!bindingIdentifier) return null;
31228
+ const variable = findBindingVariable(analysis, bindingIdentifier);
31229
+ if (!variable || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && !reference.init)) return null;
31230
+ const definition = variable.defs[0];
31231
+ if (definition.type === "FunctionName") return definition.node === functionNode ? variable : null;
31232
+ if (definition.type !== "Variable") return null;
31233
+ const declarator = definition.node;
31234
+ if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return null;
31235
+ if (declarator.init === functionNode) return variable;
31236
+ if (isNodeOfType(declarator.init, "CallExpression") && declarator.init.arguments?.[0] === functionNode && isGenuineReactHookDeclarator(analysis, declarator, "useCallback")) return variable;
31237
+ return null;
31238
+ };
31239
+ const getJsxEventValueAttribute = (identifier) => {
31240
+ const expression = findTransparentExpressionRoot(identifier);
31241
+ const expressionContainer = expression.parent;
31242
+ if (!isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== expression) return null;
31243
+ const attribute = expressionContainer.parent;
31244
+ if (!isNodeOfType(attribute, "JSXAttribute")) return null;
31245
+ const attributeName = getJsxAttributeName(attribute.name);
31246
+ return attributeName && isEventHandlerName(attributeName) ? attribute : null;
31247
+ };
31248
+ const getInlineJsxEventCallbackAttribute = (callExpression) => {
31249
+ const callbackFunction = findEnclosingFunction$1(callExpression);
31250
+ if (!callbackFunction || !isSynchronousFunction(callbackFunction)) return null;
31251
+ return getJsxEventValueAttribute(callbackFunction);
31252
+ };
31253
+ const isReactHookDependencyReference = (identifier) => {
31254
+ const expression = findTransparentExpressionRoot(identifier);
31255
+ const dependencyArray = expression.parent;
31256
+ if (!isNodeOfType(dependencyArray, "ArrayExpression") || !(dependencyArray.elements ?? []).includes(expression)) return false;
31257
+ const hookCall = dependencyArray.parent;
31258
+ if (!isNodeOfType(hookCall, "CallExpression") || hookCall.arguments?.[1] !== dependencyArray) return false;
31259
+ const callee = hookCall.callee;
31260
+ if (isNodeOfType(callee, "Identifier")) return /^use[A-Z0-9]/.test(callee.name);
31261
+ return Boolean(isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && /^use[A-Z0-9]/.test(callee.property.name));
31262
+ };
31263
+ const hasReachableJsxEventCallPath = (analysis, context, componentFunction, functionVariable, visitedVariables) => {
31264
+ if (visitedVariables.has(functionVariable)) return false;
31265
+ const nextVisitedVariables = new Set(visitedVariables).add(functionVariable);
31266
+ const callExpressions = [];
31267
+ let hasDirectJsxEventReference = false;
31268
+ for (const reference of functionVariable.references) {
31269
+ if (reference.init) continue;
31270
+ const identifier = reference.identifier;
31271
+ if (reference.isWrite()) return false;
31272
+ const jsxEventValueAttribute = getJsxEventValueAttribute(identifier);
31273
+ if (jsxEventValueAttribute) {
31274
+ if (isNodeReachableWithinFunction(jsxEventValueAttribute, context)) hasDirectJsxEventReference = true;
31275
+ continue;
31276
+ }
31277
+ if (isReactHookDependencyReference(identifier)) continue;
31278
+ const callExpression = getCallExpr(reference);
31279
+ if (!callExpression) return false;
31280
+ const jsxEventCallbackAttribute = getInlineJsxEventCallbackAttribute(callExpression);
31281
+ if (jsxEventCallbackAttribute) {
31282
+ if (isNodeReachableWithinFunction(callExpression, context) && isNodeReachableWithinFunction(jsxEventCallbackAttribute, context)) hasDirectJsxEventReference = true;
31283
+ continue;
31284
+ }
31285
+ callExpressions.push(callExpression);
31286
+ }
31287
+ if (hasDirectJsxEventReference) return true;
31288
+ for (const callExpression of callExpressions) {
31289
+ if (!isNodeReachableWithinFunction(callExpression, context)) continue;
31290
+ const callerFunction = findEnclosingFunction$1(callExpression);
31291
+ if (!callerFunction || callerFunction === componentFunction) continue;
31292
+ const callerVariable = getImmutableFunctionVariable(analysis, componentFunction, callerFunction);
31293
+ if (callerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, callerVariable, nextVisitedVariables)) return true;
31294
+ }
31295
+ return false;
31296
+ };
31297
+ const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, includeDeferredWriters = false) => {
30665
31298
  if (!setterRef.resolved) return false;
30666
31299
  const componentFunction = findEnclosingFunction$1(effectNode);
30667
31300
  if (!componentFunction) return false;
@@ -30670,6 +31303,11 @@ const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters
30670
31303
  const identifier = reference.identifier;
30671
31304
  if (isAstDescendant(identifier, effectNode)) continue;
30672
31305
  if (isIndependentWriterIdentifier(componentFunction, identifier, includeDeferredWriters)) return true;
31306
+ if (!isNodeReachableWithinFunction(identifier, context)) continue;
31307
+ const writerFunction = findEnclosingFunction$1(identifier);
31308
+ if (!writerFunction || writerFunction === componentFunction) continue;
31309
+ const writerVariable = getImmutableFunctionVariable(analysis, componentFunction, writerFunction);
31310
+ if (writerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, writerVariable, /* @__PURE__ */ new Set())) return true;
30673
31311
  }
30674
31312
  return false;
30675
31313
  };
@@ -31559,7 +32197,7 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
31559
32197
  }
31560
32198
  return false;
31561
32199
  };
31562
- const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
32200
+ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFilename) => {
31563
32201
  const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
31564
32202
  if (frames.length === 0) return [];
31565
32203
  const effectHasCleanup = hasCleanup(analysis, effectNode);
@@ -31589,7 +32227,7 @@ const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) =>
31589
32227
  for (const returnedExpression of returnedExpressions) mergeEvidence(valueEvidence, collectValueEvidence(analysis, returnedExpression, updaterFrame, remainingValueCallFrames));
31590
32228
  } else valueEvidence = collectValueEvidence(analysis, writtenValue, frame, remainingValueCallFrames);
31591
32229
  const sourceReferences = [...valueEvidence.sourceReferences].filter((sourceReference) => getUseStateDecl(analysis, sourceReference) !== stateDeclarator);
31592
- const hasIndependentWriter = hasUserInputSetterWriter(setterReference, effectNode, true);
32230
+ const hasIndependentWriter = hasUserInputSetterWriter(analysis, context, setterReference, effectNode, true);
31593
32231
  const doesMatchStateInitializer = matchesStateInitializer(analysis, callExpression, stateDeclarator);
31594
32232
  if (effectHasCleanup && (frame.isDeferred || valueEvidence.hasUnknownSource || valueEvidence.hasDeferredIntroducedValue || valueEvidence.readsExternalValue)) cleanupManagedStateDeclarators.add(stateDeclarator);
31595
32233
  const isRenderKnownCopy = sourceReferences.length > 0 && !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue && !hasIndependentWriter;
@@ -31624,13 +32262,18 @@ const noAdjustStateOnPropChange = defineRule({
31624
32262
  tags: ["test-noise"],
31625
32263
  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",
31626
32264
  create: (context) => ({ CallExpression(node) {
31627
- if (!isUseEffect(node)) return;
32265
+ if (!isReactApiCall(node, "useEffect", context.scopes, {
32266
+ allowGlobalReactNamespace: true,
32267
+ allowUnboundBareCalls: true,
32268
+ resolveConditionalAliases: true,
32269
+ resolveNamedAliases: true
32270
+ })) return;
31628
32271
  const analysis = getProgramAnalysis(node);
31629
32272
  if (!analysis) return;
31630
32273
  const dependencyReferences = getEffectDepsRefs(analysis, node);
31631
32274
  if (!dependencyReferences) return;
31632
32275
  if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
31633
- for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
32276
+ for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
31634
32277
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
31635
32278
  context.report({
31636
32279
  node: fact.callExpression,
@@ -34351,7 +34994,7 @@ const noChainStateUpdates = defineRule({
34351
34994
  if (!callExpr) continue;
34352
34995
  if (!isReachableFromStateTrigger(callExpr)) continue;
34353
34996
  if (!readsPostMountValueThroughLocals(callExpr, effectFn, { ignoreBareRefCurrent: true })) continue;
34354
- const declarator = getUseStateDeclarator(ref);
34997
+ const declarator = getUseStateDeclarator(resolveStateSetterReference(analysis, ref) ?? ref);
34355
34998
  if (declarator) domSyncedStateDeclarators.add(declarator);
34356
34999
  }
34357
35000
  for (const ref of effectFnRefs) {
@@ -34360,7 +35003,7 @@ const noChainStateUpdates = defineRule({
34360
35003
  if (!callExpr) continue;
34361
35004
  if (!isReachableFromStateTrigger(callExpr)) continue;
34362
35005
  if (getArgsUpstreamRefs(analysis, ref).some((argRef) => isState(analysis, argRef))) continue;
34363
- const setterDeclarator = getUseStateDeclarator(ref);
35006
+ const setterDeclarator = getUseStateDeclarator(resolveStateSetterReference(analysis, ref) ?? ref);
34364
35007
  if (setterDeclarator && domSyncedStateDeclarators.has(setterDeclarator)) continue;
34365
35008
  const isSelfTargeting = setterDeclarator !== null && stateDepDeclarators.has(setterDeclarator);
34366
35009
  const setterArguments = isNodeOfType(callExpr, "CallExpression") ? callExpr.arguments ?? [] : [];
@@ -36201,10 +36844,15 @@ const noDerivedState = defineRule({
36201
36844
  for (const fact of collectRenderStateWriteFacts(analysis, componentBody, context.filename)) reportStateWrite(fact.callExpression, fact.stateDeclarator);
36202
36845
  } }).visitors,
36203
36846
  CallExpression(node) {
36204
- if (!isUseEffect(node)) return;
36847
+ if (!isReactApiCall(node, "useEffect", context.scopes, {
36848
+ allowGlobalReactNamespace: true,
36849
+ allowUnboundBareCalls: true,
36850
+ resolveConditionalAliases: true,
36851
+ resolveNamedAliases: true
36852
+ })) return;
36205
36853
  const analysis = getProgramAnalysis(node);
36206
36854
  if (!analysis) return;
36207
- for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
36855
+ for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
36208
36856
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
36209
36857
  reportStateWrite(fact.callExpression, fact.stateDeclarator);
36210
36858
  }
@@ -36221,10 +36869,15 @@ const noDerivedStateEffect = defineRule({
36221
36869
  tags: ["test-noise"],
36222
36870
  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",
36223
36871
  create: (context) => ({ CallExpression(node) {
36224
- if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
36872
+ if (!isReactApiCall(node, EFFECT_HOOK_NAMES$1, context.scopes, {
36873
+ allowGlobalReactNamespace: true,
36874
+ allowUnboundBareCalls: true,
36875
+ resolveConditionalAliases: true,
36876
+ resolveNamedAliases: true
36877
+ })) return;
36225
36878
  const analysis = getProgramAnalysis(node);
36226
36879
  if (!analysis) return;
36227
- if (!collectEffectStateWriteFacts(analysis, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
36880
+ if (!collectEffectStateWriteFacts(analysis, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
36228
36881
  context.report({
36229
36882
  node,
36230
36883
  message: "You pay an extra render for state you can derive from other values."
@@ -37173,7 +37826,7 @@ const noDirectMutationState = defineRule({
37173
37826
  const isSetterIdentifier = (name) => SETTER_PATTERN.test(name);
37174
37827
  //#endregion
37175
37828
  //#region src/plugin/rules/state-and-effects/utils/collect-use-state-bindings.ts
37176
- const collectUseStateBindings = (componentBody) => {
37829
+ const collectUseStateBindings = (componentBody, scopes) => {
37177
37830
  const bindings = [];
37178
37831
  if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
37179
37832
  for (const statement of componentBody.body ?? []) {
@@ -37186,7 +37839,12 @@ const collectUseStateBindings = (componentBody) => {
37186
37839
  const setterElement = elements[1];
37187
37840
  if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
37188
37841
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
37189
- if (!isHookCall$2(declarator.init, "useState")) continue;
37842
+ if (!(scopes ? isReactApiCall(declarator.init, "useState", scopes, {
37843
+ allowGlobalReactNamespace: true,
37844
+ allowUnboundBareCalls: true,
37845
+ resolveConditionalAliases: true,
37846
+ resolveNamedAliases: true
37847
+ }) : isHookCall$2(declarator.init, "useState"))) continue;
37190
37848
  bindings.push({
37191
37849
  valueName: valueElement.name,
37192
37850
  setterName: setterElement.name,
@@ -37913,25 +38571,36 @@ const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSub
37913
38571
  };
37914
38572
  //#endregion
37915
38573
  //#region src/plugin/rules/state-and-effects/no-effect-chain.ts
37916
- const findTopLevelEffectCalls = (componentBody) => {
38574
+ const findTopLevelEffectCalls = (componentBody, scopes) => {
37917
38575
  const effectCalls = [];
37918
38576
  if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
37919
38577
  for (const statement of componentBody.body ?? []) {
37920
38578
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
37921
38579
  const expression = unwrapDiscardedExpression(statement);
37922
38580
  if (!isNodeOfType(expression, "CallExpression")) continue;
37923
- if (!isHookCall$2(expression, EFFECT_HOOK_NAMES$1)) continue;
38581
+ if (!isReactApiCall(expression, EFFECT_HOOK_NAMES$1, scopes, {
38582
+ allowGlobalReactNamespace: true,
38583
+ allowUnboundBareCalls: true,
38584
+ resolveConditionalAliases: true,
38585
+ resolveNamedAliases: true
38586
+ })) continue;
37924
38587
  effectCalls.push(expression);
37925
38588
  }
37926
38589
  return effectCalls;
37927
38590
  };
37928
- const collectDepIdentifierNames = (effectNode) => {
37929
- const depNames = /* @__PURE__ */ new Set();
37930
- if (!isNodeOfType(effectNode, "CallExpression")) return depNames;
38591
+ const collectDependencyStateSymbolIds = (effectNode, stateSymbolIds, scopes) => {
38592
+ const dependencyStateSymbolIds = /* @__PURE__ */ new Set();
38593
+ if (!isNodeOfType(effectNode, "CallExpression")) return dependencyStateSymbolIds;
37931
38594
  const depsNode = effectNode.arguments?.[1];
37932
- if (!isNodeOfType(depsNode, "ArrayExpression")) return depNames;
37933
- for (const element of depsNode.elements ?? []) if (isNodeOfType(element, "Identifier")) depNames.add(element.name);
37934
- return depNames;
38595
+ if (!isNodeOfType(depsNode, "ArrayExpression")) return dependencyStateSymbolIds;
38596
+ for (const element of depsNode.elements ?? []) {
38597
+ if (!element || isNodeOfType(element, "SpreadElement")) continue;
38598
+ const rootIdentifier = getRootIdentifier$1(element);
38599
+ if (!isNodeOfType(rootIdentifier, "Identifier")) continue;
38600
+ const symbol = resolveConstIdentifierAlias(rootIdentifier, scopes, true);
38601
+ if (symbol && stateSymbolIds.has(symbol.id)) dependencyStateSymbolIds.add(symbol.id);
38602
+ }
38603
+ return dependencyStateSymbolIds;
37935
38604
  };
37936
38605
  const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
37937
38606
  const analysisFunctions = new Set([effectCallback]);
@@ -38037,12 +38706,13 @@ const readStaticSetterValue = (setterCall, scopes) => {
38037
38706
  if (updater) return readStaticUpdaterReturnValue(updater, scopes);
38038
38707
  return readStaticEffectValue(argument, scopes, null, null);
38039
38708
  };
38040
- const collectStateWritesInEffect = (analysisFunctions, setterToStateName, scopes) => {
38709
+ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName, scopes) => {
38041
38710
  const stateWrites = /* @__PURE__ */ new Map();
38042
38711
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
38043
38712
  if (!isNodeOfType(child, "CallExpression")) return;
38044
38713
  if (!isNodeOfType(child.callee, "Identifier")) return;
38045
- const stateName = setterToStateName.get(child.callee.name);
38714
+ const setterSymbol = resolveConstIdentifierAlias(child.callee, scopes, true);
38715
+ const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
38046
38716
  if (!stateName) return;
38047
38717
  const writeInfo = stateWrites.get(stateName) ?? {
38048
38718
  values: /* @__PURE__ */ new Set(),
@@ -38128,11 +38798,12 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
38128
38798
  "keys",
38129
38799
  "values"
38130
38800
  ]);
38131
- const isFunctionShapedReturn = (returnedValue, setterToStateName, isExplicitReturnStatement) => {
38801
+ const isFunctionShapedReturn = (returnedValue, setterToStateName, setterSymbolIdToStateName, scopes, isExplicitReturnStatement) => {
38132
38802
  if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
38133
38803
  if (isNodeOfType(returnedValue, "CallExpression")) {
38134
38804
  if (isNodeOfType(returnedValue.callee, "Identifier")) {
38135
- if (setterToStateName.has(returnedValue.callee.name)) return false;
38805
+ const setterSymbol = resolveConstIdentifierAlias(returnedValue.callee, scopes, true);
38806
+ if (setterToStateName.has(returnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
38136
38807
  if (isSetterIdentifier(returnedValue.callee.name)) return true;
38137
38808
  }
38138
38809
  return isCleanupReturn(returnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
@@ -38299,11 +38970,11 @@ const isExternalSyncNode = (node) => {
38299
38970
  const receiverRootName = getRootIdentifierName(node.callee.object);
38300
38971
  return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
38301
38972
  };
38302
- const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, scopes, allowCommittedDomSync) => {
38973
+ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, scopes, allowCommittedDomSync) => {
38303
38974
  if (!isFunctionLike$1(effectCallback)) return false;
38304
38975
  if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
38305
- if (isFunctionShapedReturn(effectCallback.body, setterToStateName, false)) return true;
38306
- } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, true)) return true;
38976
+ if (isFunctionShapedReturn(effectCallback.body, setterToStateName, setterSymbolIdToStateName, scopes, false)) return true;
38977
+ } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, setterSymbolIdToStateName, scopes, true)) return true;
38307
38978
  let didFindExternalCall = false;
38308
38979
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
38309
38980
  if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
@@ -38319,10 +38990,11 @@ const noEffectChain = defineRule({
38319
38990
  create: (context) => {
38320
38991
  const checkComponent = (componentBody) => {
38321
38992
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
38322
- const useStateBindings = collectUseStateBindings(componentBody);
38993
+ const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
38323
38994
  if (useStateBindings.length === 0) return;
38324
38995
  const setterToStateName = /* @__PURE__ */ new Map();
38325
38996
  const stateSymbolIds = /* @__PURE__ */ new Map();
38997
+ const setterSymbolIdToStateName = /* @__PURE__ */ new Map();
38326
38998
  for (const binding of useStateBindings) {
38327
38999
  setterToStateName.set(binding.setterName, binding.valueName);
38328
39000
  if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
@@ -38331,21 +39003,27 @@ const noEffectChain = defineRule({
38331
39003
  const stateSymbol = context.scopes.symbolFor(stateIdentifier);
38332
39004
  if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
38333
39005
  }
39006
+ const setterIdentifier = binding.declarator.id.elements[1];
39007
+ if (isNodeOfType(setterIdentifier, "Identifier")) {
39008
+ const setterSymbol = context.scopes.symbolFor(setterIdentifier);
39009
+ if (setterSymbol) setterSymbolIdToStateName.set(setterSymbol.id, binding.valueName);
39010
+ }
38334
39011
  }
38335
39012
  const storageSetterNames = collectStorageHookSetterNames(componentBody);
39013
+ const stateSymbolIdSet = new Set(stateSymbolIds.values());
38336
39014
  const effectInfos = [];
38337
- for (const effectCall of findTopLevelEffectCalls(componentBody)) {
39015
+ for (const effectCall of findTopLevelEffectCalls(componentBody, context.scopes)) {
38338
39016
  const callback = getEffectCallback(effectCall, context.scopes);
38339
39017
  if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
38340
39018
  const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
38341
- const stateWrites = collectStateWritesInEffect(analysisFunctions, setterToStateName, context.scopes);
39019
+ const stateWrites = collectStateWritesInEffect(analysisFunctions, setterSymbolIdToStateName, context.scopes);
38342
39020
  const writtenStateNames = new Set(stateWrites.keys());
38343
39021
  effectInfos.push({
38344
39022
  node: effectCall,
38345
- depNames: collectDepIdentifierNames(effectCall),
39023
+ dependencyStateSymbolIds: collectDependencyStateSymbolIds(effectCall, stateSymbolIdSet, context.scopes),
38346
39024
  stateWrites,
38347
39025
  analysisFunctions,
38348
- isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
39026
+ isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
38349
39027
  });
38350
39028
  }
38351
39029
  if (effectInfos.length < 2) return;
@@ -38356,10 +39034,11 @@ const noEffectChain = defineRule({
38356
39034
  for (const readerEffect of effectInfos) {
38357
39035
  if (readerEffect === writerEffect) continue;
38358
39036
  if (readerEffect.isExternalSync) continue;
38359
- if (readerEffect.depNames.size === 0) continue;
39037
+ if (readerEffect.dependencyStateSymbolIds.size === 0) continue;
38360
39038
  let chainedStateName = null;
38361
39039
  for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
38362
- if (!readerEffect.depNames.has(writtenName)) continue;
39040
+ const writtenStateSymbolId = stateSymbolIds.get(writtenName);
39041
+ if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
38363
39042
  if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
38364
39043
  chainedStateName = writtenName;
38365
39044
  break;
@@ -40175,6 +40854,17 @@ const DOM_MEASUREMENT_NAMES = new Set([
40175
40854
  "scrollHeight"
40176
40855
  ]);
40177
40856
  const MEASUREMENT_HELPER_CALLEE_PATTERN = /^(?:get|measure|read)\w*(?:Width|Height|Rect|Rects|Size|Bounds|Position)$/;
40857
+ const IMPERATIVE_DOM_MUTATION_NAMES = new Set([
40858
+ "blur",
40859
+ "focus",
40860
+ "restoreSelection",
40861
+ "scroll",
40862
+ "scrollBy",
40863
+ "scrollIntoView",
40864
+ "scrollTo",
40865
+ "setRangeText",
40866
+ "setSelectionRange"
40867
+ ]);
40178
40868
  const subtreeReadsDomMeasurement = (root) => {
40179
40869
  if (!root) return false;
40180
40870
  let found = false;
@@ -40193,29 +40883,66 @@ const subtreeReadsDomMeasurement = (root) => {
40193
40883
  });
40194
40884
  return found;
40195
40885
  };
40196
- const collectMeasuringFunctionNames = (program) => {
40886
+ const collectFunctionNamesMatchingBody = (program, matchesBody) => {
40197
40887
  const names = /* @__PURE__ */ new Set();
40198
40888
  walkAst(program, (child) => {
40199
40889
  if (isNodeOfType(child, "FunctionDeclaration")) {
40200
- if (child.id && isNodeOfType(child.id, "Identifier") && subtreeReadsDomMeasurement(child.body)) names.add(child.id.name);
40890
+ if (child.id && isNodeOfType(child.id, "Identifier") && matchesBody(child.body)) names.add(child.id.name);
40201
40891
  return;
40202
40892
  }
40203
40893
  if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier")) return;
40204
40894
  let functionValue = child.init;
40205
40895
  if (functionValue && isNodeOfType(functionValue, "CallExpression") && isNodeOfType(functionValue.callee, "Identifier") && /^use[A-Z]/.test(functionValue.callee.name)) functionValue = functionValue.arguments?.[0];
40206
- if (functionValue && isFunctionLike$1(functionValue) && subtreeReadsDomMeasurement(functionValue.body)) names.add(child.id.name);
40896
+ if (functionValue && isFunctionLike$1(functionValue) && matchesBody(functionValue.body)) names.add(child.id.name);
40207
40897
  });
40208
40898
  return names;
40209
40899
  };
40210
- const callsAnyName = (root, names) => {
40211
- if (!root || names.size === 0) return false;
40900
+ const collectMeasuringFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeReadsDomMeasurement);
40901
+ const subtreeMutatesDomImperatively = (root) => {
40902
+ if (!root || isFunctionLike$1(root)) return false;
40903
+ let found = false;
40904
+ walkAst(root, (child) => {
40905
+ if (found) return false;
40906
+ if (child !== root && isFunctionLike$1(child)) return false;
40907
+ if (!isNodeOfType(child, "CallExpression")) return;
40908
+ const callee = stripParenExpression(child.callee);
40909
+ const propertyName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
40910
+ if (propertyName !== null && IMPERATIVE_DOM_MUTATION_NAMES.has(propertyName)) {
40911
+ found = true;
40912
+ return false;
40913
+ }
40914
+ });
40915
+ return found;
40916
+ };
40917
+ const collectImperativeDomFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeMutatesDomImperatively);
40918
+ const callsAnyName = (root, names, shouldSkipNestedFunctions = false) => {
40919
+ if (!root || names.size === 0 || shouldSkipNestedFunctions && isFunctionLike$1(root)) return false;
40212
40920
  let found = false;
40213
40921
  walkAst(root, (child) => {
40214
40922
  if (found) return false;
40923
+ if (shouldSkipNestedFunctions && child !== root && isFunctionLike$1(child)) return false;
40215
40924
  if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && names.has(child.callee.name)) found = true;
40216
40925
  });
40217
40926
  return found;
40218
40927
  };
40928
+ const isFollowedByImperativeDomMutation = (call, imperativeDomFunctionNames) => {
40929
+ let statement = call;
40930
+ let parent = statement.parent;
40931
+ while (parent) {
40932
+ const statements = isNodeOfType(parent, "BlockStatement") || isNodeOfType(parent, "Program") || isNodeOfType(parent, "StaticBlock") ? parent.body : isNodeOfType(parent, "SwitchCase") ? parent.consequent : null;
40933
+ if (statements) {
40934
+ const statementIndex = statements.findIndex((siblingStatement) => siblingStatement === statement);
40935
+ if (statementIndex >= 0) {
40936
+ const nextStatement = statements[statementIndex + 1];
40937
+ return subtreeMutatesDomImperatively(nextStatement) || callsAnyName(nextStatement, imperativeDomFunctionNames, true);
40938
+ }
40939
+ }
40940
+ if (isFunctionLike$1(parent) || parent.type.endsWith("Statement") && !isNodeOfType(parent, "ExpressionStatement")) return false;
40941
+ statement = parent;
40942
+ parent = parent.parent;
40943
+ }
40944
+ return false;
40945
+ };
40219
40946
  const isInsideStartViewTransition = (node) => {
40220
40947
  let cursor = node.parent;
40221
40948
  while (cursor) {
@@ -40256,11 +40983,12 @@ const importsImperativeDomLibrary = (program) => {
40256
40983
  };
40257
40984
  const hasExemptFlushSyncCall = (program, localName) => {
40258
40985
  const measuringFunctionNames = collectMeasuringFunctionNames(program);
40986
+ const imperativeDomFunctionNames = collectImperativeDomFunctionNames(program);
40259
40987
  let exempt = false;
40260
40988
  walkAst(program, (child) => {
40261
40989
  if (exempt) return false;
40262
40990
  if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "Identifier") || child.callee.name !== localName) return;
40263
- if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames)) {
40991
+ if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames) || isFollowedByImperativeDomMutation(child, imperativeDomFunctionNames)) {
40264
40992
  exempt = true;
40265
40993
  return false;
40266
40994
  }
@@ -40825,41 +41553,223 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
40825
41553
  if (leftResult === false && rightResult === false) return false;
40826
41554
  return null;
40827
41555
  };
40828
- const readHydrationConditionResult = (expression, context, runtime) => {
41556
+ const readHydrationConditionResult = (expression, context, runtime, state) => {
40829
41557
  const unwrappedExpression = stripParenExpression(expression);
40830
41558
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
40831
41559
  if (predicateMatch) return predicateMatch[`${runtime}Result`];
40832
41560
  const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
40833
41561
  if (staticResult !== null) return staticResult;
41562
+ const expressionSymbol = isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression) : null;
41563
+ const parameterValue = expressionSymbol ? state.parameterValuesBySymbolId.get(expressionSymbol.id) : null;
41564
+ if (expressionSymbol && parameterValue && !state.visitedSymbolIds.has(expressionSymbol.id)) {
41565
+ state.visitedSymbolIds.add(expressionSymbol.id);
41566
+ const result = readHydrationConditionResult(parameterValue, context, runtime, state);
41567
+ state.visitedSymbolIds.delete(expressionSymbol.id);
41568
+ return result;
41569
+ }
41570
+ if (expressionSymbol && expressionSymbol.kind === "const" && expressionSymbol.initializer && expressionSymbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(expressionSymbol.id)) {
41571
+ state.visitedSymbolIds.add(expressionSymbol.id);
41572
+ const result = readHydrationConditionResult(expressionSymbol.initializer, context, runtime, state);
41573
+ state.visitedSymbolIds.delete(expressionSymbol.id);
41574
+ return result;
41575
+ }
41576
+ if (isNodeOfType(unwrappedExpression, "CallExpression")) {
41577
+ const callArguments = unwrappedExpression.arguments ?? [];
41578
+ if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
41579
+ allowGlobalReactNamespace: true,
41580
+ resolveNamedAliases: true
41581
+ })) {
41582
+ const callbackArgument = callArguments[0];
41583
+ if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
41584
+ const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
41585
+ return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? readHydrationFunctionResult(callbackFunction, context, runtime, state) : null;
41586
+ }
41587
+ const callee = stripParenExpression(unwrappedExpression.callee);
41588
+ 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);
41589
+ const helperFunction = resolveExactLocalFunction(callee, context.scopes);
41590
+ 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;
41591
+ const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
41592
+ for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
41593
+ const parameter = helperFunction.params[parameterIndex];
41594
+ const argument = callArguments[parameterIndex];
41595
+ if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
41596
+ const parameterSymbol = context.scopes.symbolFor(parameter);
41597
+ if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
41598
+ }
41599
+ return readHydrationFunctionResult(helperFunction, context, runtime, {
41600
+ ...state,
41601
+ parameterValuesBySymbolId
41602
+ });
41603
+ }
40834
41604
  if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
40835
- const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
41605
+ const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
40836
41606
  return argumentResult === null ? null : !argumentResult;
40837
41607
  }
40838
41608
  if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
40839
- return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
41609
+ return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime, state), readHydrationConditionResult(unwrappedExpression.right, context, runtime, state));
41610
+ };
41611
+ const readHydrationStatementResult = (statement, context, runtime, state) => {
41612
+ if (isNodeOfType(statement, "ReturnStatement")) return {
41613
+ didReturn: true,
41614
+ value: statement.argument ? readHydrationConditionResult(statement.argument, context, runtime, state) : null
41615
+ };
41616
+ if (isNodeOfType(statement, "BlockStatement")) {
41617
+ for (const childStatement of statement.body) {
41618
+ const result = readHydrationStatementResult(childStatement, context, runtime, state);
41619
+ if (result.didReturn) return result;
41620
+ if (statementAlwaysExits(childStatement)) break;
41621
+ }
41622
+ return {
41623
+ didReturn: false,
41624
+ value: null
41625
+ };
41626
+ }
41627
+ if (!isNodeOfType(statement, "IfStatement")) return {
41628
+ didReturn: false,
41629
+ value: null
41630
+ };
41631
+ const conditionResult = readHydrationConditionResult(statement.test, context, runtime, state);
41632
+ if (conditionResult !== null) {
41633
+ const selectedBranch = conditionResult ? statement.consequent : statement.alternate;
41634
+ return selectedBranch ? readHydrationStatementResult(selectedBranch, context, runtime, state) : {
41635
+ didReturn: false,
41636
+ value: null
41637
+ };
41638
+ }
41639
+ const consequentResult = readHydrationStatementResult(statement.consequent, context, runtime, state);
41640
+ const alternateResult = statement.alternate ? readHydrationStatementResult(statement.alternate, context, runtime, state) : {
41641
+ didReturn: false,
41642
+ value: null
41643
+ };
41644
+ return consequentResult.didReturn && alternateResult.didReturn && consequentResult.value !== null && consequentResult.value === alternateResult.value ? consequentResult : {
41645
+ didReturn: consequentResult.didReturn || alternateResult.didReturn,
41646
+ value: null
41647
+ };
41648
+ };
41649
+ const readHydrationFunctionResult = (functionNode, context, runtime, state) => {
41650
+ if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
41651
+ state.visitedFunctionNodes.add(functionNode);
41652
+ const result = isNodeOfType(functionNode.body, "BlockStatement") ? readHydrationStatementResult(functionNode.body, context, runtime, state).value : readHydrationConditionResult(functionNode.body, context, runtime, state);
41653
+ state.visitedFunctionNodes.delete(functionNode);
41654
+ return result;
41655
+ };
41656
+ const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, scopes) => {
41657
+ const left = stripParenExpression(leftExpression);
41658
+ const right = stripParenExpression(rightExpression);
41659
+ if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
41660
+ const leftSymbol = scopes.symbolFor(left);
41661
+ const rightSymbol = scopes.symbolFor(right);
41662
+ return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
41663
+ }
41664
+ if (isNodeOfType(left, "MemberExpression") && isNodeOfType(right, "MemberExpression")) return doEquivalentExpressionBindingsMatch(left.object, right.object, scopes) && (!left.computed || doEquivalentExpressionBindingsMatch(left.property, right.property, scopes));
41665
+ if (isNodeOfType(left, "CallExpression") && isNodeOfType(right, "CallExpression")) {
41666
+ const rightArguments = right.arguments ?? [];
41667
+ return doEquivalentExpressionBindingsMatch(left.callee, right.callee, scopes) && (left.arguments ?? []).every((argument, index) => {
41668
+ const rightArgument = rightArguments[index];
41669
+ return Boolean(rightArgument && doEquivalentExpressionBindingsMatch(argument, rightArgument, scopes));
41670
+ });
41671
+ }
41672
+ return true;
41673
+ };
41674
+ const areHelperReturnValuesEquivalent = (leftValue, rightValue, context) => {
41675
+ if (areExpressionsStructurallyEqual(leftValue, rightValue)) return doEquivalentExpressionBindingsMatch(leftValue, rightValue, context.scopes);
41676
+ const leftBoolean = readInitialStateBoolean(leftValue, context.scopes);
41677
+ const rightBoolean = readInitialStateBoolean(rightValue, context.scopes);
41678
+ return leftBoolean !== null && rightBoolean !== null && leftBoolean === rightBoolean;
41679
+ };
41680
+ const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
41681
+ const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
41682
+ return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
40840
41683
  };
40841
- const matchHydrationCondition = (expression, context) => {
41684
+ const matchHydrationConditionInternal = (expression, context, state) => {
40842
41685
  const unwrappedExpression = stripParenExpression(expression);
40843
41686
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
40844
41687
  if (predicateMatch) return {
40845
41688
  predicateMatch,
40846
41689
  predicateNode: unwrappedExpression
40847
41690
  };
40848
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
40849
- if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
40850
- const leftMatch = matchHydrationCondition(unwrappedExpression.left, context);
40851
- const rightMatch = matchHydrationCondition(unwrappedExpression.right, context);
40852
- if (leftMatch && rightMatch) {
40853
- const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client");
40854
- const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server");
40855
- return clientResult !== null && serverResult !== null && clientResult !== serverResult ? leftMatch : null;
41691
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
41692
+ const symbol = context.scopes.symbolFor(unwrappedExpression);
41693
+ const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
41694
+ if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
41695
+ state.visitedSymbolIds.add(symbol.id);
41696
+ const match = matchHydrationConditionInternal(parameterValue, context, state);
41697
+ state.visitedSymbolIds.delete(symbol.id);
41698
+ return match;
41699
+ }
41700
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
41701
+ state.visitedSymbolIds.add(symbol.id);
41702
+ const match = matchHydrationConditionInternal(symbol.initializer, context, state);
41703
+ state.visitedSymbolIds.delete(symbol.id);
41704
+ return match;
40856
41705
  }
41706
+ if (isNodeOfType(unwrappedExpression, "CallExpression")) {
41707
+ const callArguments = unwrappedExpression.arguments ?? [];
41708
+ if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
41709
+ allowGlobalReactNamespace: true,
41710
+ resolveNamedAliases: true
41711
+ })) {
41712
+ const callbackArgument = callArguments[0];
41713
+ if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
41714
+ const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
41715
+ return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionResult(callbackFunction, context, state) : null;
41716
+ }
41717
+ const callee = stripParenExpression(unwrappedExpression.callee);
41718
+ if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return matchHydrationConditionInternal(callArguments[0], context, state);
41719
+ const helperFunction = resolveExactLocalFunction(callee, context.scopes);
41720
+ 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;
41721
+ const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
41722
+ for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
41723
+ const parameter = helperFunction.params[parameterIndex];
41724
+ const argument = callArguments[parameterIndex];
41725
+ if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
41726
+ const parameterSymbol = context.scopes.symbolFor(parameter);
41727
+ if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
41728
+ }
41729
+ return matchHydrationFunctionResult(helperFunction, context, {
41730
+ ...state,
41731
+ parameterValuesBySymbolId
41732
+ });
41733
+ }
41734
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
41735
+ if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
41736
+ const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
41737
+ const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
40857
41738
  const nestedMatch = leftMatch ?? rightMatch;
40858
41739
  if (!nestedMatch) return null;
40859
- const otherResult = readInitialStateBoolean(leftMatch ? unwrappedExpression.right : unwrappedExpression.left, context.scopes);
40860
- if (unwrappedExpression.operator === "&&" && otherResult === false || unwrappedExpression.operator === "||" && otherResult === true) return null;
40861
- return nestedMatch;
41740
+ const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client", state);
41741
+ const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server", state);
41742
+ return clientResult !== null && serverResult !== null && clientResult === serverResult ? null : nestedMatch;
40862
41743
  };
41744
+ const matchHydrationReturningStatement = (statement, context, state) => {
41745
+ if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? matchHydrationConditionInternal(statement.argument, context, state) : null;
41746
+ if (isNodeOfType(statement, "IfStatement")) {
41747
+ const conditionMatch = matchHydrationConditionInternal(statement.test, context, state);
41748
+ const consequentValues = getReturnedValues(statement.consequent);
41749
+ const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
41750
+ if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
41751
+ return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
41752
+ }
41753
+ if (!isNodeOfType(statement, "BlockStatement")) return null;
41754
+ for (const childStatement of statement.body) {
41755
+ const match = matchHydrationReturningStatement(childStatement, context, state);
41756
+ if (match) return match;
41757
+ if (statementAlwaysExits(childStatement)) break;
41758
+ }
41759
+ return null;
41760
+ };
41761
+ const matchHydrationFunctionResult = (functionNode, context, state) => {
41762
+ if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
41763
+ state.visitedFunctionNodes.add(functionNode);
41764
+ const match = isNodeOfType(functionNode.body, "BlockStatement") ? matchHydrationReturningStatement(functionNode.body, context, state) : matchHydrationConditionInternal(functionNode.body, context, state);
41765
+ state.visitedFunctionNodes.delete(functionNode);
41766
+ return match;
41767
+ };
41768
+ const matchHydrationCondition = (expression, context) => matchHydrationConditionInternal(expression, context, {
41769
+ parameterValuesBySymbolId: /* @__PURE__ */ new Map(),
41770
+ visitedFunctionNodes: /* @__PURE__ */ new Set(),
41771
+ visitedSymbolIds: /* @__PURE__ */ new Set()
41772
+ });
40863
41773
  const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
40864
41774
  const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
40865
41775
  if (!leftNode || !rightNode) return leftNode === rightNode;
@@ -41002,17 +41912,17 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
41002
41912
  const { predicateMatch, predicateNode } = conditionMatch;
41003
41913
  if (reportedNodes.has(predicateNode)) return;
41004
41914
  if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
41005
- const componentOrHookNode = findRenderPhaseComponentOrHook(predicateNode, context.scopes);
41915
+ const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes);
41006
41916
  if (!componentOrHookNode) return;
41007
41917
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
41008
- if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
41918
+ if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes)) return;
41009
41919
  if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
41010
- const attribute = findEnclosingJsxAttribute(predicateNode);
41920
+ const attribute = findEnclosingJsxAttribute(conditionNode);
41011
41921
  if (!attribute || isEventHandlerAttribute(attribute)) return;
41012
41922
  }
41013
- if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
41014
- if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
41015
- const openingElement = findEnclosingJsxOpeningElement(predicateNode);
41923
+ if (fileIsEmailTemplate || isGatedByFalsyInitialState(conditionNode, context.scopes)) return;
41924
+ if (isAfterClientOnlyEarlyReturn(conditionNode, componentOrHookNode, context.scopes)) return;
41925
+ const openingElement = findEnclosingJsxOpeningElement(conditionNode);
41016
41926
  if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
41017
41927
  if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
41018
41928
  if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
@@ -41394,7 +42304,7 @@ const noInitializeState = defineRule({
41394
42304
  if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
41395
42305
  const analysis = getProgramAnalysis(node);
41396
42306
  if (!analysis) return;
41397
- for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
42307
+ for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
41398
42308
  if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
41399
42309
  const stateName = getStateName(fact.stateDeclarator);
41400
42310
  context.report({
@@ -41752,7 +42662,8 @@ const noJsxElementType = defineRule({
41752
42662
  create: (context) => {
41753
42663
  let isJsxImported = false;
41754
42664
  const flaggedAnnotations = [];
41755
- const checkReturnType = (returnType) => {
42665
+ const collectComponentReturnType = (functionNode, returnType) => {
42666
+ if (!(isNodeOfType(functionNode, "TSDeclareFunction") ? Boolean(functionNode.id && isReactComponentName(functionNode.id.name)) : isComponentFunction$1(functionNode))) return;
41756
42667
  const typeAnnotation = extractReturnTypeAnnotation(returnType);
41757
42668
  if (!typeAnnotation) return;
41758
42669
  if (isJsxElementTypeReference(typeAnnotation)) flaggedAnnotations.push(typeAnnotation);
@@ -41762,19 +42673,16 @@ const noJsxElementType = defineRule({
41762
42673
  if (isJsxImportBinding(node)) isJsxImported = true;
41763
42674
  },
41764
42675
  FunctionDeclaration(node) {
41765
- checkReturnType(node.returnType);
42676
+ collectComponentReturnType(node, node.returnType);
41766
42677
  },
41767
42678
  ArrowFunctionExpression(node) {
41768
- checkReturnType(node.returnType);
42679
+ collectComponentReturnType(node, node.returnType);
41769
42680
  },
41770
42681
  FunctionExpression(node) {
41771
- checkReturnType(node.returnType);
42682
+ collectComponentReturnType(node, node.returnType);
41772
42683
  },
41773
42684
  TSDeclareFunction(node) {
41774
- checkReturnType(node.returnType);
41775
- },
41776
- TSMethodSignature(node) {
41777
- checkReturnType(node.returnType);
42685
+ collectComponentReturnType(node, node.returnType);
41778
42686
  },
41779
42687
  "Program:exit"() {
41780
42688
  if (isJsxImported) return;
@@ -45308,6 +46216,7 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
45308
46216
  "useMatchMedia",
45309
46217
  "useMediaJobProgress",
45310
46218
  "useMediaQuery",
46219
+ "useMediaQueryState",
45311
46220
  "useResizeObserver",
45312
46221
  "useVisibility",
45313
46222
  "useWindowSize"
@@ -45344,9 +46253,30 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
45344
46253
  if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
45345
46254
  return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
45346
46255
  };
45347
- const isExternalSubscriptionHookRef = (ref) => {
46256
+ const getLocalHookExternalStateProof = (analysis, ref) => {
46257
+ let hookFunction = resolveToFunction(ref);
46258
+ if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
46259
+ const definitionNode = definition.node;
46260
+ if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
46261
+ const initializer = stripParenExpression(definitionNode.init);
46262
+ if (!isNodeOfType(initializer, "CallExpression")) continue;
46263
+ const callee = stripParenExpression(initializer.callee);
46264
+ if (!isNodeOfType(callee, "Identifier")) continue;
46265
+ const calleeReference = getRef(analysis, callee);
46266
+ if (!calleeReference) continue;
46267
+ hookFunction = resolveToFunction(calleeReference);
46268
+ if (hookFunction) break;
46269
+ }
46270
+ if (!hookFunction) return null;
46271
+ const returnedReferences = collectFunctionReturnStatements(hookFunction).flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
46272
+ if (returnedReferences.length === 0) return null;
46273
+ return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
46274
+ };
46275
+ const isExternalSubscriptionHookRef = (analysis, ref) => {
45348
46276
  const identifier = ref.identifier;
45349
46277
  if (!isNodeOfType(identifier, "Identifier")) return false;
46278
+ const localHookProof = getLocalHookExternalStateProof(analysis, ref);
46279
+ if (localHookProof !== null) return localHookProof;
45350
46280
  if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
45351
46281
  return Boolean(ref.resolved?.defs.some((def) => {
45352
46282
  const node = def.node;
@@ -45430,11 +46360,11 @@ const noPassDataToParent = defineRule({
45430
46360
  if (argumentRef && resolveToFunction(argumentRef)) return [];
45431
46361
  }
45432
46362
  return getDownstreamRefs(analysis, argument);
45433
- }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
46363
+ }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) || isExternalSubscriptionHookRef(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
45434
46364
  if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
45435
46365
  if (!argsUpstreamRefs.some((argRef) => {
45436
46366
  if (isUseStateIdentifier(argRef.identifier)) return false;
45437
- if (isExternalSubscriptionHookRef(argRef)) return false;
46367
+ if (isExternalSubscriptionHookRef(analysis, argRef)) return false;
45438
46368
  if (isProp(analysis, argRef)) return false;
45439
46369
  if (isUseRefIdentifier(argRef.identifier)) return false;
45440
46370
  if (isRefCurrent(argRef)) return false;
@@ -46998,6 +47928,69 @@ const noRedundantShouldComponentUpdate = defineRule({
46998
47928
  }
46999
47929
  });
47000
47930
  //#endregion
47931
+ //#region src/plugin/rules/correctness/no-ref-callback-cleanup-before-react-19.ts
47932
+ const resolveFunctionExpressions = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
47933
+ const expression = stripParenExpression(rawExpression);
47934
+ if (isFunctionLike$1(expression)) return expression.async || expression.generator ? [] : [expression];
47935
+ if (isNodeOfType(expression, "ConditionalExpression")) {
47936
+ if (isNodeOfType(expression.test, "Literal")) return resolveFunctionExpressions(expression.test.value ? expression.consequent : expression.alternate, scopes, visitedSymbolIds);
47937
+ return [...resolveFunctionExpressions(expression.consequent, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.alternate, scopes, visitedSymbolIds)];
47938
+ }
47939
+ if (isNodeOfType(expression, "LogicalExpression")) {
47940
+ if (isNodeOfType(expression.left, "Literal")) {
47941
+ const isLeftTruthy = Boolean(expression.left.value);
47942
+ if (expression.operator === "&&" && !isLeftTruthy) return [];
47943
+ if (expression.operator === "||" && isLeftTruthy) return [];
47944
+ if (expression.operator === "??" && expression.left.value !== null) return [];
47945
+ }
47946
+ if (expression.operator === "&&") return resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds);
47947
+ return [...resolveFunctionExpressions(expression.left, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds)];
47948
+ }
47949
+ if (isNodeOfType(expression, "SequenceExpression")) {
47950
+ const finalExpression = expression.expressions.at(-1);
47951
+ return finalExpression ? resolveFunctionExpressions(finalExpression, scopes, visitedSymbolIds) : [];
47952
+ }
47953
+ if (isNodeOfType(expression, "CallExpression")) {
47954
+ if (!isReactApiCall(expression, "useCallback", scopes)) return [];
47955
+ const callback = expression.arguments[0];
47956
+ return callback && !isNodeOfType(callback, "SpreadElement") ? resolveFunctionExpressions(callback, scopes, visitedSymbolIds) : [];
47957
+ }
47958
+ if (!isNodeOfType(expression, "Identifier")) return [];
47959
+ const symbol = scopes.symbolFor(expression);
47960
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return [];
47961
+ 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]));
47962
+ const initializer = getDirectConstInitializer(symbol);
47963
+ if (!initializer) return [];
47964
+ return resolveFunctionExpressions(initializer, scopes, new Set([...visitedSymbolIds, symbol.id]));
47965
+ };
47966
+ const functionReturnsCleanupFunction = (functionExpression, scopes) => {
47967
+ if (!isFunctionLike$1(functionExpression)) return false;
47968
+ if (!isNodeOfType(functionExpression.body, "BlockStatement")) return resolveFunctionExpressions(functionExpression.body, scopes).length > 0;
47969
+ return collectFunctionReturnStatements(functionExpression).some((returnStatement) => Boolean(returnStatement.argument && resolveFunctionExpressions(returnStatement.argument, scopes).length > 0));
47970
+ };
47971
+ const callbackReturnsCleanupFunction = (callback, scopes) => {
47972
+ return resolveFunctionExpressions(callback, scopes).some((functionExpression) => functionReturnsCleanupFunction(functionExpression, scopes));
47973
+ };
47974
+ const noRefCallbackCleanupBeforeReact19 = defineRule({
47975
+ id: "no-ref-callback-cleanup-before-react-19",
47976
+ title: "Ref cleanup requires React 19",
47977
+ requires: ["react:18"],
47978
+ disabledWhen: ["react:19"],
47979
+ severity: "warn",
47980
+ 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.",
47981
+ create: (context) => ({ JSXAttribute(node) {
47982
+ if (getJsxAttributeName(node.name) !== "ref") return;
47983
+ if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
47984
+ const callback = node.value.expression;
47985
+ if (!callback || isNodeOfType(callback, "JSXEmptyExpression")) return;
47986
+ if (!callbackReturnsCleanupFunction(callback, context.scopes)) return;
47987
+ context.report({
47988
+ node,
47989
+ 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."
47990
+ });
47991
+ } })
47992
+ });
47993
+ //#endregion
47001
47994
  //#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
47002
47995
  const REPEATED_ANCESTOR_TYPES = new Set([
47003
47996
  "DoWhileStatement",
@@ -56959,8 +57952,39 @@ const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node,
56959
57952
  destructureIndex: 1
56960
57953
  });
56961
57954
  //#endregion
57955
+ //#region src/plugin/utils/unwrap-return-expression.ts
57956
+ const unwrapReturnExpression = (node) => isNodeOfType(node, "ReturnStatement") && node.argument ? node.argument : node;
57957
+ //#endregion
56962
57958
  //#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
56963
57959
  const USE_EFFECT_ONLY = new Set(["useEffect"]);
57960
+ const USE_CALLBACK_ONLY = new Set(["useCallback"]);
57961
+ const USE_STATE_ONLY = new Set(["useState"]);
57962
+ const REACT_API_CALL_OPTIONS = {
57963
+ allowGlobalReactNamespace: true,
57964
+ allowUnboundBareCalls: true,
57965
+ resolveNamedAliases: true
57966
+ };
57967
+ const expressionReadsDerivedSymbol = (context, expression, stateDerivedSymbolIds) => {
57968
+ let readsDerivedSymbol = false;
57969
+ walkAst(expression, (node) => {
57970
+ if (readsDerivedSymbol) return false;
57971
+ if (node !== expression && isFunctionLike$1(node)) return false;
57972
+ if (isNodeOfType(node, "Identifier") && stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) readsDerivedSymbol = true;
57973
+ });
57974
+ return readsDerivedSymbol;
57975
+ };
57976
+ const getStaticObjectPropertyName = (property) => {
57977
+ if (!isNodeOfType(property, "Property") || property.computed || property.method || property.kind !== "init") return null;
57978
+ if (isNodeOfType(property.key, "Identifier")) return property.key.name;
57979
+ if (isNodeOfType(property.key, "Literal") && (typeof property.key.value === "string" || typeof property.key.value === "number")) return String(property.key.value);
57980
+ return null;
57981
+ };
57982
+ const isNonVisibleJsxSpreadProperty = (propertyName) => propertyName === "id" || propertyName.startsWith("aria-") || /^on[A-Z]/.test(propertyName);
57983
+ const isTransparentAssignmentTarget = (identifier) => {
57984
+ const expressionRoot = findTransparentExpressionRoot(identifier);
57985
+ const parent = expressionRoot.parent;
57986
+ return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expressionRoot || isNodeOfType(parent, "UpdateExpression") && parent.argument === expressionRoot || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expressionRoot);
57987
+ };
56964
57988
  const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
56965
57989
  let readsCurrent = false;
56966
57990
  walkAst(argument, (child) => {
@@ -57012,6 +58036,166 @@ const isStateUsedOnlyInIdOrAriaAttributes = (setterCall, setterName) => {
57012
58036
  });
57013
58037
  return referenceCount > 0 && !nonAriaReferenceFound;
57014
58038
  };
58039
+ const isGlobalWindowMember = (context, node, propertyName) => {
58040
+ const member = stripParenExpression(node);
58041
+ if (!isNodeOfType(member, "MemberExpression") || member.computed) return false;
58042
+ const receiver = stripParenExpression(member.object);
58043
+ return isNodeOfType(receiver, "Identifier") && receiver.name === "window" && context.scopes.isGlobalReference(receiver) && isNodeOfType(member.property, "Identifier") && member.property.name === propertyName;
58044
+ };
58045
+ const getDirectWindowWidthSetter = (context, statement) => {
58046
+ const call = unwrapDiscardedExpression(statement);
58047
+ if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 1) return null;
58048
+ if (!isNodeOfType(call.callee, "Identifier") || !isSetterCall(call)) return null;
58049
+ const argument = call.arguments[0];
58050
+ return isGlobalWindowMember(context, argument, "innerWidth") ? call : null;
58051
+ };
58052
+ const getResizeListenerHandler = (context, statement, methodName) => {
58053
+ const call = unwrapDiscardedExpression(statement);
58054
+ if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 2) return null;
58055
+ if (!isGlobalWindowMember(context, call.callee, methodName)) return null;
58056
+ const eventName = call.arguments[0];
58057
+ const handler = call.arguments[1];
58058
+ if (!isNodeOfType(eventName, "Literal") || eventName.value !== "resize") return null;
58059
+ return isNodeOfType(handler, "Identifier") ? handler : null;
58060
+ };
58061
+ const getCleanupResizeHandler = (context, statement) => {
58062
+ if (!isNodeOfType(statement, "ReturnStatement") || !isFunctionLike$1(statement.argument)) return null;
58063
+ const cleanupStatements = getCallbackStatements(statement.argument);
58064
+ if (cleanupStatements.length !== 1) return null;
58065
+ return getResizeListenerHandler(context, unwrapReturnExpression(cleanupStatements[0]), "removeEventListener");
58066
+ };
58067
+ const findExactViewportState = (context, componentFunction, setterCall) => {
58068
+ if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
58069
+ const componentBody = componentFunction.body;
58070
+ if (!isNodeOfType(setterCall.callee, "Identifier")) return null;
58071
+ const setterSymbol = context.scopes.symbolFor(setterCall.callee);
58072
+ if (!setterSymbol || setterSymbol.kind !== "const" || !isNodeOfType(setterSymbol.declarationNode, "VariableDeclarator")) return null;
58073
+ const declarator = setterSymbol.declarationNode;
58074
+ if (!isNodeOfType(declarator.id, "ArrayPattern")) return null;
58075
+ const stateIdentifier = declarator.id.elements?.[0];
58076
+ const setterIdentifier = declarator.id.elements?.[1];
58077
+ 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;
58078
+ const initializer = declarator.init.arguments?.[0];
58079
+ if (!isNodeOfType(initializer, "Literal") || initializer.value !== 0) return null;
58080
+ const stateSymbol = context.scopes.symbolFor(stateIdentifier);
58081
+ if (!stateSymbol) return null;
58082
+ const stateDerivedSymbolIds = new Set([stateSymbol.id]);
58083
+ let didAddDerivedSymbol = true;
58084
+ while (didAddDerivedSymbol) {
58085
+ didAddDerivedSymbol = false;
58086
+ for (const statement of componentBody.body ?? []) {
58087
+ if (!isNodeOfType(statement, "VariableDeclaration")) continue;
58088
+ for (const candidateDeclarator of statement.declarations ?? []) {
58089
+ if (!isNodeOfType(candidateDeclarator.id, "Identifier") || !candidateDeclarator.init) continue;
58090
+ const candidateInitializer = stripParenExpression(candidateDeclarator.init);
58091
+ if (isFunctionLike$1(candidateInitializer) || isNodeOfType(candidateInitializer, "CallExpression") && isReactApiCall(candidateInitializer, USE_CALLBACK_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) continue;
58092
+ if (!expressionReadsDerivedSymbol(context, candidateInitializer, stateDerivedSymbolIds)) continue;
58093
+ const candidateSymbol = context.scopes.symbolFor(candidateDeclarator.id);
58094
+ if (candidateSymbol?.kind === "const" && candidateSymbol.references.every((reference) => reference.flag === "read" && !isTransparentAssignmentTarget(reference.identifier)) && !stateDerivedSymbolIds.has(candidateSymbol.id)) {
58095
+ stateDerivedSymbolIds.add(candidateSymbol.id);
58096
+ didAddDerivedSymbol = true;
58097
+ }
58098
+ }
58099
+ }
58100
+ }
58101
+ const staticSpreadVisibilityBySymbolId = /* @__PURE__ */ new Map();
58102
+ const hasOnlyStaticObjectReferences = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
58103
+ const symbol = context.scopes.symbolFor(identifier);
58104
+ if (!symbol) return false;
58105
+ if (visitedSymbolIds.has(symbol.id)) return true;
58106
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
58107
+ nextVisitedSymbolIds.add(symbol.id);
58108
+ let hasUnknownReference = false;
58109
+ walkAst(componentBody, (node) => {
58110
+ if (hasUnknownReference || !isNodeOfType(node, "Identifier") || context.scopes.symbolFor(node)?.id !== symbol.id || node === symbol.bindingIdentifier) return;
58111
+ const referenceRoot = findTransparentExpressionRoot(node);
58112
+ const parent = referenceRoot.parent;
58113
+ if (isNodeOfType(parent, "JSXSpreadAttribute") && parent.argument === referenceRoot) return;
58114
+ if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier") && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const" && hasOnlyStaticObjectReferences(parent.id, nextVisitedSymbolIds)) return;
58115
+ hasUnknownReference = true;
58116
+ return false;
58117
+ });
58118
+ return !hasUnknownReference;
58119
+ };
58120
+ const classifyStaticSpreadObject = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
58121
+ const symbol = context.scopes.symbolFor(identifier);
58122
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return "unknown";
58123
+ const cachedVisibility = staticSpreadVisibilityBySymbolId.get(symbol.id);
58124
+ if (cachedVisibility) return cachedVisibility;
58125
+ if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || symbol.declarationNode.id !== symbol.bindingIdentifier || !symbol.declarationNode.init) return "unknown";
58126
+ if (!hasOnlyStaticObjectReferences(identifier)) return "unknown";
58127
+ const initializer = stripParenExpression(symbol.declarationNode.init);
58128
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
58129
+ nextVisitedSymbolIds.add(symbol.id);
58130
+ if (isNodeOfType(initializer, "Identifier")) {
58131
+ const visibility = classifyStaticSpreadObject(initializer, nextVisitedSymbolIds);
58132
+ staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
58133
+ return visibility;
58134
+ }
58135
+ if (!isNodeOfType(initializer, "ObjectExpression")) return "unknown";
58136
+ let visibility = "non-visible";
58137
+ for (const property of initializer.properties ?? []) {
58138
+ const propertyName = getStaticObjectPropertyName(property);
58139
+ if (!isNodeOfType(property, "Property") || !propertyName) {
58140
+ visibility = "unknown";
58141
+ break;
58142
+ }
58143
+ if (expressionReadsDerivedSymbol(context, property.value, stateDerivedSymbolIds) && !isNonVisibleJsxSpreadProperty(propertyName)) visibility = "visible";
58144
+ }
58145
+ staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
58146
+ return visibility;
58147
+ };
58148
+ let hasNonAriaReference = false;
58149
+ walkAst(componentBody, (node) => {
58150
+ if (hasNonAriaReference) return false;
58151
+ if (!isNodeOfType(node, "Identifier") || !stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) return;
58152
+ if (findEnclosingFunction$1(node) !== componentFunction) return;
58153
+ const parent = node.parent;
58154
+ if (parent && (isNodeOfType(parent, "MemberExpression") && parent.property === node && !parent.computed || isNodeOfType(parent, "Property") && parent.key === node && !parent.computed)) return;
58155
+ let cursor = parent;
58156
+ while (cursor && cursor !== componentBody) {
58157
+ if (isFunctionLike$1(cursor)) return;
58158
+ if (isNodeOfType(cursor, "JSXSpreadAttribute")) {
58159
+ if (isNodeOfType(node, "Identifier") && classifyStaticSpreadObject(node) === "visible") hasNonAriaReference = true;
58160
+ return;
58161
+ }
58162
+ if (isNodeOfType(cursor, "JSXAttribute")) {
58163
+ if (isEventHandlerAttribute(cursor)) return;
58164
+ if (!isInsideIdOrAriaAttribute(node)) hasNonAriaReference = true;
58165
+ return;
58166
+ }
58167
+ if (isNodeOfType(cursor, "ReturnStatement")) {
58168
+ hasNonAriaReference = true;
58169
+ return;
58170
+ }
58171
+ cursor = cursor.parent;
58172
+ }
58173
+ });
58174
+ return hasNonAriaReference ? stateIdentifier.name : null;
58175
+ };
58176
+ const isExactViewportSubscriptionEffect = (context, effectCall, callback) => {
58177
+ if (!isReactApiCall(effectCall, USE_EFFECT_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return false;
58178
+ if (!isFunctionLike$1(callback) || callback.async || !isNodeOfType(callback.body, "BlockStatement")) return false;
58179
+ const statements = getCallbackStatements(callback);
58180
+ if (statements.length !== 4) return false;
58181
+ const handlerDeclaration = statements[0];
58182
+ if (!isNodeOfType(handlerDeclaration, "VariableDeclaration") || handlerDeclaration.kind !== "const" || handlerDeclaration.declarations?.length !== 1) return false;
58183
+ const handlerDeclarator = handlerDeclaration.declarations[0];
58184
+ if (!isNodeOfType(handlerDeclarator.id, "Identifier") || !isFunctionLike$1(handlerDeclarator.init)) return false;
58185
+ const handlerStatements = getCallbackStatements(handlerDeclarator.init);
58186
+ if (handlerStatements.length !== 1) return false;
58187
+ const handlerSetter = getDirectWindowWidthSetter(context, unwrapReturnExpression(handlerStatements[0]));
58188
+ const subscribedHandler = getResizeListenerHandler(context, statements[1], "addEventListener");
58189
+ const immediateSetter = getDirectWindowWidthSetter(context, statements[2]);
58190
+ const cleanupHandler = getCleanupResizeHandler(context, statements[3]);
58191
+ if (!handlerSetter || !subscribedHandler || !immediateSetter || !cleanupHandler) return false;
58192
+ const handlerSymbol = context.scopes.symbolFor(handlerDeclarator.id);
58193
+ if (!handlerSymbol || context.scopes.symbolFor(subscribedHandler) !== handlerSymbol || context.scopes.symbolFor(cleanupHandler) !== handlerSymbol) return false;
58194
+ if (!isNodeOfType(handlerSetter.callee, "Identifier") || !isNodeOfType(immediateSetter.callee, "Identifier") || context.scopes.symbolFor(handlerSetter.callee) !== context.scopes.symbolFor(immediateSetter.callee)) return false;
58195
+ const componentFunction = findEnclosingFunction$1(effectCall);
58196
+ if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return false;
58197
+ return findExactViewportState(context, componentFunction, immediateSetter) !== null;
58198
+ };
57015
58199
  const renderingHydrationNoFlicker = defineRule({
57016
58200
  id: "rendering-hydration-no-flicker",
57017
58201
  title: "useEffect setState flashes on mount",
@@ -57024,7 +58208,14 @@ const renderingHydrationNoFlicker = defineRule({
57024
58208
  if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
57025
58209
  const callback = getEffectCallback(node);
57026
58210
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
57027
- const bodyStatements = (isNodeOfType(callback.body, "BlockStatement") ? callback.body.body ?? [] : [callback.body]).filter((statement) => !isNoOpStatement(statement));
58211
+ if (isExactViewportSubscriptionEffect(context, node, callback)) {
58212
+ context.report({
58213
+ node,
58214
+ message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
58215
+ });
58216
+ return;
58217
+ }
58218
+ const bodyStatements = getCallbackStatements(callback);
57028
58219
  if (bodyStatements.length !== 1) return;
57029
58220
  const soleStatement = bodyStatements[0];
57030
58221
  if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
@@ -71567,6 +72758,17 @@ const reactDoctorRules = [
71567
72758
  requires: [...new Set(["react", ...noRedundantShouldComponentUpdate.requires ?? []])]
71568
72759
  }
71569
72760
  },
72761
+ {
72762
+ key: "react-doctor/no-ref-callback-cleanup-before-react-19",
72763
+ id: "no-ref-callback-cleanup-before-react-19",
72764
+ source: "react-doctor",
72765
+ originallyExternal: false,
72766
+ rule: {
72767
+ ...noRefCallbackCleanupBeforeReact19,
72768
+ framework: "global",
72769
+ category: "Bugs"
72770
+ }
72771
+ },
71570
72772
  {
71571
72773
  key: "react-doctor/no-ref-current-in-render",
71572
72774
  id: "no-ref-current-in-render",