oxlint-plugin-react-doctor 0.7.9-dev.093619c → 0.7.9-dev.11388bf

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 +0 -46
  2. package/dist/index.js +103 -1301
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7637,7 +7637,6 @@ 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;
7641
7640
  if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
7642
7641
  if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
7643
7642
  if (a.computed !== b.computed) return false;
@@ -8959,7 +8958,7 @@ const isReactNamespaceImport = (identifier, scopes) => {
8959
8958
  if (!symbol || !isImportedFromReact(symbol)) return false;
8960
8959
  return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
8961
8960
  };
8962
- const isReactNamespaceReceiver$1 = (receiver, scopes, options) => {
8961
+ const isReactNamespaceReceiver = (receiver, scopes, options) => {
8963
8962
  if (!isNodeOfType(receiver, "Identifier")) return false;
8964
8963
  if (isReactNamespaceImport(receiver, scopes)) return true;
8965
8964
  return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
@@ -8972,7 +8971,7 @@ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) =>
8972
8971
  for (const property of pattern.properties) {
8973
8972
  if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
8974
8973
  const propertyName = getStaticPropertyKeyName(property);
8975
- return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver$1(stripParenExpression(symbol.initializer), scopes, options));
8974
+ return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver(stripParenExpression(symbol.initializer), scopes, options));
8976
8975
  }
8977
8976
  return false;
8978
8977
  };
@@ -8996,7 +8995,7 @@ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds
8996
8995
  return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
8997
8996
  }
8998
8997
  if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
8999
- return isReactNamespaceReceiver$1(stripParenExpression(callee.object), scopes, options);
8998
+ return isReactNamespaceReceiver(stripParenExpression(callee.object), scopes, options);
9000
8999
  };
9001
9000
  //#endregion
9002
9001
  //#region src/plugin/utils/is-proven-browser-api-receiver.ts
@@ -13652,7 +13651,7 @@ const getPromiseChainCallForCallback = (candidate) => {
13652
13651
  if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
13653
13652
  return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
13654
13653
  };
13655
- const collectInvokedFunctions = (effectCallback, includePromiseCallbacks) => {
13654
+ const collectEffectInvokedFunctions = (effectCallback) => {
13656
13655
  const invokedFunctions = new Set([effectCallback]);
13657
13656
  const localFunctionBindings = /* @__PURE__ */ new Map();
13658
13657
  const calledBindingNames = /* @__PURE__ */ new Set();
@@ -13686,14 +13685,12 @@ const collectInvokedFunctions = (effectCallback, includePromiseCallbacks) => {
13686
13685
  calledBindingNames.add(callee.name);
13687
13686
  return;
13688
13687
  }
13689
- if (includePromiseCallbacks && isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
13688
+ if (isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
13690
13689
  });
13691
13690
  for (const calledName of calledBindingNames) enqueue(localFunctionBindings.get(calledName));
13692
13691
  }
13693
13692
  return invokedFunctions;
13694
13693
  };
13695
- const collectEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, true);
13696
- const collectSynchronouslyEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, false);
13697
13694
  //#endregion
13698
13695
  //#region src/plugin/utils/is-react-hook-name.ts
13699
13696
  const isReactHookName = (name) => {
@@ -14414,7 +14411,7 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
14414
14411
  const callNode = findDirectCallForReference(reference.identifier);
14415
14412
  return callNode ? [callNode] : [];
14416
14413
  });
14417
- if (invocationCalls.length !== 1 || symbol.references.length !== 1) return null;
14414
+ if (invocationCalls.length !== 1) return null;
14418
14415
  const invocationCall = invocationCalls[0];
14419
14416
  return findEnclosingFunction$1(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
14420
14417
  };
@@ -14701,108 +14698,7 @@ const hasPotentialInterruptionAfterGuard = (callback, guardState, usageNode, con
14701
14698
  });
14702
14699
  return hasPotentialInterruption;
14703
14700
  };
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
- };
14804
14701
  const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
14805
- if (hasGuardedRefOwnedNestedCleanup(callback, usage, cleanupReturns, context)) return true;
14806
14702
  const usageFunction = findEnclosingFunction$1(usage.node);
14807
14703
  const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
14808
14704
  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;
@@ -15014,7 +14910,6 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15014
14910
  if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
15015
14911
  if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
15016
14912
  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;
15018
14913
  const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
15019
14914
  if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
15020
14915
  const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
@@ -15051,8 +14946,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15051
14946
  const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
15052
14947
  if (!releaseHandler) return releaseVerbName === "off";
15053
14948
  const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
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);
14949
+ return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey;
15056
14950
  }
15057
14951
  if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
15058
14952
  return true;
@@ -15077,188 +14971,13 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
15077
14971
  if (!symbol) return false;
15078
14972
  return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
15079
14973
  };
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
- };
15253
14974
  const isReleaseReachableForUsage = (releaseNode, usage, context) => {
15254
14975
  if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
15255
14976
  const releaseFunction = findEnclosingFunction$1(releaseNode);
15256
14977
  if (!releaseFunction) return true;
15257
14978
  if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
15258
- if (isRetainedDisposerRefRelease(releaseNode, usage, context)) return true;
15259
14979
  const usageFunction = findEnclosingFunction$1(usage.node);
15260
14980
  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;
15262
14981
  return isPotentiallyReachableFunction(releaseFunction, context);
15263
14982
  };
15264
14983
  const fileContainsReleaseForUsage = (usage, context) => {
@@ -17392,7 +17111,7 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allow
17392
17111
  keys.add(depKey);
17393
17112
  continue;
17394
17113
  }
17395
- const identitySourceKeys = resolvePureCalledFunctionSourceKeys(reference, symbol, scopes) ?? resolveRenderDerivedMutableSourceKeys(reference, symbol, scopes) ?? resolveReactiveIdentitySourceKeys(symbol, scopes);
17114
+ const identitySourceKeys = resolveReactiveIdentitySourceKeys(symbol, scopes);
17396
17115
  if (identitySourceKeys) {
17397
17116
  if (identitySourceKeys.size === 0) stableCapturedNames.add(depKey);
17398
17117
  for (const identitySourceKey of identitySourceKeys) keys.add(identitySourceKey);
@@ -17475,161 +17194,6 @@ const resolveReactiveIdentitySourceKeys = (symbol, scopes) => {
17475
17194
  if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier || symbol.references.some((reference) => reference.flag !== "read")) return null;
17476
17195
  return resolveIdentitySourceKeysFromExpression(symbol.initializer, scopes, new Set([symbol.id]));
17477
17196
  };
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
- };
17633
17197
  const isUseCallbackResultDep = (node, scopes) => {
17634
17198
  const rootSymbol = getRootSymbol(node, scopes);
17635
17199
  const initializer = rootSymbol?.initializer ? unwrapExpression$3(rootSymbol.initializer) : null;
@@ -29857,7 +29421,7 @@ const nextjsNoVercelOgImport = defineRule({
29857
29421
  //#endregion
29858
29422
  //#region src/plugin/rules/a11y/no-access-key.ts
29859
29423
  const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
29860
- const isUndefinedIdentifier$1 = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
29424
+ const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
29861
29425
  const noAccessKey = defineRule({
29862
29426
  id: "no-access-key",
29863
29427
  title: "accessKey attribute used",
@@ -29882,7 +29446,7 @@ const noAccessKey = defineRule({
29882
29446
  if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
29883
29447
  const expression = attributeValue.expression;
29884
29448
  if (!expression || expression.type === "JSXEmptyExpression") return;
29885
- if (isUndefinedIdentifier$1(expression)) return;
29449
+ if (isUndefinedIdentifier(expression)) return;
29886
29450
  context.report({
29887
29451
  node: accessKey,
29888
29452
  message: MESSAGE$39
@@ -30647,12 +30211,6 @@ const isReactNamespaceImportReference = (ref) => Boolean(ref?.resolved?.defs.som
30647
30211
  const importDeclaration = declarationNode.parent;
30648
30212
  return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
30649
30213
  }));
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
- };
30656
30214
  const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
30657
30215
  if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return false;
30658
30216
  const callee = stripParenExpression(declarator.init.callee);
@@ -30661,20 +30219,24 @@ const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
30661
30219
  if (!reference?.resolved) return callee.name === hookName;
30662
30220
  return isReactNamedImportReference(reference, hookName);
30663
30221
  }
30664
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
30665
- return isReactNamespaceReceiver(analysis, callee.object);
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);
30666
30226
  };
30667
30227
  const isHookCallee$1 = (analysis, node, hookName) => {
30668
30228
  if (!node) return false;
30669
30229
  if (isNodeOfType(node, "Identifier")) {
30670
30230
  if (node.name === hookName) return true;
30671
30231
  if (isReactNamedImportReference(getRef(analysis, node), 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;
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;
30675
30234
  return false;
30676
30235
  }
30677
- if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
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
+ }
30678
30240
  return false;
30679
30241
  };
30680
30242
  const isUseEffect = (node) => {
@@ -31098,88 +30660,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
31098
30660
  if (HANDLER_BINDING_NAME_PATTERN.test(bindingName)) return true;
31099
30661
  return isSetterWiredToJsxHandler(componentFunction, bindingName);
31100
30662
  };
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) => {
30663
+ const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
31183
30664
  if (!setterRef.resolved) return false;
31184
30665
  const componentFunction = findEnclosingFunction$1(effectNode);
31185
30666
  if (!componentFunction) return false;
@@ -31188,11 +30669,6 @@ const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, incl
31188
30669
  const identifier = reference.identifier;
31189
30670
  if (isAstDescendant(identifier, effectNode)) continue;
31190
30671
  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;
31196
30672
  }
31197
30673
  return false;
31198
30674
  };
@@ -32082,7 +31558,7 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
32082
31558
  }
32083
31559
  return false;
32084
31560
  };
32085
- const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFilename) => {
31561
+ const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
32086
31562
  const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
32087
31563
  if (frames.length === 0) return [];
32088
31564
  const effectHasCleanup = hasCleanup(analysis, effectNode);
@@ -32112,7 +31588,7 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
32112
31588
  for (const returnedExpression of returnedExpressions) mergeEvidence(valueEvidence, collectValueEvidence(analysis, returnedExpression, updaterFrame, remainingValueCallFrames));
32113
31589
  } else valueEvidence = collectValueEvidence(analysis, writtenValue, frame, remainingValueCallFrames);
32114
31590
  const sourceReferences = [...valueEvidence.sourceReferences].filter((sourceReference) => getUseStateDecl(analysis, sourceReference) !== stateDeclarator);
32115
- const hasIndependentWriter = hasUserInputSetterWriter(analysis, context, setterReference, effectNode, true);
31591
+ const hasIndependentWriter = hasUserInputSetterWriter(setterReference, effectNode, true);
32116
31592
  const doesMatchStateInitializer = matchesStateInitializer(analysis, callExpression, stateDeclarator);
32117
31593
  if (effectHasCleanup && (frame.isDeferred || valueEvidence.hasUnknownSource || valueEvidence.hasDeferredIntroducedValue || valueEvidence.readsExternalValue)) cleanupManagedStateDeclarators.add(stateDeclarator);
32118
31594
  const isRenderKnownCopy = sourceReferences.length > 0 && !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue && !hasIndependentWriter;
@@ -32153,7 +31629,7 @@ const noAdjustStateOnPropChange = defineRule({
32153
31629
  const dependencyReferences = getEffectDepsRefs(analysis, node);
32154
31630
  if (!dependencyReferences) return;
32155
31631
  if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
32156
- for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
31632
+ for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
32157
31633
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
32158
31634
  context.report({
32159
31635
  node: fact.callExpression,
@@ -36727,7 +36203,7 @@ const noDerivedState = defineRule({
36727
36203
  if (!isUseEffect(node)) return;
36728
36204
  const analysis = getProgramAnalysis(node);
36729
36205
  if (!analysis) return;
36730
- for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
36206
+ for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
36731
36207
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
36732
36208
  reportStateWrite(fact.callExpression, fact.stateDeclarator);
36733
36209
  }
@@ -36747,7 +36223,7 @@ const noDerivedStateEffect = defineRule({
36747
36223
  if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
36748
36224
  const analysis = getProgramAnalysis(node);
36749
36225
  if (!analysis) return;
36750
- if (!collectEffectStateWriteFacts(analysis, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
36226
+ if (!collectEffectStateWriteFacts(analysis, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
36751
36227
  context.report({
36752
36228
  node,
36753
36229
  message: "You pay an extra render for state you can derive from other values."
@@ -37223,20 +36699,9 @@ const noDidMountSetState = defineRule({
37223
36699
  }
37224
36700
  });
37225
36701
  //#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
37236
36702
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
37237
36703
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
37238
36704
  const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
37239
- const DIFFERENCE_OPERATORS = new Set(["!=", "!=="]);
37240
36705
  const EQUALITY_OPERATORS = new Set([
37241
36706
  "==",
37242
36707
  "===",
@@ -37248,8 +36713,6 @@ const FUNCTION_NODE_TYPES = new Set([
37248
36713
  "FunctionExpression",
37249
36714
  "ArrowFunctionExpression"
37250
36715
  ]);
37251
- const CLASS_NODE_TYPES = new Set(["ClassDeclaration", "ClassExpression"]);
37252
- const callbackRefFieldNamesByClass = /* @__PURE__ */ new WeakMap();
37253
36716
  const isLifecycleMethodFunction = (node) => {
37254
36717
  if (!FUNCTION_NODE_TYPES.has(node.type)) return false;
37255
36718
  const parent = node.parent;
@@ -37305,187 +36768,6 @@ const getStaticMemberName = (node) => {
37305
36768
  if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
37306
36769
  return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
37307
36770
  };
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
- };
37489
36771
  const getThisStateFieldName = (node) => {
37490
36772
  const unwrappedNode = stripParenExpression(node);
37491
36773
  if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
@@ -37503,17 +36785,15 @@ const collectLocalInitializers = (lifecycleFunction) => {
37503
36785
  });
37504
36786
  return initializers;
37505
36787
  };
37506
- const derivesFromPostMountValue = (node, localInitializers, callbackRefFieldNames, visitedNames = /* @__PURE__ */ new Set()) => {
36788
+ const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
37507
36789
  if (readsPostMountValue(node)) return true;
37508
- const fieldName = getThisFieldName(node);
37509
- if (fieldName && callbackRefFieldNames.has(fieldName)) return true;
37510
36790
  const referencedNames = /* @__PURE__ */ new Set();
37511
36791
  collectReferenceIdentifierNames(node, referencedNames);
37512
36792
  for (const referencedName of referencedNames) {
37513
36793
  if (visitedNames.has(referencedName)) continue;
37514
36794
  const initializer = localInitializers.get(referencedName);
37515
36795
  if (!initializer) continue;
37516
- if (derivesFromPostMountValue(initializer, localInitializers, callbackRefFieldNames, new Set([...visitedNames, referencedName]))) return true;
36796
+ if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
37517
36797
  }
37518
36798
  return false;
37519
36799
  };
@@ -37527,84 +36807,50 @@ const getSetStateFieldValue = (setStateCall, fieldName) => {
37527
36807
  }
37528
36808
  return null;
37529
36809
  };
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));
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;
37574
36841
  };
37575
- const isInsideDiffGuard = (setStateCall, scopes) => {
36842
+ const isInsideDiffGuard = (setStateCall) => {
37576
36843
  const lifecycleFunction = findEnclosingLifecycleFunction(setStateCall);
37577
36844
  if (!lifecycleFunction) return false;
37578
36845
  const paramNames = /* @__PURE__ */ new Set();
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);
36846
+ for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
37585
36847
  const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
37586
36848
  const localInitializers = collectLocalInitializers(lifecycleFunction);
37587
- const lifecycleWrittenFieldNames = collectLifecycleWrittenFieldNames(lifecycleFunction);
37588
- const callbackRefFieldNames = new Set([...getCallbackRefFieldNames(findEnclosingClass(lifecycleFunction), scopes)].filter((fieldName) => !lifecycleWrittenFieldNames.has(fieldName)));
37589
36849
  let child = setStateCall;
37590
36850
  let ancestor = setStateCall.parent;
37591
36851
  while (ancestor && ancestor !== lifecycleFunction) {
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;
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;
37608
36854
  child = ancestor;
37609
36855
  ancestor = ancestor.parent ?? null;
37610
36856
  }
@@ -37626,7 +36872,7 @@ const noDidUpdateSetState = defineRule({
37626
36872
  if (!isNodeOfType(stripParenExpression(node.callee.object), "ThisExpression")) return;
37627
36873
  if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
37628
36874
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
37629
- if (isInsideDiffGuard(node, context.scopes)) return;
36875
+ if (isInsideDiffGuard(node)) return;
37630
36876
  context.report({
37631
36877
  node: node.callee,
37632
36878
  message: MESSAGE$27
@@ -41348,223 +40594,41 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
41348
40594
  if (leftResult === false && rightResult === false) return false;
41349
40595
  return null;
41350
40596
  };
41351
- const readHydrationConditionResult = (expression, context, runtime, state) => {
40597
+ const readHydrationConditionResult = (expression, context, runtime) => {
41352
40598
  const unwrappedExpression = stripParenExpression(expression);
41353
40599
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
41354
40600
  if (predicateMatch) return predicateMatch[`${runtime}Result`];
41355
40601
  const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
41356
40602
  if (staticResult !== null) return staticResult;
41357
- const expressionSymbol = isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression) : null;
41358
- const parameterValue = expressionSymbol ? state.parameterValuesBySymbolId.get(expressionSymbol.id) : null;
41359
- if (expressionSymbol && parameterValue && !state.visitedSymbolIds.has(expressionSymbol.id)) {
41360
- state.visitedSymbolIds.add(expressionSymbol.id);
41361
- const result = readHydrationConditionResult(parameterValue, context, runtime, state);
41362
- state.visitedSymbolIds.delete(expressionSymbol.id);
41363
- return result;
41364
- }
41365
- if (expressionSymbol && expressionSymbol.kind === "const" && expressionSymbol.initializer && expressionSymbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(expressionSymbol.id)) {
41366
- state.visitedSymbolIds.add(expressionSymbol.id);
41367
- const result = readHydrationConditionResult(expressionSymbol.initializer, context, runtime, state);
41368
- state.visitedSymbolIds.delete(expressionSymbol.id);
41369
- return result;
41370
- }
41371
- if (isNodeOfType(unwrappedExpression, "CallExpression")) {
41372
- const callArguments = unwrappedExpression.arguments ?? [];
41373
- if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
41374
- allowGlobalReactNamespace: true,
41375
- resolveNamedAliases: true
41376
- })) {
41377
- const callbackArgument = callArguments[0];
41378
- if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
41379
- const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
41380
- return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? readHydrationFunctionResult(callbackFunction, context, runtime, state) : null;
41381
- }
41382
- const callee = stripParenExpression(unwrappedExpression.callee);
41383
- 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);
41384
- const helperFunction = resolveExactLocalFunction(callee, context.scopes);
41385
- 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;
41386
- const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
41387
- for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
41388
- const parameter = helperFunction.params[parameterIndex];
41389
- const argument = callArguments[parameterIndex];
41390
- if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
41391
- const parameterSymbol = context.scopes.symbolFor(parameter);
41392
- if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
41393
- }
41394
- return readHydrationFunctionResult(helperFunction, context, runtime, {
41395
- ...state,
41396
- parameterValuesBySymbolId
41397
- });
41398
- }
41399
40603
  if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
41400
- const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
40604
+ const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
41401
40605
  return argumentResult === null ? null : !argumentResult;
41402
40606
  }
41403
40607
  if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
41404
- return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime, state), readHydrationConditionResult(unwrappedExpression.right, context, runtime, state));
41405
- };
41406
- const readHydrationStatementResult = (statement, context, runtime, state) => {
41407
- if (isNodeOfType(statement, "ReturnStatement")) return {
41408
- didReturn: true,
41409
- value: statement.argument ? readHydrationConditionResult(statement.argument, context, runtime, state) : null
41410
- };
41411
- if (isNodeOfType(statement, "BlockStatement")) {
41412
- for (const childStatement of statement.body) {
41413
- const result = readHydrationStatementResult(childStatement, context, runtime, state);
41414
- if (result.didReturn) return result;
41415
- if (statementAlwaysExits(childStatement)) break;
41416
- }
41417
- return {
41418
- didReturn: false,
41419
- value: null
41420
- };
41421
- }
41422
- if (!isNodeOfType(statement, "IfStatement")) return {
41423
- didReturn: false,
41424
- value: null
41425
- };
41426
- const conditionResult = readHydrationConditionResult(statement.test, context, runtime, state);
41427
- if (conditionResult !== null) {
41428
- const selectedBranch = conditionResult ? statement.consequent : statement.alternate;
41429
- return selectedBranch ? readHydrationStatementResult(selectedBranch, context, runtime, state) : {
41430
- didReturn: false,
41431
- value: null
41432
- };
41433
- }
41434
- const consequentResult = readHydrationStatementResult(statement.consequent, context, runtime, state);
41435
- const alternateResult = statement.alternate ? readHydrationStatementResult(statement.alternate, context, runtime, state) : {
41436
- didReturn: false,
41437
- value: null
41438
- };
41439
- return consequentResult.didReturn && alternateResult.didReturn && consequentResult.value !== null && consequentResult.value === alternateResult.value ? consequentResult : {
41440
- didReturn: consequentResult.didReturn || alternateResult.didReturn,
41441
- value: null
41442
- };
41443
- };
41444
- const readHydrationFunctionResult = (functionNode, context, runtime, state) => {
41445
- if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
41446
- state.visitedFunctionNodes.add(functionNode);
41447
- const result = isNodeOfType(functionNode.body, "BlockStatement") ? readHydrationStatementResult(functionNode.body, context, runtime, state).value : readHydrationConditionResult(functionNode.body, context, runtime, state);
41448
- state.visitedFunctionNodes.delete(functionNode);
41449
- return result;
40608
+ return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
41450
40609
  };
41451
- const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, scopes) => {
41452
- const left = stripParenExpression(leftExpression);
41453
- const right = stripParenExpression(rightExpression);
41454
- if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
41455
- const leftSymbol = scopes.symbolFor(left);
41456
- const rightSymbol = scopes.symbolFor(right);
41457
- return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
41458
- }
41459
- if (isNodeOfType(left, "MemberExpression") && isNodeOfType(right, "MemberExpression")) return doEquivalentExpressionBindingsMatch(left.object, right.object, scopes) && (!left.computed || doEquivalentExpressionBindingsMatch(left.property, right.property, scopes));
41460
- if (isNodeOfType(left, "CallExpression") && isNodeOfType(right, "CallExpression")) {
41461
- const rightArguments = right.arguments ?? [];
41462
- return doEquivalentExpressionBindingsMatch(left.callee, right.callee, scopes) && (left.arguments ?? []).every((argument, index) => {
41463
- const rightArgument = rightArguments[index];
41464
- return Boolean(rightArgument && doEquivalentExpressionBindingsMatch(argument, rightArgument, scopes));
41465
- });
41466
- }
41467
- return true;
41468
- };
41469
- const areHelperReturnValuesEquivalent = (leftValue, rightValue, context) => {
41470
- if (areExpressionsStructurallyEqual(leftValue, rightValue)) return doEquivalentExpressionBindingsMatch(leftValue, rightValue, context.scopes);
41471
- const leftBoolean = readInitialStateBoolean(leftValue, context.scopes);
41472
- const rightBoolean = readInitialStateBoolean(rightValue, context.scopes);
41473
- return leftBoolean !== null && rightBoolean !== null && leftBoolean === rightBoolean;
41474
- };
41475
- const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
41476
- const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
41477
- return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
41478
- };
41479
- const matchHydrationConditionInternal = (expression, context, state) => {
40610
+ const matchHydrationCondition = (expression, context) => {
41480
40611
  const unwrappedExpression = stripParenExpression(expression);
41481
40612
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
41482
40613
  if (predicateMatch) return {
41483
40614
  predicateMatch,
41484
40615
  predicateNode: unwrappedExpression
41485
40616
  };
41486
- if (isNodeOfType(unwrappedExpression, "Identifier")) {
41487
- const symbol = context.scopes.symbolFor(unwrappedExpression);
41488
- const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
41489
- if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
41490
- state.visitedSymbolIds.add(symbol.id);
41491
- const match = matchHydrationConditionInternal(parameterValue, context, state);
41492
- state.visitedSymbolIds.delete(symbol.id);
41493
- return match;
41494
- }
41495
- if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
41496
- state.visitedSymbolIds.add(symbol.id);
41497
- const match = matchHydrationConditionInternal(symbol.initializer, context, state);
41498
- state.visitedSymbolIds.delete(symbol.id);
41499
- return match;
41500
- }
41501
- if (isNodeOfType(unwrappedExpression, "CallExpression")) {
41502
- const callArguments = unwrappedExpression.arguments ?? [];
41503
- if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
41504
- allowGlobalReactNamespace: true,
41505
- resolveNamedAliases: true
41506
- })) {
41507
- const callbackArgument = callArguments[0];
41508
- if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
41509
- const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
41510
- return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionResult(callbackFunction, context, state) : null;
41511
- }
41512
- const callee = stripParenExpression(unwrappedExpression.callee);
41513
- if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return matchHydrationConditionInternal(callArguments[0], context, state);
41514
- const helperFunction = resolveExactLocalFunction(callee, context.scopes);
41515
- 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;
41516
- const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
41517
- for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
41518
- const parameter = helperFunction.params[parameterIndex];
41519
- const argument = callArguments[parameterIndex];
41520
- if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
41521
- const parameterSymbol = context.scopes.symbolFor(parameter);
41522
- if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
41523
- }
41524
- return matchHydrationFunctionResult(helperFunction, context, {
41525
- ...state,
41526
- parameterValuesBySymbolId
41527
- });
41528
- }
41529
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
40617
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
41530
40618
  if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
41531
- const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
41532
- const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
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;
40625
+ }
41533
40626
  const nestedMatch = leftMatch ?? rightMatch;
41534
40627
  if (!nestedMatch) return null;
41535
- const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client", state);
41536
- const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server", state);
41537
- return clientResult !== null && serverResult !== null && clientResult === serverResult ? null : nestedMatch;
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;
41538
40631
  };
41539
- const matchHydrationReturningStatement = (statement, context, state) => {
41540
- if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? matchHydrationConditionInternal(statement.argument, context, state) : null;
41541
- if (isNodeOfType(statement, "IfStatement")) {
41542
- const conditionMatch = matchHydrationConditionInternal(statement.test, context, state);
41543
- const consequentValues = getReturnedValues(statement.consequent);
41544
- const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
41545
- if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
41546
- return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
41547
- }
41548
- if (!isNodeOfType(statement, "BlockStatement")) return null;
41549
- for (const childStatement of statement.body) {
41550
- const match = matchHydrationReturningStatement(childStatement, context, state);
41551
- if (match) return match;
41552
- if (statementAlwaysExits(childStatement)) break;
41553
- }
41554
- return null;
41555
- };
41556
- const matchHydrationFunctionResult = (functionNode, context, state) => {
41557
- if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
41558
- state.visitedFunctionNodes.add(functionNode);
41559
- const match = isNodeOfType(functionNode.body, "BlockStatement") ? matchHydrationReturningStatement(functionNode.body, context, state) : matchHydrationConditionInternal(functionNode.body, context, state);
41560
- state.visitedFunctionNodes.delete(functionNode);
41561
- return match;
41562
- };
41563
- const matchHydrationCondition = (expression, context) => matchHydrationConditionInternal(expression, context, {
41564
- parameterValuesBySymbolId: /* @__PURE__ */ new Map(),
41565
- visitedFunctionNodes: /* @__PURE__ */ new Set(),
41566
- visitedSymbolIds: /* @__PURE__ */ new Set()
41567
- });
41568
40632
  const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
41569
40633
  const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
41570
40634
  if (!leftNode || !rightNode) return leftNode === rightNode;
@@ -41707,17 +40771,17 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
41707
40771
  const { predicateMatch, predicateNode } = conditionMatch;
41708
40772
  if (reportedNodes.has(predicateNode)) return;
41709
40773
  if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
41710
- const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes);
40774
+ const componentOrHookNode = findRenderPhaseComponentOrHook(predicateNode, context.scopes);
41711
40775
  if (!componentOrHookNode) return;
41712
40776
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
41713
- if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes)) return;
40777
+ if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
41714
40778
  if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
41715
- const attribute = findEnclosingJsxAttribute(conditionNode);
40779
+ const attribute = findEnclosingJsxAttribute(predicateNode);
41716
40780
  if (!attribute || isEventHandlerAttribute(attribute)) return;
41717
40781
  }
41718
- if (fileIsEmailTemplate || isGatedByFalsyInitialState(conditionNode, context.scopes)) return;
41719
- if (isAfterClientOnlyEarlyReturn(conditionNode, componentOrHookNode, context.scopes)) return;
41720
- const openingElement = findEnclosingJsxOpeningElement(conditionNode);
40782
+ if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
40783
+ if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
40784
+ const openingElement = findEnclosingJsxOpeningElement(predicateNode);
41721
40785
  if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
41722
40786
  if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
41723
40787
  if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
@@ -42099,7 +41163,7 @@ const noInitializeState = defineRule({
42099
41163
  if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
42100
41164
  const analysis = getProgramAnalysis(node);
42101
41165
  if (!analysis) return;
42102
- for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
41166
+ for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
42103
41167
  if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
42104
41168
  const stateName = getStateName(fact.stateDeclarator);
42105
41169
  context.report({
@@ -42457,8 +41521,7 @@ const noJsxElementType = defineRule({
42457
41521
  create: (context) => {
42458
41522
  let isJsxImported = false;
42459
41523
  const flaggedAnnotations = [];
42460
- const collectComponentReturnType = (functionNode, returnType) => {
42461
- if (!(isNodeOfType(functionNode, "TSDeclareFunction") ? Boolean(functionNode.id && isReactComponentName(functionNode.id.name)) : isComponentFunction$1(functionNode))) return;
41524
+ const checkReturnType = (returnType) => {
42462
41525
  const typeAnnotation = extractReturnTypeAnnotation(returnType);
42463
41526
  if (!typeAnnotation) return;
42464
41527
  if (isJsxElementTypeReference(typeAnnotation)) flaggedAnnotations.push(typeAnnotation);
@@ -42468,16 +41531,19 @@ const noJsxElementType = defineRule({
42468
41531
  if (isJsxImportBinding(node)) isJsxImported = true;
42469
41532
  },
42470
41533
  FunctionDeclaration(node) {
42471
- collectComponentReturnType(node, node.returnType);
41534
+ checkReturnType(node.returnType);
42472
41535
  },
42473
41536
  ArrowFunctionExpression(node) {
42474
- collectComponentReturnType(node, node.returnType);
41537
+ checkReturnType(node.returnType);
42475
41538
  },
42476
41539
  FunctionExpression(node) {
42477
- collectComponentReturnType(node, node.returnType);
41540
+ checkReturnType(node.returnType);
42478
41541
  },
42479
41542
  TSDeclareFunction(node) {
42480
- collectComponentReturnType(node, node.returnType);
41543
+ checkReturnType(node.returnType);
41544
+ },
41545
+ TSMethodSignature(node) {
41546
+ checkReturnType(node.returnType);
42481
41547
  },
42482
41548
  "Program:exit"() {
42483
41549
  if (isJsxImported) return;
@@ -47701,69 +46767,6 @@ const noRedundantShouldComponentUpdate = defineRule({
47701
46767
  }
47702
46768
  });
47703
46769
  //#endregion
47704
- //#region src/plugin/rules/correctness/no-ref-callback-cleanup-before-react-19.ts
47705
- const resolveFunctionExpressions = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
47706
- const expression = stripParenExpression(rawExpression);
47707
- if (isFunctionLike$1(expression)) return expression.async || expression.generator ? [] : [expression];
47708
- if (isNodeOfType(expression, "ConditionalExpression")) {
47709
- if (isNodeOfType(expression.test, "Literal")) return resolveFunctionExpressions(expression.test.value ? expression.consequent : expression.alternate, scopes, visitedSymbolIds);
47710
- return [...resolveFunctionExpressions(expression.consequent, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.alternate, scopes, visitedSymbolIds)];
47711
- }
47712
- if (isNodeOfType(expression, "LogicalExpression")) {
47713
- if (isNodeOfType(expression.left, "Literal")) {
47714
- const isLeftTruthy = Boolean(expression.left.value);
47715
- if (expression.operator === "&&" && !isLeftTruthy) return [];
47716
- if (expression.operator === "||" && isLeftTruthy) return [];
47717
- if (expression.operator === "??" && expression.left.value !== null) return [];
47718
- }
47719
- if (expression.operator === "&&") return resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds);
47720
- return [...resolveFunctionExpressions(expression.left, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds)];
47721
- }
47722
- if (isNodeOfType(expression, "SequenceExpression")) {
47723
- const finalExpression = expression.expressions.at(-1);
47724
- return finalExpression ? resolveFunctionExpressions(finalExpression, scopes, visitedSymbolIds) : [];
47725
- }
47726
- if (isNodeOfType(expression, "CallExpression")) {
47727
- if (!isReactApiCall(expression, "useCallback", scopes)) return [];
47728
- const callback = expression.arguments[0];
47729
- return callback && !isNodeOfType(callback, "SpreadElement") ? resolveFunctionExpressions(callback, scopes, visitedSymbolIds) : [];
47730
- }
47731
- if (!isNodeOfType(expression, "Identifier")) return [];
47732
- const symbol = scopes.symbolFor(expression);
47733
- if (!symbol || visitedSymbolIds.has(symbol.id)) return [];
47734
- 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]));
47735
- const initializer = getDirectConstInitializer(symbol);
47736
- if (!initializer) return [];
47737
- return resolveFunctionExpressions(initializer, scopes, new Set([...visitedSymbolIds, symbol.id]));
47738
- };
47739
- const functionReturnsCleanupFunction = (functionExpression, scopes) => {
47740
- if (!isFunctionLike$1(functionExpression)) return false;
47741
- if (!isNodeOfType(functionExpression.body, "BlockStatement")) return resolveFunctionExpressions(functionExpression.body, scopes).length > 0;
47742
- return collectFunctionReturnStatements(functionExpression).some((returnStatement) => Boolean(returnStatement.argument && resolveFunctionExpressions(returnStatement.argument, scopes).length > 0));
47743
- };
47744
- const callbackReturnsCleanupFunction = (callback, scopes) => {
47745
- return resolveFunctionExpressions(callback, scopes).some((functionExpression) => functionReturnsCleanupFunction(functionExpression, scopes));
47746
- };
47747
- const noRefCallbackCleanupBeforeReact19 = defineRule({
47748
- id: "no-ref-callback-cleanup-before-react-19",
47749
- title: "Ref cleanup requires React 19",
47750
- requires: ["react:18"],
47751
- disabledWhen: ["react:19"],
47752
- severity: "warn",
47753
- 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.",
47754
- create: (context) => ({ JSXAttribute(node) {
47755
- if (getJsxAttributeName(node.name) !== "ref") return;
47756
- if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
47757
- const callback = node.value.expression;
47758
- if (!callback || isNodeOfType(callback, "JSXEmptyExpression")) return;
47759
- if (!callbackReturnsCleanupFunction(callback, context.scopes)) return;
47760
- context.report({
47761
- node,
47762
- 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."
47763
- });
47764
- } })
47765
- });
47766
- //#endregion
47767
46770
  //#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
47768
46771
  const REPEATED_ANCESTOR_TYPES = new Set([
47769
46772
  "DoWhileStatement",
@@ -57725,39 +56728,8 @@ const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node,
57725
56728
  destructureIndex: 1
57726
56729
  });
57727
56730
  //#endregion
57728
- //#region src/plugin/utils/unwrap-return-expression.ts
57729
- const unwrapReturnExpression = (node) => isNodeOfType(node, "ReturnStatement") && node.argument ? node.argument : node;
57730
- //#endregion
57731
56731
  //#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
57732
56732
  const USE_EFFECT_ONLY = new Set(["useEffect"]);
57733
- const USE_CALLBACK_ONLY = new Set(["useCallback"]);
57734
- const USE_STATE_ONLY = new Set(["useState"]);
57735
- const REACT_API_CALL_OPTIONS = {
57736
- allowGlobalReactNamespace: true,
57737
- allowUnboundBareCalls: true,
57738
- resolveNamedAliases: true
57739
- };
57740
- const expressionReadsDerivedSymbol = (context, expression, stateDerivedSymbolIds) => {
57741
- let readsDerivedSymbol = false;
57742
- walkAst(expression, (node) => {
57743
- if (readsDerivedSymbol) return false;
57744
- if (node !== expression && isFunctionLike$1(node)) return false;
57745
- if (isNodeOfType(node, "Identifier") && stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) readsDerivedSymbol = true;
57746
- });
57747
- return readsDerivedSymbol;
57748
- };
57749
- const getStaticObjectPropertyName = (property) => {
57750
- if (!isNodeOfType(property, "Property") || property.computed || property.method || property.kind !== "init") return null;
57751
- if (isNodeOfType(property.key, "Identifier")) return property.key.name;
57752
- if (isNodeOfType(property.key, "Literal") && (typeof property.key.value === "string" || typeof property.key.value === "number")) return String(property.key.value);
57753
- return null;
57754
- };
57755
- const isNonVisibleJsxSpreadProperty = (propertyName) => propertyName === "id" || propertyName.startsWith("aria-") || /^on[A-Z]/.test(propertyName);
57756
- const isTransparentAssignmentTarget = (identifier) => {
57757
- const expressionRoot = findTransparentExpressionRoot(identifier);
57758
- const parent = expressionRoot.parent;
57759
- return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expressionRoot || isNodeOfType(parent, "UpdateExpression") && parent.argument === expressionRoot || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expressionRoot);
57760
- };
57761
56733
  const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
57762
56734
  let readsCurrent = false;
57763
56735
  walkAst(argument, (child) => {
@@ -57809,166 +56781,6 @@ const isStateUsedOnlyInIdOrAriaAttributes = (setterCall, setterName) => {
57809
56781
  });
57810
56782
  return referenceCount > 0 && !nonAriaReferenceFound;
57811
56783
  };
57812
- const isGlobalWindowMember = (context, node, propertyName) => {
57813
- const member = stripParenExpression(node);
57814
- if (!isNodeOfType(member, "MemberExpression") || member.computed) return false;
57815
- const receiver = stripParenExpression(member.object);
57816
- return isNodeOfType(receiver, "Identifier") && receiver.name === "window" && context.scopes.isGlobalReference(receiver) && isNodeOfType(member.property, "Identifier") && member.property.name === propertyName;
57817
- };
57818
- const getDirectWindowWidthSetter = (context, statement) => {
57819
- const call = unwrapDiscardedExpression(statement);
57820
- if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 1) return null;
57821
- if (!isNodeOfType(call.callee, "Identifier") || !isSetterCall(call)) return null;
57822
- const argument = call.arguments[0];
57823
- return isGlobalWindowMember(context, argument, "innerWidth") ? call : null;
57824
- };
57825
- const getResizeListenerHandler = (context, statement, methodName) => {
57826
- const call = unwrapDiscardedExpression(statement);
57827
- if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 2) return null;
57828
- if (!isGlobalWindowMember(context, call.callee, methodName)) return null;
57829
- const eventName = call.arguments[0];
57830
- const handler = call.arguments[1];
57831
- if (!isNodeOfType(eventName, "Literal") || eventName.value !== "resize") return null;
57832
- return isNodeOfType(handler, "Identifier") ? handler : null;
57833
- };
57834
- const getCleanupResizeHandler = (context, statement) => {
57835
- if (!isNodeOfType(statement, "ReturnStatement") || !isFunctionLike$1(statement.argument)) return null;
57836
- const cleanupStatements = getCallbackStatements(statement.argument);
57837
- if (cleanupStatements.length !== 1) return null;
57838
- return getResizeListenerHandler(context, unwrapReturnExpression(cleanupStatements[0]), "removeEventListener");
57839
- };
57840
- const findExactViewportState = (context, componentFunction, setterCall) => {
57841
- if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
57842
- const componentBody = componentFunction.body;
57843
- if (!isNodeOfType(setterCall.callee, "Identifier")) return null;
57844
- const setterSymbol = context.scopes.symbolFor(setterCall.callee);
57845
- if (!setterSymbol || setterSymbol.kind !== "const" || !isNodeOfType(setterSymbol.declarationNode, "VariableDeclarator")) return null;
57846
- const declarator = setterSymbol.declarationNode;
57847
- if (!isNodeOfType(declarator.id, "ArrayPattern")) return null;
57848
- const stateIdentifier = declarator.id.elements?.[0];
57849
- const setterIdentifier = declarator.id.elements?.[1];
57850
- 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;
57851
- const initializer = declarator.init.arguments?.[0];
57852
- if (!isNodeOfType(initializer, "Literal") || initializer.value !== 0) return null;
57853
- const stateSymbol = context.scopes.symbolFor(stateIdentifier);
57854
- if (!stateSymbol) return null;
57855
- const stateDerivedSymbolIds = new Set([stateSymbol.id]);
57856
- let didAddDerivedSymbol = true;
57857
- while (didAddDerivedSymbol) {
57858
- didAddDerivedSymbol = false;
57859
- for (const statement of componentBody.body ?? []) {
57860
- if (!isNodeOfType(statement, "VariableDeclaration")) continue;
57861
- for (const candidateDeclarator of statement.declarations ?? []) {
57862
- if (!isNodeOfType(candidateDeclarator.id, "Identifier") || !candidateDeclarator.init) continue;
57863
- const candidateInitializer = stripParenExpression(candidateDeclarator.init);
57864
- if (isFunctionLike$1(candidateInitializer) || isNodeOfType(candidateInitializer, "CallExpression") && isReactApiCall(candidateInitializer, USE_CALLBACK_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) continue;
57865
- if (!expressionReadsDerivedSymbol(context, candidateInitializer, stateDerivedSymbolIds)) continue;
57866
- const candidateSymbol = context.scopes.symbolFor(candidateDeclarator.id);
57867
- if (candidateSymbol?.kind === "const" && candidateSymbol.references.every((reference) => reference.flag === "read" && !isTransparentAssignmentTarget(reference.identifier)) && !stateDerivedSymbolIds.has(candidateSymbol.id)) {
57868
- stateDerivedSymbolIds.add(candidateSymbol.id);
57869
- didAddDerivedSymbol = true;
57870
- }
57871
- }
57872
- }
57873
- }
57874
- const staticSpreadVisibilityBySymbolId = /* @__PURE__ */ new Map();
57875
- const hasOnlyStaticObjectReferences = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
57876
- const symbol = context.scopes.symbolFor(identifier);
57877
- if (!symbol) return false;
57878
- if (visitedSymbolIds.has(symbol.id)) return true;
57879
- const nextVisitedSymbolIds = new Set(visitedSymbolIds);
57880
- nextVisitedSymbolIds.add(symbol.id);
57881
- let hasUnknownReference = false;
57882
- walkAst(componentBody, (node) => {
57883
- if (hasUnknownReference || !isNodeOfType(node, "Identifier") || context.scopes.symbolFor(node)?.id !== symbol.id || node === symbol.bindingIdentifier) return;
57884
- const referenceRoot = findTransparentExpressionRoot(node);
57885
- const parent = referenceRoot.parent;
57886
- if (isNodeOfType(parent, "JSXSpreadAttribute") && parent.argument === referenceRoot) return;
57887
- if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier") && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const" && hasOnlyStaticObjectReferences(parent.id, nextVisitedSymbolIds)) return;
57888
- hasUnknownReference = true;
57889
- return false;
57890
- });
57891
- return !hasUnknownReference;
57892
- };
57893
- const classifyStaticSpreadObject = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
57894
- const symbol = context.scopes.symbolFor(identifier);
57895
- if (!symbol || visitedSymbolIds.has(symbol.id)) return "unknown";
57896
- const cachedVisibility = staticSpreadVisibilityBySymbolId.get(symbol.id);
57897
- if (cachedVisibility) return cachedVisibility;
57898
- if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || symbol.declarationNode.id !== symbol.bindingIdentifier || !symbol.declarationNode.init) return "unknown";
57899
- if (!hasOnlyStaticObjectReferences(identifier)) return "unknown";
57900
- const initializer = stripParenExpression(symbol.declarationNode.init);
57901
- const nextVisitedSymbolIds = new Set(visitedSymbolIds);
57902
- nextVisitedSymbolIds.add(symbol.id);
57903
- if (isNodeOfType(initializer, "Identifier")) {
57904
- const visibility = classifyStaticSpreadObject(initializer, nextVisitedSymbolIds);
57905
- staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
57906
- return visibility;
57907
- }
57908
- if (!isNodeOfType(initializer, "ObjectExpression")) return "unknown";
57909
- let visibility = "non-visible";
57910
- for (const property of initializer.properties ?? []) {
57911
- const propertyName = getStaticObjectPropertyName(property);
57912
- if (!isNodeOfType(property, "Property") || !propertyName) {
57913
- visibility = "unknown";
57914
- break;
57915
- }
57916
- if (expressionReadsDerivedSymbol(context, property.value, stateDerivedSymbolIds) && !isNonVisibleJsxSpreadProperty(propertyName)) visibility = "visible";
57917
- }
57918
- staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
57919
- return visibility;
57920
- };
57921
- let hasNonAriaReference = false;
57922
- walkAst(componentBody, (node) => {
57923
- if (hasNonAriaReference) return false;
57924
- if (!isNodeOfType(node, "Identifier") || !stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) return;
57925
- if (findEnclosingFunction$1(node) !== componentFunction) return;
57926
- const parent = node.parent;
57927
- if (parent && (isNodeOfType(parent, "MemberExpression") && parent.property === node && !parent.computed || isNodeOfType(parent, "Property") && parent.key === node && !parent.computed)) return;
57928
- let cursor = parent;
57929
- while (cursor && cursor !== componentBody) {
57930
- if (isFunctionLike$1(cursor)) return;
57931
- if (isNodeOfType(cursor, "JSXSpreadAttribute")) {
57932
- if (isNodeOfType(node, "Identifier") && classifyStaticSpreadObject(node) === "visible") hasNonAriaReference = true;
57933
- return;
57934
- }
57935
- if (isNodeOfType(cursor, "JSXAttribute")) {
57936
- if (isEventHandlerAttribute(cursor)) return;
57937
- if (!isInsideIdOrAriaAttribute(node)) hasNonAriaReference = true;
57938
- return;
57939
- }
57940
- if (isNodeOfType(cursor, "ReturnStatement")) {
57941
- hasNonAriaReference = true;
57942
- return;
57943
- }
57944
- cursor = cursor.parent;
57945
- }
57946
- });
57947
- return hasNonAriaReference ? stateIdentifier.name : null;
57948
- };
57949
- const isExactViewportSubscriptionEffect = (context, effectCall, callback) => {
57950
- if (!isReactApiCall(effectCall, USE_EFFECT_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return false;
57951
- if (!isFunctionLike$1(callback) || callback.async || !isNodeOfType(callback.body, "BlockStatement")) return false;
57952
- const statements = getCallbackStatements(callback);
57953
- if (statements.length !== 4) return false;
57954
- const handlerDeclaration = statements[0];
57955
- if (!isNodeOfType(handlerDeclaration, "VariableDeclaration") || handlerDeclaration.kind !== "const" || handlerDeclaration.declarations?.length !== 1) return false;
57956
- const handlerDeclarator = handlerDeclaration.declarations[0];
57957
- if (!isNodeOfType(handlerDeclarator.id, "Identifier") || !isFunctionLike$1(handlerDeclarator.init)) return false;
57958
- const handlerStatements = getCallbackStatements(handlerDeclarator.init);
57959
- if (handlerStatements.length !== 1) return false;
57960
- const handlerSetter = getDirectWindowWidthSetter(context, unwrapReturnExpression(handlerStatements[0]));
57961
- const subscribedHandler = getResizeListenerHandler(context, statements[1], "addEventListener");
57962
- const immediateSetter = getDirectWindowWidthSetter(context, statements[2]);
57963
- const cleanupHandler = getCleanupResizeHandler(context, statements[3]);
57964
- if (!handlerSetter || !subscribedHandler || !immediateSetter || !cleanupHandler) return false;
57965
- const handlerSymbol = context.scopes.symbolFor(handlerDeclarator.id);
57966
- if (!handlerSymbol || context.scopes.symbolFor(subscribedHandler) !== handlerSymbol || context.scopes.symbolFor(cleanupHandler) !== handlerSymbol) return false;
57967
- if (!isNodeOfType(handlerSetter.callee, "Identifier") || !isNodeOfType(immediateSetter.callee, "Identifier") || context.scopes.symbolFor(handlerSetter.callee) !== context.scopes.symbolFor(immediateSetter.callee)) return false;
57968
- const componentFunction = findEnclosingFunction$1(effectCall);
57969
- if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return false;
57970
- return findExactViewportState(context, componentFunction, immediateSetter) !== null;
57971
- };
57972
56784
  const renderingHydrationNoFlicker = defineRule({
57973
56785
  id: "rendering-hydration-no-flicker",
57974
56786
  title: "useEffect setState flashes on mount",
@@ -57981,14 +56793,7 @@ const renderingHydrationNoFlicker = defineRule({
57981
56793
  if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
57982
56794
  const callback = getEffectCallback(node);
57983
56795
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
57984
- if (isExactViewportSubscriptionEffect(context, node, callback)) {
57985
- context.report({
57986
- node,
57987
- message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
57988
- });
57989
- return;
57990
- }
57991
- const bodyStatements = getCallbackStatements(callback);
56796
+ const bodyStatements = (isNodeOfType(callback.body, "BlockStatement") ? callback.body.body ?? [] : [callback.body]).filter((statement) => !isNoOpStatement(statement));
57992
56797
  if (bodyStatements.length !== 1) return;
57993
56798
  const soleStatement = bodyStatements[0];
57994
56799
  if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
@@ -67820,6 +66625,14 @@ const isStateKey = (key) => {
67820
66625
  if (isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value === "state";
67821
66626
  return false;
67822
66627
  };
66628
+ const findEnclosingClass = (node) => {
66629
+ let ancestor = node.parent;
66630
+ while (ancestor) {
66631
+ if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
66632
+ ancestor = ancestor.parent ?? null;
66633
+ }
66634
+ return null;
66635
+ };
67823
66636
  const isInConstructor = (node) => {
67824
66637
  let ancestor = node.parent;
67825
66638
  while (ancestor) {
@@ -72531,17 +71344,6 @@ const reactDoctorRules = [
72531
71344
  requires: [...new Set(["react", ...noRedundantShouldComponentUpdate.requires ?? []])]
72532
71345
  }
72533
71346
  },
72534
- {
72535
- key: "react-doctor/no-ref-callback-cleanup-before-react-19",
72536
- id: "no-ref-callback-cleanup-before-react-19",
72537
- source: "react-doctor",
72538
- originallyExternal: false,
72539
- rule: {
72540
- ...noRefCallbackCleanupBeforeReact19,
72541
- framework: "global",
72542
- category: "Bugs"
72543
- }
72544
- },
72545
71347
  {
72546
71348
  key: "react-doctor/no-ref-current-in-render",
72547
71349
  id: "no-ref-current-in-render",