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.
Files changed (2) hide show
  1. package/dist/index.js +156 -1170
  2. 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
- return Boolean(isNodeOfType(callNode, "CallExpression") && isSynchronousIteratorCallbackCall(callNode, functionNode));
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 stableOptionsNode = optionsNode ? resolveReadOnlyEventListenerOptions(optionsNode, context) : null;
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 (!stableOptionsNode) return null;
18384
- const unwrappedOptions = stripParenExpression(stableOptionsNode);
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
- const currentNode = findTransparentExpressionRoot(resourceNode);
18432
- const parentNode = currentNode.parent;
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) ?? findCallbackRefReplacementReleaseGuard(releaseCall, usageFunction, releaseReceiverKey, registrationReceiverKey, context) ?? releaseCall;
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
- const registrationCallee = isNodeOfType(usage.node, "CallExpression") ? stripParenExpression(usage.node.callee) : null;
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.handlerKey ?? usage.eventKey : usage.handlerKey;
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
- const arrayNode = stripParenExpression(node);
29581
- if (!isNodeOfType(arrayNode, "ArrayExpression")) return false;
29582
- const elements = arrayNode.elements ?? [];
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 isNonMutatingSmallArrayMethodReference = (identifier) => {
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, context.scopes)) return;
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 getDirectBlockEntry = (node, functionNode) => {
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 && !acceptedAssignments.has(candidate)) {
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 collectCleanupBackedLifecycleAssignments = (effectCallback, guardKey, context) => {
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
- while (cursor && cursor !== protectingTry) {
69452
- if (isNodeOfType(cursor, "IfStatement")) {
69453
- if (cursor.consequent !== child || cursor.alternate !== null) return null;
69454
- positive.push(...collectLogicalOperands(cursor.test, "&&"));
69455
- } else if (isNodeOfType(cursor, "LogicalExpression")) {
69456
- if (cursor.operator !== "&&" || cursor.right !== child) return null;
69457
- positive.push(...collectLogicalOperands(cursor.left, "&&"));
69458
- } else if (isNodeOfType(cursor, "BlockStatement")) {
69459
- const childIndex = cursor.body.findIndex((statement) => statement === child);
69460
- if (childIndex !== -1) for (const statement of cursor.body.slice(0, childIndex)) {
69461
- if (!isNodeOfType(statement, "IfStatement") || statement.alternate !== null || !isUnconditionalReturnBranch(statement.consequent)) continue;
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
- return cursor === protectingTry && positive.length + negative.length > 0 ? {
69469
- positive,
69470
- negative
69471
- } : null;
69472
- };
69473
- const isPositiveFinalizerGuard = (expression, resetNode, functionNode, truthySets, firstRiskyAwait, context) => isCleanupBackedLifecycleGuard(expression, functionNode, context) || isClaimedOwnershipComparison(expression, "owns", functionNode, truthySets, firstRiskyAwait, resetNode, context);
69474
- const isNegativeFinalizerGuard = (expression, resetNode, functionNode, truthySets, firstRiskyAwait, context) => {
69475
- const stripped = stripParenExpression(expression);
69476
- if (isNodeOfType(stripped, "UnaryExpression") && stripped.operator === "!") return isPositiveFinalizerGuard(stripped.argument, resetNode, functionNode, truthySets, firstRiskyAwait, context);
69477
- return isClaimedOwnershipComparison(stripped, "lost", functionNode, truthySets, firstRiskyAwait, resetNode, context);
69478
- };
69479
- const isFinalizerResetProvablyGuarded = (resetNode, protectingTry, functionNode, truthySets, firstRiskyAwait, context) => {
69480
- const guards = collectFinalizerGuardExpressions(resetNode, protectingTry);
69481
- return Boolean(guards && guards.positive.every((guard) => isPositiveFinalizerGuard(guard, resetNode, functionNode, truthySets, firstRiskyAwait, context)) && guards.negative.every((guard) => isNegativeFinalizerGuard(guard, resetNode, functionNode, truthySets, firstRiskyAwait, context)));
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(finderCall.callee);
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 = finderCall.arguments[1];
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
- const lookupKeyRoot = isNodeOfType(stableLookupKey, "MemberExpression") ? getRootIdentifier(stableLookupKey) : null;
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])) || Boolean(lookupKeySymbol && isNodeOfType(stableLookupKey, "MemberExpression") && subtreeWritesSymbol(ancestor, new Set([lookupKeySymbol.id]), context, void 0, assertion))) continue;
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 isKnownNonRejectingHandlerReturn = (expression, context, visitedBindingIdentifiers = /* @__PURE__ */ new Set()) => {
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 && isKnownNonRejectingHandlerReturn(initializer, context, visitedBindingIdentifiers));
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 && !isKnownNonRejectingHandlerReturn(child.argument, context)) {
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
- if (!didReachTerminalThen && methodName === "catch" && hasRejectionHandler(cursor, rejectionHandlerArgument, context, true)) hasCatch = true;
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 && hasRejectionHandler(cursor, rejectionHandlerArgument, context, false)) hasRejectionHandlerArgument = true;
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
- if (!expression) return false;
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 getInitializationValueName = (node, scopes) => {
81516
+ const getInitializationConstructorName = (node, scopes) => {
82473
81517
  const expression = resolveImmutableInitializationValue(node, scopes);
82474
81518
  if (!expression) return null;
82475
- if (isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "CallExpression")) {
81519
+ if (isNodeOfType(expression, "NewExpression")) {
82476
81520
  const callee = stripParenExpression(expression.callee);
82477
- if (!isNodeOfType(callee, "Identifier")) return null;
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
- if (isNodeOfType(initializationExpression, "ObjectExpression") || isMatchingReturnType(typeNode, initializationExpression, scopes)) return true;
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 && isNodeOfType(initialValue, "SpreadElement") || initialValue && !isEmptySentinel(initialValue, scopes)) return false;
81539
+ if (!initialValue || isNodeOfType(initialValue, "SpreadElement") || !isEmptySentinel(initialValue, scopes)) return false;
82514
81540
  const [declaredType] = initializer.typeArguments?.params ?? [];
82515
- if (!declaredType) return false;
82516
- const domainTypes = isNodeOfType(declaredType, "TSUnionType") ? declaredType.types : [declaredType];
81541
+ if (!declaredType || !isNodeOfType(declaredType, "TSUnionType")) return false;
81542
+ let hasEmptySentinel = false;
82517
81543
  let hasTruthyDomain = false;
82518
- for (const memberType of domainTypes) {
82519
- if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword")) continue;
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 isEmptySentinel = (node, scopes) => {
82566
- const expression = stripParenExpression(node);
82567
- return isNodeOfType(expression, "Literal") && expression.value === null || isNodeOfType(expression, "Identifier") && expression.name === "undefined" && scopes.isGlobalReference(expression);
82568
- };
82569
- const isInitializationInputIndependent = (node, renderOwner, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
82570
- let isInputIndependent = true;
82571
- walkAst(node, (child) => {
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 (isOutsideAllFunctions(symbol)) return false;
82583
- if (symbol.kind === "parameter") {
82584
- if (symbol.scope.node === renderOwner) isInputIndependent = false;
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 (!symbol.initializer || symbol.references.some((reference) => reference.flag !== "read")) {
82588
- isInputIndependent = false;
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 isInputIndependent;
81600
+ return writeCount === 1 && expressionContainsRefCurrent(assignmentExpression.left, refSymbol, scopes);
82597
81601
  };
82598
- const isPredictableInitializationValue = (node, refSymbol, renderOwner, scopes, requiresClosedTruthyDomain) => isInitializationInputIndependent(node, renderOwner, scopes) && !containsNonDeterministicSource(node) && (isProvablyTruthyInitializationValue(node, scopes) && (!requiresClosedTruthyDomain || !refHasDeclaredType(refSymbol)) || refHasClosedFalsySentinelDomain(refSymbol, node, scopes));
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 && isPredictableGuardedInitialization(assignmentExpression, ancestor.consequent, renderOwner, refSymbol, scopes, true)) return true;
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 && isPredictableGuardedInitialization(assignmentExpression, guardedBranch, renderOwner, refSymbol, scopes, false)) return true;
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({