oxlint-plugin-react-doctor 0.7.7-dev.afed801 → 0.7.7-dev.c79c897
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.js +104 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11003,6 +11003,14 @@ const PROMISE_CHAIN_METHOD_NAMES$1 = new Set([
|
|
|
11003
11003
|
"catch",
|
|
11004
11004
|
"finally"
|
|
11005
11005
|
]);
|
|
11006
|
+
const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
|
|
11007
|
+
const getPromiseChainCallForCallback = (candidate) => {
|
|
11008
|
+
let callbackContainer = candidate.parent;
|
|
11009
|
+
while (callbackContainer && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(callbackContainer.type)) callbackContainer = callbackContainer.parent;
|
|
11010
|
+
if (!isNodeOfType(callbackContainer, "CallExpression")) return null;
|
|
11011
|
+
if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
|
|
11012
|
+
return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
|
|
11013
|
+
};
|
|
11006
11014
|
const collectEffectInvokedFunctions = (effectCallback) => {
|
|
11007
11015
|
const invokedFunctions = new Set([effectCallback]);
|
|
11008
11016
|
const localFunctionBindings = /* @__PURE__ */ new Map();
|
|
@@ -11014,7 +11022,6 @@ const collectEffectInvokedFunctions = (effectCallback) => {
|
|
|
11014
11022
|
invokedFunctions.add(strippedCandidate);
|
|
11015
11023
|
pendingFunctions.push(strippedCandidate);
|
|
11016
11024
|
};
|
|
11017
|
-
const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
|
|
11018
11025
|
while (pendingFunctions.length > 0) {
|
|
11019
11026
|
const currentFunction = pendingFunctions.pop();
|
|
11020
11027
|
if (!currentFunction) break;
|
|
@@ -11844,6 +11851,94 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
|
11844
11851
|
return didFindUnmountCleanup;
|
|
11845
11852
|
};
|
|
11846
11853
|
const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
|
|
11854
|
+
const collectBlockingBooleanStates = (expression, blockedExpressionValue, context) => {
|
|
11855
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
11856
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, context);
|
|
11857
|
+
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
11858
|
+
if (!(unwrappedExpression.operator === "||" && blockedExpressionValue || unwrappedExpression.operator === "&&" && !blockedExpressionValue)) return [];
|
|
11859
|
+
return [...collectBlockingBooleanStates(unwrappedExpression.left, blockedExpressionValue, context), ...collectBlockingBooleanStates(unwrappedExpression.right, blockedExpressionValue, context)];
|
|
11860
|
+
}
|
|
11861
|
+
if (isNodeOfType(unwrappedExpression, "BinaryExpression") && [
|
|
11862
|
+
"===",
|
|
11863
|
+
"==",
|
|
11864
|
+
"!==",
|
|
11865
|
+
"!="
|
|
11866
|
+
].includes(unwrappedExpression.operator)) {
|
|
11867
|
+
const leftValue = readStaticBoolean(unwrappedExpression.left);
|
|
11868
|
+
const rightValue = readStaticBoolean(unwrappedExpression.right);
|
|
11869
|
+
const booleanValue = leftValue ?? rightValue;
|
|
11870
|
+
const comparedKey = resolveExpressionKey(leftValue === null ? unwrappedExpression.left : unwrappedExpression.right, context);
|
|
11871
|
+
if (booleanValue === null || comparedKey === null) return [];
|
|
11872
|
+
return [{
|
|
11873
|
+
key: comparedKey,
|
|
11874
|
+
value: (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==") === blockedExpressionValue ? booleanValue : !booleanValue
|
|
11875
|
+
}];
|
|
11876
|
+
}
|
|
11877
|
+
const expressionKey = resolveExpressionKey(unwrappedExpression, context);
|
|
11878
|
+
return expressionKey === null ? [] : [{
|
|
11879
|
+
key: expressionKey,
|
|
11880
|
+
value: blockedExpressionValue
|
|
11881
|
+
}];
|
|
11882
|
+
};
|
|
11883
|
+
const isDirectEarlyReturnConsequent = (ifStatement) => {
|
|
11884
|
+
if (!isNodeOfType(ifStatement, "IfStatement") || ifStatement.alternate) return false;
|
|
11885
|
+
if (isNodeOfType(ifStatement.consequent, "ReturnStatement")) return true;
|
|
11886
|
+
return isNodeOfType(ifStatement.consequent, "BlockStatement") && ifStatement.consequent.body.length === 1 && isNodeOfType(ifStatement.consequent.body[0], "ReturnStatement");
|
|
11887
|
+
};
|
|
11888
|
+
const collectDeferredUsageGuardStates = (callback, usageNode, context) => {
|
|
11889
|
+
if (!isFunctionLike$2(callback) || callback.async) return [];
|
|
11890
|
+
const guardStates = [];
|
|
11891
|
+
walkAst(callback.body, (child) => {
|
|
11892
|
+
if (child !== callback.body && isFunctionLike$2(child)) return false;
|
|
11893
|
+
if (isNodeOfType(child, "IfStatement") && isDirectEarlyReturnConsequent(child) && doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], callback, context)) guardStates.push(...collectBlockingBooleanStates(child.test, true, context));
|
|
11894
|
+
});
|
|
11895
|
+
let descendant = usageNode;
|
|
11896
|
+
let ancestor = descendant.parent;
|
|
11897
|
+
while (ancestor && ancestor !== callback) {
|
|
11898
|
+
if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant) guardStates.push(...collectBlockingBooleanStates(ancestor.test, false, context));
|
|
11899
|
+
descendant = ancestor;
|
|
11900
|
+
ancestor = ancestor.parent;
|
|
11901
|
+
}
|
|
11902
|
+
return guardStates;
|
|
11903
|
+
};
|
|
11904
|
+
const cleanupReturnInvalidatesGuard = (cleanupReturn, guardState, context) => {
|
|
11905
|
+
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
|
|
11906
|
+
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
11907
|
+
if (!cleanupFunction || !isFunctionLike$2(cleanupFunction) || cleanupFunction.async) return false;
|
|
11908
|
+
let didInvalidateGuard = false;
|
|
11909
|
+
walkAst(cleanupFunction.body, (child) => {
|
|
11910
|
+
if (didInvalidateGuard) return false;
|
|
11911
|
+
if (child !== cleanupFunction.body && isFunctionLike$2(child)) return false;
|
|
11912
|
+
if (isNodeOfType(child, "AssignmentExpression") && child.operator === "=" && resolveExpressionKey(child.left, context) === guardState.key && readStaticBoolean(child.right) === guardState.value && context.cfg.isUnconditionalFromEntry(child)) {
|
|
11913
|
+
didInvalidateGuard = true;
|
|
11914
|
+
return false;
|
|
11915
|
+
}
|
|
11916
|
+
});
|
|
11917
|
+
return didInvalidateGuard;
|
|
11918
|
+
};
|
|
11919
|
+
const deferredUsageWritesGuardBeforeUsage = (callback, usageNode, guardState, context) => {
|
|
11920
|
+
const usageStart = getRangeStart(usageNode);
|
|
11921
|
+
if (!isFunctionLike$2(callback) || usageStart === null) return true;
|
|
11922
|
+
let didWriteGuard = false;
|
|
11923
|
+
walkAst(callback.body, (child) => {
|
|
11924
|
+
if (didWriteGuard) return false;
|
|
11925
|
+
if (child !== callback.body && isFunctionLike$2(child)) return false;
|
|
11926
|
+
const childStart = getRangeStart(child);
|
|
11927
|
+
if (childStart === null || childStart >= usageStart) return;
|
|
11928
|
+
const writtenExpression = isNodeOfType(child, "AssignmentExpression") ? child.left : isNodeOfType(child, "UpdateExpression") ? child.argument : null;
|
|
11929
|
+
if (writtenExpression && resolveExpressionKey(writtenExpression, context) === guardState.key) {
|
|
11930
|
+
didWriteGuard = true;
|
|
11931
|
+
return false;
|
|
11932
|
+
}
|
|
11933
|
+
});
|
|
11934
|
+
return didWriteGuard;
|
|
11935
|
+
};
|
|
11936
|
+
const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
|
|
11937
|
+
const usageFunction = findEnclosingFunction(usage.node);
|
|
11938
|
+
const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
|
|
11939
|
+
if (usage.handleKey === null || !usageFunction || usageFunction === callback || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
|
|
11940
|
+
return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
|
|
11941
|
+
};
|
|
11847
11942
|
const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
11848
11943
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
11849
11944
|
if (callback.async) return false;
|
|
@@ -11883,6 +11978,7 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
|
11883
11978
|
if (!cleanupFunction || !isFunctionLike$2(cleanupFunction)) return;
|
|
11884
11979
|
if (doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context)) matchingCleanupReturns.push(child);
|
|
11885
11980
|
});
|
|
11981
|
+
if (hasGuardedDeferredCleanup(callback, usage, matchingCleanupReturns, context)) return true;
|
|
11886
11982
|
return doMatchingNodesCoverEveryPathAfterUsage(resolveCleanupPathAnchor(usage.node, callback, context), matchingCleanupReturns, context);
|
|
11887
11983
|
};
|
|
11888
11984
|
const findFirstUsageWithoutCleanup = (callback, usages, context) => {
|
|
@@ -25449,8 +25545,11 @@ const unwrapUseCallback = (node) => {
|
|
|
25449
25545
|
const callee = node.callee;
|
|
25450
25546
|
return isNodeOfType(callee, "Identifier") && callee.name === "useCallback" || isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useCallback" ? node.arguments?.[0] : node;
|
|
25451
25547
|
};
|
|
25548
|
+
const hasParameterDefinition = (ref) => Boolean(ref.resolved?.defs.some((definition) => definition.type === "Parameter"));
|
|
25452
25549
|
const resolveToFunction = (ref) => {
|
|
25453
|
-
const
|
|
25550
|
+
const definition = ref.resolved?.defs[0];
|
|
25551
|
+
if (!definition || hasParameterDefinition(ref)) return null;
|
|
25552
|
+
const definitionNode = definition.node;
|
|
25454
25553
|
if (!definitionNode) return null;
|
|
25455
25554
|
if (isFunctionLike$2(definitionNode)) return definitionNode;
|
|
25456
25555
|
if (isNodeOfType(definitionNode, "VariableDeclarator")) {
|
|
@@ -25860,7 +25959,7 @@ const isCleanupReturnArgument = (analysis, node) => {
|
|
|
25860
25959
|
if (isNodeOfType(node, "MemberExpression")) return true;
|
|
25861
25960
|
if (isNodeOfType(node, "Identifier")) {
|
|
25862
25961
|
const ref = getRef(analysis, node);
|
|
25863
|
-
if (ref && resolveToFunction(ref)) return true;
|
|
25962
|
+
if (ref && (hasParameterDefinition(ref) || resolveToFunction(ref))) return true;
|
|
25864
25963
|
}
|
|
25865
25964
|
if (isNodeOfType(node, "ConditionalExpression")) return isCleanupReturnArgument(analysis, node.consequent) || isCleanupReturnArgument(analysis, node.alternate);
|
|
25866
25965
|
return false;
|
|
@@ -26332,7 +26431,7 @@ const resolveWrappedCallable = (analysis, node) => {
|
|
|
26332
26431
|
if (isNodeOfType(candidate, "Identifier")) {
|
|
26333
26432
|
const reference = getRef(analysis, candidate);
|
|
26334
26433
|
if (!reference) return null;
|
|
26335
|
-
if (reference.resolved?.defs.some((definition) => definition.type === "
|
|
26434
|
+
if (reference.resolved?.defs.some((definition) => definition.type === "ImportBinding")) return null;
|
|
26336
26435
|
const resolved = resolveToFunction(reference);
|
|
26337
26436
|
if (resolved) return resolved;
|
|
26338
26437
|
const definitionNode = reference.resolved?.defs[0]?.node;
|
|
@@ -38141,8 +38240,6 @@ const getWrapperHookWrappedFunction = (initializer) => {
|
|
|
38141
38240
|
if (!wrapped || !isFunctionLike$2(wrapped)) return null;
|
|
38142
38241
|
return wrapped;
|
|
38143
38242
|
};
|
|
38144
|
-
const hasParameterDef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "Parameter"));
|
|
38145
|
-
const resolvesToFunctionBinding = (ref) => !hasParameterDef(ref) && Boolean(resolveToFunction(ref));
|
|
38146
38243
|
const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
|
|
38147
38244
|
const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
|
|
38148
38245
|
if (!isProp(analysis, innerRef)) return false;
|
|
@@ -38463,7 +38560,7 @@ const noPassDataToParent = defineRule({
|
|
|
38463
38560
|
if (isConstant(argRef)) return false;
|
|
38464
38561
|
if (isParentWiredHookResultRef(analysis, argRef)) return false;
|
|
38465
38562
|
if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
|
|
38466
|
-
if (
|
|
38563
|
+
if (resolveToFunction(argRef)) return false;
|
|
38467
38564
|
const argIdentifier = argRef.identifier;
|
|
38468
38565
|
if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
|
|
38469
38566
|
if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
|