oxlint-plugin-react-doctor 0.9.2-dev.c6bdd2d → 0.9.2-dev.d6f02bb
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 +33 -466
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18012,14 +18012,6 @@ const findRenderPhaseComponentOrHook = (node, scopes) => {
|
|
|
18012
18012
|
//#region src/plugin/utils/is-event-handler-attribute.ts
|
|
18013
18013
|
const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && /^on[A-Z]/.test(node.name.name);
|
|
18014
18014
|
//#endregion
|
|
18015
|
-
//#region src/plugin/utils/is-early-exit-statement.ts
|
|
18016
|
-
const isEarlyExitStatement$1 = (statement) => {
|
|
18017
|
-
if (!statement) return false;
|
|
18018
|
-
if (statementAlwaysExits$1(statement)) return true;
|
|
18019
|
-
if (isNodeOfType(statement, "BlockStatement")) return isEarlyExitStatement$1(statement.body.at(-1));
|
|
18020
|
-
return isNodeOfType(statement, "ContinueStatement") || isNodeOfType(statement, "BreakStatement");
|
|
18021
|
-
};
|
|
18022
|
-
//#endregion
|
|
18023
18015
|
//#region src/plugin/utils/is-ast-descendant.ts
|
|
18024
18016
|
/**
|
|
18025
18017
|
* True when `inner` is `outer` itself or any descendant in the AST
|
|
@@ -18238,15 +18230,13 @@ const isSynchronousIteratorCall = (callNode, callbackArgument, scopes) => {
|
|
|
18238
18230
|
}
|
|
18239
18231
|
return Boolean(methodName && EAGER_ITERATOR_METHOD_NAMES.has(methodName) && callNode.arguments[0] === callbackArgument && !isProvablyEmptyEagerCollection(callee.object, scopes) && (methodName === "forEach" ? isProvablyEagerForEachCollection(callee.object, scopes) : isProvablyEagerCollection(callee.object, scopes)));
|
|
18240
18232
|
};
|
|
18241
|
-
const isSynchronousIteratorCallbackCall = (callNode, callbackArgument) => {
|
|
18242
|
-
const callee = stripParenExpression(callNode.callee);
|
|
18243
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
|
|
18244
|
-
if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments[1] === callbackArgument;
|
|
18245
|
-
return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments[0] === callbackArgument;
|
|
18246
|
-
};
|
|
18247
18233
|
const isSynchronousIteratorCallback = (functionNode) => {
|
|
18248
18234
|
const callNode = functionNode.parent;
|
|
18249
|
-
|
|
18235
|
+
if (!isNodeOfType(callNode, "CallExpression")) return false;
|
|
18236
|
+
const callee = stripParenExpression(callNode.callee);
|
|
18237
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
|
|
18238
|
+
if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments[1] === functionNode;
|
|
18239
|
+
return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments[0] === functionNode;
|
|
18250
18240
|
};
|
|
18251
18241
|
//#endregion
|
|
18252
18242
|
//#region src/plugin/utils/is-within-assignment-target.ts
|
|
@@ -18366,35 +18356,11 @@ const resolveEventListenerCaptureValueIdentityKey = (expression, context) => {
|
|
|
18366
18356
|
const rightIdentityKey = resolveEventListenerCaptureValueIdentityKey(unwrappedExpression.right, context);
|
|
18367
18357
|
return leftIdentityKey && rightIdentityKey ? `${unwrappedExpression.type}:${unwrappedExpression.operator}:${leftIdentityKey}:${rightIdentityKey}` : null;
|
|
18368
18358
|
};
|
|
18369
|
-
const resolveReadOnlyEventListenerOptions = (optionsNode, context) => {
|
|
18370
|
-
const unwrappedOptions = stripParenExpression(optionsNode);
|
|
18371
|
-
if (!isNodeOfType(unwrappedOptions, "Identifier")) return resolveStableValue(unwrappedOptions, context);
|
|
18372
|
-
const optionsSymbol = context.scopes.symbolFor(unwrappedOptions);
|
|
18373
|
-
const initializer = optionsSymbol?.initializer ? stripParenExpression(optionsSymbol.initializer) : null;
|
|
18374
|
-
if (!optionsSymbol || !initializer) return resolveStableValue(unwrappedOptions, context);
|
|
18375
|
-
if (!isNodeOfType(initializer, "ObjectExpression")) {
|
|
18376
|
-
if (isNodeOfType(initializer, "Identifier") || isNodeOfType(initializer, "MemberExpression")) return null;
|
|
18377
|
-
return resolveStableValue(unwrappedOptions, context);
|
|
18378
|
-
}
|
|
18379
|
-
if (optionsSymbol.kind !== "const") return null;
|
|
18380
|
-
return optionsSymbol.references.every((reference) => {
|
|
18381
|
-
if (reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier)) return false;
|
|
18382
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
18383
|
-
const callNode = referenceRoot.parent;
|
|
18384
|
-
if (!isNodeOfType(callNode, "CallExpression") || callNode.arguments[2] !== referenceRoot) return false;
|
|
18385
|
-
const callee = stripParenExpression(callNode.callee);
|
|
18386
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
18387
|
-
const methodName = getStaticPropertyKeyName(callee);
|
|
18388
|
-
return methodName === "addEventListener" || methodName === "removeEventListener";
|
|
18389
|
-
}) ? initializer : null;
|
|
18390
|
-
};
|
|
18391
18359
|
const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqueOptionsIdentity) => {
|
|
18392
|
-
const
|
|
18393
|
-
if (optionsNode && !stableOptionsNode) return null;
|
|
18394
|
-
const capture = resolveEventListenerCapture(stableOptionsNode, { allowIndeterminateEntries: true });
|
|
18360
|
+
const capture = resolveEventListenerCapture(optionsNode, { allowIndeterminateEntries: true });
|
|
18395
18361
|
if (capture !== null) return `capture:${String(capture)}`;
|
|
18396
|
-
if (!
|
|
18397
|
-
const unwrappedOptions = stripParenExpression(
|
|
18362
|
+
if (!optionsNode) return null;
|
|
18363
|
+
const unwrappedOptions = stripParenExpression(optionsNode);
|
|
18398
18364
|
if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) {
|
|
18399
18365
|
const optionsKey = allowOpaqueOptionsIdentity ? resolveEventListenerCaptureValueIdentityKey(unwrappedOptions, context) : null;
|
|
18400
18366
|
return optionsKey ? `options:${optionsKey}` : null;
|
|
@@ -18441,8 +18407,12 @@ const doEventListenerCapturesMatch = (registrationOptions, releaseOptions, conte
|
|
|
18441
18407
|
return registrationCaptureKey !== null && registrationCaptureKey === resolveEventListenerCaptureIdentityKey(releaseOptions, context, allowOpaqueOptionsIdentity);
|
|
18442
18408
|
};
|
|
18443
18409
|
const findAssignedResourceKey = (resourceNode, context) => {
|
|
18444
|
-
|
|
18445
|
-
|
|
18410
|
+
let currentNode = resourceNode;
|
|
18411
|
+
let parentNode = currentNode.parent;
|
|
18412
|
+
while (isNodeOfType(parentNode, "ChainExpression")) {
|
|
18413
|
+
currentNode = parentNode;
|
|
18414
|
+
parentNode = currentNode.parent;
|
|
18415
|
+
}
|
|
18446
18416
|
if (isNodeOfType(parentNode, "VariableDeclarator") && parentNode.init === currentNode) return resolveExpressionKey(parentNode.id, context);
|
|
18447
18417
|
if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.right === currentNode) return resolveExpressionKey(parentNode.left, context);
|
|
18448
18418
|
return null;
|
|
@@ -18713,18 +18683,6 @@ const resolveIteratorCollectionKey = (expression, context) => {
|
|
|
18713
18683
|
}
|
|
18714
18684
|
return null;
|
|
18715
18685
|
};
|
|
18716
|
-
const resolveReceiverIteratorCollectionKey = (expression, context) => {
|
|
18717
|
-
if (!expression) return null;
|
|
18718
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
18719
|
-
if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
|
|
18720
|
-
const collectionExpression = findForOfStatementForIteratorExpression(unwrappedExpression, context)?.right;
|
|
18721
|
-
if (!collectionExpression) return null;
|
|
18722
|
-
const collectionIdentifier = stripParenExpression(collectionExpression);
|
|
18723
|
-
if (!isNodeOfType(collectionIdentifier, "Identifier") || !isPrivatePlainConstIdentifier(collectionIdentifier, context)) return null;
|
|
18724
|
-
const collectionSymbol = context.scopes.symbolFor(collectionIdentifier);
|
|
18725
|
-
const initializer = collectionSymbol?.initializer ? stripParenExpression(collectionSymbol.initializer) : null;
|
|
18726
|
-
return collectionSymbol && isNodeOfType(initializer, "ArrayExpression") && hasOnlyReplayableCollectionReferences(collectionIdentifier, context, /* @__PURE__ */ new Set()) ? `symbol:${collectionSymbol.id}` : null;
|
|
18727
|
-
};
|
|
18728
18686
|
const isStableLoopReceiver = (expression, context) => {
|
|
18729
18687
|
if (!expression) return false;
|
|
18730
18688
|
const unwrappedExpression = stripParenExpression(expression);
|
|
@@ -19495,28 +19453,6 @@ const isFunctionReturnedFromReactHook = (functionNode, context, requireRefProper
|
|
|
19495
19453
|
});
|
|
19496
19454
|
};
|
|
19497
19455
|
const isFunctionUsedAsReactRef = (functionNode, context) => isFunctionForwardedToReactRef(functionNode, context) || isFunctionReturnedFromReactHook(functionNode, context, true);
|
|
19498
|
-
const findCallbackRefReplacementReleaseGuard = (releaseCall, ownerFunction, releaseReceiverKey, registrationReceiverKey, context) => {
|
|
19499
|
-
let descendant = releaseCall;
|
|
19500
|
-
let ancestor = descendant.parent;
|
|
19501
|
-
while (ancestor && ancestor !== ownerFunction) {
|
|
19502
|
-
if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant && ancestor.alternate === null) {
|
|
19503
|
-
const test = stripParenExpression(ancestor.test);
|
|
19504
|
-
if (!isNodeOfType(test, "LogicalExpression") || test.operator !== "&&") return null;
|
|
19505
|
-
const operands = [stripParenExpression(test.left), stripParenExpression(test.right)];
|
|
19506
|
-
const hasLiveReceiverTest = operands.some((operand) => doesTestRequireLiveExpressionKey(operand, releaseReceiverKey, context));
|
|
19507
|
-
const hasDifferentReceiverTest = operands.some((operand) => {
|
|
19508
|
-
if (!isNodeOfType(operand, "BinaryExpression") || operand.operator !== "!==" && operand.operator !== "!=") return false;
|
|
19509
|
-
const leftKey = resolveExpressionKey(operand.left, context);
|
|
19510
|
-
const rightKey = resolveExpressionKey(operand.right, context);
|
|
19511
|
-
return leftKey === releaseReceiverKey && rightKey === registrationReceiverKey || rightKey === releaseReceiverKey && leftKey === registrationReceiverKey;
|
|
19512
|
-
});
|
|
19513
|
-
return hasLiveReceiverTest && hasDifferentReceiverTest ? ancestor : null;
|
|
19514
|
-
}
|
|
19515
|
-
descendant = ancestor;
|
|
19516
|
-
ancestor = descendant.parent;
|
|
19517
|
-
}
|
|
19518
|
-
return null;
|
|
19519
|
-
};
|
|
19520
19456
|
const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
|
|
19521
19457
|
if (!isNodeOfType(usage.node, "CallExpression")) return false;
|
|
19522
19458
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
@@ -19537,7 +19473,7 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
|
|
|
19537
19473
|
if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
|
|
19538
19474
|
if (isNodeOfType(child, "AssignmentExpression") && child.operator === "=" && resolveReactRefSymbol(stripParenExpression(child.left), context.scopes)?.id === releaseRefSymbol.id && resolveExpressionKey(child.right, context) === registrationReceiverKey && releaseStart !== null && (getRangeStart(child) ?? -1) > releaseStart) matchingOwnershipAssignments.push(child);
|
|
19539
19475
|
});
|
|
19540
|
-
const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ??
|
|
19476
|
+
const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
|
|
19541
19477
|
const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
|
|
19542
19478
|
return doNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
|
|
19543
19479
|
};
|
|
@@ -19687,21 +19623,14 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
19687
19623
|
if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
|
|
19688
19624
|
if (usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub" || releaseVerbName === "close" || releaseVerbName === "unwatch" || releaseVerbName === "unlisten" || BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(releaseVerbName))) return true;
|
|
19689
19625
|
if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
|
|
19626
|
+
if (usage.registrationVerbName === "addListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1) return isProvenLegacyMediaQueryListMethodCall(usage.node, "addListener", context) && releaseVerbName === "removeListener" && isProvenLegacyMediaQueryListMethodCall(callNode, "removeListener", context) && usage.receiverKey !== null && resolveStableMediaQueryListenerIdentityKey(callee.object, context) === usage.receiverKey && usage.handlerKey !== null && resolveStableMediaQueryListenerIdentityKey(callNode.arguments?.[0], context) === usage.handlerKey;
|
|
19690
19627
|
if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
|
|
19691
|
-
if (usage.registrationVerbName === "addListener" && releaseVerbName === "removeListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1) {
|
|
19692
|
-
if (callNode.arguments?.length !== 1) return false;
|
|
19693
|
-
const registrationHandler = resolveStableValue(usage.node.arguments[0], context);
|
|
19694
|
-
if (!isProvenLegacyMediaQueryListMethodCall(usage.node, "addListener", context) && !isFunctionLike$1(registrationHandler)) return false;
|
|
19695
|
-
}
|
|
19696
19628
|
if (usage.registrationVerbName === "addEventListener" && releaseVerbName === "removeEventListener" && isNodeOfType(usage.node, "CallExpression")) {
|
|
19697
19629
|
if (!isNodeOfType(stripParenExpression(usage.node.callee), "MemberExpression")) return false;
|
|
19698
19630
|
if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], callNode.arguments?.[2], context, true)) return false;
|
|
19699
19631
|
}
|
|
19700
19632
|
if (isNodeOfType(usage.node, "CallExpression") && !hasSafeForEachProjectionCleanup(usage.node, callNode, context)) return false;
|
|
19701
|
-
|
|
19702
|
-
const registrationReceiverCollectionKey = isNodeOfType(registrationCallee, "MemberExpression") ? resolveReceiverIteratorCollectionKey(registrationCallee.object, context) : null;
|
|
19703
|
-
const releaseReceiverCollectionKeyForPair = resolveReceiverIteratorCollectionKey(callee.object, context);
|
|
19704
|
-
if (!(registrationReceiverCollectionKey !== null && registrationReceiverCollectionKey === releaseReceiverCollectionKeyForPair) && (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey)) return false;
|
|
19633
|
+
if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) return false;
|
|
19705
19634
|
if (usage.registrationVerbName === "subscribe" && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub") && usage.handleKey !== null && resolveExpressionKey(callNode.arguments?.[0], context) === usage.handleKey) return true;
|
|
19706
19635
|
const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
|
|
19707
19636
|
if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
|
|
@@ -19738,7 +19667,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
19738
19667
|
const usesUnaryListenerSignatureForCalls = isNodeOfType(usage.node, "CallExpression") && usesUnaryListenerSignature(usage.node, callNode);
|
|
19739
19668
|
const releaseHandler = usesUnaryListenerSignatureForCalls ? callNode.arguments?.[0] : callNode.arguments?.[1];
|
|
19740
19669
|
if (!releaseHandler) return releaseVerbName === "off";
|
|
19741
|
-
const expectedHandlerKey = usesUnaryListenerSignatureForCalls ? usage.
|
|
19670
|
+
const expectedHandlerKey = usesUnaryListenerSignatureForCalls ? usage.eventKey : usage.handlerKey;
|
|
19742
19671
|
const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignatureForCalls ? 0 : 1] : null;
|
|
19743
19672
|
return expectedHandlerKey !== null && resolveResourceIdentityKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
|
|
19744
19673
|
}
|
|
@@ -20212,7 +20141,6 @@ const findRetainedFunctionLeak = (retainedFunction, context, options) => {
|
|
|
20212
20141
|
walkAst(body, (child) => {
|
|
20213
20142
|
if (leak !== null) return false;
|
|
20214
20143
|
if (isFunctionLike$1(child)) return false;
|
|
20215
|
-
if (!isNodeReachableWithinFunction(child, context)) return false;
|
|
20216
20144
|
if (isSocketConstruction(child) && !doesResourceResultEscape(child, allowReturnedSocketEscape, false, context)) {
|
|
20217
20145
|
const socketUsage = {
|
|
20218
20146
|
kind: "socket",
|
|
@@ -20465,164 +20393,6 @@ const isInlineRetainedHandlerFunction = (functionNode, context) => {
|
|
|
20465
20393
|
const objectParent = objectExpression.parent;
|
|
20466
20394
|
return (isNodeOfType(objectParent, "CallExpression") && objectParent.arguments.some((argument) => argument === objectExpression) || isNodeOfType(objectParent, "JSXExpressionContainer")) && findRenderPhaseComponentOrHook(parentNode, context.scopes) !== null;
|
|
20467
20395
|
};
|
|
20468
|
-
const readInvocationArgumentValue = (expression, context) => {
|
|
20469
|
-
if (!expression) return {
|
|
20470
|
-
isDefinitelyUndefined: true,
|
|
20471
|
-
truthiness: "falsy"
|
|
20472
|
-
};
|
|
20473
|
-
const target = stripParenExpression(expression);
|
|
20474
|
-
if (isNodeOfType(target, "Literal")) return {
|
|
20475
|
-
isDefinitelyUndefined: false,
|
|
20476
|
-
truthiness: target.value ? "truthy" : "falsy"
|
|
20477
|
-
};
|
|
20478
|
-
if (isNodeOfType(target, "Identifier") && target.name === "undefined" && context.scopes.isGlobalReference(target)) return {
|
|
20479
|
-
isDefinitelyUndefined: true,
|
|
20480
|
-
truthiness: "falsy"
|
|
20481
|
-
};
|
|
20482
|
-
if (isNodeOfType(target, "UnaryExpression") && target.operator === "void") return {
|
|
20483
|
-
isDefinitelyUndefined: true,
|
|
20484
|
-
truthiness: "falsy"
|
|
20485
|
-
};
|
|
20486
|
-
if (isNodeOfType(target, "ArrayExpression") || isNodeOfType(target, "ArrowFunctionExpression") || isNodeOfType(target, "ClassExpression") || isNodeOfType(target, "FunctionExpression") || isNodeOfType(target, "NewExpression") || isNodeOfType(target, "ObjectExpression")) return {
|
|
20487
|
-
isDefinitelyUndefined: false,
|
|
20488
|
-
truthiness: "truthy"
|
|
20489
|
-
};
|
|
20490
|
-
return {
|
|
20491
|
-
isDefinitelyUndefined: false,
|
|
20492
|
-
truthiness: "unknown"
|
|
20493
|
-
};
|
|
20494
|
-
};
|
|
20495
|
-
const readInvocationConditionTruthiness = (expression, parameterValues, context) => {
|
|
20496
|
-
const target = stripParenExpression(expression);
|
|
20497
|
-
const atomicValue = readInvocationArgumentValue(target, context);
|
|
20498
|
-
if (atomicValue.truthiness !== "unknown") return atomicValue.truthiness;
|
|
20499
|
-
if (isNodeOfType(target, "Identifier")) {
|
|
20500
|
-
const symbol = context.scopes.symbolFor(target);
|
|
20501
|
-
return symbol ? parameterValues.get(symbol.id)?.truthiness ?? "unknown" : "unknown";
|
|
20502
|
-
}
|
|
20503
|
-
if (isNodeOfType(target, "UnaryExpression") && target.operator === "!") {
|
|
20504
|
-
const argumentTruthiness = readInvocationConditionTruthiness(target.argument, parameterValues, context);
|
|
20505
|
-
return argumentTruthiness === "truthy" ? "falsy" : argumentTruthiness === "falsy" ? "truthy" : "unknown";
|
|
20506
|
-
}
|
|
20507
|
-
if (isNodeOfType(target, "LogicalExpression")) {
|
|
20508
|
-
const leftTruthiness = readInvocationConditionTruthiness(target.left, parameterValues, context);
|
|
20509
|
-
const rightTruthiness = readInvocationConditionTruthiness(target.right, parameterValues, context);
|
|
20510
|
-
if (target.operator === "&&") {
|
|
20511
|
-
if (leftTruthiness === "falsy" || rightTruthiness === "falsy") return "falsy";
|
|
20512
|
-
return leftTruthiness === "truthy" && rightTruthiness === "truthy" ? "truthy" : "unknown";
|
|
20513
|
-
}
|
|
20514
|
-
if (target.operator === "||") {
|
|
20515
|
-
if (leftTruthiness === "truthy" || rightTruthiness === "truthy") return "truthy";
|
|
20516
|
-
return leftTruthiness === "falsy" && rightTruthiness === "falsy" ? "falsy" : "unknown";
|
|
20517
|
-
}
|
|
20518
|
-
return "unknown";
|
|
20519
|
-
}
|
|
20520
|
-
if (isNodeOfType(target, "ConditionalExpression")) {
|
|
20521
|
-
const testTruthiness = readInvocationConditionTruthiness(target.test, parameterValues, context);
|
|
20522
|
-
if (testTruthiness === "truthy") return readInvocationConditionTruthiness(target.consequent, parameterValues, context);
|
|
20523
|
-
if (testTruthiness === "falsy") return readInvocationConditionTruthiness(target.alternate, parameterValues, context);
|
|
20524
|
-
const consequentTruthiness = readInvocationConditionTruthiness(target.consequent, parameterValues, context);
|
|
20525
|
-
return consequentTruthiness === readInvocationConditionTruthiness(target.alternate, parameterValues, context) ? consequentTruthiness : "unknown";
|
|
20526
|
-
}
|
|
20527
|
-
if (isNodeOfType(target, "CallExpression") && isNodeOfType(target.callee, "Identifier") && target.callee.name === "Boolean" && context.scopes.isGlobalReference(target.callee) && target.arguments[0] && isAstNode(target.arguments[0])) return readInvocationConditionTruthiness(target.arguments[0], parameterValues, context);
|
|
20528
|
-
return "unknown";
|
|
20529
|
-
};
|
|
20530
|
-
const getInvocationParameterValues = (retainedFunction, invocation, leakNode, context) => {
|
|
20531
|
-
const parameterValues = /* @__PURE__ */ new Map();
|
|
20532
|
-
if (!isFunctionLike$1(retainedFunction) || !invocation.isDirect) return parameterValues;
|
|
20533
|
-
for (const [parameterIndex, parameter] of retainedFunction.params.entries()) {
|
|
20534
|
-
const argument = invocation.call.arguments[parameterIndex];
|
|
20535
|
-
const argumentExpression = argument && isAstNode(argument) ? argument : null;
|
|
20536
|
-
let parameterIdentifier = null;
|
|
20537
|
-
let parameterValue = readInvocationArgumentValue(argumentExpression, context);
|
|
20538
|
-
if (isNodeOfType(parameter, "Identifier")) parameterIdentifier = parameter;
|
|
20539
|
-
else if (isNodeOfType(parameter, "AssignmentPattern") && isNodeOfType(parameter.left, "Identifier")) {
|
|
20540
|
-
parameterIdentifier = parameter.left;
|
|
20541
|
-
if (parameterValue.isDefinitelyUndefined) parameterValue = readInvocationArgumentValue(parameter.right, context);
|
|
20542
|
-
} else if (isNodeOfType(parameter, "RestElement") && isNodeOfType(parameter.argument, "Identifier")) {
|
|
20543
|
-
parameterIdentifier = parameter.argument;
|
|
20544
|
-
parameterValue = {
|
|
20545
|
-
isDefinitelyUndefined: false,
|
|
20546
|
-
truthiness: "truthy"
|
|
20547
|
-
};
|
|
20548
|
-
}
|
|
20549
|
-
if (!parameterIdentifier) continue;
|
|
20550
|
-
const parameterSymbol = context.scopes.symbolFor(parameterIdentifier);
|
|
20551
|
-
if (!parameterSymbol) continue;
|
|
20552
|
-
const isWrittenBeforeLeak = parameterSymbol.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] < leakNode.range[0]);
|
|
20553
|
-
parameterValues.set(parameterSymbol.id, isWrittenBeforeLeak ? {
|
|
20554
|
-
isDefinitelyUndefined: false,
|
|
20555
|
-
truthiness: "unknown"
|
|
20556
|
-
} : parameterValue);
|
|
20557
|
-
}
|
|
20558
|
-
return parameterValues;
|
|
20559
|
-
};
|
|
20560
|
-
const isLeakPathDisabledForInvocation = (retainedFunction, leakNode, invocation, context) => {
|
|
20561
|
-
if (!invocation.isDirect) return false;
|
|
20562
|
-
const parameterValues = getInvocationParameterValues(retainedFunction, invocation, leakNode, context);
|
|
20563
|
-
let child = leakNode;
|
|
20564
|
-
let ancestor = leakNode.parent ?? null;
|
|
20565
|
-
while (ancestor && ancestor !== retainedFunction) {
|
|
20566
|
-
if (isNodeOfType(ancestor, "BlockStatement")) {
|
|
20567
|
-
const childIndex = ancestor.body.findIndex((statement) => statement === child);
|
|
20568
|
-
for (const precedingStatement of ancestor.body.slice(0, childIndex)) {
|
|
20569
|
-
if (!isNodeOfType(precedingStatement, "IfStatement") || precedingStatement.alternate || !isEarlyExitStatement$1(precedingStatement.consequent)) continue;
|
|
20570
|
-
if (readInvocationConditionTruthiness(precedingStatement.test, parameterValues, context) === "truthy") return true;
|
|
20571
|
-
}
|
|
20572
|
-
}
|
|
20573
|
-
let requiredTruthiness = null;
|
|
20574
|
-
let condition = null;
|
|
20575
|
-
if (isNodeOfType(ancestor, "IfStatement")) {
|
|
20576
|
-
condition = ancestor.test;
|
|
20577
|
-
requiredTruthiness = ancestor.consequent === child ? "truthy" : "falsy";
|
|
20578
|
-
} else if (isNodeOfType(ancestor, "ConditionalExpression")) {
|
|
20579
|
-
condition = ancestor.test;
|
|
20580
|
-
requiredTruthiness = ancestor.consequent === child ? "truthy" : "falsy";
|
|
20581
|
-
} else if (isNodeOfType(ancestor, "LogicalExpression") && ancestor.right === child && ancestor.operator !== "??") {
|
|
20582
|
-
condition = ancestor.left;
|
|
20583
|
-
requiredTruthiness = ancestor.operator === "&&" ? "truthy" : "falsy";
|
|
20584
|
-
} else if ((isNodeOfType(ancestor, "WhileStatement") || isNodeOfType(ancestor, "DoWhileStatement")) && ancestor.body === child) {
|
|
20585
|
-
condition = ancestor.test;
|
|
20586
|
-
requiredTruthiness = "truthy";
|
|
20587
|
-
} else if (isNodeOfType(ancestor, "ForStatement") && ancestor.body === child && ancestor.test) {
|
|
20588
|
-
condition = ancestor.test;
|
|
20589
|
-
requiredTruthiness = "truthy";
|
|
20590
|
-
}
|
|
20591
|
-
if (condition && requiredTruthiness) {
|
|
20592
|
-
const conditionTruthiness = readInvocationConditionTruthiness(condition, parameterValues, context);
|
|
20593
|
-
if (conditionTruthiness !== "unknown" && conditionTruthiness !== requiredTruthiness) return true;
|
|
20594
|
-
}
|
|
20595
|
-
child = ancestor;
|
|
20596
|
-
ancestor = ancestor.parent ?? null;
|
|
20597
|
-
}
|
|
20598
|
-
return false;
|
|
20599
|
-
};
|
|
20600
|
-
const getEffectRetainedInvocations = (retainedFunction, context) => {
|
|
20601
|
-
if (!isFunctionLike$1(retainedFunction)) return [];
|
|
20602
|
-
const componentFunction = findEnclosingFunction$1(retainedFunction);
|
|
20603
|
-
if (!componentFunction || !isFunctionLike$1(componentFunction)) return [];
|
|
20604
|
-
const invocations = [];
|
|
20605
|
-
walkAst(componentFunction.body, (child) => {
|
|
20606
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactHookCall(child, CLEANUP_EFFECT_HOOK_NAMES, context.scopes)) return;
|
|
20607
|
-
const effectCallback = getEffectCallback(child);
|
|
20608
|
-
if (!effectCallback || !isFunctionLike$1(effectCallback)) return;
|
|
20609
|
-
walkAst(effectCallback.body, (effectChild) => {
|
|
20610
|
-
if (effectChild !== effectCallback.body && isFunctionLike$1(effectChild)) return false;
|
|
20611
|
-
if (!isNodeOfType(effectChild, "CallExpression") || !isNodeReachableWithinFunction(effectChild, context)) return;
|
|
20612
|
-
const isDirectInvocation = resolveRefOwnedCleanupFunction(effectChild.callee, context) === retainedFunction;
|
|
20613
|
-
const isSynchronousIteratorInvocation = effectChild.arguments.some((argument) => isAstNode(argument) && resolveRefOwnedCleanupFunction(argument, context) === retainedFunction && isSynchronousIteratorCallbackCall(effectChild, argument));
|
|
20614
|
-
if (isDirectInvocation) invocations.push({
|
|
20615
|
-
call: effectChild,
|
|
20616
|
-
isDirect: true
|
|
20617
|
-
});
|
|
20618
|
-
if (isSynchronousIteratorInvocation) invocations.push({
|
|
20619
|
-
call: effectChild,
|
|
20620
|
-
isDirect: false
|
|
20621
|
-
});
|
|
20622
|
-
});
|
|
20623
|
-
});
|
|
20624
|
-
return invocations;
|
|
20625
|
-
};
|
|
20626
20396
|
const effectNeedsCleanup = defineRule({
|
|
20627
20397
|
id: "effect-needs-cleanup",
|
|
20628
20398
|
title: "Effect subscription or timer never cleaned up",
|
|
@@ -20633,19 +20403,13 @@ const effectNeedsCleanup = defineRule({
|
|
|
20633
20403
|
const reportRetainedLeak = (retainedFunction) => {
|
|
20634
20404
|
const refEffectUsage = getReactRefEffectUsage(retainedFunction, context);
|
|
20635
20405
|
if (!refEffectUsage && !isPotentiallyReachableFunction(retainedFunction, context)) return;
|
|
20636
|
-
const effectInvocations = getEffectRetainedInvocations(retainedFunction, context);
|
|
20637
|
-
const isEffectInvoked = effectInvocations.length > 0;
|
|
20638
20406
|
const leak = findRetainedFunctionLeak(retainedFunction, context, refEffectUsage ? {
|
|
20639
20407
|
allowReturnedResourceEscape: refEffectUsage.doesEffectOwnEveryResult,
|
|
20640
20408
|
allowReturnedTimerEscape: false,
|
|
20641
20409
|
includeOneShotTimers: true,
|
|
20642
20410
|
requireCallableReturnedResource: true
|
|
20643
|
-
} : isEffectInvoked ? {
|
|
20644
|
-
allowReturnedTimerEscape: false,
|
|
20645
|
-
includeOneShotTimers: true
|
|
20646
20411
|
} : void 0);
|
|
20647
20412
|
if (!leak) return;
|
|
20648
|
-
if (isEffectInvoked && leak.resourceName === "setTimeout" && (!isNodeReachableWithinFunction(leak.node, context) || isFunctionLike$1(retainedFunction) && retainedFunction.params.length > 0 && !context.cfg.isUnconditionalFromEntry(leak.node) && effectInvocations.every((invocation) => isLeakPathDisabledForInvocation(retainedFunction, leak.node, invocation, context)))) return;
|
|
20649
20413
|
const resourceNoun = RESOURCE_NOUN_BY_KIND[leak.kind];
|
|
20650
20414
|
context.report({
|
|
20651
20415
|
node: leak.node,
|
|
@@ -45616,6 +45380,14 @@ const noAriaInvalidWithoutDescription = defineRule({
|
|
|
45616
45380
|
} })
|
|
45617
45381
|
});
|
|
45618
45382
|
//#endregion
|
|
45383
|
+
//#region src/plugin/utils/is-early-exit-statement.ts
|
|
45384
|
+
const isEarlyExitStatement$1 = (statement) => {
|
|
45385
|
+
if (!statement) return false;
|
|
45386
|
+
if (statementAlwaysExits$1(statement)) return true;
|
|
45387
|
+
if (isNodeOfType(statement, "BlockStatement")) return isEarlyExitStatement$1(statement.body.at(-1));
|
|
45388
|
+
return isNodeOfType(statement, "ContinueStatement") || isNodeOfType(statement, "BreakStatement");
|
|
45389
|
+
};
|
|
45390
|
+
//#endregion
|
|
45619
45391
|
//#region src/plugin/utils/unwrap-negative-guard-form.ts
|
|
45620
45392
|
const unwrapNegativeGuardForm = (test) => {
|
|
45621
45393
|
const expression = stripParenExpression(test);
|
|
@@ -75011,29 +74783,6 @@ const doesPredicateTruthRequireMatch = (matchCall, predicateFunction) => {
|
|
|
75011
74783
|
}
|
|
75012
74784
|
return !isNegated && predicateFunction.body === child;
|
|
75013
74785
|
};
|
|
75014
|
-
const doesPredicateReturnNormalizedMatch = (matchCall, predicateFunction) => {
|
|
75015
|
-
if (!isFunctionLike$1(predicateFunction) || !isNodeOfType(predicateFunction.body, "BlockStatement") || predicateFunction.body.body.length !== 2 || !isNodeOfType(predicateFunction.body.body[0], "VariableDeclaration")) return false;
|
|
75016
|
-
const returnStatement = predicateFunction.body.body[1];
|
|
75017
|
-
if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
|
|
75018
|
-
let negationCount = 0;
|
|
75019
|
-
let expression = matchCall;
|
|
75020
|
-
let parent = expression.parent ?? null;
|
|
75021
|
-
while (parent && parent !== returnStatement) {
|
|
75022
|
-
if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "!") {
|
|
75023
|
-
negationCount += 1;
|
|
75024
|
-
expression = parent;
|
|
75025
|
-
parent = parent.parent ?? null;
|
|
75026
|
-
continue;
|
|
75027
|
-
}
|
|
75028
|
-
if (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) || isNodeOfType(parent, "ChainExpression")) {
|
|
75029
|
-
expression = parent;
|
|
75030
|
-
parent = parent.parent ?? null;
|
|
75031
|
-
continue;
|
|
75032
|
-
}
|
|
75033
|
-
return false;
|
|
75034
|
-
}
|
|
75035
|
-
return parent === returnStatement && returnStatement.argument === expression && negationCount % 2 === 0;
|
|
75036
|
-
};
|
|
75037
74786
|
const isStringTypeofGuardForPath = (test, expectedPath) => {
|
|
75038
74787
|
const target = stripParenExpression(test);
|
|
75039
74788
|
if (!isNodeOfType(target, "BinaryExpression") || target.operator !== "===") return false;
|
|
@@ -75074,26 +74823,6 @@ const pathUsesOptionalAccess = (node) => {
|
|
|
75074
74823
|
current = current.object;
|
|
75075
74824
|
}
|
|
75076
74825
|
};
|
|
75077
|
-
const getNormalizedClassNameRoot = (expression) => {
|
|
75078
|
-
const conditional = stripParenExpression(expression);
|
|
75079
|
-
if (!isNodeOfType(conditional, "ConditionalExpression")) return null;
|
|
75080
|
-
const consequent = stripParenExpression(conditional.consequent);
|
|
75081
|
-
const rootIdentifier = getRootIdentifier(consequent);
|
|
75082
|
-
if (!rootIdentifier || receiverPathKey(consequent) !== `${rootIdentifier.name}.className`) return null;
|
|
75083
|
-
const test = stripParenExpression(conditional.test);
|
|
75084
|
-
if (!isNodeOfType(test, "BinaryExpression") || test.operator !== "===") return null;
|
|
75085
|
-
const testOperands = [test.left, test.right].map((operand) => stripParenExpression(operand));
|
|
75086
|
-
const typeofOperand = testOperands.find((operand) => isNodeOfType(operand, "UnaryExpression"));
|
|
75087
|
-
const stringOperand = testOperands.find((operand) => isNodeOfType(operand, "Literal"));
|
|
75088
|
-
if (!typeofOperand || !isNodeOfType(typeofOperand, "UnaryExpression") || typeofOperand.operator !== "typeof" || receiverPathKey(typeofOperand.argument) !== `${rootIdentifier.name}.className` || !stringOperand || !isNodeOfType(stringOperand, "Literal") || stringOperand.value !== "string") return null;
|
|
75089
|
-
const alternate = stripParenExpression(conditional.alternate);
|
|
75090
|
-
if (!isNodeOfType(alternate, "LogicalExpression") || alternate.operator !== "??") return null;
|
|
75091
|
-
const fallback = stripParenExpression(alternate.right);
|
|
75092
|
-
const attributeCall = stripParenExpression(alternate.left);
|
|
75093
|
-
if (!isNodeOfType(fallback, "Literal") || fallback.value !== "" || !isNodeOfType(attributeCall, "CallExpression") || !isNodeOfType(attributeCall.callee, "MemberExpression") || getStaticPropertyName(attributeCall.callee) !== "getAttribute" || receiverPathKey(attributeCall.callee.object) !== rootIdentifier.name) return null;
|
|
75094
|
-
const attributeName = attributeCall.arguments[0] ? stripParenExpression(attributeCall.arguments[0]) : null;
|
|
75095
|
-
return attributeName && isNodeOfType(attributeName, "Literal") && attributeName.value === "class" ? rootIdentifier : null;
|
|
75096
|
-
};
|
|
75097
74826
|
const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedPattern, context) => {
|
|
75098
74827
|
const resultIdentifier = getRootIdentifier(matchReceiver);
|
|
75099
74828
|
const resultPath = receiverPathKey(matchReceiver);
|
|
@@ -75102,17 +74831,11 @@ const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedP
|
|
|
75102
74831
|
if (!isDirectFinderMatchReturn(assertion) && !isOptionalResultPath) return false;
|
|
75103
74832
|
const resultSymbol = context.scopes.symbolFor(resultIdentifier);
|
|
75104
74833
|
const initializer = resultSymbol?.initializer ? stripParenExpression(resultSymbol.initializer) : null;
|
|
75105
|
-
if (resultSymbol?.kind !== "const" || !initializer) return false;
|
|
75106
|
-
const finderCall = isNodeOfType(initializer, "CallExpression") ? initializer : isNodeOfType(initializer, "ConditionalExpression") ? (() => {
|
|
75107
|
-
const alternate = stripParenExpression(initializer.alternate);
|
|
75108
|
-
const consequent = stripParenExpression(initializer.consequent);
|
|
75109
|
-
return (isNodeOfType(alternate, "Literal") && alternate.value === null || isNodeOfType(alternate, "Identifier") && alternate.name === "undefined" && context.scopes.isGlobalReference(alternate)) && isNodeOfType(consequent, "CallExpression") ? consequent : null;
|
|
75110
|
-
})() : null;
|
|
75111
|
-
if (!finderCall || !isNodeOfType(finderCall, "CallExpression")) return false;
|
|
74834
|
+
if (resultSymbol?.kind !== "const" || !initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
75112
74835
|
if (!isOptionalResultPath && !isImmediatelyGuardedFinderResult(assertion, resultSymbol, resultPath, context)) return false;
|
|
75113
|
-
const finderCallee = stripParenExpression(
|
|
74836
|
+
const finderCallee = stripParenExpression(initializer.callee);
|
|
75114
74837
|
if (!isNodeOfType(finderCallee, "Identifier") || !(context.scopes.symbolFor(finderCallee)?.kind === "import" && getImportedNameFromModule(assertion, finderCallee.name, CLOUDSCAPE_DOM_MODULE) === "findUpUntil" || finderCallee.name === "findUpUntil" && context.scopes.isGlobalReference(finderCallee))) return false;
|
|
75115
|
-
const predicateArgument =
|
|
74838
|
+
const predicateArgument = initializer.arguments[1];
|
|
75116
74839
|
if (!predicateArgument) return false;
|
|
75117
74840
|
const predicateFunction = resolveExactLocalFunction(predicateArgument, context.scopes);
|
|
75118
74841
|
if (!predicateFunction || !isFunctionLike$1(predicateFunction)) return false;
|
|
@@ -75136,43 +74859,6 @@ const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedP
|
|
|
75136
74859
|
});
|
|
75137
74860
|
return didProveMatch;
|
|
75138
74861
|
};
|
|
75139
|
-
const isMatchProvenByNormalizedFindUpUntilPredicate = (assertion, matchReceiver, assertedPattern, context) => {
|
|
75140
|
-
const normalizedReceiver = stripParenExpression(matchReceiver);
|
|
75141
|
-
if (!isNodeOfType(normalizedReceiver, "Identifier")) return false;
|
|
75142
|
-
const normalizedReceiverSymbol = context.scopes.symbolFor(normalizedReceiver);
|
|
75143
|
-
const normalizedReceiverInitializer = normalizedReceiverSymbol?.initializer ? stripParenExpression(normalizedReceiverSymbol.initializer) : null;
|
|
75144
|
-
const resultIdentifier = normalizedReceiverInitializer ? getNormalizedClassNameRoot(normalizedReceiverInitializer) : null;
|
|
75145
|
-
if (normalizedReceiverSymbol?.kind !== "const" || normalizedReceiverSymbol.references.some((reference) => reference.flag !== "read") || !resultIdentifier) return false;
|
|
75146
|
-
const resultSymbol = context.scopes.symbolFor(resultIdentifier);
|
|
75147
|
-
const finderCall = resultSymbol?.initializer ? stripParenExpression(resultSymbol.initializer) : null;
|
|
75148
|
-
if (resultSymbol?.kind !== "const" || resultSymbol.references.some((reference) => reference.flag !== "read") || !finderCall || !isNodeOfType(finderCall, "CallExpression")) return false;
|
|
75149
|
-
const finderCallee = stripParenExpression(finderCall.callee);
|
|
75150
|
-
if (!isNodeOfType(finderCallee, "Identifier") || context.scopes.symbolFor(finderCallee)?.kind !== "import" || getImportedNameFromModule(assertion, finderCallee.name, CLOUDSCAPE_DOM_MODULE) !== "findUpUntil") return false;
|
|
75151
|
-
if (!isPresenceProvenBeforeNode(assertion, (test) => {
|
|
75152
|
-
const expression = stripParenExpression(test);
|
|
75153
|
-
return isNodeOfType(expression, "Identifier") && context.scopes.symbolFor(expression)?.id === resultSymbol.id;
|
|
75154
|
-
})) return false;
|
|
75155
|
-
const predicateArgument = finderCall.arguments[1];
|
|
75156
|
-
const predicateFunction = predicateArgument ? resolveExactLocalFunction(predicateArgument, context.scopes) : null;
|
|
75157
|
-
if (!predicateFunction || !isFunctionLike$1(predicateFunction) || predicateFunction.async || predicateFunction.generator) return false;
|
|
75158
|
-
const predicateParameter = predicateFunction.params[0];
|
|
75159
|
-
if (!isNodeOfType(predicateParameter, "Identifier")) return false;
|
|
75160
|
-
let didProveNormalizedMatch = false;
|
|
75161
|
-
walkAst(predicateFunction.body, (child) => {
|
|
75162
|
-
if (didProveNormalizedMatch || isFunctionLike$1(child)) return false;
|
|
75163
|
-
if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || getStaticPropertyName(child.callee) !== "match" || !child.arguments[0] || !areRegexPatternsEquivalent(child.arguments[0], assertedPattern, context) || !doesPredicateTruthRequireMatch(child, predicateFunction) && !doesPredicateReturnNormalizedMatch(child, predicateFunction)) return;
|
|
75164
|
-
const predicateReceiver = stripParenExpression(child.callee.object);
|
|
75165
|
-
if (!isNodeOfType(predicateReceiver, "Identifier")) return;
|
|
75166
|
-
const predicateReceiverSymbol = context.scopes.symbolFor(predicateReceiver);
|
|
75167
|
-
const predicateReceiverInitializer = predicateReceiverSymbol?.initializer ? stripParenExpression(predicateReceiverSymbol.initializer) : null;
|
|
75168
|
-
const predicateRoot = predicateReceiverInitializer ? getNormalizedClassNameRoot(predicateReceiverInitializer) : null;
|
|
75169
|
-
if (predicateReceiverSymbol?.kind === "const" && predicateReceiverSymbol.references.every((reference) => reference.flag === "read") && predicateRoot?.name === predicateParameter.name) {
|
|
75170
|
-
didProveNormalizedMatch = true;
|
|
75171
|
-
return false;
|
|
75172
|
-
}
|
|
75173
|
-
});
|
|
75174
|
-
return didProveNormalizedMatch;
|
|
75175
|
-
};
|
|
75176
74862
|
const scopeProvesFindMatch = (assertion, findReceiver, findPredicate, context) => {
|
|
75177
74863
|
if (!isStablePredicate(findPredicate, context)) return false;
|
|
75178
74864
|
return isPresenceProvenBeforeNode(assertion, (test) => testPositivelyContainsCall(test, (call) => {
|
|
@@ -75273,123 +74959,6 @@ const isEnsureThenFind = (assertion, findReceiver, findPredicate) => {
|
|
|
75273
74959
|
}
|
|
75274
74960
|
return false;
|
|
75275
74961
|
};
|
|
75276
|
-
const getReceiverRootIdentifier = (node) => {
|
|
75277
|
-
let target = stripParenExpression(node);
|
|
75278
|
-
while (isNodeOfType(target, "MemberExpression")) target = stripParenExpression(target.object);
|
|
75279
|
-
return isNodeOfType(target, "Identifier") ? target : null;
|
|
75280
|
-
};
|
|
75281
|
-
const getReceiverStatePath = (node) => {
|
|
75282
|
-
const target = stripParenExpression(node);
|
|
75283
|
-
if (isNodeOfType(target, "Identifier")) return target.name;
|
|
75284
|
-
if (!isNodeOfType(target, "MemberExpression")) return null;
|
|
75285
|
-
const objectPath = getReceiverStatePath(target.object);
|
|
75286
|
-
if (!objectPath) return null;
|
|
75287
|
-
return `${objectPath}.${getStaticPropertyName(target) ?? "*"}`;
|
|
75288
|
-
};
|
|
75289
|
-
const doesReceiverStateChangeBeforeAssertion = (ownerFunction, receiver, startOffset, assertion, context) => {
|
|
75290
|
-
const receiverRoot = getReceiverRootIdentifier(receiver);
|
|
75291
|
-
const receiverSymbol = receiverRoot ? context.scopes.symbolFor(receiverRoot) : null;
|
|
75292
|
-
const receiverPath = getReceiverStatePath(receiver);
|
|
75293
|
-
if (!receiverRoot || !receiverSymbol || !receiverPath) return true;
|
|
75294
|
-
const receiverAliasPaths = new Map([[receiverSymbol.id, receiverRoot.name]]);
|
|
75295
|
-
let didAddAlias = true;
|
|
75296
|
-
while (didAddAlias) {
|
|
75297
|
-
didAddAlias = false;
|
|
75298
|
-
walkAst(ownerFunction, (child) => {
|
|
75299
|
-
if (child !== ownerFunction && isFunctionLike$1(child)) return false;
|
|
75300
|
-
if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier") || !child.init) return;
|
|
75301
|
-
const initializer = stripParenExpression(child.init);
|
|
75302
|
-
if (!isNodeOfType(initializer, "Identifier") && !isNodeOfType(initializer, "MemberExpression")) return;
|
|
75303
|
-
const initializerRoot = getReceiverRootIdentifier(initializer);
|
|
75304
|
-
const initializerSymbol = initializerRoot ? context.scopes.symbolFor(initializerRoot) : null;
|
|
75305
|
-
const initializerBasePath = initializerSymbol ? receiverAliasPaths.get(initializerSymbol.id) : null;
|
|
75306
|
-
const initializerPath = getReceiverStatePath(initializer);
|
|
75307
|
-
if (!initializerRoot || !initializerBasePath || !initializerPath) return;
|
|
75308
|
-
const aliasSymbol = context.scopes.symbolFor(child.id);
|
|
75309
|
-
if (aliasSymbol && !receiverAliasPaths.has(aliasSymbol.id)) {
|
|
75310
|
-
const initializerSuffix = initializerPath.slice(initializerRoot.name.length);
|
|
75311
|
-
receiverAliasPaths.set(aliasSymbol.id, `${initializerBasePath}${initializerSuffix}`);
|
|
75312
|
-
didAddAlias = true;
|
|
75313
|
-
}
|
|
75314
|
-
});
|
|
75315
|
-
}
|
|
75316
|
-
let didChangeReceiverState = false;
|
|
75317
|
-
walkAst(ownerFunction, (child) => {
|
|
75318
|
-
if (didChangeReceiverState) return false;
|
|
75319
|
-
if (child !== ownerFunction && isFunctionLike$1(child)) return false;
|
|
75320
|
-
if (child.range[0] <= startOffset || child.range[0] >= assertion.range[0]) return;
|
|
75321
|
-
if (isNodeOfType(child, "CallExpression")) {
|
|
75322
|
-
didChangeReceiverState = true;
|
|
75323
|
-
return false;
|
|
75324
|
-
}
|
|
75325
|
-
const mutationTarget = isNodeOfType(child, "AssignmentExpression") || isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete" ? stripParenExpression(isNodeOfType(child, "AssignmentExpression") ? child.left : child.argument) : null;
|
|
75326
|
-
const mutationRoot = mutationTarget ? getReceiverRootIdentifier(mutationTarget) : null;
|
|
75327
|
-
const mutationSymbol = mutationRoot ? context.scopes.symbolFor(mutationRoot) : null;
|
|
75328
|
-
const mutationBasePath = mutationSymbol ? receiverAliasPaths.get(mutationSymbol.id) : null;
|
|
75329
|
-
const mutationPath = mutationTarget ? getReceiverStatePath(mutationTarget) : null;
|
|
75330
|
-
if (mutationRoot && mutationBasePath && mutationPath) {
|
|
75331
|
-
const canonicalMutationPath = `${mutationBasePath}${mutationPath.slice(mutationRoot.name.length)}`;
|
|
75332
|
-
if (canonicalMutationPath !== receiverPath && !canonicalMutationPath.startsWith(`${receiverPath}.`) && !receiverPath.startsWith(`${canonicalMutationPath}.`)) return;
|
|
75333
|
-
didChangeReceiverState = true;
|
|
75334
|
-
return false;
|
|
75335
|
-
}
|
|
75336
|
-
});
|
|
75337
|
-
return didChangeReceiverState;
|
|
75338
|
-
};
|
|
75339
|
-
const isFindProvenByGuardedMaximum = (assertion, findReceiver, findPredicate, context) => {
|
|
75340
|
-
const findLookup = findEqualityLookupParts(findPredicate);
|
|
75341
|
-
const maximumIdentifier = findLookup ? stripParenExpression(findLookup.comparedValue) : null;
|
|
75342
|
-
if (!findLookup || !maximumIdentifier || !isNodeOfType(maximumIdentifier, "Identifier")) return false;
|
|
75343
|
-
const maximumSymbol = context.scopes.symbolFor(maximumIdentifier);
|
|
75344
|
-
const maximumInitializer = maximumSymbol?.initializer ? stripParenExpression(maximumSymbol.initializer) : null;
|
|
75345
|
-
if (maximumSymbol?.kind !== "const" || maximumSymbol.references.some((reference) => reference.flag !== "read") || !maximumInitializer || !isNodeOfType(maximumInitializer, "CallExpression") || !isNodeOfType(maximumInitializer.callee, "MemberExpression") || getStaticPropertyName(maximumInitializer.callee) !== "reduce") return false;
|
|
75346
|
-
const filterCall = stripParenExpression(maximumInitializer.callee.object);
|
|
75347
|
-
if (!isNodeOfType(filterCall, "CallExpression") || !isNodeOfType(filterCall.callee, "MemberExpression") || getStaticPropertyName(filterCall.callee) !== "filter" || !areNodesLooselyEqual(filterCall.callee.object, findReceiver)) return false;
|
|
75348
|
-
const filterPredicate = filterCall.arguments[0] ? stripParenExpression(filterCall.arguments[0]) : null;
|
|
75349
|
-
if (!filterPredicate || !isStablePredicate(filterPredicate, context)) return false;
|
|
75350
|
-
const reducerArgument = maximumInitializer.arguments[0] ? stripParenExpression(maximumInitializer.arguments[0]) : null;
|
|
75351
|
-
const reducerFunction = reducerArgument ? resolveExactLocalFunction(reducerArgument, context.scopes) : null;
|
|
75352
|
-
const initialValue = maximumInitializer.arguments[1] ? stripParenExpression(maximumInitializer.arguments[1]) : null;
|
|
75353
|
-
if (!reducerFunction || !isFunctionLike$1(reducerFunction) || reducerFunction.async || reducerFunction.generator || !initialValue || !isNodeOfType(initialValue, "Literal") || typeof initialValue.value !== "number") return false;
|
|
75354
|
-
const accumulatorParameter = reducerFunction.params[0];
|
|
75355
|
-
const itemParameter = reducerFunction.params[1];
|
|
75356
|
-
const reducerBody = singleExpressionPredicateBody(reducerFunction);
|
|
75357
|
-
if (!isNodeOfType(accumulatorParameter, "Identifier") || !isNodeOfType(itemParameter, "Identifier") || !reducerBody || !isNodeOfType(reducerBody, "CallExpression") || !isNodeOfType(reducerBody.callee, "MemberExpression") || getStaticPropertyName(reducerBody.callee) !== "max") return false;
|
|
75358
|
-
const mathReceiver = stripParenExpression(reducerBody.callee.object);
|
|
75359
|
-
if (!isNodeOfType(mathReceiver, "Identifier") || mathReceiver.name !== "Math" || !context.scopes.isGlobalReference(mathReceiver) || reducerBody.arguments.length !== 2) return false;
|
|
75360
|
-
const accumulatorArgument = reducerBody.arguments.find((argument) => {
|
|
75361
|
-
const expression = stripParenExpression(argument);
|
|
75362
|
-
return isNodeOfType(expression, "Identifier") && expression.name === accumulatorParameter.name;
|
|
75363
|
-
});
|
|
75364
|
-
const itemMemberArgument = reducerBody.arguments.find((argument) => {
|
|
75365
|
-
const expression = stripParenExpression(argument);
|
|
75366
|
-
const rootIdentifier = getRootIdentifier(expression);
|
|
75367
|
-
return isNodeOfType(expression, "MemberExpression") && rootIdentifier?.name === itemParameter.name && receiverPathKey(expression)?.slice(itemParameter.name.length + 1) === findLookup.propertyName;
|
|
75368
|
-
});
|
|
75369
|
-
if (!accumulatorArgument || !itemMemberArgument) return false;
|
|
75370
|
-
if (!receiverPathKey(findReceiver)) return false;
|
|
75371
|
-
let ownerFunction = assertion.parent ?? null;
|
|
75372
|
-
while (ownerFunction && !isFunctionLike$1(ownerFunction)) ownerFunction = ownerFunction.parent ?? null;
|
|
75373
|
-
if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
|
|
75374
|
-
const maximumEnd = maximumInitializer.range[1];
|
|
75375
|
-
if (doesReceiverStateChangeBeforeAssertion(ownerFunction.body, findReceiver, maximumEnd, assertion, context)) return false;
|
|
75376
|
-
return isPresenceProvenBeforeNode(assertion, (test) => {
|
|
75377
|
-
const comparison = stripParenExpression(test);
|
|
75378
|
-
if (!isNodeOfType(comparison, "BinaryExpression")) return false;
|
|
75379
|
-
return [[
|
|
75380
|
-
comparison.left,
|
|
75381
|
-
comparison.right,
|
|
75382
|
-
comparison.operator
|
|
75383
|
-
], [
|
|
75384
|
-
comparison.right,
|
|
75385
|
-
comparison.left,
|
|
75386
|
-
comparison.operator === "<" ? ">" : comparison.operator === ">" ? "<" : comparison.operator
|
|
75387
|
-
]].some(([candidateMaximum, candidateInitial, operator]) => {
|
|
75388
|
-
const candidateMaximumIdentifier = stripParenExpression(candidateMaximum);
|
|
75389
|
-
return operator === ">" && isNodeOfType(candidateMaximumIdentifier, "Identifier") && context.scopes.symbolFor(candidateMaximumIdentifier)?.id === maximumSymbol.id && areNodesLooselyEqual(stripParenExpression(candidateInitial), initialValue);
|
|
75390
|
-
});
|
|
75391
|
-
});
|
|
75392
|
-
};
|
|
75393
74962
|
const isDefinitelyNonNullishMapValue = (value) => {
|
|
75394
74963
|
if (!value) return false;
|
|
75395
74964
|
const expression = stripParenExpression(value);
|
|
@@ -75413,15 +74982,15 @@ const unwrapFalseBooleanGuard = (test) => {
|
|
|
75413
74982
|
};
|
|
75414
74983
|
const isEnsureThenMapGet = (assertion, receiver, lookupKey, context) => {
|
|
75415
74984
|
const stableLookupKey = stripParenExpression(lookupKey);
|
|
75416
|
-
|
|
75417
|
-
const lookupKeySymbol = isNodeOfType(stableLookupKey, "Identifier") ? context.scopes.symbolFor(stableLookupKey) : lookupKeyRoot ? context.scopes.symbolFor(lookupKeyRoot) : null;
|
|
75418
|
-
if (!isNodeOfType(stableLookupKey, "Identifier") && !isNodeOfType(stableLookupKey, "Literal") && (!isNodeOfType(stableLookupKey, "MemberExpression") || !lookupKeyRoot || lookupKeySymbol?.kind !== "const")) return false;
|
|
74985
|
+
if (!isNodeOfType(stableLookupKey, "Identifier") && !isNodeOfType(stableLookupKey, "Literal")) return false;
|
|
75419
74986
|
const receiverSymbol = context.scopes.symbolFor(receiver);
|
|
75420
74987
|
if (!receiverSymbol) return false;
|
|
75421
74988
|
const receiverMatches = (candidate) => {
|
|
75422
74989
|
const target = stripParenExpression(candidate);
|
|
75423
74990
|
return isNodeOfType(target, "Identifier") && context.scopes.symbolFor(target)?.id === receiverSymbol.id;
|
|
75424
74991
|
};
|
|
74992
|
+
const lookupKeyExpression = stripParenExpression(lookupKey);
|
|
74993
|
+
const lookupKeySymbol = isNodeOfType(lookupKeyExpression, "Identifier") ? context.scopes.symbolFor(lookupKeyExpression) : null;
|
|
75425
74994
|
let child = assertion;
|
|
75426
74995
|
let ancestor = assertion.parent ?? null;
|
|
75427
74996
|
while (ancestor && !isFunctionLike$1(ancestor)) {
|
|
@@ -75441,7 +75010,7 @@ const isEnsureThenMapGet = (assertion, receiver, lookupKey, context) => {
|
|
|
75441
75010
|
const populationCall = populationCalls[0];
|
|
75442
75011
|
if (!populationCall) continue;
|
|
75443
75012
|
const populationCallStart = populationCall.range[0];
|
|
75444
|
-
if (Boolean(lookupKeySymbol?.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] > populationCallStart && reference.identifier.range[0] < assertion.range[0]))
|
|
75013
|
+
if (Boolean(lookupKeySymbol?.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] > populationCallStart && reference.identifier.range[0] < assertion.range[0]))) continue;
|
|
75445
75014
|
if (receiverSymbol.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] > populationCallStart && reference.identifier.range[0] < assertion.range[0])) continue;
|
|
75446
75015
|
if (!indexedRelevantCalls(ancestor).some((laterCall) => {
|
|
75447
75016
|
if (laterCall.range[0] <= populationCallStart || laterCall.range[0] >= assertion.range[0] || !isNodeOfType(laterCall.callee, "MemberExpression") || !receiverMatches(laterCall.callee.object)) return false;
|
|
@@ -75551,7 +75120,6 @@ const noNonNullAssertionOnMaybeUndefinedResult = defineRule({
|
|
|
75551
75120
|
const findReceiver = callee.object;
|
|
75552
75121
|
if (predicate && isExhaustiveLiteralTupleMapping(findReceiver, predicate, context)) return;
|
|
75553
75122
|
if (predicate && scopeProvesFindMatch(node, findReceiver, predicate, context)) return;
|
|
75554
|
-
if (predicate && isFindProvenByGuardedMaximum(node, findReceiver, predicate, context)) return;
|
|
75555
75123
|
if (predicate && isEnsureThenFind(node, findReceiver, predicate)) return;
|
|
75556
75124
|
}
|
|
75557
75125
|
if (methodName === "match") {
|
|
@@ -75561,7 +75129,6 @@ const noNonNullAssertionOnMaybeUndefinedResult = defineRule({
|
|
|
75561
75129
|
const regexKey = pattern ? regexComparableKey(pattern, context) : null;
|
|
75562
75130
|
if (pattern && isGuardedAnchoredCharacterMatch(node, matchReceiver, pattern)) return;
|
|
75563
75131
|
if (pattern && regexKey && isMatchProvenByFindUpUntilPredicate(node, matchReceiver, pattern, context)) return;
|
|
75564
|
-
if (pattern && regexKey && isMatchProvenByNormalizedFindUpUntilPredicate(node, matchReceiver, pattern, context)) return;
|
|
75565
75132
|
if (regexKey && scopeProvesMatchTested(node, regexKey, matchReceiver, context)) return;
|
|
75566
75133
|
}
|
|
75567
75134
|
if (methodName === "get") {
|