oxlint-plugin-react-doctor 0.7.9-dev.093619c → 0.7.9-dev.11388bf
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +0 -46
- package/dist/index.js +103 -1301
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7637,7 +7637,6 @@ const areExpressionsStructurallyEqual = (a, b) => {
|
|
|
7637
7637
|
if (a.type !== b.type) return false;
|
|
7638
7638
|
if (isNodeOfType(a, "ThisExpression")) return true;
|
|
7639
7639
|
if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
|
|
7640
|
-
if (isNodeOfType(a, "PrivateIdentifier") && isNodeOfType(b, "PrivateIdentifier")) return a.name === b.name;
|
|
7641
7640
|
if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
|
|
7642
7641
|
if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
|
|
7643
7642
|
if (a.computed !== b.computed) return false;
|
|
@@ -8959,7 +8958,7 @@ const isReactNamespaceImport = (identifier, scopes) => {
|
|
|
8959
8958
|
if (!symbol || !isImportedFromReact(symbol)) return false;
|
|
8960
8959
|
return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
|
|
8961
8960
|
};
|
|
8962
|
-
const isReactNamespaceReceiver
|
|
8961
|
+
const isReactNamespaceReceiver = (receiver, scopes, options) => {
|
|
8963
8962
|
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
8964
8963
|
if (isReactNamespaceImport(receiver, scopes)) return true;
|
|
8965
8964
|
return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
|
|
@@ -8972,7 +8971,7 @@ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) =>
|
|
|
8972
8971
|
for (const property of pattern.properties) {
|
|
8973
8972
|
if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
|
|
8974
8973
|
const propertyName = getStaticPropertyKeyName(property);
|
|
8975
|
-
return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver
|
|
8974
|
+
return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver(stripParenExpression(symbol.initializer), scopes, options));
|
|
8976
8975
|
}
|
|
8977
8976
|
return false;
|
|
8978
8977
|
};
|
|
@@ -8996,7 +8995,7 @@ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds
|
|
|
8996
8995
|
return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
|
|
8997
8996
|
}
|
|
8998
8997
|
if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
|
|
8999
|
-
return isReactNamespaceReceiver
|
|
8998
|
+
return isReactNamespaceReceiver(stripParenExpression(callee.object), scopes, options);
|
|
9000
8999
|
};
|
|
9001
9000
|
//#endregion
|
|
9002
9001
|
//#region src/plugin/utils/is-proven-browser-api-receiver.ts
|
|
@@ -13652,7 +13651,7 @@ const getPromiseChainCallForCallback = (candidate) => {
|
|
|
13652
13651
|
if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
|
|
13653
13652
|
return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
|
|
13654
13653
|
};
|
|
13655
|
-
const
|
|
13654
|
+
const collectEffectInvokedFunctions = (effectCallback) => {
|
|
13656
13655
|
const invokedFunctions = new Set([effectCallback]);
|
|
13657
13656
|
const localFunctionBindings = /* @__PURE__ */ new Map();
|
|
13658
13657
|
const calledBindingNames = /* @__PURE__ */ new Set();
|
|
@@ -13686,14 +13685,12 @@ const collectInvokedFunctions = (effectCallback, includePromiseCallbacks) => {
|
|
|
13686
13685
|
calledBindingNames.add(callee.name);
|
|
13687
13686
|
return;
|
|
13688
13687
|
}
|
|
13689
|
-
if (
|
|
13688
|
+
if (isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
|
|
13690
13689
|
});
|
|
13691
13690
|
for (const calledName of calledBindingNames) enqueue(localFunctionBindings.get(calledName));
|
|
13692
13691
|
}
|
|
13693
13692
|
return invokedFunctions;
|
|
13694
13693
|
};
|
|
13695
|
-
const collectEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, true);
|
|
13696
|
-
const collectSynchronouslyEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, false);
|
|
13697
13694
|
//#endregion
|
|
13698
13695
|
//#region src/plugin/utils/is-react-hook-name.ts
|
|
13699
13696
|
const isReactHookName = (name) => {
|
|
@@ -14414,7 +14411,7 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
|
|
|
14414
14411
|
const callNode = findDirectCallForReference(reference.identifier);
|
|
14415
14412
|
return callNode ? [callNode] : [];
|
|
14416
14413
|
});
|
|
14417
|
-
if (invocationCalls.length !== 1
|
|
14414
|
+
if (invocationCalls.length !== 1) return null;
|
|
14418
14415
|
const invocationCall = invocationCalls[0];
|
|
14419
14416
|
return findEnclosingFunction$1(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
|
|
14420
14417
|
};
|
|
@@ -14701,108 +14698,7 @@ const hasPotentialInterruptionAfterGuard = (callback, guardState, usageNode, con
|
|
|
14701
14698
|
});
|
|
14702
14699
|
return hasPotentialInterruption;
|
|
14703
14700
|
};
|
|
14704
|
-
const getNumericReactRefCurrentKey = (expression, context) => {
|
|
14705
|
-
const refSymbol = resolveReactRefSymbol(stripParenExpression(expression), context.scopes);
|
|
14706
|
-
const initializer = refSymbol?.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
14707
|
-
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
14708
|
-
const initialValue = initializer.arguments?.[0] ? stripParenExpression(initializer.arguments[0]) : null;
|
|
14709
|
-
if (!isNodeOfType(initialValue, "Literal") || typeof initialValue.value !== "number") return null;
|
|
14710
|
-
return resolveExpressionKey(expression, context);
|
|
14711
|
-
};
|
|
14712
|
-
const getBlockingGenerationKey = (expression, context) => {
|
|
14713
|
-
const test = stripParenExpression(expression);
|
|
14714
|
-
if (isNodeOfType(test, "LogicalExpression") && test.operator === "||") return getBlockingGenerationKey(test.left, context) ?? getBlockingGenerationKey(test.right, context);
|
|
14715
|
-
if (!isNodeOfType(test, "BinaryExpression") || test.operator !== "!==" && test.operator !== "!=") return null;
|
|
14716
|
-
const leftKey = getNumericReactRefCurrentKey(test.left, context);
|
|
14717
|
-
const rightKey = getNumericReactRefCurrentKey(test.right, context);
|
|
14718
|
-
const snapshotExpression = leftKey ? stripParenExpression(test.right) : stripParenExpression(test.left);
|
|
14719
|
-
const key = leftKey ?? rightKey;
|
|
14720
|
-
return key && isNodeOfType(snapshotExpression, "Identifier") ? key : null;
|
|
14721
|
-
};
|
|
14722
|
-
const findGenerationGuardKeyForDeferredUsage = (usageFunction, usageNode, context) => {
|
|
14723
|
-
if (!isFunctionLike$1(usageFunction)) return null;
|
|
14724
|
-
let generationKey = null;
|
|
14725
|
-
walkAst(usageFunction.body, (child) => {
|
|
14726
|
-
if (generationKey) return false;
|
|
14727
|
-
if (child !== usageFunction.body && isFunctionLike$1(child)) return false;
|
|
14728
|
-
if (!isNodeOfType(child, "IfStatement") || child.alternate) return;
|
|
14729
|
-
const key = getBlockingGenerationKey(child.test, context);
|
|
14730
|
-
if (!key || canNodeReachLaterNodeWithinFunction(child.consequent, usageNode, usageFunction, context) || !doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], usageFunction, context)) return;
|
|
14731
|
-
generationKey = key;
|
|
14732
|
-
});
|
|
14733
|
-
return generationKey;
|
|
14734
|
-
};
|
|
14735
|
-
const isGenerationAdvance = (node, generationKey, context) => {
|
|
14736
|
-
if (isNodeOfType(node, "UpdateExpression") && resolveExpressionKey(node.argument, context) === generationKey) return true;
|
|
14737
|
-
if (!isNodeOfType(node, "AssignmentExpression") || resolveExpressionKey(node.left, context) !== generationKey || node.operator !== "+=" && node.operator !== "-=") return false;
|
|
14738
|
-
const amount = stripParenExpression(node.right);
|
|
14739
|
-
return isNodeOfType(amount, "Literal") && typeof amount.value === "number" && amount.value !== 0;
|
|
14740
|
-
};
|
|
14741
|
-
const functionAdvancesGeneration = (owner, generationKey, context) => {
|
|
14742
|
-
if (!isFunctionLike$1(owner)) return false;
|
|
14743
|
-
let didAdvanceGeneration = false;
|
|
14744
|
-
walkAst(owner.body, (child) => {
|
|
14745
|
-
if (didAdvanceGeneration) return false;
|
|
14746
|
-
if (child !== owner.body && isFunctionLike$1(child)) return false;
|
|
14747
|
-
if (isGenerationAdvance(child, generationKey, context)) {
|
|
14748
|
-
didAdvanceGeneration = true;
|
|
14749
|
-
return false;
|
|
14750
|
-
}
|
|
14751
|
-
});
|
|
14752
|
-
return didAdvanceGeneration;
|
|
14753
|
-
};
|
|
14754
|
-
const cleanupReturnsReleaseUsage = (cleanupReturns, usage, context) => cleanupReturns.length > 0 && cleanupReturns.every((cleanupReturn) => {
|
|
14755
|
-
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
|
|
14756
|
-
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
14757
|
-
return Boolean(cleanupFunction && isFunctionLike$1(cleanupFunction) && doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context));
|
|
14758
|
-
});
|
|
14759
|
-
const getOwnedFunctionReference = (reference, usageFunction, usageNode, callback, cleanupReturns, context) => {
|
|
14760
|
-
const directCall = findDirectCallForReference(reference);
|
|
14761
|
-
if (directCall) {
|
|
14762
|
-
const referenceOwner = findEnclosingFunction$1(directCall);
|
|
14763
|
-
if (referenceOwner && referenceOwner !== usageFunction && collectSynchronouslyEffectInvokedFunctions(callback).has(referenceOwner)) return { generationKey: null };
|
|
14764
|
-
const generationKey = referenceOwner ? findGenerationGuardKeyForDeferredUsage(referenceOwner, directCall, context) : null;
|
|
14765
|
-
return generationKey ? { generationKey } : null;
|
|
14766
|
-
}
|
|
14767
|
-
const referenceRoot = findTransparentExpressionRoot(reference);
|
|
14768
|
-
const schedulerCall = referenceRoot.parent;
|
|
14769
|
-
if (!isNodeOfType(schedulerCall, "CallExpression") || !schedulerCall.arguments.some((argument) => argument === referenceRoot) || !isNodeOfType(schedulerCall.callee, "Identifier") || schedulerCall.callee.name !== "setTimeout" || !context.scopes.isGlobalReference(schedulerCall.callee)) return null;
|
|
14770
|
-
const schedulerUsage = {
|
|
14771
|
-
kind: "timer",
|
|
14772
|
-
node: schedulerCall,
|
|
14773
|
-
resourceName: schedulerCall.callee.name,
|
|
14774
|
-
handleKey: findAssignedResourceKey(schedulerCall, context),
|
|
14775
|
-
receiverKey: null,
|
|
14776
|
-
registrationVerbName: schedulerCall.callee.name,
|
|
14777
|
-
eventKey: null,
|
|
14778
|
-
handlerKey: null
|
|
14779
|
-
};
|
|
14780
|
-
const generationKey = findGenerationGuardKeyForDeferredUsage(usageFunction, usageNode, context);
|
|
14781
|
-
return schedulerUsage.handleKey !== null && cleanupReturnsReleaseUsage(cleanupReturns, schedulerUsage, context) && generationKey ? { generationKey } : null;
|
|
14782
|
-
};
|
|
14783
|
-
const hasGuardedRefOwnedNestedCleanup = (callback, usage, cleanupReturns, context) => {
|
|
14784
|
-
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
14785
|
-
const usageExpression = findTransparentExpressionRoot(usage.node);
|
|
14786
|
-
const usageAssignment = usageExpression.parent;
|
|
14787
|
-
if (usage.kind !== "subscribe" && usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usageAssignment, "AssignmentExpression") || usageAssignment.operator !== "=" || usageAssignment.right !== usageExpression || !resolveReactRefSymbol(stripParenExpression(usageAssignment.left), context.scopes) || !collectSynchronouslyEffectInvokedFunctions(callback).has(usageFunction) || !cleanupReturnsReleaseUsage(cleanupReturns, usage, context) || !doMatchingNodesCoverEveryPathFromFunctionEntry(callback, cleanupReturns, context)) return false;
|
|
14788
|
-
const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
|
|
14789
|
-
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
|
|
14790
|
-
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
14791
|
-
return cleanupFunction && isFunctionLike$1(cleanupFunction) ? [cleanupFunction] : [];
|
|
14792
|
-
});
|
|
14793
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(usageFunction);
|
|
14794
|
-
const functionSymbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
|
|
14795
|
-
if (!functionSymbol || functionSymbol.references.length === 0) return false;
|
|
14796
|
-
const ownedReferences = functionSymbol.references.map((reference) => getOwnedFunctionReference(reference.identifier, usageFunction, usage.node, callback, cleanupReturns, context));
|
|
14797
|
-
if (ownedReferences.some((reference) => reference === null)) return false;
|
|
14798
|
-
const generationKeys = new Set(ownedReferences.flatMap((reference) => reference?.generationKey ? [reference.generationKey] : []));
|
|
14799
|
-
if (generationKeys.size !== 1) return false;
|
|
14800
|
-
const generationKey = generationKeys.values().next().value;
|
|
14801
|
-
if (typeof generationKey !== "string") return false;
|
|
14802
|
-
return [...collectSynchronouslyEffectInvokedFunctions(callback), ...cleanupFunctions].some((owner) => functionAdvancesGeneration(owner, generationKey, context));
|
|
14803
|
-
};
|
|
14804
14701
|
const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
|
|
14805
|
-
if (hasGuardedRefOwnedNestedCleanup(callback, usage, cleanupReturns, context)) return true;
|
|
14806
14702
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
14807
14703
|
const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
|
|
14808
14704
|
if (usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usage.node, "CallExpression") || !isNodeOfType(usage.node.callee, "Identifier") || !context.scopes.isGlobalReference(usage.node.callee) || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
|
|
@@ -15014,7 +14910,6 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15014
14910
|
if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
|
|
15015
14911
|
if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
|
|
15016
14912
|
if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) return false;
|
|
15017
|
-
if (usage.registrationVerbName === "subscribe" && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub") && usage.handleKey !== null && resolveExpressionKey(callNode.arguments?.[0], context) === usage.handleKey) return true;
|
|
15018
14913
|
const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
|
|
15019
14914
|
if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
|
|
15020
14915
|
const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
|
|
@@ -15051,8 +14946,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15051
14946
|
const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
|
|
15052
14947
|
if (!releaseHandler) return releaseVerbName === "off";
|
|
15053
14948
|
const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
|
|
15054
|
-
|
|
15055
|
-
return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
|
|
14949
|
+
return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey;
|
|
15056
14950
|
}
|
|
15057
14951
|
if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
|
|
15058
14952
|
return true;
|
|
@@ -15077,188 +14971,13 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
|
|
|
15077
14971
|
if (!symbol) return false;
|
|
15078
14972
|
return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
|
|
15079
14973
|
};
|
|
15080
|
-
const isJsxRefAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && node.name.name === "ref";
|
|
15081
|
-
const isFunctionForwardedToReactRef = (functionNode, context) => {
|
|
15082
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
15083
|
-
if (!bindingIdentifier) return false;
|
|
15084
|
-
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
15085
|
-
if (!symbol) return false;
|
|
15086
|
-
return symbol.references.some((reference) => {
|
|
15087
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
15088
|
-
const expressionContainer = referenceRoot.parent;
|
|
15089
|
-
return Boolean(isNodeOfType(expressionContainer, "JSXExpressionContainer") && expressionContainer.expression === referenceRoot && isJsxRefAttribute(expressionContainer.parent));
|
|
15090
|
-
});
|
|
15091
|
-
};
|
|
15092
|
-
const findRetainedDisposerStorages = (disposerFunction, usage, context) => {
|
|
15093
|
-
if (!isFunctionLike$1(disposerFunction) || disposerFunction.async || disposerFunction.generator) return [];
|
|
15094
|
-
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15095
|
-
if (!usageFunction || !isFunctionLike$1(usageFunction)) return [];
|
|
15096
|
-
const assignments = /* @__PURE__ */ new Map();
|
|
15097
|
-
const collectAssignment = (expression) => {
|
|
15098
|
-
const expressionRoot = findTransparentExpressionRoot(expression);
|
|
15099
|
-
const assignment = expressionRoot.parent;
|
|
15100
|
-
if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.right !== expressionRoot) return;
|
|
15101
|
-
const refSymbol = resolveReactRefSymbol(stripParenExpression(assignment.left), context.scopes);
|
|
15102
|
-
const refCurrentKey = resolveExpressionKey(assignment.left, context);
|
|
15103
|
-
const retainedFunction = findEnclosingFunction$1(assignment);
|
|
15104
|
-
const assignmentStart = getRangeStart(assignment);
|
|
15105
|
-
if (!refSymbol || !refCurrentKey || !retainedFunction || retainedFunction !== usageFunction || assignmentStart === null) return;
|
|
15106
|
-
assignments.set(assignmentStart, {
|
|
15107
|
-
assignmentNode: assignment,
|
|
15108
|
-
refCurrentKey,
|
|
15109
|
-
retainedFunction
|
|
15110
|
-
});
|
|
15111
|
-
};
|
|
15112
|
-
collectAssignment(disposerFunction);
|
|
15113
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(disposerFunction);
|
|
15114
|
-
const symbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
|
|
15115
|
-
for (const reference of symbol?.references ?? []) collectAssignment(reference.identifier);
|
|
15116
|
-
walkAst(usageFunction.body, (child) => {
|
|
15117
|
-
if (child !== usageFunction.body && isFunctionLike$1(child)) return false;
|
|
15118
|
-
if (isNodeOfType(child, "AssignmentExpression") && resolveStableValue(child.right, context) === disposerFunction) collectAssignment(child.right);
|
|
15119
|
-
});
|
|
15120
|
-
return [...assignments.values()];
|
|
15121
|
-
};
|
|
15122
|
-
const isRetainedDisposerStorageEstablished = (storage, usage, context) => doMatchingNodesCoverEveryPathBeforeUsage(usage.node, [storage.assignmentNode], storage.retainedFunction, context) || doMatchingNodesCoverEveryPathAfterUsage(usage.node, [storage.assignmentNode], context);
|
|
15123
|
-
const hasUnsafeRetainedDisposerOverwrite = (storage, usage, context) => {
|
|
15124
|
-
let hasUnsafeOverwrite = false;
|
|
15125
|
-
walkAst(storage.retainedFunction.body, (child) => {
|
|
15126
|
-
if (hasUnsafeOverwrite) return false;
|
|
15127
|
-
if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
|
|
15128
|
-
if (!isNodeOfType(child, "AssignmentExpression") || child === storage.assignmentNode || resolveExpressionKey(child.left, context) !== storage.refCurrentKey || !canNodeReachLaterNodeWithinFunction(usage.node, child, storage.retainedFunction, context)) return;
|
|
15129
|
-
const storedValue = resolveStableValue(child.right, context);
|
|
15130
|
-
if (!storedValue || !isFunctionLike$1(storedValue) || !doesCleanupFunctionReleaseUsage(storedValue, usage, context)) {
|
|
15131
|
-
hasUnsafeOverwrite = true;
|
|
15132
|
-
return false;
|
|
15133
|
-
}
|
|
15134
|
-
});
|
|
15135
|
-
return hasUnsafeOverwrite;
|
|
15136
|
-
};
|
|
15137
|
-
const hasEffectCleanupInvocation = (storage, usage, context) => {
|
|
15138
|
-
const componentFunction = findEnclosingFunction$1(storage.retainedFunction);
|
|
15139
|
-
if (!componentFunction || !isFunctionLike$1(componentFunction)) return false;
|
|
15140
|
-
const cleanupFunctionInvokesRef = (cleanupFunction) => {
|
|
15141
|
-
if (!isFunctionLike$1(cleanupFunction)) return false;
|
|
15142
|
-
let didFindCleanupCall = false;
|
|
15143
|
-
walkAst(cleanupFunction.body, (child) => {
|
|
15144
|
-
if (didFindCleanupCall) return false;
|
|
15145
|
-
if (child !== cleanupFunction.body && isFunctionLike$1(child)) return false;
|
|
15146
|
-
if (isNodeOfType(child, "CallExpression") && resolveExpressionKey(child.callee, context) === storage.refCurrentKey) {
|
|
15147
|
-
const callRoot = findTransparentExpressionRoot(child);
|
|
15148
|
-
const callStatement = callRoot.parent;
|
|
15149
|
-
const isDirectBlockStatement = isNodeOfType(cleanupFunction.body, "BlockStatement") && isNodeOfType(callStatement, "ExpressionStatement") && callStatement.parent === cleanupFunction.body;
|
|
15150
|
-
const isConciseBody = cleanupFunction.body === callRoot;
|
|
15151
|
-
if ((isDirectBlockStatement || isConciseBody) && !hasUnprovenReturnBeforeRefOwnedRelease(cleanupFunction, child, storage.refCurrentKey, context)) {
|
|
15152
|
-
didFindCleanupCall = true;
|
|
15153
|
-
return false;
|
|
15154
|
-
}
|
|
15155
|
-
}
|
|
15156
|
-
});
|
|
15157
|
-
return didFindCleanupCall;
|
|
15158
|
-
};
|
|
15159
|
-
const effectReturnsCleanup = (effectCallback) => {
|
|
15160
|
-
if (!isFunctionLike$1(effectCallback)) return false;
|
|
15161
|
-
if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
|
|
15162
|
-
const cleanupFunction = resolveRefOwnedCleanupFunction(effectCallback.body, context);
|
|
15163
|
-
return Boolean(cleanupFunction && cleanupFunctionInvokesRef(cleanupFunction));
|
|
15164
|
-
}
|
|
15165
|
-
const matchingReturns = [];
|
|
15166
|
-
walkInsideStatementBlocks(effectCallback.body, (child) => {
|
|
15167
|
-
if (!isNodeOfType(child, "ReturnStatement") || !child.argument) return;
|
|
15168
|
-
const cleanupFunction = resolveRefOwnedCleanupFunction(child.argument, context);
|
|
15169
|
-
if (!cleanupFunction || !cleanupFunctionInvokesRef(cleanupFunction)) return;
|
|
15170
|
-
matchingReturns.push(child);
|
|
15171
|
-
});
|
|
15172
|
-
return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
|
|
15173
|
-
};
|
|
15174
|
-
let didFindInvocation = false;
|
|
15175
|
-
walkAst(componentFunction.body, (child) => {
|
|
15176
|
-
if (didFindInvocation) return false;
|
|
15177
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
|
|
15178
|
-
const effectCallback = getEffectCallback(child);
|
|
15179
|
-
if (effectCallback && effectReturnsCleanup(effectCallback)) {
|
|
15180
|
-
didFindInvocation = true;
|
|
15181
|
-
return false;
|
|
15182
|
-
}
|
|
15183
|
-
});
|
|
15184
|
-
return didFindInvocation;
|
|
15185
|
-
};
|
|
15186
|
-
const hasCallbackRefReplacementInvocation = (storage, usage, context) => {
|
|
15187
|
-
const isReturnedCallbackRefShape = () => {
|
|
15188
|
-
if (!isFunctionLike$1(storage.retainedFunction)) return false;
|
|
15189
|
-
const callbackCall = findTransparentExpressionRoot(storage.retainedFunction).parent;
|
|
15190
|
-
if (!isNodeOfType(callbackCall, "CallExpression") || !isReactApiCall(callbackCall, "useCallback", context.scopes)) return false;
|
|
15191
|
-
const nodeParameter = storage.retainedFunction.params?.[0];
|
|
15192
|
-
const nodeParameterKey = resolveExpressionKey(nodeParameter, context);
|
|
15193
|
-
if (!nodeParameterKey || usage.receiverKey !== nodeParameterKey) return false;
|
|
15194
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(storage.retainedFunction);
|
|
15195
|
-
const symbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
|
|
15196
|
-
if (!Boolean(symbol?.references.some((reference) => {
|
|
15197
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
15198
|
-
const property = referenceRoot.parent;
|
|
15199
|
-
if (!isNodeOfType(property, "Property") || property.value !== referenceRoot || !isNodeOfType(property.parent, "ObjectExpression")) return false;
|
|
15200
|
-
const returnedObject = findTransparentExpressionRoot(property.parent);
|
|
15201
|
-
const returnStatement = returnedObject.parent;
|
|
15202
|
-
if (!isNodeOfType(returnStatement, "ReturnStatement") || returnStatement.argument !== returnedObject) return false;
|
|
15203
|
-
const hookFunction = findEnclosingFunction$1(returnStatement);
|
|
15204
|
-
return Boolean(hookFunction && getFunctionBindingIdentifier$1(hookFunction)?.name.startsWith("use"));
|
|
15205
|
-
}))) return false;
|
|
15206
|
-
const usageStart = getRangeStart(usage.node);
|
|
15207
|
-
if (usageStart === null) return false;
|
|
15208
|
-
let hasNullExit = false;
|
|
15209
|
-
walkAst(storage.retainedFunction.body, (child) => {
|
|
15210
|
-
if (hasNullExit) return false;
|
|
15211
|
-
if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
|
|
15212
|
-
if (!isNodeOfType(child, "IfStatement") || (getRangeStart(child) ?? usageStart) >= usageStart) return;
|
|
15213
|
-
const test = stripParenExpression(child.test);
|
|
15214
|
-
if (!isNodeOfType(test, "UnaryExpression") || test.operator !== "!" || resolveExpressionKey(test.argument, context) !== nodeParameterKey) return;
|
|
15215
|
-
const consequent = child.consequent;
|
|
15216
|
-
hasNullExit = isNodeOfType(consequent, "ReturnStatement") || isNodeOfType(consequent, "BlockStatement") && consequent.body.some((statement) => isNodeOfType(statement, "ReturnStatement"));
|
|
15217
|
-
if (hasNullExit) return false;
|
|
15218
|
-
});
|
|
15219
|
-
return hasNullExit;
|
|
15220
|
-
};
|
|
15221
|
-
if (!isFunctionForwardedToReactRef(storage.retainedFunction, context) && !isReturnedCallbackRefShape()) return false;
|
|
15222
|
-
const cleanupCalls = [];
|
|
15223
|
-
walkAst(storage.retainedFunction.body, (child) => {
|
|
15224
|
-
if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
|
|
15225
|
-
if (isNodeOfType(child, "CallExpression") && resolveExpressionKey(child.callee, context) === storage.refCurrentKey) cleanupCalls.push(child);
|
|
15226
|
-
});
|
|
15227
|
-
return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, cleanupCalls, storage.retainedFunction, context);
|
|
15228
|
-
};
|
|
15229
|
-
const isRetainedDisposerRefRelease = (releaseNode, usage, context) => {
|
|
15230
|
-
const disposerFunction = findEnclosingFunction$1(releaseNode);
|
|
15231
|
-
if (!disposerFunction) return false;
|
|
15232
|
-
return findRetainedDisposerStorages(disposerFunction, usage, context).some((storage) => isRetainedDisposerStorageEstablished(storage, usage, context) && !hasUnsafeRetainedDisposerOverwrite(storage, usage, context) && (hasEffectCleanupInvocation(storage, usage, context) || hasCallbackRefReplacementInvocation(storage, usage, context)));
|
|
15233
|
-
};
|
|
15234
|
-
const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
|
|
15235
|
-
if (usage.kind !== "subscribe" || usage.registrationVerbName !== "addEventListener" || usage.receiverKey === null || usage.eventKey === null || !isNodeOfType(usage.node, "CallExpression") || !isFunctionLike$1(releaseFunction) || releaseFunction.async || releaseFunction.generator || !isNodeOfType(releaseFunction.body, "BlockStatement") || !doMatchingNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseNode], context)) return false;
|
|
15236
|
-
const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15237
|
-
const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
|
|
15238
|
-
if (!isNodeOfType(releaseCall, "CallExpression")) return false;
|
|
15239
|
-
const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
|
|
15240
|
-
if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
|
|
15241
|
-
const ownerFunction = findEnclosingFunction$1(releaseFunction);
|
|
15242
|
-
if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
|
|
15243
|
-
const triggerRegistrations = [];
|
|
15244
|
-
walkAst(ownerFunction.body, (child) => {
|
|
15245
|
-
if (child !== ownerFunction.body && isFunctionLike$1(child)) return false;
|
|
15246
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
15247
|
-
const registrationDetails = getCallRegistrationDetails(child, context);
|
|
15248
|
-
if (registrationDetails.registrationVerbName === "addEventListener" && registrationDetails.receiverKey === usage.receiverKey && resolveStableValue(child.arguments?.[1], context) === releaseFunction) triggerRegistrations.push(child);
|
|
15249
|
-
});
|
|
15250
|
-
if (triggerRegistrations.some((triggerRegistration) => triggerRegistration === usage.node)) return true;
|
|
15251
|
-
return doMatchingNodesCoverEveryPathAfterUsage(usage.node, triggerRegistrations, context) || doMatchingNodesCoverEveryPathBeforeUsage(usage.node, triggerRegistrations, ownerFunction, context);
|
|
15252
|
-
};
|
|
15253
14974
|
const isReleaseReachableForUsage = (releaseNode, usage, context) => {
|
|
15254
14975
|
if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
|
|
15255
14976
|
const releaseFunction = findEnclosingFunction$1(releaseNode);
|
|
15256
14977
|
if (!releaseFunction) return true;
|
|
15257
14978
|
if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
|
|
15258
|
-
if (isRetainedDisposerRefRelease(releaseNode, usage, context)) return true;
|
|
15259
14979
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15260
14980
|
if (usageFunction && isFunctionLike$1(usageFunction) && getAssignedReactRefSymbol(usageFunction, context) && isCleanupFunctionReferencedByReturn(usageFunction, releaseFunction, context)) return isReactRefCallbackCleanupOwnedByEffect(usageFunction, releaseFunction, usage, context);
|
|
15261
|
-
if (isSelfReleasingListenerRelease(releaseNode, releaseFunction, usage, context)) return true;
|
|
15262
14981
|
return isPotentiallyReachableFunction(releaseFunction, context);
|
|
15263
14982
|
};
|
|
15264
14983
|
const fileContainsReleaseForUsage = (usage, context) => {
|
|
@@ -17392,7 +17111,7 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allow
|
|
|
17392
17111
|
keys.add(depKey);
|
|
17393
17112
|
continue;
|
|
17394
17113
|
}
|
|
17395
|
-
const identitySourceKeys =
|
|
17114
|
+
const identitySourceKeys = resolveReactiveIdentitySourceKeys(symbol, scopes);
|
|
17396
17115
|
if (identitySourceKeys) {
|
|
17397
17116
|
if (identitySourceKeys.size === 0) stableCapturedNames.add(depKey);
|
|
17398
17117
|
for (const identitySourceKey of identitySourceKeys) keys.add(identitySourceKey);
|
|
@@ -17475,161 +17194,6 @@ const resolveReactiveIdentitySourceKeys = (symbol, scopes) => {
|
|
|
17475
17194
|
if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier || symbol.references.some((reference) => reference.flag !== "read")) return null;
|
|
17476
17195
|
return resolveIdentitySourceKeysFromExpression(symbol.initializer, scopes, new Set([symbol.id]));
|
|
17477
17196
|
};
|
|
17478
|
-
const isPureDerivedExpression = (expression) => {
|
|
17479
|
-
const candidate = unwrapExpression$3(expression);
|
|
17480
|
-
if (isNodeOfType(candidate, "Literal") || isNodeOfType(candidate, "Identifier")) return true;
|
|
17481
|
-
if (isNodeOfType(candidate, "MemberExpression")) return isPureDerivedExpression(candidate.object) && (!candidate.computed || isPureDerivedExpression(candidate.property));
|
|
17482
|
-
if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return isPureDerivedExpression(candidate.left) && isPureDerivedExpression(candidate.right);
|
|
17483
|
-
if (isNodeOfType(candidate, "UnaryExpression")) return candidate.operator !== "delete" && isPureDerivedExpression(candidate.argument);
|
|
17484
|
-
if (isNodeOfType(candidate, "ConditionalExpression")) return isPureDerivedExpression(candidate.test) && isPureDerivedExpression(candidate.consequent) && isPureDerivedExpression(candidate.alternate);
|
|
17485
|
-
if (isNodeOfType(candidate, "TemplateLiteral")) return candidate.expressions.every((nestedExpression) => isPureDerivedExpression(nestedExpression));
|
|
17486
|
-
return false;
|
|
17487
|
-
};
|
|
17488
|
-
const isPureDerivedStatement = (statement) => {
|
|
17489
|
-
if (isNodeOfType(statement, "BlockStatement")) return statement.body.every((nestedStatement) => isPureDerivedStatement(nestedStatement));
|
|
17490
|
-
if (isNodeOfType(statement, "ReturnStatement")) return !statement.argument || isPureDerivedExpression(statement.argument);
|
|
17491
|
-
if (isNodeOfType(statement, "IfStatement")) return isPureDerivedExpression(statement.test) && isPureDerivedStatement(statement.consequent) && (!statement.alternate || isPureDerivedStatement(statement.alternate));
|
|
17492
|
-
return false;
|
|
17493
|
-
};
|
|
17494
|
-
const isPureDerivedFunction = (functionNode) => {
|
|
17495
|
-
if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return false;
|
|
17496
|
-
if (functionNode.async || functionNode.generator) return false;
|
|
17497
|
-
return isNodeOfType(functionNode.body, "BlockStatement") ? isPureDerivedStatement(functionNode.body) : isPureDerivedExpression(functionNode.body);
|
|
17498
|
-
};
|
|
17499
|
-
const resolvePureCalledFunctionSourceKeys = (reference, symbol, scopes) => {
|
|
17500
|
-
if (symbol.references.some((symbolReference) => symbolReference.flag !== "read")) return null;
|
|
17501
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
17502
|
-
const callExpression = referenceRoot.parent;
|
|
17503
|
-
if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== referenceRoot) return null;
|
|
17504
|
-
const functionNode = getFunctionValueNode(symbol);
|
|
17505
|
-
if (!functionNode || !isPureDerivedFunction(functionNode)) return null;
|
|
17506
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17507
|
-
for (const capturedReference of closureCaptures(functionNode, scopes)) {
|
|
17508
|
-
const capturedSymbol = capturedReference.resolvedSymbol;
|
|
17509
|
-
if (!capturedSymbol || capturedSymbol.id === symbol.id) continue;
|
|
17510
|
-
if (isOutsideAllFunctions(capturedSymbol) || symbolHasStableValue(capturedSymbol, scopes)) continue;
|
|
17511
|
-
const capturedKey = computeDepKey(capturedReference);
|
|
17512
|
-
if (!capturedKey) return null;
|
|
17513
|
-
if (capturedKey === capturedSymbol.name) {
|
|
17514
|
-
const nestedSourceKeys = resolveReactiveIdentitySourceKeys(capturedSymbol, scopes);
|
|
17515
|
-
if (nestedSourceKeys) {
|
|
17516
|
-
for (const nestedSourceKey of nestedSourceKeys) sourceKeys.add(nestedSourceKey);
|
|
17517
|
-
continue;
|
|
17518
|
-
}
|
|
17519
|
-
}
|
|
17520
|
-
sourceKeys.add(capturedKey);
|
|
17521
|
-
}
|
|
17522
|
-
return sourceKeys.size > 0 ? sourceKeys : null;
|
|
17523
|
-
};
|
|
17524
|
-
const mergeDerivedExpressionSourceKeys = (expressions, scopes, visitedSymbolIds) => {
|
|
17525
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17526
|
-
for (const expression of expressions) {
|
|
17527
|
-
const expressionSourceKeys = resolveDerivedExpressionSourceKeys(expression, scopes, visitedSymbolIds);
|
|
17528
|
-
if (!expressionSourceKeys) return null;
|
|
17529
|
-
for (const expressionSourceKey of expressionSourceKeys) sourceKeys.add(expressionSourceKey);
|
|
17530
|
-
}
|
|
17531
|
-
return sourceKeys;
|
|
17532
|
-
};
|
|
17533
|
-
const resolveDerivedExpressionSourceKeys = (expression, scopes, visitedSymbolIds) => {
|
|
17534
|
-
const candidate = unwrapExpression$3(expression);
|
|
17535
|
-
if (isNodeOfType(candidate, "Literal")) return /* @__PURE__ */ new Set();
|
|
17536
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
17537
|
-
if (scopes.isGlobalReference(candidate)) return /* @__PURE__ */ new Set();
|
|
17538
|
-
const sourceSymbol = scopes.symbolFor(candidate);
|
|
17539
|
-
if (!sourceSymbol) return null;
|
|
17540
|
-
if (isOutsideAllFunctions(sourceSymbol) || symbolHasStableValue(sourceSymbol, scopes)) return /* @__PURE__ */ new Set();
|
|
17541
|
-
if (sourceSymbol.kind === "const" && sourceSymbol.initializer && isNodeOfType(sourceSymbol.declarationNode, "VariableDeclarator") && sourceSymbol.declarationNode.id === sourceSymbol.bindingIdentifier && sourceSymbol.references.every((sourceReference) => sourceReference.flag === "read") && !visitedSymbolIds.has(sourceSymbol.id)) {
|
|
17542
|
-
visitedSymbolIds.add(sourceSymbol.id);
|
|
17543
|
-
const sourceKeys = resolveDerivedExpressionSourceKeys(sourceSymbol.initializer, scopes, visitedSymbolIds);
|
|
17544
|
-
visitedSymbolIds.delete(sourceSymbol.id);
|
|
17545
|
-
if (sourceKeys) return sourceKeys;
|
|
17546
|
-
}
|
|
17547
|
-
return new Set([sourceSymbol.name]);
|
|
17548
|
-
}
|
|
17549
|
-
if (isNodeOfType(candidate, "MemberExpression")) {
|
|
17550
|
-
if (hasComputedMemberExpression(candidate)) return null;
|
|
17551
|
-
const sourceKey = stringifyMemberChain(candidate);
|
|
17552
|
-
const rootIdentifier = getMemberRootIdentifier(candidate);
|
|
17553
|
-
const rootSymbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
|
|
17554
|
-
if (!sourceKey || !rootSymbol) return null;
|
|
17555
|
-
if (isOutsideAllFunctions(rootSymbol) || symbolHasStableValue(rootSymbol, scopes)) return /* @__PURE__ */ new Set();
|
|
17556
|
-
return new Set([sourceKey]);
|
|
17557
|
-
}
|
|
17558
|
-
if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return mergeDerivedExpressionSourceKeys([candidate.left, candidate.right], scopes, visitedSymbolIds);
|
|
17559
|
-
if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator !== "delete") return resolveDerivedExpressionSourceKeys(candidate.argument, scopes, visitedSymbolIds);
|
|
17560
|
-
if (isNodeOfType(candidate, "ConditionalExpression")) return mergeDerivedExpressionSourceKeys([
|
|
17561
|
-
candidate.test,
|
|
17562
|
-
candidate.consequent,
|
|
17563
|
-
candidate.alternate
|
|
17564
|
-
], scopes, visitedSymbolIds);
|
|
17565
|
-
if (isNodeOfType(candidate, "TemplateLiteral")) return mergeDerivedExpressionSourceKeys(candidate.expressions, scopes, visitedSymbolIds);
|
|
17566
|
-
if (isNodeOfType(candidate, "NewExpression")) {
|
|
17567
|
-
const callee = unwrapExpression$3(candidate.callee);
|
|
17568
|
-
if (!isNodeOfType(callee, "Identifier") || callee.name !== "Error" || !scopes.isGlobalReference(callee)) return null;
|
|
17569
|
-
const argumentsToAnalyze = [];
|
|
17570
|
-
for (const argument of candidate.arguments) {
|
|
17571
|
-
if (!isAstNode(argument) || isNodeOfType(argument, "SpreadElement")) return null;
|
|
17572
|
-
argumentsToAnalyze.push(argument);
|
|
17573
|
-
}
|
|
17574
|
-
return mergeDerivedExpressionSourceKeys(argumentsToAnalyze, scopes, visitedSymbolIds);
|
|
17575
|
-
}
|
|
17576
|
-
return null;
|
|
17577
|
-
};
|
|
17578
|
-
const resolveWriteControlSourceKeys = (assignment, boundaryFunction, scopes) => {
|
|
17579
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17580
|
-
let currentNode = assignment;
|
|
17581
|
-
while (currentNode.parent && currentNode.parent !== boundaryFunction) {
|
|
17582
|
-
const parentNode = currentNode.parent;
|
|
17583
|
-
if (isNodeOfType(parentNode, "IfStatement")) {
|
|
17584
|
-
if (parentNode.test === currentNode) return null;
|
|
17585
|
-
const testSourceKeys = resolveDerivedExpressionSourceKeys(parentNode.test, scopes, /* @__PURE__ */ new Set());
|
|
17586
|
-
if (!testSourceKeys) return null;
|
|
17587
|
-
for (const testSourceKey of testSourceKeys) sourceKeys.add(testSourceKey);
|
|
17588
|
-
} else if (!isNodeOfType(parentNode, "ExpressionStatement") && !isNodeOfType(parentNode, "BlockStatement")) return null;
|
|
17589
|
-
currentNode = parentNode;
|
|
17590
|
-
}
|
|
17591
|
-
return currentNode.parent === boundaryFunction ? sourceKeys : null;
|
|
17592
|
-
};
|
|
17593
|
-
const isReadOnlyInitialStateUse = (referenceNode, scopes) => {
|
|
17594
|
-
const referenceRoot = findTransparentExpressionRoot(referenceNode);
|
|
17595
|
-
const callExpression = referenceRoot.parent;
|
|
17596
|
-
return isNodeOfType(callExpression, "CallExpression") && callExpression.arguments.some((argument) => argument === referenceRoot) && isReactApiCall(callExpression, "useState", scopes, {
|
|
17597
|
-
allowGlobalReactNamespace: true,
|
|
17598
|
-
allowUnboundBareCalls: true,
|
|
17599
|
-
resolveNamedAliases: true
|
|
17600
|
-
});
|
|
17601
|
-
};
|
|
17602
|
-
const resolveRenderDerivedMutableSourceKeys = (capturedReference, symbol, scopes) => {
|
|
17603
|
-
if (symbol.kind !== "let" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
17604
|
-
const boundaryFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
|
|
17605
|
-
if (!boundaryFunction) return null;
|
|
17606
|
-
const capturingFunction = findEnclosingFunction$1(capturedReference.identifier);
|
|
17607
|
-
if (!capturingFunction || capturingFunction === boundaryFunction) return null;
|
|
17608
|
-
const sourceKeys = /* @__PURE__ */ new Set();
|
|
17609
|
-
if (symbol.initializer) {
|
|
17610
|
-
const initializerSourceKeys = resolveDerivedExpressionSourceKeys(symbol.initializer, scopes, new Set([symbol.id]));
|
|
17611
|
-
if (!initializerSourceKeys) return null;
|
|
17612
|
-
for (const initializerSourceKey of initializerSourceKeys) sourceKeys.add(initializerSourceKey);
|
|
17613
|
-
}
|
|
17614
|
-
let writeCount = 0;
|
|
17615
|
-
for (const symbolReference of symbol.references) {
|
|
17616
|
-
if (symbolReference.flag === "read") {
|
|
17617
|
-
if (findEnclosingFunction$1(symbolReference.identifier) !== capturingFunction && !isReadOnlyInitialStateUse(symbolReference.identifier, scopes)) return null;
|
|
17618
|
-
continue;
|
|
17619
|
-
}
|
|
17620
|
-
if (symbolReference.flag !== "write") return null;
|
|
17621
|
-
const referenceRoot = findTransparentExpressionRoot(symbolReference.identifier);
|
|
17622
|
-
const assignment = referenceRoot.parent;
|
|
17623
|
-
if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== referenceRoot || findEnclosingFunction$1(referenceRoot) !== boundaryFunction) return null;
|
|
17624
|
-
const assignmentSourceKeys = resolveDerivedExpressionSourceKeys(assignment.right, scopes, new Set([symbol.id]));
|
|
17625
|
-
const controlSourceKeys = resolveWriteControlSourceKeys(assignment, boundaryFunction, scopes);
|
|
17626
|
-
if (!assignmentSourceKeys || !controlSourceKeys) return null;
|
|
17627
|
-
for (const assignmentSourceKey of assignmentSourceKeys) sourceKeys.add(assignmentSourceKey);
|
|
17628
|
-
for (const controlSourceKey of controlSourceKeys) sourceKeys.add(controlSourceKey);
|
|
17629
|
-
writeCount += 1;
|
|
17630
|
-
}
|
|
17631
|
-
return writeCount > 0 && sourceKeys.size > 0 ? sourceKeys : null;
|
|
17632
|
-
};
|
|
17633
17197
|
const isUseCallbackResultDep = (node, scopes) => {
|
|
17634
17198
|
const rootSymbol = getRootSymbol(node, scopes);
|
|
17635
17199
|
const initializer = rootSymbol?.initializer ? unwrapExpression$3(rootSymbol.initializer) : null;
|
|
@@ -29857,7 +29421,7 @@ const nextjsNoVercelOgImport = defineRule({
|
|
|
29857
29421
|
//#endregion
|
|
29858
29422
|
//#region src/plugin/rules/a11y/no-access-key.ts
|
|
29859
29423
|
const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
|
|
29860
|
-
const isUndefinedIdentifier
|
|
29424
|
+
const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
|
|
29861
29425
|
const noAccessKey = defineRule({
|
|
29862
29426
|
id: "no-access-key",
|
|
29863
29427
|
title: "accessKey attribute used",
|
|
@@ -29882,7 +29446,7 @@ const noAccessKey = defineRule({
|
|
|
29882
29446
|
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
29883
29447
|
const expression = attributeValue.expression;
|
|
29884
29448
|
if (!expression || expression.type === "JSXEmptyExpression") return;
|
|
29885
|
-
if (isUndefinedIdentifier
|
|
29449
|
+
if (isUndefinedIdentifier(expression)) return;
|
|
29886
29450
|
context.report({
|
|
29887
29451
|
node: accessKey,
|
|
29888
29452
|
message: MESSAGE$39
|
|
@@ -30647,12 +30211,6 @@ const isReactNamespaceImportReference = (ref) => Boolean(ref?.resolved?.defs.som
|
|
|
30647
30211
|
const importDeclaration = declarationNode.parent;
|
|
30648
30212
|
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
|
|
30649
30213
|
}));
|
|
30650
|
-
const isReactNamespaceReceiver = (analysis, node) => {
|
|
30651
|
-
const receiver = stripParenExpression(node);
|
|
30652
|
-
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
30653
|
-
const namespaceReference = getRef(analysis, receiver);
|
|
30654
|
-
return namespaceReference?.resolved ? isReactNamespaceImportReference(namespaceReference) : receiver.name === "React";
|
|
30655
|
-
};
|
|
30656
30214
|
const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
|
|
30657
30215
|
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return false;
|
|
30658
30216
|
const callee = stripParenExpression(declarator.init.callee);
|
|
@@ -30661,20 +30219,24 @@ const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
|
|
|
30661
30219
|
if (!reference?.resolved) return callee.name === hookName;
|
|
30662
30220
|
return isReactNamedImportReference(reference, hookName);
|
|
30663
30221
|
}
|
|
30664
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
|
|
30665
|
-
|
|
30222
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
|
|
30223
|
+
const namespaceReference = getRef(analysis, callee.object);
|
|
30224
|
+
if (!namespaceReference?.resolved) return callee.object.name === "React";
|
|
30225
|
+
return isReactNamespaceImportReference(namespaceReference);
|
|
30666
30226
|
};
|
|
30667
30227
|
const isHookCallee$1 = (analysis, node, hookName) => {
|
|
30668
30228
|
if (!node) return false;
|
|
30669
30229
|
if (isNodeOfType(node, "Identifier")) {
|
|
30670
30230
|
if (node.name === hookName) return true;
|
|
30671
30231
|
if (isReactNamedImportReference(getRef(analysis, node), hookName)) return true;
|
|
30672
|
-
const
|
|
30673
|
-
|
|
30674
|
-
if (parent && isNodeOfType(parent, "MemberExpression") && parent.object === receiverRoot && isReactNamespaceReceiver(analysis, node) && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
|
|
30232
|
+
const parent = node.parent;
|
|
30233
|
+
if (parent && isNodeOfType(parent, "MemberExpression") && isNodeOfType(parent.object, "Identifier") && parent.object.name === "React" && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
|
|
30675
30234
|
return false;
|
|
30676
30235
|
}
|
|
30677
|
-
if (isNodeOfType(node, "MemberExpression"))
|
|
30236
|
+
if (isNodeOfType(node, "MemberExpression")) {
|
|
30237
|
+
const receiver = stripParenExpression(node.object);
|
|
30238
|
+
return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
|
|
30239
|
+
}
|
|
30678
30240
|
return false;
|
|
30679
30241
|
};
|
|
30680
30242
|
const isUseEffect = (node) => {
|
|
@@ -31098,88 +30660,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
|
|
|
31098
30660
|
if (HANDLER_BINDING_NAME_PATTERN.test(bindingName)) return true;
|
|
31099
30661
|
return isSetterWiredToJsxHandler(componentFunction, bindingName);
|
|
31100
30662
|
};
|
|
31101
|
-
const
|
|
31102
|
-
const functionMetadata = functionNode;
|
|
31103
|
-
return functionMetadata.async !== true && functionMetadata.generator !== true;
|
|
31104
|
-
};
|
|
31105
|
-
const findBindingVariable = (analysis, bindingIdentifier) => {
|
|
31106
|
-
for (const scope of analysis.scopeManager.scopes) for (const variable of scope.variables) if (variable.identifiers.includes(bindingIdentifier)) return variable;
|
|
31107
|
-
return null;
|
|
31108
|
-
};
|
|
31109
|
-
const getImmutableFunctionVariable = (analysis, componentFunction, functionNode) => {
|
|
31110
|
-
if (!isSynchronousFunction(functionNode) || !isAstDescendant(functionNode, componentFunction)) return null;
|
|
31111
|
-
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
31112
|
-
if (!bindingIdentifier) return null;
|
|
31113
|
-
const variable = findBindingVariable(analysis, bindingIdentifier);
|
|
31114
|
-
if (!variable || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && !reference.init)) return null;
|
|
31115
|
-
const definition = variable.defs[0];
|
|
31116
|
-
if (definition.type === "FunctionName") return definition.node === functionNode ? variable : null;
|
|
31117
|
-
if (definition.type !== "Variable") return null;
|
|
31118
|
-
const declarator = definition.node;
|
|
31119
|
-
if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return null;
|
|
31120
|
-
if (declarator.init === functionNode) return variable;
|
|
31121
|
-
if (isNodeOfType(declarator.init, "CallExpression") && declarator.init.arguments?.[0] === functionNode && isGenuineReactHookDeclarator(analysis, declarator, "useCallback")) return variable;
|
|
31122
|
-
return null;
|
|
31123
|
-
};
|
|
31124
|
-
const getJsxEventValueAttribute = (identifier) => {
|
|
31125
|
-
const expression = findTransparentExpressionRoot(identifier);
|
|
31126
|
-
const expressionContainer = expression.parent;
|
|
31127
|
-
if (!isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== expression) return null;
|
|
31128
|
-
const attribute = expressionContainer.parent;
|
|
31129
|
-
if (!isNodeOfType(attribute, "JSXAttribute")) return null;
|
|
31130
|
-
const attributeName = getJsxAttributeName(attribute.name);
|
|
31131
|
-
return attributeName && isEventHandlerName(attributeName) ? attribute : null;
|
|
31132
|
-
};
|
|
31133
|
-
const getInlineJsxEventCallbackAttribute = (callExpression) => {
|
|
31134
|
-
const callbackFunction = findEnclosingFunction$1(callExpression);
|
|
31135
|
-
if (!callbackFunction || !isSynchronousFunction(callbackFunction)) return null;
|
|
31136
|
-
return getJsxEventValueAttribute(callbackFunction);
|
|
31137
|
-
};
|
|
31138
|
-
const isReactHookDependencyReference = (identifier) => {
|
|
31139
|
-
const expression = findTransparentExpressionRoot(identifier);
|
|
31140
|
-
const dependencyArray = expression.parent;
|
|
31141
|
-
if (!isNodeOfType(dependencyArray, "ArrayExpression") || !(dependencyArray.elements ?? []).includes(expression)) return false;
|
|
31142
|
-
const hookCall = dependencyArray.parent;
|
|
31143
|
-
if (!isNodeOfType(hookCall, "CallExpression") || hookCall.arguments?.[1] !== dependencyArray) return false;
|
|
31144
|
-
const callee = hookCall.callee;
|
|
31145
|
-
if (isNodeOfType(callee, "Identifier")) return /^use[A-Z0-9]/.test(callee.name);
|
|
31146
|
-
return Boolean(isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && /^use[A-Z0-9]/.test(callee.property.name));
|
|
31147
|
-
};
|
|
31148
|
-
const hasReachableJsxEventCallPath = (analysis, context, componentFunction, functionVariable, visitedVariables) => {
|
|
31149
|
-
if (visitedVariables.has(functionVariable)) return false;
|
|
31150
|
-
const nextVisitedVariables = new Set(visitedVariables).add(functionVariable);
|
|
31151
|
-
const callExpressions = [];
|
|
31152
|
-
let hasDirectJsxEventReference = false;
|
|
31153
|
-
for (const reference of functionVariable.references) {
|
|
31154
|
-
if (reference.init) continue;
|
|
31155
|
-
const identifier = reference.identifier;
|
|
31156
|
-
if (reference.isWrite()) return false;
|
|
31157
|
-
const jsxEventValueAttribute = getJsxEventValueAttribute(identifier);
|
|
31158
|
-
if (jsxEventValueAttribute) {
|
|
31159
|
-
if (isNodeReachableWithinFunction(jsxEventValueAttribute, context)) hasDirectJsxEventReference = true;
|
|
31160
|
-
continue;
|
|
31161
|
-
}
|
|
31162
|
-
if (isReactHookDependencyReference(identifier)) continue;
|
|
31163
|
-
const callExpression = getCallExpr(reference);
|
|
31164
|
-
if (!callExpression) return false;
|
|
31165
|
-
const jsxEventCallbackAttribute = getInlineJsxEventCallbackAttribute(callExpression);
|
|
31166
|
-
if (jsxEventCallbackAttribute) {
|
|
31167
|
-
if (isNodeReachableWithinFunction(callExpression, context) && isNodeReachableWithinFunction(jsxEventCallbackAttribute, context)) hasDirectJsxEventReference = true;
|
|
31168
|
-
continue;
|
|
31169
|
-
}
|
|
31170
|
-
callExpressions.push(callExpression);
|
|
31171
|
-
}
|
|
31172
|
-
if (hasDirectJsxEventReference) return true;
|
|
31173
|
-
for (const callExpression of callExpressions) {
|
|
31174
|
-
if (!isNodeReachableWithinFunction(callExpression, context)) continue;
|
|
31175
|
-
const callerFunction = findEnclosingFunction$1(callExpression);
|
|
31176
|
-
if (!callerFunction || callerFunction === componentFunction) continue;
|
|
31177
|
-
const callerVariable = getImmutableFunctionVariable(analysis, componentFunction, callerFunction);
|
|
31178
|
-
if (callerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, callerVariable, nextVisitedVariables)) return true;
|
|
31179
|
-
}
|
|
31180
|
-
return false;
|
|
31181
|
-
};
|
|
31182
|
-
const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, includeDeferredWriters = false) => {
|
|
30663
|
+
const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
|
|
31183
30664
|
if (!setterRef.resolved) return false;
|
|
31184
30665
|
const componentFunction = findEnclosingFunction$1(effectNode);
|
|
31185
30666
|
if (!componentFunction) return false;
|
|
@@ -31188,11 +30669,6 @@ const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, incl
|
|
|
31188
30669
|
const identifier = reference.identifier;
|
|
31189
30670
|
if (isAstDescendant(identifier, effectNode)) continue;
|
|
31190
30671
|
if (isIndependentWriterIdentifier(componentFunction, identifier, includeDeferredWriters)) return true;
|
|
31191
|
-
if (!isNodeReachableWithinFunction(identifier, context)) continue;
|
|
31192
|
-
const writerFunction = findEnclosingFunction$1(identifier);
|
|
31193
|
-
if (!writerFunction || writerFunction === componentFunction) continue;
|
|
31194
|
-
const writerVariable = getImmutableFunctionVariable(analysis, componentFunction, writerFunction);
|
|
31195
|
-
if (writerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, writerVariable, /* @__PURE__ */ new Set())) return true;
|
|
31196
30672
|
}
|
|
31197
30673
|
return false;
|
|
31198
30674
|
};
|
|
@@ -32082,7 +31558,7 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
|
|
|
32082
31558
|
}
|
|
32083
31559
|
return false;
|
|
32084
31560
|
};
|
|
32085
|
-
const collectEffectStateWriteFacts = (analysis,
|
|
31561
|
+
const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
|
|
32086
31562
|
const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
|
|
32087
31563
|
if (frames.length === 0) return [];
|
|
32088
31564
|
const effectHasCleanup = hasCleanup(analysis, effectNode);
|
|
@@ -32112,7 +31588,7 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
|
|
|
32112
31588
|
for (const returnedExpression of returnedExpressions) mergeEvidence(valueEvidence, collectValueEvidence(analysis, returnedExpression, updaterFrame, remainingValueCallFrames));
|
|
32113
31589
|
} else valueEvidence = collectValueEvidence(analysis, writtenValue, frame, remainingValueCallFrames);
|
|
32114
31590
|
const sourceReferences = [...valueEvidence.sourceReferences].filter((sourceReference) => getUseStateDecl(analysis, sourceReference) !== stateDeclarator);
|
|
32115
|
-
const hasIndependentWriter = hasUserInputSetterWriter(
|
|
31591
|
+
const hasIndependentWriter = hasUserInputSetterWriter(setterReference, effectNode, true);
|
|
32116
31592
|
const doesMatchStateInitializer = matchesStateInitializer(analysis, callExpression, stateDeclarator);
|
|
32117
31593
|
if (effectHasCleanup && (frame.isDeferred || valueEvidence.hasUnknownSource || valueEvidence.hasDeferredIntroducedValue || valueEvidence.readsExternalValue)) cleanupManagedStateDeclarators.add(stateDeclarator);
|
|
32118
31594
|
const isRenderKnownCopy = sourceReferences.length > 0 && !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue && !hasIndependentWriter;
|
|
@@ -32153,7 +31629,7 @@ const noAdjustStateOnPropChange = defineRule({
|
|
|
32153
31629
|
const dependencyReferences = getEffectDepsRefs(analysis, node);
|
|
32154
31630
|
if (!dependencyReferences) return;
|
|
32155
31631
|
if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
|
|
32156
|
-
for (const fact of collectEffectStateWriteFacts(analysis,
|
|
31632
|
+
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
32157
31633
|
if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
|
|
32158
31634
|
context.report({
|
|
32159
31635
|
node: fact.callExpression,
|
|
@@ -36727,7 +36203,7 @@ const noDerivedState = defineRule({
|
|
|
36727
36203
|
if (!isUseEffect(node)) return;
|
|
36728
36204
|
const analysis = getProgramAnalysis(node);
|
|
36729
36205
|
if (!analysis) return;
|
|
36730
|
-
for (const fact of collectEffectStateWriteFacts(analysis,
|
|
36206
|
+
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
36731
36207
|
if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
|
|
36732
36208
|
reportStateWrite(fact.callExpression, fact.stateDeclarator);
|
|
36733
36209
|
}
|
|
@@ -36747,7 +36223,7 @@ const noDerivedStateEffect = defineRule({
|
|
|
36747
36223
|
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
36748
36224
|
const analysis = getProgramAnalysis(node);
|
|
36749
36225
|
if (!analysis) return;
|
|
36750
|
-
if (!collectEffectStateWriteFacts(analysis,
|
|
36226
|
+
if (!collectEffectStateWriteFacts(analysis, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
|
|
36751
36227
|
context.report({
|
|
36752
36228
|
node,
|
|
36753
36229
|
message: "You pay an extra render for state you can derive from other values."
|
|
@@ -37223,20 +36699,9 @@ const noDidMountSetState = defineRule({
|
|
|
37223
36699
|
}
|
|
37224
36700
|
});
|
|
37225
36701
|
//#endregion
|
|
37226
|
-
//#region src/plugin/utils/find-enclosing-class.ts
|
|
37227
|
-
const findEnclosingClass = (node) => {
|
|
37228
|
-
let ancestor = node.parent;
|
|
37229
|
-
while (ancestor) {
|
|
37230
|
-
if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
|
|
37231
|
-
ancestor = ancestor.parent ?? null;
|
|
37232
|
-
}
|
|
37233
|
-
return null;
|
|
37234
|
-
};
|
|
37235
|
-
//#endregion
|
|
37236
36702
|
//#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
|
|
37237
36703
|
const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
|
|
37238
36704
|
const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
|
|
37239
|
-
const DIFFERENCE_OPERATORS = new Set(["!=", "!=="]);
|
|
37240
36705
|
const EQUALITY_OPERATORS = new Set([
|
|
37241
36706
|
"==",
|
|
37242
36707
|
"===",
|
|
@@ -37248,8 +36713,6 @@ const FUNCTION_NODE_TYPES = new Set([
|
|
|
37248
36713
|
"FunctionExpression",
|
|
37249
36714
|
"ArrowFunctionExpression"
|
|
37250
36715
|
]);
|
|
37251
|
-
const CLASS_NODE_TYPES = new Set(["ClassDeclaration", "ClassExpression"]);
|
|
37252
|
-
const callbackRefFieldNamesByClass = /* @__PURE__ */ new WeakMap();
|
|
37253
36716
|
const isLifecycleMethodFunction = (node) => {
|
|
37254
36717
|
if (!FUNCTION_NODE_TYPES.has(node.type)) return false;
|
|
37255
36718
|
const parent = node.parent;
|
|
@@ -37305,187 +36768,6 @@ const getStaticMemberName = (node) => {
|
|
|
37305
36768
|
if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
|
|
37306
36769
|
return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
|
|
37307
36770
|
};
|
|
37308
|
-
const getMemberIdentity = (property) => {
|
|
37309
|
-
const propertyName = getPropertyKeyName$2(property);
|
|
37310
|
-
if (propertyName !== void 0) return isNodeOfType(property, "PrivateIdentifier") ? `#${propertyName}` : propertyName;
|
|
37311
|
-
return isNodeOfType(property, "Literal") && typeof property.value === "string" ? property.value : null;
|
|
37312
|
-
};
|
|
37313
|
-
const collectPreviousSourcePaths = (pattern, domain, members, previousSourcePaths) => {
|
|
37314
|
-
if (!pattern) return;
|
|
37315
|
-
const unwrappedPattern = stripParenExpression(pattern);
|
|
37316
|
-
if (isNodeOfType(unwrappedPattern, "Identifier")) {
|
|
37317
|
-
previousSourcePaths.set(unwrappedPattern.name, {
|
|
37318
|
-
domain,
|
|
37319
|
-
members: [...members],
|
|
37320
|
-
source: "previous"
|
|
37321
|
-
});
|
|
37322
|
-
return;
|
|
37323
|
-
}
|
|
37324
|
-
if (isNodeOfType(unwrappedPattern, "AssignmentPattern")) {
|
|
37325
|
-
collectPreviousSourcePaths(unwrappedPattern.left, domain, members, previousSourcePaths);
|
|
37326
|
-
return;
|
|
37327
|
-
}
|
|
37328
|
-
if (!isNodeOfType(unwrappedPattern, "ObjectPattern")) return;
|
|
37329
|
-
for (const property of unwrappedPattern.properties) {
|
|
37330
|
-
if (!isNodeOfType(property, "Property")) continue;
|
|
37331
|
-
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
37332
|
-
if (!propertyName) continue;
|
|
37333
|
-
collectPreviousSourcePaths(property.value, domain, [...members, propertyName], previousSourcePaths);
|
|
37334
|
-
}
|
|
37335
|
-
};
|
|
37336
|
-
const getStateSourcePath = (node, previousSourcePaths) => {
|
|
37337
|
-
let currentNode = stripParenExpression(node);
|
|
37338
|
-
const members = [];
|
|
37339
|
-
while (isNodeOfType(currentNode, "MemberExpression")) {
|
|
37340
|
-
const memberName = getStaticMemberName(currentNode);
|
|
37341
|
-
if (!memberName) return null;
|
|
37342
|
-
members.unshift(memberName);
|
|
37343
|
-
currentNode = stripParenExpression(currentNode.object);
|
|
37344
|
-
}
|
|
37345
|
-
if (isNodeOfType(currentNode, "ThisExpression")) {
|
|
37346
|
-
const [domain, ...pathMembers] = members;
|
|
37347
|
-
if (domain !== "props" && domain !== "state") return null;
|
|
37348
|
-
return {
|
|
37349
|
-
domain,
|
|
37350
|
-
members: pathMembers,
|
|
37351
|
-
source: "current"
|
|
37352
|
-
};
|
|
37353
|
-
}
|
|
37354
|
-
if (!isNodeOfType(currentNode, "Identifier")) return null;
|
|
37355
|
-
const previousSourcePath = previousSourcePaths.get(currentNode.name);
|
|
37356
|
-
return previousSourcePath ? {
|
|
37357
|
-
...previousSourcePath,
|
|
37358
|
-
members: [...previousSourcePath.members, ...members]
|
|
37359
|
-
} : null;
|
|
37360
|
-
};
|
|
37361
|
-
const haveMatchingStateSourcePaths = (left, right) => left.domain === right.domain && left.members.length === right.members.length && left.members.every((member, index) => member === right.members[index]);
|
|
37362
|
-
const collectConjunctiveStateSourceComparisons = (test, previousSourcePaths, comparisons) => {
|
|
37363
|
-
const expression = stripParenExpression(test);
|
|
37364
|
-
if (isNodeOfType(expression, "LogicalExpression") && expression.operator === "&&") {
|
|
37365
|
-
collectConjunctiveStateSourceComparisons(expression.left, previousSourcePaths, comparisons);
|
|
37366
|
-
collectConjunctiveStateSourceComparisons(expression.right, previousSourcePaths, comparisons);
|
|
37367
|
-
return;
|
|
37368
|
-
}
|
|
37369
|
-
if (!isNodeOfType(expression, "BinaryExpression") || !EQUALITY_OPERATORS.has(expression.operator)) return;
|
|
37370
|
-
const leftPath = getStateSourcePath(expression.left, previousSourcePaths);
|
|
37371
|
-
const rightPath = getStateSourcePath(expression.right, previousSourcePaths);
|
|
37372
|
-
if (Boolean(leftPath) === Boolean(rightPath)) return;
|
|
37373
|
-
const path = leftPath ?? rightPath;
|
|
37374
|
-
if (!path) return;
|
|
37375
|
-
comparisons.push({
|
|
37376
|
-
comparedValue: leftPath ? expression.right : expression.left,
|
|
37377
|
-
isDifference: DIFFERENCE_OPERATORS.has(expression.operator),
|
|
37378
|
-
path
|
|
37379
|
-
});
|
|
37380
|
-
};
|
|
37381
|
-
const isHistoricalToCurrentTransitionGuard = (test, previousSourcePaths) => {
|
|
37382
|
-
const expression = stripParenExpression(test);
|
|
37383
|
-
if (isNodeOfType(expression, "LogicalExpression") && expression.operator === "||") return isHistoricalToCurrentTransitionGuard(expression.left, previousSourcePaths) && isHistoricalToCurrentTransitionGuard(expression.right, previousSourcePaths);
|
|
37384
|
-
const comparisons = [];
|
|
37385
|
-
collectConjunctiveStateSourceComparisons(expression, previousSourcePaths, comparisons);
|
|
37386
|
-
return comparisons.some((comparison, index) => comparisons.slice(index + 1).some((candidate) => comparison.path.source !== candidate.path.source && comparison.isDifference !== candidate.isDifference && haveMatchingStateSourcePaths(comparison.path, candidate.path) && areExpressionsStructurallyEqual(comparison.comparedValue, candidate.comparedValue)));
|
|
37387
|
-
};
|
|
37388
|
-
const getThisFieldName = (node) => {
|
|
37389
|
-
const unwrappedNode = stripParenExpression(node);
|
|
37390
|
-
if (!isNodeOfType(unwrappedNode, "MemberExpression") || unwrappedNode.computed === true || !isNodeOfType(stripParenExpression(unwrappedNode.object), "ThisExpression")) return null;
|
|
37391
|
-
return getMemberIdentity(unwrappedNode.property);
|
|
37392
|
-
};
|
|
37393
|
-
const isUndefinedIdentifier = (node) => {
|
|
37394
|
-
const unwrappedNode = stripParenExpression(node);
|
|
37395
|
-
return isNodeOfType(unwrappedNode, "Identifier") && unwrappedNode.name === "undefined";
|
|
37396
|
-
};
|
|
37397
|
-
const isDirectRefParameterValue = (node, parameterSymbolId, scopes) => {
|
|
37398
|
-
const unwrappedNode = stripParenExpression(node);
|
|
37399
|
-
if (isNodeOfType(unwrappedNode, "Identifier")) return scopes.symbolFor(unwrappedNode)?.id === parameterSymbolId;
|
|
37400
|
-
if (!isNodeOfType(unwrappedNode, "LogicalExpression") || unwrappedNode.operator !== "??") return false;
|
|
37401
|
-
const left = stripParenExpression(unwrappedNode.left);
|
|
37402
|
-
return isNodeOfType(left, "Identifier") && scopes.symbolFor(left)?.id === parameterSymbolId && isUndefinedIdentifier(unwrappedNode.right);
|
|
37403
|
-
};
|
|
37404
|
-
const getCallbackRefAssignedFields = (callback, scopes) => {
|
|
37405
|
-
const firstParameter = (callback.params ?? [])[0];
|
|
37406
|
-
if (!firstParameter) return /* @__PURE__ */ new Set();
|
|
37407
|
-
const parameterIdentifier = isNodeOfType(firstParameter, "AssignmentPattern") ? firstParameter.left : firstParameter;
|
|
37408
|
-
if (!isNodeOfType(parameterIdentifier, "Identifier")) return /* @__PURE__ */ new Set();
|
|
37409
|
-
const parameterSymbolId = scopes.symbolFor(parameterIdentifier)?.id;
|
|
37410
|
-
if (parameterSymbolId === void 0) return /* @__PURE__ */ new Set();
|
|
37411
|
-
const body = callback.body;
|
|
37412
|
-
if (!body) return /* @__PURE__ */ new Set();
|
|
37413
|
-
const assignedFieldNames = /* @__PURE__ */ new Set();
|
|
37414
|
-
walkAst(body, (node) => {
|
|
37415
|
-
if (node !== body && (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node) || CLASS_NODE_TYPES.has(node.type))) return false;
|
|
37416
|
-
const assignmentTarget = isNodeOfType(node, "AssignmentExpression") && node.left || isNodeOfType(node, "UpdateExpression") && node.argument || isNodeOfType(node, "UnaryExpression") && node.operator === "delete" && node.argument || null;
|
|
37417
|
-
if (!assignmentTarget) return;
|
|
37418
|
-
const fieldName = getThisFieldName(assignmentTarget);
|
|
37419
|
-
if (!fieldName) return;
|
|
37420
|
-
if (isNodeOfType(node, "AssignmentExpression") && node.operator === "=" && isDirectRefParameterValue(node.right, parameterSymbolId, scopes)) {
|
|
37421
|
-
assignedFieldNames.add(fieldName);
|
|
37422
|
-
return;
|
|
37423
|
-
}
|
|
37424
|
-
assignedFieldNames.delete(fieldName);
|
|
37425
|
-
});
|
|
37426
|
-
return assignedFieldNames;
|
|
37427
|
-
};
|
|
37428
|
-
const getClassMemberCallback = (classNode, memberName) => {
|
|
37429
|
-
const classBody = classNode.body?.body ?? [];
|
|
37430
|
-
for (const member of classBody) {
|
|
37431
|
-
if (!isNodeOfType(member, "MethodDefinition") && !isNodeOfType(member, "PropertyDefinition")) continue;
|
|
37432
|
-
if (member.static === true) continue;
|
|
37433
|
-
const key = member.key;
|
|
37434
|
-
if (getMemberIdentity(key) !== memberName) continue;
|
|
37435
|
-
const value = member.value;
|
|
37436
|
-
return value && FUNCTION_NODE_TYPES.has(value.type) ? value : null;
|
|
37437
|
-
}
|
|
37438
|
-
return null;
|
|
37439
|
-
};
|
|
37440
|
-
const collectCallbackRefFieldsFromExpression = (expression, classNode, fieldNames, scopes) => {
|
|
37441
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
37442
|
-
if (FUNCTION_NODE_TYPES.has(unwrappedExpression.type)) {
|
|
37443
|
-
for (const fieldName of getCallbackRefAssignedFields(unwrappedExpression, scopes)) fieldNames.add(fieldName);
|
|
37444
|
-
return;
|
|
37445
|
-
}
|
|
37446
|
-
const handlerName = getThisFieldName(unwrappedExpression);
|
|
37447
|
-
if (handlerName) {
|
|
37448
|
-
const callback = getClassMemberCallback(classNode, handlerName);
|
|
37449
|
-
if (callback) for (const fieldName of getCallbackRefAssignedFields(callback, scopes)) fieldNames.add(fieldName);
|
|
37450
|
-
return;
|
|
37451
|
-
}
|
|
37452
|
-
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
|
|
37453
|
-
collectCallbackRefFieldsFromExpression(unwrappedExpression.consequent, classNode, fieldNames, scopes);
|
|
37454
|
-
collectCallbackRefFieldsFromExpression(unwrappedExpression.alternate, classNode, fieldNames, scopes);
|
|
37455
|
-
return;
|
|
37456
|
-
}
|
|
37457
|
-
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
37458
|
-
if (unwrappedExpression.operator !== "&&") collectCallbackRefFieldsFromExpression(unwrappedExpression.left, classNode, fieldNames, scopes);
|
|
37459
|
-
collectCallbackRefFieldsFromExpression(unwrappedExpression.right, classNode, fieldNames, scopes);
|
|
37460
|
-
}
|
|
37461
|
-
};
|
|
37462
|
-
const getCallbackRefFieldNames = (classNode, scopes) => {
|
|
37463
|
-
if (!classNode) return /* @__PURE__ */ new Set();
|
|
37464
|
-
const cachedFieldNames = callbackRefFieldNamesByClass.get(classNode);
|
|
37465
|
-
if (cachedFieldNames) return cachedFieldNames;
|
|
37466
|
-
const fieldNames = /* @__PURE__ */ new Set();
|
|
37467
|
-
const classBody = classNode.body;
|
|
37468
|
-
if (classBody) walkAst(classBody, (node) => {
|
|
37469
|
-
if (node !== classBody && CLASS_NODE_TYPES.has(node.type)) return false;
|
|
37470
|
-
if (!isNodeOfType(node, "JSXAttribute") || !isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "ref" || !node.value || !isNodeOfType(node.value, "JSXExpressionContainer") || !node.value.expression) return;
|
|
37471
|
-
collectCallbackRefFieldsFromExpression(node.value.expression, classNode, fieldNames, scopes);
|
|
37472
|
-
});
|
|
37473
|
-
callbackRefFieldNamesByClass.set(classNode, fieldNames);
|
|
37474
|
-
return fieldNames;
|
|
37475
|
-
};
|
|
37476
|
-
const collectLifecycleWrittenFieldNames = (lifecycleFunction) => {
|
|
37477
|
-
const fieldNames = /* @__PURE__ */ new Set();
|
|
37478
|
-
const body = lifecycleFunction.body;
|
|
37479
|
-
if (!body) return fieldNames;
|
|
37480
|
-
walkAst(body, (node) => {
|
|
37481
|
-
if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
|
|
37482
|
-
const target = isNodeOfType(node, "AssignmentExpression") && node.left || isNodeOfType(node, "UpdateExpression") && node.argument || null;
|
|
37483
|
-
if (!target) return;
|
|
37484
|
-
const fieldName = getThisFieldName(target);
|
|
37485
|
-
if (fieldName) fieldNames.add(fieldName);
|
|
37486
|
-
});
|
|
37487
|
-
return fieldNames;
|
|
37488
|
-
};
|
|
37489
36771
|
const getThisStateFieldName = (node) => {
|
|
37490
36772
|
const unwrappedNode = stripParenExpression(node);
|
|
37491
36773
|
if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
|
|
@@ -37503,17 +36785,15 @@ const collectLocalInitializers = (lifecycleFunction) => {
|
|
|
37503
36785
|
});
|
|
37504
36786
|
return initializers;
|
|
37505
36787
|
};
|
|
37506
|
-
const derivesFromPostMountValue = (node, localInitializers,
|
|
36788
|
+
const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
|
|
37507
36789
|
if (readsPostMountValue(node)) return true;
|
|
37508
|
-
const fieldName = getThisFieldName(node);
|
|
37509
|
-
if (fieldName && callbackRefFieldNames.has(fieldName)) return true;
|
|
37510
36790
|
const referencedNames = /* @__PURE__ */ new Set();
|
|
37511
36791
|
collectReferenceIdentifierNames(node, referencedNames);
|
|
37512
36792
|
for (const referencedName of referencedNames) {
|
|
37513
36793
|
if (visitedNames.has(referencedName)) continue;
|
|
37514
36794
|
const initializer = localInitializers.get(referencedName);
|
|
37515
36795
|
if (!initializer) continue;
|
|
37516
|
-
if (derivesFromPostMountValue(initializer, localInitializers,
|
|
36796
|
+
if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
|
|
37517
36797
|
}
|
|
37518
36798
|
return false;
|
|
37519
36799
|
};
|
|
@@ -37527,84 +36807,50 @@ const getSetStateFieldValue = (setStateCall, fieldName) => {
|
|
|
37527
36807
|
}
|
|
37528
36808
|
return null;
|
|
37529
36809
|
};
|
|
37530
|
-
const isConvergentPostMountGuard = (test, setStateCall, localInitializers
|
|
37531
|
-
|
|
37532
|
-
|
|
37533
|
-
if (
|
|
37534
|
-
|
|
37535
|
-
const
|
|
37536
|
-
|
|
37537
|
-
|
|
37538
|
-
|
|
37539
|
-
|
|
37540
|
-
|
|
37541
|
-
|
|
37542
|
-
|
|
37543
|
-
|
|
37544
|
-
|
|
37545
|
-
|
|
37546
|
-
return
|
|
37547
|
-
};
|
|
37548
|
-
const
|
|
37549
|
-
|
|
37550
|
-
|
|
37551
|
-
|
|
37552
|
-
|
|
37553
|
-
|
|
37554
|
-
|
|
37555
|
-
|
|
37556
|
-
|
|
37557
|
-
|
|
37558
|
-
|
|
37559
|
-
|
|
37560
|
-
|
|
37561
|
-
}
|
|
37562
|
-
return false;
|
|
37563
|
-
};
|
|
37564
|
-
const isDiffGuardTest = (test, paramNames, derivedNames, isTruthfulBranch) => {
|
|
37565
|
-
const expression = stripParenExpression(test);
|
|
37566
|
-
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
37567
|
-
if (expression.operator !== "&&" && expression.operator !== "||") return false;
|
|
37568
|
-
const leftIsDiffGuard = isDiffGuardTest(expression.left, paramNames, derivedNames, isTruthfulBranch);
|
|
37569
|
-
const rightIsDiffGuard = isDiffGuardTest(expression.right, paramNames, derivedNames, isTruthfulBranch);
|
|
37570
|
-
return isTruthfulBranch && expression.operator === "||" || !isTruthfulBranch && expression.operator === "&&" ? leftIsDiffGuard && rightIsDiffGuard : leftIsDiffGuard || rightIsDiffGuard;
|
|
37571
|
-
}
|
|
37572
|
-
if (!isNodeOfType(expression, "BinaryExpression") || !(isTruthfulBranch ? DIFFERENCE_OPERATORS.has(expression.operator) : EQUALITY_OPERATORS.has(expression.operator) && !DIFFERENCE_OPERATORS.has(expression.operator))) return false;
|
|
37573
|
-
return isStatefulOperand(expression.left, paramNames, derivedNames) && isStatefulOperand(expression.right, paramNames, derivedNames) && (referencesAnyName(expression.left, paramNames) || referencesAnyName(expression.right, paramNames) || referencesAnyName(expression.left, derivedNames) || referencesAnyName(expression.right, derivedNames));
|
|
36810
|
+
const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
|
|
36811
|
+
let qualifies = false;
|
|
36812
|
+
walkAst(test, (node) => {
|
|
36813
|
+
if (qualifies) return false;
|
|
36814
|
+
if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
|
|
36815
|
+
const leftFieldName = getThisStateFieldName(node.left);
|
|
36816
|
+
const rightFieldName = getThisStateFieldName(node.right);
|
|
36817
|
+
const fieldName = leftFieldName ?? rightFieldName;
|
|
36818
|
+
const comparedValue = leftFieldName ? node.right : node.left;
|
|
36819
|
+
if (!fieldName || !leftFieldName && !rightFieldName) return;
|
|
36820
|
+
const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
|
|
36821
|
+
if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
|
|
36822
|
+
if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
|
|
36823
|
+
qualifies = true;
|
|
36824
|
+
return false;
|
|
36825
|
+
});
|
|
36826
|
+
return qualifies;
|
|
36827
|
+
};
|
|
36828
|
+
const isDiffGuardTest = (test, paramNames, derivedNames) => {
|
|
36829
|
+
if (referencesAnyName(test, paramNames)) return true;
|
|
36830
|
+
let qualifies = false;
|
|
36831
|
+
walkAst(test, (node) => {
|
|
36832
|
+
if (qualifies) return false;
|
|
36833
|
+
if (!isNodeOfType(node, "BinaryExpression")) return;
|
|
36834
|
+
if (!EQUALITY_OPERATORS.has(node.operator)) return;
|
|
36835
|
+
if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames) && (referencesAnyName(node.left, derivedNames) || referencesAnyName(node.right, derivedNames))) {
|
|
36836
|
+
qualifies = true;
|
|
36837
|
+
return false;
|
|
36838
|
+
}
|
|
36839
|
+
});
|
|
36840
|
+
return qualifies;
|
|
37574
36841
|
};
|
|
37575
|
-
const isInsideDiffGuard = (setStateCall
|
|
36842
|
+
const isInsideDiffGuard = (setStateCall) => {
|
|
37576
36843
|
const lifecycleFunction = findEnclosingLifecycleFunction(setStateCall);
|
|
37577
36844
|
if (!lifecycleFunction) return false;
|
|
37578
36845
|
const paramNames = /* @__PURE__ */ new Set();
|
|
37579
|
-
const
|
|
37580
|
-
for (const param of parameters) collectPatternNames(param, paramNames);
|
|
37581
|
-
const previousSourcePaths = /* @__PURE__ */ new Map();
|
|
37582
|
-
const [previousPropsParameter, previousStateParameter] = parameters;
|
|
37583
|
-
collectPreviousSourcePaths(previousPropsParameter, "props", [], previousSourcePaths);
|
|
37584
|
-
collectPreviousSourcePaths(previousStateParameter, "state", [], previousSourcePaths);
|
|
36846
|
+
for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
|
|
37585
36847
|
const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
|
|
37586
36848
|
const localInitializers = collectLocalInitializers(lifecycleFunction);
|
|
37587
|
-
const lifecycleWrittenFieldNames = collectLifecycleWrittenFieldNames(lifecycleFunction);
|
|
37588
|
-
const callbackRefFieldNames = new Set([...getCallbackRefFieldNames(findEnclosingClass(lifecycleFunction), scopes)].filter((fieldName) => !lifecycleWrittenFieldNames.has(fieldName)));
|
|
37589
36849
|
let child = setStateCall;
|
|
37590
36850
|
let ancestor = setStateCall.parent;
|
|
37591
36851
|
while (ancestor && ancestor !== lifecycleFunction) {
|
|
37592
|
-
|
|
37593
|
-
|
|
37594
|
-
if (isNodeOfType(ancestor, "IfStatement")) {
|
|
37595
|
-
if (child === ancestor.consequent) guardTest = ancestor.test;
|
|
37596
|
-
else if (child === ancestor.alternate) {
|
|
37597
|
-
guardTest = ancestor.test;
|
|
37598
|
-
isTruthfulBranch = false;
|
|
37599
|
-
}
|
|
37600
|
-
} else if (isNodeOfType(ancestor, "ConditionalExpression")) {
|
|
37601
|
-
if (child === ancestor.consequent) guardTest = ancestor.test;
|
|
37602
|
-
else if (child === ancestor.alternate) {
|
|
37603
|
-
guardTest = ancestor.test;
|
|
37604
|
-
isTruthfulBranch = false;
|
|
37605
|
-
}
|
|
37606
|
-
} else if (isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right) guardTest = ancestor.left;
|
|
37607
|
-
if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames, isTruthfulBranch) || isTruthfulBranch && isHistoricalToCurrentTransitionGuard(guardTest, previousSourcePaths) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch) || isTruthfulBranch && isConvergentUndefinedClearGuard(guardTest, setStateCall))) return true;
|
|
36852
|
+
const guardTest = isNodeOfType(ancestor, "IfStatement") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "ConditionalExpression") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right && ancestor.left || null;
|
|
36853
|
+
if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers))) return true;
|
|
37608
36854
|
child = ancestor;
|
|
37609
36855
|
ancestor = ancestor.parent ?? null;
|
|
37610
36856
|
}
|
|
@@ -37626,7 +36872,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
37626
36872
|
if (!isNodeOfType(stripParenExpression(node.callee.object), "ThisExpression")) return;
|
|
37627
36873
|
if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
|
|
37628
36874
|
if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
37629
|
-
if (isInsideDiffGuard(node
|
|
36875
|
+
if (isInsideDiffGuard(node)) return;
|
|
37630
36876
|
context.report({
|
|
37631
36877
|
node: node.callee,
|
|
37632
36878
|
message: MESSAGE$27
|
|
@@ -41348,223 +40594,41 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
|
|
|
41348
40594
|
if (leftResult === false && rightResult === false) return false;
|
|
41349
40595
|
return null;
|
|
41350
40596
|
};
|
|
41351
|
-
const readHydrationConditionResult = (expression, context, runtime
|
|
40597
|
+
const readHydrationConditionResult = (expression, context, runtime) => {
|
|
41352
40598
|
const unwrappedExpression = stripParenExpression(expression);
|
|
41353
40599
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
41354
40600
|
if (predicateMatch) return predicateMatch[`${runtime}Result`];
|
|
41355
40601
|
const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
|
|
41356
40602
|
if (staticResult !== null) return staticResult;
|
|
41357
|
-
const expressionSymbol = isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression) : null;
|
|
41358
|
-
const parameterValue = expressionSymbol ? state.parameterValuesBySymbolId.get(expressionSymbol.id) : null;
|
|
41359
|
-
if (expressionSymbol && parameterValue && !state.visitedSymbolIds.has(expressionSymbol.id)) {
|
|
41360
|
-
state.visitedSymbolIds.add(expressionSymbol.id);
|
|
41361
|
-
const result = readHydrationConditionResult(parameterValue, context, runtime, state);
|
|
41362
|
-
state.visitedSymbolIds.delete(expressionSymbol.id);
|
|
41363
|
-
return result;
|
|
41364
|
-
}
|
|
41365
|
-
if (expressionSymbol && expressionSymbol.kind === "const" && expressionSymbol.initializer && expressionSymbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(expressionSymbol.id)) {
|
|
41366
|
-
state.visitedSymbolIds.add(expressionSymbol.id);
|
|
41367
|
-
const result = readHydrationConditionResult(expressionSymbol.initializer, context, runtime, state);
|
|
41368
|
-
state.visitedSymbolIds.delete(expressionSymbol.id);
|
|
41369
|
-
return result;
|
|
41370
|
-
}
|
|
41371
|
-
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
41372
|
-
const callArguments = unwrappedExpression.arguments ?? [];
|
|
41373
|
-
if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
|
|
41374
|
-
allowGlobalReactNamespace: true,
|
|
41375
|
-
resolveNamedAliases: true
|
|
41376
|
-
})) {
|
|
41377
|
-
const callbackArgument = callArguments[0];
|
|
41378
|
-
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
|
|
41379
|
-
const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
41380
|
-
return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? readHydrationFunctionResult(callbackFunction, context, runtime, state) : null;
|
|
41381
|
-
}
|
|
41382
|
-
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
41383
|
-
if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return readHydrationConditionResult(callArguments[0], context, runtime, state);
|
|
41384
|
-
const helperFunction = resolveExactLocalFunction(callee, context.scopes);
|
|
41385
|
-
if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
|
|
41386
|
-
const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
|
|
41387
|
-
for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
|
|
41388
|
-
const parameter = helperFunction.params[parameterIndex];
|
|
41389
|
-
const argument = callArguments[parameterIndex];
|
|
41390
|
-
if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
|
|
41391
|
-
const parameterSymbol = context.scopes.symbolFor(parameter);
|
|
41392
|
-
if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
|
|
41393
|
-
}
|
|
41394
|
-
return readHydrationFunctionResult(helperFunction, context, runtime, {
|
|
41395
|
-
...state,
|
|
41396
|
-
parameterValuesBySymbolId
|
|
41397
|
-
});
|
|
41398
|
-
}
|
|
41399
40603
|
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
|
|
41400
|
-
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime
|
|
40604
|
+
const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
|
|
41401
40605
|
return argumentResult === null ? null : !argumentResult;
|
|
41402
40606
|
}
|
|
41403
40607
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
41404
|
-
return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime
|
|
41405
|
-
};
|
|
41406
|
-
const readHydrationStatementResult = (statement, context, runtime, state) => {
|
|
41407
|
-
if (isNodeOfType(statement, "ReturnStatement")) return {
|
|
41408
|
-
didReturn: true,
|
|
41409
|
-
value: statement.argument ? readHydrationConditionResult(statement.argument, context, runtime, state) : null
|
|
41410
|
-
};
|
|
41411
|
-
if (isNodeOfType(statement, "BlockStatement")) {
|
|
41412
|
-
for (const childStatement of statement.body) {
|
|
41413
|
-
const result = readHydrationStatementResult(childStatement, context, runtime, state);
|
|
41414
|
-
if (result.didReturn) return result;
|
|
41415
|
-
if (statementAlwaysExits(childStatement)) break;
|
|
41416
|
-
}
|
|
41417
|
-
return {
|
|
41418
|
-
didReturn: false,
|
|
41419
|
-
value: null
|
|
41420
|
-
};
|
|
41421
|
-
}
|
|
41422
|
-
if (!isNodeOfType(statement, "IfStatement")) return {
|
|
41423
|
-
didReturn: false,
|
|
41424
|
-
value: null
|
|
41425
|
-
};
|
|
41426
|
-
const conditionResult = readHydrationConditionResult(statement.test, context, runtime, state);
|
|
41427
|
-
if (conditionResult !== null) {
|
|
41428
|
-
const selectedBranch = conditionResult ? statement.consequent : statement.alternate;
|
|
41429
|
-
return selectedBranch ? readHydrationStatementResult(selectedBranch, context, runtime, state) : {
|
|
41430
|
-
didReturn: false,
|
|
41431
|
-
value: null
|
|
41432
|
-
};
|
|
41433
|
-
}
|
|
41434
|
-
const consequentResult = readHydrationStatementResult(statement.consequent, context, runtime, state);
|
|
41435
|
-
const alternateResult = statement.alternate ? readHydrationStatementResult(statement.alternate, context, runtime, state) : {
|
|
41436
|
-
didReturn: false,
|
|
41437
|
-
value: null
|
|
41438
|
-
};
|
|
41439
|
-
return consequentResult.didReturn && alternateResult.didReturn && consequentResult.value !== null && consequentResult.value === alternateResult.value ? consequentResult : {
|
|
41440
|
-
didReturn: consequentResult.didReturn || alternateResult.didReturn,
|
|
41441
|
-
value: null
|
|
41442
|
-
};
|
|
41443
|
-
};
|
|
41444
|
-
const readHydrationFunctionResult = (functionNode, context, runtime, state) => {
|
|
41445
|
-
if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
|
|
41446
|
-
state.visitedFunctionNodes.add(functionNode);
|
|
41447
|
-
const result = isNodeOfType(functionNode.body, "BlockStatement") ? readHydrationStatementResult(functionNode.body, context, runtime, state).value : readHydrationConditionResult(functionNode.body, context, runtime, state);
|
|
41448
|
-
state.visitedFunctionNodes.delete(functionNode);
|
|
41449
|
-
return result;
|
|
40608
|
+
return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
|
|
41450
40609
|
};
|
|
41451
|
-
const
|
|
41452
|
-
const left = stripParenExpression(leftExpression);
|
|
41453
|
-
const right = stripParenExpression(rightExpression);
|
|
41454
|
-
if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
|
|
41455
|
-
const leftSymbol = scopes.symbolFor(left);
|
|
41456
|
-
const rightSymbol = scopes.symbolFor(right);
|
|
41457
|
-
return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
|
|
41458
|
-
}
|
|
41459
|
-
if (isNodeOfType(left, "MemberExpression") && isNodeOfType(right, "MemberExpression")) return doEquivalentExpressionBindingsMatch(left.object, right.object, scopes) && (!left.computed || doEquivalentExpressionBindingsMatch(left.property, right.property, scopes));
|
|
41460
|
-
if (isNodeOfType(left, "CallExpression") && isNodeOfType(right, "CallExpression")) {
|
|
41461
|
-
const rightArguments = right.arguments ?? [];
|
|
41462
|
-
return doEquivalentExpressionBindingsMatch(left.callee, right.callee, scopes) && (left.arguments ?? []).every((argument, index) => {
|
|
41463
|
-
const rightArgument = rightArguments[index];
|
|
41464
|
-
return Boolean(rightArgument && doEquivalentExpressionBindingsMatch(argument, rightArgument, scopes));
|
|
41465
|
-
});
|
|
41466
|
-
}
|
|
41467
|
-
return true;
|
|
41468
|
-
};
|
|
41469
|
-
const areHelperReturnValuesEquivalent = (leftValue, rightValue, context) => {
|
|
41470
|
-
if (areExpressionsStructurallyEqual(leftValue, rightValue)) return doEquivalentExpressionBindingsMatch(leftValue, rightValue, context.scopes);
|
|
41471
|
-
const leftBoolean = readInitialStateBoolean(leftValue, context.scopes);
|
|
41472
|
-
const rightBoolean = readInitialStateBoolean(rightValue, context.scopes);
|
|
41473
|
-
return leftBoolean !== null && rightBoolean !== null && leftBoolean === rightBoolean;
|
|
41474
|
-
};
|
|
41475
|
-
const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
|
|
41476
|
-
const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
|
|
41477
|
-
return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
|
|
41478
|
-
};
|
|
41479
|
-
const matchHydrationConditionInternal = (expression, context, state) => {
|
|
40610
|
+
const matchHydrationCondition = (expression, context) => {
|
|
41480
40611
|
const unwrappedExpression = stripParenExpression(expression);
|
|
41481
40612
|
const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
|
|
41482
40613
|
if (predicateMatch) return {
|
|
41483
40614
|
predicateMatch,
|
|
41484
40615
|
predicateNode: unwrappedExpression
|
|
41485
40616
|
};
|
|
41486
|
-
if (isNodeOfType(unwrappedExpression, "
|
|
41487
|
-
const symbol = context.scopes.symbolFor(unwrappedExpression);
|
|
41488
|
-
const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
|
|
41489
|
-
if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
|
|
41490
|
-
state.visitedSymbolIds.add(symbol.id);
|
|
41491
|
-
const match = matchHydrationConditionInternal(parameterValue, context, state);
|
|
41492
|
-
state.visitedSymbolIds.delete(symbol.id);
|
|
41493
|
-
return match;
|
|
41494
|
-
}
|
|
41495
|
-
if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
|
|
41496
|
-
state.visitedSymbolIds.add(symbol.id);
|
|
41497
|
-
const match = matchHydrationConditionInternal(symbol.initializer, context, state);
|
|
41498
|
-
state.visitedSymbolIds.delete(symbol.id);
|
|
41499
|
-
return match;
|
|
41500
|
-
}
|
|
41501
|
-
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
41502
|
-
const callArguments = unwrappedExpression.arguments ?? [];
|
|
41503
|
-
if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
|
|
41504
|
-
allowGlobalReactNamespace: true,
|
|
41505
|
-
resolveNamedAliases: true
|
|
41506
|
-
})) {
|
|
41507
|
-
const callbackArgument = callArguments[0];
|
|
41508
|
-
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
|
|
41509
|
-
const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
41510
|
-
return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionResult(callbackFunction, context, state) : null;
|
|
41511
|
-
}
|
|
41512
|
-
const callee = stripParenExpression(unwrappedExpression.callee);
|
|
41513
|
-
if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return matchHydrationConditionInternal(callArguments[0], context, state);
|
|
41514
|
-
const helperFunction = resolveExactLocalFunction(callee, context.scopes);
|
|
41515
|
-
if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
|
|
41516
|
-
const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
|
|
41517
|
-
for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
|
|
41518
|
-
const parameter = helperFunction.params[parameterIndex];
|
|
41519
|
-
const argument = callArguments[parameterIndex];
|
|
41520
|
-
if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
|
|
41521
|
-
const parameterSymbol = context.scopes.symbolFor(parameter);
|
|
41522
|
-
if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
|
|
41523
|
-
}
|
|
41524
|
-
return matchHydrationFunctionResult(helperFunction, context, {
|
|
41525
|
-
...state,
|
|
41526
|
-
parameterValuesBySymbolId
|
|
41527
|
-
});
|
|
41528
|
-
}
|
|
41529
|
-
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
|
|
40617
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
|
|
41530
40618
|
if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
|
|
41531
|
-
const leftMatch =
|
|
41532
|
-
const rightMatch =
|
|
40619
|
+
const leftMatch = matchHydrationCondition(unwrappedExpression.left, context);
|
|
40620
|
+
const rightMatch = matchHydrationCondition(unwrappedExpression.right, context);
|
|
40621
|
+
if (leftMatch && rightMatch) {
|
|
40622
|
+
const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client");
|
|
40623
|
+
const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server");
|
|
40624
|
+
return clientResult !== null && serverResult !== null && clientResult !== serverResult ? leftMatch : null;
|
|
40625
|
+
}
|
|
41533
40626
|
const nestedMatch = leftMatch ?? rightMatch;
|
|
41534
40627
|
if (!nestedMatch) return null;
|
|
41535
|
-
const
|
|
41536
|
-
|
|
41537
|
-
return
|
|
40628
|
+
const otherResult = readInitialStateBoolean(leftMatch ? unwrappedExpression.right : unwrappedExpression.left, context.scopes);
|
|
40629
|
+
if (unwrappedExpression.operator === "&&" && otherResult === false || unwrappedExpression.operator === "||" && otherResult === true) return null;
|
|
40630
|
+
return nestedMatch;
|
|
41538
40631
|
};
|
|
41539
|
-
const matchHydrationReturningStatement = (statement, context, state) => {
|
|
41540
|
-
if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? matchHydrationConditionInternal(statement.argument, context, state) : null;
|
|
41541
|
-
if (isNodeOfType(statement, "IfStatement")) {
|
|
41542
|
-
const conditionMatch = matchHydrationConditionInternal(statement.test, context, state);
|
|
41543
|
-
const consequentValues = getReturnedValues(statement.consequent);
|
|
41544
|
-
const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
|
|
41545
|
-
if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
|
|
41546
|
-
return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
|
|
41547
|
-
}
|
|
41548
|
-
if (!isNodeOfType(statement, "BlockStatement")) return null;
|
|
41549
|
-
for (const childStatement of statement.body) {
|
|
41550
|
-
const match = matchHydrationReturningStatement(childStatement, context, state);
|
|
41551
|
-
if (match) return match;
|
|
41552
|
-
if (statementAlwaysExits(childStatement)) break;
|
|
41553
|
-
}
|
|
41554
|
-
return null;
|
|
41555
|
-
};
|
|
41556
|
-
const matchHydrationFunctionResult = (functionNode, context, state) => {
|
|
41557
|
-
if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
|
|
41558
|
-
state.visitedFunctionNodes.add(functionNode);
|
|
41559
|
-
const match = isNodeOfType(functionNode.body, "BlockStatement") ? matchHydrationReturningStatement(functionNode.body, context, state) : matchHydrationConditionInternal(functionNode.body, context, state);
|
|
41560
|
-
state.visitedFunctionNodes.delete(functionNode);
|
|
41561
|
-
return match;
|
|
41562
|
-
};
|
|
41563
|
-
const matchHydrationCondition = (expression, context) => matchHydrationConditionInternal(expression, context, {
|
|
41564
|
-
parameterValuesBySymbolId: /* @__PURE__ */ new Map(),
|
|
41565
|
-
visitedFunctionNodes: /* @__PURE__ */ new Set(),
|
|
41566
|
-
visitedSymbolIds: /* @__PURE__ */ new Set()
|
|
41567
|
-
});
|
|
41568
40632
|
const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
|
|
41569
40633
|
const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
|
|
41570
40634
|
if (!leftNode || !rightNode) return leftNode === rightNode;
|
|
@@ -41707,17 +40771,17 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
|
|
|
41707
40771
|
const { predicateMatch, predicateNode } = conditionMatch;
|
|
41708
40772
|
if (reportedNodes.has(predicateNode)) return;
|
|
41709
40773
|
if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
|
|
41710
|
-
const componentOrHookNode = findRenderPhaseComponentOrHook(
|
|
40774
|
+
const componentOrHookNode = findRenderPhaseComponentOrHook(predicateNode, context.scopes);
|
|
41711
40775
|
if (!componentOrHookNode) return;
|
|
41712
40776
|
if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
|
|
41713
|
-
if (requiresRenderedContext && !isInRenderedOutput(
|
|
40777
|
+
if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
|
|
41714
40778
|
if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
|
|
41715
|
-
const attribute = findEnclosingJsxAttribute(
|
|
40779
|
+
const attribute = findEnclosingJsxAttribute(predicateNode);
|
|
41716
40780
|
if (!attribute || isEventHandlerAttribute(attribute)) return;
|
|
41717
40781
|
}
|
|
41718
|
-
if (fileIsEmailTemplate || isGatedByFalsyInitialState(
|
|
41719
|
-
if (isAfterClientOnlyEarlyReturn(
|
|
41720
|
-
const openingElement = findEnclosingJsxOpeningElement(
|
|
40782
|
+
if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
|
|
40783
|
+
if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
|
|
40784
|
+
const openingElement = findEnclosingJsxOpeningElement(predicateNode);
|
|
41721
40785
|
if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
|
|
41722
40786
|
if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
|
|
41723
40787
|
if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
|
|
@@ -42099,7 +41163,7 @@ const noInitializeState = defineRule({
|
|
|
42099
41163
|
if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
|
|
42100
41164
|
const analysis = getProgramAnalysis(node);
|
|
42101
41165
|
if (!analysis) return;
|
|
42102
|
-
for (const fact of collectEffectStateWriteFacts(analysis,
|
|
41166
|
+
for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
|
|
42103
41167
|
if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
|
|
42104
41168
|
const stateName = getStateName(fact.stateDeclarator);
|
|
42105
41169
|
context.report({
|
|
@@ -42457,8 +41521,7 @@ const noJsxElementType = defineRule({
|
|
|
42457
41521
|
create: (context) => {
|
|
42458
41522
|
let isJsxImported = false;
|
|
42459
41523
|
const flaggedAnnotations = [];
|
|
42460
|
-
const
|
|
42461
|
-
if (!(isNodeOfType(functionNode, "TSDeclareFunction") ? Boolean(functionNode.id && isReactComponentName(functionNode.id.name)) : isComponentFunction$1(functionNode))) return;
|
|
41524
|
+
const checkReturnType = (returnType) => {
|
|
42462
41525
|
const typeAnnotation = extractReturnTypeAnnotation(returnType);
|
|
42463
41526
|
if (!typeAnnotation) return;
|
|
42464
41527
|
if (isJsxElementTypeReference(typeAnnotation)) flaggedAnnotations.push(typeAnnotation);
|
|
@@ -42468,16 +41531,19 @@ const noJsxElementType = defineRule({
|
|
|
42468
41531
|
if (isJsxImportBinding(node)) isJsxImported = true;
|
|
42469
41532
|
},
|
|
42470
41533
|
FunctionDeclaration(node) {
|
|
42471
|
-
|
|
41534
|
+
checkReturnType(node.returnType);
|
|
42472
41535
|
},
|
|
42473
41536
|
ArrowFunctionExpression(node) {
|
|
42474
|
-
|
|
41537
|
+
checkReturnType(node.returnType);
|
|
42475
41538
|
},
|
|
42476
41539
|
FunctionExpression(node) {
|
|
42477
|
-
|
|
41540
|
+
checkReturnType(node.returnType);
|
|
42478
41541
|
},
|
|
42479
41542
|
TSDeclareFunction(node) {
|
|
42480
|
-
|
|
41543
|
+
checkReturnType(node.returnType);
|
|
41544
|
+
},
|
|
41545
|
+
TSMethodSignature(node) {
|
|
41546
|
+
checkReturnType(node.returnType);
|
|
42481
41547
|
},
|
|
42482
41548
|
"Program:exit"() {
|
|
42483
41549
|
if (isJsxImported) return;
|
|
@@ -47701,69 +46767,6 @@ const noRedundantShouldComponentUpdate = defineRule({
|
|
|
47701
46767
|
}
|
|
47702
46768
|
});
|
|
47703
46769
|
//#endregion
|
|
47704
|
-
//#region src/plugin/rules/correctness/no-ref-callback-cleanup-before-react-19.ts
|
|
47705
|
-
const resolveFunctionExpressions = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
47706
|
-
const expression = stripParenExpression(rawExpression);
|
|
47707
|
-
if (isFunctionLike$1(expression)) return expression.async || expression.generator ? [] : [expression];
|
|
47708
|
-
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
47709
|
-
if (isNodeOfType(expression.test, "Literal")) return resolveFunctionExpressions(expression.test.value ? expression.consequent : expression.alternate, scopes, visitedSymbolIds);
|
|
47710
|
-
return [...resolveFunctionExpressions(expression.consequent, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.alternate, scopes, visitedSymbolIds)];
|
|
47711
|
-
}
|
|
47712
|
-
if (isNodeOfType(expression, "LogicalExpression")) {
|
|
47713
|
-
if (isNodeOfType(expression.left, "Literal")) {
|
|
47714
|
-
const isLeftTruthy = Boolean(expression.left.value);
|
|
47715
|
-
if (expression.operator === "&&" && !isLeftTruthy) return [];
|
|
47716
|
-
if (expression.operator === "||" && isLeftTruthy) return [];
|
|
47717
|
-
if (expression.operator === "??" && expression.left.value !== null) return [];
|
|
47718
|
-
}
|
|
47719
|
-
if (expression.operator === "&&") return resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds);
|
|
47720
|
-
return [...resolveFunctionExpressions(expression.left, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds)];
|
|
47721
|
-
}
|
|
47722
|
-
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
47723
|
-
const finalExpression = expression.expressions.at(-1);
|
|
47724
|
-
return finalExpression ? resolveFunctionExpressions(finalExpression, scopes, visitedSymbolIds) : [];
|
|
47725
|
-
}
|
|
47726
|
-
if (isNodeOfType(expression, "CallExpression")) {
|
|
47727
|
-
if (!isReactApiCall(expression, "useCallback", scopes)) return [];
|
|
47728
|
-
const callback = expression.arguments[0];
|
|
47729
|
-
return callback && !isNodeOfType(callback, "SpreadElement") ? resolveFunctionExpressions(callback, scopes, visitedSymbolIds) : [];
|
|
47730
|
-
}
|
|
47731
|
-
if (!isNodeOfType(expression, "Identifier")) return [];
|
|
47732
|
-
const symbol = scopes.symbolFor(expression);
|
|
47733
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return [];
|
|
47734
|
-
if (symbol.kind === "function" && isNodeOfType(symbol.declarationNode, "FunctionDeclaration") && symbol.references.every((reference) => reference.flag === "read")) return resolveFunctionExpressions(symbol.declarationNode, scopes, new Set([...visitedSymbolIds, symbol.id]));
|
|
47735
|
-
const initializer = getDirectConstInitializer(symbol);
|
|
47736
|
-
if (!initializer) return [];
|
|
47737
|
-
return resolveFunctionExpressions(initializer, scopes, new Set([...visitedSymbolIds, symbol.id]));
|
|
47738
|
-
};
|
|
47739
|
-
const functionReturnsCleanupFunction = (functionExpression, scopes) => {
|
|
47740
|
-
if (!isFunctionLike$1(functionExpression)) return false;
|
|
47741
|
-
if (!isNodeOfType(functionExpression.body, "BlockStatement")) return resolveFunctionExpressions(functionExpression.body, scopes).length > 0;
|
|
47742
|
-
return collectFunctionReturnStatements(functionExpression).some((returnStatement) => Boolean(returnStatement.argument && resolveFunctionExpressions(returnStatement.argument, scopes).length > 0));
|
|
47743
|
-
};
|
|
47744
|
-
const callbackReturnsCleanupFunction = (callback, scopes) => {
|
|
47745
|
-
return resolveFunctionExpressions(callback, scopes).some((functionExpression) => functionReturnsCleanupFunction(functionExpression, scopes));
|
|
47746
|
-
};
|
|
47747
|
-
const noRefCallbackCleanupBeforeReact19 = defineRule({
|
|
47748
|
-
id: "no-ref-callback-cleanup-before-react-19",
|
|
47749
|
-
title: "Ref cleanup requires React 19",
|
|
47750
|
-
requires: ["react:18"],
|
|
47751
|
-
disabledWhen: ["react:19"],
|
|
47752
|
-
severity: "warn",
|
|
47753
|
-
recommendation: "React 18 ignores functions returned from ref callbacks. Handle cleanup when React calls the ref with `null`, or require React 19 before returning a cleanup function.",
|
|
47754
|
-
create: (context) => ({ JSXAttribute(node) {
|
|
47755
|
-
if (getJsxAttributeName(node.name) !== "ref") return;
|
|
47756
|
-
if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
|
|
47757
|
-
const callback = node.value.expression;
|
|
47758
|
-
if (!callback || isNodeOfType(callback, "JSXEmptyExpression")) return;
|
|
47759
|
-
if (!callbackReturnsCleanupFunction(callback, context.scopes)) return;
|
|
47760
|
-
context.report({
|
|
47761
|
-
node,
|
|
47762
|
-
message: "This ref callback returns a cleanup function, but React 18 ignores ref cleanup returns, so the cleanup never runs. Handle detachment when React calls the ref with `null`, or require React 19."
|
|
47763
|
-
});
|
|
47764
|
-
} })
|
|
47765
|
-
});
|
|
47766
|
-
//#endregion
|
|
47767
46770
|
//#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
|
|
47768
46771
|
const REPEATED_ANCESTOR_TYPES = new Set([
|
|
47769
46772
|
"DoWhileStatement",
|
|
@@ -57725,39 +56728,8 @@ const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node,
|
|
|
57725
56728
|
destructureIndex: 1
|
|
57726
56729
|
});
|
|
57727
56730
|
//#endregion
|
|
57728
|
-
//#region src/plugin/utils/unwrap-return-expression.ts
|
|
57729
|
-
const unwrapReturnExpression = (node) => isNodeOfType(node, "ReturnStatement") && node.argument ? node.argument : node;
|
|
57730
|
-
//#endregion
|
|
57731
56731
|
//#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
|
|
57732
56732
|
const USE_EFFECT_ONLY = new Set(["useEffect"]);
|
|
57733
|
-
const USE_CALLBACK_ONLY = new Set(["useCallback"]);
|
|
57734
|
-
const USE_STATE_ONLY = new Set(["useState"]);
|
|
57735
|
-
const REACT_API_CALL_OPTIONS = {
|
|
57736
|
-
allowGlobalReactNamespace: true,
|
|
57737
|
-
allowUnboundBareCalls: true,
|
|
57738
|
-
resolveNamedAliases: true
|
|
57739
|
-
};
|
|
57740
|
-
const expressionReadsDerivedSymbol = (context, expression, stateDerivedSymbolIds) => {
|
|
57741
|
-
let readsDerivedSymbol = false;
|
|
57742
|
-
walkAst(expression, (node) => {
|
|
57743
|
-
if (readsDerivedSymbol) return false;
|
|
57744
|
-
if (node !== expression && isFunctionLike$1(node)) return false;
|
|
57745
|
-
if (isNodeOfType(node, "Identifier") && stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) readsDerivedSymbol = true;
|
|
57746
|
-
});
|
|
57747
|
-
return readsDerivedSymbol;
|
|
57748
|
-
};
|
|
57749
|
-
const getStaticObjectPropertyName = (property) => {
|
|
57750
|
-
if (!isNodeOfType(property, "Property") || property.computed || property.method || property.kind !== "init") return null;
|
|
57751
|
-
if (isNodeOfType(property.key, "Identifier")) return property.key.name;
|
|
57752
|
-
if (isNodeOfType(property.key, "Literal") && (typeof property.key.value === "string" || typeof property.key.value === "number")) return String(property.key.value);
|
|
57753
|
-
return null;
|
|
57754
|
-
};
|
|
57755
|
-
const isNonVisibleJsxSpreadProperty = (propertyName) => propertyName === "id" || propertyName.startsWith("aria-") || /^on[A-Z]/.test(propertyName);
|
|
57756
|
-
const isTransparentAssignmentTarget = (identifier) => {
|
|
57757
|
-
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
57758
|
-
const parent = expressionRoot.parent;
|
|
57759
|
-
return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expressionRoot || isNodeOfType(parent, "UpdateExpression") && parent.argument === expressionRoot || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expressionRoot);
|
|
57760
|
-
};
|
|
57761
56733
|
const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
|
|
57762
56734
|
let readsCurrent = false;
|
|
57763
56735
|
walkAst(argument, (child) => {
|
|
@@ -57809,166 +56781,6 @@ const isStateUsedOnlyInIdOrAriaAttributes = (setterCall, setterName) => {
|
|
|
57809
56781
|
});
|
|
57810
56782
|
return referenceCount > 0 && !nonAriaReferenceFound;
|
|
57811
56783
|
};
|
|
57812
|
-
const isGlobalWindowMember = (context, node, propertyName) => {
|
|
57813
|
-
const member = stripParenExpression(node);
|
|
57814
|
-
if (!isNodeOfType(member, "MemberExpression") || member.computed) return false;
|
|
57815
|
-
const receiver = stripParenExpression(member.object);
|
|
57816
|
-
return isNodeOfType(receiver, "Identifier") && receiver.name === "window" && context.scopes.isGlobalReference(receiver) && isNodeOfType(member.property, "Identifier") && member.property.name === propertyName;
|
|
57817
|
-
};
|
|
57818
|
-
const getDirectWindowWidthSetter = (context, statement) => {
|
|
57819
|
-
const call = unwrapDiscardedExpression(statement);
|
|
57820
|
-
if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 1) return null;
|
|
57821
|
-
if (!isNodeOfType(call.callee, "Identifier") || !isSetterCall(call)) return null;
|
|
57822
|
-
const argument = call.arguments[0];
|
|
57823
|
-
return isGlobalWindowMember(context, argument, "innerWidth") ? call : null;
|
|
57824
|
-
};
|
|
57825
|
-
const getResizeListenerHandler = (context, statement, methodName) => {
|
|
57826
|
-
const call = unwrapDiscardedExpression(statement);
|
|
57827
|
-
if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 2) return null;
|
|
57828
|
-
if (!isGlobalWindowMember(context, call.callee, methodName)) return null;
|
|
57829
|
-
const eventName = call.arguments[0];
|
|
57830
|
-
const handler = call.arguments[1];
|
|
57831
|
-
if (!isNodeOfType(eventName, "Literal") || eventName.value !== "resize") return null;
|
|
57832
|
-
return isNodeOfType(handler, "Identifier") ? handler : null;
|
|
57833
|
-
};
|
|
57834
|
-
const getCleanupResizeHandler = (context, statement) => {
|
|
57835
|
-
if (!isNodeOfType(statement, "ReturnStatement") || !isFunctionLike$1(statement.argument)) return null;
|
|
57836
|
-
const cleanupStatements = getCallbackStatements(statement.argument);
|
|
57837
|
-
if (cleanupStatements.length !== 1) return null;
|
|
57838
|
-
return getResizeListenerHandler(context, unwrapReturnExpression(cleanupStatements[0]), "removeEventListener");
|
|
57839
|
-
};
|
|
57840
|
-
const findExactViewportState = (context, componentFunction, setterCall) => {
|
|
57841
|
-
if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
|
|
57842
|
-
const componentBody = componentFunction.body;
|
|
57843
|
-
if (!isNodeOfType(setterCall.callee, "Identifier")) return null;
|
|
57844
|
-
const setterSymbol = context.scopes.symbolFor(setterCall.callee);
|
|
57845
|
-
if (!setterSymbol || setterSymbol.kind !== "const" || !isNodeOfType(setterSymbol.declarationNode, "VariableDeclarator")) return null;
|
|
57846
|
-
const declarator = setterSymbol.declarationNode;
|
|
57847
|
-
if (!isNodeOfType(declarator.id, "ArrayPattern")) return null;
|
|
57848
|
-
const stateIdentifier = declarator.id.elements?.[0];
|
|
57849
|
-
const setterIdentifier = declarator.id.elements?.[1];
|
|
57850
|
-
if (!isNodeOfType(stateIdentifier, "Identifier") || !isNodeOfType(setterIdentifier, "Identifier") || setterIdentifier !== setterSymbol.bindingIdentifier || !isNodeOfType(declarator.init, "CallExpression") || !isReactApiCall(declarator.init, USE_STATE_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return null;
|
|
57851
|
-
const initializer = declarator.init.arguments?.[0];
|
|
57852
|
-
if (!isNodeOfType(initializer, "Literal") || initializer.value !== 0) return null;
|
|
57853
|
-
const stateSymbol = context.scopes.symbolFor(stateIdentifier);
|
|
57854
|
-
if (!stateSymbol) return null;
|
|
57855
|
-
const stateDerivedSymbolIds = new Set([stateSymbol.id]);
|
|
57856
|
-
let didAddDerivedSymbol = true;
|
|
57857
|
-
while (didAddDerivedSymbol) {
|
|
57858
|
-
didAddDerivedSymbol = false;
|
|
57859
|
-
for (const statement of componentBody.body ?? []) {
|
|
57860
|
-
if (!isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
57861
|
-
for (const candidateDeclarator of statement.declarations ?? []) {
|
|
57862
|
-
if (!isNodeOfType(candidateDeclarator.id, "Identifier") || !candidateDeclarator.init) continue;
|
|
57863
|
-
const candidateInitializer = stripParenExpression(candidateDeclarator.init);
|
|
57864
|
-
if (isFunctionLike$1(candidateInitializer) || isNodeOfType(candidateInitializer, "CallExpression") && isReactApiCall(candidateInitializer, USE_CALLBACK_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) continue;
|
|
57865
|
-
if (!expressionReadsDerivedSymbol(context, candidateInitializer, stateDerivedSymbolIds)) continue;
|
|
57866
|
-
const candidateSymbol = context.scopes.symbolFor(candidateDeclarator.id);
|
|
57867
|
-
if (candidateSymbol?.kind === "const" && candidateSymbol.references.every((reference) => reference.flag === "read" && !isTransparentAssignmentTarget(reference.identifier)) && !stateDerivedSymbolIds.has(candidateSymbol.id)) {
|
|
57868
|
-
stateDerivedSymbolIds.add(candidateSymbol.id);
|
|
57869
|
-
didAddDerivedSymbol = true;
|
|
57870
|
-
}
|
|
57871
|
-
}
|
|
57872
|
-
}
|
|
57873
|
-
}
|
|
57874
|
-
const staticSpreadVisibilityBySymbolId = /* @__PURE__ */ new Map();
|
|
57875
|
-
const hasOnlyStaticObjectReferences = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
57876
|
-
const symbol = context.scopes.symbolFor(identifier);
|
|
57877
|
-
if (!symbol) return false;
|
|
57878
|
-
if (visitedSymbolIds.has(symbol.id)) return true;
|
|
57879
|
-
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
57880
|
-
nextVisitedSymbolIds.add(symbol.id);
|
|
57881
|
-
let hasUnknownReference = false;
|
|
57882
|
-
walkAst(componentBody, (node) => {
|
|
57883
|
-
if (hasUnknownReference || !isNodeOfType(node, "Identifier") || context.scopes.symbolFor(node)?.id !== symbol.id || node === symbol.bindingIdentifier) return;
|
|
57884
|
-
const referenceRoot = findTransparentExpressionRoot(node);
|
|
57885
|
-
const parent = referenceRoot.parent;
|
|
57886
|
-
if (isNodeOfType(parent, "JSXSpreadAttribute") && parent.argument === referenceRoot) return;
|
|
57887
|
-
if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier") && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const" && hasOnlyStaticObjectReferences(parent.id, nextVisitedSymbolIds)) return;
|
|
57888
|
-
hasUnknownReference = true;
|
|
57889
|
-
return false;
|
|
57890
|
-
});
|
|
57891
|
-
return !hasUnknownReference;
|
|
57892
|
-
};
|
|
57893
|
-
const classifyStaticSpreadObject = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
57894
|
-
const symbol = context.scopes.symbolFor(identifier);
|
|
57895
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return "unknown";
|
|
57896
|
-
const cachedVisibility = staticSpreadVisibilityBySymbolId.get(symbol.id);
|
|
57897
|
-
if (cachedVisibility) return cachedVisibility;
|
|
57898
|
-
if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || symbol.declarationNode.id !== symbol.bindingIdentifier || !symbol.declarationNode.init) return "unknown";
|
|
57899
|
-
if (!hasOnlyStaticObjectReferences(identifier)) return "unknown";
|
|
57900
|
-
const initializer = stripParenExpression(symbol.declarationNode.init);
|
|
57901
|
-
const nextVisitedSymbolIds = new Set(visitedSymbolIds);
|
|
57902
|
-
nextVisitedSymbolIds.add(symbol.id);
|
|
57903
|
-
if (isNodeOfType(initializer, "Identifier")) {
|
|
57904
|
-
const visibility = classifyStaticSpreadObject(initializer, nextVisitedSymbolIds);
|
|
57905
|
-
staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
|
|
57906
|
-
return visibility;
|
|
57907
|
-
}
|
|
57908
|
-
if (!isNodeOfType(initializer, "ObjectExpression")) return "unknown";
|
|
57909
|
-
let visibility = "non-visible";
|
|
57910
|
-
for (const property of initializer.properties ?? []) {
|
|
57911
|
-
const propertyName = getStaticObjectPropertyName(property);
|
|
57912
|
-
if (!isNodeOfType(property, "Property") || !propertyName) {
|
|
57913
|
-
visibility = "unknown";
|
|
57914
|
-
break;
|
|
57915
|
-
}
|
|
57916
|
-
if (expressionReadsDerivedSymbol(context, property.value, stateDerivedSymbolIds) && !isNonVisibleJsxSpreadProperty(propertyName)) visibility = "visible";
|
|
57917
|
-
}
|
|
57918
|
-
staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
|
|
57919
|
-
return visibility;
|
|
57920
|
-
};
|
|
57921
|
-
let hasNonAriaReference = false;
|
|
57922
|
-
walkAst(componentBody, (node) => {
|
|
57923
|
-
if (hasNonAriaReference) return false;
|
|
57924
|
-
if (!isNodeOfType(node, "Identifier") || !stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) return;
|
|
57925
|
-
if (findEnclosingFunction$1(node) !== componentFunction) return;
|
|
57926
|
-
const parent = node.parent;
|
|
57927
|
-
if (parent && (isNodeOfType(parent, "MemberExpression") && parent.property === node && !parent.computed || isNodeOfType(parent, "Property") && parent.key === node && !parent.computed)) return;
|
|
57928
|
-
let cursor = parent;
|
|
57929
|
-
while (cursor && cursor !== componentBody) {
|
|
57930
|
-
if (isFunctionLike$1(cursor)) return;
|
|
57931
|
-
if (isNodeOfType(cursor, "JSXSpreadAttribute")) {
|
|
57932
|
-
if (isNodeOfType(node, "Identifier") && classifyStaticSpreadObject(node) === "visible") hasNonAriaReference = true;
|
|
57933
|
-
return;
|
|
57934
|
-
}
|
|
57935
|
-
if (isNodeOfType(cursor, "JSXAttribute")) {
|
|
57936
|
-
if (isEventHandlerAttribute(cursor)) return;
|
|
57937
|
-
if (!isInsideIdOrAriaAttribute(node)) hasNonAriaReference = true;
|
|
57938
|
-
return;
|
|
57939
|
-
}
|
|
57940
|
-
if (isNodeOfType(cursor, "ReturnStatement")) {
|
|
57941
|
-
hasNonAriaReference = true;
|
|
57942
|
-
return;
|
|
57943
|
-
}
|
|
57944
|
-
cursor = cursor.parent;
|
|
57945
|
-
}
|
|
57946
|
-
});
|
|
57947
|
-
return hasNonAriaReference ? stateIdentifier.name : null;
|
|
57948
|
-
};
|
|
57949
|
-
const isExactViewportSubscriptionEffect = (context, effectCall, callback) => {
|
|
57950
|
-
if (!isReactApiCall(effectCall, USE_EFFECT_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return false;
|
|
57951
|
-
if (!isFunctionLike$1(callback) || callback.async || !isNodeOfType(callback.body, "BlockStatement")) return false;
|
|
57952
|
-
const statements = getCallbackStatements(callback);
|
|
57953
|
-
if (statements.length !== 4) return false;
|
|
57954
|
-
const handlerDeclaration = statements[0];
|
|
57955
|
-
if (!isNodeOfType(handlerDeclaration, "VariableDeclaration") || handlerDeclaration.kind !== "const" || handlerDeclaration.declarations?.length !== 1) return false;
|
|
57956
|
-
const handlerDeclarator = handlerDeclaration.declarations[0];
|
|
57957
|
-
if (!isNodeOfType(handlerDeclarator.id, "Identifier") || !isFunctionLike$1(handlerDeclarator.init)) return false;
|
|
57958
|
-
const handlerStatements = getCallbackStatements(handlerDeclarator.init);
|
|
57959
|
-
if (handlerStatements.length !== 1) return false;
|
|
57960
|
-
const handlerSetter = getDirectWindowWidthSetter(context, unwrapReturnExpression(handlerStatements[0]));
|
|
57961
|
-
const subscribedHandler = getResizeListenerHandler(context, statements[1], "addEventListener");
|
|
57962
|
-
const immediateSetter = getDirectWindowWidthSetter(context, statements[2]);
|
|
57963
|
-
const cleanupHandler = getCleanupResizeHandler(context, statements[3]);
|
|
57964
|
-
if (!handlerSetter || !subscribedHandler || !immediateSetter || !cleanupHandler) return false;
|
|
57965
|
-
const handlerSymbol = context.scopes.symbolFor(handlerDeclarator.id);
|
|
57966
|
-
if (!handlerSymbol || context.scopes.symbolFor(subscribedHandler) !== handlerSymbol || context.scopes.symbolFor(cleanupHandler) !== handlerSymbol) return false;
|
|
57967
|
-
if (!isNodeOfType(handlerSetter.callee, "Identifier") || !isNodeOfType(immediateSetter.callee, "Identifier") || context.scopes.symbolFor(handlerSetter.callee) !== context.scopes.symbolFor(immediateSetter.callee)) return false;
|
|
57968
|
-
const componentFunction = findEnclosingFunction$1(effectCall);
|
|
57969
|
-
if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return false;
|
|
57970
|
-
return findExactViewportState(context, componentFunction, immediateSetter) !== null;
|
|
57971
|
-
};
|
|
57972
56784
|
const renderingHydrationNoFlicker = defineRule({
|
|
57973
56785
|
id: "rendering-hydration-no-flicker",
|
|
57974
56786
|
title: "useEffect setState flashes on mount",
|
|
@@ -57981,14 +56793,7 @@ const renderingHydrationNoFlicker = defineRule({
|
|
|
57981
56793
|
if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
|
|
57982
56794
|
const callback = getEffectCallback(node);
|
|
57983
56795
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
57984
|
-
|
|
57985
|
-
context.report({
|
|
57986
|
-
node,
|
|
57987
|
-
message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
|
|
57988
|
-
});
|
|
57989
|
-
return;
|
|
57990
|
-
}
|
|
57991
|
-
const bodyStatements = getCallbackStatements(callback);
|
|
56796
|
+
const bodyStatements = (isNodeOfType(callback.body, "BlockStatement") ? callback.body.body ?? [] : [callback.body]).filter((statement) => !isNoOpStatement(statement));
|
|
57992
56797
|
if (bodyStatements.length !== 1) return;
|
|
57993
56798
|
const soleStatement = bodyStatements[0];
|
|
57994
56799
|
if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
|
|
@@ -67820,6 +66625,14 @@ const isStateKey = (key) => {
|
|
|
67820
66625
|
if (isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value === "state";
|
|
67821
66626
|
return false;
|
|
67822
66627
|
};
|
|
66628
|
+
const findEnclosingClass = (node) => {
|
|
66629
|
+
let ancestor = node.parent;
|
|
66630
|
+
while (ancestor) {
|
|
66631
|
+
if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
|
|
66632
|
+
ancestor = ancestor.parent ?? null;
|
|
66633
|
+
}
|
|
66634
|
+
return null;
|
|
66635
|
+
};
|
|
67823
66636
|
const isInConstructor = (node) => {
|
|
67824
66637
|
let ancestor = node.parent;
|
|
67825
66638
|
while (ancestor) {
|
|
@@ -72531,17 +71344,6 @@ const reactDoctorRules = [
|
|
|
72531
71344
|
requires: [...new Set(["react", ...noRedundantShouldComponentUpdate.requires ?? []])]
|
|
72532
71345
|
}
|
|
72533
71346
|
},
|
|
72534
|
-
{
|
|
72535
|
-
key: "react-doctor/no-ref-callback-cleanup-before-react-19",
|
|
72536
|
-
id: "no-ref-callback-cleanup-before-react-19",
|
|
72537
|
-
source: "react-doctor",
|
|
72538
|
-
originallyExternal: false,
|
|
72539
|
-
rule: {
|
|
72540
|
-
...noRefCallbackCleanupBeforeReact19,
|
|
72541
|
-
framework: "global",
|
|
72542
|
-
category: "Bugs"
|
|
72543
|
-
}
|
|
72544
|
-
},
|
|
72545
71347
|
{
|
|
72546
71348
|
key: "react-doctor/no-ref-current-in-render",
|
|
72547
71349
|
id: "no-ref-current-in-render",
|