oxlint-plugin-react-doctor 0.9.2-dev.444e177 → 0.9.2-dev.5f23826

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 +1825 -166
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -388,7 +388,7 @@ const SOURCE_FILE_PATTERN = /\.(?:[cm]?[jt]sx?)$/i;
388
388
  const SCRIPT_SOURCE_FILE_PATTERN = /\.(?:[cm]?[jt]sx?|py|php)$/i;
389
389
  const DATABASE_SOURCE_FILE_PATTERN = /\.(?:[cm]?[jt]sx?|py)$/i;
390
390
  const SERVER_CONTEXT_PATTERN = /(?:^|\/)(?:api|backend|server|servers|middleware|route|routes|functions|lambdas|workers)(?:\/|$)|(?:^|\/)[^/]+\.server\.[cm]?[jt]sx?$/i;
391
- const TEST_CONTEXT_PATTERN = /(?:^|\/)(?:__fixtures__|__mocks__|__tests__|__integration__|fixtures|mocks|test|tests|testdata|test-data|e2e|playwright|cypress|specs?)(?:\/|$)|\.(?:test|spec|e2e|e2e-spec|integration-test|fixture|fixtures|stories|story)\.[cm]?[jt]sx?$|(?:^|\/)(?:playwright|cypress|vitest|jest|karma)[^/]*\.conf(?:ig)?\.[cm]?[jt]s$|(?:^|\/)(?:test_[^/]+|[^/]+_test|conftest)\.py$|\.env\.[^/]*(?:test|e2e)[^/]*$/i;
391
+ const TEST_CONTEXT_PATTERN = /(?:^|\/)(?:__fixtures__|__mocks__|__tests__|__integration__|fixtures|mocks|test|tests|testdata|test-data|e2e|playwright|cypress|specs?)(?:\/|$)|^(?:test|spec)-[^/]+\.[cm]?[jt]sx?$|\.(?:test|spec|e2e|e2e-spec|integration-test|fixture|fixtures|stories|story)\.[cm]?[jt]sx?$|(?:^|\/)(?:playwright|cypress|vitest|jest|karma)[^/]*\.conf(?:ig)?\.[cm]?[jt]s$|(?:^|\/)(?:test_[^/]+|[^/]+_test|conftest)\.py$|\.env\.[^/]*(?:test|e2e)[^/]*$/i;
392
392
  const BUILD_CONFIG_FILE_PATTERN = /(?:^|\/)(?:vite|next|nuxt|astro|remix|webpack|rollup|rspack|rsbuild|esbuild|tsup|metro|expo|babel|tailwind|postcss|svelte|farm|parcel|snowpack)[^/]*\.config\.[cm]?[jt]sx?$/i;
393
393
  const BUILD_SCRIPT_CONTEXT_PATTERN = /(?:^|\/)scripts(?:\/|$)/i;
394
394
  const DEMO_CONTEXT_PATTERN = /(?:^|\/)(?:examples?|tutorials?|demos?|samples?|playgrounds?)(?:\/|$)/i;
@@ -665,19 +665,6 @@ 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
- ]);
681
668
  const SETTER_PATTERN = /^set[A-Z]/;
682
669
  const RENDER_FUNCTION_PATTERN = /^render[A-Z]/;
683
670
  const UPPERCASE_PATTERN = /^[A-Z]/;
@@ -5887,6 +5874,7 @@ const isInlineFunctionExpression = (node) => Boolean(node && (isNodeOfType(node,
5887
5874
  //#endregion
5888
5875
  //#region src/plugin/rules/js-performance/async-await-in-loop.ts
5889
5876
  const LOOP_STATEMENT_TYPES$1 = new Set(LOOP_TYPES);
5877
+ const ORDERED_OUTPUT_INSERTION_METHOD_NAMES = new Set(["push", "unshift"]);
5890
5878
  const findFirstAwaitOutsideNestedFunctions = (block, skipNestedLoops = false) => {
5891
5879
  let firstAwait = null;
5892
5880
  walkAst(block, (child) => {
@@ -5953,7 +5941,151 @@ const isAwaitingManualPromiseWait = (awaitNode) => {
5953
5941
  });
5954
5942
  return isWaitLike;
5955
5943
  };
5956
- const isIntentionallySequentialAwait = (awaitNode, context) => isAwaitingPossiblyMutatedMemberCall(awaitNode, context) || isAwaitingSleepLikeCall(awaitNode, context) || isAwaitingPromiseConcurrencyCall(awaitNode) || isAwaitingManualPromiseWait(awaitNode);
5944
+ const getRootObjectIdentifierName = (node) => {
5945
+ let current = node;
5946
+ while (isNodeOfType(current, "MemberExpression")) current = current.object;
5947
+ return isNodeOfType(current, "Identifier") ? current.name : null;
5948
+ };
5949
+ const isScopeWithinFunction = (candidateScope, functionScope) => {
5950
+ let currentScope = candidateScope;
5951
+ while (currentScope) {
5952
+ if (currentScope === functionScope) return true;
5953
+ currentScope = currentScope.parent;
5954
+ }
5955
+ return false;
5956
+ };
5957
+ const isSymbolDirectlyReturned = (symbol, callerFunction) => Boolean(callerFunction) && symbol.references.some((reference) => {
5958
+ const expressionRoot = findTransparentExpressionRoot(reference.identifier);
5959
+ const parent = expressionRoot.parent;
5960
+ return isNodeOfType(parent, "ReturnStatement") && parent.argument === expressionRoot && findEnclosingFunction$1(parent) === callerFunction;
5961
+ });
5962
+ const collectPatternBindingSymbolIds = (pattern, scopes, target) => {
5963
+ if (isNodeOfType(pattern, "Identifier")) {
5964
+ const symbol = scopes.symbolFor(pattern);
5965
+ if (symbol) target.add(symbol.id);
5966
+ return;
5967
+ }
5968
+ if (isNodeOfType(pattern, "ObjectPattern")) {
5969
+ for (const property of pattern.properties ?? []) if (isNodeOfType(property, "Property") && property.value) collectPatternBindingSymbolIds(property.value, scopes, target);
5970
+ else if (isNodeOfType(property, "RestElement") && property.argument) collectPatternBindingSymbolIds(property.argument, scopes, target);
5971
+ return;
5972
+ }
5973
+ if (isNodeOfType(pattern, "ArrayPattern")) {
5974
+ for (const element of pattern.elements ?? []) if (element) collectPatternBindingSymbolIds(element, scopes, target);
5975
+ return;
5976
+ }
5977
+ if (isNodeOfType(pattern, "AssignmentPattern") && pattern.left) collectPatternBindingSymbolIds(pattern.left, scopes, target);
5978
+ };
5979
+ const collectReferencedSymbolIds = (expression, scopes) => {
5980
+ const referencedSymbolIds = /* @__PURE__ */ new Set();
5981
+ walkAst(expression, (child) => {
5982
+ if (child !== expression && isFunctionLike$1(child)) return false;
5983
+ if (!isNodeOfType(child, "Identifier")) return;
5984
+ const symbol = scopes.symbolFor(child);
5985
+ if (symbol) referencedSymbolIds.add(symbol.id);
5986
+ });
5987
+ return referencedSymbolIds;
5988
+ };
5989
+ const collectAwaitDerivedSymbolIds = (block, scopes) => {
5990
+ const awaitDerivedSymbolIds = /* @__PURE__ */ new Set();
5991
+ const bindingDependencies = [];
5992
+ walkAst(block, (child) => {
5993
+ if (child !== block && isFunctionLike$1(child)) return false;
5994
+ if (isNodeOfType(child, "VariableDeclarator") && child.id && child.init) {
5995
+ const declaredSymbolIds = /* @__PURE__ */ new Set();
5996
+ collectPatternBindingSymbolIds(child.id, scopes, declaredSymbolIds);
5997
+ if (containsDirectAwait(child.init)) for (const symbolId of declaredSymbolIds) awaitDerivedSymbolIds.add(symbolId);
5998
+ const referencedSymbolIds = collectReferencedSymbolIds(child.init, scopes);
5999
+ for (const declaredSymbolId of declaredSymbolIds) bindingDependencies.push({
6000
+ declaredSymbolId,
6001
+ referencedSymbolIds
6002
+ });
6003
+ return;
6004
+ }
6005
+ if (isNodeOfType(child, "AssignmentExpression") && child.left) {
6006
+ const assignedSymbolIds = /* @__PURE__ */ new Set();
6007
+ collectPatternBindingSymbolIds(child.left, scopes, assignedSymbolIds);
6008
+ if (containsDirectAwait(child.right)) for (const symbolId of assignedSymbolIds) awaitDerivedSymbolIds.add(symbolId);
6009
+ const referencedSymbolIds = collectReferencedSymbolIds(child.right, scopes);
6010
+ for (const assignedSymbolId of assignedSymbolIds) bindingDependencies.push({
6011
+ declaredSymbolId: assignedSymbolId,
6012
+ referencedSymbolIds
6013
+ });
6014
+ }
6015
+ });
6016
+ let didGrow = true;
6017
+ while (didGrow) {
6018
+ didGrow = false;
6019
+ for (const { declaredSymbolId, referencedSymbolIds } of bindingDependencies) {
6020
+ if (awaitDerivedSymbolIds.has(declaredSymbolId)) continue;
6021
+ for (const referencedSymbolId of referencedSymbolIds) {
6022
+ if (!awaitDerivedSymbolIds.has(referencedSymbolId)) continue;
6023
+ awaitDerivedSymbolIds.add(declaredSymbolId);
6024
+ didGrow = true;
6025
+ break;
6026
+ }
6027
+ }
6028
+ }
6029
+ return awaitDerivedSymbolIds;
6030
+ };
6031
+ const getSimpleParameterIdentifier = (parameter) => {
6032
+ if (isNodeOfType(parameter, "Identifier")) return parameter;
6033
+ if (isNodeOfType(parameter, "AssignmentPattern") && isNodeOfType(parameter.left, "Identifier")) return parameter.left;
6034
+ return null;
6035
+ };
6036
+ const doesAwaitedLocalCallInsertAwaitDerivedOutput = (awaitNode, context) => {
6037
+ if (!isNodeOfType(awaitNode, "AwaitExpression")) return false;
6038
+ const callExpression = awaitNode.argument;
6039
+ if (!isNodeOfType(callExpression, "CallExpression")) return false;
6040
+ const localFunction = resolveStaticLocalCallFunction(callExpression, context.scopes);
6041
+ if (!isFunctionLike$1(localFunction)) return false;
6042
+ const callerFunction = findEnclosingFunction$1(callExpression);
6043
+ const functionScope = context.scopes.ownScopeFor(localFunction);
6044
+ if (!functionScope) return false;
6045
+ const awaitDerivedSymbolIds = collectAwaitDerivedSymbolIds(localFunction.body, context.scopes);
6046
+ const externallyReachableParameterSymbolIds = /* @__PURE__ */ new Set();
6047
+ for (const [parameterIndex, parameter] of localFunction.params.entries()) {
6048
+ const parameterIdentifier = getSimpleParameterIdentifier(parameter);
6049
+ if (!parameterIdentifier) continue;
6050
+ const argument = callExpression.arguments[parameterIndex];
6051
+ if (!isNodeOfType(argument, "Identifier")) continue;
6052
+ const parameterSymbol = context.scopes.symbolFor(parameterIdentifier);
6053
+ const argumentSymbol = context.scopes.symbolFor(argument);
6054
+ if (parameterSymbol && argumentSymbol && (isSymbolDirectlyReturned(argumentSymbol, callerFunction) || argumentSymbol.id === parameterSymbol.id)) externallyReachableParameterSymbolIds.add(parameterSymbol.id);
6055
+ }
6056
+ let doesInsertAwaitDerivedOutput = false;
6057
+ walkAst(localFunction.body, (child) => {
6058
+ if (doesInsertAwaitDerivedOutput) return false;
6059
+ if (child !== localFunction.body && isFunctionLike$1(child)) return false;
6060
+ if (!isNodeOfType(child, "CallExpression")) return;
6061
+ const callee = child.callee;
6062
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || !ORDERED_OUTPUT_INSERTION_METHOD_NAMES.has(callee.property.name)) return;
6063
+ let doesMutationConsumeAwaitedValue = false;
6064
+ for (const mutationArgument of child.arguments ?? []) {
6065
+ if (containsDirectAwait(mutationArgument)) {
6066
+ doesMutationConsumeAwaitedValue = true;
6067
+ break;
6068
+ }
6069
+ const referencedSymbolIds = collectReferencedSymbolIds(mutationArgument, context.scopes);
6070
+ for (const referencedSymbolId of referencedSymbolIds) if (awaitDerivedSymbolIds.has(referencedSymbolId)) {
6071
+ doesMutationConsumeAwaitedValue = true;
6072
+ break;
6073
+ }
6074
+ if (doesMutationConsumeAwaitedValue) break;
6075
+ }
6076
+ if (!doesMutationConsumeAwaitedValue) return;
6077
+ let receiverIdentifier = callee.object;
6078
+ while (isNodeOfType(receiverIdentifier, "MemberExpression")) receiverIdentifier = receiverIdentifier.object;
6079
+ if (!isNodeOfType(receiverIdentifier, "Identifier")) return;
6080
+ const receiverSymbol = context.scopes.symbolFor(receiverIdentifier);
6081
+ if (receiverSymbol && (externallyReachableParameterSymbolIds.has(receiverSymbol.id) || !isScopeWithinFunction(receiverSymbol.scope, functionScope) && isSymbolDirectlyReturned(receiverSymbol, callerFunction))) {
6082
+ doesInsertAwaitDerivedOutput = true;
6083
+ return false;
6084
+ }
6085
+ });
6086
+ return doesInsertAwaitDerivedOutput;
6087
+ };
6088
+ const isIntentionallySequentialAwait = (awaitNode, context) => isAwaitingPossiblyMutatedMemberCall(awaitNode, context) || isAwaitingSleepLikeCall(awaitNode, context) || isAwaitingPromiseConcurrencyCall(awaitNode) || isAwaitingManualPromiseWait(awaitNode) || doesAwaitedLocalCallInsertAwaitDerivedOutput(awaitNode, context);
5957
6089
  const collectPatternIdentifiers = (pattern, target) => {
5958
6090
  if (isNodeOfType(pattern, "Identifier")) target.add(pattern.name);
5959
6091
  else if (isNodeOfType(pattern, "ObjectPattern")) {
@@ -6099,11 +6231,6 @@ const loopBodyHasAwaitDependentEarlyExit = (block, loopLabelName) => {
6099
6231
  });
6100
6232
  return hasAwaitDependentExit;
6101
6233
  };
6102
- const getRootObjectIdentifierName = (node) => {
6103
- let current = node;
6104
- while (isNodeOfType(current, "MemberExpression")) current = current.object;
6105
- return isNodeOfType(current, "Identifier") ? current.name : null;
6106
- };
6107
6234
  const MUTATING_ARRAY_METHOD_NAMES$2 = new Set([
6108
6235
  ...ARRAY_MUTATION_METHOD_NAMES,
6109
6236
  "pop",
@@ -17872,6 +17999,14 @@ const findRenderPhaseComponentOrHook = (node, scopes) => {
17872
17999
  //#region src/plugin/utils/is-event-handler-attribute.ts
17873
18000
  const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && /^on[A-Z]/.test(node.name.name);
17874
18001
  //#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
17875
18010
  //#region src/plugin/utils/is-ast-descendant.ts
17876
18011
  /**
17877
18012
  * True when `inner` is `outer` itself or any descendant in the AST
@@ -18090,13 +18225,15 @@ const isSynchronousIteratorCall = (callNode, callbackArgument, scopes) => {
18090
18225
  }
18091
18226
  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)));
18092
18227
  };
18093
- const isSynchronousIteratorCallback = (functionNode) => {
18094
- const callNode = functionNode.parent;
18095
- if (!isNodeOfType(callNode, "CallExpression")) return false;
18228
+ const isSynchronousIteratorCallbackCall = (callNode, callbackArgument) => {
18096
18229
  const callee = stripParenExpression(callNode.callee);
18097
18230
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
18098
- if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments[1] === functionNode;
18099
- return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments[0] === functionNode;
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
+ const isSynchronousIteratorCallback = (functionNode) => {
18235
+ const callNode = functionNode.parent;
18236
+ return Boolean(isNodeOfType(callNode, "CallExpression") && isSynchronousIteratorCallbackCall(callNode, functionNode));
18100
18237
  };
18101
18238
  //#endregion
18102
18239
  //#region src/plugin/utils/is-within-assignment-target.ts
@@ -18216,11 +18353,35 @@ const resolveEventListenerCaptureValueIdentityKey = (expression, context) => {
18216
18353
  const rightIdentityKey = resolveEventListenerCaptureValueIdentityKey(unwrappedExpression.right, context);
18217
18354
  return leftIdentityKey && rightIdentityKey ? `${unwrappedExpression.type}:${unwrappedExpression.operator}:${leftIdentityKey}:${rightIdentityKey}` : null;
18218
18355
  };
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
+ };
18219
18378
  const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqueOptionsIdentity) => {
18220
- const capture = resolveEventListenerCapture(optionsNode, { allowIndeterminateEntries: true });
18379
+ const stableOptionsNode = optionsNode ? resolveReadOnlyEventListenerOptions(optionsNode, context) : null;
18380
+ if (optionsNode && !stableOptionsNode) return null;
18381
+ const capture = resolveEventListenerCapture(stableOptionsNode, { allowIndeterminateEntries: true });
18221
18382
  if (capture !== null) return `capture:${String(capture)}`;
18222
- if (!optionsNode) return null;
18223
- const unwrappedOptions = stripParenExpression(optionsNode);
18383
+ if (!stableOptionsNode) return null;
18384
+ const unwrappedOptions = stripParenExpression(stableOptionsNode);
18224
18385
  if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) {
18225
18386
  const optionsKey = allowOpaqueOptionsIdentity ? resolveEventListenerCaptureValueIdentityKey(unwrappedOptions, context) : null;
18226
18387
  return optionsKey ? `options:${optionsKey}` : null;
@@ -18267,12 +18428,8 @@ const doEventListenerCapturesMatch = (registrationOptions, releaseOptions, conte
18267
18428
  return registrationCaptureKey !== null && registrationCaptureKey === resolveEventListenerCaptureIdentityKey(releaseOptions, context, allowOpaqueOptionsIdentity);
18268
18429
  };
18269
18430
  const findAssignedResourceKey = (resourceNode, context) => {
18270
- let currentNode = resourceNode;
18271
- let parentNode = currentNode.parent;
18272
- while (isNodeOfType(parentNode, "ChainExpression")) {
18273
- currentNode = parentNode;
18274
- parentNode = currentNode.parent;
18275
- }
18431
+ const currentNode = findTransparentExpressionRoot(resourceNode);
18432
+ const parentNode = currentNode.parent;
18276
18433
  if (isNodeOfType(parentNode, "VariableDeclarator") && parentNode.init === currentNode) return resolveExpressionKey(parentNode.id, context);
18277
18434
  if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.right === currentNode) return resolveExpressionKey(parentNode.left, context);
18278
18435
  return null;
@@ -18543,6 +18700,18 @@ const resolveIteratorCollectionKey = (expression, context) => {
18543
18700
  }
18544
18701
  return null;
18545
18702
  };
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
+ };
18546
18715
  const isStableLoopReceiver = (expression, context) => {
18547
18716
  if (!expression) return false;
18548
18717
  const unwrappedExpression = stripParenExpression(expression);
@@ -19313,6 +19482,28 @@ const isFunctionReturnedFromReactHook = (functionNode, context, requireRefProper
19313
19482
  });
19314
19483
  };
19315
19484
  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
+ };
19316
19507
  const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
19317
19508
  if (!isNodeOfType(usage.node, "CallExpression")) return false;
19318
19509
  const usageFunction = findEnclosingFunction$1(usage.node);
@@ -19333,7 +19524,7 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
19333
19524
  if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
19334
19525
  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);
19335
19526
  });
19336
- const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
19527
+ const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? findCallbackRefReplacementReleaseGuard(releaseCall, usageFunction, releaseReceiverKey, registrationReceiverKey, context) ?? releaseCall;
19337
19528
  const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
19338
19529
  return doNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
19339
19530
  };
@@ -19483,14 +19674,21 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
19483
19674
  if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
19484
19675
  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;
19485
19676
  if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
19486
- 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;
19487
19677
  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
+ }
19488
19683
  if (usage.registrationVerbName === "addEventListener" && releaseVerbName === "removeEventListener" && isNodeOfType(usage.node, "CallExpression")) {
19489
19684
  if (!isNodeOfType(stripParenExpression(usage.node.callee), "MemberExpression")) return false;
19490
19685
  if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], callNode.arguments?.[2], context, true)) return false;
19491
19686
  }
19492
19687
  if (isNodeOfType(usage.node, "CallExpression") && !hasSafeForEachProjectionCleanup(usage.node, callNode, context)) return false;
19493
- if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) 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;
19494
19692
  if (usage.registrationVerbName === "subscribe" && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub") && usage.handleKey !== null && resolveExpressionKey(callNode.arguments?.[0], context) === usage.handleKey) return true;
19495
19693
  const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
19496
19694
  if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
@@ -19527,7 +19725,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
19527
19725
  const usesUnaryListenerSignatureForCalls = isNodeOfType(usage.node, "CallExpression") && usesUnaryListenerSignature(usage.node, callNode);
19528
19726
  const releaseHandler = usesUnaryListenerSignatureForCalls ? callNode.arguments?.[0] : callNode.arguments?.[1];
19529
19727
  if (!releaseHandler) return releaseVerbName === "off";
19530
- const expectedHandlerKey = usesUnaryListenerSignatureForCalls ? usage.eventKey : usage.handlerKey;
19728
+ const expectedHandlerKey = usesUnaryListenerSignatureForCalls ? usage.handlerKey ?? usage.eventKey : usage.handlerKey;
19531
19729
  const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignatureForCalls ? 0 : 1] : null;
19532
19730
  return expectedHandlerKey !== null && resolveResourceIdentityKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
19533
19731
  }
@@ -20001,6 +20199,7 @@ const findRetainedFunctionLeak = (retainedFunction, context, options) => {
20001
20199
  walkAst(body, (child) => {
20002
20200
  if (leak !== null) return false;
20003
20201
  if (isFunctionLike$1(child)) return false;
20202
+ if (!isNodeReachableWithinFunction(child, context)) return false;
20004
20203
  if (isSocketConstruction(child) && !doesResourceResultEscape(child, allowReturnedSocketEscape, false, context)) {
20005
20204
  const socketUsage = {
20006
20205
  kind: "socket",
@@ -20253,6 +20452,164 @@ const isInlineRetainedHandlerFunction = (functionNode, context) => {
20253
20452
  const objectParent = objectExpression.parent;
20254
20453
  return (isNodeOfType(objectParent, "CallExpression") && objectParent.arguments.some((argument) => argument === objectExpression) || isNodeOfType(objectParent, "JSXExpressionContainer")) && findRenderPhaseComponentOrHook(parentNode, context.scopes) !== null;
20255
20454
  };
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
+ };
20256
20613
  const effectNeedsCleanup = defineRule({
20257
20614
  id: "effect-needs-cleanup",
20258
20615
  title: "Effect subscription or timer never cleaned up",
@@ -20263,13 +20620,19 @@ const effectNeedsCleanup = defineRule({
20263
20620
  const reportRetainedLeak = (retainedFunction) => {
20264
20621
  const refEffectUsage = getReactRefEffectUsage(retainedFunction, context);
20265
20622
  if (!refEffectUsage && !isPotentiallyReachableFunction(retainedFunction, context)) return;
20623
+ const effectInvocations = getEffectRetainedInvocations(retainedFunction, context);
20624
+ const isEffectInvoked = effectInvocations.length > 0;
20266
20625
  const leak = findRetainedFunctionLeak(retainedFunction, context, refEffectUsage ? {
20267
20626
  allowReturnedResourceEscape: refEffectUsage.doesEffectOwnEveryResult,
20268
20627
  allowReturnedTimerEscape: false,
20269
20628
  includeOneShotTimers: true,
20270
20629
  requireCallableReturnedResource: true
20630
+ } : isEffectInvoked ? {
20631
+ allowReturnedTimerEscape: false,
20632
+ includeOneShotTimers: true
20271
20633
  } : void 0);
20272
20634
  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;
20273
20636
  const resourceNoun = RESOURCE_NOUN_BY_KIND[leak.kind];
20274
20637
  context.report({
20275
20638
  node: leak.node,
@@ -24689,14 +25052,14 @@ const getFirstLegendChild = (children, targetNode) => {
24689
25052
  if (isNodeOfType(child, "JSXExpressionContainer")) {
24690
25053
  const potentialLegends = [];
24691
25054
  collectPotentialLegends(child.expression, potentialLegends);
24692
- const containingLegend = potentialLegends.find((legend) => isDescendantOf(targetNode, legend));
25055
+ const containingLegend = potentialLegends.find((legend) => isDescendantOf$1(targetNode, legend));
24693
25056
  if (containingLegend) return containingLegend;
24694
25057
  if (potentialLegends[0]) return potentialLegends[0];
24695
25058
  }
24696
25059
  }
24697
25060
  return null;
24698
25061
  };
24699
- const isDescendantOf = (node, ancestor) => {
25062
+ const isDescendantOf$1 = (node, ancestor) => {
24700
25063
  let current = node.parent;
24701
25064
  while (current) {
24702
25065
  if (current === ancestor) return true;
@@ -24721,7 +25084,7 @@ const isDisabledByFieldsetAncestor = (node, context) => {
24721
25084
  while (ancestor) {
24722
25085
  if (isNodeOfType(ancestor, "JSXElement") && resolveJsxElementType(ancestor.openingElement) === "fieldset" && openingElementMayBeDisabled(ancestor.openingElement, context)) {
24723
25086
  const firstLegend = getFirstLegendChild(ancestor.children, node);
24724
- if (!firstLegend || !isDescendantOf(node, firstLegend)) return true;
25087
+ if (!firstLegend || !isDescendantOf$1(node, firstLegend)) return true;
24725
25088
  }
24726
25089
  ancestor = ancestor.parent;
24727
25090
  }
@@ -30534,7 +30897,8 @@ const STRING_TYPED_PROPERTY_NAMES = new Set([
30534
30897
  "code",
30535
30898
  "label",
30536
30899
  "slug",
30537
- "prefix"
30900
+ "prefix",
30901
+ "__html"
30538
30902
  ]);
30539
30903
  const STRING_TYPED_IDENTIFIER_SUFFIXES = [
30540
30904
  "Text",
@@ -30652,13 +31016,25 @@ const STRING_TYPED_IDENTIFIER_NAMES = new Set([
30652
31016
  "title"
30653
31017
  ]);
30654
31018
  const STRING_RETURNING_CALLEE_PREFIX_PATTERN = /^(?:normalize|format|stringify|serialize)/;
31019
+ const FRESH_ARRAY_METHOD_NAMES$2 = new Set([
31020
+ "concat",
31021
+ "filter",
31022
+ "flat",
31023
+ "flatMap",
31024
+ "map",
31025
+ "slice",
31026
+ "split"
31027
+ ]);
30655
31028
  const isLikelyStringReceiver = (receiver) => {
30656
31029
  if (!receiver) return false;
31030
+ const unwrappedReceiver = stripParenExpression(receiver);
31031
+ if (unwrappedReceiver !== receiver) return isLikelyStringReceiver(unwrappedReceiver);
30657
31032
  if (isNodeOfType(receiver, "Literal") && typeof receiver.value === "string") return true;
30658
31033
  if (isNodeOfType(receiver, "TemplateLiteral")) return true;
30659
31034
  if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "Identifier") && receiver.callee.name === "String") return true;
30660
31035
  if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "MemberExpression") && isNodeOfType(receiver.callee.property, "Identifier") && STRING_RETURNING_METHODS.has(receiver.callee.property.name)) return true;
30661
31036
  if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "Identifier") && STRING_RETURNING_CALLEE_PREFIX_PATTERN.test(receiver.callee.name)) return true;
31037
+ if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "MemberExpression") && isNodeOfType(receiver.callee.property, "Identifier") && (receiver.callee.property.name === "concat" || receiver.callee.property.name === "slice") && isLikelyStringReceiver(receiver.callee.object)) return true;
30662
31038
  if (isNodeOfType(receiver, "MemberExpression") && isNodeOfType(receiver.property, "Identifier")) {
30663
31039
  if (STRING_TYPED_PROPERTY_NAMES.has(receiver.property.name)) return true;
30664
31040
  }
@@ -30676,8 +31052,46 @@ const isLikelyStringReceiver = (receiver) => {
30676
31052
  }
30677
31053
  if (isNodeOfType(receiver, "BinaryExpression") && receiver.operator === "+") return isLikelyStringReceiver(receiver.left) || isLikelyStringReceiver(receiver.right);
30678
31054
  if (isNodeOfType(receiver, "ConditionalExpression")) return isLikelyStringReceiver(receiver.consequent) && isLikelyStringReceiver(receiver.alternate);
31055
+ if (isNodeOfType(receiver, "LogicalExpression")) return isLikelyStringReceiver(receiver.left) && isLikelyStringReceiver(receiver.right);
30679
31056
  return false;
30680
31057
  };
31058
+ const isFreshArrayReceiver = (receiver) => {
31059
+ const unwrappedReceiver = stripParenExpression(receiver);
31060
+ if (unwrappedReceiver !== receiver) return isFreshArrayReceiver(unwrappedReceiver);
31061
+ if (!isNodeOfType(receiver, "CallExpression") || !isNodeOfType(receiver.callee, "MemberExpression") || !isNodeOfType(receiver.callee.property, "Identifier")) return false;
31062
+ if (!FRESH_ARRAY_METHOD_NAMES$2.has(receiver.callee.property.name)) return false;
31063
+ if (receiver.callee.property.name === "split") return isLikelyStringReceiver(receiver.callee.object);
31064
+ const sourceReceiver = stripParenExpression(receiver.callee.object);
31065
+ return isKnownNativeArrayReceiver(sourceReceiver) || isFreshArrayReceiver(sourceReceiver);
31066
+ };
31067
+ const isSmallRestHelperOmissionList = (node) => {
31068
+ if (!node) return false;
31069
+ const candidate = stripParenExpression(node);
31070
+ if (!isNodeOfType(candidate, "ArrayExpression")) return false;
31071
+ const elements = candidate.elements ?? [];
31072
+ return elements.length <= 8 && elements.every((element) => element === null || !isNodeOfType(element, "SpreadElement"));
31073
+ };
31074
+ const isTypeScriptRestHelperLookup = (lookupCall, receiver, scopes) => {
31075
+ if (!isNodeOfType(receiver, "Identifier")) return false;
31076
+ const enclosingFunction = findEnclosingFunction$1(lookupCall);
31077
+ if (!isNodeOfType(enclosingFunction, "FunctionExpression") || !isNodeOfType(enclosingFunction.params?.[1], "Identifier") || enclosingFunction.params[1].name !== receiver.name) return false;
31078
+ let bindingIdentifier = null;
31079
+ let ancestor = enclosingFunction.parent;
31080
+ while (ancestor && !isFunctionLike$1(ancestor)) {
31081
+ if (isNodeOfType(ancestor, "VariableDeclarator") && isNodeOfType(ancestor.id, "Identifier") && ancestor.id.name === "__rest") {
31082
+ bindingIdentifier = ancestor.id;
31083
+ break;
31084
+ }
31085
+ ancestor = ancestor.parent;
31086
+ }
31087
+ if (!bindingIdentifier) return false;
31088
+ const helperSymbol = scopes.symbolFor(bindingIdentifier);
31089
+ if (!helperSymbol || helperSymbol.references.length === 0) return false;
31090
+ return helperSymbol.references.every((reference) => {
31091
+ const callExpression = reference.identifier.parent;
31092
+ return isNodeOfType(callExpression, "CallExpression") && callExpression.callee === reference.identifier && isSmallRestHelperOmissionList(callExpression.arguments?.[1]);
31093
+ });
31094
+ };
30681
31095
  const INDEX_LIKE_IDENTIFIER_NAMES = new Set([
30682
31096
  "i",
30683
31097
  "j",
@@ -31274,6 +31688,8 @@ const jsSetMapLookups = defineRule({
31274
31688
  const query = node.arguments[0];
31275
31689
  if (methodName === "indexOf" && !isKnownSafeIndexOfQuery(query) && (isKnownUnsafeIndexOfQuery(query, receiver) || isKnownUnsafeIndexOfReceiver(receiver))) return;
31276
31690
  if (isLikelyStringReceiver(receiver)) return;
31691
+ if (isFreshArrayReceiver(receiver)) return;
31692
+ if (isTypeScriptRestHelperLookup(node, receiver, context.scopes)) return;
31277
31693
  if (isSmallInlineLiteralArray(receiver)) return;
31278
31694
  if (isScreamingSnakeCaseConstantReceiver(receiver)) return;
31279
31695
  if (isSmallFixedListMember(receiver)) return;
@@ -45187,14 +45603,6 @@ const noAriaInvalidWithoutDescription = defineRule({
45187
45603
  } })
45188
45604
  });
45189
45605
  //#endregion
45190
- //#region src/plugin/utils/is-early-exit-statement.ts
45191
- const isEarlyExitStatement$1 = (statement) => {
45192
- if (!statement) return false;
45193
- if (statementAlwaysExits$1(statement)) return true;
45194
- if (isNodeOfType(statement, "BlockStatement")) return isEarlyExitStatement$1(statement.body.at(-1));
45195
- return isNodeOfType(statement, "ContinueStatement") || isNodeOfType(statement, "BreakStatement");
45196
- };
45197
- //#endregion
45198
45606
  //#region src/plugin/utils/unwrap-negative-guard-form.ts
45199
45607
  const unwrapNegativeGuardForm = (test) => {
45200
45608
  const expression = stripParenExpression(test);
@@ -46715,6 +47123,101 @@ const isLoopCounterDeclarator = (declarator, referenceNode, indexName) => {
46715
47123
  }
46716
47124
  return isBindingReassignedOrMutated(referenceNode, indexName);
46717
47125
  };
47126
+ const findEnclosingWhileLoop = (node) => {
47127
+ let current = node.parent;
47128
+ while (current) {
47129
+ if (isNodeOfType(current, "WhileStatement") || isNodeOfType(current, "DoWhileStatement")) return current;
47130
+ if (isFunctionLike$1(current) || isNodeOfType(current, "Program")) return null;
47131
+ current = current.parent;
47132
+ }
47133
+ return null;
47134
+ };
47135
+ const isStaticMemberChain = (expression) => {
47136
+ const candidate = stripParenExpression(expression);
47137
+ if (isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "ThisExpression")) return true;
47138
+ return Boolean(isNodeOfType(candidate, "MemberExpression") && !candidate.computed && isNodeOfType(candidate.property, "Identifier") && isStaticMemberChain(candidate.object));
47139
+ };
47140
+ const findLengthBoundCollections = (expression) => {
47141
+ const collections = [];
47142
+ walkAst(expression, (child) => {
47143
+ if (isNodeOfType(child, "MemberExpression") && !child.computed && isStaticMemberChain(child.object) && isNodeOfType(child.property, "Identifier") && child.property.name === "length") collections.push(child.object);
47144
+ });
47145
+ return collections;
47146
+ };
47147
+ const areSameBoundStaticMemberChains = (first, second) => isStaticMemberChain(first) && isStaticMemberChain(second) && areExpressionsStructurallyEqual(first, second, { areIdentifiersEqual: (firstIdentifier, secondIdentifier) => {
47148
+ if (!isNodeOfType(firstIdentifier, "Identifier") || !isNodeOfType(secondIdentifier, "Identifier") || firstIdentifier.name !== secondIdentifier.name) return false;
47149
+ const firstBinding = findVariableInitializer(firstIdentifier, firstIdentifier.name);
47150
+ const secondBinding = findVariableInitializer(secondIdentifier, secondIdentifier.name);
47151
+ return firstBinding?.bindingIdentifier === secondBinding?.bindingIdentifier;
47152
+ } });
47153
+ const RELATIONAL_BOUND_OPERATORS = new Set([
47154
+ "<",
47155
+ "<=",
47156
+ ">",
47157
+ ">="
47158
+ ]);
47159
+ const loopTestBoundsCounterByLength = (expression, indexName, bindingIdentifier) => {
47160
+ const readsCounter = (candidate) => {
47161
+ let didReadCounter = false;
47162
+ walkAst(candidate, (child) => {
47163
+ if (didReadCounter) return false;
47164
+ if (isNodeOfType(child, "Identifier") && child.name === indexName && findVariableInitializer(child, indexName)?.bindingIdentifier === bindingIdentifier) {
47165
+ didReadCounter = true;
47166
+ return false;
47167
+ }
47168
+ });
47169
+ return didReadCounter;
47170
+ };
47171
+ const readsLength = (candidate) => {
47172
+ let didReadLength = false;
47173
+ walkAst(candidate, (child) => {
47174
+ if (didReadLength) return false;
47175
+ if (isNodeOfType(child, "MemberExpression") && !child.computed && isNodeOfType(child.property, "Identifier") && child.property.name === "length") {
47176
+ didReadLength = true;
47177
+ return false;
47178
+ }
47179
+ });
47180
+ return didReadLength;
47181
+ };
47182
+ let didFindLengthBound = false;
47183
+ walkAst(expression, (child) => {
47184
+ if (didFindLengthBound) return false;
47185
+ if (!isNodeOfType(child, "BinaryExpression") || !RELATIONAL_BOUND_OPERATORS.has(child.operator)) return;
47186
+ if (readsCounter(child.left) && readsLength(child.right) || readsCounter(child.right) && readsLength(child.left)) {
47187
+ didFindLengthBound = true;
47188
+ return false;
47189
+ }
47190
+ });
47191
+ return didFindLengthBound;
47192
+ };
47193
+ const isDataIndexedWhileLoopCounter = (referenceNode, bindingIdentifier, indexName) => {
47194
+ if (!INDEX_PARAMETER_NAMES.has(indexName)) return false;
47195
+ const loop = findEnclosingWhileLoop(referenceNode);
47196
+ if (!loop) return false;
47197
+ let doesTestReadCounter = false;
47198
+ walkAst(loop.test, (child) => {
47199
+ if (doesTestReadCounter) return false;
47200
+ if (!isNodeOfType(child, "Identifier") || child.name !== indexName) return;
47201
+ if (findVariableInitializer(child, indexName)?.bindingIdentifier === bindingIdentifier) {
47202
+ doesTestReadCounter = true;
47203
+ return false;
47204
+ }
47205
+ });
47206
+ if (!doesTestReadCounter) return false;
47207
+ const lengthBoundCollections = findLengthBoundCollections(loop.test);
47208
+ if (lengthBoundCollections.length === 0) return false;
47209
+ let didFindIndexedCollectionRead = false;
47210
+ walkAst(loop.body, (child) => {
47211
+ if (didFindIndexedCollectionRead) return false;
47212
+ if (isFunctionLike$1(child)) return false;
47213
+ if (!isNodeOfType(child, "MemberExpression") || !child.computed || !isNodeOfType(child.property, "Identifier") || child.property.name !== indexName) return;
47214
+ if (findVariableInitializer(child.property, indexName)?.bindingIdentifier === bindingIdentifier && lengthBoundCollections.some((collection) => areSameBoundStaticMemberChains(collection, child.object))) {
47215
+ didFindIndexedCollectionRead = true;
47216
+ return false;
47217
+ }
47218
+ });
47219
+ return didFindIndexedCollectionRead;
47220
+ };
46718
47221
  /**
46719
47222
  * Resolves whether an identifier is PROVABLY the positional array
46720
47223
  * index, by classifying its binding. Per the official rule prompt,
@@ -46765,6 +47268,12 @@ const resolvePositionalIndexBinding = (identifierNode, depth) => {
46765
47268
  }
46766
47269
  const declarator = binding.bindingIdentifier.parent;
46767
47270
  if (declarator && isNodeOfType(declarator, "VariableDeclarator") && declarator.id === binding.bindingIdentifier && declarator.init) {
47271
+ if (isDataIndexedWhileLoopCounter(identifierNode, binding.bindingIdentifier, identifierNode.name)) return {
47272
+ iteratorCall: null,
47273
+ bindingFunction: null,
47274
+ indexParameterPosition: null,
47275
+ isDataIndexedLoopCounter: true
47276
+ };
46768
47277
  const initializer = stripParenExpression(declarator.init);
46769
47278
  if (isNodeOfType(initializer, "Literal") && typeof initializer.value === "number") {
46770
47279
  if (!INDEX_PARAMETER_NAMES.has(identifierNode.name)) return null;
@@ -46799,6 +47308,101 @@ const iteratorCallExemptsIndexKey = (iteratorCall) => {
46799
47308
  const receiver = iteratorCall.callee.object;
46800
47309
  return isStaticPlaceholderReceiver(receiver) || isFixedMemoReceiver(receiver) || isStaticDefaultLiteralReceiver(receiver) || isStringDerivedReceiver(receiver);
46801
47310
  };
47311
+ const isReactNamespaceIdentifier = (node) => {
47312
+ if (!isNodeOfType(node, "Identifier")) return false;
47313
+ const importBinding = getImportBindingForName(node, node.name);
47314
+ const visibleBinding = findVariableInitializer(node, node.name);
47315
+ if (!importBinding) return node.name === "React" && !visibleBinding;
47316
+ return visibleBinding?.initializer?.type.startsWith("Import") === true && importBinding.source === "react" && (importBinding.isNamespace || importBinding.exportedName === "default");
47317
+ };
47318
+ const isReactChildrenObject = (node) => {
47319
+ const candidate = stripParenExpression(node);
47320
+ if (isNodeOfType(candidate, "Identifier")) {
47321
+ const importBinding = getImportBindingForName(candidate, candidate.name);
47322
+ const visibleBinding = findVariableInitializer(candidate, candidate.name);
47323
+ return Boolean(visibleBinding?.initializer?.type === "ImportSpecifier" && importBinding?.source === "react" && importBinding.exportedName === "Children");
47324
+ }
47325
+ return Boolean(isNodeOfType(candidate, "MemberExpression") && !candidate.computed && isReactNamespaceIdentifier(candidate.object) && isNodeOfType(candidate.property, "Identifier") && candidate.property.name === "Children");
47326
+ };
47327
+ const isReactChildrenToArrayCall = (node) => {
47328
+ const candidate = stripParenExpression(node);
47329
+ if (!isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || candidate.callee.computed || !isReactChildrenObject(candidate.callee.object) || !isNodeOfType(candidate.callee.property, "Identifier") || candidate.callee.property.name !== "toArray") return false;
47330
+ const normalizedValueNode = candidate.arguments?.[0];
47331
+ const normalizedValue = normalizedValueNode ? stripParenExpression(normalizedValueNode) : null;
47332
+ if (!normalizedValue || !isNodeOfType(normalizedValue, "Identifier") || normalizedValue.name !== "children") return false;
47333
+ const normalizedBinding = findVariableInitializer(normalizedValue, normalizedValue.name);
47334
+ return Boolean(normalizedBinding && findEnclosingParameter(normalizedBinding.bindingIdentifier));
47335
+ };
47336
+ const isSameIdentifier = (first, second) => {
47337
+ const firstIdentifier = stripParenExpression(first);
47338
+ const secondIdentifier = stripParenExpression(second);
47339
+ if (!isNodeOfType(firstIdentifier, "Identifier") || !isNodeOfType(secondIdentifier, "Identifier") || firstIdentifier.name !== secondIdentifier.name) return false;
47340
+ const firstBinding = findVariableInitializer(firstIdentifier, firstIdentifier.name);
47341
+ const secondBinding = findVariableInitializer(secondIdentifier, secondIdentifier.name);
47342
+ return firstBinding?.bindingIdentifier === secondBinding?.bindingIdentifier;
47343
+ };
47344
+ const isReactChildrenArrayNormalization = (node) => {
47345
+ const candidate = stripParenExpression(node);
47346
+ if (!isNodeOfType(candidate, "ConditionalExpression")) return false;
47347
+ const test = stripParenExpression(candidate.test);
47348
+ if (!isNodeOfType(test, "CallExpression") || !isNodeOfType(test.callee, "MemberExpression") || test.callee.computed || !isNodeOfType(test.callee.object, "Identifier") || test.callee.object.name !== "Array" || findVariableInitializer(test.callee.object, "Array") || !isNodeOfType(test.callee.property, "Identifier") || test.callee.property.name !== "isArray") return false;
47349
+ const testedValueNode = test.arguments?.[0];
47350
+ if (!testedValueNode) return false;
47351
+ const testedValue = stripParenExpression(testedValueNode);
47352
+ if (!isNodeOfType(testedValue, "Identifier")) return false;
47353
+ const testedBinding = findVariableInitializer(testedValue, testedValue.name);
47354
+ if (testedValue.name !== "children" || !testedBinding || !findEnclosingParameter(testedBinding.bindingIdentifier)) return false;
47355
+ const branchWrapsTestedValue = (branch) => {
47356
+ const unwrappedBranch = stripParenExpression(branch);
47357
+ return isNodeOfType(unwrappedBranch, "ArrayExpression") && unwrappedBranch.elements?.length === 1 && Boolean(unwrappedBranch.elements[0] && isSameIdentifier(unwrappedBranch.elements[0], testedValue));
47358
+ };
47359
+ return isSameIdentifier(candidate.consequent, testedValue) && branchWrapsTestedValue(candidate.alternate) || isSameIdentifier(candidate.alternate, testedValue) && branchWrapsTestedValue(candidate.consequent);
47360
+ };
47361
+ const isMutatedEmptyArrayBinding = (identifierNode, depth) => {
47362
+ const binding = findVariableInitializer(identifierNode, identifierNode.name);
47363
+ const initializer = binding?.initializer ? stripParenExpression(binding.initializer) : null;
47364
+ if (!binding || !initializer || !isNodeOfType(initializer, "ArrayExpression") || initializer.elements?.length !== 0) return false;
47365
+ const program = findProgramRoot(identifierNode);
47366
+ if (!program) return false;
47367
+ let didFindReactChildPush = false;
47368
+ walkAst(program, (child) => {
47369
+ if (didFindReactChildPush) return false;
47370
+ if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || child.callee.computed || !isNodeOfType(child.callee.object, "Identifier") || child.callee.object.name !== identifierNode.name || !isNodeOfType(child.callee.property, "Identifier") || child.callee.property.name !== "push") return;
47371
+ if (findVariableInitializer(child.callee.object, identifierNode.name)?.bindingIdentifier === binding.bindingIdentifier && (child.arguments ?? []).some((argument) => {
47372
+ const candidate = stripParenExpression(argument);
47373
+ if (!(isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "JSXElement") || isNodeOfType(candidate, "JSXFragment"))) return false;
47374
+ let doesCarryReactChild = false;
47375
+ walkAst(candidate, (argumentChild) => {
47376
+ if (doesCarryReactChild) return false;
47377
+ if (!isNodeOfType(argumentChild, "Identifier")) return;
47378
+ const declarator = findVariableInitializer(argumentChild, argumentChild.name)?.bindingIdentifier.parent;
47379
+ const declaration = declarator?.parent;
47380
+ const forOfStatement = declaration?.parent;
47381
+ if (declarator && isNodeOfType(declarator, "VariableDeclarator") && declaration && isNodeOfType(declaration, "VariableDeclaration") && forOfStatement && isNodeOfType(forOfStatement, "ForOfStatement") && forOfStatement.left === declaration && isDynamicReactChildrenExpression(forOfStatement.right, depth + 1)) {
47382
+ doesCarryReactChild = true;
47383
+ return false;
47384
+ }
47385
+ });
47386
+ return doesCarryReactChild;
47387
+ })) {
47388
+ didFindReactChildPush = true;
47389
+ return false;
47390
+ }
47391
+ });
47392
+ return didFindReactChildPush;
47393
+ };
47394
+ const isDynamicReactChildrenExpression = (expression, depth) => {
47395
+ if (depth > TYPE_RESOLUTION_DEPTH_LIMIT$2) return false;
47396
+ const candidate = stripParenExpression(expression);
47397
+ if (isReactChildrenToArrayCall(candidate) || isReactChildrenArrayNormalization(candidate)) return true;
47398
+ if (isNodeOfType(candidate, "Identifier")) {
47399
+ if (isMutatedEmptyArrayBinding(candidate, depth)) return true;
47400
+ const binding = findVariableInitializer(candidate, candidate.name);
47401
+ return Boolean(binding?.initializer && isDynamicReactChildrenExpression(binding.initializer, depth + 1));
47402
+ }
47403
+ if (isNodeOfType(candidate, "CallExpression") && isNodeOfType(candidate.callee, "MemberExpression") && !candidate.callee.computed && isNodeOfType(candidate.callee.property, "Identifier") && candidate.callee.property.name === "filter") return isDynamicReactChildrenExpression(candidate.callee.object, depth + 1);
47404
+ return false;
47405
+ };
46802
47406
  const resolveKeyTemplateLiteral = (expression) => {
46803
47407
  const node = stripParenExpression(expression);
46804
47408
  if (isNodeOfType(node, "TemplateLiteral")) return node;
@@ -46851,28 +47455,19 @@ const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
46851
47455
  }
46852
47456
  return referencedItemNames;
46853
47457
  };
46854
- const forLoopTestReadsDataLength = (test) => {
46855
- let didFindLengthRead = false;
46856
- walkAst(test, (child) => {
46857
- if (didFindLengthRead) return false;
46858
- if (isNodeOfType(child, "MemberExpression") && isNodeOfType(child.property, "Identifier") && child.property.name === "length") {
46859
- didFindLengthRead = true;
46860
- return false;
46861
- }
46862
- });
46863
- return didFindLengthRead;
46864
- };
46865
- const isNumericForLoopCounter = (attributeNode, indexName) => {
47458
+ const isNumericPlaceholderLoopCounter = (attributeNode, indexName) => {
46866
47459
  const binding = findVariableInitializer(attributeNode, indexName);
46867
47460
  if (!binding) return false;
46868
47461
  const declarator = binding.bindingIdentifier.parent;
46869
47462
  if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return false;
46870
47463
  const declaration = declarator.parent;
46871
47464
  if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) return false;
47465
+ if (!declarator.init || !isNodeOfType(declarator.init, "Literal") || typeof declarator.init.value !== "number") return false;
46872
47466
  const forStatement = declaration.parent;
46873
- if (!forStatement || !isNodeOfType(forStatement, "ForStatement") || forStatement.init !== declaration || !declarator.init || !isNodeOfType(declarator.init, "Literal") || typeof declarator.init.value !== "number") return false;
46874
- if (forStatement.test && forLoopTestReadsDataLength(forStatement.test)) return false;
46875
- return true;
47467
+ if (forStatement && isNodeOfType(forStatement, "ForStatement") && forStatement.init === declaration) return !(forStatement.test && loopTestBoundsCounterByLength(forStatement.test, indexName, binding.bindingIdentifier));
47468
+ const whileLoop = findEnclosingWhileLoop(attributeNode);
47469
+ if (!whileLoop) return false;
47470
+ return !loopTestBoundsCounterByLength(whileLoop.test, indexName, binding.bindingIdentifier);
46876
47471
  };
46877
47472
  const EMPTY_NAME_SET$1 = /* @__PURE__ */ new Set();
46878
47473
  const findIteratorItemNamesOfBinding = (binding) => {
@@ -46906,11 +47501,11 @@ const collectDerivedRowContentNames = (bindingFunction, itemNames) => {
46906
47501
  * observable harm; anything stateful — form controls, media, custom
46907
47502
  * components, unknown calls — keeps the diagnostic.
46908
47503
  */
46909
- const fragmentHasStatefulChildren = (openingElement, itemNames, derivedNames) => {
47504
+ const fragmentHasStatefulChildren = (openingElement, itemNames, derivedNames, areBareItemsDynamicReactChildren) => {
46910
47505
  const jsxElement = openingElement.parent;
46911
47506
  if (!jsxElement || !isNodeOfType(jsxElement, "JSXElement")) return false;
46912
47507
  const children = jsxElement.children ?? [];
46913
- const bareIdentifierNames = children.some((child) => isNodeOfType(child, "JSXElement")) ? derivedNames : new Set([...derivedNames, ...itemNames]);
47508
+ const bareIdentifierNames = children.some((child) => isNodeOfType(child, "JSXElement")) ? derivedNames : areBareItemsDynamicReactChildren ? derivedNames : new Set([...derivedNames, ...itemNames]);
46914
47509
  return children.some((child) => containsStatefulDescendant(child, {
46915
47510
  memberRootNames: itemNames,
46916
47511
  allowAnyMemberRead: true,
@@ -46918,6 +47513,15 @@ const fragmentHasStatefulChildren = (openingElement, itemNames, derivedNames) =>
46918
47513
  callCalleeRootNames: itemNames
46919
47514
  }));
46920
47515
  };
47516
+ const elementHasDirectItemChild = (openingElement, itemNames) => {
47517
+ const jsxElement = openingElement.parent;
47518
+ if (!jsxElement || !isNodeOfType(jsxElement, "JSXElement")) return false;
47519
+ return (jsxElement.children ?? []).some((child) => {
47520
+ if (!isNodeOfType(child, "JSXExpressionContainer")) return false;
47521
+ const expression = stripParenExpression(child.expression);
47522
+ return isNodeOfType(expression, "Identifier") && itemNames.has(expression.name);
47523
+ });
47524
+ };
46921
47525
  const callbackFiltersRows = (bindingFunction) => {
46922
47526
  if (!bindingFunction) return false;
46923
47527
  let didFindNullReturn = false;
@@ -46969,19 +47573,21 @@ const noArrayIndexAsKey = defineRule({
46969
47573
  const indexUse = findPositionalIndexUse(node.value.expression, 0);
46970
47574
  if (!indexUse) return;
46971
47575
  const indexName = indexUse.identifier.name;
46972
- if (isNumericForLoopCounter(node, indexName)) return;
47576
+ if (isNumericPlaceholderLoopCounter(node, indexName)) return;
46973
47577
  if (indexUse.binding.iteratorCall && iteratorCallExemptsIndexKey(indexUse.binding.iteratorCall)) return;
46974
47578
  const keyTemplate = resolveKeyTemplateLiteral(node.value.expression);
46975
47579
  if (keyTemplate && templateHasOuterMemberIdentity(keyTemplate, indexUse.binding.bindingFunction)) return;
46976
- if (hasAriaHiddenAncestor(node)) return;
47580
+ if (hasAriaHiddenAncestor(node) && !indexUse.binding.isDataIndexedLoopCounter) return;
46977
47581
  const itemNames = findIteratorItemNamesOfBinding(indexUse.binding);
46978
47582
  const derivedNames = collectDerivedRowContentNames(indexUse.binding.bindingFunction, itemNames);
47583
+ const iteratorCallee = indexUse.binding.iteratorCall?.callee;
47584
+ const hasDynamicReactChildren = Boolean(iteratorCallee && isNodeOfType(iteratorCallee, "MemberExpression") && isDynamicReactChildrenExpression(iteratorCallee.object, 0));
46979
47585
  const openingElement = node.parent;
46980
47586
  if (openingElement && isNodeOfType(openingElement, "JSXOpeningElement")) {
46981
47587
  const elementName = openingElement.name;
46982
47588
  if (isNodeOfType(elementName, "JSXIdentifier")) {
46983
47589
  if (elementName.name === "Fragment") {
46984
- if (!fragmentHasStatefulChildren(openingElement, itemNames, derivedNames)) return;
47590
+ if (!fragmentHasStatefulChildren(openingElement, itemNames, derivedNames, hasDynamicReactChildren)) return;
46985
47591
  } else if (PURE_SVG_PRIMITIVE_TAGS.has(elementName.name)) {
46986
47592
  if (!callbackFiltersRows(indexUse.binding.bindingFunction)) return;
46987
47593
  } else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
@@ -46989,14 +47595,14 @@ const noArrayIndexAsKey = defineRule({
46989
47595
  if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
46990
47596
  const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
46991
47597
  const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET$1;
46992
- if (!containsStatefulDescendant(jsxElement, {
47598
+ if (!(hasDynamicReactChildren && elementHasDirectItemChild(openingElement, itemNames) || containsStatefulDescendant(jsxElement, {
46993
47599
  memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET$1,
46994
47600
  bareIdentifierNames: primitiveItemNames.size > 0 ? new Set([...derivedNames, ...primitiveItemNames]) : derivedNames
46995
- })) return;
47601
+ }))) return;
46996
47602
  }
46997
47603
  }
46998
47604
  }
46999
- if (isNodeOfType(elementName, "JSXMemberExpression") && isNodeOfType(elementName.object, "JSXIdentifier") && isNodeOfType(elementName.property, "JSXIdentifier") && elementName.object.name === "React" && elementName.property.name === "Fragment" && !fragmentHasStatefulChildren(openingElement, itemNames, derivedNames)) return;
47605
+ if (isNodeOfType(elementName, "JSXMemberExpression") && isNodeOfType(elementName.object, "JSXIdentifier") && isNodeOfType(elementName.property, "JSXIdentifier") && elementName.object.name === "React" && elementName.property.name === "Fragment" && !fragmentHasStatefulChildren(openingElement, itemNames, derivedNames, hasDynamicReactChildren)) return;
47000
47606
  }
47001
47607
  context.report({
47002
47608
  node,
@@ -63892,6 +64498,106 @@ const isGatedByFalsyInitialState = (node, scopes) => {
63892
64498
  };
63893
64499
  //#endregion
63894
64500
  //#region src/plugin/rules/performance/no-hydration-branch-on-browser-global.ts
64501
+ const findGuardingIfStatements = (node, functionBoundary) => {
64502
+ const guardingIfStatements = [];
64503
+ let currentNode = node.parent;
64504
+ while (currentNode && currentNode !== functionBoundary) {
64505
+ if (isNodeOfType(currentNode, "IfStatement")) guardingIfStatements.push(currentNode);
64506
+ currentNode = currentNode.parent;
64507
+ }
64508
+ return guardingIfStatements;
64509
+ };
64510
+ const doesNodeReadSymbol = (node, symbol) => {
64511
+ let doesReadSymbol = false;
64512
+ walkAst(node, (childNode) => {
64513
+ if (isNodeOfType(childNode, "Identifier") && symbol.references.some((reference) => reference.identifier === childNode && reference.flag !== "write")) {
64514
+ doesReadSymbol = true;
64515
+ return false;
64516
+ }
64517
+ });
64518
+ return doesReadSymbol;
64519
+ };
64520
+ const collectWrittenSymbols = (node, scopes) => {
64521
+ const writtenSymbols = /* @__PURE__ */ new Set();
64522
+ walkAst(node, (childNode) => {
64523
+ if (childNode !== node && isFunctionLike$1(childNode)) return false;
64524
+ if (!isNodeOfType(childNode, "Identifier")) return;
64525
+ const reference = scopes.referenceFor(childNode);
64526
+ if (!reference || reference.flag === "read" || !reference.resolvedSymbol) return;
64527
+ writtenSymbols.add(reference.resolvedSymbol);
64528
+ });
64529
+ return writtenSymbols;
64530
+ };
64531
+ const isDescendantOf = (node, ancestorNode) => {
64532
+ let currentNode = node.parent;
64533
+ while (currentNode) {
64534
+ if (currentNode === ancestorNode) return true;
64535
+ currentNode = currentNode.parent;
64536
+ }
64537
+ return false;
64538
+ };
64539
+ const getAssignedValue = (identifier) => {
64540
+ const assignmentExpression = identifier.parent;
64541
+ return isNodeOfType(assignmentExpression, "AssignmentExpression") && assignmentExpression.operator === "=" && assignmentExpression.left === identifier ? assignmentExpression.right : null;
64542
+ };
64543
+ const doesGuardPreserveInitialSymbolValue = (symbol, guardingIfStatement, scopes) => {
64544
+ const initialValue = symbol.initializer;
64545
+ if (!initialValue) return false;
64546
+ const guardedWrites = symbol.references.filter((reference) => reference.flag !== "read" && isDescendantOf(reference.identifier, guardingIfStatement));
64547
+ return guardedWrites.length > 0 && guardedWrites.every((reference) => {
64548
+ const assignedValue = getAssignedValue(reference.identifier);
64549
+ return Boolean(assignedValue && areExpressionsStructurallyEqual(initialValue, assignedValue) && doEquivalentExpressionBindingsMatch(initialValue, assignedValue, scopes));
64550
+ });
64551
+ };
64552
+ const isWriteOverwrittenBefore = (symbol, writeIdentifier, guardingIfStatement, readIdentifier, context) => symbol.references.some((reference) => reference.flag !== "read" && reference.identifier !== writeIdentifier && !isDescendantOf(reference.identifier, guardingIfStatement) && isNodeReachableWithinFunction(reference.identifier, context) && isUnconditionalOrStaticallySelected(reference.identifier, context) && getNodeStartIndex(reference.identifier) > getNodeStartIndex(writeIdentifier) && getNodeStartIndex(reference.identifier) < getNodeStartIndex(readIdentifier));
64553
+ const isUnconditionalOrStaticallySelected = (node, context) => {
64554
+ if (context.cfg.isUnconditionalFromEntry(node)) return true;
64555
+ let currentNode = node;
64556
+ let outermostStaticIfStatement = null;
64557
+ let parentNode = currentNode.parent;
64558
+ while (parentNode) {
64559
+ if (isFunctionLike$1(parentNode)) break;
64560
+ if (isNodeOfType(parentNode, "IfStatement")) {
64561
+ const staticResult = readInitialStateBoolean(parentNode.test, context.scopes);
64562
+ let selectedBranch = null;
64563
+ if (staticResult === true) selectedBranch = parentNode.consequent;
64564
+ if (staticResult === false) selectedBranch = parentNode.alternate;
64565
+ if (!selectedBranch || currentNode !== selectedBranch && !isDescendantOf(currentNode, selectedBranch)) return false;
64566
+ outermostStaticIfStatement = parentNode;
64567
+ }
64568
+ currentNode = parentNode;
64569
+ parentNode = currentNode.parent;
64570
+ }
64571
+ return Boolean(outermostStaticIfStatement && context.cfg.isUnconditionalFromEntry(outermostStaticIfStatement));
64572
+ };
64573
+ const containsExplicitReactRuntimeReference = (node, scopes) => {
64574
+ let hasRuntimeReference = false;
64575
+ walkAst(node, (childNode) => {
64576
+ if (isNodeOfType(childNode, "ImportDeclaration") && typeof childNode.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(childNode.source.value)) {
64577
+ hasRuntimeReference = true;
64578
+ return false;
64579
+ }
64580
+ if (!isNodeOfType(childNode, "CallExpression")) return;
64581
+ const sourceArgument = (childNode.arguments ?? [])[0];
64582
+ if (!isNodeOfType(childNode.callee, "Identifier") || childNode.callee.name !== "require" || !scopes.isGlobalReference(childNode.callee) || !isNodeOfType(sourceArgument, "Literal") || typeof sourceArgument.value !== "string" || !REACT_RUNTIME_MODULE_SOURCES.has(sourceArgument.value)) return;
64583
+ hasRuntimeReference = true;
64584
+ return false;
64585
+ });
64586
+ return hasRuntimeReference;
64587
+ };
64588
+ const findComponentRenderingLocalFunctionResult = (functionNode, scopes) => {
64589
+ const bindingIdentifier = getDirectFunctionBindingIdentifier(functionNode);
64590
+ if (!isNodeOfType(bindingIdentifier, "Identifier")) return null;
64591
+ const functionSymbol = scopes.symbolFor(bindingIdentifier);
64592
+ if (!functionSymbol) return null;
64593
+ for (const reference of functionSymbol.references) {
64594
+ const callExpression = reference.identifier.parent;
64595
+ if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== reference.identifier) continue;
64596
+ const componentOrHookNode = findRenderPhaseComponentOrHook(callExpression, scopes);
64597
+ if (componentOrHookNode && isInRenderedOutput(callExpression, componentOrHookNode, scopes)) return componentOrHookNode;
64598
+ }
64599
+ return null;
64600
+ };
63895
64601
  const evaluateEquality$1 = (operator, left, right) => {
63896
64602
  if (operator === "===" || operator === "==") return left === right;
63897
64603
  if (operator === "!==" || operator === "!=") return left !== right;
@@ -63942,6 +64648,107 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
63942
64648
  if (leftResult === false && rightResult === false) return false;
63943
64649
  return null;
63944
64650
  };
64651
+ const areLooselyEqualPrimitiveResults = (left, right) => {
64652
+ if (left.kind === right.kind) return left.value === right.value;
64653
+ if (left.kind === "null" && right.kind === "undefined" || left.kind === "undefined" && right.kind === "null") return true;
64654
+ if (left.kind === "boolean") return areLooselyEqualPrimitiveResults({
64655
+ kind: "number",
64656
+ value: left.value ? 1 : 0
64657
+ }, right);
64658
+ if (right.kind === "boolean") return areLooselyEqualPrimitiveResults(left, {
64659
+ kind: "number",
64660
+ value: right.value ? 1 : 0
64661
+ });
64662
+ if (left.kind === "number" && right.kind === "string") return left.value === Number(right.value);
64663
+ if (left.kind === "string" && right.kind === "number") return Number(left.value) === right.value;
64664
+ return false;
64665
+ };
64666
+ const readHydrationPrimitiveResult = (expression, context, runtime, state) => {
64667
+ const unwrappedExpression = stripParenExpression(expression);
64668
+ const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
64669
+ if (predicateMatch) return {
64670
+ kind: "boolean",
64671
+ value: predicateMatch[`${runtime}Result`]
64672
+ };
64673
+ if (isNodeOfType(unwrappedExpression, "Literal")) {
64674
+ const value = unwrappedExpression.value;
64675
+ if (value === null) return {
64676
+ kind: "null",
64677
+ value
64678
+ };
64679
+ if (typeof value === "boolean") return {
64680
+ kind: "boolean",
64681
+ value
64682
+ };
64683
+ if (typeof value === "number") return {
64684
+ kind: "number",
64685
+ value
64686
+ };
64687
+ if (typeof value === "string") return {
64688
+ kind: "string",
64689
+ value
64690
+ };
64691
+ return null;
64692
+ }
64693
+ if (isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "undefined" && context.scopes.isGlobalReference(unwrappedExpression)) return {
64694
+ kind: "undefined",
64695
+ value: void 0
64696
+ };
64697
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
64698
+ const symbol = context.scopes.symbolFor(unwrappedExpression);
64699
+ const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
64700
+ if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
64701
+ state.visitedSymbolIds.add(symbol.id);
64702
+ const result = readHydrationPrimitiveResult(parameterValue, context, runtime, state);
64703
+ state.visitedSymbolIds.delete(symbol.id);
64704
+ return result;
64705
+ }
64706
+ if (symbol && symbol.kind === "const" && symbol.initializer && symbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(symbol.id)) {
64707
+ state.visitedSymbolIds.add(symbol.id);
64708
+ const result = readHydrationPrimitiveResult(symbol.initializer, context, runtime, state);
64709
+ state.visitedSymbolIds.delete(symbol.id);
64710
+ return result;
64711
+ }
64712
+ }
64713
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
64714
+ const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
64715
+ return argumentResult === null ? null : {
64716
+ kind: "boolean",
64717
+ value: !argumentResult
64718
+ };
64719
+ }
64720
+ if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
64721
+ const leftResult = readHydrationPrimitiveResult(unwrappedExpression.left, context, runtime, state);
64722
+ const rightResult = readHydrationPrimitiveResult(unwrappedExpression.right, context, runtime, state);
64723
+ if (!leftResult || !rightResult) return null;
64724
+ if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
64725
+ const areEqual = leftResult.kind === rightResult.kind && leftResult.value === rightResult.value;
64726
+ return {
64727
+ kind: "boolean",
64728
+ value: unwrappedExpression.operator === "===" ? areEqual : !areEqual
64729
+ };
64730
+ }
64731
+ if (unwrappedExpression.operator === "==" || unwrappedExpression.operator === "!=") {
64732
+ const areEqual = areLooselyEqualPrimitiveResults(leftResult, rightResult);
64733
+ return {
64734
+ kind: "boolean",
64735
+ value: unwrappedExpression.operator === "==" ? areEqual : !areEqual
64736
+ };
64737
+ }
64738
+ }
64739
+ if (isNodeOfType(unwrappedExpression, "CallExpression")) {
64740
+ const callArguments = unwrappedExpression.arguments ?? [];
64741
+ const callee = stripParenExpression(unwrappedExpression.callee);
64742
+ if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) {
64743
+ const argumentResult = readHydrationConditionResult(callArguments[0], context, runtime, state);
64744
+ return argumentResult === null ? null : {
64745
+ kind: "boolean",
64746
+ value: argumentResult
64747
+ };
64748
+ }
64749
+ }
64750
+ return null;
64751
+ };
63945
64752
  const readHydrationConditionResult = (expression, context, runtime, state) => {
63946
64753
  const unwrappedExpression = stripParenExpression(expression);
63947
64754
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
@@ -63990,6 +64797,10 @@ const readHydrationConditionResult = (expression, context, runtime, state) => {
63990
64797
  parameterValuesBySymbolId
63991
64798
  });
63992
64799
  }
64800
+ if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
64801
+ const result = readHydrationPrimitiveResult(unwrappedExpression, context, runtime, state);
64802
+ return result?.kind === "boolean" && typeof result.value === "boolean" ? result.value : null;
64803
+ }
63993
64804
  if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
63994
64805
  const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
63995
64806
  return argumentResult === null ? null : !argumentResult;
@@ -64050,13 +64861,19 @@ const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, sc
64050
64861
  const rightSymbol = scopes.symbolFor(right);
64051
64862
  return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
64052
64863
  }
64053
- if (isNodeOfType(left, "MemberExpression") && isNodeOfType(right, "MemberExpression")) return doEquivalentExpressionBindingsMatch(left.object, right.object, scopes) && (!left.computed || doEquivalentExpressionBindingsMatch(left.property, right.property, scopes));
64054
- if (isNodeOfType(left, "CallExpression") && isNodeOfType(right, "CallExpression")) {
64055
- const rightArguments = right.arguments ?? [];
64056
- return doEquivalentExpressionBindingsMatch(left.callee, right.callee, scopes) && (left.arguments ?? []).every((argument, index) => {
64057
- const rightArgument = rightArguments[index];
64058
- return Boolean(rightArgument && doEquivalentExpressionBindingsMatch(argument, rightArgument, scopes));
64059
- });
64864
+ const rightEntries = new Map(Object.entries(right));
64865
+ for (const [key, leftValue] of Object.entries(left)) {
64866
+ if (key === "parent") continue;
64867
+ const rightValue = rightEntries.get(key);
64868
+ if (isAstNode(leftValue)) {
64869
+ if (!isAstNode(rightValue) || !doEquivalentExpressionBindingsMatch(leftValue, rightValue, scopes)) return false;
64870
+ continue;
64871
+ }
64872
+ if (!Array.isArray(leftValue)) continue;
64873
+ if (!Array.isArray(rightValue)) return false;
64874
+ const leftNodes = leftValue.filter(isAstNode);
64875
+ const rightNodes = rightValue.filter(isAstNode);
64876
+ if (leftNodes.length !== rightNodes.length || leftNodes.some((leftNode, index) => !rightNodes[index] || !doEquivalentExpressionBindingsMatch(leftNode, rightNodes[index], scopes))) return false;
64060
64877
  }
64061
64878
  return true;
64062
64879
  };
@@ -64070,6 +64887,57 @@ const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
64070
64887
  const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
64071
64888
  return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
64072
64889
  };
64890
+ const isExpressionProvablyReflexive = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
64891
+ const unwrappedExpression = stripParenExpression(expression);
64892
+ if (matchBrowserPredicate(unwrappedExpression, context)) return true;
64893
+ if (isNodeOfType(unwrappedExpression, "Literal")) return typeof unwrappedExpression.value !== "number" || !Number.isNaN(unwrappedExpression.value);
64894
+ if (isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "undefined" && context.scopes.isGlobalReference(unwrappedExpression)) return true;
64895
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
64896
+ const symbol = context.scopes.symbolFor(unwrappedExpression);
64897
+ if (!symbol || visitedSymbolIds.has(symbol.id) || !symbol.initializer) return false;
64898
+ visitedSymbolIds.add(symbol.id);
64899
+ const assignedValues = symbol.references.filter((reference) => reference.flag !== "read").map((reference) => getAssignedValue(reference.identifier));
64900
+ const isReflexive = isExpressionProvablyReflexive(symbol.initializer, context, visitedSymbolIds) && assignedValues.every((assignedValue) => Boolean(assignedValue && isExpressionProvablyReflexive(assignedValue, context, visitedSymbolIds)));
64901
+ visitedSymbolIds.delete(symbol.id);
64902
+ return isReflexive;
64903
+ }
64904
+ if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return isExpressionProvablyReflexive(unwrappedExpression.consequent, context, visitedSymbolIds) && isExpressionProvablyReflexive(unwrappedExpression.alternate, context, visitedSymbolIds);
64905
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && (unwrappedExpression.operator === "!" || unwrappedExpression.operator === "typeof" || unwrappedExpression.operator === "void")) return true;
64906
+ if (isNodeOfType(unwrappedExpression, "BinaryExpression")) return unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==" || unwrappedExpression.operator === "==" || unwrappedExpression.operator === "!=";
64907
+ if (isNodeOfType(unwrappedExpression, "ArrayExpression") || isNodeOfType(unwrappedExpression, "ObjectExpression") || isNodeOfType(unwrappedExpression, "FunctionExpression") || isNodeOfType(unwrappedExpression, "ArrowFunctionExpression") || isNodeOfType(unwrappedExpression, "TemplateLiteral")) return true;
64908
+ if (!isNodeOfType(unwrappedExpression, "CallExpression")) return false;
64909
+ const callee = stripParenExpression(unwrappedExpression.callee);
64910
+ return isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee);
64911
+ };
64912
+ const getReturnedObjectPropertyValues = (node, propertyName, scopes) => {
64913
+ if (isNodeOfType(node, "ReturnStatement")) return node.argument ? getReturnedObjectPropertyValues(node.argument, propertyName, scopes) : [];
64914
+ if (isNodeOfType(node, "ObjectExpression")) return node.properties.flatMap((property) => isNodeOfType(property, "Property") && property.kind === "init" && getResolvedStaticPropertyName(property, scopes) === propertyName ? [property.value] : []);
64915
+ if (isNodeOfType(node, "IfStatement")) return [...getReturnedObjectPropertyValues(node.consequent, propertyName, scopes), ...node.alternate ? getReturnedObjectPropertyValues(node.alternate, propertyName, scopes) : []];
64916
+ if (isNodeOfType(node, "TryStatement")) return [
64917
+ ...getReturnedObjectPropertyValues(node.block, propertyName, scopes),
64918
+ ...node.handler ? getReturnedObjectPropertyValues(node.handler.body, propertyName, scopes) : [],
64919
+ ...node.finalizer ? getReturnedObjectPropertyValues(node.finalizer, propertyName, scopes) : []
64920
+ ];
64921
+ if (!isNodeOfType(node, "BlockStatement")) return [];
64922
+ const propertyValues = [];
64923
+ for (const childStatement of node.body) {
64924
+ propertyValues.push(...getReturnedObjectPropertyValues(childStatement, propertyName, scopes));
64925
+ if (statementAlwaysExits$1(childStatement)) break;
64926
+ }
64927
+ return propertyValues;
64928
+ };
64929
+ const matchHydrationFunctionPropertyResult = (functionNode, propertyName, context, state) => {
64930
+ if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
64931
+ state.visitedFunctionNodes.add(functionNode);
64932
+ const propertyValues = getReturnedObjectPropertyValues(functionNode.body, propertyName, context.scopes);
64933
+ let match = null;
64934
+ for (const propertyValue of propertyValues) {
64935
+ match = matchHydrationConditionInternal(propertyValue, context, state);
64936
+ if (match) break;
64937
+ }
64938
+ state.visitedFunctionNodes.delete(functionNode);
64939
+ return match;
64940
+ };
64073
64941
  const matchHydrationConditionInternal = (expression, context, state) => {
64074
64942
  const unwrappedExpression = stripParenExpression(expression);
64075
64943
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
@@ -64086,14 +64954,90 @@ const matchHydrationConditionInternal = (expression, context, state) => {
64086
64954
  state.visitedSymbolIds.delete(symbol.id);
64087
64955
  return match;
64088
64956
  }
64957
+ if (symbol && (symbol.kind === "let" || symbol.kind === "var") && !state.visitedSymbolIds.has(symbol.id)) {
64958
+ state.visitedSymbolIds.add(symbol.id);
64959
+ if (symbol.initializer && symbol.references.every((reference) => reference.flag === "read")) {
64960
+ const match = matchHydrationConditionInternal(symbol.initializer, context, state);
64961
+ state.visitedSymbolIds.delete(symbol.id);
64962
+ return match;
64963
+ }
64964
+ for (const reference of symbol.references) {
64965
+ if (reference.flag === "read") continue;
64966
+ if (!isNodeReachableWithinFunction(reference.identifier, context)) continue;
64967
+ const enclosingFunction = findEnclosingFunction$1(reference.identifier);
64968
+ if (!enclosingFunction) continue;
64969
+ for (const guardingIfStatement of findGuardingIfStatements(reference.identifier, enclosingFunction)) {
64970
+ if (doesGuardPreserveInitialSymbolValue(symbol, guardingIfStatement, context.scopes) || isWriteOverwrittenBefore(symbol, reference.identifier, guardingIfStatement, unwrappedExpression, context)) continue;
64971
+ const match = matchHydrationConditionInternal(guardingIfStatement.test, context, state);
64972
+ if (match) {
64973
+ state.visitedSymbolIds.delete(symbol.id);
64974
+ return match;
64975
+ }
64976
+ }
64977
+ }
64978
+ const readingFunction = findEnclosingFunction$1(unwrappedExpression);
64979
+ for (const reference of symbol.references) {
64980
+ if (reference.flag === "read") continue;
64981
+ const writingFunction = findEnclosingFunction$1(reference.identifier);
64982
+ if (!readingFunction || !isFunctionLike$1(writingFunction) || writingFunction === readingFunction || writingFunction.async || writingFunction.params.length > 0 || isNodeOfType(writingFunction, "FunctionDeclaration") && writingFunction.generator || isNodeOfType(writingFunction, "FunctionExpression") && writingFunction.generator) continue;
64983
+ const assignedValue = getAssignedValue(reference.identifier);
64984
+ if (symbol.initializer && assignedValue && areExpressionsStructurallyEqual(symbol.initializer, assignedValue) && doEquivalentExpressionBindingsMatch(symbol.initializer, assignedValue, context.scopes)) continue;
64985
+ const functionBinding = getDirectFunctionBindingIdentifier(writingFunction);
64986
+ if (!isNodeOfType(functionBinding, "Identifier")) continue;
64987
+ const functionSymbol = context.scopes.symbolFor(functionBinding);
64988
+ if (!functionSymbol) continue;
64989
+ for (const functionReference of functionSymbol.references) {
64990
+ const callExpression = functionReference.identifier.parent;
64991
+ if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== functionReference.identifier || (callExpression.arguments ?? []).length > 0 || findEnclosingFunction$1(callExpression) !== readingFunction || !isNodeReachableWithinFunction(callExpression, context) || getNodeStartIndex(callExpression) >= getNodeStartIndex(unwrappedExpression)) continue;
64992
+ for (const guardingIfStatement of findGuardingIfStatements(callExpression, readingFunction)) {
64993
+ if (isWriteOverwrittenBefore(symbol, callExpression, guardingIfStatement, unwrappedExpression, context)) continue;
64994
+ const match = matchHydrationConditionInternal(guardingIfStatement.test, context, state);
64995
+ if (match) {
64996
+ state.visitedSymbolIds.delete(symbol.id);
64997
+ return match;
64998
+ }
64999
+ }
65000
+ }
65001
+ }
65002
+ state.visitedSymbolIds.delete(symbol.id);
65003
+ }
64089
65004
  if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
64090
65005
  state.visitedSymbolIds.add(symbol.id);
64091
65006
  const match = matchHydrationConditionInternal(symbol.initializer, context, state);
64092
65007
  state.visitedSymbolIds.delete(symbol.id);
64093
65008
  return match;
64094
65009
  }
65010
+ if (isNodeOfType(unwrappedExpression, "MemberExpression")) {
65011
+ const propertyName = getResolvedStaticPropertyName(unwrappedExpression, context.scopes, {
65012
+ allowConstNumericLiteral: true,
65013
+ stringifyNonStringLiterals: true
65014
+ });
65015
+ const object = stripParenExpression(unwrappedExpression.object);
65016
+ if (propertyName === null || !isNodeOfType(object, "CallExpression")) return null;
65017
+ const callArguments = object.arguments ?? [];
65018
+ if (isReactApiCall(object, "useMemo", context.scopes, {
65019
+ allowGlobalReactNamespace: true,
65020
+ resolveNamedAliases: true
65021
+ })) {
65022
+ const callbackArgument = callArguments[0];
65023
+ if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
65024
+ const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
65025
+ return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionPropertyResult(callbackFunction, propertyName, context, state) : null;
65026
+ }
65027
+ const helperFunction = resolveExactLocalFunction(object.callee, context.scopes);
65028
+ return isFunctionLike$1(helperFunction) && helperFunction.params.length === 0 && callArguments.length === 0 ? matchHydrationFunctionPropertyResult(helperFunction, propertyName, context, state) : null;
65029
+ }
64095
65030
  if (isNodeOfType(unwrappedExpression, "CallExpression")) {
64096
65031
  const callArguments = unwrappedExpression.arguments ?? [];
65032
+ if (isReactApiCall(unwrappedExpression, "useState", context.scopes, {
65033
+ allowGlobalReactNamespace: true,
65034
+ resolveNamedAliases: true
65035
+ })) {
65036
+ const initialState = callArguments[0];
65037
+ if (!initialState || isNodeOfType(initialState, "SpreadElement")) return null;
65038
+ const lazyInitializer = resolveExactLocalFunction(initialState, context.scopes);
65039
+ return isFunctionLike$1(lazyInitializer) && lazyInitializer.params.length === 0 ? matchHydrationFunctionResult(lazyInitializer, context, state) : matchHydrationConditionInternal(initialState, context, state);
65040
+ }
64097
65041
  if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
64098
65042
  allowGlobalReactNamespace: true,
64099
65043
  resolveNamedAliases: true
@@ -64121,6 +65065,22 @@ const matchHydrationConditionInternal = (expression, context, state) => {
64121
65065
  });
64122
65066
  }
64123
65067
  if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
65068
+ if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
65069
+ const staticTestResult = readInitialStateBoolean(unwrappedExpression.test, context.scopes);
65070
+ if (staticTestResult !== null) return matchHydrationConditionInternal(staticTestResult ? unwrappedExpression.consequent : unwrappedExpression.alternate, context, state);
65071
+ return matchHydrationConditionInternal(unwrappedExpression.test, context, state) ?? matchHydrationConditionInternal(unwrappedExpression.consequent, context, state) ?? matchHydrationConditionInternal(unwrappedExpression.alternate, context, state);
65072
+ }
65073
+ if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
65074
+ if (unwrappedExpression.operator !== "===" && unwrappedExpression.operator !== "!==" && unwrappedExpression.operator !== "==" && unwrappedExpression.operator !== "!=") return null;
65075
+ const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
65076
+ const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
65077
+ const nestedMatch = leftMatch ?? rightMatch;
65078
+ if (!nestedMatch) return null;
65079
+ const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client", state);
65080
+ const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server", state);
65081
+ if (clientResult !== null && serverResult !== null) return clientResult !== serverResult ? nestedMatch : null;
65082
+ return leftMatch && rightMatch && areExpressionsStructurallyEqual(unwrappedExpression.left, unwrappedExpression.right) && doEquivalentExpressionBindingsMatch(unwrappedExpression.left, unwrappedExpression.right, context.scopes) && isExpressionProvablyReflexive(unwrappedExpression.left, context) ? null : nestedMatch;
65083
+ }
64124
65084
  if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
64125
65085
  const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
64126
65086
  const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
@@ -64137,8 +65097,13 @@ const matchHydrationReturningStatement = (statement, context, state) => {
64137
65097
  const consequentValues = getReturnedValues(statement.consequent);
64138
65098
  const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
64139
65099
  if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
65100
+ if (conditionMatch) {
65101
+ const followingReturnedValues = findFollowingReturnedValues(statement);
65102
+ if ([...new Set([...collectWrittenSymbols(statement.consequent, context.scopes), ...statement.alternate ? collectWrittenSymbols(statement.alternate, context.scopes) : []])].some((symbol) => !doesGuardPreserveInitialSymbolValue(symbol, statement, context.scopes) && followingReturnedValues.some((value) => doesNodeReadSymbol(value, symbol)))) return conditionMatch;
65103
+ }
64140
65104
  return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
64141
65105
  }
65106
+ if (isNodeOfType(statement, "TryStatement")) return matchHydrationReturningStatement(statement.block, context, state) ?? (statement.handler ? matchHydrationReturningStatement(statement.handler.body, context, state) : null) ?? (statement.finalizer ? matchHydrationReturningStatement(statement.finalizer, context, state) : null);
64142
65107
  if (!isNodeOfType(statement, "BlockStatement")) return null;
64143
65108
  for (const childStatement of statement.body) {
64144
65109
  const match = matchHydrationReturningStatement(childStatement, context, state);
@@ -64159,47 +65124,62 @@ const matchHydrationCondition = (expression, context) => matchHydrationCondition
64159
65124
  visitedFunctionNodes: /* @__PURE__ */ new Set(),
64160
65125
  visitedSymbolIds: /* @__PURE__ */ new Set()
64161
65126
  });
64162
- const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
64163
- const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
65127
+ const areNodeArraysEquivalent = (leftNodes, rightNodes, scopes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index], scopes));
65128
+ const areRenderedBranchesEquivalent = (leftNode, rightNode, scopes) => {
64164
65129
  if (!leftNode || !rightNode) return leftNode === rightNode;
64165
65130
  const left = stripParenExpression(leftNode);
64166
65131
  const right = stripParenExpression(rightNode);
64167
- if (areExpressionsStructurallyEqual(left, right)) return true;
65132
+ if (areExpressionsStructurallyEqual(left, right)) return doEquivalentExpressionBindingsMatch(left, right, scopes);
64168
65133
  if (left.type !== right.type) return false;
64169
65134
  if (isNodeOfType(left, "JSXText") && isNodeOfType(right, "JSXText")) return left.value === right.value;
64170
65135
  if (isNodeOfType(left, "JSXExpressionContainer") && isNodeOfType(right, "JSXExpressionContainer")) {
64171
65136
  if (!isAstNode(left.expression) || !isAstNode(right.expression)) return left.expression.type === right.expression.type;
64172
- return areRenderedBranchesEquivalent(left.expression, right.expression);
65137
+ return areRenderedBranchesEquivalent(left.expression, right.expression, scopes);
64173
65138
  }
64174
65139
  if (isNodeOfType(left, "JSXElement") && isNodeOfType(right, "JSXElement")) {
64175
65140
  if (flattenJsxName$1(left.openingElement.name) !== flattenJsxName$1(right.openingElement.name)) return false;
64176
- if (!areNodeArraysEquivalent(left.openingElement.attributes, right.openingElement.attributes)) return false;
64177
- return areNodeArraysEquivalent(left.children, right.children);
65141
+ if (!areNodeArraysEquivalent(left.openingElement.attributes, right.openingElement.attributes, scopes)) return false;
65142
+ return areNodeArraysEquivalent(left.children, right.children, scopes);
64178
65143
  }
64179
- if (isNodeOfType(left, "JSXFragment") && isNodeOfType(right, "JSXFragment")) return areNodeArraysEquivalent(left.children, right.children);
65144
+ if (isNodeOfType(left, "JSXFragment") && isNodeOfType(right, "JSXFragment")) return areNodeArraysEquivalent(left.children, right.children, scopes);
64180
65145
  if (isNodeOfType(left, "JSXAttribute") && isNodeOfType(right, "JSXAttribute")) {
64181
65146
  if (flattenJsxName$1(left.name) !== flattenJsxName$1(right.name)) return false;
64182
- return areRenderedBranchesEquivalent(left.value, right.value);
65147
+ return areRenderedBranchesEquivalent(left.value, right.value, scopes);
64183
65148
  }
64184
- if (isNodeOfType(left, "JSXSpreadAttribute") && isNodeOfType(right, "JSXSpreadAttribute")) return areRenderedBranchesEquivalent(left.argument, right.argument);
65149
+ if (isNodeOfType(left, "JSXSpreadAttribute") && isNodeOfType(right, "JSXSpreadAttribute")) return areRenderedBranchesEquivalent(left.argument, right.argument, scopes);
64185
65150
  if (isNodeOfType(left, "TemplateLiteral") && isNodeOfType(right, "TemplateLiteral")) {
64186
65151
  if (left.quasis.length !== right.quasis.length) return false;
64187
65152
  if (!left.quasis.every((quasi, index) => quasi.value.cooked === right.quasis[index]?.value.cooked && quasi.value.raw === right.quasis[index]?.value.raw)) return false;
64188
- return areNodeArraysEquivalent(left.expressions, right.expressions);
65153
+ return areNodeArraysEquivalent(left.expressions, right.expressions, scopes);
64189
65154
  }
64190
65155
  return false;
64191
65156
  };
64192
- const isRenderedValue = (node) => {
65157
+ const isProvenReactCreateElementCall = (node, scopes) => {
65158
+ if (isReactApiCall(node, "createElement", scopes, {
65159
+ allowGlobalReactNamespace: true,
65160
+ resolveNamedAliases: true
65161
+ })) return true;
65162
+ if (!isNodeOfType(node, "CallExpression")) return false;
65163
+ const callee = stripParenExpression(node.callee);
65164
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "createElement") return false;
65165
+ const receiver = stripParenExpression(callee.object);
65166
+ const namespaceIdentifier = isNodeOfType(receiver, "MemberExpression") && !receiver.computed && isNodeOfType(receiver.property, "Identifier") && receiver.property.name === "default" ? stripParenExpression(receiver.object) : receiver;
65167
+ if (!isNodeOfType(namespaceIdentifier, "Identifier")) return false;
65168
+ const namespaceSymbol = scopes.symbolFor(namespaceIdentifier);
65169
+ return Boolean(namespaceSymbol?.initializer && namespaceSymbol.references.every((reference) => reference.flag === "read") && containsExplicitReactRuntimeReference(namespaceSymbol.initializer, scopes));
65170
+ };
65171
+ const isRenderedValue = (node, scopes) => {
64193
65172
  const unwrappedNode = stripParenExpression(node);
64194
65173
  if (isNodeOfType(unwrappedNode, "Literal")) return unwrappedNode.value !== null && unwrappedNode.value !== true && unwrappedNode.value !== false && unwrappedNode.value !== "";
64195
65174
  if (isNodeOfType(unwrappedNode, "TemplateLiteral")) return unwrappedNode.expressions.length > 0 || unwrappedNode.quasis[0]?.value.cooked !== "";
65175
+ if (isNodeOfType(unwrappedNode, "CallExpression")) return isProvenReactCreateElementCall(unwrappedNode, scopes);
64196
65176
  return isNodeOfType(unwrappedNode, "JSXElement") || isNodeOfType(unwrappedNode, "JSXFragment");
64197
65177
  };
64198
- const findRenderedValueInAndBranch = (node) => {
65178
+ const findRenderedValueInAndBranch = (node, scopes) => {
64199
65179
  const unwrappedNode = stripParenExpression(node);
64200
- if (isRenderedValue(unwrappedNode)) return unwrappedNode;
65180
+ if (isPotentiallyRenderedValue(unwrappedNode, scopes)) return unwrappedNode;
64201
65181
  if (!isNodeOfType(unwrappedNode, "LogicalExpression") || unwrappedNode.operator !== "&&") return null;
64202
- return findRenderedValueInAndBranch(unwrappedNode.right);
65182
+ return findRenderedValueInAndBranch(unwrappedNode.right, scopes);
64203
65183
  };
64204
65184
  const findEnclosingJsxAttribute = (node) => {
64205
65185
  let currentNode = node.parent;
@@ -64232,6 +65212,11 @@ const getReturnedValues = (statement) => {
64232
65212
  if (!statement) return [];
64233
65213
  if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? [statement.argument] : [];
64234
65214
  if (isNodeOfType(statement, "IfStatement")) return [...getReturnedValues(statement.consequent), ...getReturnedValues(statement.alternate)];
65215
+ if (isNodeOfType(statement, "TryStatement")) return [
65216
+ ...getReturnedValues(statement.block),
65217
+ ...getReturnedValues(statement.handler?.body),
65218
+ ...getReturnedValues(statement.finalizer)
65219
+ ];
64235
65220
  if (!isNodeOfType(statement, "BlockStatement")) return [];
64236
65221
  const returnedValues = [];
64237
65222
  for (const childStatement of statement.body) {
@@ -64240,6 +65225,127 @@ const getReturnedValues = (statement) => {
64240
65225
  }
64241
65226
  return returnedValues;
64242
65227
  };
65228
+ const isPotentiallyRenderedValueInternal = (node, scopes, visitedFunctionNodes) => {
65229
+ const unwrappedNode = stripParenExpression(node);
65230
+ if (isRenderedValue(unwrappedNode, scopes)) return true;
65231
+ if (isNodeOfType(unwrappedNode, "ConditionalExpression")) return isPotentiallyRenderedValueInternal(unwrappedNode.consequent, scopes, visitedFunctionNodes) && isPotentiallyRenderedValueInternal(unwrappedNode.alternate, scopes, visitedFunctionNodes);
65232
+ if (isNodeOfType(unwrappedNode, "LogicalExpression")) return isPotentiallyRenderedValueInternal(unwrappedNode.right, scopes, visitedFunctionNodes);
65233
+ if (!isNodeOfType(unwrappedNode, "CallExpression")) return false;
65234
+ const calledFunction = resolveExactLocalFunction(unwrappedNode.callee, scopes);
65235
+ if (!isFunctionLike$1(calledFunction) || visitedFunctionNodes.has(calledFunction)) return false;
65236
+ visitedFunctionNodes.add(calledFunction);
65237
+ const returnedValues = isNodeOfType(calledFunction.body, "BlockStatement") ? getReturnedValues(calledFunction.body) : [calledFunction.body];
65238
+ const isPotentiallyRendered = returnedValues.length > 0 && returnedValues.every((returnedValue) => isPotentiallyRenderedValueInternal(returnedValue, scopes, visitedFunctionNodes));
65239
+ visitedFunctionNodes.delete(calledFunction);
65240
+ return isPotentiallyRendered;
65241
+ };
65242
+ const isPotentiallyRenderedValue = (node, scopes) => isPotentiallyRenderedValueInternal(node, scopes, /* @__PURE__ */ new Set());
65243
+ const findUseStateBindingSymbol = (node, componentOrHookNode, scopes) => {
65244
+ let currentNode = node.parent;
65245
+ while (currentNode && currentNode !== componentOrHookNode) {
65246
+ if (isNodeOfType(currentNode, "CallExpression") && isReactApiCall(currentNode, "useState", scopes, {
65247
+ allowGlobalReactNamespace: true,
65248
+ resolveNamedAliases: true
65249
+ }) && isNodeOfType(currentNode.parent, "VariableDeclarator") && isNodeOfType(currentNode.parent.id, "ArrayPattern")) {
65250
+ const stateBinding = currentNode.parent.id.elements?.[0];
65251
+ return isNodeOfType(stateBinding, "Identifier") ? scopes.symbolFor(stateBinding) : null;
65252
+ }
65253
+ if (isFunctionLike$1(currentNode)) return null;
65254
+ currentNode = currentNode.parent;
65255
+ }
65256
+ return null;
65257
+ };
65258
+ const doesReferenceControlStructuralRenderedValue = (referenceIdentifier) => {
65259
+ let currentNode = referenceIdentifier;
65260
+ let parentNode = currentNode.parent;
65261
+ while (parentNode) {
65262
+ if (isNodeOfType(parentNode, "ConditionalExpression") && parentNode.test === currentNode && (isStructuralRenderedValue(parentNode.consequent) || isStructuralRenderedValue(parentNode.alternate))) return true;
65263
+ if (isNodeOfType(parentNode, "LogicalExpression") && parentNode.left === currentNode && isStructuralRenderedValue(parentNode.right)) return true;
65264
+ if (isNodeOfType(parentNode, "JSXExpressionContainer") || isFunctionLike$1(parentNode)) return false;
65265
+ currentNode = parentNode;
65266
+ parentNode = currentNode.parent;
65267
+ }
65268
+ return false;
65269
+ };
65270
+ const isRenderedHydrationConsumer = (node, producerHookNode, scopes) => {
65271
+ const renderingComponent = findRenderPhaseComponentOrHook(node, scopes);
65272
+ return Boolean(renderingComponent && renderingComponent !== producerHookNode && isInRenderedOutput(node, renderingComponent, scopes) && !isGatedByFalsyInitialState(node, scopes) && !isAfterClientOnlyEarlyReturn(node, renderingComponent, scopes) && (!hasSuppressHydrationWarningAttribute(findEnclosingJsxOpeningElement(node)) || doesReferenceControlStructuralRenderedValue(node)));
65273
+ };
65274
+ const doesConsumerExpressionReachRenderedOutput = (node, producerHookNode, scopes, visitedSymbolIds) => {
65275
+ if (isRenderedHydrationConsumer(node, producerHookNode, scopes)) return true;
65276
+ const parentNode = node.parent;
65277
+ if (!isNodeOfType(parentNode, "VariableDeclarator") || parentNode.init !== node || !isNodeOfType(parentNode.id, "Identifier")) return false;
65278
+ const aliasSymbol = scopes.symbolFor(parentNode.id);
65279
+ if (!aliasSymbol || visitedSymbolIds.has(aliasSymbol.id)) return false;
65280
+ visitedSymbolIds.add(aliasSymbol.id);
65281
+ const doesReachRenderedOutput = aliasSymbol.references.some((reference) => doesConsumerExpressionReachRenderedOutput(reference.identifier, producerHookNode, scopes, visitedSymbolIds));
65282
+ visitedSymbolIds.delete(aliasSymbol.id);
65283
+ return doesReachRenderedOutput;
65284
+ };
65285
+ const doesConsumerBindingReachRenderedOutput = (bindingIdentifier, producerHookNode, scopes) => {
65286
+ if (!isNodeOfType(bindingIdentifier, "Identifier")) return false;
65287
+ const consumerSymbol = scopes.symbolFor(bindingIdentifier);
65288
+ if (!consumerSymbol) return false;
65289
+ return consumerSymbol.references.some((reference) => doesConsumerExpressionReachRenderedOutput(reference.identifier, producerHookNode, scopes, new Set([consumerSymbol.id])));
65290
+ };
65291
+ const getReturnedStatePaths = (returnedValue, stateSymbol, scopes) => {
65292
+ const unwrappedValue = stripParenExpression(returnedValue);
65293
+ if (isNodeOfType(unwrappedValue, "ObjectExpression")) return unwrappedValue.properties.flatMap((property) => {
65294
+ if (!isNodeOfType(property, "Property") || property.kind !== "init" || !doesNodeReadSymbol(property.value, stateSymbol)) return [];
65295
+ const propertyName = getResolvedStaticPropertyName(property, scopes);
65296
+ return propertyName === null ? [] : [{
65297
+ kind: "property",
65298
+ key: propertyName
65299
+ }];
65300
+ });
65301
+ if (isNodeOfType(unwrappedValue, "ArrayExpression")) return (unwrappedValue.elements ?? []).flatMap((element, index) => element && isAstNode(element) && doesNodeReadSymbol(element, stateSymbol) ? [{
65302
+ kind: "index",
65303
+ key: String(index)
65304
+ }] : []);
65305
+ return doesNodeReadSymbol(unwrappedValue, stateSymbol) ? [{
65306
+ kind: "direct",
65307
+ key: null
65308
+ }] : [];
65309
+ };
65310
+ const doesCallResultPathReachRenderedOutput = (callExpression, returnedStatePath, producerHookNode, scopes) => {
65311
+ const callParent = callExpression.parent;
65312
+ if (returnedStatePath.kind === "direct") return doesConsumerExpressionReachRenderedOutput(callExpression, producerHookNode, scopes, /* @__PURE__ */ new Set());
65313
+ if (isNodeOfType(callParent, "MemberExpression") && callParent.object === callExpression && getResolvedStaticPropertyName(callParent, scopes, {
65314
+ allowConstNumericLiteral: true,
65315
+ stringifyNonStringLiterals: true
65316
+ }) === returnedStatePath.key) return doesConsumerExpressionReachRenderedOutput(callParent, producerHookNode, scopes, /* @__PURE__ */ new Set());
65317
+ if (!isNodeOfType(callParent, "VariableDeclarator") || callParent.init !== callExpression) return false;
65318
+ if (returnedStatePath.kind === "property" && isNodeOfType(callParent.id, "ObjectPattern")) return callParent.id.properties.some((property) => isNodeOfType(property, "Property") && getResolvedStaticPropertyName(property, scopes) === returnedStatePath.key && doesConsumerBindingReachRenderedOutput(property.value, producerHookNode, scopes));
65319
+ if (returnedStatePath.kind === "index" && isNodeOfType(callParent.id, "ArrayPattern")) {
65320
+ const element = callParent.id.elements?.[Number(returnedStatePath.key)];
65321
+ return Boolean(element && doesConsumerBindingReachRenderedOutput(element, producerHookNode, scopes));
65322
+ }
65323
+ if (!isNodeOfType(callParent.id, "Identifier")) return false;
65324
+ const resultSymbol = scopes.symbolFor(callParent.id);
65325
+ if (!resultSymbol) return false;
65326
+ return resultSymbol.references.some((reference) => {
65327
+ const memberExpression = reference.identifier.parent;
65328
+ return Boolean(isNodeOfType(memberExpression, "MemberExpression") && memberExpression.object === reference.identifier && getResolvedStaticPropertyName(memberExpression, scopes, {
65329
+ allowConstNumericLiteral: true,
65330
+ stringifyNonStringLiterals: true
65331
+ }) === returnedStatePath.key && doesConsumerExpressionReachRenderedOutput(memberExpression, producerHookNode, scopes, new Set([resultSymbol.id])));
65332
+ });
65333
+ };
65334
+ const isReturnedUseStateInitializerRendered = (node, componentOrHookNode, scopes) => {
65335
+ if (!isFunctionLike$1(componentOrHookNode)) return false;
65336
+ const stateSymbol = findUseStateBindingSymbol(node, componentOrHookNode, scopes);
65337
+ if (!stateSymbol) return false;
65338
+ const returnedStatePaths = (isNodeOfType(componentOrHookNode.body, "BlockStatement") ? getReturnedValues(componentOrHookNode.body) : [componentOrHookNode.body]).flatMap((returnedValue) => getReturnedStatePaths(returnedValue, stateSymbol, scopes));
65339
+ if (returnedStatePaths.length === 0) return false;
65340
+ const functionBinding = getDirectFunctionBindingIdentifier(componentOrHookNode);
65341
+ if (!isNodeOfType(functionBinding, "Identifier")) return false;
65342
+ const functionSymbol = scopes.symbolFor(functionBinding);
65343
+ if (!functionSymbol) return false;
65344
+ return functionSymbol.references.some((functionReference) => {
65345
+ const callExpression = functionReference.identifier.parent;
65346
+ return Boolean(isNodeOfType(callExpression, "CallExpression") && callExpression.callee === functionReference.identifier && returnedStatePaths.some((returnedStatePath) => doesCallResultPathReachRenderedOutput(callExpression, returnedStatePath, componentOrHookNode, scopes)));
65347
+ });
65348
+ };
64243
65349
  const findFollowingReturnedValues = (ifStatement) => {
64244
65350
  const parentNode = ifStatement.parent;
64245
65351
  if (!isNodeOfType(parentNode, "BlockStatement")) return [];
@@ -64252,24 +65358,24 @@ const findFollowingReturnedValues = (ifStatement) => {
64252
65358
  }
64253
65359
  return returnedValues;
64254
65360
  };
64255
- const areConditionExpressionsEquivalent = (leftExpression, rightExpression) => {
65361
+ const areConditionExpressionsEquivalent = (leftExpression, rightExpression, scopes) => {
64256
65362
  const left = stripParenExpression(leftExpression);
64257
65363
  const right = stripParenExpression(rightExpression);
64258
- if (areExpressionsStructurallyEqual(left, right)) return true;
65364
+ if (areExpressionsStructurallyEqual(left, right)) return doEquivalentExpressionBindingsMatch(left, right, scopes);
64259
65365
  if (left.type !== right.type) return false;
64260
- if (isNodeOfType(left, "UnaryExpression") && isNodeOfType(right, "UnaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.argument, right.argument);
64261
- if (isNodeOfType(left, "LogicalExpression") && isNodeOfType(right, "LogicalExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left) && areConditionExpressionsEquivalent(left.right, right.right);
64262
- if (isNodeOfType(left, "BinaryExpression") && isNodeOfType(right, "BinaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left) && areConditionExpressionsEquivalent(left.right, right.right);
65366
+ if (isNodeOfType(left, "UnaryExpression") && isNodeOfType(right, "UnaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.argument, right.argument, scopes);
65367
+ if (isNodeOfType(left, "LogicalExpression") && isNodeOfType(right, "LogicalExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left, scopes) && areConditionExpressionsEquivalent(left.right, right.right, scopes);
65368
+ if (isNodeOfType(left, "BinaryExpression") && isNodeOfType(right, "BinaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left, scopes) && areConditionExpressionsEquivalent(left.right, right.right, scopes);
64263
65369
  return false;
64264
65370
  };
64265
- const areReturnTreesEquivalent = (leftStatement, rightStatement) => {
65371
+ const areReturnTreesEquivalent = (leftStatement, rightStatement, scopes) => {
64266
65372
  if (!leftStatement || !rightStatement) return leftStatement === rightStatement;
64267
- if (isNodeOfType(leftStatement, "ReturnStatement") && isNodeOfType(rightStatement, "ReturnStatement")) return areRenderedBranchesEquivalent(leftStatement.argument, rightStatement.argument);
64268
- if (isNodeOfType(leftStatement, "IfStatement") && isNodeOfType(rightStatement, "IfStatement")) return areConditionExpressionsEquivalent(leftStatement.test, rightStatement.test) && areReturnTreesEquivalent(leftStatement.consequent, rightStatement.consequent) && areReturnTreesEquivalent(leftStatement.alternate, rightStatement.alternate);
65373
+ if (isNodeOfType(leftStatement, "ReturnStatement") && isNodeOfType(rightStatement, "ReturnStatement")) return areRenderedBranchesEquivalent(leftStatement.argument, rightStatement.argument, scopes);
65374
+ if (isNodeOfType(leftStatement, "IfStatement") && isNodeOfType(rightStatement, "IfStatement")) return areConditionExpressionsEquivalent(leftStatement.test, rightStatement.test, scopes) && areReturnTreesEquivalent(leftStatement.consequent, rightStatement.consequent, scopes) && areReturnTreesEquivalent(leftStatement.alternate, rightStatement.alternate, scopes);
64269
65375
  if (!isNodeOfType(leftStatement, "BlockStatement") || !isNodeOfType(rightStatement, "BlockStatement")) return false;
64270
65376
  const leftReturningStatements = leftStatement.body.filter((statement) => getReturnedValues(statement).length > 0);
64271
65377
  const rightReturningStatements = rightStatement.body.filter((statement) => getReturnedValues(statement).length > 0);
64272
- return leftReturningStatements.length === rightReturningStatements.length && leftReturningStatements.every((statement, index) => areReturnTreesEquivalent(statement, rightReturningStatements[index]));
65378
+ return leftReturningStatements.length === rightReturningStatements.length && leftReturningStatements.every((statement, index) => areReturnTreesEquivalent(statement, rightReturningStatements[index], scopes));
64273
65379
  };
64274
65380
  const isStructuralRenderedValue = (node) => {
64275
65381
  if (!node) return false;
@@ -64293,19 +65399,22 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
64293
65399
  if (isTestlikeFilename(context.filename)) return {};
64294
65400
  if (classifyReactNativeFileTarget(context) === "react-native") return {};
64295
65401
  let fileHasUseClientDirective = false;
65402
+ let fileHasExplicitReactRuntimeReference = false;
64296
65403
  let fileIsEmailTemplate = false;
64297
65404
  const reportedNodes = /* @__PURE__ */ new Set();
64298
- const reportHydrationBranch = (conditionNode, leftBranch, rightBranch, requiresRenderedContext) => {
65405
+ const reportHydrationBranch = (conditionNode, leftBranch, rightBranch, requiresRenderedContext, hasProvenRenderedConsumer = false) => {
64299
65406
  const conditionMatch = matchHydrationCondition(conditionNode, context);
64300
65407
  if (!conditionMatch) return;
64301
65408
  const { predicateMatch, predicateNode } = conditionMatch;
64302
65409
  if (reportedNodes.has(predicateNode)) return;
64303
- if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
64304
- const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes);
65410
+ if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch, context.scopes)) return;
65411
+ const enclosingFunction = findEnclosingFunction$1(conditionNode);
65412
+ const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes) ?? (enclosingFunction ? findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) : null);
64305
65413
  if (!componentOrHookNode) return;
64306
- if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
64307
- if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes)) return;
64308
- if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
65414
+ const hasRenderedLocalFunctionConsumer = Boolean(enclosingFunction && enclosingFunction !== componentOrHookNode && findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) === componentOrHookNode);
65415
+ if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective) && !fileHasExplicitReactRuntimeReference) return;
65416
+ if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes) && !hasRenderedLocalFunctionConsumer) return;
65417
+ if (!hasProvenRenderedConsumer && !(requiresRenderedContext ? isPotentiallyRenderedValue(leftBranch, context.scopes) : isRenderedValue(leftBranch, context.scopes)) && (!rightBranch || !(requiresRenderedContext ? isPotentiallyRenderedValue(rightBranch, context.scopes) : isRenderedValue(rightBranch, context.scopes)))) {
64309
65418
  const attribute = findEnclosingJsxAttribute(conditionNode);
64310
65419
  if (!attribute || isEventHandlerAttribute(attribute)) return;
64311
65420
  }
@@ -64324,28 +65433,31 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
64324
65433
  return {
64325
65434
  Program(node) {
64326
65435
  fileHasUseClientDirective = hasDirective(node, "use client");
65436
+ fileHasExplicitReactRuntimeReference = containsExplicitReactRuntimeReference(node, context.scopes);
64327
65437
  fileIsEmailTemplate = hasEmailTemplateImport(node);
64328
65438
  },
64329
65439
  ConditionalExpression(node) {
64330
65440
  reportHydrationBranch(node.test, node.consequent, node.alternate, true);
65441
+ const componentOrHookNode = findRenderPhaseComponentOrHook(node, context.scopes);
65442
+ if (componentOrHookNode && isReturnedUseStateInitializerRendered(node, componentOrHookNode, context.scopes)) reportHydrationBranch(node.test, node.consequent, node.alternate, false, true);
64331
65443
  },
64332
65444
  LogicalExpression(node) {
64333
65445
  if (node.operator !== "&&" && node.operator !== "||") return;
64334
- const renderedValue = node.operator === "&&" ? findRenderedValueInAndBranch(node.right) : isRenderedValue(node.right) ? node.right : null;
65446
+ const renderedValue = node.operator === "&&" ? findRenderedValueInAndBranch(node.right, context.scopes) : isPotentiallyRenderedValue(node.right, context.scopes) ? node.right : null;
64335
65447
  if (!renderedValue) return;
64336
65448
  reportHydrationBranch(node, renderedValue, null, true);
64337
65449
  },
64338
65450
  IfStatement(node) {
64339
- if (node.alternate && areReturnTreesEquivalent(node.consequent, node.alternate)) return;
65451
+ if (node.alternate && areReturnTreesEquivalent(node.consequent, node.alternate, context.scopes)) return;
64340
65452
  const consequentValues = getReturnedValues(node.consequent);
64341
65453
  const alternateValues = node.alternate ? getReturnedValues(node.alternate) : findFollowingReturnedValues(node);
64342
65454
  if (consequentValues.length === 0 || alternateValues.length === 0) return;
64343
- const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes);
64344
- if (!componentOrHookNode) return;
64345
65455
  const enclosingFunction = findEnclosingFunction$1(node);
64346
- if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes))) return;
65456
+ const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes) ?? (enclosingFunction ? findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) : null);
65457
+ if (!componentOrHookNode) return;
65458
+ if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes) && findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) !== componentOrHookNode)) return;
64347
65459
  for (const consequentValue of consequentValues) for (const alternateValue of alternateValues) {
64348
- if (!isRenderedValue(consequentValue) && !isRenderedValue(alternateValue)) continue;
65460
+ if (!isRenderedValue(consequentValue, context.scopes) && !isRenderedValue(alternateValue, context.scopes)) continue;
64349
65461
  reportHydrationBranch(node.test, consequentValue, alternateValue, false);
64350
65462
  }
64351
65463
  }
@@ -67511,6 +68623,77 @@ const isCancellationGuardTest = (test) => {
67511
68623
  });
67512
68624
  return matches;
67513
68625
  };
68626
+ const getReactRefCurrent = (expression, context) => {
68627
+ const stripped = stripParenExpression(expression);
68628
+ if (!isNodeOfType(stripped, "MemberExpression") || getStaticPropertyName(stripped) !== "current") return null;
68629
+ const receiver = stripParenExpression(stripped.object);
68630
+ if (!isNodeOfType(receiver, "Identifier")) return null;
68631
+ const binding = findVariableInitializer(receiver, receiver.name);
68632
+ const initializer = binding?.initializer ? stripParenExpression(binding.initializer) : null;
68633
+ return initializer && isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, USE_REF_HOOK_NAMES$1, context.scopes, {
68634
+ allowGlobalReactNamespace: true,
68635
+ allowUnboundBareCalls: true
68636
+ }) ? stripped : null;
68637
+ };
68638
+ const getStableOwnershipToken = (expression, context) => {
68639
+ const stripped = stripParenExpression(expression);
68640
+ if (!isNodeOfType(stripped, "Identifier")) return null;
68641
+ const symbol = context.scopes.symbolFor(stripped);
68642
+ const initializer = symbol?.initializer ? stripParenExpression(symbol.initializer) : null;
68643
+ const isStableAsyncIdentity = Boolean(initializer && isNodeOfType(initializer, "ObjectExpression")) || Boolean(initializer && getReactRefCurrent(initializer, context)) || Boolean(initializer && isNodeOfType(initializer, "UpdateExpression") && initializer.operator === "++" && getReactRefCurrent(initializer.argument, context));
68644
+ return symbol && symbol.kind === "const" && symbol.references.every((reference) => reference.flag === "read") && isStableAsyncIdentity ? stripped : null;
68645
+ };
68646
+ const getAsyncOwnershipComparison = (test, context) => {
68647
+ const stripped = stripParenExpression(test);
68648
+ if (!isNodeOfType(stripped, "BinaryExpression")) return null;
68649
+ const leftRef = getReactRefCurrent(stripped.left, context);
68650
+ const rightRef = getReactRefCurrent(stripped.right, context);
68651
+ const leftToken = getStableOwnershipToken(stripped.left, context);
68652
+ const rightToken = getStableOwnershipToken(stripped.right, context);
68653
+ if (stripped.operator === "===" || stripped.operator === "==") {
68654
+ if (leftRef && rightToken) return {
68655
+ refCurrent: leftRef,
68656
+ token: rightToken,
68657
+ mode: "owns",
68658
+ isOrdered: false
68659
+ };
68660
+ if (rightRef && leftToken) return {
68661
+ refCurrent: rightRef,
68662
+ token: leftToken,
68663
+ mode: "owns",
68664
+ isOrdered: false
68665
+ };
68666
+ return null;
68667
+ }
68668
+ if (stripped.operator === "!==" || stripped.operator === "!=") {
68669
+ if (leftRef && rightToken) return {
68670
+ refCurrent: leftRef,
68671
+ token: rightToken,
68672
+ mode: "lost",
68673
+ isOrdered: false
68674
+ };
68675
+ if (rightRef && leftToken) return {
68676
+ refCurrent: rightRef,
68677
+ token: leftToken,
68678
+ mode: "lost",
68679
+ isOrdered: false
68680
+ };
68681
+ return null;
68682
+ }
68683
+ if (stripped.operator === "<=" && leftRef && rightToken) return {
68684
+ refCurrent: leftRef,
68685
+ token: rightToken,
68686
+ mode: "owns",
68687
+ isOrdered: true
68688
+ };
68689
+ if (stripped.operator === ">=" && rightRef && leftToken) return {
68690
+ refCurrent: rightRef,
68691
+ token: leftToken,
68692
+ mode: "owns",
68693
+ isOrdered: true
68694
+ };
68695
+ return null;
68696
+ };
67514
68697
  const dedupeCatchPathStates = (states) => {
67515
68698
  const statesByKey = /* @__PURE__ */ new Map();
67516
68699
  for (const state of states) statesByKey.set(`${Number(state.isCleared)}:${Number(state.isCancellationPath)}`, state);
@@ -67843,7 +69026,221 @@ const isInsideTryFinalizer = (node, tryStatement) => {
67843
69026
  }
67844
69027
  return false;
67845
69028
  };
67846
- const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, acceptedCleanupAssignments, context) => {
69029
+ const getDirectBlockEntry = (node, functionNode) => {
69030
+ let entry = node;
69031
+ let cursor = node.parent;
69032
+ while (cursor && cursor !== functionNode) {
69033
+ if (isNodeOfType(cursor, "BlockStatement")) return {
69034
+ block: cursor,
69035
+ entry
69036
+ };
69037
+ entry = cursor;
69038
+ cursor = cursor.parent ?? null;
69039
+ }
69040
+ return null;
69041
+ };
69042
+ const claimPrecedesTruthySet = (claimNode, truthySet, firstRiskyAwait, functionNode, context) => {
69043
+ const claimStart = getNodeStart$1(claimNode);
69044
+ if (claimStart === null || claimStart >= firstRiskyAwait.start || truthySet.start >= firstRiskyAwait.start) return false;
69045
+ const claimEntry = getDirectBlockEntry(claimNode, functionNode);
69046
+ const truthyEntry = getDirectBlockEntry(truthySet.node, functionNode);
69047
+ if (!claimEntry || !truthyEntry || claimEntry.block !== truthyEntry.block) return false;
69048
+ let claimCursor = claimNode.parent;
69049
+ while (claimCursor && claimCursor !== claimEntry.block) {
69050
+ 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;
69051
+ claimCursor = claimCursor.parent ?? null;
69052
+ }
69053
+ const claimIndex = claimEntry.block.body.findIndex((statement) => statement === claimEntry.entry);
69054
+ const truthyIndex = claimEntry.block.body.findIndex((statement) => statement === truthyEntry.entry);
69055
+ if (claimIndex === -1 || truthyIndex === -1 || claimIndex >= truthyIndex) return false;
69056
+ return claimEntry.block.body.slice(claimIndex + 1, truthyIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, functionNode, context));
69057
+ };
69058
+ const getOwningFunction = (functionNode) => {
69059
+ let ownerFunction = functionNode;
69060
+ let cursor = functionNode.parent;
69061
+ while (cursor) {
69062
+ if (isFunctionLike$1(cursor)) ownerFunction = cursor;
69063
+ cursor = cursor.parent ?? null;
69064
+ }
69065
+ return ownerFunction;
69066
+ };
69067
+ const isEffectInvalidationPairedWithReset = (writeNode, truthySets, context) => {
69068
+ const truthyCall = truthySets[0]?.node;
69069
+ if (!truthyCall || !isNodeOfType(truthyCall, "CallExpression")) return false;
69070
+ const setter = getSetterBooleanValue(truthyCall, context);
69071
+ if (!setter) return false;
69072
+ let effectCallback = writeNode.parent;
69073
+ while (effectCallback && !isFunctionLike$1(effectCallback)) effectCallback = effectCallback.parent ?? null;
69074
+ if (!effectCallback || !isEffectCallback(effectCallback, context)) return false;
69075
+ if (!isUnconditionallyExecutedWithinFunction(writeNode, effectCallback, context)) return false;
69076
+ const writeEntry = getDirectBlockEntry(writeNode, effectCallback);
69077
+ if (!writeEntry) return false;
69078
+ let isPaired = false;
69079
+ walkOwnFunctionScope(effectCallback, (candidate) => {
69080
+ if (isPaired || !isNodeOfType(candidate, "CallExpression")) return;
69081
+ const candidateSetter = getSetterBooleanValue(candidate, context);
69082
+ if (candidateSetter?.setterKey !== setter.setterKey || candidateSetter.value || !isUnconditionallyExecutedWithinFunction(candidate, effectCallback, context)) return;
69083
+ const resetEntry = getDirectBlockEntry(candidate, effectCallback);
69084
+ if (!resetEntry || resetEntry.block !== writeEntry.block) return;
69085
+ const writeIndex = writeEntry.block.body.findIndex((statement) => statement === writeEntry.entry);
69086
+ const resetIndex = resetEntry.block.body.findIndex((statement) => statement === resetEntry.entry);
69087
+ if (writeIndex === -1 || resetIndex === -1) return;
69088
+ if (resetIndex <= writeIndex) {
69089
+ isPaired = true;
69090
+ return false;
69091
+ }
69092
+ isPaired = writeEntry.block.body.slice(writeIndex + 1, resetIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, effectCallback, context));
69093
+ return isPaired ? false : void 0;
69094
+ });
69095
+ return isPaired;
69096
+ };
69097
+ const isUnconditionalReturnBranch = (statement) => {
69098
+ if (isNodeOfType(statement, "ReturnStatement")) return true;
69099
+ return Boolean(isNodeOfType(statement, "BlockStatement") && statement.body.length === 1 && isNodeOfType(statement.body[0], "ReturnStatement"));
69100
+ };
69101
+ const findSingleFlightSnapshotClaim = (tokenInitializer, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
69102
+ const snapshotEntry = getDirectBlockEntry(tokenInitializer, functionNode);
69103
+ const resetEntry = getDirectBlockEntry(resetNode, functionNode);
69104
+ if (!snapshotEntry || !resetEntry) return null;
69105
+ const claimCandidates = [];
69106
+ const releaseCandidates = [];
69107
+ walkOwnFunctionScope(functionNode, (candidate) => {
69108
+ if (!isNodeOfType(candidate, "AssignmentExpression") || candidate.operator !== "=" || !getReactRefCurrent(candidate.left, context)) return;
69109
+ const assignedValue = stripParenExpression(candidate.right);
69110
+ if (!isNodeOfType(assignedValue, "Literal") || typeof assignedValue.value !== "boolean") return;
69111
+ const candidateKey = serializeReferenceKey({
69112
+ node: candidate.left,
69113
+ scopes: context.scopes
69114
+ });
69115
+ if (!candidateKey) return;
69116
+ if (!assignedValue.value) {
69117
+ if (getDirectBlockEntry(candidate, functionNode)?.block === resetEntry.block) releaseCandidates.push(candidate);
69118
+ return;
69119
+ }
69120
+ if (!truthySets.some((truthySet) => claimPrecedesTruthySet(candidate, truthySet, firstRiskyAwait, functionNode, context))) return;
69121
+ const candidateEntry = getDirectBlockEntry(candidate, functionNode);
69122
+ if (!candidateEntry || candidateEntry.block !== snapshotEntry.block) return;
69123
+ const candidateIndex = candidateEntry.block.body.findIndex((statement) => statement === candidateEntry.entry);
69124
+ const snapshotIndex = candidateEntry.block.body.findIndex((statement) => statement === snapshotEntry.entry);
69125
+ if (candidateIndex === -1 || snapshotIndex === -1 || candidateIndex >= snapshotIndex) return;
69126
+ const guardIndex = candidateEntry.block.body.findLastIndex((statement, statementIndex) => {
69127
+ if (statementIndex >= candidateIndex || !isNodeOfType(statement, "IfStatement") || statement.alternate !== null || !isUnconditionalReturnBranch(statement.consequent)) return false;
69128
+ return serializeReferenceKey({
69129
+ node: stripParenExpression(statement.test),
69130
+ scopes: context.scopes
69131
+ }) === candidateKey;
69132
+ });
69133
+ if (guardIndex === -1 || !candidateEntry.block.body.slice(guardIndex + 1, candidateIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, functionNode, context))) return;
69134
+ claimCandidates.push(candidate);
69135
+ });
69136
+ const claim = claimCandidates.find((claimCandidate) => {
69137
+ const candidateKey = serializeReferenceKey({
69138
+ node: claimCandidate.left,
69139
+ scopes: context.scopes
69140
+ });
69141
+ return releaseCandidates.some((releaseCandidate) => serializeReferenceKey({
69142
+ node: releaseCandidate.left,
69143
+ scopes: context.scopes
69144
+ }) === candidateKey);
69145
+ });
69146
+ if (!claim) return null;
69147
+ const claimKey = serializeReferenceKey({
69148
+ node: claim.left,
69149
+ scopes: context.scopes
69150
+ });
69151
+ const release = releaseCandidates.find((releaseCandidate) => serializeReferenceKey({
69152
+ node: releaseCandidate.left,
69153
+ scopes: context.scopes
69154
+ }) === claimKey);
69155
+ if (!claimKey || !release) return null;
69156
+ const releaseEntry = getDirectBlockEntry(release, functionNode);
69157
+ if (!releaseEntry || releaseEntry.block !== resetEntry.block) return null;
69158
+ const releaseIndex = resetEntry.block.body.findIndex((statement) => statement === releaseEntry.entry);
69159
+ const resetIndex = resetEntry.block.body.findIndex((statement) => statement === resetEntry.entry);
69160
+ 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;
69161
+ let didFindUnsafeWrite = false;
69162
+ walkAst(getOwningFunction(functionNode), (candidate) => {
69163
+ if (didFindUnsafeWrite || candidate === claim || candidate === release) return;
69164
+ const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
69165
+ if (writeTarget && serializeReferenceKey({
69166
+ node: writeTarget,
69167
+ scopes: context.scopes
69168
+ }) === claimKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindUnsafeWrite = true;
69169
+ });
69170
+ return didFindUnsafeWrite ? null : claim;
69171
+ };
69172
+ const findOwnershipClaim = (comparison, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
69173
+ const refKey = serializeReferenceKey({
69174
+ node: comparison.refCurrent,
69175
+ scopes: context.scopes
69176
+ });
69177
+ const tokenKey = serializeReferenceKey({
69178
+ node: comparison.token,
69179
+ scopes: context.scopes
69180
+ });
69181
+ if (!refKey || !tokenKey) return null;
69182
+ const candidates = [];
69183
+ const tokenSymbol = context.scopes.symbolFor(comparison.token);
69184
+ const tokenInitializer = tokenSymbol?.initializer ? stripParenExpression(tokenSymbol.initializer) : null;
69185
+ if (comparison.isOrdered && !isNodeOfType(tokenInitializer, "UpdateExpression")) return null;
69186
+ if (tokenInitializer && isNodeOfType(tokenInitializer, "UpdateExpression") && tokenInitializer.operator === "++" && serializeReferenceKey({
69187
+ node: tokenInitializer.argument,
69188
+ scopes: context.scopes
69189
+ }) === refKey) candidates.push(tokenInitializer);
69190
+ if (tokenInitializer && getReactRefCurrent(tokenInitializer, context) && serializeReferenceKey({
69191
+ node: tokenInitializer,
69192
+ scopes: context.scopes
69193
+ }) === refKey) {
69194
+ const singleFlightClaim = findSingleFlightSnapshotClaim(tokenInitializer, functionNode, truthySets, firstRiskyAwait, resetNode, context);
69195
+ if (singleFlightClaim) candidates.push(singleFlightClaim);
69196
+ }
69197
+ if (tokenInitializer && isNodeOfType(tokenInitializer, "UpdateExpression")) {
69198
+ const generationKey = serializeReferenceKey({
69199
+ node: tokenInitializer.argument,
69200
+ scopes: context.scopes
69201
+ });
69202
+ if (generationKey && generationKey === refKey) {
69203
+ const ownerFunction = getOwningFunction(functionNode);
69204
+ let didFindOtherGenerationWrite = false;
69205
+ walkAst(ownerFunction, (candidate) => {
69206
+ if (didFindOtherGenerationWrite || candidate === tokenInitializer) return;
69207
+ const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
69208
+ if (writeTarget && serializeReferenceKey({
69209
+ node: writeTarget,
69210
+ scopes: context.scopes
69211
+ }) === generationKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindOtherGenerationWrite = true;
69212
+ });
69213
+ if (didFindOtherGenerationWrite) return null;
69214
+ }
69215
+ }
69216
+ walkOwnFunctionScope(functionNode, (candidate) => {
69217
+ if (!isNodeOfType(candidate, "AssignmentExpression") || candidate.operator !== "=") return;
69218
+ if (serializeReferenceKey({
69219
+ node: candidate.left,
69220
+ scopes: context.scopes
69221
+ }) === refKey && serializeReferenceKey({
69222
+ node: candidate.right,
69223
+ scopes: context.scopes
69224
+ }) === tokenKey) candidates.push(candidate);
69225
+ });
69226
+ const claim = candidates.find((candidate) => truthySets.some((truthySet) => claimPrecedesTruthySet(candidate, truthySet, firstRiskyAwait, functionNode, context)));
69227
+ if (!claim) return null;
69228
+ let didFindOtherWrite = false;
69229
+ walkAst(getOwningFunction(functionNode), (candidate) => {
69230
+ if (didFindOtherWrite || candidate === claim) return;
69231
+ const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
69232
+ if (writeTarget && serializeReferenceKey({
69233
+ node: writeTarget,
69234
+ scopes: context.scopes
69235
+ }) === refKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindOtherWrite = true;
69236
+ });
69237
+ return didFindOtherWrite ? null : claim;
69238
+ };
69239
+ const isClaimedOwnershipComparison = (test, expectedMode, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
69240
+ const comparison = getAsyncOwnershipComparison(test, context);
69241
+ return Boolean(comparison && comparison.mode === expectedMode && findOwnershipClaim(comparison, functionNode, truthySets, firstRiskyAwait, resetNode, context));
69242
+ };
69243
+ const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, acceptedAssignments, context) => {
67847
69244
  let didFindOtherWrite = false;
67848
69245
  walkAst(effectCallback, (candidate) => {
67849
69246
  if (didFindOtherWrite) return false;
@@ -67851,7 +69248,7 @@ const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, accepted
67851
69248
  if (serializeReferenceKey({
67852
69249
  node: candidate.left,
67853
69250
  scopes: context.scopes
67854
- }) === guardKey && !acceptedCleanupAssignments.has(candidate)) {
69251
+ }) === guardKey && !acceptedAssignments.has(candidate)) {
67855
69252
  didFindOtherWrite = true;
67856
69253
  return false;
67857
69254
  }
@@ -67867,48 +69264,103 @@ const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, accepted
67867
69264
  });
67868
69265
  return didFindOtherWrite;
67869
69266
  };
67870
- const isResetGuardedByCleanupBackedLifecycle = (resetNode, functionNode, context) => {
69267
+ const collectCleanupBackedLifecycleAssignments = (effectCallback, guardKey, context) => {
69268
+ const acceptedAssignments = /* @__PURE__ */ new Set();
69269
+ for (const cleanupFunction of collectReturnedCleanupFunctions(effectCallback, context.scopes)) walkOwnFunctionScope(cleanupFunction, (cleanupNode) => {
69270
+ const assignedValue = isNodeOfType(cleanupNode, "AssignmentExpression") ? stripParenExpression(cleanupNode.right) : null;
69271
+ if (!isNodeOfType(cleanupNode, "AssignmentExpression") || cleanupNode.operator !== "=" || !isNodeOfType(assignedValue, "Literal") || assignedValue.value !== false || serializeReferenceKey({
69272
+ node: cleanupNode.left,
69273
+ scopes: context.scopes
69274
+ }) !== guardKey || !isUnconditionallyExecutedWithinFunction(cleanupNode, cleanupFunction, context)) return;
69275
+ acceptedAssignments.add(cleanupNode);
69276
+ });
69277
+ if (acceptedAssignments.size === 0) return null;
69278
+ walkOwnFunctionScope(effectCallback, (effectNode) => {
69279
+ const assignedValue = isNodeOfType(effectNode, "AssignmentExpression") ? stripParenExpression(effectNode.right) : null;
69280
+ if (isNodeOfType(effectNode, "AssignmentExpression") && effectNode.operator === "=" && isNodeOfType(assignedValue, "Literal") && assignedValue.value === true && serializeReferenceKey({
69281
+ node: effectNode.left,
69282
+ scopes: context.scopes
69283
+ }) === guardKey && isUnconditionallyExecutedWithinFunction(effectNode, effectCallback, context)) acceptedAssignments.add(effectNode);
69284
+ });
69285
+ return acceptedAssignments;
69286
+ };
69287
+ const isEffectCallback = (node, context) => {
69288
+ const callbackRoot = findTransparentExpressionRoot(node);
69289
+ const callbackCall = callbackRoot.parent;
69290
+ return Boolean(callbackCall && isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments[0] === callbackRoot && isReactApiCall(callbackCall, EFFECT_HOOK_NAMES$6, context.scopes, {
69291
+ allowGlobalReactNamespace: true,
69292
+ allowUnboundBareCalls: true
69293
+ }));
69294
+ };
69295
+ const isCleanupBackedLifecycleGuard = (guardExpression, functionNode, context) => {
69296
+ const guardKey = serializeReferenceKey({
69297
+ node: guardExpression,
69298
+ scopes: context.scopes
69299
+ });
69300
+ if (!guardKey || !isInitiallyActiveLifecycleGuard(guardExpression, context)) return false;
69301
+ let ownerFunction = functionNode.parent;
69302
+ while (ownerFunction && !isFunctionLike$1(ownerFunction)) ownerFunction = ownerFunction.parent ?? null;
69303
+ if (!ownerFunction) return false;
69304
+ const effectCallbacks = [];
69305
+ if (isEffectCallback(ownerFunction, context)) effectCallbacks.push(ownerFunction);
69306
+ walkOwnFunctionScope(ownerFunction, (candidate) => {
69307
+ if (!isNodeOfType(candidate, "CallExpression")) return;
69308
+ if (!isReactApiCall(candidate, EFFECT_HOOK_NAMES$6, context.scopes, {
69309
+ allowGlobalReactNamespace: true,
69310
+ allowUnboundBareCalls: true
69311
+ })) return;
69312
+ const effectCallback = candidate.arguments[0];
69313
+ if (effectCallback && isFunctionLike$1(effectCallback)) effectCallbacks.push(effectCallback);
69314
+ });
69315
+ const acceptedAssignments = /* @__PURE__ */ new Set();
69316
+ for (const effectCallback of effectCallbacks) {
69317
+ const effectAssignments = collectCleanupBackedLifecycleAssignments(effectCallback, guardKey, context);
69318
+ if (!effectAssignments) continue;
69319
+ for (const assignment of effectAssignments) acceptedAssignments.add(assignment);
69320
+ }
69321
+ return Boolean(acceptedAssignments.size > 0 && !hasLifecycleGuardWriteOutsideCleanup(ownerFunction, guardKey, acceptedAssignments, context));
69322
+ };
69323
+ const collectLogicalOperands = (expression, operator) => {
69324
+ const stripped = stripParenExpression(expression);
69325
+ if (isNodeOfType(stripped, "LogicalExpression") && stripped.operator === operator) return [...collectLogicalOperands(stripped.left, operator), ...collectLogicalOperands(stripped.right, operator)];
69326
+ return [stripped];
69327
+ };
69328
+ const collectFinalizerGuardExpressions = (resetNode, protectingTry) => {
69329
+ const positive = [];
69330
+ const negative = [];
67871
69331
  let child = resetNode;
67872
69332
  let cursor = resetNode.parent;
67873
- let guardKey = null;
67874
- let guardExpression = null;
67875
- while (cursor && cursor !== functionNode) {
67876
- if (isNodeOfType(cursor, "IfStatement") && cursor.consequent === child && cursor.alternate === null) {
67877
- guardExpression = cursor.test;
67878
- guardKey = serializeReferenceKey({
67879
- node: cursor.test,
67880
- scopes: context.scopes
67881
- });
67882
- break;
67883
- }
69333
+ while (cursor && cursor !== protectingTry) {
69334
+ if (isNodeOfType(cursor, "IfStatement")) {
69335
+ if (cursor.consequent !== child || cursor.alternate !== null) return null;
69336
+ positive.push(...collectLogicalOperands(cursor.test, "&&"));
69337
+ } else if (isNodeOfType(cursor, "LogicalExpression")) {
69338
+ if (cursor.operator !== "&&" || cursor.right !== child) return null;
69339
+ positive.push(...collectLogicalOperands(cursor.left, "&&"));
69340
+ } else if (isNodeOfType(cursor, "BlockStatement")) {
69341
+ const childIndex = cursor.body.findIndex((statement) => statement === child);
69342
+ if (childIndex !== -1) for (const statement of cursor.body.slice(0, childIndex)) {
69343
+ if (!isNodeOfType(statement, "IfStatement") || statement.alternate !== null || !isUnconditionalReturnBranch(statement.consequent)) continue;
69344
+ negative.push(...collectLogicalOperands(statement.test, "||"));
69345
+ }
69346
+ } 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;
67884
69347
  child = cursor;
67885
69348
  cursor = cursor.parent ?? null;
67886
69349
  }
67887
- if (!guardKey || !guardExpression || !isInitiallyActiveLifecycleGuard(guardExpression, context)) return false;
67888
- cursor = functionNode.parent;
67889
- while (cursor) {
67890
- if (isFunctionLike$1(cursor)) {
67891
- const callbackRoot = findTransparentExpressionRoot(cursor);
67892
- const callbackCall = callbackRoot.parent;
67893
- if (Boolean(callbackCall && isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments[0] === callbackRoot && isReactApiCall(callbackCall, EFFECT_HOOK_NAMES$6, context.scopes, {
67894
- allowGlobalReactNamespace: true,
67895
- allowUnboundBareCalls: true
67896
- }))) {
67897
- const acceptedCleanupAssignments = /* @__PURE__ */ new Set();
67898
- for (const cleanupFunction of collectReturnedCleanupFunctions(cursor, context.scopes)) walkOwnFunctionScope(cleanupFunction, (cleanupNode) => {
67899
- const assignedValue = isNodeOfType(cleanupNode, "AssignmentExpression") ? stripParenExpression(cleanupNode.right) : null;
67900
- if (!isNodeOfType(cleanupNode, "AssignmentExpression") || cleanupNode.operator !== "=" || !isNodeOfType(assignedValue, "Literal") || assignedValue.value !== false || serializeReferenceKey({
67901
- node: cleanupNode.left,
67902
- scopes: context.scopes
67903
- }) !== guardKey || !isUnconditionallyExecutedWithinFunction(cleanupNode, cleanupFunction, context)) return;
67904
- acceptedCleanupAssignments.add(cleanupNode);
67905
- });
67906
- if (acceptedCleanupAssignments.size > 0 && !hasLifecycleGuardWriteOutsideCleanup(cursor, guardKey, acceptedCleanupAssignments, context)) return true;
67907
- }
67908
- }
67909
- cursor = cursor.parent ?? null;
67910
- }
67911
- return false;
69350
+ return cursor === protectingTry && positive.length + negative.length > 0 ? {
69351
+ positive,
69352
+ negative
69353
+ } : null;
69354
+ };
69355
+ const isPositiveFinalizerGuard = (expression, resetNode, functionNode, truthySets, firstRiskyAwait, context) => isCleanupBackedLifecycleGuard(expression, functionNode, context) || isClaimedOwnershipComparison(expression, "owns", functionNode, truthySets, firstRiskyAwait, resetNode, context);
69356
+ const isNegativeFinalizerGuard = (expression, resetNode, functionNode, truthySets, firstRiskyAwait, context) => {
69357
+ const stripped = stripParenExpression(expression);
69358
+ if (isNodeOfType(stripped, "UnaryExpression") && stripped.operator === "!") return isPositiveFinalizerGuard(stripped.argument, resetNode, functionNode, truthySets, firstRiskyAwait, context);
69359
+ return isClaimedOwnershipComparison(stripped, "lost", functionNode, truthySets, firstRiskyAwait, resetNode, context);
69360
+ };
69361
+ const isFinalizerResetProvablyGuarded = (resetNode, protectingTry, functionNode, truthySets, firstRiskyAwait, context) => {
69362
+ const guards = collectFinalizerGuardExpressions(resetNode, protectingTry);
69363
+ 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)));
67912
69364
  };
67913
69365
  const isAwaitInsideProtectedTry = (awaitNode, tryStatement) => {
67914
69366
  let child = awaitNode;
@@ -68014,7 +69466,13 @@ const analyzeFunction = (functionNode, context) => {
68014
69466
  const exceptionallyProtectedAwaits = collectExceptionallyProtectedAwaits(awaitSites, calls);
68015
69467
  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)));
68016
69468
  if (riskyAwaitsWithTruthySet.length === 0) continue;
68017
- 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)));
69469
+ const conditionalExceptionalResets = calls.filter((call) => {
69470
+ if (call.value || call.context === "plain" || call.isUnconditional || call.protectingTry === null) return false;
69471
+ const protectingTry = call.protectingTry;
69472
+ if (!isInsideTryFinalizer(call.node, protectingTry)) return true;
69473
+ const firstRiskyAwait = riskyAwaitsWithTruthySet.find((awaitSite) => isAwaitInsideProtectedTry(awaitSite.node, protectingTry));
69474
+ return !(firstRiskyAwait && isFinalizerResetProvablyGuarded(call.node, protectingTry, functionNode, truthySets, firstRiskyAwait, context));
69475
+ });
68018
69476
  for (const reset of conditionalExceptionalResets) {
68019
69477
  const catchHandler = reset.protectingTry?.handler;
68020
69478
  if (catchHandler && !catchHandlerCanBypassReset(catchHandler, functionNode, setterKey, context, false)) continue;
@@ -73886,6 +75344,29 @@ const doesPredicateTruthRequireMatch = (matchCall, predicateFunction) => {
73886
75344
  }
73887
75345
  return !isNegated && predicateFunction.body === child;
73888
75346
  };
75347
+ const doesPredicateReturnNormalizedMatch = (matchCall, predicateFunction) => {
75348
+ if (!isFunctionLike$1(predicateFunction) || !isNodeOfType(predicateFunction.body, "BlockStatement") || predicateFunction.body.body.length !== 2 || !isNodeOfType(predicateFunction.body.body[0], "VariableDeclaration")) return false;
75349
+ const returnStatement = predicateFunction.body.body[1];
75350
+ if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
75351
+ let negationCount = 0;
75352
+ let expression = matchCall;
75353
+ let parent = expression.parent ?? null;
75354
+ while (parent && parent !== returnStatement) {
75355
+ if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "!") {
75356
+ negationCount += 1;
75357
+ expression = parent;
75358
+ parent = parent.parent ?? null;
75359
+ continue;
75360
+ }
75361
+ if (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) || isNodeOfType(parent, "ChainExpression")) {
75362
+ expression = parent;
75363
+ parent = parent.parent ?? null;
75364
+ continue;
75365
+ }
75366
+ return false;
75367
+ }
75368
+ return parent === returnStatement && returnStatement.argument === expression && negationCount % 2 === 0;
75369
+ };
73889
75370
  const isStringTypeofGuardForPath = (test, expectedPath) => {
73890
75371
  const target = stripParenExpression(test);
73891
75372
  if (!isNodeOfType(target, "BinaryExpression") || target.operator !== "===") return false;
@@ -73926,6 +75407,26 @@ const pathUsesOptionalAccess = (node) => {
73926
75407
  current = current.object;
73927
75408
  }
73928
75409
  };
75410
+ const getNormalizedClassNameRoot = (expression) => {
75411
+ const conditional = stripParenExpression(expression);
75412
+ if (!isNodeOfType(conditional, "ConditionalExpression")) return null;
75413
+ const consequent = stripParenExpression(conditional.consequent);
75414
+ const rootIdentifier = getRootIdentifier(consequent);
75415
+ if (!rootIdentifier || receiverPathKey(consequent) !== `${rootIdentifier.name}.className`) return null;
75416
+ const test = stripParenExpression(conditional.test);
75417
+ if (!isNodeOfType(test, "BinaryExpression") || test.operator !== "===") return null;
75418
+ const testOperands = [test.left, test.right].map((operand) => stripParenExpression(operand));
75419
+ const typeofOperand = testOperands.find((operand) => isNodeOfType(operand, "UnaryExpression"));
75420
+ const stringOperand = testOperands.find((operand) => isNodeOfType(operand, "Literal"));
75421
+ if (!typeofOperand || !isNodeOfType(typeofOperand, "UnaryExpression") || typeofOperand.operator !== "typeof" || receiverPathKey(typeofOperand.argument) !== `${rootIdentifier.name}.className` || !stringOperand || !isNodeOfType(stringOperand, "Literal") || stringOperand.value !== "string") return null;
75422
+ const alternate = stripParenExpression(conditional.alternate);
75423
+ if (!isNodeOfType(alternate, "LogicalExpression") || alternate.operator !== "??") return null;
75424
+ const fallback = stripParenExpression(alternate.right);
75425
+ const attributeCall = stripParenExpression(alternate.left);
75426
+ 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;
75427
+ const attributeName = attributeCall.arguments[0] ? stripParenExpression(attributeCall.arguments[0]) : null;
75428
+ return attributeName && isNodeOfType(attributeName, "Literal") && attributeName.value === "class" ? rootIdentifier : null;
75429
+ };
73929
75430
  const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedPattern, context) => {
73930
75431
  const resultIdentifier = getRootIdentifier(matchReceiver);
73931
75432
  const resultPath = receiverPathKey(matchReceiver);
@@ -73934,11 +75435,17 @@ const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedP
73934
75435
  if (!isDirectFinderMatchReturn(assertion) && !isOptionalResultPath) return false;
73935
75436
  const resultSymbol = context.scopes.symbolFor(resultIdentifier);
73936
75437
  const initializer = resultSymbol?.initializer ? stripParenExpression(resultSymbol.initializer) : null;
73937
- if (resultSymbol?.kind !== "const" || !initializer || !isNodeOfType(initializer, "CallExpression")) return false;
75438
+ if (resultSymbol?.kind !== "const" || !initializer) return false;
75439
+ const finderCall = isNodeOfType(initializer, "CallExpression") ? initializer : isNodeOfType(initializer, "ConditionalExpression") ? (() => {
75440
+ const alternate = stripParenExpression(initializer.alternate);
75441
+ const consequent = stripParenExpression(initializer.consequent);
75442
+ return (isNodeOfType(alternate, "Literal") && alternate.value === null || isNodeOfType(alternate, "Identifier") && alternate.name === "undefined" && context.scopes.isGlobalReference(alternate)) && isNodeOfType(consequent, "CallExpression") ? consequent : null;
75443
+ })() : null;
75444
+ if (!finderCall || !isNodeOfType(finderCall, "CallExpression")) return false;
73938
75445
  if (!isOptionalResultPath && !isImmediatelyGuardedFinderResult(assertion, resultSymbol, resultPath, context)) return false;
73939
- const finderCallee = stripParenExpression(initializer.callee);
75446
+ const finderCallee = stripParenExpression(finderCall.callee);
73940
75447
  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;
73941
- const predicateArgument = initializer.arguments[1];
75448
+ const predicateArgument = finderCall.arguments[1];
73942
75449
  if (!predicateArgument) return false;
73943
75450
  const predicateFunction = resolveExactLocalFunction(predicateArgument, context.scopes);
73944
75451
  if (!predicateFunction || !isFunctionLike$1(predicateFunction)) return false;
@@ -73962,6 +75469,43 @@ const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedP
73962
75469
  });
73963
75470
  return didProveMatch;
73964
75471
  };
75472
+ const isMatchProvenByNormalizedFindUpUntilPredicate = (assertion, matchReceiver, assertedPattern, context) => {
75473
+ const normalizedReceiver = stripParenExpression(matchReceiver);
75474
+ if (!isNodeOfType(normalizedReceiver, "Identifier")) return false;
75475
+ const normalizedReceiverSymbol = context.scopes.symbolFor(normalizedReceiver);
75476
+ const normalizedReceiverInitializer = normalizedReceiverSymbol?.initializer ? stripParenExpression(normalizedReceiverSymbol.initializer) : null;
75477
+ const resultIdentifier = normalizedReceiverInitializer ? getNormalizedClassNameRoot(normalizedReceiverInitializer) : null;
75478
+ if (normalizedReceiverSymbol?.kind !== "const" || normalizedReceiverSymbol.references.some((reference) => reference.flag !== "read") || !resultIdentifier) return false;
75479
+ const resultSymbol = context.scopes.symbolFor(resultIdentifier);
75480
+ const finderCall = resultSymbol?.initializer ? stripParenExpression(resultSymbol.initializer) : null;
75481
+ if (resultSymbol?.kind !== "const" || resultSymbol.references.some((reference) => reference.flag !== "read") || !finderCall || !isNodeOfType(finderCall, "CallExpression")) return false;
75482
+ const finderCallee = stripParenExpression(finderCall.callee);
75483
+ if (!isNodeOfType(finderCallee, "Identifier") || context.scopes.symbolFor(finderCallee)?.kind !== "import" || getImportedNameFromModule(assertion, finderCallee.name, CLOUDSCAPE_DOM_MODULE) !== "findUpUntil") return false;
75484
+ if (!isPresenceProvenBeforeNode(assertion, (test) => {
75485
+ const expression = stripParenExpression(test);
75486
+ return isNodeOfType(expression, "Identifier") && context.scopes.symbolFor(expression)?.id === resultSymbol.id;
75487
+ })) return false;
75488
+ const predicateArgument = finderCall.arguments[1];
75489
+ const predicateFunction = predicateArgument ? resolveExactLocalFunction(predicateArgument, context.scopes) : null;
75490
+ if (!predicateFunction || !isFunctionLike$1(predicateFunction) || predicateFunction.async || predicateFunction.generator) return false;
75491
+ const predicateParameter = predicateFunction.params[0];
75492
+ if (!isNodeOfType(predicateParameter, "Identifier")) return false;
75493
+ let didProveNormalizedMatch = false;
75494
+ walkAst(predicateFunction.body, (child) => {
75495
+ if (didProveNormalizedMatch || isFunctionLike$1(child)) return false;
75496
+ 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;
75497
+ const predicateReceiver = stripParenExpression(child.callee.object);
75498
+ if (!isNodeOfType(predicateReceiver, "Identifier")) return;
75499
+ const predicateReceiverSymbol = context.scopes.symbolFor(predicateReceiver);
75500
+ const predicateReceiverInitializer = predicateReceiverSymbol?.initializer ? stripParenExpression(predicateReceiverSymbol.initializer) : null;
75501
+ const predicateRoot = predicateReceiverInitializer ? getNormalizedClassNameRoot(predicateReceiverInitializer) : null;
75502
+ if (predicateReceiverSymbol?.kind === "const" && predicateReceiverSymbol.references.every((reference) => reference.flag === "read") && predicateRoot?.name === predicateParameter.name) {
75503
+ didProveNormalizedMatch = true;
75504
+ return false;
75505
+ }
75506
+ });
75507
+ return didProveNormalizedMatch;
75508
+ };
73965
75509
  const scopeProvesFindMatch = (assertion, findReceiver, findPredicate, context) => {
73966
75510
  if (!isStablePredicate(findPredicate, context)) return false;
73967
75511
  return isPresenceProvenBeforeNode(assertion, (test) => testPositivelyContainsCall(test, (call) => {
@@ -74062,6 +75606,123 @@ const isEnsureThenFind = (assertion, findReceiver, findPredicate) => {
74062
75606
  }
74063
75607
  return false;
74064
75608
  };
75609
+ const getReceiverRootIdentifier = (node) => {
75610
+ let target = stripParenExpression(node);
75611
+ while (isNodeOfType(target, "MemberExpression")) target = stripParenExpression(target.object);
75612
+ return isNodeOfType(target, "Identifier") ? target : null;
75613
+ };
75614
+ const getReceiverStatePath = (node) => {
75615
+ const target = stripParenExpression(node);
75616
+ if (isNodeOfType(target, "Identifier")) return target.name;
75617
+ if (!isNodeOfType(target, "MemberExpression")) return null;
75618
+ const objectPath = getReceiverStatePath(target.object);
75619
+ if (!objectPath) return null;
75620
+ return `${objectPath}.${getStaticPropertyName(target) ?? "*"}`;
75621
+ };
75622
+ const doesReceiverStateChangeBeforeAssertion = (ownerFunction, receiver, startOffset, assertion, context) => {
75623
+ const receiverRoot = getReceiverRootIdentifier(receiver);
75624
+ const receiverSymbol = receiverRoot ? context.scopes.symbolFor(receiverRoot) : null;
75625
+ const receiverPath = getReceiverStatePath(receiver);
75626
+ if (!receiverRoot || !receiverSymbol || !receiverPath) return true;
75627
+ const receiverAliasPaths = new Map([[receiverSymbol.id, receiverRoot.name]]);
75628
+ let didAddAlias = true;
75629
+ while (didAddAlias) {
75630
+ didAddAlias = false;
75631
+ walkAst(ownerFunction, (child) => {
75632
+ if (child !== ownerFunction && isFunctionLike$1(child)) return false;
75633
+ if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier") || !child.init) return;
75634
+ const initializer = stripParenExpression(child.init);
75635
+ if (!isNodeOfType(initializer, "Identifier") && !isNodeOfType(initializer, "MemberExpression")) return;
75636
+ const initializerRoot = getReceiverRootIdentifier(initializer);
75637
+ const initializerSymbol = initializerRoot ? context.scopes.symbolFor(initializerRoot) : null;
75638
+ const initializerBasePath = initializerSymbol ? receiverAliasPaths.get(initializerSymbol.id) : null;
75639
+ const initializerPath = getReceiverStatePath(initializer);
75640
+ if (!initializerRoot || !initializerBasePath || !initializerPath) return;
75641
+ const aliasSymbol = context.scopes.symbolFor(child.id);
75642
+ if (aliasSymbol && !receiverAliasPaths.has(aliasSymbol.id)) {
75643
+ const initializerSuffix = initializerPath.slice(initializerRoot.name.length);
75644
+ receiverAliasPaths.set(aliasSymbol.id, `${initializerBasePath}${initializerSuffix}`);
75645
+ didAddAlias = true;
75646
+ }
75647
+ });
75648
+ }
75649
+ let didChangeReceiverState = false;
75650
+ walkAst(ownerFunction, (child) => {
75651
+ if (didChangeReceiverState) return false;
75652
+ if (child !== ownerFunction && isFunctionLike$1(child)) return false;
75653
+ if (child.range[0] <= startOffset || child.range[0] >= assertion.range[0]) return;
75654
+ if (isNodeOfType(child, "CallExpression")) {
75655
+ didChangeReceiverState = true;
75656
+ return false;
75657
+ }
75658
+ const mutationTarget = isNodeOfType(child, "AssignmentExpression") || isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete" ? stripParenExpression(isNodeOfType(child, "AssignmentExpression") ? child.left : child.argument) : null;
75659
+ const mutationRoot = mutationTarget ? getReceiverRootIdentifier(mutationTarget) : null;
75660
+ const mutationSymbol = mutationRoot ? context.scopes.symbolFor(mutationRoot) : null;
75661
+ const mutationBasePath = mutationSymbol ? receiverAliasPaths.get(mutationSymbol.id) : null;
75662
+ const mutationPath = mutationTarget ? getReceiverStatePath(mutationTarget) : null;
75663
+ if (mutationRoot && mutationBasePath && mutationPath) {
75664
+ const canonicalMutationPath = `${mutationBasePath}${mutationPath.slice(mutationRoot.name.length)}`;
75665
+ if (canonicalMutationPath !== receiverPath && !canonicalMutationPath.startsWith(`${receiverPath}.`) && !receiverPath.startsWith(`${canonicalMutationPath}.`)) return;
75666
+ didChangeReceiverState = true;
75667
+ return false;
75668
+ }
75669
+ });
75670
+ return didChangeReceiverState;
75671
+ };
75672
+ const isFindProvenByGuardedMaximum = (assertion, findReceiver, findPredicate, context) => {
75673
+ const findLookup = findEqualityLookupParts(findPredicate);
75674
+ const maximumIdentifier = findLookup ? stripParenExpression(findLookup.comparedValue) : null;
75675
+ if (!findLookup || !maximumIdentifier || !isNodeOfType(maximumIdentifier, "Identifier")) return false;
75676
+ const maximumSymbol = context.scopes.symbolFor(maximumIdentifier);
75677
+ const maximumInitializer = maximumSymbol?.initializer ? stripParenExpression(maximumSymbol.initializer) : null;
75678
+ 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;
75679
+ const filterCall = stripParenExpression(maximumInitializer.callee.object);
75680
+ if (!isNodeOfType(filterCall, "CallExpression") || !isNodeOfType(filterCall.callee, "MemberExpression") || getStaticPropertyName(filterCall.callee) !== "filter" || !areNodesLooselyEqual(filterCall.callee.object, findReceiver)) return false;
75681
+ const filterPredicate = filterCall.arguments[0] ? stripParenExpression(filterCall.arguments[0]) : null;
75682
+ if (!filterPredicate || !isStablePredicate(filterPredicate, context)) return false;
75683
+ const reducerArgument = maximumInitializer.arguments[0] ? stripParenExpression(maximumInitializer.arguments[0]) : null;
75684
+ const reducerFunction = reducerArgument ? resolveExactLocalFunction(reducerArgument, context.scopes) : null;
75685
+ const initialValue = maximumInitializer.arguments[1] ? stripParenExpression(maximumInitializer.arguments[1]) : null;
75686
+ if (!reducerFunction || !isFunctionLike$1(reducerFunction) || reducerFunction.async || reducerFunction.generator || !initialValue || !isNodeOfType(initialValue, "Literal") || typeof initialValue.value !== "number") return false;
75687
+ const accumulatorParameter = reducerFunction.params[0];
75688
+ const itemParameter = reducerFunction.params[1];
75689
+ const reducerBody = singleExpressionPredicateBody(reducerFunction);
75690
+ if (!isNodeOfType(accumulatorParameter, "Identifier") || !isNodeOfType(itemParameter, "Identifier") || !reducerBody || !isNodeOfType(reducerBody, "CallExpression") || !isNodeOfType(reducerBody.callee, "MemberExpression") || getStaticPropertyName(reducerBody.callee) !== "max") return false;
75691
+ const mathReceiver = stripParenExpression(reducerBody.callee.object);
75692
+ if (!isNodeOfType(mathReceiver, "Identifier") || mathReceiver.name !== "Math" || !context.scopes.isGlobalReference(mathReceiver) || reducerBody.arguments.length !== 2) return false;
75693
+ const accumulatorArgument = reducerBody.arguments.find((argument) => {
75694
+ const expression = stripParenExpression(argument);
75695
+ return isNodeOfType(expression, "Identifier") && expression.name === accumulatorParameter.name;
75696
+ });
75697
+ const itemMemberArgument = reducerBody.arguments.find((argument) => {
75698
+ const expression = stripParenExpression(argument);
75699
+ const rootIdentifier = getRootIdentifier(expression);
75700
+ return isNodeOfType(expression, "MemberExpression") && rootIdentifier?.name === itemParameter.name && receiverPathKey(expression)?.slice(itemParameter.name.length + 1) === findLookup.propertyName;
75701
+ });
75702
+ if (!accumulatorArgument || !itemMemberArgument) return false;
75703
+ if (!receiverPathKey(findReceiver)) return false;
75704
+ let ownerFunction = assertion.parent ?? null;
75705
+ while (ownerFunction && !isFunctionLike$1(ownerFunction)) ownerFunction = ownerFunction.parent ?? null;
75706
+ if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
75707
+ const maximumEnd = maximumInitializer.range[1];
75708
+ if (doesReceiverStateChangeBeforeAssertion(ownerFunction.body, findReceiver, maximumEnd, assertion, context)) return false;
75709
+ return isPresenceProvenBeforeNode(assertion, (test) => {
75710
+ const comparison = stripParenExpression(test);
75711
+ if (!isNodeOfType(comparison, "BinaryExpression")) return false;
75712
+ return [[
75713
+ comparison.left,
75714
+ comparison.right,
75715
+ comparison.operator
75716
+ ], [
75717
+ comparison.right,
75718
+ comparison.left,
75719
+ comparison.operator === "<" ? ">" : comparison.operator === ">" ? "<" : comparison.operator
75720
+ ]].some(([candidateMaximum, candidateInitial, operator]) => {
75721
+ const candidateMaximumIdentifier = stripParenExpression(candidateMaximum);
75722
+ return operator === ">" && isNodeOfType(candidateMaximumIdentifier, "Identifier") && context.scopes.symbolFor(candidateMaximumIdentifier)?.id === maximumSymbol.id && areNodesLooselyEqual(stripParenExpression(candidateInitial), initialValue);
75723
+ });
75724
+ });
75725
+ };
74065
75726
  const isDefinitelyNonNullishMapValue = (value) => {
74066
75727
  if (!value) return false;
74067
75728
  const expression = stripParenExpression(value);
@@ -74085,15 +75746,15 @@ const unwrapFalseBooleanGuard = (test) => {
74085
75746
  };
74086
75747
  const isEnsureThenMapGet = (assertion, receiver, lookupKey, context) => {
74087
75748
  const stableLookupKey = stripParenExpression(lookupKey);
74088
- if (!isNodeOfType(stableLookupKey, "Identifier") && !isNodeOfType(stableLookupKey, "Literal")) return false;
75749
+ const lookupKeyRoot = isNodeOfType(stableLookupKey, "MemberExpression") ? getRootIdentifier(stableLookupKey) : null;
75750
+ const lookupKeySymbol = isNodeOfType(stableLookupKey, "Identifier") ? context.scopes.symbolFor(stableLookupKey) : lookupKeyRoot ? context.scopes.symbolFor(lookupKeyRoot) : null;
75751
+ if (!isNodeOfType(stableLookupKey, "Identifier") && !isNodeOfType(stableLookupKey, "Literal") && (!isNodeOfType(stableLookupKey, "MemberExpression") || !lookupKeyRoot || lookupKeySymbol?.kind !== "const")) return false;
74089
75752
  const receiverSymbol = context.scopes.symbolFor(receiver);
74090
75753
  if (!receiverSymbol) return false;
74091
75754
  const receiverMatches = (candidate) => {
74092
75755
  const target = stripParenExpression(candidate);
74093
75756
  return isNodeOfType(target, "Identifier") && context.scopes.symbolFor(target)?.id === receiverSymbol.id;
74094
75757
  };
74095
- const lookupKeyExpression = stripParenExpression(lookupKey);
74096
- const lookupKeySymbol = isNodeOfType(lookupKeyExpression, "Identifier") ? context.scopes.symbolFor(lookupKeyExpression) : null;
74097
75758
  let child = assertion;
74098
75759
  let ancestor = assertion.parent ?? null;
74099
75760
  while (ancestor && !isFunctionLike$1(ancestor)) {
@@ -74113,7 +75774,7 @@ const isEnsureThenMapGet = (assertion, receiver, lookupKey, context) => {
74113
75774
  const populationCall = populationCalls[0];
74114
75775
  if (!populationCall) continue;
74115
75776
  const populationCallStart = populationCall.range[0];
74116
- if (Boolean(lookupKeySymbol?.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] > populationCallStart && reference.identifier.range[0] < assertion.range[0]))) continue;
75777
+ 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;
74117
75778
  if (receiverSymbol.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] > populationCallStart && reference.identifier.range[0] < assertion.range[0])) continue;
74118
75779
  if (!indexedRelevantCalls(ancestor).some((laterCall) => {
74119
75780
  if (laterCall.range[0] <= populationCallStart || laterCall.range[0] >= assertion.range[0] || !isNodeOfType(laterCall.callee, "MemberExpression") || !receiverMatches(laterCall.callee.object)) return false;
@@ -74223,6 +75884,7 @@ const noNonNullAssertionOnMaybeUndefinedResult = defineRule({
74223
75884
  const findReceiver = callee.object;
74224
75885
  if (predicate && isExhaustiveLiteralTupleMapping(findReceiver, predicate, context)) return;
74225
75886
  if (predicate && scopeProvesFindMatch(node, findReceiver, predicate, context)) return;
75887
+ if (predicate && isFindProvenByGuardedMaximum(node, findReceiver, predicate, context)) return;
74226
75888
  if (predicate && isEnsureThenFind(node, findReceiver, predicate)) return;
74227
75889
  }
74228
75890
  if (methodName === "match") {
@@ -74232,6 +75894,7 @@ const noNonNullAssertionOnMaybeUndefinedResult = defineRule({
74232
75894
  const regexKey = pattern ? regexComparableKey(pattern, context) : null;
74233
75895
  if (pattern && isGuardedAnchoredCharacterMatch(node, matchReceiver, pattern)) return;
74234
75896
  if (pattern && regexKey && isMatchProvenByFindUpUntilPredicate(node, matchReceiver, pattern, context)) return;
75897
+ if (pattern && regexKey && isMatchProvenByNormalizedFindUpUntilPredicate(node, matchReceiver, pattern, context)) return;
74235
75898
  if (regexKey && scopeProvesMatchTested(node, regexKey, matchReceiver, context)) return;
74236
75899
  }
74237
75900
  if (methodName === "get") {
@@ -108573,9 +110236,6 @@ const rerenderFunctionalSetstate = defineRule({
108573
110236
  } })
108574
110237
  });
108575
110238
  //#endregion
108576
- //#region src/plugin/utils/is-trivial-built-in-construction.ts
108577
- const isTrivialBuiltInConstruction = (expression) => isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && TRIVIAL_CONSTRUCTOR_NAMES.has(expression.callee.name) && (expression.arguments ?? []).length === 0;
108578
- //#endregion
108579
110239
  //#region src/plugin/rules/state-and-effects/rerender-lazy-ref-init.ts
108580
110240
  const rerenderLazyRefInit = defineRule({
108581
110241
  id: "rerender-lazy-ref-init",
@@ -108594,7 +110254,6 @@ const rerenderLazyRefInit = defineRule({
108594
110254
  const memberPropertyName = isNodeOfType(callee, "MemberExpression") && (isNodeOfType(callee.property, "Identifier") || isNodeOfType(callee.property, "PrivateIdentifier")) ? callee.property.name : null;
108595
110255
  const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : memberPropertyName ?? "fn";
108596
110256
  if (TRIVIAL_INITIALIZER_NAMES.has(calleeName)) return;
108597
- if (isTrivialBuiltInConstruction(initializer)) return;
108598
110257
  if (isPlainCall && isReactHookName(calleeName)) return;
108599
110258
  const callShape = isNewCall ? `new ${calleeName}()` : `${calleeName}()`;
108600
110259
  context.report({