oxlint-plugin-react-doctor 0.7.9-dev.093619c → 0.7.9-dev.0ff57fa
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 +13 -520
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -5503,29 +5503,6 @@ declare const REACT_DOCTOR_RULES: readonly [{
|
|
|
5503
5503
|
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
5504
5504
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5505
5505
|
};
|
|
5506
|
-
}, {
|
|
5507
|
-
readonly key: "react-doctor/no-ref-callback-cleanup-before-react-19";
|
|
5508
|
-
readonly id: "no-ref-callback-cleanup-before-react-19";
|
|
5509
|
-
readonly source: "react-doctor";
|
|
5510
|
-
readonly originallyExternal: false;
|
|
5511
|
-
readonly rule: {
|
|
5512
|
-
readonly framework: "global";
|
|
5513
|
-
readonly category: "Bugs";
|
|
5514
|
-
readonly id: string;
|
|
5515
|
-
readonly title?: string;
|
|
5516
|
-
readonly severity: RuleSeverity;
|
|
5517
|
-
readonly requires?: ReadonlyArray<Capability>;
|
|
5518
|
-
readonly disabledWhen?: ReadonlyArray<Capability>;
|
|
5519
|
-
readonly tags?: ReadonlyArray<string>;
|
|
5520
|
-
readonly matchByOccurrence?: boolean;
|
|
5521
|
-
readonly defaultEnabled?: boolean;
|
|
5522
|
-
readonly lifecycle?: "retired";
|
|
5523
|
-
readonly scan?: FileScan;
|
|
5524
|
-
readonly committedFilesOnly?: boolean;
|
|
5525
|
-
readonly recommendation?: string;
|
|
5526
|
-
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
5527
|
-
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5528
|
-
};
|
|
5529
5506
|
}, {
|
|
5530
5507
|
readonly key: "react-doctor/no-ref-current-in-render";
|
|
5531
5508
|
readonly id: "no-ref-current-in-render";
|
|
@@ -14815,29 +14792,6 @@ declare const RULES: readonly [{
|
|
|
14815
14792
|
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
14816
14793
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
14817
14794
|
};
|
|
14818
|
-
}, {
|
|
14819
|
-
readonly key: "react-doctor/no-ref-callback-cleanup-before-react-19";
|
|
14820
|
-
readonly id: "no-ref-callback-cleanup-before-react-19";
|
|
14821
|
-
readonly source: "react-doctor";
|
|
14822
|
-
readonly originallyExternal: false;
|
|
14823
|
-
readonly rule: {
|
|
14824
|
-
readonly framework: "global";
|
|
14825
|
-
readonly category: "Bugs";
|
|
14826
|
-
readonly id: string;
|
|
14827
|
-
readonly title?: string;
|
|
14828
|
-
readonly severity: RuleSeverity;
|
|
14829
|
-
readonly requires?: ReadonlyArray<Capability>;
|
|
14830
|
-
readonly disabledWhen?: ReadonlyArray<Capability>;
|
|
14831
|
-
readonly tags?: ReadonlyArray<string>;
|
|
14832
|
-
readonly matchByOccurrence?: boolean;
|
|
14833
|
-
readonly defaultEnabled?: boolean;
|
|
14834
|
-
readonly lifecycle?: "retired";
|
|
14835
|
-
readonly scan?: FileScan;
|
|
14836
|
-
readonly committedFilesOnly?: boolean;
|
|
14837
|
-
readonly recommendation?: string;
|
|
14838
|
-
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
14839
|
-
readonly create: (context: RuleContext) => RuleVisitors;
|
|
14840
|
-
};
|
|
14841
14795
|
}, {
|
|
14842
14796
|
readonly key: "react-doctor/no-ref-current-in-render";
|
|
14843
14797
|
readonly id: "no-ref-current-in-render";
|
package/dist/index.js
CHANGED
|
@@ -13652,7 +13652,7 @@ const getPromiseChainCallForCallback = (candidate) => {
|
|
|
13652
13652
|
if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
|
|
13653
13653
|
return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
|
|
13654
13654
|
};
|
|
13655
|
-
const
|
|
13655
|
+
const collectEffectInvokedFunctions = (effectCallback) => {
|
|
13656
13656
|
const invokedFunctions = new Set([effectCallback]);
|
|
13657
13657
|
const localFunctionBindings = /* @__PURE__ */ new Map();
|
|
13658
13658
|
const calledBindingNames = /* @__PURE__ */ new Set();
|
|
@@ -13686,14 +13686,12 @@ const collectInvokedFunctions = (effectCallback, includePromiseCallbacks) => {
|
|
|
13686
13686
|
calledBindingNames.add(callee.name);
|
|
13687
13687
|
return;
|
|
13688
13688
|
}
|
|
13689
|
-
if (
|
|
13689
|
+
if (isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
|
|
13690
13690
|
});
|
|
13691
13691
|
for (const calledName of calledBindingNames) enqueue(localFunctionBindings.get(calledName));
|
|
13692
13692
|
}
|
|
13693
13693
|
return invokedFunctions;
|
|
13694
13694
|
};
|
|
13695
|
-
const collectEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, true);
|
|
13696
|
-
const collectSynchronouslyEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, false);
|
|
13697
13695
|
//#endregion
|
|
13698
13696
|
//#region src/plugin/utils/is-react-hook-name.ts
|
|
13699
13697
|
const isReactHookName = (name) => {
|
|
@@ -14414,7 +14412,7 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
|
|
|
14414
14412
|
const callNode = findDirectCallForReference(reference.identifier);
|
|
14415
14413
|
return callNode ? [callNode] : [];
|
|
14416
14414
|
});
|
|
14417
|
-
if (invocationCalls.length !== 1
|
|
14415
|
+
if (invocationCalls.length !== 1) return null;
|
|
14418
14416
|
const invocationCall = invocationCalls[0];
|
|
14419
14417
|
return findEnclosingFunction$1(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
|
|
14420
14418
|
};
|
|
@@ -14701,108 +14699,7 @@ const hasPotentialInterruptionAfterGuard = (callback, guardState, usageNode, con
|
|
|
14701
14699
|
});
|
|
14702
14700
|
return hasPotentialInterruption;
|
|
14703
14701
|
};
|
|
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
14702
|
const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
|
|
14805
|
-
if (hasGuardedRefOwnedNestedCleanup(callback, usage, cleanupReturns, context)) return true;
|
|
14806
14703
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
14807
14704
|
const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
|
|
14808
14705
|
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 +14911,6 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15014
14911
|
if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
|
|
15015
14912
|
if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
|
|
15016
14913
|
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
14914
|
const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
|
|
15019
14915
|
if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
|
|
15020
14916
|
const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
|
|
@@ -15051,8 +14947,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
15051
14947
|
const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
|
|
15052
14948
|
if (!releaseHandler) return releaseVerbName === "off";
|
|
15053
14949
|
const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
|
|
15054
|
-
|
|
15055
|
-
return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
|
|
14950
|
+
return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey;
|
|
15056
14951
|
}
|
|
15057
14952
|
if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
|
|
15058
14953
|
return true;
|
|
@@ -15077,188 +14972,13 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
|
|
|
15077
14972
|
if (!symbol) return false;
|
|
15078
14973
|
return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
|
|
15079
14974
|
};
|
|
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
14975
|
const isReleaseReachableForUsage = (releaseNode, usage, context) => {
|
|
15254
14976
|
if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
|
|
15255
14977
|
const releaseFunction = findEnclosingFunction$1(releaseNode);
|
|
15256
14978
|
if (!releaseFunction) return true;
|
|
15257
14979
|
if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
|
|
15258
|
-
if (isRetainedDisposerRefRelease(releaseNode, usage, context)) return true;
|
|
15259
14980
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
15260
14981
|
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
14982
|
return isPotentiallyReachableFunction(releaseFunction, context);
|
|
15263
14983
|
};
|
|
15264
14984
|
const fileContainsReleaseForUsage = (usage, context) => {
|
|
@@ -17392,7 +17112,7 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allow
|
|
|
17392
17112
|
keys.add(depKey);
|
|
17393
17113
|
continue;
|
|
17394
17114
|
}
|
|
17395
|
-
const identitySourceKeys =
|
|
17115
|
+
const identitySourceKeys = resolveReactiveIdentitySourceKeys(symbol, scopes);
|
|
17396
17116
|
if (identitySourceKeys) {
|
|
17397
17117
|
if (identitySourceKeys.size === 0) stableCapturedNames.add(depKey);
|
|
17398
17118
|
for (const identitySourceKey of identitySourceKeys) keys.add(identitySourceKey);
|
|
@@ -17475,161 +17195,6 @@ const resolveReactiveIdentitySourceKeys = (symbol, scopes) => {
|
|
|
17475
17195
|
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
17196
|
return resolveIdentitySourceKeysFromExpression(symbol.initializer, scopes, new Set([symbol.id]));
|
|
17477
17197
|
};
|
|
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
17198
|
const isUseCallbackResultDep = (node, scopes) => {
|
|
17634
17199
|
const rootSymbol = getRootSymbol(node, scopes);
|
|
17635
17200
|
const initializer = rootSymbol?.initializer ? unwrapExpression$3(rootSymbol.initializer) : null;
|
|
@@ -42457,8 +42022,7 @@ const noJsxElementType = defineRule({
|
|
|
42457
42022
|
create: (context) => {
|
|
42458
42023
|
let isJsxImported = false;
|
|
42459
42024
|
const flaggedAnnotations = [];
|
|
42460
|
-
const
|
|
42461
|
-
if (!(isNodeOfType(functionNode, "TSDeclareFunction") ? Boolean(functionNode.id && isReactComponentName(functionNode.id.name)) : isComponentFunction$1(functionNode))) return;
|
|
42025
|
+
const checkReturnType = (returnType) => {
|
|
42462
42026
|
const typeAnnotation = extractReturnTypeAnnotation(returnType);
|
|
42463
42027
|
if (!typeAnnotation) return;
|
|
42464
42028
|
if (isJsxElementTypeReference(typeAnnotation)) flaggedAnnotations.push(typeAnnotation);
|
|
@@ -42468,16 +42032,19 @@ const noJsxElementType = defineRule({
|
|
|
42468
42032
|
if (isJsxImportBinding(node)) isJsxImported = true;
|
|
42469
42033
|
},
|
|
42470
42034
|
FunctionDeclaration(node) {
|
|
42471
|
-
|
|
42035
|
+
checkReturnType(node.returnType);
|
|
42472
42036
|
},
|
|
42473
42037
|
ArrowFunctionExpression(node) {
|
|
42474
|
-
|
|
42038
|
+
checkReturnType(node.returnType);
|
|
42475
42039
|
},
|
|
42476
42040
|
FunctionExpression(node) {
|
|
42477
|
-
|
|
42041
|
+
checkReturnType(node.returnType);
|
|
42478
42042
|
},
|
|
42479
42043
|
TSDeclareFunction(node) {
|
|
42480
|
-
|
|
42044
|
+
checkReturnType(node.returnType);
|
|
42045
|
+
},
|
|
42046
|
+
TSMethodSignature(node) {
|
|
42047
|
+
checkReturnType(node.returnType);
|
|
42481
42048
|
},
|
|
42482
42049
|
"Program:exit"() {
|
|
42483
42050
|
if (isJsxImported) return;
|
|
@@ -47701,69 +47268,6 @@ const noRedundantShouldComponentUpdate = defineRule({
|
|
|
47701
47268
|
}
|
|
47702
47269
|
});
|
|
47703
47270
|
//#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
47271
|
//#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
|
|
47768
47272
|
const REPEATED_ANCESTOR_TYPES = new Set([
|
|
47769
47273
|
"DoWhileStatement",
|
|
@@ -72531,17 +72035,6 @@ const reactDoctorRules = [
|
|
|
72531
72035
|
requires: [...new Set(["react", ...noRedundantShouldComponentUpdate.requires ?? []])]
|
|
72532
72036
|
}
|
|
72533
72037
|
},
|
|
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
72038
|
{
|
|
72546
72039
|
key: "react-doctor/no-ref-current-in-render",
|
|
72547
72040
|
id: "no-ref-current-in-render",
|