oxlint-plugin-react-doctor 0.7.7-dev.1043416 → 0.7.7-dev.287dff7
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 +44 -180
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11673,25 +11673,6 @@ const getFunctionBindingName$1 = (functionNode) => getFunctionBindingIdentifier(
|
|
|
11673
11673
|
//#region src/plugin/utils/is-event-handler-attribute.ts
|
|
11674
11674
|
const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && /^on[A-Z]/.test(node.name.name);
|
|
11675
11675
|
//#endregion
|
|
11676
|
-
//#region src/plugin/utils/is-ast-descendant.ts
|
|
11677
|
-
/**
|
|
11678
|
-
* True when `inner` is `outer` itself or any descendant in the AST
|
|
11679
|
-
* `parent` chain. Walks `inner.parent` upward and stops at either a
|
|
11680
|
-
* match (`true`) or the chain's root (`false`).
|
|
11681
|
-
*
|
|
11682
|
-
* Was duplicated byte-identical in:
|
|
11683
|
-
* - exhaustive-deps-symbol-stability.ts
|
|
11684
|
-
* - semantic/closure-captures.ts
|
|
11685
|
-
*/
|
|
11686
|
-
const isAstDescendant = (inner, outer) => {
|
|
11687
|
-
let current = inner;
|
|
11688
|
-
while (current) {
|
|
11689
|
-
if (current === outer) return true;
|
|
11690
|
-
current = current.parent ?? null;
|
|
11691
|
-
}
|
|
11692
|
-
return false;
|
|
11693
|
-
};
|
|
11694
|
-
//#endregion
|
|
11695
11676
|
//#region src/plugin/utils/react-ref-origin.ts
|
|
11696
11677
|
const resolveReactRefSymbol = (memberExpression, scopes) => {
|
|
11697
11678
|
const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
|
|
@@ -12341,39 +12322,26 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
|
|
|
12341
12322
|
});
|
|
12342
12323
|
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
|
|
12343
12324
|
};
|
|
12344
|
-
const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => {
|
|
12345
|
-
if (usage.handleKey === null) return null;
|
|
12346
|
-
const doesTestRequireLiveHandle = (test) => {
|
|
12347
|
-
if (resolveExpressionKey(test, context) === usage.handleKey) return true;
|
|
12348
|
-
const unwrappedTest = stripParenExpression(test);
|
|
12349
|
-
if (!isNodeOfType(unwrappedTest, "BinaryExpression") || unwrappedTest.operator !== "!=" && unwrappedTest.operator !== "!==") return false;
|
|
12350
|
-
const isNullishOperand = (operand) => {
|
|
12351
|
-
const unwrappedOperand = stripParenExpression(operand);
|
|
12352
|
-
return isNodeOfType(unwrappedOperand, "Literal") && unwrappedOperand.value === null || isNodeOfType(unwrappedOperand, "Identifier") && unwrappedOperand.name === "undefined" && context.scopes.isGlobalReference(unwrappedOperand);
|
|
12353
|
-
};
|
|
12354
|
-
return resolveExpressionKey(unwrappedTest.left, context) === usage.handleKey && isNullishOperand(unwrappedTest.right) || resolveExpressionKey(unwrappedTest.right, context) === usage.handleKey && isNullishOperand(unwrappedTest.left);
|
|
12355
|
-
};
|
|
12356
|
-
let ancestor = releaseCall.parent;
|
|
12357
|
-
while (ancestor && ancestor !== owner) {
|
|
12358
|
-
if (isNodeOfType(ancestor, "IfStatement")) {
|
|
12359
|
-
if (ancestor.alternate !== null || !doesTestRequireLiveHandle(ancestor.test) || !doMatchingNodesCoverEveryPathAfterUsage(ancestor.consequent, [releaseCall], context)) return null;
|
|
12360
|
-
return ancestor;
|
|
12361
|
-
}
|
|
12362
|
-
ancestor = ancestor.parent;
|
|
12363
|
-
}
|
|
12364
|
-
return null;
|
|
12365
|
-
};
|
|
12366
12325
|
const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
|
|
12367
12326
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
|
|
12368
12327
|
const functionCfg = context.cfg.cfgFor(callback);
|
|
12369
12328
|
const usageBlock = functionCfg?.blockOf(usage.node);
|
|
12370
12329
|
const usageStart = getRangeStart(usage.node);
|
|
12371
12330
|
if (!functionCfg || !usageBlock || usageStart === null) return false;
|
|
12331
|
+
const findHandleGuard = (releaseCall) => {
|
|
12332
|
+
if (usage.handleKey === null) return null;
|
|
12333
|
+
let ancestor = releaseCall.parent;
|
|
12334
|
+
while (ancestor && ancestor !== callback.body) {
|
|
12335
|
+
if (isNodeOfType(ancestor, "IfStatement")) return ancestor.alternate === null && resolveExpressionKey(ancestor.test, context) === usage.handleKey && getRangeStart(ancestor) !== null && (getRangeStart(ancestor) ?? usageStart) < usageStart ? ancestor : null;
|
|
12336
|
+
ancestor = ancestor.parent;
|
|
12337
|
+
}
|
|
12338
|
+
return null;
|
|
12339
|
+
};
|
|
12372
12340
|
const matchingReleaseAnchors = [];
|
|
12373
12341
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
12374
12342
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
12375
12343
|
const releaseStart = getRangeStart(child);
|
|
12376
|
-
const handleGuard =
|
|
12344
|
+
const handleGuard = findHandleGuard(child);
|
|
12377
12345
|
if (releaseStart === null || releaseStart >= usageStart || functionCfg.blockOf(child) !== usageBlock && !handleGuard) return;
|
|
12378
12346
|
if (doesReleaseCallMatchUsage(child, usage, context)) {
|
|
12379
12347
|
matchingReleaseAnchors.push(handleGuard ?? child);
|
|
@@ -12403,12 +12371,12 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
|
12403
12371
|
return didFindUnmountCleanup;
|
|
12404
12372
|
};
|
|
12405
12373
|
const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
|
|
12406
|
-
const collectBlockingBooleanStates = (expression, blockedExpressionValue,
|
|
12374
|
+
const collectBlockingBooleanStates = (expression, blockedExpressionValue, context) => {
|
|
12407
12375
|
const unwrappedExpression = stripParenExpression(expression);
|
|
12408
|
-
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue,
|
|
12376
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, context);
|
|
12409
12377
|
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
|
|
12410
12378
|
if (!(unwrappedExpression.operator === "||" && blockedExpressionValue || unwrappedExpression.operator === "&&" && !blockedExpressionValue)) return [];
|
|
12411
|
-
return [...collectBlockingBooleanStates(unwrappedExpression.left, blockedExpressionValue,
|
|
12379
|
+
return [...collectBlockingBooleanStates(unwrappedExpression.left, blockedExpressionValue, context), ...collectBlockingBooleanStates(unwrappedExpression.right, blockedExpressionValue, context)];
|
|
12412
12380
|
}
|
|
12413
12381
|
if (isNodeOfType(unwrappedExpression, "BinaryExpression") && [
|
|
12414
12382
|
"===",
|
|
@@ -12419,59 +12387,35 @@ const collectBlockingBooleanStates = (expression, blockedExpressionValue, guardN
|
|
|
12419
12387
|
const leftValue = readStaticBoolean(unwrappedExpression.left);
|
|
12420
12388
|
const rightValue = readStaticBoolean(unwrappedExpression.right);
|
|
12421
12389
|
const booleanValue = leftValue ?? rightValue;
|
|
12422
|
-
const
|
|
12423
|
-
const comparedKey = resolveExpressionKey(comparedExpression, context);
|
|
12390
|
+
const comparedKey = resolveExpressionKey(leftValue === null ? unwrappedExpression.left : unwrappedExpression.right, context);
|
|
12424
12391
|
if (booleanValue === null || comparedKey === null) return [];
|
|
12425
|
-
const isEquality = unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==";
|
|
12426
12392
|
return [{
|
|
12427
|
-
bindingIdentifier: isNodeOfType(comparedExpression, "Identifier") ? context.scopes.symbolFor(comparedExpression)?.bindingIdentifier ?? null : null,
|
|
12428
|
-
guardNode,
|
|
12429
12393
|
key: comparedKey,
|
|
12430
|
-
value:
|
|
12394
|
+
value: (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==") === blockedExpressionValue ? booleanValue : !booleanValue
|
|
12431
12395
|
}];
|
|
12432
12396
|
}
|
|
12433
12397
|
const expressionKey = resolveExpressionKey(unwrappedExpression, context);
|
|
12434
12398
|
return expressionKey === null ? [] : [{
|
|
12435
|
-
bindingIdentifier: isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression)?.bindingIdentifier ?? null : null,
|
|
12436
|
-
guardNode,
|
|
12437
12399
|
key: expressionKey,
|
|
12438
12400
|
value: blockedExpressionValue
|
|
12439
12401
|
}];
|
|
12440
12402
|
};
|
|
12441
|
-
const
|
|
12442
|
-
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
const sourceStart = getRangeStart(sourceNode);
|
|
12446
|
-
const targetStart = getRangeStart(targetNode);
|
|
12447
|
-
if (!functionCfg || !sourceBlock || !targetBlock || sourceStart === null || targetStart === null) return true;
|
|
12448
|
-
if (!isNodeReachableWithinFunction(sourceNode, context)) return false;
|
|
12449
|
-
if (sourceBlock === targetBlock) return sourceStart < targetStart;
|
|
12450
|
-
const visitedBlocks = new Set([sourceBlock]);
|
|
12451
|
-
const pendingBlocks = [sourceBlock];
|
|
12452
|
-
while (pendingBlocks.length > 0) {
|
|
12453
|
-
const currentBlock = pendingBlocks.pop();
|
|
12454
|
-
if (!currentBlock) break;
|
|
12455
|
-
for (const edge of currentBlock.successors) {
|
|
12456
|
-
if (edge.to === targetBlock) return true;
|
|
12457
|
-
if (visitedBlocks.has(edge.to)) continue;
|
|
12458
|
-
visitedBlocks.add(edge.to);
|
|
12459
|
-
pendingBlocks.push(edge.to);
|
|
12460
|
-
}
|
|
12461
|
-
}
|
|
12462
|
-
return false;
|
|
12403
|
+
const isDirectEarlyReturnConsequent = (ifStatement) => {
|
|
12404
|
+
if (!isNodeOfType(ifStatement, "IfStatement") || ifStatement.alternate) return false;
|
|
12405
|
+
if (isNodeOfType(ifStatement.consequent, "ReturnStatement")) return true;
|
|
12406
|
+
return isNodeOfType(ifStatement.consequent, "BlockStatement") && ifStatement.consequent.body.length === 1 && isNodeOfType(ifStatement.consequent.body[0], "ReturnStatement");
|
|
12463
12407
|
};
|
|
12464
12408
|
const collectDeferredUsageGuardStates = (callback, usageNode, context) => {
|
|
12465
12409
|
if (!isFunctionLike$1(callback) || callback.async) return [];
|
|
12466
12410
|
const guardStates = [];
|
|
12467
12411
|
walkAst(callback.body, (child) => {
|
|
12468
12412
|
if (child !== callback.body && isFunctionLike$1(child)) return false;
|
|
12469
|
-
if (isNodeOfType(child, "IfStatement") &&
|
|
12413
|
+
if (isNodeOfType(child, "IfStatement") && isDirectEarlyReturnConsequent(child) && doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], callback, context)) guardStates.push(...collectBlockingBooleanStates(child.test, true, context));
|
|
12470
12414
|
});
|
|
12471
12415
|
let descendant = usageNode;
|
|
12472
12416
|
let ancestor = descendant.parent;
|
|
12473
12417
|
while (ancestor && ancestor !== callback) {
|
|
12474
|
-
if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant) guardStates.push(...collectBlockingBooleanStates(ancestor.test, false,
|
|
12418
|
+
if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant) guardStates.push(...collectBlockingBooleanStates(ancestor.test, false, context));
|
|
12475
12419
|
descendant = ancestor;
|
|
12476
12420
|
ancestor = ancestor.parent;
|
|
12477
12421
|
}
|
|
@@ -12480,7 +12424,7 @@ const collectDeferredUsageGuardStates = (callback, usageNode, context) => {
|
|
|
12480
12424
|
const cleanupReturnInvalidatesGuard = (cleanupReturn, guardState, context) => {
|
|
12481
12425
|
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
|
|
12482
12426
|
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
12483
|
-
if (!cleanupFunction || !isFunctionLike$1(cleanupFunction) || cleanupFunction.async
|
|
12427
|
+
if (!cleanupFunction || !isFunctionLike$1(cleanupFunction) || cleanupFunction.async) return false;
|
|
12484
12428
|
let didInvalidateGuard = false;
|
|
12485
12429
|
walkAst(cleanupFunction.body, (child) => {
|
|
12486
12430
|
if (didInvalidateGuard) return false;
|
|
@@ -12509,110 +12453,11 @@ const deferredUsageWritesGuardBeforeUsage = (callback, usageNode, guardState, co
|
|
|
12509
12453
|
});
|
|
12510
12454
|
return didWriteGuard;
|
|
12511
12455
|
};
|
|
12512
|
-
const canInterruptionReachUsageThroughCatch = (interruptionNode, usageNode, owner, context) => {
|
|
12513
|
-
let descendant = interruptionNode;
|
|
12514
|
-
let ancestor = descendant.parent;
|
|
12515
|
-
while (ancestor && ancestor !== owner) {
|
|
12516
|
-
if (isNodeOfType(ancestor, "TryStatement") && ancestor.block === descendant && ancestor.handler && canNodeReachLaterNodeWithinFunction(ancestor.handler.body, usageNode, owner, context)) return true;
|
|
12517
|
-
descendant = ancestor;
|
|
12518
|
-
ancestor = ancestor.parent;
|
|
12519
|
-
}
|
|
12520
|
-
return false;
|
|
12521
|
-
};
|
|
12522
|
-
const isEffectLocalLifecycleGuard = (callback, guardState, cleanupFunctions, context) => {
|
|
12523
|
-
if (!guardState.bindingIdentifier) return false;
|
|
12524
|
-
const guardSymbol = context.scopes.symbolFor(guardState.bindingIdentifier);
|
|
12525
|
-
if (!guardSymbol || guardSymbol.kind !== "let" && guardSymbol.kind !== "var" || !isNodeOfType(guardSymbol.declarationNode, "VariableDeclarator") || findEnclosingFunction$1(guardSymbol.declarationNode) !== callback) return false;
|
|
12526
|
-
return guardSymbol.references.every((reference) => {
|
|
12527
|
-
if (!isWithinAssignmentTarget(reference.identifier)) return true;
|
|
12528
|
-
const assignmentTarget = findTransparentExpressionRoot(reference.identifier);
|
|
12529
|
-
const assignment = assignmentTarget.parent;
|
|
12530
|
-
return isNodeOfType(assignment, "AssignmentExpression") && assignment.operator === "=" && assignment.left === assignmentTarget && readStaticBoolean(assignment.right) === guardState.value && cleanupFunctions.includes(findEnclosingFunction$1(assignment) ?? assignment);
|
|
12531
|
-
});
|
|
12532
|
-
};
|
|
12533
|
-
const hasPotentialInterruptionAfterGuard = (callback, guardState, usageNode, context) => {
|
|
12534
|
-
if (!isFunctionLike$1(callback)) return true;
|
|
12535
|
-
const guardStart = getRangeStart(guardState.guardNode);
|
|
12536
|
-
const usageStart = getRangeStart(usageNode);
|
|
12537
|
-
if (guardStart === null || usageStart === null) return true;
|
|
12538
|
-
let hasPotentialInterruption = false;
|
|
12539
|
-
walkAst(callback.body, (child) => {
|
|
12540
|
-
if (hasPotentialInterruption) return false;
|
|
12541
|
-
if (child !== callback.body && isFunctionLike$1(child)) return false;
|
|
12542
|
-
const childStart = getRangeStart(child);
|
|
12543
|
-
if (childStart === null || childStart <= guardStart || childStart >= usageStart) return;
|
|
12544
|
-
if (isNodeOfType(child, "CallExpression") || isNodeOfType(child, "AwaitExpression") || isNodeOfType(child, "YieldExpression")) {
|
|
12545
|
-
if (canNodeReachLaterNodeWithinFunction(child, usageNode, callback, context) || canInterruptionReachUsageThroughCatch(child, usageNode, callback, context)) {
|
|
12546
|
-
hasPotentialInterruption = true;
|
|
12547
|
-
return false;
|
|
12548
|
-
}
|
|
12549
|
-
}
|
|
12550
|
-
});
|
|
12551
|
-
return hasPotentialInterruption;
|
|
12552
|
-
};
|
|
12553
12456
|
const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
|
|
12554
12457
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
12555
12458
|
const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
|
|
12556
|
-
if (usage.
|
|
12557
|
-
|
|
12558
|
-
const usageAssignment = usageExpression.parent;
|
|
12559
|
-
if (!isNodeOfType(usageAssignment, "AssignmentExpression") || usageAssignment.operator !== "=" || usageAssignment.right !== usageExpression || !isNodeOfType(usageAssignment.left, "Identifier")) return false;
|
|
12560
|
-
const handleSymbol = context.scopes.symbolFor(usageAssignment.left);
|
|
12561
|
-
if (!handleSymbol || handleSymbol.kind !== "let" && handleSymbol.kind !== "var" || !isNodeOfType(handleSymbol.declarationNode, "VariableDeclarator") || findEnclosingFunction$1(handleSymbol.declarationNode) !== callback) return false;
|
|
12562
|
-
const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
|
|
12563
|
-
if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
|
|
12564
|
-
const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
|
|
12565
|
-
return cleanupFunction && isFunctionLike$1(cleanupFunction) ? [cleanupFunction] : [];
|
|
12566
|
-
});
|
|
12567
|
-
if (cleanupFunctions.length !== cleanupReturns.length) return false;
|
|
12568
|
-
const globalReleaseProofsByCleanup = /* @__PURE__ */ new Map();
|
|
12569
|
-
for (const cleanupFunction of cleanupFunctions) {
|
|
12570
|
-
if (!isFunctionLike$1(cleanupFunction)) return false;
|
|
12571
|
-
const globalReleaseProofs = [];
|
|
12572
|
-
walkAst(cleanupFunction.body, (child) => {
|
|
12573
|
-
if (child !== cleanupFunction.body && isFunctionLike$1(child)) return false;
|
|
12574
|
-
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && context.scopes.isGlobalReference(child.callee) && doesReleaseCallMatchUsage(child, usage, context)) {
|
|
12575
|
-
const handleGuard = findDirectHandleGuardForRelease(child, cleanupFunction, usage, context);
|
|
12576
|
-
globalReleaseProofs.push({
|
|
12577
|
-
anchor: handleGuard ?? child,
|
|
12578
|
-
call: child,
|
|
12579
|
-
handleGuard
|
|
12580
|
-
});
|
|
12581
|
-
}
|
|
12582
|
-
});
|
|
12583
|
-
if (!doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, globalReleaseProofs.map((releaseProof) => releaseProof.anchor), context)) return false;
|
|
12584
|
-
globalReleaseProofsByCleanup.set(cleanupFunction, globalReleaseProofs);
|
|
12585
|
-
}
|
|
12586
|
-
const handleAssignments = handleSymbol.references.filter((reference) => isWithinAssignmentTarget(reference.identifier));
|
|
12587
|
-
const hasUsageAssignment = handleAssignments.some((handleAssignment) => findTransparentExpressionRoot(handleAssignment.identifier).parent === usageAssignment);
|
|
12588
|
-
const hasUnsafeHandleAssignment = handleAssignments.some((handleAssignment) => {
|
|
12589
|
-
const assignmentTarget = findTransparentExpressionRoot(handleAssignment.identifier);
|
|
12590
|
-
const assignment = assignmentTarget.parent;
|
|
12591
|
-
if (assignment === usageAssignment) return false;
|
|
12592
|
-
if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== assignmentTarget) return true;
|
|
12593
|
-
const assignedValue = stripParenExpression(assignment.right);
|
|
12594
|
-
if (!(isNodeOfType(assignedValue, "Literal") && assignedValue.value === null || isNodeOfType(assignedValue, "Identifier") && assignedValue.name === "undefined" && context.scopes.isGlobalReference(assignedValue))) return true;
|
|
12595
|
-
const cleanupFunction = findEnclosingFunction$1(assignment);
|
|
12596
|
-
const globalReleaseProofs = cleanupFunction ? globalReleaseProofsByCleanup.get(cleanupFunction) : void 0;
|
|
12597
|
-
return !(cleanupFunction && globalReleaseProofs && doMatchingNodesCoverEveryPathBeforeUsage(assignment, globalReleaseProofs.map((releaseProof) => releaseProof.handleGuard && isAstDescendant(assignment, releaseProof.handleGuard.consequent) ? releaseProof.call : releaseProof.anchor), cleanupFunction, context));
|
|
12598
|
-
});
|
|
12599
|
-
if (!hasUsageAssignment || hasUnsafeHandleAssignment) return false;
|
|
12600
|
-
let usageAncestor = usage.node.parent;
|
|
12601
|
-
while (usageAncestor && usageAncestor !== usageFunction) {
|
|
12602
|
-
if (isNodeOfType(usageAncestor, "ForStatement") || isNodeOfType(usageAncestor, "ForInStatement") || isNodeOfType(usageAncestor, "ForOfStatement") || isNodeOfType(usageAncestor, "WhileStatement") || isNodeOfType(usageAncestor, "DoWhileStatement")) return false;
|
|
12603
|
-
usageAncestor = usageAncestor.parent;
|
|
12604
|
-
}
|
|
12605
|
-
let hasPotentialInterruption = false;
|
|
12606
|
-
for (const argument of usage.node.arguments ?? []) walkAst(argument, (argumentChild) => {
|
|
12607
|
-
if (hasPotentialInterruption) return false;
|
|
12608
|
-
if (isFunctionLike$1(argumentChild)) return false;
|
|
12609
|
-
if (isNodeOfType(argumentChild, "CallExpression") || isNodeOfType(argumentChild, "AwaitExpression") || isNodeOfType(argumentChild, "YieldExpression")) {
|
|
12610
|
-
hasPotentialInterruption = true;
|
|
12611
|
-
return false;
|
|
12612
|
-
}
|
|
12613
|
-
});
|
|
12614
|
-
if (hasPotentialInterruption) return false;
|
|
12615
|
-
return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => isEffectLocalLifecycleGuard(callback, guardState, cleanupFunctions, context) && !hasPotentialInterruptionAfterGuard(usageFunction, guardState, usage.node, context) && !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
|
|
12459
|
+
if (usage.handleKey === null || !usageFunction || usageFunction === callback || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
|
|
12460
|
+
return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
|
|
12616
12461
|
};
|
|
12617
12462
|
const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
12618
12463
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
@@ -13178,7 +13023,7 @@ const effectNeedsCleanup = defineRule({
|
|
|
13178
13023
|
const hookName = getCalleeName$2(node) ?? "effect";
|
|
13179
13024
|
context.report({
|
|
13180
13025
|
node,
|
|
13181
|
-
message: `\`${firstUsage.resourceName}\` creates a ${resourceNoun} in ${hookName} without
|
|
13026
|
+
message: `\`${firstUsage.resourceName}\` creates a ${resourceNoun} in ${hookName} without returning cleanup. Return a cleanup function so it does not leak after unmount.`
|
|
13182
13027
|
});
|
|
13183
13028
|
},
|
|
13184
13029
|
FunctionDeclaration(node) {
|
|
@@ -14628,6 +14473,25 @@ const resolveExhaustiveDepsSettings = (settings) => {
|
|
|
14628
14473
|
};
|
|
14629
14474
|
};
|
|
14630
14475
|
//#endregion
|
|
14476
|
+
//#region src/plugin/utils/is-ast-descendant.ts
|
|
14477
|
+
/**
|
|
14478
|
+
* True when `inner` is `outer` itself or any descendant in the AST
|
|
14479
|
+
* `parent` chain. Walks `inner.parent` upward and stops at either a
|
|
14480
|
+
* match (`true`) or the chain's root (`false`).
|
|
14481
|
+
*
|
|
14482
|
+
* Was duplicated byte-identical in:
|
|
14483
|
+
* - exhaustive-deps-symbol-stability.ts
|
|
14484
|
+
* - semantic/closure-captures.ts
|
|
14485
|
+
*/
|
|
14486
|
+
const isAstDescendant = (inner, outer) => {
|
|
14487
|
+
let current = inner;
|
|
14488
|
+
while (current) {
|
|
14489
|
+
if (current === outer) return true;
|
|
14490
|
+
current = current.parent ?? null;
|
|
14491
|
+
}
|
|
14492
|
+
return false;
|
|
14493
|
+
};
|
|
14494
|
+
//#endregion
|
|
14631
14495
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-sole-writer-guard.ts
|
|
14632
14496
|
const EQUALITY_BINARY_OPERATORS = new Set(["===", "!=="]);
|
|
14633
14497
|
const getUseStateSetterSymbol = (stateSymbol, scopes) => {
|