oxlint-plugin-react-doctor 0.7.9-dev.b51022f → 0.7.9-dev.c245b9d

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 +1653 -149
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7637,6 +7637,7 @@ const areExpressionsStructurallyEqual = (a, b) => {
7637
7637
  if (a.type !== b.type) return false;
7638
7638
  if (isNodeOfType(a, "ThisExpression")) return true;
7639
7639
  if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
7640
+ if (isNodeOfType(a, "PrivateIdentifier") && isNodeOfType(b, "PrivateIdentifier")) return a.name === b.name;
7640
7641
  if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
7641
7642
  if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
7642
7643
  if (a.computed !== b.computed) return false;
@@ -8958,7 +8959,7 @@ const isReactNamespaceImport = (identifier, scopes) => {
8958
8959
  if (!symbol || !isImportedFromReact(symbol)) return false;
8959
8960
  return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
8960
8961
  };
8961
- const isReactNamespaceReceiver = (receiver, scopes, options) => {
8962
+ const isReactNamespaceReceiver$1 = (receiver, scopes, options) => {
8962
8963
  if (!isNodeOfType(receiver, "Identifier")) return false;
8963
8964
  if (isReactNamespaceImport(receiver, scopes)) return true;
8964
8965
  return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
@@ -8971,7 +8972,7 @@ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) =>
8971
8972
  for (const property of pattern.properties) {
8972
8973
  if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
8973
8974
  const propertyName = getStaticPropertyKeyName(property);
8974
- return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver(stripParenExpression(symbol.initializer), scopes, options));
8975
+ return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver$1(stripParenExpression(symbol.initializer), scopes, options));
8975
8976
  }
8976
8977
  return false;
8977
8978
  };
@@ -8995,7 +8996,7 @@ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds
8995
8996
  return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
8996
8997
  }
8997
8998
  if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
8998
- return isReactNamespaceReceiver(stripParenExpression(callee.object), scopes, options);
8999
+ return isReactNamespaceReceiver$1(stripParenExpression(callee.object), scopes, options);
8999
9000
  };
9000
9001
  //#endregion
9001
9002
  //#region src/plugin/utils/is-proven-browser-api-receiver.ts
@@ -13651,7 +13652,7 @@ const getPromiseChainCallForCallback = (candidate) => {
13651
13652
  if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
13652
13653
  return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
13653
13654
  };
13654
- const collectEffectInvokedFunctions = (effectCallback) => {
13655
+ const collectInvokedFunctions = (effectCallback, includePromiseCallbacks) => {
13655
13656
  const invokedFunctions = new Set([effectCallback]);
13656
13657
  const localFunctionBindings = /* @__PURE__ */ new Map();
13657
13658
  const calledBindingNames = /* @__PURE__ */ new Set();
@@ -13685,12 +13686,14 @@ const collectEffectInvokedFunctions = (effectCallback) => {
13685
13686
  calledBindingNames.add(callee.name);
13686
13687
  return;
13687
13688
  }
13688
- if (isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
13689
+ if (includePromiseCallbacks && isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
13689
13690
  });
13690
13691
  for (const calledName of calledBindingNames) enqueue(localFunctionBindings.get(calledName));
13691
13692
  }
13692
13693
  return invokedFunctions;
13693
13694
  };
13695
+ const collectEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, true);
13696
+ const collectSynchronouslyEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, false);
13694
13697
  //#endregion
13695
13698
  //#region src/plugin/utils/is-react-hook-name.ts
13696
13699
  const isReactHookName = (name) => {
@@ -14411,7 +14414,7 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
14411
14414
  const callNode = findDirectCallForReference(reference.identifier);
14412
14415
  return callNode ? [callNode] : [];
14413
14416
  });
14414
- if (invocationCalls.length !== 1) return null;
14417
+ if (invocationCalls.length !== 1 || symbol.references.length !== 1) return null;
14415
14418
  const invocationCall = invocationCalls[0];
14416
14419
  return findEnclosingFunction$1(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
14417
14420
  };
@@ -14698,7 +14701,108 @@ const hasPotentialInterruptionAfterGuard = (callback, guardState, usageNode, con
14698
14701
  });
14699
14702
  return hasPotentialInterruption;
14700
14703
  };
14704
+ const getNumericReactRefCurrentKey = (expression, context) => {
14705
+ const refSymbol = resolveReactRefSymbol(stripParenExpression(expression), context.scopes);
14706
+ const initializer = refSymbol?.initializer ? stripParenExpression(refSymbol.initializer) : null;
14707
+ if (!isNodeOfType(initializer, "CallExpression")) return null;
14708
+ const initialValue = initializer.arguments?.[0] ? stripParenExpression(initializer.arguments[0]) : null;
14709
+ if (!isNodeOfType(initialValue, "Literal") || typeof initialValue.value !== "number") return null;
14710
+ return resolveExpressionKey(expression, context);
14711
+ };
14712
+ const getBlockingGenerationKey = (expression, context) => {
14713
+ const test = stripParenExpression(expression);
14714
+ if (isNodeOfType(test, "LogicalExpression") && test.operator === "||") return getBlockingGenerationKey(test.left, context) ?? getBlockingGenerationKey(test.right, context);
14715
+ if (!isNodeOfType(test, "BinaryExpression") || test.operator !== "!==" && test.operator !== "!=") return null;
14716
+ const leftKey = getNumericReactRefCurrentKey(test.left, context);
14717
+ const rightKey = getNumericReactRefCurrentKey(test.right, context);
14718
+ const snapshotExpression = leftKey ? stripParenExpression(test.right) : stripParenExpression(test.left);
14719
+ const key = leftKey ?? rightKey;
14720
+ return key && isNodeOfType(snapshotExpression, "Identifier") ? key : null;
14721
+ };
14722
+ const findGenerationGuardKeyForDeferredUsage = (usageFunction, usageNode, context) => {
14723
+ if (!isFunctionLike$1(usageFunction)) return null;
14724
+ let generationKey = null;
14725
+ walkAst(usageFunction.body, (child) => {
14726
+ if (generationKey) return false;
14727
+ if (child !== usageFunction.body && isFunctionLike$1(child)) return false;
14728
+ if (!isNodeOfType(child, "IfStatement") || child.alternate) return;
14729
+ const key = getBlockingGenerationKey(child.test, context);
14730
+ if (!key || canNodeReachLaterNodeWithinFunction(child.consequent, usageNode, usageFunction, context) || !doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], usageFunction, context)) return;
14731
+ generationKey = key;
14732
+ });
14733
+ return generationKey;
14734
+ };
14735
+ const isGenerationAdvance = (node, generationKey, context) => {
14736
+ if (isNodeOfType(node, "UpdateExpression") && resolveExpressionKey(node.argument, context) === generationKey) return true;
14737
+ if (!isNodeOfType(node, "AssignmentExpression") || resolveExpressionKey(node.left, context) !== generationKey || node.operator !== "+=" && node.operator !== "-=") return false;
14738
+ const amount = stripParenExpression(node.right);
14739
+ return isNodeOfType(amount, "Literal") && typeof amount.value === "number" && amount.value !== 0;
14740
+ };
14741
+ const functionAdvancesGeneration = (owner, generationKey, context) => {
14742
+ if (!isFunctionLike$1(owner)) return false;
14743
+ let didAdvanceGeneration = false;
14744
+ walkAst(owner.body, (child) => {
14745
+ if (didAdvanceGeneration) return false;
14746
+ if (child !== owner.body && isFunctionLike$1(child)) return false;
14747
+ if (isGenerationAdvance(child, generationKey, context)) {
14748
+ didAdvanceGeneration = true;
14749
+ return false;
14750
+ }
14751
+ });
14752
+ return didAdvanceGeneration;
14753
+ };
14754
+ const cleanupReturnsReleaseUsage = (cleanupReturns, usage, context) => cleanupReturns.length > 0 && cleanupReturns.every((cleanupReturn) => {
14755
+ if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
14756
+ const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
14757
+ return Boolean(cleanupFunction && isFunctionLike$1(cleanupFunction) && doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context));
14758
+ });
14759
+ const getOwnedFunctionReference = (reference, usageFunction, usageNode, callback, cleanupReturns, context) => {
14760
+ const directCall = findDirectCallForReference(reference);
14761
+ if (directCall) {
14762
+ const referenceOwner = findEnclosingFunction$1(directCall);
14763
+ if (referenceOwner && referenceOwner !== usageFunction && collectSynchronouslyEffectInvokedFunctions(callback).has(referenceOwner)) return { generationKey: null };
14764
+ const generationKey = referenceOwner ? findGenerationGuardKeyForDeferredUsage(referenceOwner, directCall, context) : null;
14765
+ return generationKey ? { generationKey } : null;
14766
+ }
14767
+ const referenceRoot = findTransparentExpressionRoot(reference);
14768
+ const schedulerCall = referenceRoot.parent;
14769
+ 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;
14770
+ const schedulerUsage = {
14771
+ kind: "timer",
14772
+ node: schedulerCall,
14773
+ resourceName: schedulerCall.callee.name,
14774
+ handleKey: findAssignedResourceKey(schedulerCall, context),
14775
+ receiverKey: null,
14776
+ registrationVerbName: schedulerCall.callee.name,
14777
+ eventKey: null,
14778
+ handlerKey: null
14779
+ };
14780
+ const generationKey = findGenerationGuardKeyForDeferredUsage(usageFunction, usageNode, context);
14781
+ return schedulerUsage.handleKey !== null && cleanupReturnsReleaseUsage(cleanupReturns, schedulerUsage, context) && generationKey ? { generationKey } : null;
14782
+ };
14783
+ const hasGuardedRefOwnedNestedCleanup = (callback, usage, cleanupReturns, context) => {
14784
+ const usageFunction = findEnclosingFunction$1(usage.node);
14785
+ const usageExpression = findTransparentExpressionRoot(usage.node);
14786
+ const usageAssignment = usageExpression.parent;
14787
+ 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;
14788
+ const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
14789
+ if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
14790
+ const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
14791
+ return cleanupFunction && isFunctionLike$1(cleanupFunction) ? [cleanupFunction] : [];
14792
+ });
14793
+ const bindingIdentifier = getFunctionBindingIdentifier$1(usageFunction);
14794
+ const functionSymbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
14795
+ if (!functionSymbol || functionSymbol.references.length === 0) return false;
14796
+ const ownedReferences = functionSymbol.references.map((reference) => getOwnedFunctionReference(reference.identifier, usageFunction, usage.node, callback, cleanupReturns, context));
14797
+ if (ownedReferences.some((reference) => reference === null)) return false;
14798
+ const generationKeys = new Set(ownedReferences.flatMap((reference) => reference?.generationKey ? [reference.generationKey] : []));
14799
+ if (generationKeys.size !== 1) return false;
14800
+ const generationKey = generationKeys.values().next().value;
14801
+ if (typeof generationKey !== "string") return false;
14802
+ return [...collectSynchronouslyEffectInvokedFunctions(callback), ...cleanupFunctions].some((owner) => functionAdvancesGeneration(owner, generationKey, context));
14803
+ };
14701
14804
  const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
14805
+ if (hasGuardedRefOwnedNestedCleanup(callback, usage, cleanupReturns, context)) return true;
14702
14806
  const usageFunction = findEnclosingFunction$1(usage.node);
14703
14807
  const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
14704
14808
  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;
@@ -14910,6 +15014,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
14910
15014
  if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
14911
15015
  if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
14912
15016
  if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) return false;
15017
+ if (usage.registrationVerbName === "subscribe" && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub") && usage.handleKey !== null && resolveExpressionKey(callNode.arguments?.[0], context) === usage.handleKey) return true;
14913
15018
  const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
14914
15019
  if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
14915
15020
  const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
@@ -14946,7 +15051,8 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
14946
15051
  const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
14947
15052
  if (!releaseHandler) return releaseVerbName === "off";
14948
15053
  const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
14949
- return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey;
15054
+ const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignature ? 0 : 1] : null;
15055
+ return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
14950
15056
  }
14951
15057
  if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
14952
15058
  return true;
@@ -14971,13 +15077,188 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
14971
15077
  if (!symbol) return false;
14972
15078
  return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
14973
15079
  };
15080
+ const isJsxRefAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && node.name.name === "ref";
15081
+ const isFunctionForwardedToReactRef = (functionNode, context) => {
15082
+ const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
15083
+ if (!bindingIdentifier) return false;
15084
+ const symbol = context.scopes.symbolFor(bindingIdentifier);
15085
+ if (!symbol) return false;
15086
+ return symbol.references.some((reference) => {
15087
+ const referenceRoot = findTransparentExpressionRoot(reference.identifier);
15088
+ const expressionContainer = referenceRoot.parent;
15089
+ return Boolean(isNodeOfType(expressionContainer, "JSXExpressionContainer") && expressionContainer.expression === referenceRoot && isJsxRefAttribute(expressionContainer.parent));
15090
+ });
15091
+ };
15092
+ const findRetainedDisposerStorages = (disposerFunction, usage, context) => {
15093
+ if (!isFunctionLike$1(disposerFunction) || disposerFunction.async || disposerFunction.generator) return [];
15094
+ const usageFunction = findEnclosingFunction$1(usage.node);
15095
+ if (!usageFunction || !isFunctionLike$1(usageFunction)) return [];
15096
+ const assignments = /* @__PURE__ */ new Map();
15097
+ const collectAssignment = (expression) => {
15098
+ const expressionRoot = findTransparentExpressionRoot(expression);
15099
+ const assignment = expressionRoot.parent;
15100
+ if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.right !== expressionRoot) return;
15101
+ const refSymbol = resolveReactRefSymbol(stripParenExpression(assignment.left), context.scopes);
15102
+ const refCurrentKey = resolveExpressionKey(assignment.left, context);
15103
+ const retainedFunction = findEnclosingFunction$1(assignment);
15104
+ const assignmentStart = getRangeStart(assignment);
15105
+ if (!refSymbol || !refCurrentKey || !retainedFunction || retainedFunction !== usageFunction || assignmentStart === null) return;
15106
+ assignments.set(assignmentStart, {
15107
+ assignmentNode: assignment,
15108
+ refCurrentKey,
15109
+ retainedFunction
15110
+ });
15111
+ };
15112
+ collectAssignment(disposerFunction);
15113
+ const bindingIdentifier = getFunctionBindingIdentifier$1(disposerFunction);
15114
+ const symbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
15115
+ for (const reference of symbol?.references ?? []) collectAssignment(reference.identifier);
15116
+ walkAst(usageFunction.body, (child) => {
15117
+ if (child !== usageFunction.body && isFunctionLike$1(child)) return false;
15118
+ if (isNodeOfType(child, "AssignmentExpression") && resolveStableValue(child.right, context) === disposerFunction) collectAssignment(child.right);
15119
+ });
15120
+ return [...assignments.values()];
15121
+ };
15122
+ const isRetainedDisposerStorageEstablished = (storage, usage, context) => doMatchingNodesCoverEveryPathBeforeUsage(usage.node, [storage.assignmentNode], storage.retainedFunction, context) || doMatchingNodesCoverEveryPathAfterUsage(usage.node, [storage.assignmentNode], context);
15123
+ const hasUnsafeRetainedDisposerOverwrite = (storage, usage, context) => {
15124
+ let hasUnsafeOverwrite = false;
15125
+ walkAst(storage.retainedFunction.body, (child) => {
15126
+ if (hasUnsafeOverwrite) return false;
15127
+ if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
15128
+ if (!isNodeOfType(child, "AssignmentExpression") || child === storage.assignmentNode || resolveExpressionKey(child.left, context) !== storage.refCurrentKey || !canNodeReachLaterNodeWithinFunction(usage.node, child, storage.retainedFunction, context)) return;
15129
+ const storedValue = resolveStableValue(child.right, context);
15130
+ if (!storedValue || !isFunctionLike$1(storedValue) || !doesCleanupFunctionReleaseUsage(storedValue, usage, context)) {
15131
+ hasUnsafeOverwrite = true;
15132
+ return false;
15133
+ }
15134
+ });
15135
+ return hasUnsafeOverwrite;
15136
+ };
15137
+ const hasEffectCleanupInvocation = (storage, usage, context) => {
15138
+ const componentFunction = findEnclosingFunction$1(storage.retainedFunction);
15139
+ if (!componentFunction || !isFunctionLike$1(componentFunction)) return false;
15140
+ const cleanupFunctionInvokesRef = (cleanupFunction) => {
15141
+ if (!isFunctionLike$1(cleanupFunction)) return false;
15142
+ let didFindCleanupCall = false;
15143
+ walkAst(cleanupFunction.body, (child) => {
15144
+ if (didFindCleanupCall) return false;
15145
+ if (child !== cleanupFunction.body && isFunctionLike$1(child)) return false;
15146
+ if (isNodeOfType(child, "CallExpression") && resolveExpressionKey(child.callee, context) === storage.refCurrentKey) {
15147
+ const callRoot = findTransparentExpressionRoot(child);
15148
+ const callStatement = callRoot.parent;
15149
+ const isDirectBlockStatement = isNodeOfType(cleanupFunction.body, "BlockStatement") && isNodeOfType(callStatement, "ExpressionStatement") && callStatement.parent === cleanupFunction.body;
15150
+ const isConciseBody = cleanupFunction.body === callRoot;
15151
+ if ((isDirectBlockStatement || isConciseBody) && !hasUnprovenReturnBeforeRefOwnedRelease(cleanupFunction, child, storage.refCurrentKey, context)) {
15152
+ didFindCleanupCall = true;
15153
+ return false;
15154
+ }
15155
+ }
15156
+ });
15157
+ return didFindCleanupCall;
15158
+ };
15159
+ const effectReturnsCleanup = (effectCallback) => {
15160
+ if (!isFunctionLike$1(effectCallback)) return false;
15161
+ if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
15162
+ const cleanupFunction = resolveRefOwnedCleanupFunction(effectCallback.body, context);
15163
+ return Boolean(cleanupFunction && cleanupFunctionInvokesRef(cleanupFunction));
15164
+ }
15165
+ const matchingReturns = [];
15166
+ walkInsideStatementBlocks(effectCallback.body, (child) => {
15167
+ if (!isNodeOfType(child, "ReturnStatement") || !child.argument) return;
15168
+ const cleanupFunction = resolveRefOwnedCleanupFunction(child.argument, context);
15169
+ if (!cleanupFunction || !cleanupFunctionInvokesRef(cleanupFunction)) return;
15170
+ matchingReturns.push(child);
15171
+ });
15172
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15173
+ };
15174
+ let didFindInvocation = false;
15175
+ walkAst(componentFunction.body, (child) => {
15176
+ if (didFindInvocation) return false;
15177
+ if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
15178
+ const effectCallback = getEffectCallback(child);
15179
+ if (effectCallback && effectReturnsCleanup(effectCallback)) {
15180
+ didFindInvocation = true;
15181
+ return false;
15182
+ }
15183
+ });
15184
+ return didFindInvocation;
15185
+ };
15186
+ const hasCallbackRefReplacementInvocation = (storage, usage, context) => {
15187
+ const isReturnedCallbackRefShape = () => {
15188
+ if (!isFunctionLike$1(storage.retainedFunction)) return false;
15189
+ const callbackCall = findTransparentExpressionRoot(storage.retainedFunction).parent;
15190
+ if (!isNodeOfType(callbackCall, "CallExpression") || !isReactApiCall(callbackCall, "useCallback", context.scopes)) return false;
15191
+ const nodeParameter = storage.retainedFunction.params?.[0];
15192
+ const nodeParameterKey = resolveExpressionKey(nodeParameter, context);
15193
+ if (!nodeParameterKey || usage.receiverKey !== nodeParameterKey) return false;
15194
+ const bindingIdentifier = getFunctionBindingIdentifier$1(storage.retainedFunction);
15195
+ const symbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
15196
+ if (!Boolean(symbol?.references.some((reference) => {
15197
+ const referenceRoot = findTransparentExpressionRoot(reference.identifier);
15198
+ const property = referenceRoot.parent;
15199
+ if (!isNodeOfType(property, "Property") || property.value !== referenceRoot || !isNodeOfType(property.parent, "ObjectExpression")) return false;
15200
+ const returnedObject = findTransparentExpressionRoot(property.parent);
15201
+ const returnStatement = returnedObject.parent;
15202
+ if (!isNodeOfType(returnStatement, "ReturnStatement") || returnStatement.argument !== returnedObject) return false;
15203
+ const hookFunction = findEnclosingFunction$1(returnStatement);
15204
+ return Boolean(hookFunction && getFunctionBindingIdentifier$1(hookFunction)?.name.startsWith("use"));
15205
+ }))) return false;
15206
+ const usageStart = getRangeStart(usage.node);
15207
+ if (usageStart === null) return false;
15208
+ let hasNullExit = false;
15209
+ walkAst(storage.retainedFunction.body, (child) => {
15210
+ if (hasNullExit) return false;
15211
+ if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
15212
+ if (!isNodeOfType(child, "IfStatement") || (getRangeStart(child) ?? usageStart) >= usageStart) return;
15213
+ const test = stripParenExpression(child.test);
15214
+ if (!isNodeOfType(test, "UnaryExpression") || test.operator !== "!" || resolveExpressionKey(test.argument, context) !== nodeParameterKey) return;
15215
+ const consequent = child.consequent;
15216
+ hasNullExit = isNodeOfType(consequent, "ReturnStatement") || isNodeOfType(consequent, "BlockStatement") && consequent.body.some((statement) => isNodeOfType(statement, "ReturnStatement"));
15217
+ if (hasNullExit) return false;
15218
+ });
15219
+ return hasNullExit;
15220
+ };
15221
+ if (!isFunctionForwardedToReactRef(storage.retainedFunction, context) && !isReturnedCallbackRefShape()) return false;
15222
+ const cleanupCalls = [];
15223
+ walkAst(storage.retainedFunction.body, (child) => {
15224
+ if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
15225
+ if (isNodeOfType(child, "CallExpression") && resolveExpressionKey(child.callee, context) === storage.refCurrentKey) cleanupCalls.push(child);
15226
+ });
15227
+ return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, cleanupCalls, storage.retainedFunction, context);
15228
+ };
15229
+ const isRetainedDisposerRefRelease = (releaseNode, usage, context) => {
15230
+ const disposerFunction = findEnclosingFunction$1(releaseNode);
15231
+ if (!disposerFunction) return false;
15232
+ return findRetainedDisposerStorages(disposerFunction, usage, context).some((storage) => isRetainedDisposerStorageEstablished(storage, usage, context) && !hasUnsafeRetainedDisposerOverwrite(storage, usage, context) && (hasEffectCleanupInvocation(storage, usage, context) || hasCallbackRefReplacementInvocation(storage, usage, context)));
15233
+ };
15234
+ const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
15235
+ 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;
15236
+ const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
15237
+ const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
15238
+ if (!isNodeOfType(releaseCall, "CallExpression")) return false;
15239
+ const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
15240
+ if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
15241
+ const ownerFunction = findEnclosingFunction$1(releaseFunction);
15242
+ if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
15243
+ const triggerRegistrations = [];
15244
+ walkAst(ownerFunction.body, (child) => {
15245
+ if (child !== ownerFunction.body && isFunctionLike$1(child)) return false;
15246
+ if (!isNodeOfType(child, "CallExpression")) return;
15247
+ const registrationDetails = getCallRegistrationDetails(child, context);
15248
+ if (registrationDetails.registrationVerbName === "addEventListener" && registrationDetails.receiverKey === usage.receiverKey && resolveStableValue(child.arguments?.[1], context) === releaseFunction) triggerRegistrations.push(child);
15249
+ });
15250
+ if (triggerRegistrations.some((triggerRegistration) => triggerRegistration === usage.node)) return true;
15251
+ return doMatchingNodesCoverEveryPathAfterUsage(usage.node, triggerRegistrations, context) || doMatchingNodesCoverEveryPathBeforeUsage(usage.node, triggerRegistrations, ownerFunction, context);
15252
+ };
14974
15253
  const isReleaseReachableForUsage = (releaseNode, usage, context) => {
14975
15254
  if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
14976
15255
  const releaseFunction = findEnclosingFunction$1(releaseNode);
14977
15256
  if (!releaseFunction) return true;
14978
15257
  if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
15258
+ if (isRetainedDisposerRefRelease(releaseNode, usage, context)) return true;
14979
15259
  const usageFunction = findEnclosingFunction$1(usage.node);
14980
15260
  if (usageFunction && isFunctionLike$1(usageFunction) && getAssignedReactRefSymbol(usageFunction, context) && isCleanupFunctionReferencedByReturn(usageFunction, releaseFunction, context)) return isReactRefCallbackCleanupOwnedByEffect(usageFunction, releaseFunction, usage, context);
15261
+ if (isSelfReleasingListenerRelease(releaseNode, releaseFunction, usage, context)) return true;
14981
15262
  return isPotentiallyReachableFunction(releaseFunction, context);
14982
15263
  };
14983
15264
  const fileContainsReleaseForUsage = (usage, context) => {
@@ -17111,7 +17392,7 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allow
17111
17392
  keys.add(depKey);
17112
17393
  continue;
17113
17394
  }
17114
- const identitySourceKeys = resolveReactiveIdentitySourceKeys(symbol, scopes);
17395
+ const identitySourceKeys = resolvePureCalledFunctionSourceKeys(reference, symbol, scopes) ?? resolveRenderDerivedMutableSourceKeys(reference, symbol, scopes) ?? resolveReactiveIdentitySourceKeys(symbol, scopes);
17115
17396
  if (identitySourceKeys) {
17116
17397
  if (identitySourceKeys.size === 0) stableCapturedNames.add(depKey);
17117
17398
  for (const identitySourceKey of identitySourceKeys) keys.add(identitySourceKey);
@@ -17194,6 +17475,161 @@ const resolveReactiveIdentitySourceKeys = (symbol, scopes) => {
17194
17475
  if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier || symbol.references.some((reference) => reference.flag !== "read")) return null;
17195
17476
  return resolveIdentitySourceKeysFromExpression(symbol.initializer, scopes, new Set([symbol.id]));
17196
17477
  };
17478
+ const isPureDerivedExpression = (expression) => {
17479
+ const candidate = unwrapExpression$3(expression);
17480
+ if (isNodeOfType(candidate, "Literal") || isNodeOfType(candidate, "Identifier")) return true;
17481
+ if (isNodeOfType(candidate, "MemberExpression")) return isPureDerivedExpression(candidate.object) && (!candidate.computed || isPureDerivedExpression(candidate.property));
17482
+ if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return isPureDerivedExpression(candidate.left) && isPureDerivedExpression(candidate.right);
17483
+ if (isNodeOfType(candidate, "UnaryExpression")) return candidate.operator !== "delete" && isPureDerivedExpression(candidate.argument);
17484
+ if (isNodeOfType(candidate, "ConditionalExpression")) return isPureDerivedExpression(candidate.test) && isPureDerivedExpression(candidate.consequent) && isPureDerivedExpression(candidate.alternate);
17485
+ if (isNodeOfType(candidate, "TemplateLiteral")) return candidate.expressions.every((nestedExpression) => isPureDerivedExpression(nestedExpression));
17486
+ return false;
17487
+ };
17488
+ const isPureDerivedStatement = (statement) => {
17489
+ if (isNodeOfType(statement, "BlockStatement")) return statement.body.every((nestedStatement) => isPureDerivedStatement(nestedStatement));
17490
+ if (isNodeOfType(statement, "ReturnStatement")) return !statement.argument || isPureDerivedExpression(statement.argument);
17491
+ if (isNodeOfType(statement, "IfStatement")) return isPureDerivedExpression(statement.test) && isPureDerivedStatement(statement.consequent) && (!statement.alternate || isPureDerivedStatement(statement.alternate));
17492
+ return false;
17493
+ };
17494
+ const isPureDerivedFunction = (functionNode) => {
17495
+ if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return false;
17496
+ if (functionNode.async || functionNode.generator) return false;
17497
+ return isNodeOfType(functionNode.body, "BlockStatement") ? isPureDerivedStatement(functionNode.body) : isPureDerivedExpression(functionNode.body);
17498
+ };
17499
+ const resolvePureCalledFunctionSourceKeys = (reference, symbol, scopes) => {
17500
+ if (symbol.references.some((symbolReference) => symbolReference.flag !== "read")) return null;
17501
+ const referenceRoot = findTransparentExpressionRoot(reference.identifier);
17502
+ const callExpression = referenceRoot.parent;
17503
+ if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== referenceRoot) return null;
17504
+ const functionNode = getFunctionValueNode(symbol);
17505
+ if (!functionNode || !isPureDerivedFunction(functionNode)) return null;
17506
+ const sourceKeys = /* @__PURE__ */ new Set();
17507
+ for (const capturedReference of closureCaptures(functionNode, scopes)) {
17508
+ const capturedSymbol = capturedReference.resolvedSymbol;
17509
+ if (!capturedSymbol || capturedSymbol.id === symbol.id) continue;
17510
+ if (isOutsideAllFunctions(capturedSymbol) || symbolHasStableValue(capturedSymbol, scopes)) continue;
17511
+ const capturedKey = computeDepKey(capturedReference);
17512
+ if (!capturedKey) return null;
17513
+ if (capturedKey === capturedSymbol.name) {
17514
+ const nestedSourceKeys = resolveReactiveIdentitySourceKeys(capturedSymbol, scopes);
17515
+ if (nestedSourceKeys) {
17516
+ for (const nestedSourceKey of nestedSourceKeys) sourceKeys.add(nestedSourceKey);
17517
+ continue;
17518
+ }
17519
+ }
17520
+ sourceKeys.add(capturedKey);
17521
+ }
17522
+ return sourceKeys.size > 0 ? sourceKeys : null;
17523
+ };
17524
+ const mergeDerivedExpressionSourceKeys = (expressions, scopes, visitedSymbolIds) => {
17525
+ const sourceKeys = /* @__PURE__ */ new Set();
17526
+ for (const expression of expressions) {
17527
+ const expressionSourceKeys = resolveDerivedExpressionSourceKeys(expression, scopes, visitedSymbolIds);
17528
+ if (!expressionSourceKeys) return null;
17529
+ for (const expressionSourceKey of expressionSourceKeys) sourceKeys.add(expressionSourceKey);
17530
+ }
17531
+ return sourceKeys;
17532
+ };
17533
+ const resolveDerivedExpressionSourceKeys = (expression, scopes, visitedSymbolIds) => {
17534
+ const candidate = unwrapExpression$3(expression);
17535
+ if (isNodeOfType(candidate, "Literal")) return /* @__PURE__ */ new Set();
17536
+ if (isNodeOfType(candidate, "Identifier")) {
17537
+ if (scopes.isGlobalReference(candidate)) return /* @__PURE__ */ new Set();
17538
+ const sourceSymbol = scopes.symbolFor(candidate);
17539
+ if (!sourceSymbol) return null;
17540
+ if (isOutsideAllFunctions(sourceSymbol) || symbolHasStableValue(sourceSymbol, scopes)) return /* @__PURE__ */ new Set();
17541
+ 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)) {
17542
+ visitedSymbolIds.add(sourceSymbol.id);
17543
+ const sourceKeys = resolveDerivedExpressionSourceKeys(sourceSymbol.initializer, scopes, visitedSymbolIds);
17544
+ visitedSymbolIds.delete(sourceSymbol.id);
17545
+ if (sourceKeys) return sourceKeys;
17546
+ }
17547
+ return new Set([sourceSymbol.name]);
17548
+ }
17549
+ if (isNodeOfType(candidate, "MemberExpression")) {
17550
+ if (hasComputedMemberExpression(candidate)) return null;
17551
+ const sourceKey = stringifyMemberChain(candidate);
17552
+ const rootIdentifier = getMemberRootIdentifier(candidate);
17553
+ const rootSymbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
17554
+ if (!sourceKey || !rootSymbol) return null;
17555
+ if (isOutsideAllFunctions(rootSymbol) || symbolHasStableValue(rootSymbol, scopes)) return /* @__PURE__ */ new Set();
17556
+ return new Set([sourceKey]);
17557
+ }
17558
+ if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return mergeDerivedExpressionSourceKeys([candidate.left, candidate.right], scopes, visitedSymbolIds);
17559
+ if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator !== "delete") return resolveDerivedExpressionSourceKeys(candidate.argument, scopes, visitedSymbolIds);
17560
+ if (isNodeOfType(candidate, "ConditionalExpression")) return mergeDerivedExpressionSourceKeys([
17561
+ candidate.test,
17562
+ candidate.consequent,
17563
+ candidate.alternate
17564
+ ], scopes, visitedSymbolIds);
17565
+ if (isNodeOfType(candidate, "TemplateLiteral")) return mergeDerivedExpressionSourceKeys(candidate.expressions, scopes, visitedSymbolIds);
17566
+ if (isNodeOfType(candidate, "NewExpression")) {
17567
+ const callee = unwrapExpression$3(candidate.callee);
17568
+ if (!isNodeOfType(callee, "Identifier") || callee.name !== "Error" || !scopes.isGlobalReference(callee)) return null;
17569
+ const argumentsToAnalyze = [];
17570
+ for (const argument of candidate.arguments) {
17571
+ if (!isAstNode(argument) || isNodeOfType(argument, "SpreadElement")) return null;
17572
+ argumentsToAnalyze.push(argument);
17573
+ }
17574
+ return mergeDerivedExpressionSourceKeys(argumentsToAnalyze, scopes, visitedSymbolIds);
17575
+ }
17576
+ return null;
17577
+ };
17578
+ const resolveWriteControlSourceKeys = (assignment, boundaryFunction, scopes) => {
17579
+ const sourceKeys = /* @__PURE__ */ new Set();
17580
+ let currentNode = assignment;
17581
+ while (currentNode.parent && currentNode.parent !== boundaryFunction) {
17582
+ const parentNode = currentNode.parent;
17583
+ if (isNodeOfType(parentNode, "IfStatement")) {
17584
+ if (parentNode.test === currentNode) return null;
17585
+ const testSourceKeys = resolveDerivedExpressionSourceKeys(parentNode.test, scopes, /* @__PURE__ */ new Set());
17586
+ if (!testSourceKeys) return null;
17587
+ for (const testSourceKey of testSourceKeys) sourceKeys.add(testSourceKey);
17588
+ } else if (!isNodeOfType(parentNode, "ExpressionStatement") && !isNodeOfType(parentNode, "BlockStatement")) return null;
17589
+ currentNode = parentNode;
17590
+ }
17591
+ return currentNode.parent === boundaryFunction ? sourceKeys : null;
17592
+ };
17593
+ const isReadOnlyInitialStateUse = (referenceNode, scopes) => {
17594
+ const referenceRoot = findTransparentExpressionRoot(referenceNode);
17595
+ const callExpression = referenceRoot.parent;
17596
+ return isNodeOfType(callExpression, "CallExpression") && callExpression.arguments.some((argument) => argument === referenceRoot) && isReactApiCall(callExpression, "useState", scopes, {
17597
+ allowGlobalReactNamespace: true,
17598
+ allowUnboundBareCalls: true,
17599
+ resolveNamedAliases: true
17600
+ });
17601
+ };
17602
+ const resolveRenderDerivedMutableSourceKeys = (capturedReference, symbol, scopes) => {
17603
+ if (symbol.kind !== "let" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
17604
+ const boundaryFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
17605
+ if (!boundaryFunction) return null;
17606
+ const capturingFunction = findEnclosingFunction$1(capturedReference.identifier);
17607
+ if (!capturingFunction || capturingFunction === boundaryFunction) return null;
17608
+ const sourceKeys = /* @__PURE__ */ new Set();
17609
+ if (symbol.initializer) {
17610
+ const initializerSourceKeys = resolveDerivedExpressionSourceKeys(symbol.initializer, scopes, new Set([symbol.id]));
17611
+ if (!initializerSourceKeys) return null;
17612
+ for (const initializerSourceKey of initializerSourceKeys) sourceKeys.add(initializerSourceKey);
17613
+ }
17614
+ let writeCount = 0;
17615
+ for (const symbolReference of symbol.references) {
17616
+ if (symbolReference.flag === "read") {
17617
+ if (findEnclosingFunction$1(symbolReference.identifier) !== capturingFunction && !isReadOnlyInitialStateUse(symbolReference.identifier, scopes)) return null;
17618
+ continue;
17619
+ }
17620
+ if (symbolReference.flag !== "write") return null;
17621
+ const referenceRoot = findTransparentExpressionRoot(symbolReference.identifier);
17622
+ const assignment = referenceRoot.parent;
17623
+ if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== referenceRoot || findEnclosingFunction$1(referenceRoot) !== boundaryFunction) return null;
17624
+ const assignmentSourceKeys = resolveDerivedExpressionSourceKeys(assignment.right, scopes, new Set([symbol.id]));
17625
+ const controlSourceKeys = resolveWriteControlSourceKeys(assignment, boundaryFunction, scopes);
17626
+ if (!assignmentSourceKeys || !controlSourceKeys) return null;
17627
+ for (const assignmentSourceKey of assignmentSourceKeys) sourceKeys.add(assignmentSourceKey);
17628
+ for (const controlSourceKey of controlSourceKeys) sourceKeys.add(controlSourceKey);
17629
+ writeCount += 1;
17630
+ }
17631
+ return writeCount > 0 && sourceKeys.size > 0 ? sourceKeys : null;
17632
+ };
17197
17633
  const isUseCallbackResultDep = (node, scopes) => {
17198
17634
  const rootSymbol = getRootSymbol(node, scopes);
17199
17635
  const initializer = rootSymbol?.initializer ? unwrapExpression$3(rootSymbol.initializer) : null;
@@ -29421,7 +29857,7 @@ const nextjsNoVercelOgImport = defineRule({
29421
29857
  //#endregion
29422
29858
  //#region src/plugin/rules/a11y/no-access-key.ts
29423
29859
  const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
29424
- const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
29860
+ const isUndefinedIdentifier$1 = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
29425
29861
  const noAccessKey = defineRule({
29426
29862
  id: "no-access-key",
29427
29863
  title: "accessKey attribute used",
@@ -29446,7 +29882,7 @@ const noAccessKey = defineRule({
29446
29882
  if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
29447
29883
  const expression = attributeValue.expression;
29448
29884
  if (!expression || expression.type === "JSXEmptyExpression") return;
29449
- if (isUndefinedIdentifier(expression)) return;
29885
+ if (isUndefinedIdentifier$1(expression)) return;
29450
29886
  context.report({
29451
29887
  node: accessKey,
29452
29888
  message: MESSAGE$39
@@ -30211,6 +30647,12 @@ const isReactNamespaceImportReference = (ref) => Boolean(ref?.resolved?.defs.som
30211
30647
  const importDeclaration = declarationNode.parent;
30212
30648
  return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
30213
30649
  }));
30650
+ const isReactNamespaceReceiver = (analysis, node) => {
30651
+ const receiver = stripParenExpression(node);
30652
+ if (!isNodeOfType(receiver, "Identifier")) return false;
30653
+ const namespaceReference = getRef(analysis, receiver);
30654
+ return namespaceReference?.resolved ? isReactNamespaceImportReference(namespaceReference) : receiver.name === "React";
30655
+ };
30214
30656
  const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
30215
30657
  if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return false;
30216
30658
  const callee = stripParenExpression(declarator.init.callee);
@@ -30219,24 +30661,20 @@ const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
30219
30661
  if (!reference?.resolved) return callee.name === hookName;
30220
30662
  return isReactNamedImportReference(reference, hookName);
30221
30663
  }
30222
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
30223
- const namespaceReference = getRef(analysis, callee.object);
30224
- if (!namespaceReference?.resolved) return callee.object.name === "React";
30225
- return isReactNamespaceImportReference(namespaceReference);
30664
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
30665
+ return isReactNamespaceReceiver(analysis, callee.object);
30226
30666
  };
30227
30667
  const isHookCallee$1 = (analysis, node, hookName) => {
30228
30668
  if (!node) return false;
30229
30669
  if (isNodeOfType(node, "Identifier")) {
30230
30670
  if (node.name === hookName) return true;
30231
30671
  if (isReactNamedImportReference(getRef(analysis, node), hookName)) return true;
30232
- const parent = node.parent;
30233
- if (parent && isNodeOfType(parent, "MemberExpression") && isNodeOfType(parent.object, "Identifier") && parent.object.name === "React" && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
30672
+ const receiverRoot = findTransparentExpressionRoot(node);
30673
+ const parent = receiverRoot.parent;
30674
+ if (parent && isNodeOfType(parent, "MemberExpression") && parent.object === receiverRoot && isReactNamespaceReceiver(analysis, node) && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
30234
30675
  return false;
30235
30676
  }
30236
- if (isNodeOfType(node, "MemberExpression")) {
30237
- const receiver = stripParenExpression(node.object);
30238
- return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
30239
- }
30677
+ if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
30240
30678
  return false;
30241
30679
  };
30242
30680
  const isUseEffect = (node) => {
@@ -30660,7 +31098,88 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
30660
31098
  if (HANDLER_BINDING_NAME_PATTERN.test(bindingName)) return true;
30661
31099
  return isSetterWiredToJsxHandler(componentFunction, bindingName);
30662
31100
  };
30663
- const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
31101
+ const isSynchronousFunction = (functionNode) => {
31102
+ const functionMetadata = functionNode;
31103
+ return functionMetadata.async !== true && functionMetadata.generator !== true;
31104
+ };
31105
+ const findBindingVariable = (analysis, bindingIdentifier) => {
31106
+ for (const scope of analysis.scopeManager.scopes) for (const variable of scope.variables) if (variable.identifiers.includes(bindingIdentifier)) return variable;
31107
+ return null;
31108
+ };
31109
+ const getImmutableFunctionVariable = (analysis, componentFunction, functionNode) => {
31110
+ if (!isSynchronousFunction(functionNode) || !isAstDescendant(functionNode, componentFunction)) return null;
31111
+ const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
31112
+ if (!bindingIdentifier) return null;
31113
+ const variable = findBindingVariable(analysis, bindingIdentifier);
31114
+ if (!variable || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && !reference.init)) return null;
31115
+ const definition = variable.defs[0];
31116
+ if (definition.type === "FunctionName") return definition.node === functionNode ? variable : null;
31117
+ if (definition.type !== "Variable") return null;
31118
+ const declarator = definition.node;
31119
+ if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return null;
31120
+ if (declarator.init === functionNode) return variable;
31121
+ if (isNodeOfType(declarator.init, "CallExpression") && declarator.init.arguments?.[0] === functionNode && isGenuineReactHookDeclarator(analysis, declarator, "useCallback")) return variable;
31122
+ return null;
31123
+ };
31124
+ const getJsxEventValueAttribute = (identifier) => {
31125
+ const expression = findTransparentExpressionRoot(identifier);
31126
+ const expressionContainer = expression.parent;
31127
+ if (!isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== expression) return null;
31128
+ const attribute = expressionContainer.parent;
31129
+ if (!isNodeOfType(attribute, "JSXAttribute")) return null;
31130
+ const attributeName = getJsxAttributeName(attribute.name);
31131
+ return attributeName && isEventHandlerName(attributeName) ? attribute : null;
31132
+ };
31133
+ const getInlineJsxEventCallbackAttribute = (callExpression) => {
31134
+ const callbackFunction = findEnclosingFunction$1(callExpression);
31135
+ if (!callbackFunction || !isSynchronousFunction(callbackFunction)) return null;
31136
+ return getJsxEventValueAttribute(callbackFunction);
31137
+ };
31138
+ const isReactHookDependencyReference = (identifier) => {
31139
+ const expression = findTransparentExpressionRoot(identifier);
31140
+ const dependencyArray = expression.parent;
31141
+ if (!isNodeOfType(dependencyArray, "ArrayExpression") || !(dependencyArray.elements ?? []).includes(expression)) return false;
31142
+ const hookCall = dependencyArray.parent;
31143
+ if (!isNodeOfType(hookCall, "CallExpression") || hookCall.arguments?.[1] !== dependencyArray) return false;
31144
+ const callee = hookCall.callee;
31145
+ if (isNodeOfType(callee, "Identifier")) return /^use[A-Z0-9]/.test(callee.name);
31146
+ return Boolean(isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && /^use[A-Z0-9]/.test(callee.property.name));
31147
+ };
31148
+ const hasReachableJsxEventCallPath = (analysis, context, componentFunction, functionVariable, visitedVariables) => {
31149
+ if (visitedVariables.has(functionVariable)) return false;
31150
+ const nextVisitedVariables = new Set(visitedVariables).add(functionVariable);
31151
+ const callExpressions = [];
31152
+ let hasDirectJsxEventReference = false;
31153
+ for (const reference of functionVariable.references) {
31154
+ if (reference.init) continue;
31155
+ const identifier = reference.identifier;
31156
+ if (reference.isWrite()) return false;
31157
+ const jsxEventValueAttribute = getJsxEventValueAttribute(identifier);
31158
+ if (jsxEventValueAttribute) {
31159
+ if (isNodeReachableWithinFunction(jsxEventValueAttribute, context)) hasDirectJsxEventReference = true;
31160
+ continue;
31161
+ }
31162
+ if (isReactHookDependencyReference(identifier)) continue;
31163
+ const callExpression = getCallExpr(reference);
31164
+ if (!callExpression) return false;
31165
+ const jsxEventCallbackAttribute = getInlineJsxEventCallbackAttribute(callExpression);
31166
+ if (jsxEventCallbackAttribute) {
31167
+ if (isNodeReachableWithinFunction(callExpression, context) && isNodeReachableWithinFunction(jsxEventCallbackAttribute, context)) hasDirectJsxEventReference = true;
31168
+ continue;
31169
+ }
31170
+ callExpressions.push(callExpression);
31171
+ }
31172
+ if (hasDirectJsxEventReference) return true;
31173
+ for (const callExpression of callExpressions) {
31174
+ if (!isNodeReachableWithinFunction(callExpression, context)) continue;
31175
+ const callerFunction = findEnclosingFunction$1(callExpression);
31176
+ if (!callerFunction || callerFunction === componentFunction) continue;
31177
+ const callerVariable = getImmutableFunctionVariable(analysis, componentFunction, callerFunction);
31178
+ if (callerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, callerVariable, nextVisitedVariables)) return true;
31179
+ }
31180
+ return false;
31181
+ };
31182
+ const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, includeDeferredWriters = false) => {
30664
31183
  if (!setterRef.resolved) return false;
30665
31184
  const componentFunction = findEnclosingFunction$1(effectNode);
30666
31185
  if (!componentFunction) return false;
@@ -30669,6 +31188,11 @@ const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters
30669
31188
  const identifier = reference.identifier;
30670
31189
  if (isAstDescendant(identifier, effectNode)) continue;
30671
31190
  if (isIndependentWriterIdentifier(componentFunction, identifier, includeDeferredWriters)) return true;
31191
+ if (!isNodeReachableWithinFunction(identifier, context)) continue;
31192
+ const writerFunction = findEnclosingFunction$1(identifier);
31193
+ if (!writerFunction || writerFunction === componentFunction) continue;
31194
+ const writerVariable = getImmutableFunctionVariable(analysis, componentFunction, writerFunction);
31195
+ if (writerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, writerVariable, /* @__PURE__ */ new Set())) return true;
30672
31196
  }
30673
31197
  return false;
30674
31198
  };
@@ -31558,7 +32082,7 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
31558
32082
  }
31559
32083
  return false;
31560
32084
  };
31561
- const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
32085
+ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFilename) => {
31562
32086
  const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
31563
32087
  if (frames.length === 0) return [];
31564
32088
  const effectHasCleanup = hasCleanup(analysis, effectNode);
@@ -31588,7 +32112,7 @@ const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) =>
31588
32112
  for (const returnedExpression of returnedExpressions) mergeEvidence(valueEvidence, collectValueEvidence(analysis, returnedExpression, updaterFrame, remainingValueCallFrames));
31589
32113
  } else valueEvidence = collectValueEvidence(analysis, writtenValue, frame, remainingValueCallFrames);
31590
32114
  const sourceReferences = [...valueEvidence.sourceReferences].filter((sourceReference) => getUseStateDecl(analysis, sourceReference) !== stateDeclarator);
31591
- const hasIndependentWriter = hasUserInputSetterWriter(setterReference, effectNode, true);
32115
+ const hasIndependentWriter = hasUserInputSetterWriter(analysis, context, setterReference, effectNode, true);
31592
32116
  const doesMatchStateInitializer = matchesStateInitializer(analysis, callExpression, stateDeclarator);
31593
32117
  if (effectHasCleanup && (frame.isDeferred || valueEvidence.hasUnknownSource || valueEvidence.hasDeferredIntroducedValue || valueEvidence.readsExternalValue)) cleanupManagedStateDeclarators.add(stateDeclarator);
31594
32118
  const isRenderKnownCopy = sourceReferences.length > 0 && !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue && !hasIndependentWriter;
@@ -31629,7 +32153,7 @@ const noAdjustStateOnPropChange = defineRule({
31629
32153
  const dependencyReferences = getEffectDepsRefs(analysis, node);
31630
32154
  if (!dependencyReferences) return;
31631
32155
  if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
31632
- for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
32156
+ for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
31633
32157
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
31634
32158
  context.report({
31635
32159
  node: fact.callExpression,
@@ -36203,7 +36727,7 @@ const noDerivedState = defineRule({
36203
36727
  if (!isUseEffect(node)) return;
36204
36728
  const analysis = getProgramAnalysis(node);
36205
36729
  if (!analysis) return;
36206
- for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
36730
+ for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
36207
36731
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
36208
36732
  reportStateWrite(fact.callExpression, fact.stateDeclarator);
36209
36733
  }
@@ -36223,7 +36747,7 @@ const noDerivedStateEffect = defineRule({
36223
36747
  if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
36224
36748
  const analysis = getProgramAnalysis(node);
36225
36749
  if (!analysis) return;
36226
- if (!collectEffectStateWriteFacts(analysis, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
36750
+ if (!collectEffectStateWriteFacts(analysis, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
36227
36751
  context.report({
36228
36752
  node,
36229
36753
  message: "You pay an extra render for state you can derive from other values."
@@ -36699,9 +37223,20 @@ const noDidMountSetState = defineRule({
36699
37223
  }
36700
37224
  });
36701
37225
  //#endregion
37226
+ //#region src/plugin/utils/find-enclosing-class.ts
37227
+ const findEnclosingClass = (node) => {
37228
+ let ancestor = node.parent;
37229
+ while (ancestor) {
37230
+ if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
37231
+ ancestor = ancestor.parent ?? null;
37232
+ }
37233
+ return null;
37234
+ };
37235
+ //#endregion
36702
37236
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
36703
37237
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
36704
37238
  const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
37239
+ const DIFFERENCE_OPERATORS = new Set(["!=", "!=="]);
36705
37240
  const EQUALITY_OPERATORS = new Set([
36706
37241
  "==",
36707
37242
  "===",
@@ -36713,6 +37248,8 @@ const FUNCTION_NODE_TYPES = new Set([
36713
37248
  "FunctionExpression",
36714
37249
  "ArrowFunctionExpression"
36715
37250
  ]);
37251
+ const CLASS_NODE_TYPES = new Set(["ClassDeclaration", "ClassExpression"]);
37252
+ const callbackRefFieldNamesByClass = /* @__PURE__ */ new WeakMap();
36716
37253
  const isLifecycleMethodFunction = (node) => {
36717
37254
  if (!FUNCTION_NODE_TYPES.has(node.type)) return false;
36718
37255
  const parent = node.parent;
@@ -36768,6 +37305,187 @@ const getStaticMemberName = (node) => {
36768
37305
  if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
36769
37306
  return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
36770
37307
  };
37308
+ const getMemberIdentity = (property) => {
37309
+ const propertyName = getPropertyKeyName$2(property);
37310
+ if (propertyName !== void 0) return isNodeOfType(property, "PrivateIdentifier") ? `#${propertyName}` : propertyName;
37311
+ return isNodeOfType(property, "Literal") && typeof property.value === "string" ? property.value : null;
37312
+ };
37313
+ const collectPreviousSourcePaths = (pattern, domain, members, previousSourcePaths) => {
37314
+ if (!pattern) return;
37315
+ const unwrappedPattern = stripParenExpression(pattern);
37316
+ if (isNodeOfType(unwrappedPattern, "Identifier")) {
37317
+ previousSourcePaths.set(unwrappedPattern.name, {
37318
+ domain,
37319
+ members: [...members],
37320
+ source: "previous"
37321
+ });
37322
+ return;
37323
+ }
37324
+ if (isNodeOfType(unwrappedPattern, "AssignmentPattern")) {
37325
+ collectPreviousSourcePaths(unwrappedPattern.left, domain, members, previousSourcePaths);
37326
+ return;
37327
+ }
37328
+ if (!isNodeOfType(unwrappedPattern, "ObjectPattern")) return;
37329
+ for (const property of unwrappedPattern.properties) {
37330
+ if (!isNodeOfType(property, "Property")) continue;
37331
+ const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
37332
+ if (!propertyName) continue;
37333
+ collectPreviousSourcePaths(property.value, domain, [...members, propertyName], previousSourcePaths);
37334
+ }
37335
+ };
37336
+ const getStateSourcePath = (node, previousSourcePaths) => {
37337
+ let currentNode = stripParenExpression(node);
37338
+ const members = [];
37339
+ while (isNodeOfType(currentNode, "MemberExpression")) {
37340
+ const memberName = getStaticMemberName(currentNode);
37341
+ if (!memberName) return null;
37342
+ members.unshift(memberName);
37343
+ currentNode = stripParenExpression(currentNode.object);
37344
+ }
37345
+ if (isNodeOfType(currentNode, "ThisExpression")) {
37346
+ const [domain, ...pathMembers] = members;
37347
+ if (domain !== "props" && domain !== "state") return null;
37348
+ return {
37349
+ domain,
37350
+ members: pathMembers,
37351
+ source: "current"
37352
+ };
37353
+ }
37354
+ if (!isNodeOfType(currentNode, "Identifier")) return null;
37355
+ const previousSourcePath = previousSourcePaths.get(currentNode.name);
37356
+ return previousSourcePath ? {
37357
+ ...previousSourcePath,
37358
+ members: [...previousSourcePath.members, ...members]
37359
+ } : null;
37360
+ };
37361
+ const haveMatchingStateSourcePaths = (left, right) => left.domain === right.domain && left.members.length === right.members.length && left.members.every((member, index) => member === right.members[index]);
37362
+ const collectConjunctiveStateSourceComparisons = (test, previousSourcePaths, comparisons) => {
37363
+ const expression = stripParenExpression(test);
37364
+ if (isNodeOfType(expression, "LogicalExpression") && expression.operator === "&&") {
37365
+ collectConjunctiveStateSourceComparisons(expression.left, previousSourcePaths, comparisons);
37366
+ collectConjunctiveStateSourceComparisons(expression.right, previousSourcePaths, comparisons);
37367
+ return;
37368
+ }
37369
+ if (!isNodeOfType(expression, "BinaryExpression") || !EQUALITY_OPERATORS.has(expression.operator)) return;
37370
+ const leftPath = getStateSourcePath(expression.left, previousSourcePaths);
37371
+ const rightPath = getStateSourcePath(expression.right, previousSourcePaths);
37372
+ if (Boolean(leftPath) === Boolean(rightPath)) return;
37373
+ const path = leftPath ?? rightPath;
37374
+ if (!path) return;
37375
+ comparisons.push({
37376
+ comparedValue: leftPath ? expression.right : expression.left,
37377
+ isDifference: DIFFERENCE_OPERATORS.has(expression.operator),
37378
+ path
37379
+ });
37380
+ };
37381
+ const isHistoricalToCurrentTransitionGuard = (test, previousSourcePaths) => {
37382
+ const expression = stripParenExpression(test);
37383
+ if (isNodeOfType(expression, "LogicalExpression") && expression.operator === "||") return isHistoricalToCurrentTransitionGuard(expression.left, previousSourcePaths) && isHistoricalToCurrentTransitionGuard(expression.right, previousSourcePaths);
37384
+ const comparisons = [];
37385
+ collectConjunctiveStateSourceComparisons(expression, previousSourcePaths, comparisons);
37386
+ return comparisons.some((comparison, index) => comparisons.slice(index + 1).some((candidate) => comparison.path.source !== candidate.path.source && comparison.isDifference !== candidate.isDifference && haveMatchingStateSourcePaths(comparison.path, candidate.path) && areExpressionsStructurallyEqual(comparison.comparedValue, candidate.comparedValue)));
37387
+ };
37388
+ const getThisFieldName = (node) => {
37389
+ const unwrappedNode = stripParenExpression(node);
37390
+ if (!isNodeOfType(unwrappedNode, "MemberExpression") || unwrappedNode.computed === true || !isNodeOfType(stripParenExpression(unwrappedNode.object), "ThisExpression")) return null;
37391
+ return getMemberIdentity(unwrappedNode.property);
37392
+ };
37393
+ const isUndefinedIdentifier = (node) => {
37394
+ const unwrappedNode = stripParenExpression(node);
37395
+ return isNodeOfType(unwrappedNode, "Identifier") && unwrappedNode.name === "undefined";
37396
+ };
37397
+ const isDirectRefParameterValue = (node, parameterSymbolId, scopes) => {
37398
+ const unwrappedNode = stripParenExpression(node);
37399
+ if (isNodeOfType(unwrappedNode, "Identifier")) return scopes.symbolFor(unwrappedNode)?.id === parameterSymbolId;
37400
+ if (!isNodeOfType(unwrappedNode, "LogicalExpression") || unwrappedNode.operator !== "??") return false;
37401
+ const left = stripParenExpression(unwrappedNode.left);
37402
+ return isNodeOfType(left, "Identifier") && scopes.symbolFor(left)?.id === parameterSymbolId && isUndefinedIdentifier(unwrappedNode.right);
37403
+ };
37404
+ const getCallbackRefAssignedFields = (callback, scopes) => {
37405
+ const firstParameter = (callback.params ?? [])[0];
37406
+ if (!firstParameter) return /* @__PURE__ */ new Set();
37407
+ const parameterIdentifier = isNodeOfType(firstParameter, "AssignmentPattern") ? firstParameter.left : firstParameter;
37408
+ if (!isNodeOfType(parameterIdentifier, "Identifier")) return /* @__PURE__ */ new Set();
37409
+ const parameterSymbolId = scopes.symbolFor(parameterIdentifier)?.id;
37410
+ if (parameterSymbolId === void 0) return /* @__PURE__ */ new Set();
37411
+ const body = callback.body;
37412
+ if (!body) return /* @__PURE__ */ new Set();
37413
+ const assignedFieldNames = /* @__PURE__ */ new Set();
37414
+ walkAst(body, (node) => {
37415
+ if (node !== body && (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node) || CLASS_NODE_TYPES.has(node.type))) return false;
37416
+ const assignmentTarget = isNodeOfType(node, "AssignmentExpression") && node.left || isNodeOfType(node, "UpdateExpression") && node.argument || isNodeOfType(node, "UnaryExpression") && node.operator === "delete" && node.argument || null;
37417
+ if (!assignmentTarget) return;
37418
+ const fieldName = getThisFieldName(assignmentTarget);
37419
+ if (!fieldName) return;
37420
+ if (isNodeOfType(node, "AssignmentExpression") && node.operator === "=" && isDirectRefParameterValue(node.right, parameterSymbolId, scopes)) {
37421
+ assignedFieldNames.add(fieldName);
37422
+ return;
37423
+ }
37424
+ assignedFieldNames.delete(fieldName);
37425
+ });
37426
+ return assignedFieldNames;
37427
+ };
37428
+ const getClassMemberCallback = (classNode, memberName) => {
37429
+ const classBody = classNode.body?.body ?? [];
37430
+ for (const member of classBody) {
37431
+ if (!isNodeOfType(member, "MethodDefinition") && !isNodeOfType(member, "PropertyDefinition")) continue;
37432
+ if (member.static === true) continue;
37433
+ const key = member.key;
37434
+ if (getMemberIdentity(key) !== memberName) continue;
37435
+ const value = member.value;
37436
+ return value && FUNCTION_NODE_TYPES.has(value.type) ? value : null;
37437
+ }
37438
+ return null;
37439
+ };
37440
+ const collectCallbackRefFieldsFromExpression = (expression, classNode, fieldNames, scopes) => {
37441
+ const unwrappedExpression = stripParenExpression(expression);
37442
+ if (FUNCTION_NODE_TYPES.has(unwrappedExpression.type)) {
37443
+ for (const fieldName of getCallbackRefAssignedFields(unwrappedExpression, scopes)) fieldNames.add(fieldName);
37444
+ return;
37445
+ }
37446
+ const handlerName = getThisFieldName(unwrappedExpression);
37447
+ if (handlerName) {
37448
+ const callback = getClassMemberCallback(classNode, handlerName);
37449
+ if (callback) for (const fieldName of getCallbackRefAssignedFields(callback, scopes)) fieldNames.add(fieldName);
37450
+ return;
37451
+ }
37452
+ if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
37453
+ collectCallbackRefFieldsFromExpression(unwrappedExpression.consequent, classNode, fieldNames, scopes);
37454
+ collectCallbackRefFieldsFromExpression(unwrappedExpression.alternate, classNode, fieldNames, scopes);
37455
+ return;
37456
+ }
37457
+ if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
37458
+ if (unwrappedExpression.operator !== "&&") collectCallbackRefFieldsFromExpression(unwrappedExpression.left, classNode, fieldNames, scopes);
37459
+ collectCallbackRefFieldsFromExpression(unwrappedExpression.right, classNode, fieldNames, scopes);
37460
+ }
37461
+ };
37462
+ const getCallbackRefFieldNames = (classNode, scopes) => {
37463
+ if (!classNode) return /* @__PURE__ */ new Set();
37464
+ const cachedFieldNames = callbackRefFieldNamesByClass.get(classNode);
37465
+ if (cachedFieldNames) return cachedFieldNames;
37466
+ const fieldNames = /* @__PURE__ */ new Set();
37467
+ const classBody = classNode.body;
37468
+ if (classBody) walkAst(classBody, (node) => {
37469
+ if (node !== classBody && CLASS_NODE_TYPES.has(node.type)) return false;
37470
+ if (!isNodeOfType(node, "JSXAttribute") || !isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "ref" || !node.value || !isNodeOfType(node.value, "JSXExpressionContainer") || !node.value.expression) return;
37471
+ collectCallbackRefFieldsFromExpression(node.value.expression, classNode, fieldNames, scopes);
37472
+ });
37473
+ callbackRefFieldNamesByClass.set(classNode, fieldNames);
37474
+ return fieldNames;
37475
+ };
37476
+ const collectLifecycleWrittenFieldNames = (lifecycleFunction) => {
37477
+ const fieldNames = /* @__PURE__ */ new Set();
37478
+ const body = lifecycleFunction.body;
37479
+ if (!body) return fieldNames;
37480
+ walkAst(body, (node) => {
37481
+ if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
37482
+ const target = isNodeOfType(node, "AssignmentExpression") && node.left || isNodeOfType(node, "UpdateExpression") && node.argument || null;
37483
+ if (!target) return;
37484
+ const fieldName = getThisFieldName(target);
37485
+ if (fieldName) fieldNames.add(fieldName);
37486
+ });
37487
+ return fieldNames;
37488
+ };
36771
37489
  const getThisStateFieldName = (node) => {
36772
37490
  const unwrappedNode = stripParenExpression(node);
36773
37491
  if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
@@ -36785,15 +37503,17 @@ const collectLocalInitializers = (lifecycleFunction) => {
36785
37503
  });
36786
37504
  return initializers;
36787
37505
  };
36788
- const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
37506
+ const derivesFromPostMountValue = (node, localInitializers, callbackRefFieldNames, visitedNames = /* @__PURE__ */ new Set()) => {
36789
37507
  if (readsPostMountValue(node)) return true;
37508
+ const fieldName = getThisFieldName(node);
37509
+ if (fieldName && callbackRefFieldNames.has(fieldName)) return true;
36790
37510
  const referencedNames = /* @__PURE__ */ new Set();
36791
37511
  collectReferenceIdentifierNames(node, referencedNames);
36792
37512
  for (const referencedName of referencedNames) {
36793
37513
  if (visitedNames.has(referencedName)) continue;
36794
37514
  const initializer = localInitializers.get(referencedName);
36795
37515
  if (!initializer) continue;
36796
- if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
37516
+ if (derivesFromPostMountValue(initializer, localInitializers, callbackRefFieldNames, new Set([...visitedNames, referencedName]))) return true;
36797
37517
  }
36798
37518
  return false;
36799
37519
  };
@@ -36807,50 +37527,84 @@ const getSetStateFieldValue = (setStateCall, fieldName) => {
36807
37527
  }
36808
37528
  return null;
36809
37529
  };
36810
- const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
36811
- let qualifies = false;
36812
- walkAst(test, (node) => {
36813
- if (qualifies) return false;
36814
- if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
36815
- const leftFieldName = getThisStateFieldName(node.left);
36816
- const rightFieldName = getThisStateFieldName(node.right);
36817
- const fieldName = leftFieldName ?? rightFieldName;
36818
- const comparedValue = leftFieldName ? node.right : node.left;
36819
- if (!fieldName || !leftFieldName && !rightFieldName) return;
36820
- const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
36821
- if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
36822
- if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
36823
- qualifies = true;
36824
- return false;
36825
- });
36826
- return qualifies;
36827
- };
36828
- const isDiffGuardTest = (test, paramNames, derivedNames) => {
36829
- if (referencesAnyName(test, paramNames)) return true;
36830
- let qualifies = false;
36831
- walkAst(test, (node) => {
36832
- if (qualifies) return false;
36833
- if (!isNodeOfType(node, "BinaryExpression")) return;
36834
- if (!EQUALITY_OPERATORS.has(node.operator)) return;
36835
- if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames) && (referencesAnyName(node.left, derivedNames) || referencesAnyName(node.right, derivedNames))) {
36836
- qualifies = true;
36837
- return false;
36838
- }
36839
- });
36840
- return qualifies;
37530
+ const isConvergentPostMountGuard = (test, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch) => {
37531
+ const expression = stripParenExpression(test);
37532
+ if (isNodeOfType(expression, "LogicalExpression")) {
37533
+ if (expression.operator !== "&&" && expression.operator !== "||") return false;
37534
+ const leftIsConvergent = isConvergentPostMountGuard(expression.left, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch);
37535
+ const rightIsConvergent = isConvergentPostMountGuard(expression.right, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch);
37536
+ return isTruthfulBranch && expression.operator === "||" || !isTruthfulBranch && expression.operator === "&&" ? leftIsConvergent && rightIsConvergent : leftIsConvergent || rightIsConvergent;
37537
+ }
37538
+ if (!isNodeOfType(expression, "BinaryExpression") || !(isTruthfulBranch ? DIFFERENCE_OPERATORS.has(expression.operator) : EQUALITY_OPERATORS.has(expression.operator) && !DIFFERENCE_OPERATORS.has(expression.operator))) return false;
37539
+ const leftFieldName = getThisStateFieldName(expression.left);
37540
+ const rightFieldName = getThisStateFieldName(expression.right);
37541
+ const fieldName = leftFieldName ?? rightFieldName;
37542
+ const comparedValue = leftFieldName ? expression.right : expression.left;
37543
+ if (!fieldName) return false;
37544
+ const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
37545
+ if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return false;
37546
+ return isUndefinedIdentifier(comparedValue) || derivesFromPostMountValue(comparedValue, localInitializers, callbackRefFieldNames);
37547
+ };
37548
+ const containsPositiveStateFieldTest = (test, fieldName) => {
37549
+ const unwrappedTest = stripParenExpression(test);
37550
+ if (getThisStateFieldName(unwrappedTest) === fieldName) return true;
37551
+ return isNodeOfType(unwrappedTest, "LogicalExpression") && unwrappedTest.operator === "&&" && (containsPositiveStateFieldTest(unwrappedTest.left, fieldName) || containsPositiveStateFieldTest(unwrappedTest.right, fieldName));
37552
+ };
37553
+ const isConvergentUndefinedClearGuard = (test, setStateCall) => {
37554
+ if (!isNodeOfType(setStateCall, "CallExpression")) return false;
37555
+ const argument = setStateCall.arguments?.[0];
37556
+ if (!argument || !isNodeOfType(argument, "ObjectExpression")) return false;
37557
+ for (const property of argument.properties ?? []) {
37558
+ if (!isNodeOfType(property, "Property") || property.computed === true || !isUndefinedIdentifier(property.value)) continue;
37559
+ const fieldName = isNodeOfType(property.key, "Identifier") && property.key.name || isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" && property.key.value || null;
37560
+ if (fieldName && containsPositiveStateFieldTest(test, fieldName)) return true;
37561
+ }
37562
+ return false;
37563
+ };
37564
+ const isDiffGuardTest = (test, paramNames, derivedNames, isTruthfulBranch) => {
37565
+ const expression = stripParenExpression(test);
37566
+ if (isNodeOfType(expression, "LogicalExpression")) {
37567
+ if (expression.operator !== "&&" && expression.operator !== "||") return false;
37568
+ const leftIsDiffGuard = isDiffGuardTest(expression.left, paramNames, derivedNames, isTruthfulBranch);
37569
+ const rightIsDiffGuard = isDiffGuardTest(expression.right, paramNames, derivedNames, isTruthfulBranch);
37570
+ return isTruthfulBranch && expression.operator === "||" || !isTruthfulBranch && expression.operator === "&&" ? leftIsDiffGuard && rightIsDiffGuard : leftIsDiffGuard || rightIsDiffGuard;
37571
+ }
37572
+ if (!isNodeOfType(expression, "BinaryExpression") || !(isTruthfulBranch ? DIFFERENCE_OPERATORS.has(expression.operator) : EQUALITY_OPERATORS.has(expression.operator) && !DIFFERENCE_OPERATORS.has(expression.operator))) return false;
37573
+ return isStatefulOperand(expression.left, paramNames, derivedNames) && isStatefulOperand(expression.right, paramNames, derivedNames) && (referencesAnyName(expression.left, paramNames) || referencesAnyName(expression.right, paramNames) || referencesAnyName(expression.left, derivedNames) || referencesAnyName(expression.right, derivedNames));
36841
37574
  };
36842
- const isInsideDiffGuard = (setStateCall) => {
37575
+ const isInsideDiffGuard = (setStateCall, scopes) => {
36843
37576
  const lifecycleFunction = findEnclosingLifecycleFunction(setStateCall);
36844
37577
  if (!lifecycleFunction) return false;
36845
37578
  const paramNames = /* @__PURE__ */ new Set();
36846
- for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
37579
+ const parameters = lifecycleFunction.params ?? [];
37580
+ for (const param of parameters) collectPatternNames(param, paramNames);
37581
+ const previousSourcePaths = /* @__PURE__ */ new Map();
37582
+ const [previousPropsParameter, previousStateParameter] = parameters;
37583
+ collectPreviousSourcePaths(previousPropsParameter, "props", [], previousSourcePaths);
37584
+ collectPreviousSourcePaths(previousStateParameter, "state", [], previousSourcePaths);
36847
37585
  const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
36848
37586
  const localInitializers = collectLocalInitializers(lifecycleFunction);
37587
+ const lifecycleWrittenFieldNames = collectLifecycleWrittenFieldNames(lifecycleFunction);
37588
+ const callbackRefFieldNames = new Set([...getCallbackRefFieldNames(findEnclosingClass(lifecycleFunction), scopes)].filter((fieldName) => !lifecycleWrittenFieldNames.has(fieldName)));
36849
37589
  let child = setStateCall;
36850
37590
  let ancestor = setStateCall.parent;
36851
37591
  while (ancestor && ancestor !== lifecycleFunction) {
36852
- const guardTest = isNodeOfType(ancestor, "IfStatement") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "ConditionalExpression") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right && ancestor.left || null;
36853
- if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers))) return true;
37592
+ let guardTest = null;
37593
+ let isTruthfulBranch = true;
37594
+ if (isNodeOfType(ancestor, "IfStatement")) {
37595
+ if (child === ancestor.consequent) guardTest = ancestor.test;
37596
+ else if (child === ancestor.alternate) {
37597
+ guardTest = ancestor.test;
37598
+ isTruthfulBranch = false;
37599
+ }
37600
+ } else if (isNodeOfType(ancestor, "ConditionalExpression")) {
37601
+ if (child === ancestor.consequent) guardTest = ancestor.test;
37602
+ else if (child === ancestor.alternate) {
37603
+ guardTest = ancestor.test;
37604
+ isTruthfulBranch = false;
37605
+ }
37606
+ } else if (isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right) guardTest = ancestor.left;
37607
+ if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames, isTruthfulBranch) || isTruthfulBranch && isHistoricalToCurrentTransitionGuard(guardTest, previousSourcePaths) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch) || isTruthfulBranch && isConvergentUndefinedClearGuard(guardTest, setStateCall))) return true;
36854
37608
  child = ancestor;
36855
37609
  ancestor = ancestor.parent ?? null;
36856
37610
  }
@@ -36872,7 +37626,7 @@ const noDidUpdateSetState = defineRule({
36872
37626
  if (!isNodeOfType(stripParenExpression(node.callee.object), "ThisExpression")) return;
36873
37627
  if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
36874
37628
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
36875
- if (isInsideDiffGuard(node)) return;
37629
+ if (isInsideDiffGuard(node, context.scopes)) return;
36876
37630
  context.report({
36877
37631
  node: node.callee,
36878
37632
  message: MESSAGE$27
@@ -39944,6 +40698,17 @@ const DOM_MEASUREMENT_NAMES = new Set([
39944
40698
  "scrollHeight"
39945
40699
  ]);
39946
40700
  const MEASUREMENT_HELPER_CALLEE_PATTERN = /^(?:get|measure|read)\w*(?:Width|Height|Rect|Rects|Size|Bounds|Position)$/;
40701
+ const IMPERATIVE_DOM_MUTATION_NAMES = new Set([
40702
+ "blur",
40703
+ "focus",
40704
+ "restoreSelection",
40705
+ "scroll",
40706
+ "scrollBy",
40707
+ "scrollIntoView",
40708
+ "scrollTo",
40709
+ "setRangeText",
40710
+ "setSelectionRange"
40711
+ ]);
39947
40712
  const subtreeReadsDomMeasurement = (root) => {
39948
40713
  if (!root) return false;
39949
40714
  let found = false;
@@ -39962,29 +40727,66 @@ const subtreeReadsDomMeasurement = (root) => {
39962
40727
  });
39963
40728
  return found;
39964
40729
  };
39965
- const collectMeasuringFunctionNames = (program) => {
40730
+ const collectFunctionNamesMatchingBody = (program, matchesBody) => {
39966
40731
  const names = /* @__PURE__ */ new Set();
39967
40732
  walkAst(program, (child) => {
39968
40733
  if (isNodeOfType(child, "FunctionDeclaration")) {
39969
- if (child.id && isNodeOfType(child.id, "Identifier") && subtreeReadsDomMeasurement(child.body)) names.add(child.id.name);
40734
+ if (child.id && isNodeOfType(child.id, "Identifier") && matchesBody(child.body)) names.add(child.id.name);
39970
40735
  return;
39971
40736
  }
39972
40737
  if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier")) return;
39973
40738
  let functionValue = child.init;
39974
40739
  if (functionValue && isNodeOfType(functionValue, "CallExpression") && isNodeOfType(functionValue.callee, "Identifier") && /^use[A-Z]/.test(functionValue.callee.name)) functionValue = functionValue.arguments?.[0];
39975
- if (functionValue && isFunctionLike$1(functionValue) && subtreeReadsDomMeasurement(functionValue.body)) names.add(child.id.name);
40740
+ if (functionValue && isFunctionLike$1(functionValue) && matchesBody(functionValue.body)) names.add(child.id.name);
39976
40741
  });
39977
40742
  return names;
39978
40743
  };
39979
- const callsAnyName = (root, names) => {
39980
- if (!root || names.size === 0) return false;
40744
+ const collectMeasuringFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeReadsDomMeasurement);
40745
+ const subtreeMutatesDomImperatively = (root) => {
40746
+ if (!root || isFunctionLike$1(root)) return false;
39981
40747
  let found = false;
39982
40748
  walkAst(root, (child) => {
39983
40749
  if (found) return false;
40750
+ if (child !== root && isFunctionLike$1(child)) return false;
40751
+ if (!isNodeOfType(child, "CallExpression")) return;
40752
+ const callee = stripParenExpression(child.callee);
40753
+ const propertyName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
40754
+ if (propertyName !== null && IMPERATIVE_DOM_MUTATION_NAMES.has(propertyName)) {
40755
+ found = true;
40756
+ return false;
40757
+ }
40758
+ });
40759
+ return found;
40760
+ };
40761
+ const collectImperativeDomFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeMutatesDomImperatively);
40762
+ const callsAnyName = (root, names, shouldSkipNestedFunctions = false) => {
40763
+ if (!root || names.size === 0 || shouldSkipNestedFunctions && isFunctionLike$1(root)) return false;
40764
+ let found = false;
40765
+ walkAst(root, (child) => {
40766
+ if (found) return false;
40767
+ if (shouldSkipNestedFunctions && child !== root && isFunctionLike$1(child)) return false;
39984
40768
  if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && names.has(child.callee.name)) found = true;
39985
40769
  });
39986
40770
  return found;
39987
40771
  };
40772
+ const isFollowedByImperativeDomMutation = (call, imperativeDomFunctionNames) => {
40773
+ let statement = call;
40774
+ let parent = statement.parent;
40775
+ while (parent) {
40776
+ const statements = isNodeOfType(parent, "BlockStatement") || isNodeOfType(parent, "Program") || isNodeOfType(parent, "StaticBlock") ? parent.body : isNodeOfType(parent, "SwitchCase") ? parent.consequent : null;
40777
+ if (statements) {
40778
+ const statementIndex = statements.findIndex((siblingStatement) => siblingStatement === statement);
40779
+ if (statementIndex >= 0) {
40780
+ const nextStatement = statements[statementIndex + 1];
40781
+ return subtreeMutatesDomImperatively(nextStatement) || callsAnyName(nextStatement, imperativeDomFunctionNames, true);
40782
+ }
40783
+ }
40784
+ if (isFunctionLike$1(parent) || parent.type.endsWith("Statement") && !isNodeOfType(parent, "ExpressionStatement")) return false;
40785
+ statement = parent;
40786
+ parent = parent.parent;
40787
+ }
40788
+ return false;
40789
+ };
39988
40790
  const isInsideStartViewTransition = (node) => {
39989
40791
  let cursor = node.parent;
39990
40792
  while (cursor) {
@@ -40025,11 +40827,12 @@ const importsImperativeDomLibrary = (program) => {
40025
40827
  };
40026
40828
  const hasExemptFlushSyncCall = (program, localName) => {
40027
40829
  const measuringFunctionNames = collectMeasuringFunctionNames(program);
40830
+ const imperativeDomFunctionNames = collectImperativeDomFunctionNames(program);
40028
40831
  let exempt = false;
40029
40832
  walkAst(program, (child) => {
40030
40833
  if (exempt) return false;
40031
40834
  if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "Identifier") || child.callee.name !== localName) return;
40032
- if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames)) {
40835
+ if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames) || isFollowedByImperativeDomMutation(child, imperativeDomFunctionNames)) {
40033
40836
  exempt = true;
40034
40837
  return false;
40035
40838
  }
@@ -40594,41 +41397,223 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
40594
41397
  if (leftResult === false && rightResult === false) return false;
40595
41398
  return null;
40596
41399
  };
40597
- const readHydrationConditionResult = (expression, context, runtime) => {
41400
+ const readHydrationConditionResult = (expression, context, runtime, state) => {
40598
41401
  const unwrappedExpression = stripParenExpression(expression);
40599
41402
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
40600
41403
  if (predicateMatch) return predicateMatch[`${runtime}Result`];
40601
41404
  const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
40602
41405
  if (staticResult !== null) return staticResult;
41406
+ const expressionSymbol = isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression) : null;
41407
+ const parameterValue = expressionSymbol ? state.parameterValuesBySymbolId.get(expressionSymbol.id) : null;
41408
+ if (expressionSymbol && parameterValue && !state.visitedSymbolIds.has(expressionSymbol.id)) {
41409
+ state.visitedSymbolIds.add(expressionSymbol.id);
41410
+ const result = readHydrationConditionResult(parameterValue, context, runtime, state);
41411
+ state.visitedSymbolIds.delete(expressionSymbol.id);
41412
+ return result;
41413
+ }
41414
+ if (expressionSymbol && expressionSymbol.kind === "const" && expressionSymbol.initializer && expressionSymbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(expressionSymbol.id)) {
41415
+ state.visitedSymbolIds.add(expressionSymbol.id);
41416
+ const result = readHydrationConditionResult(expressionSymbol.initializer, context, runtime, state);
41417
+ state.visitedSymbolIds.delete(expressionSymbol.id);
41418
+ return result;
41419
+ }
41420
+ if (isNodeOfType(unwrappedExpression, "CallExpression")) {
41421
+ const callArguments = unwrappedExpression.arguments ?? [];
41422
+ if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
41423
+ allowGlobalReactNamespace: true,
41424
+ resolveNamedAliases: true
41425
+ })) {
41426
+ const callbackArgument = callArguments[0];
41427
+ if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
41428
+ const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
41429
+ return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? readHydrationFunctionResult(callbackFunction, context, runtime, state) : null;
41430
+ }
41431
+ const callee = stripParenExpression(unwrappedExpression.callee);
41432
+ 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);
41433
+ const helperFunction = resolveExactLocalFunction(callee, context.scopes);
41434
+ 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;
41435
+ const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
41436
+ for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
41437
+ const parameter = helperFunction.params[parameterIndex];
41438
+ const argument = callArguments[parameterIndex];
41439
+ if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
41440
+ const parameterSymbol = context.scopes.symbolFor(parameter);
41441
+ if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
41442
+ }
41443
+ return readHydrationFunctionResult(helperFunction, context, runtime, {
41444
+ ...state,
41445
+ parameterValuesBySymbolId
41446
+ });
41447
+ }
40603
41448
  if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
40604
- const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
41449
+ const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
40605
41450
  return argumentResult === null ? null : !argumentResult;
40606
41451
  }
40607
41452
  if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
40608
- return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
41453
+ return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime, state), readHydrationConditionResult(unwrappedExpression.right, context, runtime, state));
40609
41454
  };
40610
- const matchHydrationCondition = (expression, context) => {
41455
+ const readHydrationStatementResult = (statement, context, runtime, state) => {
41456
+ if (isNodeOfType(statement, "ReturnStatement")) return {
41457
+ didReturn: true,
41458
+ value: statement.argument ? readHydrationConditionResult(statement.argument, context, runtime, state) : null
41459
+ };
41460
+ if (isNodeOfType(statement, "BlockStatement")) {
41461
+ for (const childStatement of statement.body) {
41462
+ const result = readHydrationStatementResult(childStatement, context, runtime, state);
41463
+ if (result.didReturn) return result;
41464
+ if (statementAlwaysExits(childStatement)) break;
41465
+ }
41466
+ return {
41467
+ didReturn: false,
41468
+ value: null
41469
+ };
41470
+ }
41471
+ if (!isNodeOfType(statement, "IfStatement")) return {
41472
+ didReturn: false,
41473
+ value: null
41474
+ };
41475
+ const conditionResult = readHydrationConditionResult(statement.test, context, runtime, state);
41476
+ if (conditionResult !== null) {
41477
+ const selectedBranch = conditionResult ? statement.consequent : statement.alternate;
41478
+ return selectedBranch ? readHydrationStatementResult(selectedBranch, context, runtime, state) : {
41479
+ didReturn: false,
41480
+ value: null
41481
+ };
41482
+ }
41483
+ const consequentResult = readHydrationStatementResult(statement.consequent, context, runtime, state);
41484
+ const alternateResult = statement.alternate ? readHydrationStatementResult(statement.alternate, context, runtime, state) : {
41485
+ didReturn: false,
41486
+ value: null
41487
+ };
41488
+ return consequentResult.didReturn && alternateResult.didReturn && consequentResult.value !== null && consequentResult.value === alternateResult.value ? consequentResult : {
41489
+ didReturn: consequentResult.didReturn || alternateResult.didReturn,
41490
+ value: null
41491
+ };
41492
+ };
41493
+ const readHydrationFunctionResult = (functionNode, context, runtime, state) => {
41494
+ if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
41495
+ state.visitedFunctionNodes.add(functionNode);
41496
+ const result = isNodeOfType(functionNode.body, "BlockStatement") ? readHydrationStatementResult(functionNode.body, context, runtime, state).value : readHydrationConditionResult(functionNode.body, context, runtime, state);
41497
+ state.visitedFunctionNodes.delete(functionNode);
41498
+ return result;
41499
+ };
41500
+ const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, scopes) => {
41501
+ const left = stripParenExpression(leftExpression);
41502
+ const right = stripParenExpression(rightExpression);
41503
+ if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
41504
+ const leftSymbol = scopes.symbolFor(left);
41505
+ const rightSymbol = scopes.symbolFor(right);
41506
+ return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
41507
+ }
41508
+ if (isNodeOfType(left, "MemberExpression") && isNodeOfType(right, "MemberExpression")) return doEquivalentExpressionBindingsMatch(left.object, right.object, scopes) && (!left.computed || doEquivalentExpressionBindingsMatch(left.property, right.property, scopes));
41509
+ if (isNodeOfType(left, "CallExpression") && isNodeOfType(right, "CallExpression")) {
41510
+ const rightArguments = right.arguments ?? [];
41511
+ return doEquivalentExpressionBindingsMatch(left.callee, right.callee, scopes) && (left.arguments ?? []).every((argument, index) => {
41512
+ const rightArgument = rightArguments[index];
41513
+ return Boolean(rightArgument && doEquivalentExpressionBindingsMatch(argument, rightArgument, scopes));
41514
+ });
41515
+ }
41516
+ return true;
41517
+ };
41518
+ const areHelperReturnValuesEquivalent = (leftValue, rightValue, context) => {
41519
+ if (areExpressionsStructurallyEqual(leftValue, rightValue)) return doEquivalentExpressionBindingsMatch(leftValue, rightValue, context.scopes);
41520
+ const leftBoolean = readInitialStateBoolean(leftValue, context.scopes);
41521
+ const rightBoolean = readInitialStateBoolean(rightValue, context.scopes);
41522
+ return leftBoolean !== null && rightBoolean !== null && leftBoolean === rightBoolean;
41523
+ };
41524
+ const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
41525
+ const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
41526
+ return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
41527
+ };
41528
+ const matchHydrationConditionInternal = (expression, context, state) => {
40611
41529
  const unwrappedExpression = stripParenExpression(expression);
40612
41530
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
40613
41531
  if (predicateMatch) return {
40614
41532
  predicateMatch,
40615
41533
  predicateNode: unwrappedExpression
40616
41534
  };
40617
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
40618
- if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
40619
- const leftMatch = matchHydrationCondition(unwrappedExpression.left, context);
40620
- const rightMatch = matchHydrationCondition(unwrappedExpression.right, context);
40621
- if (leftMatch && rightMatch) {
40622
- const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client");
40623
- const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server");
40624
- return clientResult !== null && serverResult !== null && clientResult !== serverResult ? leftMatch : null;
41535
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
41536
+ const symbol = context.scopes.symbolFor(unwrappedExpression);
41537
+ const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
41538
+ if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
41539
+ state.visitedSymbolIds.add(symbol.id);
41540
+ const match = matchHydrationConditionInternal(parameterValue, context, state);
41541
+ state.visitedSymbolIds.delete(symbol.id);
41542
+ return match;
41543
+ }
41544
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
41545
+ state.visitedSymbolIds.add(symbol.id);
41546
+ const match = matchHydrationConditionInternal(symbol.initializer, context, state);
41547
+ state.visitedSymbolIds.delete(symbol.id);
41548
+ return match;
41549
+ }
41550
+ if (isNodeOfType(unwrappedExpression, "CallExpression")) {
41551
+ const callArguments = unwrappedExpression.arguments ?? [];
41552
+ if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
41553
+ allowGlobalReactNamespace: true,
41554
+ resolveNamedAliases: true
41555
+ })) {
41556
+ const callbackArgument = callArguments[0];
41557
+ if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
41558
+ const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
41559
+ return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionResult(callbackFunction, context, state) : null;
41560
+ }
41561
+ const callee = stripParenExpression(unwrappedExpression.callee);
41562
+ if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return matchHydrationConditionInternal(callArguments[0], context, state);
41563
+ const helperFunction = resolveExactLocalFunction(callee, context.scopes);
41564
+ 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;
41565
+ const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
41566
+ for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
41567
+ const parameter = helperFunction.params[parameterIndex];
41568
+ const argument = callArguments[parameterIndex];
41569
+ if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
41570
+ const parameterSymbol = context.scopes.symbolFor(parameter);
41571
+ if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
41572
+ }
41573
+ return matchHydrationFunctionResult(helperFunction, context, {
41574
+ ...state,
41575
+ parameterValuesBySymbolId
41576
+ });
40625
41577
  }
41578
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
41579
+ if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
41580
+ const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
41581
+ const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
40626
41582
  const nestedMatch = leftMatch ?? rightMatch;
40627
41583
  if (!nestedMatch) return null;
40628
- const otherResult = readInitialStateBoolean(leftMatch ? unwrappedExpression.right : unwrappedExpression.left, context.scopes);
40629
- if (unwrappedExpression.operator === "&&" && otherResult === false || unwrappedExpression.operator === "||" && otherResult === true) return null;
40630
- return nestedMatch;
41584
+ const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client", state);
41585
+ const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server", state);
41586
+ return clientResult !== null && serverResult !== null && clientResult === serverResult ? null : nestedMatch;
41587
+ };
41588
+ const matchHydrationReturningStatement = (statement, context, state) => {
41589
+ if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? matchHydrationConditionInternal(statement.argument, context, state) : null;
41590
+ if (isNodeOfType(statement, "IfStatement")) {
41591
+ const conditionMatch = matchHydrationConditionInternal(statement.test, context, state);
41592
+ const consequentValues = getReturnedValues(statement.consequent);
41593
+ const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
41594
+ if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
41595
+ return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
41596
+ }
41597
+ if (!isNodeOfType(statement, "BlockStatement")) return null;
41598
+ for (const childStatement of statement.body) {
41599
+ const match = matchHydrationReturningStatement(childStatement, context, state);
41600
+ if (match) return match;
41601
+ if (statementAlwaysExits(childStatement)) break;
41602
+ }
41603
+ return null;
40631
41604
  };
41605
+ const matchHydrationFunctionResult = (functionNode, context, state) => {
41606
+ if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
41607
+ state.visitedFunctionNodes.add(functionNode);
41608
+ const match = isNodeOfType(functionNode.body, "BlockStatement") ? matchHydrationReturningStatement(functionNode.body, context, state) : matchHydrationConditionInternal(functionNode.body, context, state);
41609
+ state.visitedFunctionNodes.delete(functionNode);
41610
+ return match;
41611
+ };
41612
+ const matchHydrationCondition = (expression, context) => matchHydrationConditionInternal(expression, context, {
41613
+ parameterValuesBySymbolId: /* @__PURE__ */ new Map(),
41614
+ visitedFunctionNodes: /* @__PURE__ */ new Set(),
41615
+ visitedSymbolIds: /* @__PURE__ */ new Set()
41616
+ });
40632
41617
  const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
40633
41618
  const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
40634
41619
  if (!leftNode || !rightNode) return leftNode === rightNode;
@@ -40771,17 +41756,17 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
40771
41756
  const { predicateMatch, predicateNode } = conditionMatch;
40772
41757
  if (reportedNodes.has(predicateNode)) return;
40773
41758
  if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
40774
- const componentOrHookNode = findRenderPhaseComponentOrHook(predicateNode, context.scopes);
41759
+ const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes);
40775
41760
  if (!componentOrHookNode) return;
40776
41761
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
40777
- if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
41762
+ if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes)) return;
40778
41763
  if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
40779
- const attribute = findEnclosingJsxAttribute(predicateNode);
41764
+ const attribute = findEnclosingJsxAttribute(conditionNode);
40780
41765
  if (!attribute || isEventHandlerAttribute(attribute)) return;
40781
41766
  }
40782
- if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
40783
- if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
40784
- const openingElement = findEnclosingJsxOpeningElement(predicateNode);
41767
+ if (fileIsEmailTemplate || isGatedByFalsyInitialState(conditionNode, context.scopes)) return;
41768
+ if (isAfterClientOnlyEarlyReturn(conditionNode, componentOrHookNode, context.scopes)) return;
41769
+ const openingElement = findEnclosingJsxOpeningElement(conditionNode);
40785
41770
  if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
40786
41771
  if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
40787
41772
  if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
@@ -41163,7 +42148,7 @@ const noInitializeState = defineRule({
41163
42148
  if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
41164
42149
  const analysis = getProgramAnalysis(node);
41165
42150
  if (!analysis) return;
41166
- for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
42151
+ for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
41167
42152
  if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
41168
42153
  const stateName = getStateName(fact.stateDeclarator);
41169
42154
  context.report({
@@ -41521,7 +42506,8 @@ const noJsxElementType = defineRule({
41521
42506
  create: (context) => {
41522
42507
  let isJsxImported = false;
41523
42508
  const flaggedAnnotations = [];
41524
- const checkReturnType = (returnType) => {
42509
+ const collectComponentReturnType = (functionNode, returnType) => {
42510
+ if (!(isNodeOfType(functionNode, "TSDeclareFunction") ? Boolean(functionNode.id && isReactComponentName(functionNode.id.name)) : isComponentFunction$1(functionNode))) return;
41525
42511
  const typeAnnotation = extractReturnTypeAnnotation(returnType);
41526
42512
  if (!typeAnnotation) return;
41527
42513
  if (isJsxElementTypeReference(typeAnnotation)) flaggedAnnotations.push(typeAnnotation);
@@ -41531,19 +42517,16 @@ const noJsxElementType = defineRule({
41531
42517
  if (isJsxImportBinding(node)) isJsxImported = true;
41532
42518
  },
41533
42519
  FunctionDeclaration(node) {
41534
- checkReturnType(node.returnType);
42520
+ collectComponentReturnType(node, node.returnType);
41535
42521
  },
41536
42522
  ArrowFunctionExpression(node) {
41537
- checkReturnType(node.returnType);
42523
+ collectComponentReturnType(node, node.returnType);
41538
42524
  },
41539
42525
  FunctionExpression(node) {
41540
- checkReturnType(node.returnType);
42526
+ collectComponentReturnType(node, node.returnType);
41541
42527
  },
41542
42528
  TSDeclareFunction(node) {
41543
- checkReturnType(node.returnType);
41544
- },
41545
- TSMethodSignature(node) {
41546
- checkReturnType(node.returnType);
42529
+ collectComponentReturnType(node, node.returnType);
41547
42530
  },
41548
42531
  "Program:exit"() {
41549
42532
  if (isJsxImported) return;
@@ -44570,6 +45553,114 @@ const DATA_SINK_METHOD_NAMES = new Set([
44570
45553
  "deserialize"
44571
45554
  ]);
44572
45555
  //#endregion
45556
+ //#region src/plugin/utils/get-transparent-react-callback-wrapper-argument.ts
45557
+ const getTransparentReactCallbackWrapperArgument = (initializer, resultSymbol, scopes) => {
45558
+ const callExpression = stripParenExpression(initializer);
45559
+ if (!isNodeOfType(callExpression, "CallExpression")) return null;
45560
+ const callbackArgument = callExpression.arguments[0];
45561
+ if (!callbackArgument) return null;
45562
+ if (resultSymbol && symbolHasReactUseEffectEventOrigin(resultSymbol, scopes)) return callbackArgument;
45563
+ return isReactApiCall(callExpression, "useCallback", scopes, {
45564
+ allowGlobalReactNamespace: true,
45565
+ allowUnboundBareCalls: true
45566
+ }) ? callbackArgument : null;
45567
+ };
45568
+ //#endregion
45569
+ //#region src/plugin/rules/state-and-effects/utils/resolve-parent-callback-provenance.ts
45570
+ const getDeclarationKind$1 = (declarator) => {
45571
+ const declaration = declarator.parent;
45572
+ return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
45573
+ };
45574
+ const hasMutableBindingWrite$2 = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
45575
+ const mergeRequiredBranches = (leftNames, rightNames) => {
45576
+ if (!leftNames || !rightNames) return null;
45577
+ return new Set([...leftNames, ...rightNames]);
45578
+ };
45579
+ const getPropReferenceName = (analysis, identifier) => {
45580
+ if (!isNodeOfType(identifier, "Identifier")) return null;
45581
+ const reference = getRef(analysis, identifier);
45582
+ if (!reference || !isProp(analysis, reference) || isWholePropsObjectReference(analysis, reference)) return null;
45583
+ const bindingIdentifier = (reference.resolved?.defs.find((definition) => definition.type === "Parameter"))?.name;
45584
+ return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? identifier.name;
45585
+ };
45586
+ const getSingleConstDeclarator = (reference) => {
45587
+ if (!reference.resolved || hasMutableBindingWrite$2(reference)) return null;
45588
+ const declarators = reference.resolved.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
45589
+ if (declarators.length !== 1) return null;
45590
+ const declarator = declarators[0];
45591
+ if (!declarator || getDeclarationKind$1(declarator) !== "const") return null;
45592
+ return declarator;
45593
+ };
45594
+ const resolveParentCallbackPropNames = (analysis, expression, scopes, visitedReferences, allowFunctionForwarder = false) => {
45595
+ const unwrappedExpression = stripParenExpression(expression);
45596
+ if (isFunctionLike$1(unwrappedExpression)) {
45597
+ if (!allowFunctionForwarder || Boolean(unwrappedExpression.async)) return null;
45598
+ const callbackNames = /* @__PURE__ */ new Set();
45599
+ walkInsideStatementBlocks(unwrappedExpression.body, (child) => {
45600
+ if (!isNodeOfType(child, "CallExpression")) return;
45601
+ const resolvedNames = resolveParentCallbackPropNames(analysis, child.callee, scopes, new Set(visitedReferences), false);
45602
+ if (!resolvedNames) return;
45603
+ for (const resolvedName of resolvedNames) callbackNames.add(resolvedName);
45604
+ });
45605
+ return callbackNames.size > 0 ? callbackNames : null;
45606
+ }
45607
+ if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return mergeRequiredBranches(resolveParentCallbackPropNames(analysis, unwrappedExpression.consequent, scopes, new Set(visitedReferences), false), resolveParentCallbackPropNames(analysis, unwrappedExpression.alternate, scopes, new Set(visitedReferences), false));
45608
+ if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return mergeRequiredBranches(resolveParentCallbackPropNames(analysis, unwrappedExpression.left, scopes, new Set(visitedReferences), false), resolveParentCallbackPropNames(analysis, unwrappedExpression.right, scopes, new Set(visitedReferences)));
45609
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
45610
+ const propName = getPropReferenceName(analysis, unwrappedExpression);
45611
+ if (propName) return new Set([propName]);
45612
+ const reference = getRef(analysis, unwrappedExpression);
45613
+ if (!reference?.resolved || visitedReferences.has(reference.resolved)) return null;
45614
+ const declarator = getSingleConstDeclarator(reference);
45615
+ if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return null;
45616
+ visitedReferences.add(reference.resolved);
45617
+ const wrappedArgument = getTransparentReactCallbackWrapperArgument(declarator.init, scopes.symbolFor(unwrappedExpression), scopes);
45618
+ const allowsFunctionForwarder = Boolean(wrappedArgument && !isReactApiCall(declarator.init, "useCallback", scopes, {
45619
+ allowGlobalReactNamespace: true,
45620
+ allowUnboundBareCalls: true
45621
+ }));
45622
+ return resolveParentCallbackPropNames(analysis, wrappedArgument ?? declarator.init, scopes, visitedReferences, allowsFunctionForwarder);
45623
+ }
45624
+ if (!isNodeOfType(unwrappedExpression, "MemberExpression")) return null;
45625
+ const propertyName = getStaticMemberPropertyName(unwrappedExpression);
45626
+ if (!propertyName) return null;
45627
+ const receiver = stripParenExpression(unwrappedExpression.object);
45628
+ if (!isNodeOfType(receiver, "Identifier")) return null;
45629
+ const receiverReference = getRef(analysis, receiver);
45630
+ if (!receiverReference?.resolved || visitedReferences.has(receiverReference.resolved)) return null;
45631
+ if (isWholePropsObjectReference(analysis, receiverReference)) return new Set([propertyName]);
45632
+ const declarator = getSingleConstDeclarator(receiverReference);
45633
+ if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return null;
45634
+ visitedReferences.add(receiverReference.resolved);
45635
+ const initializer = stripParenExpression(declarator.init);
45636
+ if (propertyName === "current" && isNodeOfType(initializer, "CallExpression")) {
45637
+ if (!isReactApiCall(initializer, "useRef", scopes, {
45638
+ allowGlobalReactNamespace: true,
45639
+ allowUnboundBareCalls: true
45640
+ })) return null;
45641
+ const callbackArgument = initializer.arguments[0];
45642
+ if (!callbackArgument) return null;
45643
+ let callbackNames = resolveParentCallbackPropNames(analysis, callbackArgument, scopes, new Set(visitedReferences), false);
45644
+ if (!callbackNames) return null;
45645
+ for (const candidateReference of receiverReference.resolved.references) {
45646
+ const candidateIdentifier = candidateReference.identifier;
45647
+ const candidateMember = candidateIdentifier.parent;
45648
+ if (!candidateMember || !isNodeOfType(candidateMember, "MemberExpression") || candidateMember.object !== candidateIdentifier || getStaticMemberPropertyName(candidateMember) !== "current") continue;
45649
+ const assignment = candidateMember.parent;
45650
+ if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.left !== candidateMember) continue;
45651
+ if (assignment.operator !== "=") return null;
45652
+ callbackNames = mergeRequiredBranches(callbackNames, resolveParentCallbackPropNames(analysis, assignment.right, scopes, new Set(visitedReferences), false));
45653
+ if (!callbackNames) return null;
45654
+ }
45655
+ return callbackNames;
45656
+ }
45657
+ if (!isNodeOfType(initializer, "ObjectExpression")) return null;
45658
+ const property = initializer.properties.find((candidateProperty) => isNodeOfType(candidateProperty, "Property") && getStaticPropertyKeyName(candidateProperty, { allowComputedString: true }) === propertyName);
45659
+ if (!property || !isNodeOfType(property, "Property")) return null;
45660
+ return resolveParentCallbackPropNames(analysis, property.value, scopes, visitedReferences, false);
45661
+ };
45662
+ const getParentCallbackPropNames = ({ analysis, expression, scopes }) => resolveParentCallbackPropNames(analysis, expression, scopes, /* @__PURE__ */ new Set(), false);
45663
+ //#endregion
44573
45664
  //#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
44574
45665
  const isUseStateIdentifier = (identifier) => {
44575
45666
  if (!isNodeOfType(identifier, "Identifier")) return false;
@@ -44598,14 +45689,18 @@ const FUNCTION_WRAPPER_HOOK_NAMES$1 = new Set([
44598
45689
  "useStableCallback",
44599
45690
  "useCallbackRef"
44600
45691
  ]);
44601
- const getWrapperHookWrappedFunction = (initializer) => {
45692
+ const getWrapperHookWrappedFunction = (initializer, resultSymbol, scopes) => {
44602
45693
  if (!isNodeOfType(initializer, "CallExpression")) return null;
45694
+ const transparentReactArgument = getTransparentReactCallbackWrapperArgument(initializer, resultSymbol, scopes);
45695
+ if (transparentReactArgument) return transparentReactArgument;
44603
45696
  const callee = initializer.callee;
44604
45697
  const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") ? callee.property.name : null;
44605
45698
  if (!calleeName || !FUNCTION_WRAPPER_HOOK_NAMES$1.has(calleeName)) return null;
44606
45699
  const wrapped = initializer.arguments?.[0];
44607
- if (!wrapped || !isFunctionLike$1(wrapped)) return null;
44608
- return wrapped;
45700
+ if (!wrapped) return null;
45701
+ if (calleeName === "useEffectEvent") return null;
45702
+ if (isFunctionLike$1(wrapped)) return wrapped;
45703
+ return null;
44609
45704
  };
44610
45705
  const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
44611
45706
  const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
@@ -44615,16 +45710,29 @@ const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstre
44615
45710
  const innerParent = innerIdentifier.parent;
44616
45711
  return Boolean(innerParent && isNodeOfType(innerParent, "CallExpression") && innerParent.callee === innerIdentifier);
44617
45712
  });
44618
- const isDirectParentCallbackRef = (analysis, ref) => {
45713
+ const isDirectParentCallbackRef = (analysis, ref, scopes) => {
44619
45714
  if (isProp(analysis, ref)) return true;
45715
+ if (hasMutableBindingWrite$1(ref)) {
45716
+ if (!(ref.resolved?.references.filter((candidateReference) => candidateReference.isWrite() && !candidateReference.init) ?? []).every((candidateReference) => {
45717
+ const candidateIdentifier = candidateReference.identifier;
45718
+ const assignment = candidateIdentifier.parent;
45719
+ if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== candidateIdentifier) return false;
45720
+ const assignedReferences = getDownstreamRefs(analysis, assignment.right);
45721
+ return assignedReferences.length > 0 && assignedReferences.every((assignedReference) => isProp(analysis, assignedReference));
45722
+ })) return false;
45723
+ }
44620
45724
  return Boolean(ref.resolved?.defs.some((def) => {
44621
45725
  const node = def.node;
44622
45726
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
44623
45727
  const initializer = unwrapChainExpression(node.init);
44624
- const wrappedFunction = getWrapperHookWrappedFunction(initializer);
45728
+ const wrappedFunction = getWrapperHookWrappedFunction(initializer, isNodeOfType(node.id, "Identifier") ? scopes.symbolFor(node.id) ?? null : null, scopes);
44625
45729
  if (wrappedFunction) {
44626
45730
  if (wrappedFunction.async) return false;
44627
- return wrappedFunctionNotifiesParent(analysis, wrappedFunction);
45731
+ if (isFunctionLike$1(wrappedFunction)) return wrappedFunctionNotifiesParent(analysis, wrappedFunction);
45732
+ const directName = getParentCallbackPropName(analysis, wrappedFunction);
45733
+ const downstreamReferences = getDownstreamRefs(analysis, wrappedFunction);
45734
+ if (directName !== null) return true;
45735
+ return downstreamReferences.some((wrappedReference) => !hasMutableBindingWrite$1(wrappedReference) && getUpstreamRefs(analysis, wrappedReference).some((upstreamReference) => isProp(analysis, upstreamReference)));
44628
45736
  }
44629
45737
  if (!isNodeOfType(initializer, "Identifier") && !isNodeOfType(initializer, "MemberExpression")) return false;
44630
45738
  return getDownstreamRefs(analysis, initializer).some((initializerRef) => getUpstreamRefs(analysis, initializerRef).some((upstreamRef) => isProp(analysis, upstreamRef)));
@@ -44634,7 +45742,7 @@ const getDeclarationKind = (declarator) => {
44634
45742
  const declaration = declarator.parent;
44635
45743
  return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
44636
45744
  };
44637
- const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
45745
+ const hasMutableBindingWrite$1 = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
44638
45746
  const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
44639
45747
  const unwrappedExpression = stripParenExpression(expression);
44640
45748
  if (isNodeOfType(unwrappedExpression, "Identifier")) {
@@ -44646,7 +45754,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
44646
45754
  const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
44647
45755
  return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
44648
45756
  }
44649
- if (hasMutableBindingWrite(callbackReference)) return null;
45757
+ if (hasMutableBindingWrite$1(callbackReference)) return null;
44650
45758
  const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
44651
45759
  if (definitions.length !== 1) return null;
44652
45760
  const declarator = definitions[0];
@@ -44712,7 +45820,7 @@ const getRefAliasDeclarator = (identifier) => {
44712
45820
  const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
44713
45821
  if (!isNodeOfType(receiver, "Identifier")) return null;
44714
45822
  const receiverReference = getRef(analysis, receiver);
44715
- if (!receiverReference?.resolved || hasMutableBindingWrite(receiverReference)) return null;
45823
+ if (!receiverReference?.resolved || hasMutableBindingWrite$1(receiverReference)) return null;
44716
45824
  const variables = /* @__PURE__ */ new Set();
44717
45825
  let currentVariable = receiverReference.resolved;
44718
45826
  let refCall = null;
@@ -44728,7 +45836,7 @@ const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
44728
45836
  }
44729
45837
  if (getDeclarationKind(declarator) !== "const" || !isNodeOfType(stripParenExpression(declarator.init), "Identifier")) return null;
44730
45838
  const upstreamReference = getRef(analysis, stripParenExpression(declarator.init));
44731
- if (!upstreamReference?.resolved || hasMutableBindingWrite(upstreamReference)) return null;
45839
+ if (!upstreamReference?.resolved || hasMutableBindingWrite$1(upstreamReference)) return null;
44732
45840
  currentVariable = upstreamReference.resolved;
44733
45841
  }
44734
45842
  if (!refCall) return null;
@@ -44803,7 +45911,7 @@ const isParentPropsContextMerge = (analysis, expression) => {
44803
45911
  while (isNodeOfType(currentExpression, "Identifier")) {
44804
45912
  const currentReference = getRef(analysis, currentExpression);
44805
45913
  const currentVariable = currentReference?.resolved;
44806
- if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return false;
45914
+ if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite$1(currentReference)) return false;
44807
45915
  visitedVariables.add(currentVariable);
44808
45916
  const definitions = currentVariable.defs.filter((definition) => isNodeOfType(definition.node, "VariableDeclarator"));
44809
45917
  if (definitions.length !== 1) return false;
@@ -44817,11 +45925,11 @@ const isParentPropsContextMerge = (analysis, expression) => {
44817
45925
  const propsExpression = stripParenExpression(propsSpread.argument);
44818
45926
  if (!isNodeOfType(propsExpression, "Identifier")) return false;
44819
45927
  const propsReference = getRef(analysis, propsExpression);
44820
- if (!propsReference?.resolved || !isWholePropsObjectReference(analysis, propsReference) || hasMutableBindingWrite(propsReference) || propsReference.resolved.references.some((candidateReference) => candidateReference !== propsReference)) return false;
45928
+ if (!propsReference?.resolved || !isWholePropsObjectReference(analysis, propsReference) || hasMutableBindingWrite$1(propsReference) || propsReference.resolved.references.some((candidateReference) => candidateReference !== propsReference)) return false;
44821
45929
  const contextExpression = stripParenExpression(contextSpread.argument);
44822
45930
  if (!isNodeOfType(contextExpression, "Identifier")) return false;
44823
45931
  const contextReference = getRef(analysis, contextExpression);
44824
- if (!contextReference?.resolved || hasMutableBindingWrite(contextReference) || contextReference.resolved.references.some((candidateReference) => !candidateReference.init && candidateReference !== contextReference)) return false;
45932
+ if (!contextReference?.resolved || hasMutableBindingWrite$1(contextReference) || contextReference.resolved.references.some((candidateReference) => !candidateReference.init && candidateReference !== contextReference)) return false;
44825
45933
  const contextInitializer = contextReference.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
44826
45934
  if (!contextInitializer || !isNodeOfType(contextInitializer, "VariableDeclarator") || getDeclarationKind(contextInitializer) !== "const" || !contextInitializer.init || !isNodeOfType(contextInitializer.init, "CallExpression")) return false;
44827
45935
  const contextHook = stripParenExpression(contextInitializer.init.callee);
@@ -44835,7 +45943,7 @@ const getImmutableParentCallbackPropName = (analysis, expression) => {
44835
45943
  while (isNodeOfType(currentExpression, "Identifier")) {
44836
45944
  const currentReference = getRef(analysis, currentExpression);
44837
45945
  const currentVariable = currentReference?.resolved;
44838
- if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return null;
45946
+ if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite$1(currentReference)) return null;
44839
45947
  visitedVariables.add(currentVariable);
44840
45948
  const definition = currentVariable.defs.length === 1 ? currentVariable.defs[0] : null;
44841
45949
  const bindingIdentifier = definition?.name;
@@ -44904,7 +46012,7 @@ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) =>
44904
46012
  while (isNodeOfType(currentExpression, "Identifier")) {
44905
46013
  const callbackReference = getRef(analysis, currentExpression);
44906
46014
  const callbackVariable = callbackReference?.resolved;
44907
- if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite(callbackReference)) return null;
46015
+ if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite$1(callbackReference)) return null;
44908
46016
  visitedVariables.add(callbackVariable);
44909
46017
  const definition = callbackVariable.defs.length === 1 ? callbackVariable.defs[0] : null;
44910
46018
  const declarator = definition?.node;
@@ -44924,10 +46032,11 @@ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) =>
44924
46032
  if (!propertyName || !COMMAND_PROP_NAME_PATTERN.test(propertyName)) return null;
44925
46033
  return refCurrentObjectPreservesCallbackProperty(analysis, currentExpression.object, propertyName, isReactUseRefCall) ? propertyName : null;
44926
46034
  };
44927
- const isWrapperHookCallbackRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
46035
+ const isWrapperHookCallbackRef = (analysis, ref, scopes) => Boolean(ref.resolved?.defs.some((def) => {
44928
46036
  const node = def.node;
44929
46037
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
44930
- return getWrapperHookWrappedFunction(unwrapChainExpression(node.init)) !== null;
46038
+ const resultSymbol = isNodeOfType(node.id, "Identifier") ? scopes.symbolFor(node.id) ?? null : null;
46039
+ return getWrapperHookWrappedFunction(unwrapChainExpression(node.init), resultSymbol, scopes) !== null;
44931
46040
  }));
44932
46041
  const isHandlerBagArgument = (analysis, argument) => {
44933
46042
  if (!isNodeOfType(argument, "ObjectExpression")) return false;
@@ -44946,14 +46055,25 @@ const isHandlerBagArgument = (analysis, argument) => {
44946
46055
  };
44947
46056
  const getFunctionalUpdaterDataRefs = (analysis, updater) => getDownstreamRefs(analysis, updater).filter((updaterRef) => !updaterRef.resolved?.defs.some((def) => def.type === "Parameter" && def.node === updater));
44948
46057
  const HOOK_NAME_PATTERN$1 = /^use[A-Z0-9]/;
44949
- const EXTERNAL_SUBSCRIPTION_HOOK_NAMES = new Set([
46058
+ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
44950
46059
  "useIntersectionObserver",
44951
46060
  "useMatchMedia",
46061
+ "useMediaJobProgress",
44952
46062
  "useMediaQuery",
44953
46063
  "useResizeObserver",
44954
46064
  "useVisibility",
44955
46065
  "useWindowSize"
44956
46066
  ]);
46067
+ const isCallbackPropReference = (analysis, ref) => {
46068
+ if (!isProp(analysis, ref)) return false;
46069
+ const identifier = ref.identifier;
46070
+ if (!isNodeOfType(identifier, "Identifier")) return false;
46071
+ if (!isWholePropsObjectReference(analysis, ref)) return HANDLER_NAMED_PROP_PATTERN.test(identifier.name);
46072
+ const member = identifier.parent;
46073
+ if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier) return false;
46074
+ const propertyName = getStaticMemberPropertyName(member);
46075
+ return Boolean(propertyName && HANDLER_NAMED_PROP_PATTERN.test(propertyName));
46076
+ };
44957
46077
  const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
44958
46078
  const node = def.node;
44959
46079
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
@@ -44961,7 +46081,7 @@ const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs
44961
46081
  if (!isNodeOfType(init, "CallExpression")) return false;
44962
46082
  const callee = init.callee;
44963
46083
  if (!isNodeOfType(callee, "Identifier") || !HOOK_NAME_PATTERN$1.test(callee.name)) return false;
44964
- return (init.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isProp(analysis, downstreamRef)));
46084
+ return (init.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
44965
46085
  }));
44966
46086
  const isParentWiredHookResultArgument = (analysis, argument) => {
44967
46087
  if (!isNodeOfType(argument, "Identifier")) return false;
@@ -44974,19 +46094,19 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
44974
46094
  if (!isNodeOfType(identifier, "Identifier") || !HOOK_NAME_PATTERN$1.test(identifier.name)) return false;
44975
46095
  const parent = identifier.parent;
44976
46096
  if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
44977
- return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isProp(analysis, downstreamRef)));
46097
+ return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
44978
46098
  };
44979
46099
  const isExternalSubscriptionHookRef = (ref) => {
44980
46100
  const identifier = ref.identifier;
44981
46101
  if (!isNodeOfType(identifier, "Identifier")) return false;
44982
- if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(identifier.name) && isCalleePosition(identifier)) return true;
46102
+ if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
44983
46103
  return Boolean(ref.resolved?.defs.some((def) => {
44984
46104
  const node = def.node;
44985
46105
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
44986
46106
  const initializer = stripParenExpression(node.init);
44987
46107
  if (!isNodeOfType(initializer, "CallExpression")) return false;
44988
46108
  const callee = stripParenExpression(initializer.callee);
44989
- return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(callee.name);
46109
+ return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name);
44990
46110
  }));
44991
46111
  };
44992
46112
  const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
@@ -45022,16 +46142,22 @@ const noPassDataToParent = defineRule({
45022
46142
  const callExpr = getCallExpr(ref);
45023
46143
  if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
45024
46144
  const callbackRefProvenance = getCallbackRefProvenance(analysis, node, callExpr, isReactUseRefCall, isReactUseEffectCall);
45025
- if (isRefCall(analysis, ref) && !callbackRefProvenance) continue;
45026
46145
  if (!isSynchronous(ref.identifier, effectFn)) continue;
45027
46146
  const calleeNode = unwrapChainExpression(callExpr.callee);
45028
46147
  const identifier = ref.identifier;
45029
- if (callbackRefProvenance) {
45030
- if ([...callbackRefProvenance.callbackPropNames].some((callbackPropName) => COMMAND_PROP_NAME_PATTERN.test(callbackPropName))) continue;
46148
+ const resolvedCallbackPropNames = isNodeOfType(calleeNode, "MemberExpression") && getStaticMemberPropertyName(calleeNode) === "current" ? null : getParentCallbackPropNames({
46149
+ analysis,
46150
+ expression: calleeNode,
46151
+ scopes: context.scopes
46152
+ });
46153
+ const callbackPropNames = callbackRefProvenance?.callbackPropNames ?? resolvedCallbackPropNames;
46154
+ if (isRefCall(analysis, ref) && !callbackPropNames) continue;
46155
+ if (callbackPropNames) {
46156
+ if ([...callbackPropNames].some((callbackPropName) => COMMAND_PROP_NAME_PATTERN.test(callbackPropName))) continue;
45031
46157
  } else if (calleeNode === identifier) {
45032
46158
  const callbackPropName = getCommandCallbackPropName(analysis, identifier, isReactUseRefCall);
45033
46159
  if (callbackPropName && COMMAND_PROP_NAME_PATTERN.test(callbackPropName)) continue;
45034
- if (!isDirectParentCallbackRef(analysis, ref)) continue;
46160
+ if (!isDirectParentCallbackRef(analysis, ref, context.scopes)) continue;
45035
46161
  if (isNodeOfType(identifier, "Identifier") && COMMAND_PROP_NAME_PATTERN.test(identifier.name)) continue;
45036
46162
  } else if (isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === identifier) {
45037
46163
  if (!isWholePropsObjectReference(analysis, ref)) continue;
@@ -45039,10 +46165,10 @@ const noPassDataToParent = defineRule({
45039
46165
  } else continue;
45040
46166
  const methodName = getCallMethodName(calleeNode);
45041
46167
  const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
45042
- if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
46168
+ if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead && !callbackPropNames) continue;
45043
46169
  if (methodName && COMMAND_PROP_NAME_PATTERN.test(methodName)) continue;
45044
- if (!callbackRefProvenance && isNamespacedApiCallee(calleeNode)) continue;
45045
- const isSetterNamedCallee = callbackRefProvenance ? [...callbackRefProvenance.callbackPropNames].every((callbackPropName) => SETTER_NAMED_PROP_PATTERN.test(callbackPropName)) : Boolean((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) && SETTER_NAMED_PROP_PATTERN.test((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) ?? ""));
46170
+ if (!callbackPropNames && isNamespacedApiCallee(calleeNode)) continue;
46171
+ const isSetterNamedCallee = callbackPropNames ? [...callbackPropNames].every((callbackPropName) => SETTER_NAMED_PROP_PATTERN.test(callbackPropName)) : Boolean((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) && SETTER_NAMED_PROP_PATTERN.test((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) ?? ""));
45046
46172
  const isLeafRef = (argRef) => getUpstreamRefs(analysis, argRef).length === 1;
45047
46173
  const argsUpstreamRefs = (callExpr.arguments ?? []).flatMap((argument) => {
45048
46174
  if (isFunctionLike$1(argument)) {
@@ -45057,7 +46183,7 @@ const noPassDataToParent = defineRule({
45057
46183
  }
45058
46184
  return getDownstreamRefs(analysis, argument);
45059
46185
  }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
45060
- if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
46186
+ if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
45061
46187
  if (!argsUpstreamRefs.some((argRef) => {
45062
46188
  if (isUseStateIdentifier(argRef.identifier)) return false;
45063
46189
  if (isExternalSubscriptionHookRef(argRef)) return false;
@@ -45095,9 +46221,47 @@ const isCallResultConsumedAsArgument = (callExpression) => {
45095
46221
  return false;
45096
46222
  };
45097
46223
  //#endregion
46224
+ //#region src/plugin/rules/state-and-effects/utils/is-custom-hook-state-result-reference.ts
46225
+ const NON_STATE_CUSTOM_HOOK_NAMES = new Set([
46226
+ "useCallbackRef",
46227
+ "useEffectEvent",
46228
+ "useEvent",
46229
+ "useEventCallback",
46230
+ "useLatest",
46231
+ "useMemoizedFn",
46232
+ "useStableCallback"
46233
+ ]);
46234
+ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES = new Set([
46235
+ "useIntersectionObserver",
46236
+ "useMatchMedia",
46237
+ "useMediaJobProgress",
46238
+ "useMediaQuery",
46239
+ "useResizeObserver",
46240
+ "useVisibility",
46241
+ "useWindowSize"
46242
+ ]);
46243
+ const getHookCalleeName = (initializer) => {
46244
+ const unwrappedInitializer = stripParenExpression(initializer);
46245
+ if (!isNodeOfType(unwrappedInitializer, "CallExpression")) return null;
46246
+ const callee = stripParenExpression(unwrappedInitializer.callee);
46247
+ if (isNodeOfType(callee, "Identifier")) return callee.name;
46248
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
46249
+ return null;
46250
+ };
46251
+ const isCustomHookStateResultReference = (analysis, reference) => Boolean(reference.resolved?.defs.some((definition) => {
46252
+ const declarator = definition.node;
46253
+ if (!isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return false;
46254
+ const calleeName = getHookCalleeName(declarator.init);
46255
+ if (!calleeName || !HOOK_NAME_PATTERN$3.test(calleeName) || BUILTIN_HOOK_NAMES.has(calleeName) || NON_STATE_CUSTOM_HOOK_NAMES.has(calleeName) || EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(calleeName)) return false;
46256
+ const initializer = stripParenExpression(declarator.init);
46257
+ if (!isNodeOfType(initializer, "CallExpression")) return false;
46258
+ return initializer.arguments.some((argument) => getDownstreamRefs(analysis, argument).some((argumentReference) => isProp(analysis, argumentReference)));
46259
+ }));
46260
+ //#endregion
45098
46261
  //#region src/plugin/rules/state-and-effects/no-pass-live-state-to-parent.ts
45099
46262
  const SETTER_NAMED_CALLBACK_PATTERN = /^set[A-Z]/;
45100
46263
  const DATA_FETCHING_CALLBACK_PATTERN = /^(fetch|refetch|load|query|request)([A-Z_]|$)/;
46264
+ const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
45101
46265
  const getCallCalleeName = (callExpr) => {
45102
46266
  if (!isNodeOfType(callExpr, "CallExpression")) return null;
45103
46267
  const callee = callExpr.callee;
@@ -45142,6 +46306,10 @@ const collectUpstreamStateRefs = (analysis, ref, stateRefs, visited) => {
45142
46306
  stateRefs.push(ref);
45143
46307
  return;
45144
46308
  }
46309
+ if (isCustomHookStateResultReference(analysis, ref)) {
46310
+ stateRefs.push(ref);
46311
+ return;
46312
+ }
45145
46313
  for (const def of ref.resolved?.defs ?? []) {
45146
46314
  if (def.type === "ImportBinding" || def.type === "Parameter") continue;
45147
46315
  const defNode = def.node;
@@ -45171,6 +46339,32 @@ const collectPropCallbackBoundStateRefs = (analysis, ref, isPropCallbackRef) =>
45171
46339
  }
45172
46340
  return stateRefs;
45173
46341
  };
46342
+ const collectDirectCallStateRefs = (analysis, callExpression) => {
46343
+ const stateReferences = [];
46344
+ for (const argument of callExpression.arguments) {
46345
+ if (isFunctionLike$1(argument)) continue;
46346
+ for (const argumentReference of getDownstreamRefs(analysis, argument)) {
46347
+ if (resolveToFunction(argumentReference)) continue;
46348
+ collectUpstreamStateRefs(analysis, argumentReference, stateReferences, /* @__PURE__ */ new Set());
46349
+ }
46350
+ }
46351
+ return stateReferences;
46352
+ };
46353
+ const getTransparentWrapperPropReference = (analysis, reference, context) => {
46354
+ for (const definition of reference.resolved?.defs ?? []) {
46355
+ const declarator = definition.node;
46356
+ if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
46357
+ const resultSymbol = context.scopes.symbolFor(declarator.id);
46358
+ const callbackArgument = getTransparentReactCallbackWrapperArgument(declarator.init, resultSymbol, context.scopes);
46359
+ if (!callbackArgument) continue;
46360
+ const callbackReferences = getDownstreamRefs(analysis, callbackArgument);
46361
+ const callbackReference = callbackReferences.find((candidateReference) => isPropCallbackInvocationRef(analysis, candidateReference));
46362
+ if (callbackReference) return callbackReference;
46363
+ const propReference = callbackReferences.find((candidateReference) => isProp(analysis, candidateReference) && !candidateReference.resolved?.references.some((candidateUsage) => candidateUsage.isWrite() && !candidateUsage.init));
46364
+ if (propReference) return propReference;
46365
+ }
46366
+ return null;
46367
+ };
45174
46368
  const isSetterNamedCallbackReceivingData = (callbackRef) => {
45175
46369
  const callExpr = getCallExpr(callbackRef);
45176
46370
  if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) return false;
@@ -45206,6 +46400,16 @@ const resolvesToLocalHookReturnBinding = (ref) => Boolean(ref?.resolved?.defs?.s
45206
46400
  const calleeName = getInitializerCalleeName(node.init);
45207
46401
  return calleeName !== null && isReactHookName(calleeName) && !FUNCTION_WRAPPER_HOOK_NAMES.has(calleeName);
45208
46402
  }));
46403
+ const getDirectLocalEffectHelper = (callExpression, effectFunction, context) => {
46404
+ const helperFunction = resolveExactLocalFunction(callExpression.callee, context.scopes);
46405
+ if (!helperFunction) return null;
46406
+ let ancestor = callExpression.parent;
46407
+ while (ancestor && ancestor !== effectFunction) {
46408
+ if (isFunctionLike$1(ancestor)) return null;
46409
+ ancestor = ancestor.parent;
46410
+ }
46411
+ return ancestor === effectFunction ? helperFunction : null;
46412
+ };
45209
46413
  const noPassLiveStateToParent = defineRule({
45210
46414
  id: "no-pass-live-state-to-parent",
45211
46415
  title: "Live state pushed to parent via effect",
@@ -45220,20 +46424,32 @@ const noPassLiveStateToParent = defineRule({
45220
46424
  if (!effectFnRefs) return;
45221
46425
  const effectFn = getEffectFn(analysis, node);
45222
46426
  if (!effectFn) return;
46427
+ const effectFunctionBody = isNodeOfType(effectFn, "ArrowFunctionExpression") || isNodeOfType(effectFn, "FunctionExpression") || isNodeOfType(effectFn, "FunctionDeclaration") ? effectFn.body : null;
45223
46428
  for (const ref of effectFnRefs) {
45224
- const propCallbackRefs = getEventualCallRefsTo(analysis, ref, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef));
45225
- if (propCallbackRefs.length === 0) continue;
45226
- if (resolvesToLocalHookReturnBinding(ref)) continue;
45227
- if (!isSynchronous(ref.identifier, effectFn)) continue;
45228
46429
  const callExpr = getCallExpr(ref);
45229
- if (!callExpr) continue;
46430
+ if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
46431
+ const directLocalEffectHelper = getDirectLocalEffectHelper(callExpr, effectFn, context);
46432
+ const callGraphReferences = directLocalEffectHelper ? [ref, ...getDownstreamRefs(analysis, directLocalEffectHelper)] : [ref];
46433
+ const resolvedCallbackPropNames = getParentCallbackPropNames({
46434
+ analysis,
46435
+ expression: callExpr.callee,
46436
+ scopes: context.scopes
46437
+ });
46438
+ const callExpressionRoot = findTransparentExpressionRoot(callExpr);
46439
+ const notificationCallbackPropNames = Boolean(resolvedCallbackPropNames && callExpr.arguments.length > 0 && (!isCallResultCapturedToLocal(callExpr) || isNodeOfType(callExpressionRoot.parent, "ReturnStatement") && callExpressionRoot.parent.parent === effectFunctionBody) && [...resolvedCallbackPropNames].every((callbackPropName) => !DATA_FETCHING_CALLBACK_PATTERN.test(callbackPropName))) ? resolvedCallbackPropNames : null;
46440
+ if (!notificationCallbackPropNames && hasMutableBindingWrite(ref)) continue;
46441
+ const propCallbackRefs = callGraphReferences.flatMap((callGraphReference) => getEventualCallRefsTo(analysis, callGraphReference, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef)));
46442
+ const transparentPropReference = propCallbackRefs.length === 0 ? getTransparentWrapperPropReference(analysis, ref, context) : null;
46443
+ if (propCallbackRefs.length === 0 && !transparentPropReference && !notificationCallbackPropNames) continue;
46444
+ if (!notificationCallbackPropNames && resolvesToLocalHookReturnBinding(ref)) continue;
46445
+ if (!isSynchronous(ref.identifier, effectFn) && !directLocalEffectHelper) continue;
45230
46446
  if (isCallResultConsumedAsArgument(callExpr)) continue;
45231
46447
  const calleeNode = callExpr.callee;
45232
46448
  const methodName = calleeNode ? getCallMethodName(calleeNode) : null;
45233
46449
  const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && calleeNode && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
45234
- if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
45235
- if (calleeNode && isNamespacedApiCallee(calleeNode)) continue;
45236
- const stateArgRefs = collectPropCallbackBoundStateRefs(analysis, ref, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef));
46450
+ if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead && !notificationCallbackPropNames) continue;
46451
+ if (!notificationCallbackPropNames && calleeNode && isNamespacedApiCallee(calleeNode)) continue;
46452
+ const stateArgRefs = transparentPropReference || notificationCallbackPropNames ? collectDirectCallStateRefs(analysis, callExpr) : callGraphReferences.flatMap((callGraphReference) => collectPropCallbackBoundStateRefs(analysis, callGraphReference, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef)));
45237
46453
  const handsSetterNamedCallbackData = propCallbackRefs.some(isSetterNamedCallbackReceivingData);
45238
46454
  if (stateArgRefs.length === 0 && !handsSetterNamedCallbackData) continue;
45239
46455
  context.report({
@@ -45626,6 +46842,7 @@ const isStateLikeDependency = (analysis, element, isPropName) => {
45626
46842
  if (!analysis) return true;
45627
46843
  const reference = getRef(analysis, element);
45628
46844
  if (!reference) return true;
46845
+ if (isCustomHookStateResultReference(analysis, reference)) return true;
45629
46846
  const upstreamReferences = getUpstreamRefs(analysis, reference);
45630
46847
  if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
45631
46848
  return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
@@ -45642,6 +46859,22 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
45642
46859
  if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
45643
46860
  return isPropName(callbackArgument.name) ? callbackArgument.name : null;
45644
46861
  };
46862
+ const getTransparentWrappedPropCallbackName = (callExpression, context, isPropName) => {
46863
+ const callee = stripParenExpression(callExpression.callee);
46864
+ if (!isNodeOfType(callee, "Identifier")) return null;
46865
+ const binding = findVariableInitializer(callExpression, callee.name);
46866
+ if (!binding?.initializer) return null;
46867
+ const resultSymbol = context.scopes.symbolFor(callee);
46868
+ const callbackArgument = getTransparentReactCallbackWrapperArgument(binding.initializer, resultSymbol, context.scopes);
46869
+ if (!callbackArgument) return null;
46870
+ const callbackSource = stripParenExpression(callbackArgument);
46871
+ if (isNodeOfType(callbackSource, "Identifier")) return isPropName(callbackSource.name, callbackSource) ? callbackSource.name : null;
46872
+ if (!isNodeOfType(callbackSource, "MemberExpression")) return null;
46873
+ const receiver = stripParenExpression(callbackSource.object);
46874
+ const propertyName = getStaticPropertyName(callbackSource);
46875
+ if (!isNodeOfType(receiver, "Identifier") || !propertyName) return null;
46876
+ return isPropName(receiver.name, receiver) ? propertyName : null;
46877
+ };
45645
46878
  const noPropCallbackInEffect = defineRule({
45646
46879
  id: "no-prop-callback-in-effect",
45647
46880
  title: "Parent kept in sync with a callback effect",
@@ -45675,9 +46908,16 @@ const noPropCallbackInEffect = defineRule({
45675
46908
  walkInsideStatementBlocks(callback.body, (child) => {
45676
46909
  if (!isNodeOfType(child, "CallExpression")) return;
45677
46910
  const directCallee = stripParenExpression(child.callee);
45678
- const calleeName = isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName);
46911
+ const resolvedCallbackPropNames = analysis && propStackTracker.getCurrentPropNames().size > 0 ? getParentCallbackPropNames({
46912
+ analysis,
46913
+ expression: directCallee,
46914
+ scopes: context.scopes
46915
+ }) : null;
46916
+ const calleeName = resolvedCallbackPropNames && [...resolvedCallbackPropNames][0] || isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName) || getTransparentWrappedPropCallbackName(child, context, propStackTracker.isPropName);
45679
46917
  if (!calleeName) return;
45680
- if (!isResultDiscardedCall(child)) return;
46918
+ const callExpressionRoot = findTransparentExpressionRoot(child);
46919
+ const isDirectEffectReturn = isNodeOfType(callExpressionRoot.parent, "ReturnStatement") && callExpressionRoot.parent.parent === callback.body;
46920
+ if (!isResultDiscardedCall(child) && !isDirectEffectReturn) return;
45681
46921
  if (reportedNodes.has(child)) return;
45682
46922
  reportedNodes.add(child);
45683
46923
  context.report({
@@ -46510,6 +47750,69 @@ const noRedundantShouldComponentUpdate = defineRule({
46510
47750
  }
46511
47751
  });
46512
47752
  //#endregion
47753
+ //#region src/plugin/rules/correctness/no-ref-callback-cleanup-before-react-19.ts
47754
+ const resolveFunctionExpressions = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
47755
+ const expression = stripParenExpression(rawExpression);
47756
+ if (isFunctionLike$1(expression)) return expression.async || expression.generator ? [] : [expression];
47757
+ if (isNodeOfType(expression, "ConditionalExpression")) {
47758
+ if (isNodeOfType(expression.test, "Literal")) return resolveFunctionExpressions(expression.test.value ? expression.consequent : expression.alternate, scopes, visitedSymbolIds);
47759
+ return [...resolveFunctionExpressions(expression.consequent, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.alternate, scopes, visitedSymbolIds)];
47760
+ }
47761
+ if (isNodeOfType(expression, "LogicalExpression")) {
47762
+ if (isNodeOfType(expression.left, "Literal")) {
47763
+ const isLeftTruthy = Boolean(expression.left.value);
47764
+ if (expression.operator === "&&" && !isLeftTruthy) return [];
47765
+ if (expression.operator === "||" && isLeftTruthy) return [];
47766
+ if (expression.operator === "??" && expression.left.value !== null) return [];
47767
+ }
47768
+ if (expression.operator === "&&") return resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds);
47769
+ return [...resolveFunctionExpressions(expression.left, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds)];
47770
+ }
47771
+ if (isNodeOfType(expression, "SequenceExpression")) {
47772
+ const finalExpression = expression.expressions.at(-1);
47773
+ return finalExpression ? resolveFunctionExpressions(finalExpression, scopes, visitedSymbolIds) : [];
47774
+ }
47775
+ if (isNodeOfType(expression, "CallExpression")) {
47776
+ if (!isReactApiCall(expression, "useCallback", scopes)) return [];
47777
+ const callback = expression.arguments[0];
47778
+ return callback && !isNodeOfType(callback, "SpreadElement") ? resolveFunctionExpressions(callback, scopes, visitedSymbolIds) : [];
47779
+ }
47780
+ if (!isNodeOfType(expression, "Identifier")) return [];
47781
+ const symbol = scopes.symbolFor(expression);
47782
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return [];
47783
+ 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]));
47784
+ const initializer = getDirectConstInitializer(symbol);
47785
+ if (!initializer) return [];
47786
+ return resolveFunctionExpressions(initializer, scopes, new Set([...visitedSymbolIds, symbol.id]));
47787
+ };
47788
+ const functionReturnsCleanupFunction = (functionExpression, scopes) => {
47789
+ if (!isFunctionLike$1(functionExpression)) return false;
47790
+ if (!isNodeOfType(functionExpression.body, "BlockStatement")) return resolveFunctionExpressions(functionExpression.body, scopes).length > 0;
47791
+ return collectFunctionReturnStatements(functionExpression).some((returnStatement) => Boolean(returnStatement.argument && resolveFunctionExpressions(returnStatement.argument, scopes).length > 0));
47792
+ };
47793
+ const callbackReturnsCleanupFunction = (callback, scopes) => {
47794
+ return resolveFunctionExpressions(callback, scopes).some((functionExpression) => functionReturnsCleanupFunction(functionExpression, scopes));
47795
+ };
47796
+ const noRefCallbackCleanupBeforeReact19 = defineRule({
47797
+ id: "no-ref-callback-cleanup-before-react-19",
47798
+ title: "Ref cleanup requires React 19",
47799
+ requires: ["react:18"],
47800
+ disabledWhen: ["react:19"],
47801
+ severity: "warn",
47802
+ 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.",
47803
+ create: (context) => ({ JSXAttribute(node) {
47804
+ if (getJsxAttributeName(node.name) !== "ref") return;
47805
+ if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
47806
+ const callback = node.value.expression;
47807
+ if (!callback || isNodeOfType(callback, "JSXEmptyExpression")) return;
47808
+ if (!callbackReturnsCleanupFunction(callback, context.scopes)) return;
47809
+ context.report({
47810
+ node,
47811
+ 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."
47812
+ });
47813
+ } })
47814
+ });
47815
+ //#endregion
46513
47816
  //#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
46514
47817
  const REPEATED_ANCESTOR_TYPES = new Set([
46515
47818
  "DoWhileStatement",
@@ -56471,8 +57774,39 @@ const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node,
56471
57774
  destructureIndex: 1
56472
57775
  });
56473
57776
  //#endregion
57777
+ //#region src/plugin/utils/unwrap-return-expression.ts
57778
+ const unwrapReturnExpression = (node) => isNodeOfType(node, "ReturnStatement") && node.argument ? node.argument : node;
57779
+ //#endregion
56474
57780
  //#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
56475
57781
  const USE_EFFECT_ONLY = new Set(["useEffect"]);
57782
+ const USE_CALLBACK_ONLY = new Set(["useCallback"]);
57783
+ const USE_STATE_ONLY = new Set(["useState"]);
57784
+ const REACT_API_CALL_OPTIONS = {
57785
+ allowGlobalReactNamespace: true,
57786
+ allowUnboundBareCalls: true,
57787
+ resolveNamedAliases: true
57788
+ };
57789
+ const expressionReadsDerivedSymbol = (context, expression, stateDerivedSymbolIds) => {
57790
+ let readsDerivedSymbol = false;
57791
+ walkAst(expression, (node) => {
57792
+ if (readsDerivedSymbol) return false;
57793
+ if (node !== expression && isFunctionLike$1(node)) return false;
57794
+ if (isNodeOfType(node, "Identifier") && stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) readsDerivedSymbol = true;
57795
+ });
57796
+ return readsDerivedSymbol;
57797
+ };
57798
+ const getStaticObjectPropertyName = (property) => {
57799
+ if (!isNodeOfType(property, "Property") || property.computed || property.method || property.kind !== "init") return null;
57800
+ if (isNodeOfType(property.key, "Identifier")) return property.key.name;
57801
+ if (isNodeOfType(property.key, "Literal") && (typeof property.key.value === "string" || typeof property.key.value === "number")) return String(property.key.value);
57802
+ return null;
57803
+ };
57804
+ const isNonVisibleJsxSpreadProperty = (propertyName) => propertyName === "id" || propertyName.startsWith("aria-") || /^on[A-Z]/.test(propertyName);
57805
+ const isTransparentAssignmentTarget = (identifier) => {
57806
+ const expressionRoot = findTransparentExpressionRoot(identifier);
57807
+ const parent = expressionRoot.parent;
57808
+ return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expressionRoot || isNodeOfType(parent, "UpdateExpression") && parent.argument === expressionRoot || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expressionRoot);
57809
+ };
56476
57810
  const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
56477
57811
  let readsCurrent = false;
56478
57812
  walkAst(argument, (child) => {
@@ -56524,6 +57858,166 @@ const isStateUsedOnlyInIdOrAriaAttributes = (setterCall, setterName) => {
56524
57858
  });
56525
57859
  return referenceCount > 0 && !nonAriaReferenceFound;
56526
57860
  };
57861
+ const isGlobalWindowMember = (context, node, propertyName) => {
57862
+ const member = stripParenExpression(node);
57863
+ if (!isNodeOfType(member, "MemberExpression") || member.computed) return false;
57864
+ const receiver = stripParenExpression(member.object);
57865
+ return isNodeOfType(receiver, "Identifier") && receiver.name === "window" && context.scopes.isGlobalReference(receiver) && isNodeOfType(member.property, "Identifier") && member.property.name === propertyName;
57866
+ };
57867
+ const getDirectWindowWidthSetter = (context, statement) => {
57868
+ const call = unwrapDiscardedExpression(statement);
57869
+ if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 1) return null;
57870
+ if (!isNodeOfType(call.callee, "Identifier") || !isSetterCall(call)) return null;
57871
+ const argument = call.arguments[0];
57872
+ return isGlobalWindowMember(context, argument, "innerWidth") ? call : null;
57873
+ };
57874
+ const getResizeListenerHandler = (context, statement, methodName) => {
57875
+ const call = unwrapDiscardedExpression(statement);
57876
+ if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 2) return null;
57877
+ if (!isGlobalWindowMember(context, call.callee, methodName)) return null;
57878
+ const eventName = call.arguments[0];
57879
+ const handler = call.arguments[1];
57880
+ if (!isNodeOfType(eventName, "Literal") || eventName.value !== "resize") return null;
57881
+ return isNodeOfType(handler, "Identifier") ? handler : null;
57882
+ };
57883
+ const getCleanupResizeHandler = (context, statement) => {
57884
+ if (!isNodeOfType(statement, "ReturnStatement") || !isFunctionLike$1(statement.argument)) return null;
57885
+ const cleanupStatements = getCallbackStatements(statement.argument);
57886
+ if (cleanupStatements.length !== 1) return null;
57887
+ return getResizeListenerHandler(context, unwrapReturnExpression(cleanupStatements[0]), "removeEventListener");
57888
+ };
57889
+ const findExactViewportState = (context, componentFunction, setterCall) => {
57890
+ if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
57891
+ const componentBody = componentFunction.body;
57892
+ if (!isNodeOfType(setterCall.callee, "Identifier")) return null;
57893
+ const setterSymbol = context.scopes.symbolFor(setterCall.callee);
57894
+ if (!setterSymbol || setterSymbol.kind !== "const" || !isNodeOfType(setterSymbol.declarationNode, "VariableDeclarator")) return null;
57895
+ const declarator = setterSymbol.declarationNode;
57896
+ if (!isNodeOfType(declarator.id, "ArrayPattern")) return null;
57897
+ const stateIdentifier = declarator.id.elements?.[0];
57898
+ const setterIdentifier = declarator.id.elements?.[1];
57899
+ 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;
57900
+ const initializer = declarator.init.arguments?.[0];
57901
+ if (!isNodeOfType(initializer, "Literal") || initializer.value !== 0) return null;
57902
+ const stateSymbol = context.scopes.symbolFor(stateIdentifier);
57903
+ if (!stateSymbol) return null;
57904
+ const stateDerivedSymbolIds = new Set([stateSymbol.id]);
57905
+ let didAddDerivedSymbol = true;
57906
+ while (didAddDerivedSymbol) {
57907
+ didAddDerivedSymbol = false;
57908
+ for (const statement of componentBody.body ?? []) {
57909
+ if (!isNodeOfType(statement, "VariableDeclaration")) continue;
57910
+ for (const candidateDeclarator of statement.declarations ?? []) {
57911
+ if (!isNodeOfType(candidateDeclarator.id, "Identifier") || !candidateDeclarator.init) continue;
57912
+ const candidateInitializer = stripParenExpression(candidateDeclarator.init);
57913
+ if (isFunctionLike$1(candidateInitializer) || isNodeOfType(candidateInitializer, "CallExpression") && isReactApiCall(candidateInitializer, USE_CALLBACK_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) continue;
57914
+ if (!expressionReadsDerivedSymbol(context, candidateInitializer, stateDerivedSymbolIds)) continue;
57915
+ const candidateSymbol = context.scopes.symbolFor(candidateDeclarator.id);
57916
+ if (candidateSymbol?.kind === "const" && candidateSymbol.references.every((reference) => reference.flag === "read" && !isTransparentAssignmentTarget(reference.identifier)) && !stateDerivedSymbolIds.has(candidateSymbol.id)) {
57917
+ stateDerivedSymbolIds.add(candidateSymbol.id);
57918
+ didAddDerivedSymbol = true;
57919
+ }
57920
+ }
57921
+ }
57922
+ }
57923
+ const staticSpreadVisibilityBySymbolId = /* @__PURE__ */ new Map();
57924
+ const hasOnlyStaticObjectReferences = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
57925
+ const symbol = context.scopes.symbolFor(identifier);
57926
+ if (!symbol) return false;
57927
+ if (visitedSymbolIds.has(symbol.id)) return true;
57928
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
57929
+ nextVisitedSymbolIds.add(symbol.id);
57930
+ let hasUnknownReference = false;
57931
+ walkAst(componentBody, (node) => {
57932
+ if (hasUnknownReference || !isNodeOfType(node, "Identifier") || context.scopes.symbolFor(node)?.id !== symbol.id || node === symbol.bindingIdentifier) return;
57933
+ const referenceRoot = findTransparentExpressionRoot(node);
57934
+ const parent = referenceRoot.parent;
57935
+ if (isNodeOfType(parent, "JSXSpreadAttribute") && parent.argument === referenceRoot) return;
57936
+ if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier") && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const" && hasOnlyStaticObjectReferences(parent.id, nextVisitedSymbolIds)) return;
57937
+ hasUnknownReference = true;
57938
+ return false;
57939
+ });
57940
+ return !hasUnknownReference;
57941
+ };
57942
+ const classifyStaticSpreadObject = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
57943
+ const symbol = context.scopes.symbolFor(identifier);
57944
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return "unknown";
57945
+ const cachedVisibility = staticSpreadVisibilityBySymbolId.get(symbol.id);
57946
+ if (cachedVisibility) return cachedVisibility;
57947
+ if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || symbol.declarationNode.id !== symbol.bindingIdentifier || !symbol.declarationNode.init) return "unknown";
57948
+ if (!hasOnlyStaticObjectReferences(identifier)) return "unknown";
57949
+ const initializer = stripParenExpression(symbol.declarationNode.init);
57950
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
57951
+ nextVisitedSymbolIds.add(symbol.id);
57952
+ if (isNodeOfType(initializer, "Identifier")) {
57953
+ const visibility = classifyStaticSpreadObject(initializer, nextVisitedSymbolIds);
57954
+ staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
57955
+ return visibility;
57956
+ }
57957
+ if (!isNodeOfType(initializer, "ObjectExpression")) return "unknown";
57958
+ let visibility = "non-visible";
57959
+ for (const property of initializer.properties ?? []) {
57960
+ const propertyName = getStaticObjectPropertyName(property);
57961
+ if (!isNodeOfType(property, "Property") || !propertyName) {
57962
+ visibility = "unknown";
57963
+ break;
57964
+ }
57965
+ if (expressionReadsDerivedSymbol(context, property.value, stateDerivedSymbolIds) && !isNonVisibleJsxSpreadProperty(propertyName)) visibility = "visible";
57966
+ }
57967
+ staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
57968
+ return visibility;
57969
+ };
57970
+ let hasNonAriaReference = false;
57971
+ walkAst(componentBody, (node) => {
57972
+ if (hasNonAriaReference) return false;
57973
+ if (!isNodeOfType(node, "Identifier") || !stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) return;
57974
+ if (findEnclosingFunction$1(node) !== componentFunction) return;
57975
+ const parent = node.parent;
57976
+ if (parent && (isNodeOfType(parent, "MemberExpression") && parent.property === node && !parent.computed || isNodeOfType(parent, "Property") && parent.key === node && !parent.computed)) return;
57977
+ let cursor = parent;
57978
+ while (cursor && cursor !== componentBody) {
57979
+ if (isFunctionLike$1(cursor)) return;
57980
+ if (isNodeOfType(cursor, "JSXSpreadAttribute")) {
57981
+ if (isNodeOfType(node, "Identifier") && classifyStaticSpreadObject(node) === "visible") hasNonAriaReference = true;
57982
+ return;
57983
+ }
57984
+ if (isNodeOfType(cursor, "JSXAttribute")) {
57985
+ if (isEventHandlerAttribute(cursor)) return;
57986
+ if (!isInsideIdOrAriaAttribute(node)) hasNonAriaReference = true;
57987
+ return;
57988
+ }
57989
+ if (isNodeOfType(cursor, "ReturnStatement")) {
57990
+ hasNonAriaReference = true;
57991
+ return;
57992
+ }
57993
+ cursor = cursor.parent;
57994
+ }
57995
+ });
57996
+ return hasNonAriaReference ? stateIdentifier.name : null;
57997
+ };
57998
+ const isExactViewportSubscriptionEffect = (context, effectCall, callback) => {
57999
+ if (!isReactApiCall(effectCall, USE_EFFECT_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return false;
58000
+ if (!isFunctionLike$1(callback) || callback.async || !isNodeOfType(callback.body, "BlockStatement")) return false;
58001
+ const statements = getCallbackStatements(callback);
58002
+ if (statements.length !== 4) return false;
58003
+ const handlerDeclaration = statements[0];
58004
+ if (!isNodeOfType(handlerDeclaration, "VariableDeclaration") || handlerDeclaration.kind !== "const" || handlerDeclaration.declarations?.length !== 1) return false;
58005
+ const handlerDeclarator = handlerDeclaration.declarations[0];
58006
+ if (!isNodeOfType(handlerDeclarator.id, "Identifier") || !isFunctionLike$1(handlerDeclarator.init)) return false;
58007
+ const handlerStatements = getCallbackStatements(handlerDeclarator.init);
58008
+ if (handlerStatements.length !== 1) return false;
58009
+ const handlerSetter = getDirectWindowWidthSetter(context, unwrapReturnExpression(handlerStatements[0]));
58010
+ const subscribedHandler = getResizeListenerHandler(context, statements[1], "addEventListener");
58011
+ const immediateSetter = getDirectWindowWidthSetter(context, statements[2]);
58012
+ const cleanupHandler = getCleanupResizeHandler(context, statements[3]);
58013
+ if (!handlerSetter || !subscribedHandler || !immediateSetter || !cleanupHandler) return false;
58014
+ const handlerSymbol = context.scopes.symbolFor(handlerDeclarator.id);
58015
+ if (!handlerSymbol || context.scopes.symbolFor(subscribedHandler) !== handlerSymbol || context.scopes.symbolFor(cleanupHandler) !== handlerSymbol) return false;
58016
+ if (!isNodeOfType(handlerSetter.callee, "Identifier") || !isNodeOfType(immediateSetter.callee, "Identifier") || context.scopes.symbolFor(handlerSetter.callee) !== context.scopes.symbolFor(immediateSetter.callee)) return false;
58017
+ const componentFunction = findEnclosingFunction$1(effectCall);
58018
+ if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return false;
58019
+ return findExactViewportState(context, componentFunction, immediateSetter) !== null;
58020
+ };
56527
58021
  const renderingHydrationNoFlicker = defineRule({
56528
58022
  id: "rendering-hydration-no-flicker",
56529
58023
  title: "useEffect setState flashes on mount",
@@ -56536,7 +58030,14 @@ const renderingHydrationNoFlicker = defineRule({
56536
58030
  if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
56537
58031
  const callback = getEffectCallback(node);
56538
58032
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
56539
- const bodyStatements = (isNodeOfType(callback.body, "BlockStatement") ? callback.body.body ?? [] : [callback.body]).filter((statement) => !isNoOpStatement(statement));
58033
+ if (isExactViewportSubscriptionEffect(context, node, callback)) {
58034
+ context.report({
58035
+ node,
58036
+ message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
58037
+ });
58038
+ return;
58039
+ }
58040
+ const bodyStatements = getCallbackStatements(callback);
56540
58041
  if (bodyStatements.length !== 1) return;
56541
58042
  const soleStatement = bodyStatements[0];
56542
58043
  if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
@@ -66368,14 +67869,6 @@ const isStateKey = (key) => {
66368
67869
  if (isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value === "state";
66369
67870
  return false;
66370
67871
  };
66371
- const findEnclosingClass = (node) => {
66372
- let ancestor = node.parent;
66373
- while (ancestor) {
66374
- if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
66375
- ancestor = ancestor.parent ?? null;
66376
- }
66377
- return null;
66378
- };
66379
67872
  const isInConstructor = (node) => {
66380
67873
  let ancestor = node.parent;
66381
67874
  while (ancestor) {
@@ -71087,6 +72580,17 @@ const reactDoctorRules = [
71087
72580
  requires: [...new Set(["react", ...noRedundantShouldComponentUpdate.requires ?? []])]
71088
72581
  }
71089
72582
  },
72583
+ {
72584
+ key: "react-doctor/no-ref-callback-cleanup-before-react-19",
72585
+ id: "no-ref-callback-cleanup-before-react-19",
72586
+ source: "react-doctor",
72587
+ originallyExternal: false,
72588
+ rule: {
72589
+ ...noRefCallbackCleanupBeforeReact19,
72590
+ framework: "global",
72591
+ category: "Bugs"
72592
+ }
72593
+ },
71090
72594
  {
71091
72595
  key: "react-doctor/no-ref-current-in-render",
71092
72596
  id: "no-ref-current-in-render",