oxlint-plugin-react-doctor 0.9.2-dev.c126684 → 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 +156 -1170
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -665,6 +665,19 @@ const TRIVIAL_INITIALIZER_NAMES = new Set([
|
|
|
665
665
|
"parseInt",
|
|
666
666
|
"parseFloat"
|
|
667
667
|
]);
|
|
668
|
+
const TRIVIAL_CONSTRUCTOR_NAMES = new Set([
|
|
669
|
+
"Date",
|
|
670
|
+
"Map",
|
|
671
|
+
"Set",
|
|
672
|
+
"WeakMap",
|
|
673
|
+
"WeakSet",
|
|
674
|
+
"WeakRef",
|
|
675
|
+
"RegExp",
|
|
676
|
+
"Error",
|
|
677
|
+
"URL",
|
|
678
|
+
"URLSearchParams",
|
|
679
|
+
"AbortController"
|
|
680
|
+
]);
|
|
668
681
|
const SETTER_PATTERN = /^set[A-Z]/;
|
|
669
682
|
const RENDER_FUNCTION_PATTERN = /^render[A-Z]/;
|
|
670
683
|
const UPPERCASE_PATTERN = /^[A-Z]/;
|
|
@@ -17999,14 +18012,6 @@ const findRenderPhaseComponentOrHook = (node, scopes) => {
|
|
|
17999
18012
|
//#region src/plugin/utils/is-event-handler-attribute.ts
|
|
18000
18013
|
const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && /^on[A-Z]/.test(node.name.name);
|
|
18001
18014
|
//#endregion
|
|
18002
|
-
//#region src/plugin/utils/is-early-exit-statement.ts
|
|
18003
|
-
const isEarlyExitStatement$1 = (statement) => {
|
|
18004
|
-
if (!statement) return false;
|
|
18005
|
-
if (statementAlwaysExits$1(statement)) return true;
|
|
18006
|
-
if (isNodeOfType(statement, "BlockStatement")) return isEarlyExitStatement$1(statement.body.at(-1));
|
|
18007
|
-
return isNodeOfType(statement, "ContinueStatement") || isNodeOfType(statement, "BreakStatement");
|
|
18008
|
-
};
|
|
18009
|
-
//#endregion
|
|
18010
18015
|
//#region src/plugin/utils/is-ast-descendant.ts
|
|
18011
18016
|
/**
|
|
18012
18017
|
* True when `inner` is `outer` itself or any descendant in the AST
|
|
@@ -18225,15 +18230,13 @@ const isSynchronousIteratorCall = (callNode, callbackArgument, scopes) => {
|
|
|
18225
18230
|
}
|
|
18226
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)));
|
|
18227
18232
|
};
|
|
18228
|
-
const isSynchronousIteratorCallbackCall = (callNode, callbackArgument) => {
|
|
18229
|
-
const callee = stripParenExpression(callNode.callee);
|
|
18230
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
|
|
18231
|
-
if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments[1] === callbackArgument;
|
|
18232
|
-
return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments[0] === callbackArgument;
|
|
18233
|
-
};
|
|
18234
18233
|
const isSynchronousIteratorCallback = (functionNode) => {
|
|
18235
18234
|
const callNode = functionNode.parent;
|
|
18236
|
-
|
|
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;
|
|
18237
18240
|
};
|
|
18238
18241
|
//#endregion
|
|
18239
18242
|
//#region src/plugin/utils/is-within-assignment-target.ts
|
|
@@ -18353,35 +18356,11 @@ const resolveEventListenerCaptureValueIdentityKey = (expression, context) => {
|
|
|
18353
18356
|
const rightIdentityKey = resolveEventListenerCaptureValueIdentityKey(unwrappedExpression.right, context);
|
|
18354
18357
|
return leftIdentityKey && rightIdentityKey ? `${unwrappedExpression.type}:${unwrappedExpression.operator}:${leftIdentityKey}:${rightIdentityKey}` : null;
|
|
18355
18358
|
};
|
|
18356
|
-
const resolveReadOnlyEventListenerOptions = (optionsNode, context) => {
|
|
18357
|
-
const unwrappedOptions = stripParenExpression(optionsNode);
|
|
18358
|
-
if (!isNodeOfType(unwrappedOptions, "Identifier")) return resolveStableValue(unwrappedOptions, context);
|
|
18359
|
-
const optionsSymbol = context.scopes.symbolFor(unwrappedOptions);
|
|
18360
|
-
const initializer = optionsSymbol?.initializer ? stripParenExpression(optionsSymbol.initializer) : null;
|
|
18361
|
-
if (!optionsSymbol || !initializer) return resolveStableValue(unwrappedOptions, context);
|
|
18362
|
-
if (!isNodeOfType(initializer, "ObjectExpression")) {
|
|
18363
|
-
if (isNodeOfType(initializer, "Identifier") || isNodeOfType(initializer, "MemberExpression")) return null;
|
|
18364
|
-
return resolveStableValue(unwrappedOptions, context);
|
|
18365
|
-
}
|
|
18366
|
-
if (optionsSymbol.kind !== "const") return null;
|
|
18367
|
-
return optionsSymbol.references.every((reference) => {
|
|
18368
|
-
if (reference.flag !== "read" || isWithinAssignmentTarget(reference.identifier)) return false;
|
|
18369
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
18370
|
-
const callNode = referenceRoot.parent;
|
|
18371
|
-
if (!isNodeOfType(callNode, "CallExpression") || callNode.arguments[2] !== referenceRoot) return false;
|
|
18372
|
-
const callee = stripParenExpression(callNode.callee);
|
|
18373
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
18374
|
-
const methodName = getStaticPropertyKeyName(callee);
|
|
18375
|
-
return methodName === "addEventListener" || methodName === "removeEventListener";
|
|
18376
|
-
}) ? initializer : null;
|
|
18377
|
-
};
|
|
18378
18359
|
const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqueOptionsIdentity) => {
|
|
18379
|
-
const
|
|
18380
|
-
if (optionsNode && !stableOptionsNode) return null;
|
|
18381
|
-
const capture = resolveEventListenerCapture(stableOptionsNode, { allowIndeterminateEntries: true });
|
|
18360
|
+
const capture = resolveEventListenerCapture(optionsNode, { allowIndeterminateEntries: true });
|
|
18382
18361
|
if (capture !== null) return `capture:${String(capture)}`;
|
|
18383
|
-
if (!
|
|
18384
|
-
const unwrappedOptions = stripParenExpression(
|
|
18362
|
+
if (!optionsNode) return null;
|
|
18363
|
+
const unwrappedOptions = stripParenExpression(optionsNode);
|
|
18385
18364
|
if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) {
|
|
18386
18365
|
const optionsKey = allowOpaqueOptionsIdentity ? resolveEventListenerCaptureValueIdentityKey(unwrappedOptions, context) : null;
|
|
18387
18366
|
return optionsKey ? `options:${optionsKey}` : null;
|
|
@@ -18428,8 +18407,12 @@ const doEventListenerCapturesMatch = (registrationOptions, releaseOptions, conte
|
|
|
18428
18407
|
return registrationCaptureKey !== null && registrationCaptureKey === resolveEventListenerCaptureIdentityKey(releaseOptions, context, allowOpaqueOptionsIdentity);
|
|
18429
18408
|
};
|
|
18430
18409
|
const findAssignedResourceKey = (resourceNode, context) => {
|
|
18431
|
-
|
|
18432
|
-
|
|
18410
|
+
let currentNode = resourceNode;
|
|
18411
|
+
let parentNode = currentNode.parent;
|
|
18412
|
+
while (isNodeOfType(parentNode, "ChainExpression")) {
|
|
18413
|
+
currentNode = parentNode;
|
|
18414
|
+
parentNode = currentNode.parent;
|
|
18415
|
+
}
|
|
18433
18416
|
if (isNodeOfType(parentNode, "VariableDeclarator") && parentNode.init === currentNode) return resolveExpressionKey(parentNode.id, context);
|
|
18434
18417
|
if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.right === currentNode) return resolveExpressionKey(parentNode.left, context);
|
|
18435
18418
|
return null;
|
|
@@ -18700,18 +18683,6 @@ const resolveIteratorCollectionKey = (expression, context) => {
|
|
|
18700
18683
|
}
|
|
18701
18684
|
return null;
|
|
18702
18685
|
};
|
|
18703
|
-
const resolveReceiverIteratorCollectionKey = (expression, context) => {
|
|
18704
|
-
if (!expression) return null;
|
|
18705
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
18706
|
-
if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
|
|
18707
|
-
const collectionExpression = findForOfStatementForIteratorExpression(unwrappedExpression, context)?.right;
|
|
18708
|
-
if (!collectionExpression) return null;
|
|
18709
|
-
const collectionIdentifier = stripParenExpression(collectionExpression);
|
|
18710
|
-
if (!isNodeOfType(collectionIdentifier, "Identifier") || !isPrivatePlainConstIdentifier(collectionIdentifier, context)) return null;
|
|
18711
|
-
const collectionSymbol = context.scopes.symbolFor(collectionIdentifier);
|
|
18712
|
-
const initializer = collectionSymbol?.initializer ? stripParenExpression(collectionSymbol.initializer) : null;
|
|
18713
|
-
return collectionSymbol && isNodeOfType(initializer, "ArrayExpression") && hasOnlyReplayableCollectionReferences(collectionIdentifier, context, /* @__PURE__ */ new Set()) ? `symbol:${collectionSymbol.id}` : null;
|
|
18714
|
-
};
|
|
18715
18686
|
const isStableLoopReceiver = (expression, context) => {
|
|
18716
18687
|
if (!expression) return false;
|
|
18717
18688
|
const unwrappedExpression = stripParenExpression(expression);
|
|
@@ -19482,28 +19453,6 @@ const isFunctionReturnedFromReactHook = (functionNode, context, requireRefProper
|
|
|
19482
19453
|
});
|
|
19483
19454
|
};
|
|
19484
19455
|
const isFunctionUsedAsReactRef = (functionNode, context) => isFunctionForwardedToReactRef(functionNode, context) || isFunctionReturnedFromReactHook(functionNode, context, true);
|
|
19485
|
-
const findCallbackRefReplacementReleaseGuard = (releaseCall, ownerFunction, releaseReceiverKey, registrationReceiverKey, context) => {
|
|
19486
|
-
let descendant = releaseCall;
|
|
19487
|
-
let ancestor = descendant.parent;
|
|
19488
|
-
while (ancestor && ancestor !== ownerFunction) {
|
|
19489
|
-
if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant && ancestor.alternate === null) {
|
|
19490
|
-
const test = stripParenExpression(ancestor.test);
|
|
19491
|
-
if (!isNodeOfType(test, "LogicalExpression") || test.operator !== "&&") return null;
|
|
19492
|
-
const operands = [stripParenExpression(test.left), stripParenExpression(test.right)];
|
|
19493
|
-
const hasLiveReceiverTest = operands.some((operand) => doesTestRequireLiveExpressionKey(operand, releaseReceiverKey, context));
|
|
19494
|
-
const hasDifferentReceiverTest = operands.some((operand) => {
|
|
19495
|
-
if (!isNodeOfType(operand, "BinaryExpression") || operand.operator !== "!==" && operand.operator !== "!=") return false;
|
|
19496
|
-
const leftKey = resolveExpressionKey(operand.left, context);
|
|
19497
|
-
const rightKey = resolveExpressionKey(operand.right, context);
|
|
19498
|
-
return leftKey === releaseReceiverKey && rightKey === registrationReceiverKey || rightKey === releaseReceiverKey && leftKey === registrationReceiverKey;
|
|
19499
|
-
});
|
|
19500
|
-
return hasLiveReceiverTest && hasDifferentReceiverTest ? ancestor : null;
|
|
19501
|
-
}
|
|
19502
|
-
descendant = ancestor;
|
|
19503
|
-
ancestor = descendant.parent;
|
|
19504
|
-
}
|
|
19505
|
-
return null;
|
|
19506
|
-
};
|
|
19507
19456
|
const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
|
|
19508
19457
|
if (!isNodeOfType(usage.node, "CallExpression")) return false;
|
|
19509
19458
|
const usageFunction = findEnclosingFunction$1(usage.node);
|
|
@@ -19524,7 +19473,7 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
|
|
|
19524
19473
|
if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
|
|
19525
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);
|
|
19526
19475
|
});
|
|
19527
|
-
const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ??
|
|
19476
|
+
const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
|
|
19528
19477
|
const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
|
|
19529
19478
|
return doNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
|
|
19530
19479
|
};
|
|
@@ -19674,21 +19623,14 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
19674
19623
|
if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
|
|
19675
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;
|
|
19676
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;
|
|
19677
19627
|
if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
|
|
19678
|
-
if (usage.registrationVerbName === "addListener" && releaseVerbName === "removeListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1) {
|
|
19679
|
-
if (callNode.arguments?.length !== 1) return false;
|
|
19680
|
-
const registrationHandler = resolveStableValue(usage.node.arguments[0], context);
|
|
19681
|
-
if (!isProvenLegacyMediaQueryListMethodCall(usage.node, "addListener", context) && !isFunctionLike$1(registrationHandler)) return false;
|
|
19682
|
-
}
|
|
19683
19628
|
if (usage.registrationVerbName === "addEventListener" && releaseVerbName === "removeEventListener" && isNodeOfType(usage.node, "CallExpression")) {
|
|
19684
19629
|
if (!isNodeOfType(stripParenExpression(usage.node.callee), "MemberExpression")) return false;
|
|
19685
19630
|
if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], callNode.arguments?.[2], context, true)) return false;
|
|
19686
19631
|
}
|
|
19687
19632
|
if (isNodeOfType(usage.node, "CallExpression") && !hasSafeForEachProjectionCleanup(usage.node, callNode, context)) return false;
|
|
19688
|
-
|
|
19689
|
-
const registrationReceiverCollectionKey = isNodeOfType(registrationCallee, "MemberExpression") ? resolveReceiverIteratorCollectionKey(registrationCallee.object, context) : null;
|
|
19690
|
-
const releaseReceiverCollectionKeyForPair = resolveReceiverIteratorCollectionKey(callee.object, context);
|
|
19691
|
-
if (!(registrationReceiverCollectionKey !== null && registrationReceiverCollectionKey === releaseReceiverCollectionKeyForPair) && (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey)) return false;
|
|
19633
|
+
if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) return false;
|
|
19692
19634
|
if (usage.registrationVerbName === "subscribe" && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub") && usage.handleKey !== null && resolveExpressionKey(callNode.arguments?.[0], context) === usage.handleKey) return true;
|
|
19693
19635
|
const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
|
|
19694
19636
|
if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
|
|
@@ -19725,7 +19667,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
|
|
|
19725
19667
|
const usesUnaryListenerSignatureForCalls = isNodeOfType(usage.node, "CallExpression") && usesUnaryListenerSignature(usage.node, callNode);
|
|
19726
19668
|
const releaseHandler = usesUnaryListenerSignatureForCalls ? callNode.arguments?.[0] : callNode.arguments?.[1];
|
|
19727
19669
|
if (!releaseHandler) return releaseVerbName === "off";
|
|
19728
|
-
const expectedHandlerKey = usesUnaryListenerSignatureForCalls ? usage.
|
|
19670
|
+
const expectedHandlerKey = usesUnaryListenerSignatureForCalls ? usage.eventKey : usage.handlerKey;
|
|
19729
19671
|
const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignatureForCalls ? 0 : 1] : null;
|
|
19730
19672
|
return expectedHandlerKey !== null && resolveResourceIdentityKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
|
|
19731
19673
|
}
|
|
@@ -20199,7 +20141,6 @@ const findRetainedFunctionLeak = (retainedFunction, context, options) => {
|
|
|
20199
20141
|
walkAst(body, (child) => {
|
|
20200
20142
|
if (leak !== null) return false;
|
|
20201
20143
|
if (isFunctionLike$1(child)) return false;
|
|
20202
|
-
if (!isNodeReachableWithinFunction(child, context)) return false;
|
|
20203
20144
|
if (isSocketConstruction(child) && !doesResourceResultEscape(child, allowReturnedSocketEscape, false, context)) {
|
|
20204
20145
|
const socketUsage = {
|
|
20205
20146
|
kind: "socket",
|
|
@@ -20452,164 +20393,6 @@ const isInlineRetainedHandlerFunction = (functionNode, context) => {
|
|
|
20452
20393
|
const objectParent = objectExpression.parent;
|
|
20453
20394
|
return (isNodeOfType(objectParent, "CallExpression") && objectParent.arguments.some((argument) => argument === objectExpression) || isNodeOfType(objectParent, "JSXExpressionContainer")) && findRenderPhaseComponentOrHook(parentNode, context.scopes) !== null;
|
|
20454
20395
|
};
|
|
20455
|
-
const readInvocationArgumentValue = (expression, context) => {
|
|
20456
|
-
if (!expression) return {
|
|
20457
|
-
isDefinitelyUndefined: true,
|
|
20458
|
-
truthiness: "falsy"
|
|
20459
|
-
};
|
|
20460
|
-
const target = stripParenExpression(expression);
|
|
20461
|
-
if (isNodeOfType(target, "Literal")) return {
|
|
20462
|
-
isDefinitelyUndefined: false,
|
|
20463
|
-
truthiness: target.value ? "truthy" : "falsy"
|
|
20464
|
-
};
|
|
20465
|
-
if (isNodeOfType(target, "Identifier") && target.name === "undefined" && context.scopes.isGlobalReference(target)) return {
|
|
20466
|
-
isDefinitelyUndefined: true,
|
|
20467
|
-
truthiness: "falsy"
|
|
20468
|
-
};
|
|
20469
|
-
if (isNodeOfType(target, "UnaryExpression") && target.operator === "void") return {
|
|
20470
|
-
isDefinitelyUndefined: true,
|
|
20471
|
-
truthiness: "falsy"
|
|
20472
|
-
};
|
|
20473
|
-
if (isNodeOfType(target, "ArrayExpression") || isNodeOfType(target, "ArrowFunctionExpression") || isNodeOfType(target, "ClassExpression") || isNodeOfType(target, "FunctionExpression") || isNodeOfType(target, "NewExpression") || isNodeOfType(target, "ObjectExpression")) return {
|
|
20474
|
-
isDefinitelyUndefined: false,
|
|
20475
|
-
truthiness: "truthy"
|
|
20476
|
-
};
|
|
20477
|
-
return {
|
|
20478
|
-
isDefinitelyUndefined: false,
|
|
20479
|
-
truthiness: "unknown"
|
|
20480
|
-
};
|
|
20481
|
-
};
|
|
20482
|
-
const readInvocationConditionTruthiness = (expression, parameterValues, context) => {
|
|
20483
|
-
const target = stripParenExpression(expression);
|
|
20484
|
-
const atomicValue = readInvocationArgumentValue(target, context);
|
|
20485
|
-
if (atomicValue.truthiness !== "unknown") return atomicValue.truthiness;
|
|
20486
|
-
if (isNodeOfType(target, "Identifier")) {
|
|
20487
|
-
const symbol = context.scopes.symbolFor(target);
|
|
20488
|
-
return symbol ? parameterValues.get(symbol.id)?.truthiness ?? "unknown" : "unknown";
|
|
20489
|
-
}
|
|
20490
|
-
if (isNodeOfType(target, "UnaryExpression") && target.operator === "!") {
|
|
20491
|
-
const argumentTruthiness = readInvocationConditionTruthiness(target.argument, parameterValues, context);
|
|
20492
|
-
return argumentTruthiness === "truthy" ? "falsy" : argumentTruthiness === "falsy" ? "truthy" : "unknown";
|
|
20493
|
-
}
|
|
20494
|
-
if (isNodeOfType(target, "LogicalExpression")) {
|
|
20495
|
-
const leftTruthiness = readInvocationConditionTruthiness(target.left, parameterValues, context);
|
|
20496
|
-
const rightTruthiness = readInvocationConditionTruthiness(target.right, parameterValues, context);
|
|
20497
|
-
if (target.operator === "&&") {
|
|
20498
|
-
if (leftTruthiness === "falsy" || rightTruthiness === "falsy") return "falsy";
|
|
20499
|
-
return leftTruthiness === "truthy" && rightTruthiness === "truthy" ? "truthy" : "unknown";
|
|
20500
|
-
}
|
|
20501
|
-
if (target.operator === "||") {
|
|
20502
|
-
if (leftTruthiness === "truthy" || rightTruthiness === "truthy") return "truthy";
|
|
20503
|
-
return leftTruthiness === "falsy" && rightTruthiness === "falsy" ? "falsy" : "unknown";
|
|
20504
|
-
}
|
|
20505
|
-
return "unknown";
|
|
20506
|
-
}
|
|
20507
|
-
if (isNodeOfType(target, "ConditionalExpression")) {
|
|
20508
|
-
const testTruthiness = readInvocationConditionTruthiness(target.test, parameterValues, context);
|
|
20509
|
-
if (testTruthiness === "truthy") return readInvocationConditionTruthiness(target.consequent, parameterValues, context);
|
|
20510
|
-
if (testTruthiness === "falsy") return readInvocationConditionTruthiness(target.alternate, parameterValues, context);
|
|
20511
|
-
const consequentTruthiness = readInvocationConditionTruthiness(target.consequent, parameterValues, context);
|
|
20512
|
-
return consequentTruthiness === readInvocationConditionTruthiness(target.alternate, parameterValues, context) ? consequentTruthiness : "unknown";
|
|
20513
|
-
}
|
|
20514
|
-
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);
|
|
20515
|
-
return "unknown";
|
|
20516
|
-
};
|
|
20517
|
-
const getInvocationParameterValues = (retainedFunction, invocation, leakNode, context) => {
|
|
20518
|
-
const parameterValues = /* @__PURE__ */ new Map();
|
|
20519
|
-
if (!isFunctionLike$1(retainedFunction) || !invocation.isDirect) return parameterValues;
|
|
20520
|
-
for (const [parameterIndex, parameter] of retainedFunction.params.entries()) {
|
|
20521
|
-
const argument = invocation.call.arguments[parameterIndex];
|
|
20522
|
-
const argumentExpression = argument && isAstNode(argument) ? argument : null;
|
|
20523
|
-
let parameterIdentifier = null;
|
|
20524
|
-
let parameterValue = readInvocationArgumentValue(argumentExpression, context);
|
|
20525
|
-
if (isNodeOfType(parameter, "Identifier")) parameterIdentifier = parameter;
|
|
20526
|
-
else if (isNodeOfType(parameter, "AssignmentPattern") && isNodeOfType(parameter.left, "Identifier")) {
|
|
20527
|
-
parameterIdentifier = parameter.left;
|
|
20528
|
-
if (parameterValue.isDefinitelyUndefined) parameterValue = readInvocationArgumentValue(parameter.right, context);
|
|
20529
|
-
} else if (isNodeOfType(parameter, "RestElement") && isNodeOfType(parameter.argument, "Identifier")) {
|
|
20530
|
-
parameterIdentifier = parameter.argument;
|
|
20531
|
-
parameterValue = {
|
|
20532
|
-
isDefinitelyUndefined: false,
|
|
20533
|
-
truthiness: "truthy"
|
|
20534
|
-
};
|
|
20535
|
-
}
|
|
20536
|
-
if (!parameterIdentifier) continue;
|
|
20537
|
-
const parameterSymbol = context.scopes.symbolFor(parameterIdentifier);
|
|
20538
|
-
if (!parameterSymbol) continue;
|
|
20539
|
-
const isWrittenBeforeLeak = parameterSymbol.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] < leakNode.range[0]);
|
|
20540
|
-
parameterValues.set(parameterSymbol.id, isWrittenBeforeLeak ? {
|
|
20541
|
-
isDefinitelyUndefined: false,
|
|
20542
|
-
truthiness: "unknown"
|
|
20543
|
-
} : parameterValue);
|
|
20544
|
-
}
|
|
20545
|
-
return parameterValues;
|
|
20546
|
-
};
|
|
20547
|
-
const isLeakPathDisabledForInvocation = (retainedFunction, leakNode, invocation, context) => {
|
|
20548
|
-
if (!invocation.isDirect) return false;
|
|
20549
|
-
const parameterValues = getInvocationParameterValues(retainedFunction, invocation, leakNode, context);
|
|
20550
|
-
let child = leakNode;
|
|
20551
|
-
let ancestor = leakNode.parent ?? null;
|
|
20552
|
-
while (ancestor && ancestor !== retainedFunction) {
|
|
20553
|
-
if (isNodeOfType(ancestor, "BlockStatement")) {
|
|
20554
|
-
const childIndex = ancestor.body.findIndex((statement) => statement === child);
|
|
20555
|
-
for (const precedingStatement of ancestor.body.slice(0, childIndex)) {
|
|
20556
|
-
if (!isNodeOfType(precedingStatement, "IfStatement") || precedingStatement.alternate || !isEarlyExitStatement$1(precedingStatement.consequent)) continue;
|
|
20557
|
-
if (readInvocationConditionTruthiness(precedingStatement.test, parameterValues, context) === "truthy") return true;
|
|
20558
|
-
}
|
|
20559
|
-
}
|
|
20560
|
-
let requiredTruthiness = null;
|
|
20561
|
-
let condition = null;
|
|
20562
|
-
if (isNodeOfType(ancestor, "IfStatement")) {
|
|
20563
|
-
condition = ancestor.test;
|
|
20564
|
-
requiredTruthiness = ancestor.consequent === child ? "truthy" : "falsy";
|
|
20565
|
-
} else if (isNodeOfType(ancestor, "ConditionalExpression")) {
|
|
20566
|
-
condition = ancestor.test;
|
|
20567
|
-
requiredTruthiness = ancestor.consequent === child ? "truthy" : "falsy";
|
|
20568
|
-
} else if (isNodeOfType(ancestor, "LogicalExpression") && ancestor.right === child && ancestor.operator !== "??") {
|
|
20569
|
-
condition = ancestor.left;
|
|
20570
|
-
requiredTruthiness = ancestor.operator === "&&" ? "truthy" : "falsy";
|
|
20571
|
-
} else if ((isNodeOfType(ancestor, "WhileStatement") || isNodeOfType(ancestor, "DoWhileStatement")) && ancestor.body === child) {
|
|
20572
|
-
condition = ancestor.test;
|
|
20573
|
-
requiredTruthiness = "truthy";
|
|
20574
|
-
} else if (isNodeOfType(ancestor, "ForStatement") && ancestor.body === child && ancestor.test) {
|
|
20575
|
-
condition = ancestor.test;
|
|
20576
|
-
requiredTruthiness = "truthy";
|
|
20577
|
-
}
|
|
20578
|
-
if (condition && requiredTruthiness) {
|
|
20579
|
-
const conditionTruthiness = readInvocationConditionTruthiness(condition, parameterValues, context);
|
|
20580
|
-
if (conditionTruthiness !== "unknown" && conditionTruthiness !== requiredTruthiness) return true;
|
|
20581
|
-
}
|
|
20582
|
-
child = ancestor;
|
|
20583
|
-
ancestor = ancestor.parent ?? null;
|
|
20584
|
-
}
|
|
20585
|
-
return false;
|
|
20586
|
-
};
|
|
20587
|
-
const getEffectRetainedInvocations = (retainedFunction, context) => {
|
|
20588
|
-
if (!isFunctionLike$1(retainedFunction)) return [];
|
|
20589
|
-
const componentFunction = findEnclosingFunction$1(retainedFunction);
|
|
20590
|
-
if (!componentFunction || !isFunctionLike$1(componentFunction)) return [];
|
|
20591
|
-
const invocations = [];
|
|
20592
|
-
walkAst(componentFunction.body, (child) => {
|
|
20593
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactHookCall(child, CLEANUP_EFFECT_HOOK_NAMES, context.scopes)) return;
|
|
20594
|
-
const effectCallback = getEffectCallback(child);
|
|
20595
|
-
if (!effectCallback || !isFunctionLike$1(effectCallback)) return;
|
|
20596
|
-
walkAst(effectCallback.body, (effectChild) => {
|
|
20597
|
-
if (effectChild !== effectCallback.body && isFunctionLike$1(effectChild)) return false;
|
|
20598
|
-
if (!isNodeOfType(effectChild, "CallExpression") || !isNodeReachableWithinFunction(effectChild, context)) return;
|
|
20599
|
-
const isDirectInvocation = resolveRefOwnedCleanupFunction(effectChild.callee, context) === retainedFunction;
|
|
20600
|
-
const isSynchronousIteratorInvocation = effectChild.arguments.some((argument) => isAstNode(argument) && resolveRefOwnedCleanupFunction(argument, context) === retainedFunction && isSynchronousIteratorCallbackCall(effectChild, argument));
|
|
20601
|
-
if (isDirectInvocation) invocations.push({
|
|
20602
|
-
call: effectChild,
|
|
20603
|
-
isDirect: true
|
|
20604
|
-
});
|
|
20605
|
-
if (isSynchronousIteratorInvocation) invocations.push({
|
|
20606
|
-
call: effectChild,
|
|
20607
|
-
isDirect: false
|
|
20608
|
-
});
|
|
20609
|
-
});
|
|
20610
|
-
});
|
|
20611
|
-
return invocations;
|
|
20612
|
-
};
|
|
20613
20396
|
const effectNeedsCleanup = defineRule({
|
|
20614
20397
|
id: "effect-needs-cleanup",
|
|
20615
20398
|
title: "Effect subscription or timer never cleaned up",
|
|
@@ -20620,19 +20403,13 @@ const effectNeedsCleanup = defineRule({
|
|
|
20620
20403
|
const reportRetainedLeak = (retainedFunction) => {
|
|
20621
20404
|
const refEffectUsage = getReactRefEffectUsage(retainedFunction, context);
|
|
20622
20405
|
if (!refEffectUsage && !isPotentiallyReachableFunction(retainedFunction, context)) return;
|
|
20623
|
-
const effectInvocations = getEffectRetainedInvocations(retainedFunction, context);
|
|
20624
|
-
const isEffectInvoked = effectInvocations.length > 0;
|
|
20625
20406
|
const leak = findRetainedFunctionLeak(retainedFunction, context, refEffectUsage ? {
|
|
20626
20407
|
allowReturnedResourceEscape: refEffectUsage.doesEffectOwnEveryResult,
|
|
20627
20408
|
allowReturnedTimerEscape: false,
|
|
20628
20409
|
includeOneShotTimers: true,
|
|
20629
20410
|
requireCallableReturnedResource: true
|
|
20630
|
-
} : isEffectInvoked ? {
|
|
20631
|
-
allowReturnedTimerEscape: false,
|
|
20632
|
-
includeOneShotTimers: true
|
|
20633
20411
|
} : void 0);
|
|
20634
20412
|
if (!leak) return;
|
|
20635
|
-
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;
|
|
20636
20413
|
const resourceNoun = RESOURCE_NOUN_BY_KIND[leak.kind];
|
|
20637
20414
|
context.report({
|
|
20638
20415
|
node: leak.node,
|
|
@@ -29461,11 +29238,6 @@ const jsCacheStorage = defineRule({
|
|
|
29461
29238
|
});
|
|
29462
29239
|
//#endregion
|
|
29463
29240
|
//#region src/plugin/rules/js-performance/js-combine-iterations.ts
|
|
29464
|
-
const SMALL_ARRAY_NON_MUTATING_METHODS = new Set([
|
|
29465
|
-
...CHAINABLE_ITERATION_METHODS,
|
|
29466
|
-
"find",
|
|
29467
|
-
"some"
|
|
29468
|
-
]);
|
|
29469
29241
|
const isIteratorProducingCall = (callExpression, generatorNamesInFile) => {
|
|
29470
29242
|
const callee = callExpression.callee;
|
|
29471
29243
|
if (isNodeOfType(callee, "MemberExpression")) {
|
|
@@ -29577,34 +29349,21 @@ const isStringSplitRootedChain = (receiverNode) => {
|
|
|
29577
29349
|
return false;
|
|
29578
29350
|
};
|
|
29579
29351
|
const isSmallLiteralArray = (node) => {
|
|
29580
|
-
|
|
29581
|
-
|
|
29582
|
-
|
|
29583
|
-
if (elements.length === 0 || elements.length > 9) return false;
|
|
29352
|
+
if (!isNodeOfType(node, "ArrayExpression")) return false;
|
|
29353
|
+
const elements = node.elements ?? [];
|
|
29354
|
+
if (elements.length === 0 || elements.length > 8) return false;
|
|
29584
29355
|
for (const element of elements) {
|
|
29585
29356
|
if (!element) continue;
|
|
29586
29357
|
if (isNodeOfType(element, "SpreadElement")) return false;
|
|
29587
29358
|
}
|
|
29588
29359
|
return true;
|
|
29589
29360
|
};
|
|
29590
|
-
const
|
|
29591
|
-
const identifierRoot = findTransparentExpressionRoot(identifier);
|
|
29592
|
-
const memberExpression = identifierRoot.parent;
|
|
29593
|
-
if (!isNodeOfType(memberExpression, "MemberExpression") || memberExpression.object !== identifierRoot || !isNodeOfType(memberExpression.property, "Identifier") || !SMALL_ARRAY_NON_MUTATING_METHODS.has(memberExpression.property.name)) return false;
|
|
29594
|
-
const callExpression = memberExpression.parent;
|
|
29595
|
-
return isNodeOfType(callExpression, "CallExpression") && callExpression.callee === memberExpression;
|
|
29596
|
-
};
|
|
29597
|
-
const isSmallLiteralArrayRootedChain = (receiverNode, scopes) => {
|
|
29361
|
+
const isSmallLiteralArrayRootedChain = (receiverNode, smallConstArrayNames) => {
|
|
29598
29362
|
let cursor = receiverNode;
|
|
29599
29363
|
while (cursor) {
|
|
29600
29364
|
cursor = stripParenExpression(cursor);
|
|
29601
29365
|
if (isNodeOfType(cursor, "ArrayExpression")) return isSmallLiteralArray(cursor);
|
|
29602
|
-
if (isNodeOfType(cursor, "Identifier"))
|
|
29603
|
-
const symbol = scopes.symbolFor(cursor);
|
|
29604
|
-
if (!symbol?.initializer || !isSmallLiteralArray(symbol.initializer)) return false;
|
|
29605
|
-
if (!isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier")) return false;
|
|
29606
|
-
return (symbol.kind === "const" || symbol.kind === "let" || symbol.kind === "var") && symbol.references.every((reference) => reference.flag === "read" && isNonMutatingSmallArrayMethodReference(reference.identifier));
|
|
29607
|
-
}
|
|
29366
|
+
if (isNodeOfType(cursor, "Identifier")) return smallConstArrayNames.has(cursor.name);
|
|
29608
29367
|
if (!isNodeOfType(cursor, "CallExpression")) return false;
|
|
29609
29368
|
if (!isChainPassThroughCall(cursor)) return false;
|
|
29610
29369
|
const nextCallee = cursor.callee;
|
|
@@ -29613,6 +29372,22 @@ const isSmallLiteralArrayRootedChain = (receiverNode, scopes) => {
|
|
|
29613
29372
|
}
|
|
29614
29373
|
return false;
|
|
29615
29374
|
};
|
|
29375
|
+
const collectSmallConstArrayNames = (programNode) => {
|
|
29376
|
+
const names = /* @__PURE__ */ new Set();
|
|
29377
|
+
const statements = programNode.body ?? [];
|
|
29378
|
+
for (const statement of statements) {
|
|
29379
|
+
const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
|
|
29380
|
+
if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) continue;
|
|
29381
|
+
if (declaration.kind !== "const") continue;
|
|
29382
|
+
for (const declarator of declaration.declarations ?? []) {
|
|
29383
|
+
if (!isNodeOfType(declarator, "VariableDeclarator")) continue;
|
|
29384
|
+
if (!isNodeOfType(declarator.id, "Identifier")) continue;
|
|
29385
|
+
if (!declarator.init || !isSmallLiteralArray(declarator.init)) continue;
|
|
29386
|
+
names.add(declarator.id.name);
|
|
29387
|
+
}
|
|
29388
|
+
}
|
|
29389
|
+
return names;
|
|
29390
|
+
};
|
|
29616
29391
|
const collectGeneratorNames = (programNode) => {
|
|
29617
29392
|
const generatorNames = /* @__PURE__ */ new Set();
|
|
29618
29393
|
walkAst(programNode, (child) => {
|
|
@@ -29633,11 +29408,16 @@ const jsCombineIterations = defineRule({
|
|
|
29633
29408
|
create: (context) => {
|
|
29634
29409
|
let programNode = null;
|
|
29635
29410
|
let generatorNamesInFile = null;
|
|
29411
|
+
let smallConstArrayNames = null;
|
|
29636
29412
|
const coveredChainCalls = /* @__PURE__ */ new WeakSet();
|
|
29637
29413
|
const getGeneratorNamesInFile = () => {
|
|
29638
29414
|
generatorNamesInFile ??= programNode ? collectGeneratorNames(programNode) : /* @__PURE__ */ new Set();
|
|
29639
29415
|
return generatorNamesInFile;
|
|
29640
29416
|
};
|
|
29417
|
+
const getSmallConstArrayNames = () => {
|
|
29418
|
+
smallConstArrayNames ??= programNode ? collectSmallConstArrayNames(programNode) : /* @__PURE__ */ new Set();
|
|
29419
|
+
return smallConstArrayNames;
|
|
29420
|
+
};
|
|
29641
29421
|
return {
|
|
29642
29422
|
Program(node) {
|
|
29643
29423
|
programNode = node;
|
|
@@ -29667,7 +29447,7 @@ const jsCombineIterations = defineRule({
|
|
|
29667
29447
|
if (isTypePredicateArrow(filterArgument)) return;
|
|
29668
29448
|
}
|
|
29669
29449
|
if (isReceiverChainIteratorRooted(innerCall.callee.object, getGeneratorNamesInFile())) return;
|
|
29670
|
-
if (isSmallLiteralArrayRootedChain(innerCall.callee.object,
|
|
29450
|
+
if (isSmallLiteralArrayRootedChain(innerCall.callee.object, getSmallConstArrayNames())) return;
|
|
29671
29451
|
if (isStringSplitRootedChain(innerCall.callee.object)) return;
|
|
29672
29452
|
coveredChainCalls.add(innerCall);
|
|
29673
29453
|
context.report({
|
|
@@ -45600,6 +45380,14 @@ const noAriaInvalidWithoutDescription = defineRule({
|
|
|
45600
45380
|
} })
|
|
45601
45381
|
});
|
|
45602
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
|
|
45603
45391
|
//#region src/plugin/utils/unwrap-negative-guard-form.ts
|
|
45604
45392
|
const unwrapNegativeGuardForm = (test) => {
|
|
45605
45393
|
const expression = stripParenExpression(test);
|
|
@@ -56087,9 +55875,6 @@ const isInitialOnlyPropName = (propName) => {
|
|
|
56087
55875
|
return /^initial[A-Z]/.test(propName) || /^default[A-Z]/.test(propName) || /^seed[A-Z]/.test(propName) || /^starting[A-Z]/.test(propName) || /^baseline[A-Z]/.test(propName) || /^preset[A-Z]/.test(propName);
|
|
56088
55876
|
};
|
|
56089
55877
|
//#endregion
|
|
56090
|
-
//#region src/plugin/utils/nextjs-page-data-export-names.ts
|
|
56091
|
-
const NEXTJS_PAGE_DATA_EXPORT_NAMES = new Set(["getServerSideProps", "getStaticProps"]);
|
|
56092
|
-
//#endregion
|
|
56093
55878
|
//#region src/plugin/rules/state-and-effects/no-derived-use-state.ts
|
|
56094
55879
|
const isInitialOnlySeedName = (propName) => isInitialOnlyPropName(propName) || propName === "initial" || propName === "autoFocus" || propName === "autoPlay" || propName === "startOpen" || /^initially[A-Z]/.test(propName) || /Initial([A-Z]|$)/.test(propName);
|
|
56095
55880
|
const SNAPSHOT_STATE_NAME_PATTERN = /^(initial|previous|prev|preserved|saved|original|cached|snapshot|prior|debounced|deferred)([A-Z_]|$)/;
|
|
@@ -56246,6 +56031,7 @@ const isDraftCommittedToParent = (componentFunction, stateValueName, isPropName)
|
|
|
56246
56031
|
});
|
|
56247
56032
|
return isCommitted;
|
|
56248
56033
|
};
|
|
56034
|
+
const NEXTJS_PAGE_DATA_EXPORT_NAMES = new Set(["getServerSideProps", "getStaticProps"]);
|
|
56249
56035
|
const isNextjsDataFetchingPage = (node) => {
|
|
56250
56036
|
const program = findProgramRoot(node);
|
|
56251
56037
|
if (!program) return false;
|
|
@@ -67190,124 +66976,6 @@ const isInsideSnapshotHelper = (node) => {
|
|
|
67190
66976
|
}
|
|
67191
66977
|
return false;
|
|
67192
66978
|
};
|
|
67193
|
-
const findEnclosingNextjsPageDataFunction = (node) => {
|
|
67194
|
-
let outermostFunction = null;
|
|
67195
|
-
let cursor = node.parent;
|
|
67196
|
-
while (cursor) {
|
|
67197
|
-
if (isFunctionLike$1(cursor)) outermostFunction = cursor;
|
|
67198
|
-
if (isNodeOfType(cursor, "Program")) {
|
|
67199
|
-
if (!outermostFunction) return null;
|
|
67200
|
-
for (const exportName of NEXTJS_PAGE_DATA_EXPORT_NAMES) {
|
|
67201
|
-
const exportedValue = findExportedValue(cursor, exportName);
|
|
67202
|
-
if (exportedValue && isAstDescendant(outermostFunction, exportedValue)) return outermostFunction;
|
|
67203
|
-
}
|
|
67204
|
-
return null;
|
|
67205
|
-
}
|
|
67206
|
-
cursor = cursor.parent ?? null;
|
|
67207
|
-
}
|
|
67208
|
-
return null;
|
|
67209
|
-
};
|
|
67210
|
-
const findConditionalReturnExpressionRoot = (node) => {
|
|
67211
|
-
let expressionRoot = findTransparentExpressionRoot(node);
|
|
67212
|
-
while (expressionRoot.parent && isNodeOfType(expressionRoot.parent, "ConditionalExpression") && (expressionRoot.parent.consequent === expressionRoot || expressionRoot.parent.alternate === expressionRoot)) expressionRoot = findTransparentExpressionRoot(expressionRoot.parent);
|
|
67213
|
-
return expressionRoot;
|
|
67214
|
-
};
|
|
67215
|
-
const isReturnedPageDataResultBinding = (returnExpression, pageDataFunction, context) => {
|
|
67216
|
-
const declarator = returnExpression.parent;
|
|
67217
|
-
if (!isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== returnExpression || !isNodeOfType(declarator.id, "Identifier") || findEnclosingFunction$1(declarator) !== pageDataFunction) return false;
|
|
67218
|
-
const bindingSymbol = context.scopes.symbolFor(declarator.id);
|
|
67219
|
-
if (!bindingSymbol || bindingSymbol.references.length !== 1) return false;
|
|
67220
|
-
const referenceRoot = findTransparentExpressionRoot(bindingSymbol.references[0].identifier);
|
|
67221
|
-
const returnStatement = referenceRoot.parent;
|
|
67222
|
-
return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === referenceRoot && findEnclosingFunction$1(returnStatement) === pageDataFunction;
|
|
67223
|
-
};
|
|
67224
|
-
const isSameShorthandPropertyValue = (node, property) => property.shorthand && (node === property.key || node === property.value);
|
|
67225
|
-
const isValueForwardedThroughLiteralStructure = (node, structure) => {
|
|
67226
|
-
const strippedNode = stripParenExpression(node);
|
|
67227
|
-
const strippedStructure = stripParenExpression(structure);
|
|
67228
|
-
if (strippedNode === strippedStructure) return true;
|
|
67229
|
-
if (isNodeOfType(strippedStructure, "ConditionalExpression")) return isValueForwardedThroughLiteralStructure(strippedNode, strippedStructure.consequent) || isValueForwardedThroughLiteralStructure(strippedNode, strippedStructure.alternate);
|
|
67230
|
-
if (isNodeOfType(strippedStructure, "ArrayExpression")) return strippedStructure.elements.some((element) => element && !isNodeOfType(element, "SpreadElement") && isValueForwardedThroughLiteralStructure(strippedNode, element));
|
|
67231
|
-
if (!isNodeOfType(strippedStructure, "ObjectExpression")) return false;
|
|
67232
|
-
return strippedStructure.properties.some((property) => {
|
|
67233
|
-
if (isNodeOfType(property, "SpreadElement")) return isValueForwardedThroughLiteralStructure(strippedNode, property.argument);
|
|
67234
|
-
if (!isNodeOfType(property, "Property")) return false;
|
|
67235
|
-
if (isValueForwardedThroughLiteralStructure(strippedNode, property.value)) return true;
|
|
67236
|
-
return isSameShorthandPropertyValue(strippedNode, property);
|
|
67237
|
-
});
|
|
67238
|
-
};
|
|
67239
|
-
const isValueForwardedToPropertyValue = (node, property) => {
|
|
67240
|
-
const directValue = findConditionalReturnExpressionRoot(node);
|
|
67241
|
-
if (isValueForwardedThroughLiteralStructure(directValue, property.value)) return true;
|
|
67242
|
-
return isSameShorthandPropertyValue(directValue, property);
|
|
67243
|
-
};
|
|
67244
|
-
const isInsideReturnedNextjsProps = (node, pageDataFunction, context) => {
|
|
67245
|
-
let cursor = node.parent;
|
|
67246
|
-
while (cursor && cursor !== pageDataFunction) {
|
|
67247
|
-
if (isNodeOfType(cursor, "Property") && getStaticPropertyKeyName(cursor, { allowComputedString: true }) === "props" && isValueForwardedToPropertyValue(node, cursor)) {
|
|
67248
|
-
const propertyContainer = cursor.parent;
|
|
67249
|
-
if (!propertyContainer) return false;
|
|
67250
|
-
const returnExpression = findConditionalReturnExpressionRoot(propertyContainer);
|
|
67251
|
-
const returnStatement = returnExpression.parent;
|
|
67252
|
-
if (isNodeOfType(returnStatement, "ReturnStatement") && findEnclosingFunction$1(returnStatement) === pageDataFunction) return true;
|
|
67253
|
-
if (isNodeOfType(pageDataFunction, "ArrowFunctionExpression") && !isNodeOfType(pageDataFunction.body, "BlockStatement") && stripParenExpression(pageDataFunction.body) === stripParenExpression(returnExpression)) return true;
|
|
67254
|
-
if (isReturnedPageDataResultBinding(returnExpression, pageDataFunction, context)) return true;
|
|
67255
|
-
}
|
|
67256
|
-
cursor = cursor.parent ?? null;
|
|
67257
|
-
}
|
|
67258
|
-
return false;
|
|
67259
|
-
};
|
|
67260
|
-
const isExpressionReturnedByFunction = (node, functionNode) => {
|
|
67261
|
-
const returnExpression = findConditionalReturnExpressionRoot(node);
|
|
67262
|
-
if (isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode.body, "BlockStatement")) return stripParenExpression(functionNode.body) === stripParenExpression(returnExpression);
|
|
67263
|
-
const returnStatement = returnExpression.parent;
|
|
67264
|
-
return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === returnExpression && findEnclosingFunction$1(returnStatement) === functionNode;
|
|
67265
|
-
};
|
|
67266
|
-
const isValueForwardedToBindingInitializer = (node, bindingInitializer) => {
|
|
67267
|
-
if (isValueForwardedThroughLiteralStructure(findConditionalReturnExpressionRoot(node), bindingInitializer)) return true;
|
|
67268
|
-
const initializer = stripParenExpression(bindingInitializer);
|
|
67269
|
-
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
67270
|
-
const callee = stripParenExpression(initializer.callee);
|
|
67271
|
-
return isFunctionLike$1(callee) && isExpressionReturnedByFunction(node, callee);
|
|
67272
|
-
};
|
|
67273
|
-
const findPageDataResultBinding = (node) => {
|
|
67274
|
-
let cursor = node.parent;
|
|
67275
|
-
while (cursor) {
|
|
67276
|
-
if (isNodeOfType(cursor, "VariableDeclarator")) {
|
|
67277
|
-
if (cursor.init && isNodeOfType(cursor.id, "Identifier") && isValueForwardedToBindingInitializer(node, cursor.init)) return cursor.id;
|
|
67278
|
-
return null;
|
|
67279
|
-
}
|
|
67280
|
-
cursor = cursor.parent ?? null;
|
|
67281
|
-
}
|
|
67282
|
-
return null;
|
|
67283
|
-
};
|
|
67284
|
-
const isUsedToSerializeNextjsPageProps = (node, context) => {
|
|
67285
|
-
if (!isInProjectDirectory(context, "pages") || isInProjectDirectory(context, "pages/api")) return false;
|
|
67286
|
-
const pageDataFunction = findEnclosingNextjsPageDataFunction(node);
|
|
67287
|
-
if (!pageDataFunction) return false;
|
|
67288
|
-
if (isInsideReturnedNextjsProps(node, pageDataFunction, context)) return true;
|
|
67289
|
-
const bindingIdentifier = findPageDataResultBinding(node);
|
|
67290
|
-
const bindingSymbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
|
|
67291
|
-
if (!bindingSymbol) return false;
|
|
67292
|
-
const aliasSymbols = collectConstAliasSymbols(bindingSymbol, context.scopes);
|
|
67293
|
-
const aliasSymbolIds = new Set(aliasSymbols.map((aliasSymbol) => aliasSymbol.id));
|
|
67294
|
-
let hasPagePropsReference = false;
|
|
67295
|
-
for (const aliasSymbol of aliasSymbols) for (const reference of aliasSymbol.references) {
|
|
67296
|
-
if (findEnclosingFunction$1(reference.identifier) !== pageDataFunction) return false;
|
|
67297
|
-
if (isInsideReturnedNextjsProps(reference.identifier, pageDataFunction, context)) {
|
|
67298
|
-
hasPagePropsReference = true;
|
|
67299
|
-
continue;
|
|
67300
|
-
}
|
|
67301
|
-
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
67302
|
-
const declarator = referenceRoot.parent;
|
|
67303
|
-
if (isNodeOfType(declarator, "VariableDeclarator") && declarator.init === referenceRoot && isNodeOfType(declarator.id, "Identifier")) {
|
|
67304
|
-
const aliasSymbolForReference = context.scopes.symbolFor(declarator.id);
|
|
67305
|
-
if (aliasSymbolForReference && aliasSymbolIds.has(aliasSymbolForReference.id)) continue;
|
|
67306
|
-
}
|
|
67307
|
-
return false;
|
|
67308
|
-
}
|
|
67309
|
-
return hasPagePropsReference;
|
|
67310
|
-
};
|
|
67311
66979
|
const noJsonParseStringifyClone = defineRule({
|
|
67312
66980
|
id: "no-json-parse-stringify-clone",
|
|
67313
66981
|
title: "JSON parse/stringify deep clone",
|
|
@@ -67325,7 +66993,6 @@ const noJsonParseStringifyClone = defineRule({
|
|
|
67325
66993
|
if (isInsideSnapshotHelper(node)) return;
|
|
67326
66994
|
if (isAssignedToNormalizationBinding(node)) return;
|
|
67327
66995
|
if (isCatchParameterRoundTrip(firstArgument)) return;
|
|
67328
|
-
if (isUsedToSerializeNextjsPageProps(node, context)) return;
|
|
67329
66996
|
context.report({
|
|
67330
66997
|
node,
|
|
67331
66998
|
message: MESSAGE$35
|
|
@@ -68741,77 +68408,6 @@ const isCancellationGuardTest = (test) => {
|
|
|
68741
68408
|
});
|
|
68742
68409
|
return matches;
|
|
68743
68410
|
};
|
|
68744
|
-
const getReactRefCurrent = (expression, context) => {
|
|
68745
|
-
const stripped = stripParenExpression(expression);
|
|
68746
|
-
if (!isNodeOfType(stripped, "MemberExpression") || getStaticPropertyName(stripped) !== "current") return null;
|
|
68747
|
-
const receiver = stripParenExpression(stripped.object);
|
|
68748
|
-
if (!isNodeOfType(receiver, "Identifier")) return null;
|
|
68749
|
-
const binding = findVariableInitializer(receiver, receiver.name);
|
|
68750
|
-
const initializer = binding?.initializer ? stripParenExpression(binding.initializer) : null;
|
|
68751
|
-
return initializer && isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, USE_REF_HOOK_NAMES$1, context.scopes, {
|
|
68752
|
-
allowGlobalReactNamespace: true,
|
|
68753
|
-
allowUnboundBareCalls: true
|
|
68754
|
-
}) ? stripped : null;
|
|
68755
|
-
};
|
|
68756
|
-
const getStableOwnershipToken = (expression, context) => {
|
|
68757
|
-
const stripped = stripParenExpression(expression);
|
|
68758
|
-
if (!isNodeOfType(stripped, "Identifier")) return null;
|
|
68759
|
-
const symbol = context.scopes.symbolFor(stripped);
|
|
68760
|
-
const initializer = symbol?.initializer ? stripParenExpression(symbol.initializer) : null;
|
|
68761
|
-
const isStableAsyncIdentity = Boolean(initializer && isNodeOfType(initializer, "ObjectExpression")) || Boolean(initializer && getReactRefCurrent(initializer, context)) || Boolean(initializer && isNodeOfType(initializer, "UpdateExpression") && initializer.operator === "++" && getReactRefCurrent(initializer.argument, context));
|
|
68762
|
-
return symbol && symbol.kind === "const" && symbol.references.every((reference) => reference.flag === "read") && isStableAsyncIdentity ? stripped : null;
|
|
68763
|
-
};
|
|
68764
|
-
const getAsyncOwnershipComparison = (test, context) => {
|
|
68765
|
-
const stripped = stripParenExpression(test);
|
|
68766
|
-
if (!isNodeOfType(stripped, "BinaryExpression")) return null;
|
|
68767
|
-
const leftRef = getReactRefCurrent(stripped.left, context);
|
|
68768
|
-
const rightRef = getReactRefCurrent(stripped.right, context);
|
|
68769
|
-
const leftToken = getStableOwnershipToken(stripped.left, context);
|
|
68770
|
-
const rightToken = getStableOwnershipToken(stripped.right, context);
|
|
68771
|
-
if (stripped.operator === "===" || stripped.operator === "==") {
|
|
68772
|
-
if (leftRef && rightToken) return {
|
|
68773
|
-
refCurrent: leftRef,
|
|
68774
|
-
token: rightToken,
|
|
68775
|
-
mode: "owns",
|
|
68776
|
-
isOrdered: false
|
|
68777
|
-
};
|
|
68778
|
-
if (rightRef && leftToken) return {
|
|
68779
|
-
refCurrent: rightRef,
|
|
68780
|
-
token: leftToken,
|
|
68781
|
-
mode: "owns",
|
|
68782
|
-
isOrdered: false
|
|
68783
|
-
};
|
|
68784
|
-
return null;
|
|
68785
|
-
}
|
|
68786
|
-
if (stripped.operator === "!==" || stripped.operator === "!=") {
|
|
68787
|
-
if (leftRef && rightToken) return {
|
|
68788
|
-
refCurrent: leftRef,
|
|
68789
|
-
token: rightToken,
|
|
68790
|
-
mode: "lost",
|
|
68791
|
-
isOrdered: false
|
|
68792
|
-
};
|
|
68793
|
-
if (rightRef && leftToken) return {
|
|
68794
|
-
refCurrent: rightRef,
|
|
68795
|
-
token: leftToken,
|
|
68796
|
-
mode: "lost",
|
|
68797
|
-
isOrdered: false
|
|
68798
|
-
};
|
|
68799
|
-
return null;
|
|
68800
|
-
}
|
|
68801
|
-
if (stripped.operator === "<=" && leftRef && rightToken) return {
|
|
68802
|
-
refCurrent: leftRef,
|
|
68803
|
-
token: rightToken,
|
|
68804
|
-
mode: "owns",
|
|
68805
|
-
isOrdered: true
|
|
68806
|
-
};
|
|
68807
|
-
if (stripped.operator === ">=" && rightRef && leftToken) return {
|
|
68808
|
-
refCurrent: rightRef,
|
|
68809
|
-
token: leftToken,
|
|
68810
|
-
mode: "owns",
|
|
68811
|
-
isOrdered: true
|
|
68812
|
-
};
|
|
68813
|
-
return null;
|
|
68814
|
-
};
|
|
68815
68411
|
const dedupeCatchPathStates = (states) => {
|
|
68816
68412
|
const statesByKey = /* @__PURE__ */ new Map();
|
|
68817
68413
|
for (const state of states) statesByKey.set(`${Number(state.isCleared)}:${Number(state.isCancellationPath)}`, state);
|
|
@@ -69144,221 +68740,7 @@ const isInsideTryFinalizer = (node, tryStatement) => {
|
|
|
69144
68740
|
}
|
|
69145
68741
|
return false;
|
|
69146
68742
|
};
|
|
69147
|
-
const
|
|
69148
|
-
let entry = node;
|
|
69149
|
-
let cursor = node.parent;
|
|
69150
|
-
while (cursor && cursor !== functionNode) {
|
|
69151
|
-
if (isNodeOfType(cursor, "BlockStatement")) return {
|
|
69152
|
-
block: cursor,
|
|
69153
|
-
entry
|
|
69154
|
-
};
|
|
69155
|
-
entry = cursor;
|
|
69156
|
-
cursor = cursor.parent ?? null;
|
|
69157
|
-
}
|
|
69158
|
-
return null;
|
|
69159
|
-
};
|
|
69160
|
-
const claimPrecedesTruthySet = (claimNode, truthySet, firstRiskyAwait, functionNode, context) => {
|
|
69161
|
-
const claimStart = getNodeStart$1(claimNode);
|
|
69162
|
-
if (claimStart === null || claimStart >= firstRiskyAwait.start || truthySet.start >= firstRiskyAwait.start) return false;
|
|
69163
|
-
const claimEntry = getDirectBlockEntry(claimNode, functionNode);
|
|
69164
|
-
const truthyEntry = getDirectBlockEntry(truthySet.node, functionNode);
|
|
69165
|
-
if (!claimEntry || !truthyEntry || claimEntry.block !== truthyEntry.block) return false;
|
|
69166
|
-
let claimCursor = claimNode.parent;
|
|
69167
|
-
while (claimCursor && claimCursor !== claimEntry.block) {
|
|
69168
|
-
if (isNodeOfType(claimCursor, "IfStatement") || isNodeOfType(claimCursor, "SwitchCase") || isNodeOfType(claimCursor, "ConditionalExpression") || isNodeOfType(claimCursor, "LogicalExpression") || isNodeOfType(claimCursor, "ForStatement") || isNodeOfType(claimCursor, "ForInStatement") || isNodeOfType(claimCursor, "ForOfStatement") || isNodeOfType(claimCursor, "WhileStatement") || isNodeOfType(claimCursor, "DoWhileStatement")) return false;
|
|
69169
|
-
claimCursor = claimCursor.parent ?? null;
|
|
69170
|
-
}
|
|
69171
|
-
const claimIndex = claimEntry.block.body.findIndex((statement) => statement === claimEntry.entry);
|
|
69172
|
-
const truthyIndex = claimEntry.block.body.findIndex((statement) => statement === truthyEntry.entry);
|
|
69173
|
-
if (claimIndex === -1 || truthyIndex === -1 || claimIndex >= truthyIndex) return false;
|
|
69174
|
-
return claimEntry.block.body.slice(claimIndex + 1, truthyIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, functionNode, context));
|
|
69175
|
-
};
|
|
69176
|
-
const getOwningFunction = (functionNode) => {
|
|
69177
|
-
let ownerFunction = functionNode;
|
|
69178
|
-
let cursor = functionNode.parent;
|
|
69179
|
-
while (cursor) {
|
|
69180
|
-
if (isFunctionLike$1(cursor)) ownerFunction = cursor;
|
|
69181
|
-
cursor = cursor.parent ?? null;
|
|
69182
|
-
}
|
|
69183
|
-
return ownerFunction;
|
|
69184
|
-
};
|
|
69185
|
-
const isEffectInvalidationPairedWithReset = (writeNode, truthySets, context) => {
|
|
69186
|
-
const truthyCall = truthySets[0]?.node;
|
|
69187
|
-
if (!truthyCall || !isNodeOfType(truthyCall, "CallExpression")) return false;
|
|
69188
|
-
const setter = getSetterBooleanValue(truthyCall, context);
|
|
69189
|
-
if (!setter) return false;
|
|
69190
|
-
let effectCallback = writeNode.parent;
|
|
69191
|
-
while (effectCallback && !isFunctionLike$1(effectCallback)) effectCallback = effectCallback.parent ?? null;
|
|
69192
|
-
if (!effectCallback || !isEffectCallback(effectCallback, context)) return false;
|
|
69193
|
-
if (!isUnconditionallyExecutedWithinFunction(writeNode, effectCallback, context)) return false;
|
|
69194
|
-
const writeEntry = getDirectBlockEntry(writeNode, effectCallback);
|
|
69195
|
-
if (!writeEntry) return false;
|
|
69196
|
-
let isPaired = false;
|
|
69197
|
-
walkOwnFunctionScope(effectCallback, (candidate) => {
|
|
69198
|
-
if (isPaired || !isNodeOfType(candidate, "CallExpression")) return;
|
|
69199
|
-
const candidateSetter = getSetterBooleanValue(candidate, context);
|
|
69200
|
-
if (candidateSetter?.setterKey !== setter.setterKey || candidateSetter.value || !isUnconditionallyExecutedWithinFunction(candidate, effectCallback, context)) return;
|
|
69201
|
-
const resetEntry = getDirectBlockEntry(candidate, effectCallback);
|
|
69202
|
-
if (!resetEntry || resetEntry.block !== writeEntry.block) return;
|
|
69203
|
-
const writeIndex = writeEntry.block.body.findIndex((statement) => statement === writeEntry.entry);
|
|
69204
|
-
const resetIndex = resetEntry.block.body.findIndex((statement) => statement === resetEntry.entry);
|
|
69205
|
-
if (writeIndex === -1 || resetIndex === -1) return;
|
|
69206
|
-
if (resetIndex <= writeIndex) {
|
|
69207
|
-
isPaired = true;
|
|
69208
|
-
return false;
|
|
69209
|
-
}
|
|
69210
|
-
isPaired = writeEntry.block.body.slice(writeIndex + 1, resetIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, effectCallback, context));
|
|
69211
|
-
return isPaired ? false : void 0;
|
|
69212
|
-
});
|
|
69213
|
-
return isPaired;
|
|
69214
|
-
};
|
|
69215
|
-
const isUnconditionalReturnBranch = (statement) => {
|
|
69216
|
-
if (isNodeOfType(statement, "ReturnStatement")) return true;
|
|
69217
|
-
return Boolean(isNodeOfType(statement, "BlockStatement") && statement.body.length === 1 && isNodeOfType(statement.body[0], "ReturnStatement"));
|
|
69218
|
-
};
|
|
69219
|
-
const findSingleFlightSnapshotClaim = (tokenInitializer, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
|
|
69220
|
-
const snapshotEntry = getDirectBlockEntry(tokenInitializer, functionNode);
|
|
69221
|
-
const resetEntry = getDirectBlockEntry(resetNode, functionNode);
|
|
69222
|
-
if (!snapshotEntry || !resetEntry) return null;
|
|
69223
|
-
const claimCandidates = [];
|
|
69224
|
-
const releaseCandidates = [];
|
|
69225
|
-
walkOwnFunctionScope(functionNode, (candidate) => {
|
|
69226
|
-
if (!isNodeOfType(candidate, "AssignmentExpression") || candidate.operator !== "=" || !getReactRefCurrent(candidate.left, context)) return;
|
|
69227
|
-
const assignedValue = stripParenExpression(candidate.right);
|
|
69228
|
-
if (!isNodeOfType(assignedValue, "Literal") || typeof assignedValue.value !== "boolean") return;
|
|
69229
|
-
const candidateKey = serializeReferenceKey({
|
|
69230
|
-
node: candidate.left,
|
|
69231
|
-
scopes: context.scopes
|
|
69232
|
-
});
|
|
69233
|
-
if (!candidateKey) return;
|
|
69234
|
-
if (!assignedValue.value) {
|
|
69235
|
-
if (getDirectBlockEntry(candidate, functionNode)?.block === resetEntry.block) releaseCandidates.push(candidate);
|
|
69236
|
-
return;
|
|
69237
|
-
}
|
|
69238
|
-
if (!truthySets.some((truthySet) => claimPrecedesTruthySet(candidate, truthySet, firstRiskyAwait, functionNode, context))) return;
|
|
69239
|
-
const candidateEntry = getDirectBlockEntry(candidate, functionNode);
|
|
69240
|
-
if (!candidateEntry || candidateEntry.block !== snapshotEntry.block) return;
|
|
69241
|
-
const candidateIndex = candidateEntry.block.body.findIndex((statement) => statement === candidateEntry.entry);
|
|
69242
|
-
const snapshotIndex = candidateEntry.block.body.findIndex((statement) => statement === snapshotEntry.entry);
|
|
69243
|
-
if (candidateIndex === -1 || snapshotIndex === -1 || candidateIndex >= snapshotIndex) return;
|
|
69244
|
-
const guardIndex = candidateEntry.block.body.findLastIndex((statement, statementIndex) => {
|
|
69245
|
-
if (statementIndex >= candidateIndex || !isNodeOfType(statement, "IfStatement") || statement.alternate !== null || !isUnconditionalReturnBranch(statement.consequent)) return false;
|
|
69246
|
-
return serializeReferenceKey({
|
|
69247
|
-
node: stripParenExpression(statement.test),
|
|
69248
|
-
scopes: context.scopes
|
|
69249
|
-
}) === candidateKey;
|
|
69250
|
-
});
|
|
69251
|
-
if (guardIndex === -1 || !candidateEntry.block.body.slice(guardIndex + 1, candidateIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, functionNode, context))) return;
|
|
69252
|
-
claimCandidates.push(candidate);
|
|
69253
|
-
});
|
|
69254
|
-
const claim = claimCandidates.find((claimCandidate) => {
|
|
69255
|
-
const candidateKey = serializeReferenceKey({
|
|
69256
|
-
node: claimCandidate.left,
|
|
69257
|
-
scopes: context.scopes
|
|
69258
|
-
});
|
|
69259
|
-
return releaseCandidates.some((releaseCandidate) => serializeReferenceKey({
|
|
69260
|
-
node: releaseCandidate.left,
|
|
69261
|
-
scopes: context.scopes
|
|
69262
|
-
}) === candidateKey);
|
|
69263
|
-
});
|
|
69264
|
-
if (!claim) return null;
|
|
69265
|
-
const claimKey = serializeReferenceKey({
|
|
69266
|
-
node: claim.left,
|
|
69267
|
-
scopes: context.scopes
|
|
69268
|
-
});
|
|
69269
|
-
const release = releaseCandidates.find((releaseCandidate) => serializeReferenceKey({
|
|
69270
|
-
node: releaseCandidate.left,
|
|
69271
|
-
scopes: context.scopes
|
|
69272
|
-
}) === claimKey);
|
|
69273
|
-
if (!claimKey || !release) return null;
|
|
69274
|
-
const releaseEntry = getDirectBlockEntry(release, functionNode);
|
|
69275
|
-
if (!releaseEntry || releaseEntry.block !== resetEntry.block) return null;
|
|
69276
|
-
const releaseIndex = resetEntry.block.body.findIndex((statement) => statement === releaseEntry.entry);
|
|
69277
|
-
const resetIndex = resetEntry.block.body.findIndex((statement) => statement === resetEntry.entry);
|
|
69278
|
-
if (releaseIndex === -1 || resetIndex === -1 || !resetEntry.block.body.slice(Math.min(releaseIndex, resetIndex) + 1, Math.max(releaseIndex, resetIndex)).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, functionNode, context))) return null;
|
|
69279
|
-
let didFindUnsafeWrite = false;
|
|
69280
|
-
walkAst(getOwningFunction(functionNode), (candidate) => {
|
|
69281
|
-
if (didFindUnsafeWrite || candidate === claim || candidate === release) return;
|
|
69282
|
-
const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
|
|
69283
|
-
if (writeTarget && serializeReferenceKey({
|
|
69284
|
-
node: writeTarget,
|
|
69285
|
-
scopes: context.scopes
|
|
69286
|
-
}) === claimKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindUnsafeWrite = true;
|
|
69287
|
-
});
|
|
69288
|
-
return didFindUnsafeWrite ? null : claim;
|
|
69289
|
-
};
|
|
69290
|
-
const findOwnershipClaim = (comparison, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
|
|
69291
|
-
const refKey = serializeReferenceKey({
|
|
69292
|
-
node: comparison.refCurrent,
|
|
69293
|
-
scopes: context.scopes
|
|
69294
|
-
});
|
|
69295
|
-
const tokenKey = serializeReferenceKey({
|
|
69296
|
-
node: comparison.token,
|
|
69297
|
-
scopes: context.scopes
|
|
69298
|
-
});
|
|
69299
|
-
if (!refKey || !tokenKey) return null;
|
|
69300
|
-
const candidates = [];
|
|
69301
|
-
const tokenSymbol = context.scopes.symbolFor(comparison.token);
|
|
69302
|
-
const tokenInitializer = tokenSymbol?.initializer ? stripParenExpression(tokenSymbol.initializer) : null;
|
|
69303
|
-
if (comparison.isOrdered && !isNodeOfType(tokenInitializer, "UpdateExpression")) return null;
|
|
69304
|
-
if (tokenInitializer && isNodeOfType(tokenInitializer, "UpdateExpression") && tokenInitializer.operator === "++" && serializeReferenceKey({
|
|
69305
|
-
node: tokenInitializer.argument,
|
|
69306
|
-
scopes: context.scopes
|
|
69307
|
-
}) === refKey) candidates.push(tokenInitializer);
|
|
69308
|
-
if (tokenInitializer && getReactRefCurrent(tokenInitializer, context) && serializeReferenceKey({
|
|
69309
|
-
node: tokenInitializer,
|
|
69310
|
-
scopes: context.scopes
|
|
69311
|
-
}) === refKey) {
|
|
69312
|
-
const singleFlightClaim = findSingleFlightSnapshotClaim(tokenInitializer, functionNode, truthySets, firstRiskyAwait, resetNode, context);
|
|
69313
|
-
if (singleFlightClaim) candidates.push(singleFlightClaim);
|
|
69314
|
-
}
|
|
69315
|
-
if (tokenInitializer && isNodeOfType(tokenInitializer, "UpdateExpression")) {
|
|
69316
|
-
const generationKey = serializeReferenceKey({
|
|
69317
|
-
node: tokenInitializer.argument,
|
|
69318
|
-
scopes: context.scopes
|
|
69319
|
-
});
|
|
69320
|
-
if (generationKey && generationKey === refKey) {
|
|
69321
|
-
const ownerFunction = getOwningFunction(functionNode);
|
|
69322
|
-
let didFindOtherGenerationWrite = false;
|
|
69323
|
-
walkAst(ownerFunction, (candidate) => {
|
|
69324
|
-
if (didFindOtherGenerationWrite || candidate === tokenInitializer) return;
|
|
69325
|
-
const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
|
|
69326
|
-
if (writeTarget && serializeReferenceKey({
|
|
69327
|
-
node: writeTarget,
|
|
69328
|
-
scopes: context.scopes
|
|
69329
|
-
}) === generationKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindOtherGenerationWrite = true;
|
|
69330
|
-
});
|
|
69331
|
-
if (didFindOtherGenerationWrite) return null;
|
|
69332
|
-
}
|
|
69333
|
-
}
|
|
69334
|
-
walkOwnFunctionScope(functionNode, (candidate) => {
|
|
69335
|
-
if (!isNodeOfType(candidate, "AssignmentExpression") || candidate.operator !== "=") return;
|
|
69336
|
-
if (serializeReferenceKey({
|
|
69337
|
-
node: candidate.left,
|
|
69338
|
-
scopes: context.scopes
|
|
69339
|
-
}) === refKey && serializeReferenceKey({
|
|
69340
|
-
node: candidate.right,
|
|
69341
|
-
scopes: context.scopes
|
|
69342
|
-
}) === tokenKey) candidates.push(candidate);
|
|
69343
|
-
});
|
|
69344
|
-
const claim = candidates.find((candidate) => truthySets.some((truthySet) => claimPrecedesTruthySet(candidate, truthySet, firstRiskyAwait, functionNode, context)));
|
|
69345
|
-
if (!claim) return null;
|
|
69346
|
-
let didFindOtherWrite = false;
|
|
69347
|
-
walkAst(getOwningFunction(functionNode), (candidate) => {
|
|
69348
|
-
if (didFindOtherWrite || candidate === claim) return;
|
|
69349
|
-
const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
|
|
69350
|
-
if (writeTarget && serializeReferenceKey({
|
|
69351
|
-
node: writeTarget,
|
|
69352
|
-
scopes: context.scopes
|
|
69353
|
-
}) === refKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindOtherWrite = true;
|
|
69354
|
-
});
|
|
69355
|
-
return didFindOtherWrite ? null : claim;
|
|
69356
|
-
};
|
|
69357
|
-
const isClaimedOwnershipComparison = (test, expectedMode, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
|
|
69358
|
-
const comparison = getAsyncOwnershipComparison(test, context);
|
|
69359
|
-
return Boolean(comparison && comparison.mode === expectedMode && findOwnershipClaim(comparison, functionNode, truthySets, firstRiskyAwait, resetNode, context));
|
|
69360
|
-
};
|
|
69361
|
-
const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, acceptedAssignments, context) => {
|
|
68743
|
+
const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, acceptedCleanupAssignments, context) => {
|
|
69362
68744
|
let didFindOtherWrite = false;
|
|
69363
68745
|
walkAst(effectCallback, (candidate) => {
|
|
69364
68746
|
if (didFindOtherWrite) return false;
|
|
@@ -69366,7 +68748,7 @@ const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, accepted
|
|
|
69366
68748
|
if (serializeReferenceKey({
|
|
69367
68749
|
node: candidate.left,
|
|
69368
68750
|
scopes: context.scopes
|
|
69369
|
-
}) === guardKey && !
|
|
68751
|
+
}) === guardKey && !acceptedCleanupAssignments.has(candidate)) {
|
|
69370
68752
|
didFindOtherWrite = true;
|
|
69371
68753
|
return false;
|
|
69372
68754
|
}
|
|
@@ -69382,103 +68764,48 @@ const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, accepted
|
|
|
69382
68764
|
});
|
|
69383
68765
|
return didFindOtherWrite;
|
|
69384
68766
|
};
|
|
69385
|
-
const
|
|
69386
|
-
const acceptedAssignments = /* @__PURE__ */ new Set();
|
|
69387
|
-
for (const cleanupFunction of collectReturnedCleanupFunctions(effectCallback, context.scopes)) walkOwnFunctionScope(cleanupFunction, (cleanupNode) => {
|
|
69388
|
-
const assignedValue = isNodeOfType(cleanupNode, "AssignmentExpression") ? stripParenExpression(cleanupNode.right) : null;
|
|
69389
|
-
if (!isNodeOfType(cleanupNode, "AssignmentExpression") || cleanupNode.operator !== "=" || !isNodeOfType(assignedValue, "Literal") || assignedValue.value !== false || serializeReferenceKey({
|
|
69390
|
-
node: cleanupNode.left,
|
|
69391
|
-
scopes: context.scopes
|
|
69392
|
-
}) !== guardKey || !isUnconditionallyExecutedWithinFunction(cleanupNode, cleanupFunction, context)) return;
|
|
69393
|
-
acceptedAssignments.add(cleanupNode);
|
|
69394
|
-
});
|
|
69395
|
-
if (acceptedAssignments.size === 0) return null;
|
|
69396
|
-
walkOwnFunctionScope(effectCallback, (effectNode) => {
|
|
69397
|
-
const assignedValue = isNodeOfType(effectNode, "AssignmentExpression") ? stripParenExpression(effectNode.right) : null;
|
|
69398
|
-
if (isNodeOfType(effectNode, "AssignmentExpression") && effectNode.operator === "=" && isNodeOfType(assignedValue, "Literal") && assignedValue.value === true && serializeReferenceKey({
|
|
69399
|
-
node: effectNode.left,
|
|
69400
|
-
scopes: context.scopes
|
|
69401
|
-
}) === guardKey && isUnconditionallyExecutedWithinFunction(effectNode, effectCallback, context)) acceptedAssignments.add(effectNode);
|
|
69402
|
-
});
|
|
69403
|
-
return acceptedAssignments;
|
|
69404
|
-
};
|
|
69405
|
-
const isEffectCallback = (node, context) => {
|
|
69406
|
-
const callbackRoot = findTransparentExpressionRoot(node);
|
|
69407
|
-
const callbackCall = callbackRoot.parent;
|
|
69408
|
-
return Boolean(callbackCall && isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments[0] === callbackRoot && isReactApiCall(callbackCall, EFFECT_HOOK_NAMES$6, context.scopes, {
|
|
69409
|
-
allowGlobalReactNamespace: true,
|
|
69410
|
-
allowUnboundBareCalls: true
|
|
69411
|
-
}));
|
|
69412
|
-
};
|
|
69413
|
-
const isCleanupBackedLifecycleGuard = (guardExpression, functionNode, context) => {
|
|
69414
|
-
const guardKey = serializeReferenceKey({
|
|
69415
|
-
node: guardExpression,
|
|
69416
|
-
scopes: context.scopes
|
|
69417
|
-
});
|
|
69418
|
-
if (!guardKey || !isInitiallyActiveLifecycleGuard(guardExpression, context)) return false;
|
|
69419
|
-
let ownerFunction = functionNode.parent;
|
|
69420
|
-
while (ownerFunction && !isFunctionLike$1(ownerFunction)) ownerFunction = ownerFunction.parent ?? null;
|
|
69421
|
-
if (!ownerFunction) return false;
|
|
69422
|
-
const effectCallbacks = [];
|
|
69423
|
-
if (isEffectCallback(ownerFunction, context)) effectCallbacks.push(ownerFunction);
|
|
69424
|
-
walkOwnFunctionScope(ownerFunction, (candidate) => {
|
|
69425
|
-
if (!isNodeOfType(candidate, "CallExpression")) return;
|
|
69426
|
-
if (!isReactApiCall(candidate, EFFECT_HOOK_NAMES$6, context.scopes, {
|
|
69427
|
-
allowGlobalReactNamespace: true,
|
|
69428
|
-
allowUnboundBareCalls: true
|
|
69429
|
-
})) return;
|
|
69430
|
-
const effectCallback = candidate.arguments[0];
|
|
69431
|
-
if (effectCallback && isFunctionLike$1(effectCallback)) effectCallbacks.push(effectCallback);
|
|
69432
|
-
});
|
|
69433
|
-
const acceptedAssignments = /* @__PURE__ */ new Set();
|
|
69434
|
-
for (const effectCallback of effectCallbacks) {
|
|
69435
|
-
const effectAssignments = collectCleanupBackedLifecycleAssignments(effectCallback, guardKey, context);
|
|
69436
|
-
if (!effectAssignments) continue;
|
|
69437
|
-
for (const assignment of effectAssignments) acceptedAssignments.add(assignment);
|
|
69438
|
-
}
|
|
69439
|
-
return Boolean(acceptedAssignments.size > 0 && !hasLifecycleGuardWriteOutsideCleanup(ownerFunction, guardKey, acceptedAssignments, context));
|
|
69440
|
-
};
|
|
69441
|
-
const collectLogicalOperands = (expression, operator) => {
|
|
69442
|
-
const stripped = stripParenExpression(expression);
|
|
69443
|
-
if (isNodeOfType(stripped, "LogicalExpression") && stripped.operator === operator) return [...collectLogicalOperands(stripped.left, operator), ...collectLogicalOperands(stripped.right, operator)];
|
|
69444
|
-
return [stripped];
|
|
69445
|
-
};
|
|
69446
|
-
const collectFinalizerGuardExpressions = (resetNode, protectingTry) => {
|
|
69447
|
-
const positive = [];
|
|
69448
|
-
const negative = [];
|
|
68767
|
+
const isResetGuardedByCleanupBackedLifecycle = (resetNode, functionNode, context) => {
|
|
69449
68768
|
let child = resetNode;
|
|
69450
68769
|
let cursor = resetNode.parent;
|
|
69451
|
-
|
|
69452
|
-
|
|
69453
|
-
|
|
69454
|
-
|
|
69455
|
-
|
|
69456
|
-
|
|
69457
|
-
|
|
69458
|
-
|
|
69459
|
-
|
|
69460
|
-
|
|
69461
|
-
|
|
69462
|
-
negative.push(...collectLogicalOperands(statement.test, "||"));
|
|
69463
|
-
}
|
|
69464
|
-
} else if (isNodeOfType(cursor, "SwitchCase") || isNodeOfType(cursor, "ConditionalExpression") || isNodeOfType(cursor, "ForStatement") || isNodeOfType(cursor, "ForInStatement") || isNodeOfType(cursor, "ForOfStatement") || isNodeOfType(cursor, "WhileStatement") || isNodeOfType(cursor, "DoWhileStatement")) return null;
|
|
68770
|
+
let guardKey = null;
|
|
68771
|
+
let guardExpression = null;
|
|
68772
|
+
while (cursor && cursor !== functionNode) {
|
|
68773
|
+
if (isNodeOfType(cursor, "IfStatement") && cursor.consequent === child && cursor.alternate === null) {
|
|
68774
|
+
guardExpression = cursor.test;
|
|
68775
|
+
guardKey = serializeReferenceKey({
|
|
68776
|
+
node: cursor.test,
|
|
68777
|
+
scopes: context.scopes
|
|
68778
|
+
});
|
|
68779
|
+
break;
|
|
68780
|
+
}
|
|
69465
68781
|
child = cursor;
|
|
69466
68782
|
cursor = cursor.parent ?? null;
|
|
69467
68783
|
}
|
|
69468
|
-
|
|
69469
|
-
|
|
69470
|
-
|
|
69471
|
-
|
|
69472
|
-
|
|
69473
|
-
const
|
|
69474
|
-
|
|
69475
|
-
|
|
69476
|
-
|
|
69477
|
-
|
|
69478
|
-
|
|
69479
|
-
const
|
|
69480
|
-
|
|
69481
|
-
|
|
68784
|
+
if (!guardKey || !guardExpression || !isInitiallyActiveLifecycleGuard(guardExpression, context)) return false;
|
|
68785
|
+
cursor = functionNode.parent;
|
|
68786
|
+
while (cursor) {
|
|
68787
|
+
if (isFunctionLike$1(cursor)) {
|
|
68788
|
+
const callbackRoot = findTransparentExpressionRoot(cursor);
|
|
68789
|
+
const callbackCall = callbackRoot.parent;
|
|
68790
|
+
if (Boolean(callbackCall && isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments[0] === callbackRoot && isReactApiCall(callbackCall, EFFECT_HOOK_NAMES$6, context.scopes, {
|
|
68791
|
+
allowGlobalReactNamespace: true,
|
|
68792
|
+
allowUnboundBareCalls: true
|
|
68793
|
+
}))) {
|
|
68794
|
+
const acceptedCleanupAssignments = /* @__PURE__ */ new Set();
|
|
68795
|
+
for (const cleanupFunction of collectReturnedCleanupFunctions(cursor, context.scopes)) walkOwnFunctionScope(cleanupFunction, (cleanupNode) => {
|
|
68796
|
+
const assignedValue = isNodeOfType(cleanupNode, "AssignmentExpression") ? stripParenExpression(cleanupNode.right) : null;
|
|
68797
|
+
if (!isNodeOfType(cleanupNode, "AssignmentExpression") || cleanupNode.operator !== "=" || !isNodeOfType(assignedValue, "Literal") || assignedValue.value !== false || serializeReferenceKey({
|
|
68798
|
+
node: cleanupNode.left,
|
|
68799
|
+
scopes: context.scopes
|
|
68800
|
+
}) !== guardKey || !isUnconditionallyExecutedWithinFunction(cleanupNode, cleanupFunction, context)) return;
|
|
68801
|
+
acceptedCleanupAssignments.add(cleanupNode);
|
|
68802
|
+
});
|
|
68803
|
+
if (acceptedCleanupAssignments.size > 0 && !hasLifecycleGuardWriteOutsideCleanup(cursor, guardKey, acceptedCleanupAssignments, context)) return true;
|
|
68804
|
+
}
|
|
68805
|
+
}
|
|
68806
|
+
cursor = cursor.parent ?? null;
|
|
68807
|
+
}
|
|
68808
|
+
return false;
|
|
69482
68809
|
};
|
|
69483
68810
|
const isAwaitInsideProtectedTry = (awaitNode, tryStatement) => {
|
|
69484
68811
|
let child = awaitNode;
|
|
@@ -69584,13 +68911,7 @@ const analyzeFunction = (functionNode, context) => {
|
|
|
69584
68911
|
const exceptionallyProtectedAwaits = collectExceptionallyProtectedAwaits(awaitSites, calls);
|
|
69585
68912
|
const riskyAwaitsWithTruthySet = awaitSites.filter((awaitSite) => rejectingAwaitNodes.has(awaitSite.node) && !exceptionallyProtectedAwaits.has(awaitSite.node) && truthySets.some((truthySet) => truthySet.start < awaitSite.start && !areOnExclusiveBranches(truthySet.node, awaitSite.node, functionNode)));
|
|
69586
68913
|
if (riskyAwaitsWithTruthySet.length === 0) continue;
|
|
69587
|
-
const conditionalExceptionalResets = calls.filter((call) =>
|
|
69588
|
-
if (call.value || call.context === "plain" || call.isUnconditional || call.protectingTry === null) return false;
|
|
69589
|
-
const protectingTry = call.protectingTry;
|
|
69590
|
-
if (!isInsideTryFinalizer(call.node, protectingTry)) return true;
|
|
69591
|
-
const firstRiskyAwait = riskyAwaitsWithTruthySet.find((awaitSite) => isAwaitInsideProtectedTry(awaitSite.node, protectingTry));
|
|
69592
|
-
return !(firstRiskyAwait && isFinalizerResetProvablyGuarded(call.node, protectingTry, functionNode, truthySets, firstRiskyAwait, context));
|
|
69593
|
-
});
|
|
68914
|
+
const conditionalExceptionalResets = calls.filter((call) => !call.value && call.context !== "plain" && !call.isUnconditional && call.protectingTry !== null && !(isInsideTryFinalizer(call.node, call.protectingTry) && isResetGuardedByCleanupBackedLifecycle(call.node, functionNode, context)));
|
|
69594
68915
|
for (const reset of conditionalExceptionalResets) {
|
|
69595
68916
|
const catchHandler = reset.protectingTry?.handler;
|
|
69596
68917
|
if (catchHandler && !catchHandlerCanBypassReset(catchHandler, functionNode, setterKey, context, false)) continue;
|
|
@@ -75462,29 +74783,6 @@ const doesPredicateTruthRequireMatch = (matchCall, predicateFunction) => {
|
|
|
75462
74783
|
}
|
|
75463
74784
|
return !isNegated && predicateFunction.body === child;
|
|
75464
74785
|
};
|
|
75465
|
-
const doesPredicateReturnNormalizedMatch = (matchCall, predicateFunction) => {
|
|
75466
|
-
if (!isFunctionLike$1(predicateFunction) || !isNodeOfType(predicateFunction.body, "BlockStatement") || predicateFunction.body.body.length !== 2 || !isNodeOfType(predicateFunction.body.body[0], "VariableDeclaration")) return false;
|
|
75467
|
-
const returnStatement = predicateFunction.body.body[1];
|
|
75468
|
-
if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
|
|
75469
|
-
let negationCount = 0;
|
|
75470
|
-
let expression = matchCall;
|
|
75471
|
-
let parent = expression.parent ?? null;
|
|
75472
|
-
while (parent && parent !== returnStatement) {
|
|
75473
|
-
if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "!") {
|
|
75474
|
-
negationCount += 1;
|
|
75475
|
-
expression = parent;
|
|
75476
|
-
parent = parent.parent ?? null;
|
|
75477
|
-
continue;
|
|
75478
|
-
}
|
|
75479
|
-
if (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) || isNodeOfType(parent, "ChainExpression")) {
|
|
75480
|
-
expression = parent;
|
|
75481
|
-
parent = parent.parent ?? null;
|
|
75482
|
-
continue;
|
|
75483
|
-
}
|
|
75484
|
-
return false;
|
|
75485
|
-
}
|
|
75486
|
-
return parent === returnStatement && returnStatement.argument === expression && negationCount % 2 === 0;
|
|
75487
|
-
};
|
|
75488
74786
|
const isStringTypeofGuardForPath = (test, expectedPath) => {
|
|
75489
74787
|
const target = stripParenExpression(test);
|
|
75490
74788
|
if (!isNodeOfType(target, "BinaryExpression") || target.operator !== "===") return false;
|
|
@@ -75525,26 +74823,6 @@ const pathUsesOptionalAccess = (node) => {
|
|
|
75525
74823
|
current = current.object;
|
|
75526
74824
|
}
|
|
75527
74825
|
};
|
|
75528
|
-
const getNormalizedClassNameRoot = (expression) => {
|
|
75529
|
-
const conditional = stripParenExpression(expression);
|
|
75530
|
-
if (!isNodeOfType(conditional, "ConditionalExpression")) return null;
|
|
75531
|
-
const consequent = stripParenExpression(conditional.consequent);
|
|
75532
|
-
const rootIdentifier = getRootIdentifier(consequent);
|
|
75533
|
-
if (!rootIdentifier || receiverPathKey(consequent) !== `${rootIdentifier.name}.className`) return null;
|
|
75534
|
-
const test = stripParenExpression(conditional.test);
|
|
75535
|
-
if (!isNodeOfType(test, "BinaryExpression") || test.operator !== "===") return null;
|
|
75536
|
-
const testOperands = [test.left, test.right].map((operand) => stripParenExpression(operand));
|
|
75537
|
-
const typeofOperand = testOperands.find((operand) => isNodeOfType(operand, "UnaryExpression"));
|
|
75538
|
-
const stringOperand = testOperands.find((operand) => isNodeOfType(operand, "Literal"));
|
|
75539
|
-
if (!typeofOperand || !isNodeOfType(typeofOperand, "UnaryExpression") || typeofOperand.operator !== "typeof" || receiverPathKey(typeofOperand.argument) !== `${rootIdentifier.name}.className` || !stringOperand || !isNodeOfType(stringOperand, "Literal") || stringOperand.value !== "string") return null;
|
|
75540
|
-
const alternate = stripParenExpression(conditional.alternate);
|
|
75541
|
-
if (!isNodeOfType(alternate, "LogicalExpression") || alternate.operator !== "??") return null;
|
|
75542
|
-
const fallback = stripParenExpression(alternate.right);
|
|
75543
|
-
const attributeCall = stripParenExpression(alternate.left);
|
|
75544
|
-
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;
|
|
75545
|
-
const attributeName = attributeCall.arguments[0] ? stripParenExpression(attributeCall.arguments[0]) : null;
|
|
75546
|
-
return attributeName && isNodeOfType(attributeName, "Literal") && attributeName.value === "class" ? rootIdentifier : null;
|
|
75547
|
-
};
|
|
75548
74826
|
const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedPattern, context) => {
|
|
75549
74827
|
const resultIdentifier = getRootIdentifier(matchReceiver);
|
|
75550
74828
|
const resultPath = receiverPathKey(matchReceiver);
|
|
@@ -75553,17 +74831,11 @@ const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedP
|
|
|
75553
74831
|
if (!isDirectFinderMatchReturn(assertion) && !isOptionalResultPath) return false;
|
|
75554
74832
|
const resultSymbol = context.scopes.symbolFor(resultIdentifier);
|
|
75555
74833
|
const initializer = resultSymbol?.initializer ? stripParenExpression(resultSymbol.initializer) : null;
|
|
75556
|
-
if (resultSymbol?.kind !== "const" || !initializer) return false;
|
|
75557
|
-
const finderCall = isNodeOfType(initializer, "CallExpression") ? initializer : isNodeOfType(initializer, "ConditionalExpression") ? (() => {
|
|
75558
|
-
const alternate = stripParenExpression(initializer.alternate);
|
|
75559
|
-
const consequent = stripParenExpression(initializer.consequent);
|
|
75560
|
-
return (isNodeOfType(alternate, "Literal") && alternate.value === null || isNodeOfType(alternate, "Identifier") && alternate.name === "undefined" && context.scopes.isGlobalReference(alternate)) && isNodeOfType(consequent, "CallExpression") ? consequent : null;
|
|
75561
|
-
})() : null;
|
|
75562
|
-
if (!finderCall || !isNodeOfType(finderCall, "CallExpression")) return false;
|
|
74834
|
+
if (resultSymbol?.kind !== "const" || !initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
75563
74835
|
if (!isOptionalResultPath && !isImmediatelyGuardedFinderResult(assertion, resultSymbol, resultPath, context)) return false;
|
|
75564
|
-
const finderCallee = stripParenExpression(
|
|
74836
|
+
const finderCallee = stripParenExpression(initializer.callee);
|
|
75565
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;
|
|
75566
|
-
const predicateArgument =
|
|
74838
|
+
const predicateArgument = initializer.arguments[1];
|
|
75567
74839
|
if (!predicateArgument) return false;
|
|
75568
74840
|
const predicateFunction = resolveExactLocalFunction(predicateArgument, context.scopes);
|
|
75569
74841
|
if (!predicateFunction || !isFunctionLike$1(predicateFunction)) return false;
|
|
@@ -75587,43 +74859,6 @@ const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedP
|
|
|
75587
74859
|
});
|
|
75588
74860
|
return didProveMatch;
|
|
75589
74861
|
};
|
|
75590
|
-
const isMatchProvenByNormalizedFindUpUntilPredicate = (assertion, matchReceiver, assertedPattern, context) => {
|
|
75591
|
-
const normalizedReceiver = stripParenExpression(matchReceiver);
|
|
75592
|
-
if (!isNodeOfType(normalizedReceiver, "Identifier")) return false;
|
|
75593
|
-
const normalizedReceiverSymbol = context.scopes.symbolFor(normalizedReceiver);
|
|
75594
|
-
const normalizedReceiverInitializer = normalizedReceiverSymbol?.initializer ? stripParenExpression(normalizedReceiverSymbol.initializer) : null;
|
|
75595
|
-
const resultIdentifier = normalizedReceiverInitializer ? getNormalizedClassNameRoot(normalizedReceiverInitializer) : null;
|
|
75596
|
-
if (normalizedReceiverSymbol?.kind !== "const" || normalizedReceiverSymbol.references.some((reference) => reference.flag !== "read") || !resultIdentifier) return false;
|
|
75597
|
-
const resultSymbol = context.scopes.symbolFor(resultIdentifier);
|
|
75598
|
-
const finderCall = resultSymbol?.initializer ? stripParenExpression(resultSymbol.initializer) : null;
|
|
75599
|
-
if (resultSymbol?.kind !== "const" || resultSymbol.references.some((reference) => reference.flag !== "read") || !finderCall || !isNodeOfType(finderCall, "CallExpression")) return false;
|
|
75600
|
-
const finderCallee = stripParenExpression(finderCall.callee);
|
|
75601
|
-
if (!isNodeOfType(finderCallee, "Identifier") || context.scopes.symbolFor(finderCallee)?.kind !== "import" || getImportedNameFromModule(assertion, finderCallee.name, CLOUDSCAPE_DOM_MODULE) !== "findUpUntil") return false;
|
|
75602
|
-
if (!isPresenceProvenBeforeNode(assertion, (test) => {
|
|
75603
|
-
const expression = stripParenExpression(test);
|
|
75604
|
-
return isNodeOfType(expression, "Identifier") && context.scopes.symbolFor(expression)?.id === resultSymbol.id;
|
|
75605
|
-
})) return false;
|
|
75606
|
-
const predicateArgument = finderCall.arguments[1];
|
|
75607
|
-
const predicateFunction = predicateArgument ? resolveExactLocalFunction(predicateArgument, context.scopes) : null;
|
|
75608
|
-
if (!predicateFunction || !isFunctionLike$1(predicateFunction) || predicateFunction.async || predicateFunction.generator) return false;
|
|
75609
|
-
const predicateParameter = predicateFunction.params[0];
|
|
75610
|
-
if (!isNodeOfType(predicateParameter, "Identifier")) return false;
|
|
75611
|
-
let didProveNormalizedMatch = false;
|
|
75612
|
-
walkAst(predicateFunction.body, (child) => {
|
|
75613
|
-
if (didProveNormalizedMatch || isFunctionLike$1(child)) return false;
|
|
75614
|
-
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;
|
|
75615
|
-
const predicateReceiver = stripParenExpression(child.callee.object);
|
|
75616
|
-
if (!isNodeOfType(predicateReceiver, "Identifier")) return;
|
|
75617
|
-
const predicateReceiverSymbol = context.scopes.symbolFor(predicateReceiver);
|
|
75618
|
-
const predicateReceiverInitializer = predicateReceiverSymbol?.initializer ? stripParenExpression(predicateReceiverSymbol.initializer) : null;
|
|
75619
|
-
const predicateRoot = predicateReceiverInitializer ? getNormalizedClassNameRoot(predicateReceiverInitializer) : null;
|
|
75620
|
-
if (predicateReceiverSymbol?.kind === "const" && predicateReceiverSymbol.references.every((reference) => reference.flag === "read") && predicateRoot?.name === predicateParameter.name) {
|
|
75621
|
-
didProveNormalizedMatch = true;
|
|
75622
|
-
return false;
|
|
75623
|
-
}
|
|
75624
|
-
});
|
|
75625
|
-
return didProveNormalizedMatch;
|
|
75626
|
-
};
|
|
75627
74862
|
const scopeProvesFindMatch = (assertion, findReceiver, findPredicate, context) => {
|
|
75628
74863
|
if (!isStablePredicate(findPredicate, context)) return false;
|
|
75629
74864
|
return isPresenceProvenBeforeNode(assertion, (test) => testPositivelyContainsCall(test, (call) => {
|
|
@@ -75724,123 +74959,6 @@ const isEnsureThenFind = (assertion, findReceiver, findPredicate) => {
|
|
|
75724
74959
|
}
|
|
75725
74960
|
return false;
|
|
75726
74961
|
};
|
|
75727
|
-
const getReceiverRootIdentifier = (node) => {
|
|
75728
|
-
let target = stripParenExpression(node);
|
|
75729
|
-
while (isNodeOfType(target, "MemberExpression")) target = stripParenExpression(target.object);
|
|
75730
|
-
return isNodeOfType(target, "Identifier") ? target : null;
|
|
75731
|
-
};
|
|
75732
|
-
const getReceiverStatePath = (node) => {
|
|
75733
|
-
const target = stripParenExpression(node);
|
|
75734
|
-
if (isNodeOfType(target, "Identifier")) return target.name;
|
|
75735
|
-
if (!isNodeOfType(target, "MemberExpression")) return null;
|
|
75736
|
-
const objectPath = getReceiverStatePath(target.object);
|
|
75737
|
-
if (!objectPath) return null;
|
|
75738
|
-
return `${objectPath}.${getStaticPropertyName(target) ?? "*"}`;
|
|
75739
|
-
};
|
|
75740
|
-
const doesReceiverStateChangeBeforeAssertion = (ownerFunction, receiver, startOffset, assertion, context) => {
|
|
75741
|
-
const receiverRoot = getReceiverRootIdentifier(receiver);
|
|
75742
|
-
const receiverSymbol = receiverRoot ? context.scopes.symbolFor(receiverRoot) : null;
|
|
75743
|
-
const receiverPath = getReceiverStatePath(receiver);
|
|
75744
|
-
if (!receiverRoot || !receiverSymbol || !receiverPath) return true;
|
|
75745
|
-
const receiverAliasPaths = new Map([[receiverSymbol.id, receiverRoot.name]]);
|
|
75746
|
-
let didAddAlias = true;
|
|
75747
|
-
while (didAddAlias) {
|
|
75748
|
-
didAddAlias = false;
|
|
75749
|
-
walkAst(ownerFunction, (child) => {
|
|
75750
|
-
if (child !== ownerFunction && isFunctionLike$1(child)) return false;
|
|
75751
|
-
if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier") || !child.init) return;
|
|
75752
|
-
const initializer = stripParenExpression(child.init);
|
|
75753
|
-
if (!isNodeOfType(initializer, "Identifier") && !isNodeOfType(initializer, "MemberExpression")) return;
|
|
75754
|
-
const initializerRoot = getReceiverRootIdentifier(initializer);
|
|
75755
|
-
const initializerSymbol = initializerRoot ? context.scopes.symbolFor(initializerRoot) : null;
|
|
75756
|
-
const initializerBasePath = initializerSymbol ? receiverAliasPaths.get(initializerSymbol.id) : null;
|
|
75757
|
-
const initializerPath = getReceiverStatePath(initializer);
|
|
75758
|
-
if (!initializerRoot || !initializerBasePath || !initializerPath) return;
|
|
75759
|
-
const aliasSymbol = context.scopes.symbolFor(child.id);
|
|
75760
|
-
if (aliasSymbol && !receiverAliasPaths.has(aliasSymbol.id)) {
|
|
75761
|
-
const initializerSuffix = initializerPath.slice(initializerRoot.name.length);
|
|
75762
|
-
receiverAliasPaths.set(aliasSymbol.id, `${initializerBasePath}${initializerSuffix}`);
|
|
75763
|
-
didAddAlias = true;
|
|
75764
|
-
}
|
|
75765
|
-
});
|
|
75766
|
-
}
|
|
75767
|
-
let didChangeReceiverState = false;
|
|
75768
|
-
walkAst(ownerFunction, (child) => {
|
|
75769
|
-
if (didChangeReceiverState) return false;
|
|
75770
|
-
if (child !== ownerFunction && isFunctionLike$1(child)) return false;
|
|
75771
|
-
if (child.range[0] <= startOffset || child.range[0] >= assertion.range[0]) return;
|
|
75772
|
-
if (isNodeOfType(child, "CallExpression")) {
|
|
75773
|
-
didChangeReceiverState = true;
|
|
75774
|
-
return false;
|
|
75775
|
-
}
|
|
75776
|
-
const mutationTarget = isNodeOfType(child, "AssignmentExpression") || isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete" ? stripParenExpression(isNodeOfType(child, "AssignmentExpression") ? child.left : child.argument) : null;
|
|
75777
|
-
const mutationRoot = mutationTarget ? getReceiverRootIdentifier(mutationTarget) : null;
|
|
75778
|
-
const mutationSymbol = mutationRoot ? context.scopes.symbolFor(mutationRoot) : null;
|
|
75779
|
-
const mutationBasePath = mutationSymbol ? receiverAliasPaths.get(mutationSymbol.id) : null;
|
|
75780
|
-
const mutationPath = mutationTarget ? getReceiverStatePath(mutationTarget) : null;
|
|
75781
|
-
if (mutationRoot && mutationBasePath && mutationPath) {
|
|
75782
|
-
const canonicalMutationPath = `${mutationBasePath}${mutationPath.slice(mutationRoot.name.length)}`;
|
|
75783
|
-
if (canonicalMutationPath !== receiverPath && !canonicalMutationPath.startsWith(`${receiverPath}.`) && !receiverPath.startsWith(`${canonicalMutationPath}.`)) return;
|
|
75784
|
-
didChangeReceiverState = true;
|
|
75785
|
-
return false;
|
|
75786
|
-
}
|
|
75787
|
-
});
|
|
75788
|
-
return didChangeReceiverState;
|
|
75789
|
-
};
|
|
75790
|
-
const isFindProvenByGuardedMaximum = (assertion, findReceiver, findPredicate, context) => {
|
|
75791
|
-
const findLookup = findEqualityLookupParts(findPredicate);
|
|
75792
|
-
const maximumIdentifier = findLookup ? stripParenExpression(findLookup.comparedValue) : null;
|
|
75793
|
-
if (!findLookup || !maximumIdentifier || !isNodeOfType(maximumIdentifier, "Identifier")) return false;
|
|
75794
|
-
const maximumSymbol = context.scopes.symbolFor(maximumIdentifier);
|
|
75795
|
-
const maximumInitializer = maximumSymbol?.initializer ? stripParenExpression(maximumSymbol.initializer) : null;
|
|
75796
|
-
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;
|
|
75797
|
-
const filterCall = stripParenExpression(maximumInitializer.callee.object);
|
|
75798
|
-
if (!isNodeOfType(filterCall, "CallExpression") || !isNodeOfType(filterCall.callee, "MemberExpression") || getStaticPropertyName(filterCall.callee) !== "filter" || !areNodesLooselyEqual(filterCall.callee.object, findReceiver)) return false;
|
|
75799
|
-
const filterPredicate = filterCall.arguments[0] ? stripParenExpression(filterCall.arguments[0]) : null;
|
|
75800
|
-
if (!filterPredicate || !isStablePredicate(filterPredicate, context)) return false;
|
|
75801
|
-
const reducerArgument = maximumInitializer.arguments[0] ? stripParenExpression(maximumInitializer.arguments[0]) : null;
|
|
75802
|
-
const reducerFunction = reducerArgument ? resolveExactLocalFunction(reducerArgument, context.scopes) : null;
|
|
75803
|
-
const initialValue = maximumInitializer.arguments[1] ? stripParenExpression(maximumInitializer.arguments[1]) : null;
|
|
75804
|
-
if (!reducerFunction || !isFunctionLike$1(reducerFunction) || reducerFunction.async || reducerFunction.generator || !initialValue || !isNodeOfType(initialValue, "Literal") || typeof initialValue.value !== "number") return false;
|
|
75805
|
-
const accumulatorParameter = reducerFunction.params[0];
|
|
75806
|
-
const itemParameter = reducerFunction.params[1];
|
|
75807
|
-
const reducerBody = singleExpressionPredicateBody(reducerFunction);
|
|
75808
|
-
if (!isNodeOfType(accumulatorParameter, "Identifier") || !isNodeOfType(itemParameter, "Identifier") || !reducerBody || !isNodeOfType(reducerBody, "CallExpression") || !isNodeOfType(reducerBody.callee, "MemberExpression") || getStaticPropertyName(reducerBody.callee) !== "max") return false;
|
|
75809
|
-
const mathReceiver = stripParenExpression(reducerBody.callee.object);
|
|
75810
|
-
if (!isNodeOfType(mathReceiver, "Identifier") || mathReceiver.name !== "Math" || !context.scopes.isGlobalReference(mathReceiver) || reducerBody.arguments.length !== 2) return false;
|
|
75811
|
-
const accumulatorArgument = reducerBody.arguments.find((argument) => {
|
|
75812
|
-
const expression = stripParenExpression(argument);
|
|
75813
|
-
return isNodeOfType(expression, "Identifier") && expression.name === accumulatorParameter.name;
|
|
75814
|
-
});
|
|
75815
|
-
const itemMemberArgument = reducerBody.arguments.find((argument) => {
|
|
75816
|
-
const expression = stripParenExpression(argument);
|
|
75817
|
-
const rootIdentifier = getRootIdentifier(expression);
|
|
75818
|
-
return isNodeOfType(expression, "MemberExpression") && rootIdentifier?.name === itemParameter.name && receiverPathKey(expression)?.slice(itemParameter.name.length + 1) === findLookup.propertyName;
|
|
75819
|
-
});
|
|
75820
|
-
if (!accumulatorArgument || !itemMemberArgument) return false;
|
|
75821
|
-
if (!receiverPathKey(findReceiver)) return false;
|
|
75822
|
-
let ownerFunction = assertion.parent ?? null;
|
|
75823
|
-
while (ownerFunction && !isFunctionLike$1(ownerFunction)) ownerFunction = ownerFunction.parent ?? null;
|
|
75824
|
-
if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
|
|
75825
|
-
const maximumEnd = maximumInitializer.range[1];
|
|
75826
|
-
if (doesReceiverStateChangeBeforeAssertion(ownerFunction.body, findReceiver, maximumEnd, assertion, context)) return false;
|
|
75827
|
-
return isPresenceProvenBeforeNode(assertion, (test) => {
|
|
75828
|
-
const comparison = stripParenExpression(test);
|
|
75829
|
-
if (!isNodeOfType(comparison, "BinaryExpression")) return false;
|
|
75830
|
-
return [[
|
|
75831
|
-
comparison.left,
|
|
75832
|
-
comparison.right,
|
|
75833
|
-
comparison.operator
|
|
75834
|
-
], [
|
|
75835
|
-
comparison.right,
|
|
75836
|
-
comparison.left,
|
|
75837
|
-
comparison.operator === "<" ? ">" : comparison.operator === ">" ? "<" : comparison.operator
|
|
75838
|
-
]].some(([candidateMaximum, candidateInitial, operator]) => {
|
|
75839
|
-
const candidateMaximumIdentifier = stripParenExpression(candidateMaximum);
|
|
75840
|
-
return operator === ">" && isNodeOfType(candidateMaximumIdentifier, "Identifier") && context.scopes.symbolFor(candidateMaximumIdentifier)?.id === maximumSymbol.id && areNodesLooselyEqual(stripParenExpression(candidateInitial), initialValue);
|
|
75841
|
-
});
|
|
75842
|
-
});
|
|
75843
|
-
};
|
|
75844
74962
|
const isDefinitelyNonNullishMapValue = (value) => {
|
|
75845
74963
|
if (!value) return false;
|
|
75846
74964
|
const expression = stripParenExpression(value);
|
|
@@ -75864,15 +74982,15 @@ const unwrapFalseBooleanGuard = (test) => {
|
|
|
75864
74982
|
};
|
|
75865
74983
|
const isEnsureThenMapGet = (assertion, receiver, lookupKey, context) => {
|
|
75866
74984
|
const stableLookupKey = stripParenExpression(lookupKey);
|
|
75867
|
-
|
|
75868
|
-
const lookupKeySymbol = isNodeOfType(stableLookupKey, "Identifier") ? context.scopes.symbolFor(stableLookupKey) : lookupKeyRoot ? context.scopes.symbolFor(lookupKeyRoot) : null;
|
|
75869
|
-
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;
|
|
75870
74986
|
const receiverSymbol = context.scopes.symbolFor(receiver);
|
|
75871
74987
|
if (!receiverSymbol) return false;
|
|
75872
74988
|
const receiverMatches = (candidate) => {
|
|
75873
74989
|
const target = stripParenExpression(candidate);
|
|
75874
74990
|
return isNodeOfType(target, "Identifier") && context.scopes.symbolFor(target)?.id === receiverSymbol.id;
|
|
75875
74991
|
};
|
|
74992
|
+
const lookupKeyExpression = stripParenExpression(lookupKey);
|
|
74993
|
+
const lookupKeySymbol = isNodeOfType(lookupKeyExpression, "Identifier") ? context.scopes.symbolFor(lookupKeyExpression) : null;
|
|
75876
74994
|
let child = assertion;
|
|
75877
74995
|
let ancestor = assertion.parent ?? null;
|
|
75878
74996
|
while (ancestor && !isFunctionLike$1(ancestor)) {
|
|
@@ -75892,7 +75010,7 @@ const isEnsureThenMapGet = (assertion, receiver, lookupKey, context) => {
|
|
|
75892
75010
|
const populationCall = populationCalls[0];
|
|
75893
75011
|
if (!populationCall) continue;
|
|
75894
75012
|
const populationCallStart = populationCall.range[0];
|
|
75895
|
-
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;
|
|
75896
75014
|
if (receiverSymbol.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] > populationCallStart && reference.identifier.range[0] < assertion.range[0])) continue;
|
|
75897
75015
|
if (!indexedRelevantCalls(ancestor).some((laterCall) => {
|
|
75898
75016
|
if (laterCall.range[0] <= populationCallStart || laterCall.range[0] >= assertion.range[0] || !isNodeOfType(laterCall.callee, "MemberExpression") || !receiverMatches(laterCall.callee.object)) return false;
|
|
@@ -76002,7 +75120,6 @@ const noNonNullAssertionOnMaybeUndefinedResult = defineRule({
|
|
|
76002
75120
|
const findReceiver = callee.object;
|
|
76003
75121
|
if (predicate && isExhaustiveLiteralTupleMapping(findReceiver, predicate, context)) return;
|
|
76004
75122
|
if (predicate && scopeProvesFindMatch(node, findReceiver, predicate, context)) return;
|
|
76005
|
-
if (predicate && isFindProvenByGuardedMaximum(node, findReceiver, predicate, context)) return;
|
|
76006
75123
|
if (predicate && isEnsureThenFind(node, findReceiver, predicate)) return;
|
|
76007
75124
|
}
|
|
76008
75125
|
if (methodName === "match") {
|
|
@@ -76012,7 +75129,6 @@ const noNonNullAssertionOnMaybeUndefinedResult = defineRule({
|
|
|
76012
75129
|
const regexKey = pattern ? regexComparableKey(pattern, context) : null;
|
|
76013
75130
|
if (pattern && isGuardedAnchoredCharacterMatch(node, matchReceiver, pattern)) return;
|
|
76014
75131
|
if (pattern && regexKey && isMatchProvenByFindUpUntilPredicate(node, matchReceiver, pattern, context)) return;
|
|
76015
|
-
if (pattern && regexKey && isMatchProvenByNormalizedFindUpUntilPredicate(node, matchReceiver, pattern, context)) return;
|
|
76016
75132
|
if (regexKey && scopeProvesMatchTested(node, regexKey, matchReceiver, context)) return;
|
|
76017
75133
|
}
|
|
76018
75134
|
if (methodName === "get") {
|
|
@@ -79951,23 +79067,16 @@ const MAX_INITIATOR_RESOLUTION_DEPTH = 3;
|
|
|
79951
79067
|
const STATE_DISPATCHER_HOOK_NAMES = new Set(["useState", "useReducer"]);
|
|
79952
79068
|
const REF_HOOK_NAMES = new Set(["useRef"]);
|
|
79953
79069
|
const MESSAGE$26 = "This promise chain runs in an effect, ends in a `.then` that sets state or mutates a ref, and has no `.catch` or enclosing try/catch, so a rejection leaves the state unset and surfaces as an unhandled rejection. Add a `.catch` handler on the chain (`.finally` does not count).";
|
|
79954
|
-
const
|
|
79070
|
+
const isKnownNonThenableHandlerReturn = (expression, context, visitedBindingIdentifiers = /* @__PURE__ */ new Set()) => {
|
|
79955
79071
|
const strippedExpression = stripParenExpression(expression);
|
|
79956
79072
|
if (isDefinitelyNonThenableValue(strippedExpression)) return true;
|
|
79957
|
-
if (isNodeOfType(strippedExpression, "CallExpression") && isNodeOfType(strippedExpression.callee, "MemberExpression")) {
|
|
79958
|
-
const receiver = stripParenExpression(strippedExpression.callee.object);
|
|
79959
|
-
if (isNodeOfType(receiver, "Identifier") && receiver.name === "Promise" && context.scopes.isGlobalReference(receiver) && getStaticPropertyName(strippedExpression.callee) === "resolve") {
|
|
79960
|
-
const resolvedValue = strippedExpression.arguments[0];
|
|
79961
|
-
return !resolvedValue || !isNodeOfType(resolvedValue, "SpreadElement") && isKnownNonRejectingHandlerReturn(resolvedValue, context, visitedBindingIdentifiers);
|
|
79962
|
-
}
|
|
79963
|
-
}
|
|
79964
79073
|
if (!isNodeOfType(strippedExpression, "Identifier")) return false;
|
|
79965
79074
|
if (strippedExpression.name === "undefined" && context.scopes.isGlobalReference(strippedExpression)) return true;
|
|
79966
79075
|
const symbol = context.scopes.symbolFor(strippedExpression);
|
|
79967
79076
|
if (!symbol || visitedBindingIdentifiers.has(symbol.bindingIdentifier)) return false;
|
|
79968
79077
|
visitedBindingIdentifiers.add(symbol.bindingIdentifier);
|
|
79969
79078
|
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
79970
|
-
return Boolean(initializer &&
|
|
79079
|
+
return Boolean(initializer && isKnownNonThenableHandlerReturn(initializer, context, visitedBindingIdentifiers));
|
|
79971
79080
|
};
|
|
79972
79081
|
const isKnownNonRejectingHandler = (argument, context) => {
|
|
79973
79082
|
if (!argument) return false;
|
|
@@ -79982,7 +79091,7 @@ const isKnownNonRejectingHandler = (argument, context) => {
|
|
|
79982
79091
|
canReject = true;
|
|
79983
79092
|
return false;
|
|
79984
79093
|
}
|
|
79985
|
-
if (isNodeOfType(child, "ReturnStatement") && child.argument && !
|
|
79094
|
+
if (isNodeOfType(child, "ReturnStatement") && child.argument && !isKnownNonThenableHandlerReturn(child.argument, context)) {
|
|
79986
79095
|
if (!isNodeOfType(stripParenExpression(child.argument), "CallExpression")) {
|
|
79987
79096
|
canReject = true;
|
|
79988
79097
|
return false;
|
|
@@ -80031,28 +79140,6 @@ const handlerHasPotentiallyThrowingMemberRead = (argument, context) => {
|
|
|
80031
79140
|
});
|
|
80032
79141
|
return hasPotentiallyThrowingMemberRead;
|
|
80033
79142
|
};
|
|
80034
|
-
const hasRejectionHandler = (chain, argument, context, allowTerminalCatchBlock) => {
|
|
80035
|
-
if (!argument) return false;
|
|
80036
|
-
if (!handlerHasPotentiallyThrowingMemberRead(argument, context) && (chainCarriesRejectionHandler(chain, context.scopes) || isKnownNonRejectingHandler(argument, context))) return true;
|
|
80037
|
-
if (!allowTerminalCatchBlock) return false;
|
|
80038
|
-
const candidate = stripParenExpression(argument);
|
|
80039
|
-
const handler = isNodeOfType(candidate, "Identifier") ? resolveExactLocalFunction(candidate, context.scopes) : candidate;
|
|
80040
|
-
if (!handler || !isFunctionLike$1(handler)) return isNodeOfType(candidate, "MemberExpression") || isNodeOfType(candidate, "Identifier") && candidate.name !== "undefined";
|
|
80041
|
-
if (!isNodeOfType(handler.body, "BlockStatement")) return false;
|
|
80042
|
-
let doesExplicitlyReject = false;
|
|
80043
|
-
walkOwnFunctionScope(handler, (child) => {
|
|
80044
|
-
if (doesExplicitlyReject) return false;
|
|
80045
|
-
if (isNodeOfType(child, "ThrowStatement") || isNodeOfType(child, "AwaitExpression")) {
|
|
80046
|
-
doesExplicitlyReject = true;
|
|
80047
|
-
return false;
|
|
80048
|
-
}
|
|
80049
|
-
if (isNodeOfType(child, "ReturnStatement") && child.argument && !isKnownNonRejectingHandlerReturn(child.argument, context)) {
|
|
80050
|
-
doesExplicitlyReject = true;
|
|
80051
|
-
return false;
|
|
80052
|
-
}
|
|
80053
|
-
});
|
|
80054
|
-
return !doesExplicitlyReject;
|
|
80055
|
-
};
|
|
80056
79143
|
const walkPromiseChain = (chainExpression, context) => {
|
|
80057
79144
|
let cursor = stripParenExpression(chainExpression);
|
|
80058
79145
|
let hasCatch = false;
|
|
@@ -80064,9 +79151,10 @@ const walkPromiseChain = (chainExpression, context) => {
|
|
|
80064
79151
|
while (isNodeOfType(cursor, "CallExpression") && isNodeOfType(cursor.callee, "MemberExpression") && PROMISE_METHOD_NAMES.has(getStaticPropertyName(cursor.callee) ?? "")) {
|
|
80065
79152
|
const methodName = getStaticPropertyName(cursor.callee);
|
|
80066
79153
|
const rejectionHandlerArgument = methodName === "catch" ? cursor.arguments[0] : cursor.arguments[1];
|
|
80067
|
-
|
|
79154
|
+
const hasAbsorbingRejectionHandler = !handlerHasPotentiallyThrowingMemberRead(rejectionHandlerArgument, context) && (chainCarriesRejectionHandler(cursor, context.scopes) || isKnownNonRejectingHandler(rejectionHandlerArgument, context));
|
|
79155
|
+
if (!didReachTerminalThen && methodName === "catch" && hasAbsorbingRejectionHandler) hasCatch = true;
|
|
80068
79156
|
if (methodName === "then") {
|
|
80069
|
-
if (!didReachTerminalThen &&
|
|
79157
|
+
if (!didReachTerminalThen && hasAbsorbingRejectionHandler) hasRejectionHandlerArgument = true;
|
|
80070
79158
|
didReachTerminalThen = true;
|
|
80071
79159
|
sawThen = true;
|
|
80072
79160
|
const callbackArgument = cursor.arguments[0];
|
|
@@ -82393,45 +81481,6 @@ const noRefCallbackCleanupBeforeReact19 = defineRule({
|
|
|
82393
81481
|
} })
|
|
82394
81482
|
});
|
|
82395
81483
|
//#endregion
|
|
82396
|
-
//#region src/plugin/utils/contains-non-deterministic-source.ts
|
|
82397
|
-
const NON_DETERMINISTIC_MEMBER_CALLS = new Set([
|
|
82398
|
-
"Math.random",
|
|
82399
|
-
"Date.now",
|
|
82400
|
-
"performance.now",
|
|
82401
|
-
"crypto.randomUUID",
|
|
82402
|
-
"crypto.getRandomValues"
|
|
82403
|
-
]);
|
|
82404
|
-
const NON_DETERMINISTIC_ID_GENERATOR_NAMES = new Set([
|
|
82405
|
-
"nanoid",
|
|
82406
|
-
"uuid",
|
|
82407
|
-
"cuid",
|
|
82408
|
-
"ulid",
|
|
82409
|
-
"createId"
|
|
82410
|
-
]);
|
|
82411
|
-
const isZeroArgDateConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Date" && (node.arguments?.length ?? 0) === 0;
|
|
82412
|
-
const containsNonDeterministicSource = (root) => {
|
|
82413
|
-
let found = false;
|
|
82414
|
-
walkAst(root, (child) => {
|
|
82415
|
-
if (found) return false;
|
|
82416
|
-
if (isFunctionLike$1(child)) return false;
|
|
82417
|
-
if (isZeroArgDateConstruction(child)) {
|
|
82418
|
-
found = true;
|
|
82419
|
-
return false;
|
|
82420
|
-
}
|
|
82421
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
82422
|
-
const callee = child.callee;
|
|
82423
|
-
if (isNodeOfType(callee, "Identifier") && NON_DETERMINISTIC_ID_GENERATOR_NAMES.has(callee.name)) {
|
|
82424
|
-
found = true;
|
|
82425
|
-
return false;
|
|
82426
|
-
}
|
|
82427
|
-
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && isNodeOfType(callee.property, "Identifier") && NON_DETERMINISTIC_MEMBER_CALLS.has(`${callee.object.name}.${callee.property.name}`)) {
|
|
82428
|
-
found = true;
|
|
82429
|
-
return false;
|
|
82430
|
-
}
|
|
82431
|
-
});
|
|
82432
|
-
return found;
|
|
82433
|
-
};
|
|
82434
|
-
//#endregion
|
|
82435
81484
|
//#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
|
|
82436
81485
|
const REPEATED_ANCESTOR_TYPES = new Set([
|
|
82437
81486
|
"DoWhileStatement",
|
|
@@ -82462,75 +81511,45 @@ const resolveImmutableInitializationValue = (node, scopes, visitedSymbolIds = /*
|
|
|
82462
81511
|
};
|
|
82463
81512
|
const isProvablyTruthyInitializationValue = (node, scopes) => {
|
|
82464
81513
|
const expression = resolveImmutableInitializationValue(node, scopes);
|
|
82465
|
-
|
|
82466
|
-
if (isNodeOfType(expression, "CallExpression")) {
|
|
82467
|
-
const callee = stripParenExpression(expression.callee);
|
|
82468
|
-
return isNodeOfType(callee, "Identifier") && callee.name.startsWith("create");
|
|
82469
|
-
}
|
|
82470
|
-
return isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "ArrayExpression") || isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "ClassExpression");
|
|
81514
|
+
return Boolean(expression && (isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "ArrayExpression") || isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "ClassExpression")));
|
|
82471
81515
|
};
|
|
82472
|
-
const
|
|
81516
|
+
const getInitializationConstructorName = (node, scopes) => {
|
|
82473
81517
|
const expression = resolveImmutableInitializationValue(node, scopes);
|
|
82474
81518
|
if (!expression) return null;
|
|
82475
|
-
if (isNodeOfType(expression, "NewExpression")
|
|
81519
|
+
if (isNodeOfType(expression, "NewExpression")) {
|
|
82476
81520
|
const callee = stripParenExpression(expression.callee);
|
|
82477
|
-
|
|
82478
|
-
return callee.name.startsWith("create") && callee.name.length > 6 ? callee.name.slice(6) : callee.name;
|
|
81521
|
+
return isNodeOfType(callee, "Identifier") ? callee.name : null;
|
|
82479
81522
|
}
|
|
82480
81523
|
return null;
|
|
82481
81524
|
};
|
|
82482
|
-
const isMatchingReturnType = (typeNode, initializationValue, scopes) => {
|
|
82483
|
-
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
82484
|
-
const typeName = typeNode.typeName;
|
|
82485
|
-
if (!isNodeOfType(typeName, "Identifier") || typeName.name !== "ReturnType") return false;
|
|
82486
|
-
const [returnTypeArgument] = typeNode.typeArguments?.params ?? [];
|
|
82487
|
-
if (!returnTypeArgument || !isNodeOfType(returnTypeArgument, "TSTypeQuery")) return false;
|
|
82488
|
-
const queriedName = returnTypeArgument.exprName;
|
|
82489
|
-
const expression = stripParenExpression(initializationValue);
|
|
82490
|
-
if (!isNodeOfType(queriedName, "Identifier") || !isNodeOfType(expression, "CallExpression")) return false;
|
|
82491
|
-
const callee = stripParenExpression(expression.callee);
|
|
82492
|
-
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
82493
|
-
const queriedSymbol = scopes.symbolFor(queriedName);
|
|
82494
|
-
const calleeSymbol = scopes.symbolFor(callee);
|
|
82495
|
-
return queriedSymbol && calleeSymbol ? queriedSymbol.id === calleeSymbol.id : queriedName.name === callee.name;
|
|
82496
|
-
};
|
|
82497
81525
|
const isClosedTruthyTypeDomain = (typeNode, initializationValue, scopes) => {
|
|
82498
81526
|
const initializationExpression = stripParenExpression(initializationValue);
|
|
82499
81527
|
if (isNodeOfType(typeNode, "TSTypeLiteral")) return isNodeOfType(initializationExpression, "ObjectExpression");
|
|
82500
81528
|
if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return isNodeOfType(initializationExpression, "ArrayExpression");
|
|
82501
81529
|
if (isNodeOfType(typeNode, "TSFunctionType") || isNodeOfType(typeNode, "TSConstructorType")) return isNodeOfType(initializationExpression, "ArrowFunctionExpression") || isNodeOfType(initializationExpression, "FunctionExpression") || isNodeOfType(initializationExpression, "ClassExpression");
|
|
82502
81530
|
if (isNodeOfType(typeNode, "TSObjectKeyword")) return true;
|
|
82503
|
-
if (isNodeOfType(typeNode, "TSIndexedAccessType")) return isNodeOfType(initializationExpression, "ObjectExpression");
|
|
82504
81531
|
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
82505
81532
|
const typeName = typeNode.typeName;
|
|
82506
|
-
|
|
82507
|
-
return isNodeOfType(typeName, "Identifier") && typeName.name === getInitializationValueName(initializationExpression, scopes);
|
|
81533
|
+
return isNodeOfType(typeName, "Identifier") && typeName.name === getInitializationConstructorName(initializationExpression, scopes);
|
|
82508
81534
|
};
|
|
82509
81535
|
const refHasClosedFalsySentinelDomain = (refSymbol, initializationValue, scopes) => {
|
|
82510
81536
|
const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
82511
81537
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
82512
81538
|
const [initialValue] = initializer.arguments ?? [];
|
|
82513
|
-
if (initialValue
|
|
81539
|
+
if (!initialValue || isNodeOfType(initialValue, "SpreadElement") || !isEmptySentinel(initialValue, scopes)) return false;
|
|
82514
81540
|
const [declaredType] = initializer.typeArguments?.params ?? [];
|
|
82515
|
-
if (!declaredType) return false;
|
|
82516
|
-
|
|
81541
|
+
if (!declaredType || !isNodeOfType(declaredType, "TSUnionType")) return false;
|
|
81542
|
+
let hasEmptySentinel = false;
|
|
82517
81543
|
let hasTruthyDomain = false;
|
|
82518
|
-
for (const memberType of
|
|
82519
|
-
if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword"))
|
|
81544
|
+
for (const memberType of declaredType.types ?? []) {
|
|
81545
|
+
if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword")) {
|
|
81546
|
+
hasEmptySentinel = true;
|
|
81547
|
+
continue;
|
|
81548
|
+
}
|
|
82520
81549
|
if (!isClosedTruthyTypeDomain(memberType, initializationValue, scopes)) return false;
|
|
82521
81550
|
hasTruthyDomain = true;
|
|
82522
81551
|
}
|
|
82523
|
-
return hasTruthyDomain;
|
|
82524
|
-
};
|
|
82525
|
-
const refHasEmptySentinelInitializer = (refSymbol, scopes) => {
|
|
82526
|
-
const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
82527
|
-
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
82528
|
-
const [initialValue] = initializer.arguments ?? [];
|
|
82529
|
-
return Boolean(!initialValue || !isNodeOfType(initialValue, "SpreadElement") && isEmptySentinel(initialValue, scopes));
|
|
82530
|
-
};
|
|
82531
|
-
const refHasDeclaredType = (refSymbol) => {
|
|
82532
|
-
const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
82533
|
-
return Boolean(initializer && isNodeOfType(initializer, "CallExpression") && (initializer.typeArguments?.params.length ?? 0) > 0);
|
|
81552
|
+
return hasEmptySentinel && hasTruthyDomain;
|
|
82534
81553
|
};
|
|
82535
81554
|
const isSafeRefIdentifierUse = (identifier) => {
|
|
82536
81555
|
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
@@ -82562,40 +81581,25 @@ const expressionContainsRefCurrent = (expression, refSymbol, scopes) => {
|
|
|
82562
81581
|
});
|
|
82563
81582
|
return didFindRefCurrent;
|
|
82564
81583
|
};
|
|
82565
|
-
const
|
|
82566
|
-
|
|
82567
|
-
|
|
82568
|
-
|
|
82569
|
-
|
|
82570
|
-
|
|
82571
|
-
|
|
82572
|
-
if (!isInputIndependent) return false;
|
|
82573
|
-
if (resolveReactRefSymbol(child, scopes)) return false;
|
|
82574
|
-
if (!isNodeOfType(child, "Identifier")) return;
|
|
82575
|
-
const symbol = scopes.symbolFor(child);
|
|
82576
|
-
if (!symbol) return;
|
|
82577
|
-
if (symbol.kind === "import") return false;
|
|
82578
|
-
if (symbol.kind === "let" || symbol.kind === "var" || symbol.kind === "using") {
|
|
82579
|
-
isInputIndependent = false;
|
|
82580
|
-
return false;
|
|
81584
|
+
const hasNoCompetingRefCurrentWrite = (branchRoot, assignmentExpression, refSymbol, scopes) => {
|
|
81585
|
+
let writeCount = 0;
|
|
81586
|
+
walkAst(branchRoot, (child) => {
|
|
81587
|
+
if (writeCount > 1) return false;
|
|
81588
|
+
if (isNodeOfType(child, "AssignmentExpression")) {
|
|
81589
|
+
if (expressionContainsRefCurrent(child.left, refSymbol, scopes)) writeCount++;
|
|
81590
|
+
return;
|
|
82581
81591
|
}
|
|
82582
|
-
if (
|
|
82583
|
-
|
|
82584
|
-
|
|
82585
|
-
return false;
|
|
81592
|
+
if (isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") {
|
|
81593
|
+
if (expressionContainsRefCurrent(child.argument, refSymbol, scopes)) writeCount++;
|
|
81594
|
+
return;
|
|
82586
81595
|
}
|
|
82587
|
-
if (
|
|
82588
|
-
|
|
82589
|
-
return false;
|
|
81596
|
+
if (isNodeOfType(child, "ForInStatement") || isNodeOfType(child, "ForOfStatement")) {
|
|
81597
|
+
if (expressionContainsRefCurrent(child.left, refSymbol, scopes)) writeCount++;
|
|
82590
81598
|
}
|
|
82591
|
-
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
82592
|
-
visitedSymbolIds.add(symbol.id);
|
|
82593
|
-
if (!isInitializationInputIndependent(symbol.initializer, renderOwner, scopes, visitedSymbolIds)) isInputIndependent = false;
|
|
82594
|
-
return false;
|
|
82595
81599
|
});
|
|
82596
|
-
return
|
|
81600
|
+
return writeCount === 1 && expressionContainsRefCurrent(assignmentExpression.left, refSymbol, scopes);
|
|
82597
81601
|
};
|
|
82598
|
-
const
|
|
81602
|
+
const isEmptySentinel = (node, scopes) => isNodeOfType(node, "Literal") && node.value === null || isNodeOfType(node, "Identifier") && node.name === "undefined" && scopes.isGlobalReference(node);
|
|
82599
81603
|
const hasRepeatedExecutionAncestor = (node, stop) => {
|
|
82600
81604
|
let ancestor = node.parent;
|
|
82601
81605
|
while (ancestor && ancestor !== stop) {
|
|
@@ -82625,27 +81629,6 @@ const canExecuteTogether = (firstConstraints, secondConstraints) => {
|
|
|
82625
81629
|
}
|
|
82626
81630
|
return true;
|
|
82627
81631
|
};
|
|
82628
|
-
const hasNoCoExecutableCompetingWrite = (assignmentExpression, renderOwner, refSymbol, scopes) => {
|
|
82629
|
-
const assignmentConstraints = getBranchConstraints(assignmentExpression, renderOwner);
|
|
82630
|
-
const synchronouslyInvokedFunctions = collectSynchronouslyEffectInvokedFunctions(renderOwner, scopes);
|
|
82631
|
-
let hasCompetingWrite = false;
|
|
82632
|
-
walkAst(renderOwner, (child) => {
|
|
82633
|
-
if (hasCompetingWrite) return false;
|
|
82634
|
-
let writtenExpression = null;
|
|
82635
|
-
if (isNodeOfType(child, "AssignmentExpression")) {
|
|
82636
|
-
if (child === assignmentExpression) return;
|
|
82637
|
-
writtenExpression = child.left;
|
|
82638
|
-
} else if (isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") writtenExpression = child.argument;
|
|
82639
|
-
else if (isNodeOfType(child, "ForInStatement") || isNodeOfType(child, "ForOfStatement")) writtenExpression = child.left;
|
|
82640
|
-
const deferredExecutionBoundary = findDeferredExecutionBoundary(child);
|
|
82641
|
-
const deferredWriteValue = isNodeOfType(child, "AssignmentExpression") && child.operator === "=" ? resolveImmutableInitializationValue(child.right, scopes) : null;
|
|
82642
|
-
const isDeferredTruthyWrite = deferredExecutionBoundary !== null && deferredExecutionBoundary !== renderOwner && !synchronouslyInvokedFunctions.has(deferredExecutionBoundary) && !executesDuringRender(deferredExecutionBoundary, scopes) && deferredWriteValue !== null && !isNodeOfType(deferredWriteValue, "CallExpression") && isProvablyTruthyInitializationValue(deferredWriteValue, scopes);
|
|
82643
|
-
if (!writtenExpression || isDeferredTruthyWrite || !expressionContainsRefCurrent(writtenExpression, refSymbol, scopes) || !canExecuteTogether(assignmentConstraints, getBranchConstraints(child, renderOwner))) return;
|
|
82644
|
-
hasCompetingWrite = true;
|
|
82645
|
-
return false;
|
|
82646
|
-
});
|
|
82647
|
-
return !hasCompetingWrite;
|
|
82648
|
-
};
|
|
82649
81632
|
const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol, scopes) => {
|
|
82650
81633
|
const assignmentConstraints = getBranchConstraints(assignmentExpression, branchRoot);
|
|
82651
81634
|
const assignmentStart = getRangeStart(assignmentExpression);
|
|
@@ -82661,17 +81644,16 @@ const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol
|
|
|
82661
81644
|
});
|
|
82662
81645
|
return !hasCoExecutableWrite;
|
|
82663
81646
|
};
|
|
82664
|
-
const isPredictableGuardedInitialization = (assignmentExpression, guardedBranch, renderOwner, refSymbol, scopes, requiresClosedTruthyDomain) => refHasEmptySentinelInitializer(refSymbol, scopes) && isPredictableInitializationValue(assignmentExpression.right, refSymbol, renderOwner, scopes, requiresClosedTruthyDomain) && !hasRepeatedExecutionAncestor(assignmentExpression, guardedBranch) && (guardedBranch === renderOwner || !hasRepeatedExecutionAncestor(guardedBranch, renderOwner)) && hasNoPriorCoExecutableWrite(assignmentExpression, renderOwner, refSymbol, scopes) && hasNoCoExecutableCompetingWrite(assignmentExpression, renderOwner, refSymbol, scopes) && refDoesNotEscape(renderOwner, refSymbol, scopes);
|
|
82665
81647
|
const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
|
|
81648
|
+
if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return true;
|
|
81649
|
+
if (assignmentExpression.operator !== "=") return false;
|
|
82666
81650
|
const renderOwner = findRenderPhaseComponentOrHook(assignmentExpression, scopes);
|
|
82667
81651
|
if (!renderOwner) return false;
|
|
82668
|
-
if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return isPredictableGuardedInitialization(assignmentExpression, renderOwner, renderOwner, refSymbol, scopes, assignmentExpression.operator === "||=");
|
|
82669
|
-
if (assignmentExpression.operator !== "=") return false;
|
|
82670
81652
|
let descendant = assignmentExpression;
|
|
82671
81653
|
let ancestor = descendant.parent;
|
|
82672
81654
|
while (ancestor) {
|
|
82673
81655
|
const test = isNodeOfType(ancestor, "IfStatement") ? stripParenExpression(ancestor.test) : null;
|
|
82674
|
-
if (isNodeOfType(ancestor, "IfStatement") && test && isNodeOfType(test, "UnaryExpression") && test.operator === "!" && isSameRefCurrentAlias(test.argument, refSymbol, scopes) && ancestor.consequent === descendant &&
|
|
81656
|
+
if (isNodeOfType(ancestor, "IfStatement") && test && isNodeOfType(test, "UnaryExpression") && test.operator === "!" && isSameRefCurrentAlias(test.argument, refSymbol, scopes) && ancestor.consequent === descendant && isProvablyTruthyInitializationValue(assignmentExpression.right, scopes) && refHasClosedFalsySentinelDomain(refSymbol, assignmentExpression.right, scopes) && !hasRepeatedExecutionAncestor(assignmentExpression, ancestor.consequent) && !hasRepeatedExecutionAncestor(ancestor, renderOwner) && hasNoPriorCoExecutableWrite(assignmentExpression, ancestor.consequent, refSymbol, scopes) && hasNoCompetingRefCurrentWrite(renderOwner, assignmentExpression, refSymbol, scopes) && refDoesNotEscape(renderOwner, refSymbol, scopes)) return true;
|
|
82675
81657
|
if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(test, "BinaryExpression") && [
|
|
82676
81658
|
"===",
|
|
82677
81659
|
"==",
|
|
@@ -82681,7 +81663,7 @@ const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes)
|
|
|
82681
81663
|
const { left, right } = test;
|
|
82682
81664
|
const comparesEmptySentinel = isSameRefCurrentAlias(left, refSymbol, scopes) && isEmptySentinel(right, scopes) || isSameRefCurrentAlias(right, refSymbol, scopes) && isEmptySentinel(left, scopes);
|
|
82683
81665
|
const guardedBranch = test.operator === "===" || test.operator === "==" ? ancestor.consequent : ancestor.alternate;
|
|
82684
|
-
if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch &&
|
|
81666
|
+
if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch && !hasRepeatedExecutionAncestor(assignmentExpression, guardedBranch) && hasNoPriorCoExecutableWrite(assignmentExpression, guardedBranch, refSymbol, scopes)) return true;
|
|
82685
81667
|
}
|
|
82686
81668
|
descendant = ancestor;
|
|
82687
81669
|
ancestor = descendant.parent;
|
|
@@ -110488,6 +109470,9 @@ const rerenderFunctionalSetstate = defineRule({
|
|
|
110488
109470
|
} })
|
|
110489
109471
|
});
|
|
110490
109472
|
//#endregion
|
|
109473
|
+
//#region src/plugin/utils/is-trivial-built-in-construction.ts
|
|
109474
|
+
const isTrivialBuiltInConstruction = (expression) => isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && TRIVIAL_CONSTRUCTOR_NAMES.has(expression.callee.name) && (expression.arguments ?? []).length === 0;
|
|
109475
|
+
//#endregion
|
|
110491
109476
|
//#region src/plugin/rules/state-and-effects/rerender-lazy-ref-init.ts
|
|
110492
109477
|
const rerenderLazyRefInit = defineRule({
|
|
110493
109478
|
id: "rerender-lazy-ref-init",
|
|
@@ -110506,6 +109491,7 @@ const rerenderLazyRefInit = defineRule({
|
|
|
110506
109491
|
const memberPropertyName = isNodeOfType(callee, "MemberExpression") && (isNodeOfType(callee.property, "Identifier") || isNodeOfType(callee.property, "PrivateIdentifier")) ? callee.property.name : null;
|
|
110507
109492
|
const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : memberPropertyName ?? "fn";
|
|
110508
109493
|
if (TRIVIAL_INITIALIZER_NAMES.has(calleeName)) return;
|
|
109494
|
+
if (isTrivialBuiltInConstruction(initializer)) return;
|
|
110509
109495
|
if (isPlainCall && isReactHookName(calleeName)) return;
|
|
110510
109496
|
const callShape = isNewCall ? `new ${calleeName}()` : `${calleeName}()`;
|
|
110511
109497
|
context.report({
|