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