oxlint-plugin-react-doctor 0.9.2-dev.b31fd85 → 0.9.2-dev.c126684

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js 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",
@@ -8675,7 +8802,7 @@ const resolveStableOptionsObject = (expression, observedPropertyNames, scopes, r
8675
8802
  //#endregion
8676
8803
  //#region src/plugin/rules/state-and-effects/class-component-missing-component-will-unmount-teardown.ts
8677
8804
  const MESSAGE$89 = "This class registers a listener or timer during mount without a matching teardown on every unmount path, so it can keep firing after the component unmounts; release it in `componentWillUnmount`.";
8678
- const GLOBAL_OBJECT_NAMES$3 = new Set([
8805
+ const GLOBAL_OBJECT_NAMES$4 = new Set([
8679
8806
  "window",
8680
8807
  "globalThis",
8681
8808
  "global",
@@ -8834,14 +8961,14 @@ const getTimerIdentifierAliasName = (identifier, scopes, visitedSymbolIds = /* @
8834
8961
  const property = symbol.bindingIdentifier.parent;
8835
8962
  const objectPattern = property?.parent;
8836
8963
  const source = declaration.init ? stripParenExpression(declaration.init) : null;
8837
- if (isNodeOfType(property, "Property") && isNodeOfType(objectPattern, "ObjectPattern") && isNodeOfType(source, "Identifier") && GLOBAL_OBJECT_NAMES$3.has(source.name) && scopes.isGlobalReference(source)) return getStaticPropertyKeyName(property, { allowComputedString: true });
8964
+ if (isNodeOfType(property, "Property") && isNodeOfType(objectPattern, "ObjectPattern") && isNodeOfType(source, "Identifier") && GLOBAL_OBJECT_NAMES$4.has(source.name) && scopes.isGlobalReference(source)) return getStaticPropertyKeyName(property, { allowComputedString: true });
8838
8965
  return null;
8839
8966
  }
8840
8967
  const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
8841
8968
  if (isNodeOfType(initializer, "Identifier")) return getTimerIdentifierAliasName(initializer, scopes, visitedSymbolIds);
8842
8969
  if (!isNodeOfType(initializer, "MemberExpression")) return null;
8843
8970
  const receiver = stripParenExpression(initializer.object);
8844
- return isNodeOfType(receiver, "Identifier") && GLOBAL_OBJECT_NAMES$3.has(receiver.name) && scopes.isGlobalReference(receiver) ? getStaticPropertyName(initializer) : null;
8971
+ return isNodeOfType(receiver, "Identifier") && GLOBAL_OBJECT_NAMES$4.has(receiver.name) && scopes.isGlobalReference(receiver) ? getStaticPropertyName(initializer) : null;
8845
8972
  };
8846
8973
  const getTimerCalleeName = (node, scopes) => {
8847
8974
  if (!isNodeOfType(node, "CallExpression")) return null;
@@ -8849,7 +8976,7 @@ const getTimerCalleeName = (node, scopes) => {
8849
8976
  if (getBareCalleeName(node) && isNodeOfType(callee, "Identifier")) return getTimerIdentifierAliasName(callee, scopes);
8850
8977
  if (!isNodeOfType(callee, "MemberExpression")) return null;
8851
8978
  const receiver = stripParenExpression(callee.object);
8852
- if (!isNodeOfType(receiver, "Identifier") || !GLOBAL_OBJECT_NAMES$3.has(receiver.name) || findVariableInitializer(receiver, receiver.name)) return null;
8979
+ if (!isNodeOfType(receiver, "Identifier") || !GLOBAL_OBJECT_NAMES$4.has(receiver.name) || findVariableInitializer(receiver, receiver.name)) return null;
8853
8980
  return getStaticPropertyName(callee);
8854
8981
  };
8855
8982
  const getClassMemberName$1 = (member) => {
@@ -10045,6 +10172,12 @@ const FOCUS_FORWARDING_METHOD_NAMES = new Set([
10045
10172
  "preventDefault",
10046
10173
  "stopImmediatePropagation"
10047
10174
  ]);
10175
+ const DOM_QUERY_METHOD_NAMES = new Set(["getElementById", "querySelector"]);
10176
+ const GLOBAL_OBJECT_NAMES$3 = new Set([
10177
+ "global",
10178
+ "globalThis",
10179
+ "window"
10180
+ ]);
10048
10181
  const isFocusForwardingCall = (node) => {
10049
10182
  if (!node) return false;
10050
10183
  const inner = isNodeOfType(node, "ChainExpression") ? node.expression : node;
@@ -10054,6 +10187,12 @@ const isFocusForwardingCall = (node) => {
10054
10187
  if (!isNodeOfType(callee.property, "Identifier")) return false;
10055
10188
  return FOCUS_FORWARDING_METHOD_NAMES.has(callee.property.name);
10056
10189
  };
10190
+ const isGlobalDocumentExpression = (expression, scopes) => {
10191
+ const candidate = stripParenExpression(expression);
10192
+ if (isNodeOfType(candidate, "Identifier")) return candidate.name === "document" && scopes.isGlobalReference(candidate);
10193
+ if (!isNodeOfType(candidate, "MemberExpression") || !isNodeOfType(candidate.object, "Identifier") || !isNodeOfType(candidate.property, "Identifier") || candidate.property.name !== "document") return false;
10194
+ return GLOBAL_OBJECT_NAMES$3.has(candidate.object.name) && scopes.isGlobalReference(candidate.object);
10195
+ };
10057
10196
  const isFocusForwardingFunctionBody = (body) => {
10058
10197
  if (!body) return false;
10059
10198
  if (isFocusForwardingCall(body)) return true;
@@ -10073,19 +10212,70 @@ const resolveHandlerFunction$2 = (attribute) => {
10073
10212
  return resolveHandlerFunctionExpression(attribute.value.expression);
10074
10213
  };
10075
10214
  const resolveHandlerFunctionExpression = (handlerExpression) => {
10076
- let expression = handlerExpression;
10215
+ let expression = stripParenExpression(handlerExpression);
10077
10216
  if (isNodeOfType(expression, "Identifier")) {
10078
10217
  const binding = findVariableInitializer(expression, expression.name);
10079
10218
  if (!binding?.initializer) return null;
10080
- expression = binding.initializer;
10219
+ expression = stripParenExpression(binding.initializer);
10081
10220
  }
10082
10221
  if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "FunctionDeclaration")) return expression;
10083
10222
  return null;
10084
10223
  };
10085
- const isFocusForwardingHandler = (attribute) => {
10224
+ const isEmptyReturn = (statement) => isNodeOfType(statement, "ReturnStatement") && statement.argument === null;
10225
+ const isClosestEarlyReturn = (statement) => {
10226
+ if (!isNodeOfType(statement, "IfStatement") || statement.alternate) return false;
10227
+ const consequent = statement.consequent;
10228
+ if (!(isNodeOfType(consequent, "BlockStatement") ? consequent.body.length === 1 && isEmptyReturn(consequent.body[0]) : isEmptyReturn(consequent))) return false;
10229
+ const test = stripParenExpression(statement.test);
10230
+ const call = isNodeOfType(test, "ChainExpression") ? test.expression : test;
10231
+ if (!isNodeOfType(call, "CallExpression") || call.arguments.length !== 1) return false;
10232
+ const callee = stripParenExpression(call.callee);
10233
+ const receiver = isNodeOfType(callee, "MemberExpression") ? stripParenExpression(callee.object) : null;
10234
+ return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "closest" && isNodeOfType(receiver, "MemberExpression") && isNodeOfType(receiver.object, "Identifier") && isNodeOfType(receiver.property, "Identifier") && receiver.property.name === "target" && isNodeOfType(call.arguments[0], "Literal") && typeof call.arguments[0].value === "string";
10235
+ };
10236
+ const isStaticSelectorExpression = (expression) => {
10237
+ const candidate = stripParenExpression(expression);
10238
+ if (isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "Literal")) return true;
10239
+ return isNodeOfType(candidate, "TemplateLiteral") && candidate.expressions.every((innerExpression) => isStaticSelectorExpression(innerExpression));
10240
+ };
10241
+ const getDomQueryVariableName = (statement, scopes) => {
10242
+ if (!isNodeOfType(statement, "VariableDeclaration") || statement.kind !== "const" || statement.declarations.length !== 1) return null;
10243
+ const declaration = statement.declarations[0];
10244
+ if (!declaration || !isNodeOfType(declaration.id, "Identifier") || !declaration.init) return null;
10245
+ const initializer = stripParenExpression(declaration.init);
10246
+ if (!isNodeOfType(initializer, "CallExpression") || initializer.arguments.length !== 1 || isNodeOfType(initializer.arguments[0], "SpreadElement") || !isStaticSelectorExpression(initializer.arguments[0])) return null;
10247
+ const callee = stripParenExpression(initializer.callee);
10248
+ if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || !DOM_QUERY_METHOD_NAMES.has(callee.property.name) || !isGlobalDocumentExpression(callee.object, scopes)) return null;
10249
+ return declaration.id.name;
10250
+ };
10251
+ const isFocusCallOnVariable = (statement, variableName) => {
10252
+ if (!isNodeOfType(statement, "ExpressionStatement")) return false;
10253
+ const expression = stripParenExpression(statement.expression);
10254
+ if (!isNodeOfType(expression, "CallExpression")) return false;
10255
+ const callee = stripParenExpression(expression.callee);
10256
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
10257
+ const receiver = stripParenExpression(callee.object);
10258
+ return isNodeOfType(receiver, "Identifier") && receiver.name === variableName && isNodeOfType(callee.property, "Identifier") && callee.property.name === "focus" && expression.arguments.length === 0;
10259
+ };
10260
+ const isConditionalFocusForwardingHandler = (attribute, scopes) => {
10261
+ if (!attribute.value || !isNodeOfType(attribute.value, "JSXExpressionContainer")) return false;
10262
+ const expression = stripParenExpression(attribute.value.expression);
10263
+ if (!isNodeOfType(expression, "ConditionalExpression")) return false;
10264
+ const consequent = stripParenExpression(expression.consequent);
10265
+ const alternate = stripParenExpression(expression.alternate);
10266
+ const nullishBranch = isNullishExpression$2(consequent) ? consequent : alternate;
10267
+ if (!isNullishExpression$2(nullishBranch) || isNodeOfType(nullishBranch, "Identifier") && !scopes.isGlobalReference(nullishBranch)) return false;
10268
+ const handlerFunction = resolveHandlerFunctionExpression(nullishBranch === consequent ? alternate : consequent);
10269
+ if (!handlerFunction || !isNodeOfType(handlerFunction.body, "BlockStatement")) return false;
10270
+ const [guard, query, focus, ...rest] = handlerFunction.body.body;
10271
+ if (!guard || !query || !focus || rest.length > 0 || !isClosestEarlyReturn(guard)) return false;
10272
+ const queryVariableName = getDomQueryVariableName(query, scopes);
10273
+ return Boolean(queryVariableName && isFocusCallOnVariable(focus, queryVariableName));
10274
+ };
10275
+ const isFocusForwardingHandler = (attribute, scopes) => {
10276
+ if (isConditionalFocusForwardingHandler(attribute, scopes)) return true;
10086
10277
  const handlerFunction = resolveHandlerFunction$2(attribute);
10087
- if (!handlerFunction) return false;
10088
- return isFocusForwardingFunctionBody(handlerFunction.body ?? null);
10278
+ return Boolean(handlerFunction && isFocusForwardingFunctionBody(handlerFunction.body ?? null));
10089
10279
  };
10090
10280
  const COMPOSITE_ITEM_ROLES$1 = new Set([
10091
10281
  "option",
@@ -10155,7 +10345,7 @@ const clickEventsHaveKeyEvents = defineRule({
10155
10345
  const spreadOnClickExpression = CLICK_HANDLERS.map((name) => spreadEventValues.get(name.toLowerCase())).find((expression) => expression !== void 0);
10156
10346
  if (!onClick && !spreadOnClickExpression) return;
10157
10347
  if (onClick && isPureEventBlockerHandler(onClick)) return;
10158
- if (onClick && isFocusForwardingHandler(onClick)) return;
10348
+ if (onClick && isFocusForwardingHandler(onClick, context.scopes)) return;
10159
10349
  const spreadHandlerFunction = spreadOnClickExpression ? resolveHandlerFunctionExpression(spreadOnClickExpression) : null;
10160
10350
  if (spreadHandlerFunction && (isFocusForwardingFunctionBody(spreadHandlerFunction.body ?? null) || containsBackdropDismissComparison(spreadHandlerFunction.body ?? null))) return;
10161
10351
  if (hasCompositeItemRole(node)) return;
@@ -17809,6 +17999,14 @@ const findRenderPhaseComponentOrHook = (node, scopes) => {
17809
17999
  //#region src/plugin/utils/is-event-handler-attribute.ts
17810
18000
  const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && /^on[A-Z]/.test(node.name.name);
17811
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
17812
18010
  //#region src/plugin/utils/is-ast-descendant.ts
17813
18011
  /**
17814
18012
  * True when `inner` is `outer` itself or any descendant in the AST
@@ -18027,13 +18225,15 @@ const isSynchronousIteratorCall = (callNode, callbackArgument, scopes) => {
18027
18225
  }
18028
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)));
18029
18227
  };
18030
- const isSynchronousIteratorCallback = (functionNode) => {
18031
- const callNode = functionNode.parent;
18032
- if (!isNodeOfType(callNode, "CallExpression")) return false;
18228
+ const isSynchronousIteratorCallbackCall = (callNode, callbackArgument) => {
18033
18229
  const callee = stripParenExpression(callNode.callee);
18034
18230
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
18035
- if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments[1] === functionNode;
18036
- 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));
18037
18237
  };
18038
18238
  //#endregion
18039
18239
  //#region src/plugin/utils/is-within-assignment-target.ts
@@ -18153,11 +18353,35 @@ const resolveEventListenerCaptureValueIdentityKey = (expression, context) => {
18153
18353
  const rightIdentityKey = resolveEventListenerCaptureValueIdentityKey(unwrappedExpression.right, context);
18154
18354
  return leftIdentityKey && rightIdentityKey ? `${unwrappedExpression.type}:${unwrappedExpression.operator}:${leftIdentityKey}:${rightIdentityKey}` : null;
18155
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
+ };
18156
18378
  const resolveEventListenerCaptureIdentityKey = (optionsNode, context, allowOpaqueOptionsIdentity) => {
18157
- 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 });
18158
18382
  if (capture !== null) return `capture:${String(capture)}`;
18159
- if (!optionsNode) return null;
18160
- const unwrappedOptions = stripParenExpression(optionsNode);
18383
+ if (!stableOptionsNode) return null;
18384
+ const unwrappedOptions = stripParenExpression(stableOptionsNode);
18161
18385
  if (!isNodeOfType(unwrappedOptions, "ObjectExpression")) {
18162
18386
  const optionsKey = allowOpaqueOptionsIdentity ? resolveEventListenerCaptureValueIdentityKey(unwrappedOptions, context) : null;
18163
18387
  return optionsKey ? `options:${optionsKey}` : null;
@@ -18204,12 +18428,8 @@ const doEventListenerCapturesMatch = (registrationOptions, releaseOptions, conte
18204
18428
  return registrationCaptureKey !== null && registrationCaptureKey === resolveEventListenerCaptureIdentityKey(releaseOptions, context, allowOpaqueOptionsIdentity);
18205
18429
  };
18206
18430
  const findAssignedResourceKey = (resourceNode, context) => {
18207
- let currentNode = resourceNode;
18208
- let parentNode = currentNode.parent;
18209
- while (isNodeOfType(parentNode, "ChainExpression")) {
18210
- currentNode = parentNode;
18211
- parentNode = currentNode.parent;
18212
- }
18431
+ const currentNode = findTransparentExpressionRoot(resourceNode);
18432
+ const parentNode = currentNode.parent;
18213
18433
  if (isNodeOfType(parentNode, "VariableDeclarator") && parentNode.init === currentNode) return resolveExpressionKey(parentNode.id, context);
18214
18434
  if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.right === currentNode) return resolveExpressionKey(parentNode.left, context);
18215
18435
  return null;
@@ -18480,6 +18700,18 @@ const resolveIteratorCollectionKey = (expression, context) => {
18480
18700
  }
18481
18701
  return null;
18482
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
+ };
18483
18715
  const isStableLoopReceiver = (expression, context) => {
18484
18716
  if (!expression) return false;
18485
18717
  const unwrappedExpression = stripParenExpression(expression);
@@ -19250,6 +19482,28 @@ const isFunctionReturnedFromReactHook = (functionNode, context, requireRefProper
19250
19482
  });
19251
19483
  };
19252
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
+ };
19253
19507
  const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
19254
19508
  if (!isNodeOfType(usage.node, "CallExpression")) return false;
19255
19509
  const usageFunction = findEnclosingFunction$1(usage.node);
@@ -19270,7 +19524,7 @@ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
19270
19524
  if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
19271
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);
19272
19526
  });
19273
- const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
19527
+ const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? findCallbackRefReplacementReleaseGuard(releaseCall, usageFunction, releaseReceiverKey, registrationReceiverKey, context) ?? releaseCall;
19274
19528
  const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
19275
19529
  return doNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
19276
19530
  };
@@ -19420,14 +19674,21 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
19420
19674
  if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
19421
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;
19422
19676
  if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
19423
- 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;
19424
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
+ }
19425
19683
  if (usage.registrationVerbName === "addEventListener" && releaseVerbName === "removeEventListener" && isNodeOfType(usage.node, "CallExpression")) {
19426
19684
  if (!isNodeOfType(stripParenExpression(usage.node.callee), "MemberExpression")) return false;
19427
19685
  if (!doEventListenerCapturesMatch(usage.node.arguments?.[2], callNode.arguments?.[2], context, true)) return false;
19428
19686
  }
19429
19687
  if (isNodeOfType(usage.node, "CallExpression") && !hasSafeForEachProjectionCleanup(usage.node, callNode, context)) return false;
19430
- 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;
19431
19692
  if (usage.registrationVerbName === "subscribe" && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub") && usage.handleKey !== null && resolveExpressionKey(callNode.arguments?.[0], context) === usage.handleKey) return true;
19432
19693
  const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
19433
19694
  if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
@@ -19464,7 +19725,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
19464
19725
  const usesUnaryListenerSignatureForCalls = isNodeOfType(usage.node, "CallExpression") && usesUnaryListenerSignature(usage.node, callNode);
19465
19726
  const releaseHandler = usesUnaryListenerSignatureForCalls ? callNode.arguments?.[0] : callNode.arguments?.[1];
19466
19727
  if (!releaseHandler) return releaseVerbName === "off";
19467
- const expectedHandlerKey = usesUnaryListenerSignatureForCalls ? usage.eventKey : usage.handlerKey;
19728
+ const expectedHandlerKey = usesUnaryListenerSignatureForCalls ? usage.handlerKey ?? usage.eventKey : usage.handlerKey;
19468
19729
  const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignatureForCalls ? 0 : 1] : null;
19469
19730
  return expectedHandlerKey !== null && resolveResourceIdentityKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
19470
19731
  }
@@ -19938,6 +20199,7 @@ const findRetainedFunctionLeak = (retainedFunction, context, options) => {
19938
20199
  walkAst(body, (child) => {
19939
20200
  if (leak !== null) return false;
19940
20201
  if (isFunctionLike$1(child)) return false;
20202
+ if (!isNodeReachableWithinFunction(child, context)) return false;
19941
20203
  if (isSocketConstruction(child) && !doesResourceResultEscape(child, allowReturnedSocketEscape, false, context)) {
19942
20204
  const socketUsage = {
19943
20205
  kind: "socket",
@@ -20190,6 +20452,164 @@ const isInlineRetainedHandlerFunction = (functionNode, context) => {
20190
20452
  const objectParent = objectExpression.parent;
20191
20453
  return (isNodeOfType(objectParent, "CallExpression") && objectParent.arguments.some((argument) => argument === objectExpression) || isNodeOfType(objectParent, "JSXExpressionContainer")) && findRenderPhaseComponentOrHook(parentNode, context.scopes) !== null;
20192
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
+ };
20193
20613
  const effectNeedsCleanup = defineRule({
20194
20614
  id: "effect-needs-cleanup",
20195
20615
  title: "Effect subscription or timer never cleaned up",
@@ -20200,13 +20620,19 @@ const effectNeedsCleanup = defineRule({
20200
20620
  const reportRetainedLeak = (retainedFunction) => {
20201
20621
  const refEffectUsage = getReactRefEffectUsage(retainedFunction, context);
20202
20622
  if (!refEffectUsage && !isPotentiallyReachableFunction(retainedFunction, context)) return;
20623
+ const effectInvocations = getEffectRetainedInvocations(retainedFunction, context);
20624
+ const isEffectInvoked = effectInvocations.length > 0;
20203
20625
  const leak = findRetainedFunctionLeak(retainedFunction, context, refEffectUsage ? {
20204
20626
  allowReturnedResourceEscape: refEffectUsage.doesEffectOwnEveryResult,
20205
20627
  allowReturnedTimerEscape: false,
20206
20628
  includeOneShotTimers: true,
20207
20629
  requireCallableReturnedResource: true
20630
+ } : isEffectInvoked ? {
20631
+ allowReturnedTimerEscape: false,
20632
+ includeOneShotTimers: true
20208
20633
  } : void 0);
20209
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;
20210
20636
  const resourceNoun = RESOURCE_NOUN_BY_KIND[leak.kind];
20211
20637
  context.report({
20212
20638
  node: leak.node,
@@ -24626,14 +25052,14 @@ const getFirstLegendChild = (children, targetNode) => {
24626
25052
  if (isNodeOfType(child, "JSXExpressionContainer")) {
24627
25053
  const potentialLegends = [];
24628
25054
  collectPotentialLegends(child.expression, potentialLegends);
24629
- const containingLegend = potentialLegends.find((legend) => isDescendantOf(targetNode, legend));
25055
+ const containingLegend = potentialLegends.find((legend) => isDescendantOf$1(targetNode, legend));
24630
25056
  if (containingLegend) return containingLegend;
24631
25057
  if (potentialLegends[0]) return potentialLegends[0];
24632
25058
  }
24633
25059
  }
24634
25060
  return null;
24635
25061
  };
24636
- const isDescendantOf = (node, ancestor) => {
25062
+ const isDescendantOf$1 = (node, ancestor) => {
24637
25063
  let current = node.parent;
24638
25064
  while (current) {
24639
25065
  if (current === ancestor) return true;
@@ -24658,7 +25084,7 @@ const isDisabledByFieldsetAncestor = (node, context) => {
24658
25084
  while (ancestor) {
24659
25085
  if (isNodeOfType(ancestor, "JSXElement") && resolveJsxElementType(ancestor.openingElement) === "fieldset" && openingElementMayBeDisabled(ancestor.openingElement, context)) {
24660
25086
  const firstLegend = getFirstLegendChild(ancestor.children, node);
24661
- if (!firstLegend || !isDescendantOf(node, firstLegend)) return true;
25087
+ if (!firstLegend || !isDescendantOf$1(node, firstLegend)) return true;
24662
25088
  }
24663
25089
  ancestor = ancestor.parent;
24664
25090
  }
@@ -29035,6 +29461,11 @@ const jsCacheStorage = defineRule({
29035
29461
  });
29036
29462
  //#endregion
29037
29463
  //#region src/plugin/rules/js-performance/js-combine-iterations.ts
29464
+ const SMALL_ARRAY_NON_MUTATING_METHODS = new Set([
29465
+ ...CHAINABLE_ITERATION_METHODS,
29466
+ "find",
29467
+ "some"
29468
+ ]);
29038
29469
  const isIteratorProducingCall = (callExpression, generatorNamesInFile) => {
29039
29470
  const callee = callExpression.callee;
29040
29471
  if (isNodeOfType(callee, "MemberExpression")) {
@@ -29146,21 +29577,34 @@ const isStringSplitRootedChain = (receiverNode) => {
29146
29577
  return false;
29147
29578
  };
29148
29579
  const isSmallLiteralArray = (node) => {
29149
- if (!isNodeOfType(node, "ArrayExpression")) return false;
29150
- const elements = node.elements ?? [];
29151
- if (elements.length === 0 || elements.length > 8) return false;
29580
+ const arrayNode = stripParenExpression(node);
29581
+ if (!isNodeOfType(arrayNode, "ArrayExpression")) return false;
29582
+ const elements = arrayNode.elements ?? [];
29583
+ if (elements.length === 0 || elements.length > 9) return false;
29152
29584
  for (const element of elements) {
29153
29585
  if (!element) continue;
29154
29586
  if (isNodeOfType(element, "SpreadElement")) return false;
29155
29587
  }
29156
29588
  return true;
29157
29589
  };
29158
- const isSmallLiteralArrayRootedChain = (receiverNode, smallConstArrayNames) => {
29590
+ const isNonMutatingSmallArrayMethodReference = (identifier) => {
29591
+ const identifierRoot = findTransparentExpressionRoot(identifier);
29592
+ const memberExpression = identifierRoot.parent;
29593
+ if (!isNodeOfType(memberExpression, "MemberExpression") || memberExpression.object !== identifierRoot || !isNodeOfType(memberExpression.property, "Identifier") || !SMALL_ARRAY_NON_MUTATING_METHODS.has(memberExpression.property.name)) return false;
29594
+ const callExpression = memberExpression.parent;
29595
+ return isNodeOfType(callExpression, "CallExpression") && callExpression.callee === memberExpression;
29596
+ };
29597
+ const isSmallLiteralArrayRootedChain = (receiverNode, scopes) => {
29159
29598
  let cursor = receiverNode;
29160
29599
  while (cursor) {
29161
29600
  cursor = stripParenExpression(cursor);
29162
29601
  if (isNodeOfType(cursor, "ArrayExpression")) return isSmallLiteralArray(cursor);
29163
- if (isNodeOfType(cursor, "Identifier")) return smallConstArrayNames.has(cursor.name);
29602
+ if (isNodeOfType(cursor, "Identifier")) {
29603
+ const symbol = scopes.symbolFor(cursor);
29604
+ if (!symbol?.initializer || !isSmallLiteralArray(symbol.initializer)) return false;
29605
+ if (!isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier")) return false;
29606
+ return (symbol.kind === "const" || symbol.kind === "let" || symbol.kind === "var") && symbol.references.every((reference) => reference.flag === "read" && isNonMutatingSmallArrayMethodReference(reference.identifier));
29607
+ }
29164
29608
  if (!isNodeOfType(cursor, "CallExpression")) return false;
29165
29609
  if (!isChainPassThroughCall(cursor)) return false;
29166
29610
  const nextCallee = cursor.callee;
@@ -29169,22 +29613,6 @@ const isSmallLiteralArrayRootedChain = (receiverNode, smallConstArrayNames) => {
29169
29613
  }
29170
29614
  return false;
29171
29615
  };
29172
- const collectSmallConstArrayNames = (programNode) => {
29173
- const names = /* @__PURE__ */ new Set();
29174
- const statements = programNode.body ?? [];
29175
- for (const statement of statements) {
29176
- const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
29177
- if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) continue;
29178
- if (declaration.kind !== "const") continue;
29179
- for (const declarator of declaration.declarations ?? []) {
29180
- if (!isNodeOfType(declarator, "VariableDeclarator")) continue;
29181
- if (!isNodeOfType(declarator.id, "Identifier")) continue;
29182
- if (!declarator.init || !isSmallLiteralArray(declarator.init)) continue;
29183
- names.add(declarator.id.name);
29184
- }
29185
- }
29186
- return names;
29187
- };
29188
29616
  const collectGeneratorNames = (programNode) => {
29189
29617
  const generatorNames = /* @__PURE__ */ new Set();
29190
29618
  walkAst(programNode, (child) => {
@@ -29205,16 +29633,11 @@ const jsCombineIterations = defineRule({
29205
29633
  create: (context) => {
29206
29634
  let programNode = null;
29207
29635
  let generatorNamesInFile = null;
29208
- let smallConstArrayNames = null;
29209
29636
  const coveredChainCalls = /* @__PURE__ */ new WeakSet();
29210
29637
  const getGeneratorNamesInFile = () => {
29211
29638
  generatorNamesInFile ??= programNode ? collectGeneratorNames(programNode) : /* @__PURE__ */ new Set();
29212
29639
  return generatorNamesInFile;
29213
29640
  };
29214
- const getSmallConstArrayNames = () => {
29215
- smallConstArrayNames ??= programNode ? collectSmallConstArrayNames(programNode) : /* @__PURE__ */ new Set();
29216
- return smallConstArrayNames;
29217
- };
29218
29641
  return {
29219
29642
  Program(node) {
29220
29643
  programNode = node;
@@ -29244,7 +29667,7 @@ const jsCombineIterations = defineRule({
29244
29667
  if (isTypePredicateArrow(filterArgument)) return;
29245
29668
  }
29246
29669
  if (isReceiverChainIteratorRooted(innerCall.callee.object, getGeneratorNamesInFile())) return;
29247
- if (isSmallLiteralArrayRootedChain(innerCall.callee.object, getSmallConstArrayNames())) return;
29670
+ if (isSmallLiteralArrayRootedChain(innerCall.callee.object, context.scopes)) return;
29248
29671
  if (isStringSplitRootedChain(innerCall.callee.object)) return;
29249
29672
  coveredChainCalls.add(innerCall);
29250
29673
  context.report({
@@ -30471,7 +30894,8 @@ const STRING_TYPED_PROPERTY_NAMES = new Set([
30471
30894
  "code",
30472
30895
  "label",
30473
30896
  "slug",
30474
- "prefix"
30897
+ "prefix",
30898
+ "__html"
30475
30899
  ]);
30476
30900
  const STRING_TYPED_IDENTIFIER_SUFFIXES = [
30477
30901
  "Text",
@@ -30589,13 +31013,25 @@ const STRING_TYPED_IDENTIFIER_NAMES = new Set([
30589
31013
  "title"
30590
31014
  ]);
30591
31015
  const STRING_RETURNING_CALLEE_PREFIX_PATTERN = /^(?:normalize|format|stringify|serialize)/;
31016
+ const FRESH_ARRAY_METHOD_NAMES$2 = new Set([
31017
+ "concat",
31018
+ "filter",
31019
+ "flat",
31020
+ "flatMap",
31021
+ "map",
31022
+ "slice",
31023
+ "split"
31024
+ ]);
30592
31025
  const isLikelyStringReceiver = (receiver) => {
30593
31026
  if (!receiver) return false;
31027
+ const unwrappedReceiver = stripParenExpression(receiver);
31028
+ if (unwrappedReceiver !== receiver) return isLikelyStringReceiver(unwrappedReceiver);
30594
31029
  if (isNodeOfType(receiver, "Literal") && typeof receiver.value === "string") return true;
30595
31030
  if (isNodeOfType(receiver, "TemplateLiteral")) return true;
30596
31031
  if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "Identifier") && receiver.callee.name === "String") return true;
30597
31032
  if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "MemberExpression") && isNodeOfType(receiver.callee.property, "Identifier") && STRING_RETURNING_METHODS.has(receiver.callee.property.name)) return true;
30598
31033
  if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "Identifier") && STRING_RETURNING_CALLEE_PREFIX_PATTERN.test(receiver.callee.name)) return true;
31034
+ 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;
30599
31035
  if (isNodeOfType(receiver, "MemberExpression") && isNodeOfType(receiver.property, "Identifier")) {
30600
31036
  if (STRING_TYPED_PROPERTY_NAMES.has(receiver.property.name)) return true;
30601
31037
  }
@@ -30613,8 +31049,46 @@ const isLikelyStringReceiver = (receiver) => {
30613
31049
  }
30614
31050
  if (isNodeOfType(receiver, "BinaryExpression") && receiver.operator === "+") return isLikelyStringReceiver(receiver.left) || isLikelyStringReceiver(receiver.right);
30615
31051
  if (isNodeOfType(receiver, "ConditionalExpression")) return isLikelyStringReceiver(receiver.consequent) && isLikelyStringReceiver(receiver.alternate);
31052
+ if (isNodeOfType(receiver, "LogicalExpression")) return isLikelyStringReceiver(receiver.left) && isLikelyStringReceiver(receiver.right);
30616
31053
  return false;
30617
31054
  };
31055
+ const isFreshArrayReceiver = (receiver) => {
31056
+ const unwrappedReceiver = stripParenExpression(receiver);
31057
+ if (unwrappedReceiver !== receiver) return isFreshArrayReceiver(unwrappedReceiver);
31058
+ if (!isNodeOfType(receiver, "CallExpression") || !isNodeOfType(receiver.callee, "MemberExpression") || !isNodeOfType(receiver.callee.property, "Identifier")) return false;
31059
+ if (!FRESH_ARRAY_METHOD_NAMES$2.has(receiver.callee.property.name)) return false;
31060
+ if (receiver.callee.property.name === "split") return isLikelyStringReceiver(receiver.callee.object);
31061
+ const sourceReceiver = stripParenExpression(receiver.callee.object);
31062
+ return isKnownNativeArrayReceiver(sourceReceiver) || isFreshArrayReceiver(sourceReceiver);
31063
+ };
31064
+ const isSmallRestHelperOmissionList = (node) => {
31065
+ if (!node) return false;
31066
+ const candidate = stripParenExpression(node);
31067
+ if (!isNodeOfType(candidate, "ArrayExpression")) return false;
31068
+ const elements = candidate.elements ?? [];
31069
+ return elements.length <= 8 && elements.every((element) => element === null || !isNodeOfType(element, "SpreadElement"));
31070
+ };
31071
+ const isTypeScriptRestHelperLookup = (lookupCall, receiver, scopes) => {
31072
+ if (!isNodeOfType(receiver, "Identifier")) return false;
31073
+ const enclosingFunction = findEnclosingFunction$1(lookupCall);
31074
+ if (!isNodeOfType(enclosingFunction, "FunctionExpression") || !isNodeOfType(enclosingFunction.params?.[1], "Identifier") || enclosingFunction.params[1].name !== receiver.name) return false;
31075
+ let bindingIdentifier = null;
31076
+ let ancestor = enclosingFunction.parent;
31077
+ while (ancestor && !isFunctionLike$1(ancestor)) {
31078
+ if (isNodeOfType(ancestor, "VariableDeclarator") && isNodeOfType(ancestor.id, "Identifier") && ancestor.id.name === "__rest") {
31079
+ bindingIdentifier = ancestor.id;
31080
+ break;
31081
+ }
31082
+ ancestor = ancestor.parent;
31083
+ }
31084
+ if (!bindingIdentifier) return false;
31085
+ const helperSymbol = scopes.symbolFor(bindingIdentifier);
31086
+ if (!helperSymbol || helperSymbol.references.length === 0) return false;
31087
+ return helperSymbol.references.every((reference) => {
31088
+ const callExpression = reference.identifier.parent;
31089
+ return isNodeOfType(callExpression, "CallExpression") && callExpression.callee === reference.identifier && isSmallRestHelperOmissionList(callExpression.arguments?.[1]);
31090
+ });
31091
+ };
30618
31092
  const INDEX_LIKE_IDENTIFIER_NAMES = new Set([
30619
31093
  "i",
30620
31094
  "j",
@@ -31211,6 +31685,8 @@ const jsSetMapLookups = defineRule({
31211
31685
  const query = node.arguments[0];
31212
31686
  if (methodName === "indexOf" && !isKnownSafeIndexOfQuery(query) && (isKnownUnsafeIndexOfQuery(query, receiver) || isKnownUnsafeIndexOfReceiver(receiver))) return;
31213
31687
  if (isLikelyStringReceiver(receiver)) return;
31688
+ if (isFreshArrayReceiver(receiver)) return;
31689
+ if (isTypeScriptRestHelperLookup(node, receiver, context.scopes)) return;
31214
31690
  if (isSmallInlineLiteralArray(receiver)) return;
31215
31691
  if (isScreamingSnakeCaseConstantReceiver(receiver)) return;
31216
31692
  if (isSmallFixedListMember(receiver)) return;
@@ -45124,14 +45600,6 @@ const noAriaInvalidWithoutDescription = defineRule({
45124
45600
  } })
45125
45601
  });
45126
45602
  //#endregion
45127
- //#region src/plugin/utils/is-early-exit-statement.ts
45128
- const isEarlyExitStatement$1 = (statement) => {
45129
- if (!statement) return false;
45130
- if (statementAlwaysExits$1(statement)) return true;
45131
- if (isNodeOfType(statement, "BlockStatement")) return isEarlyExitStatement$1(statement.body.at(-1));
45132
- return isNodeOfType(statement, "ContinueStatement") || isNodeOfType(statement, "BreakStatement");
45133
- };
45134
- //#endregion
45135
45603
  //#region src/plugin/utils/unwrap-negative-guard-form.ts
45136
45604
  const unwrapNegativeGuardForm = (test) => {
45137
45605
  const expression = stripParenExpression(test);
@@ -46652,6 +47120,101 @@ const isLoopCounterDeclarator = (declarator, referenceNode, indexName) => {
46652
47120
  }
46653
47121
  return isBindingReassignedOrMutated(referenceNode, indexName);
46654
47122
  };
47123
+ const findEnclosingWhileLoop = (node) => {
47124
+ let current = node.parent;
47125
+ while (current) {
47126
+ if (isNodeOfType(current, "WhileStatement") || isNodeOfType(current, "DoWhileStatement")) return current;
47127
+ if (isFunctionLike$1(current) || isNodeOfType(current, "Program")) return null;
47128
+ current = current.parent;
47129
+ }
47130
+ return null;
47131
+ };
47132
+ const isStaticMemberChain = (expression) => {
47133
+ const candidate = stripParenExpression(expression);
47134
+ if (isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "ThisExpression")) return true;
47135
+ return Boolean(isNodeOfType(candidate, "MemberExpression") && !candidate.computed && isNodeOfType(candidate.property, "Identifier") && isStaticMemberChain(candidate.object));
47136
+ };
47137
+ const findLengthBoundCollections = (expression) => {
47138
+ const collections = [];
47139
+ walkAst(expression, (child) => {
47140
+ if (isNodeOfType(child, "MemberExpression") && !child.computed && isStaticMemberChain(child.object) && isNodeOfType(child.property, "Identifier") && child.property.name === "length") collections.push(child.object);
47141
+ });
47142
+ return collections;
47143
+ };
47144
+ const areSameBoundStaticMemberChains = (first, second) => isStaticMemberChain(first) && isStaticMemberChain(second) && areExpressionsStructurallyEqual(first, second, { areIdentifiersEqual: (firstIdentifier, secondIdentifier) => {
47145
+ if (!isNodeOfType(firstIdentifier, "Identifier") || !isNodeOfType(secondIdentifier, "Identifier") || firstIdentifier.name !== secondIdentifier.name) return false;
47146
+ const firstBinding = findVariableInitializer(firstIdentifier, firstIdentifier.name);
47147
+ const secondBinding = findVariableInitializer(secondIdentifier, secondIdentifier.name);
47148
+ return firstBinding?.bindingIdentifier === secondBinding?.bindingIdentifier;
47149
+ } });
47150
+ const RELATIONAL_BOUND_OPERATORS = new Set([
47151
+ "<",
47152
+ "<=",
47153
+ ">",
47154
+ ">="
47155
+ ]);
47156
+ const loopTestBoundsCounterByLength = (expression, indexName, bindingIdentifier) => {
47157
+ const readsCounter = (candidate) => {
47158
+ let didReadCounter = false;
47159
+ walkAst(candidate, (child) => {
47160
+ if (didReadCounter) return false;
47161
+ if (isNodeOfType(child, "Identifier") && child.name === indexName && findVariableInitializer(child, indexName)?.bindingIdentifier === bindingIdentifier) {
47162
+ didReadCounter = true;
47163
+ return false;
47164
+ }
47165
+ });
47166
+ return didReadCounter;
47167
+ };
47168
+ const readsLength = (candidate) => {
47169
+ let didReadLength = false;
47170
+ walkAst(candidate, (child) => {
47171
+ if (didReadLength) return false;
47172
+ if (isNodeOfType(child, "MemberExpression") && !child.computed && isNodeOfType(child.property, "Identifier") && child.property.name === "length") {
47173
+ didReadLength = true;
47174
+ return false;
47175
+ }
47176
+ });
47177
+ return didReadLength;
47178
+ };
47179
+ let didFindLengthBound = false;
47180
+ walkAst(expression, (child) => {
47181
+ if (didFindLengthBound) return false;
47182
+ if (!isNodeOfType(child, "BinaryExpression") || !RELATIONAL_BOUND_OPERATORS.has(child.operator)) return;
47183
+ if (readsCounter(child.left) && readsLength(child.right) || readsCounter(child.right) && readsLength(child.left)) {
47184
+ didFindLengthBound = true;
47185
+ return false;
47186
+ }
47187
+ });
47188
+ return didFindLengthBound;
47189
+ };
47190
+ const isDataIndexedWhileLoopCounter = (referenceNode, bindingIdentifier, indexName) => {
47191
+ if (!INDEX_PARAMETER_NAMES.has(indexName)) return false;
47192
+ const loop = findEnclosingWhileLoop(referenceNode);
47193
+ if (!loop) return false;
47194
+ let doesTestReadCounter = false;
47195
+ walkAst(loop.test, (child) => {
47196
+ if (doesTestReadCounter) return false;
47197
+ if (!isNodeOfType(child, "Identifier") || child.name !== indexName) return;
47198
+ if (findVariableInitializer(child, indexName)?.bindingIdentifier === bindingIdentifier) {
47199
+ doesTestReadCounter = true;
47200
+ return false;
47201
+ }
47202
+ });
47203
+ if (!doesTestReadCounter) return false;
47204
+ const lengthBoundCollections = findLengthBoundCollections(loop.test);
47205
+ if (lengthBoundCollections.length === 0) return false;
47206
+ let didFindIndexedCollectionRead = false;
47207
+ walkAst(loop.body, (child) => {
47208
+ if (didFindIndexedCollectionRead) return false;
47209
+ if (isFunctionLike$1(child)) return false;
47210
+ if (!isNodeOfType(child, "MemberExpression") || !child.computed || !isNodeOfType(child.property, "Identifier") || child.property.name !== indexName) return;
47211
+ if (findVariableInitializer(child.property, indexName)?.bindingIdentifier === bindingIdentifier && lengthBoundCollections.some((collection) => areSameBoundStaticMemberChains(collection, child.object))) {
47212
+ didFindIndexedCollectionRead = true;
47213
+ return false;
47214
+ }
47215
+ });
47216
+ return didFindIndexedCollectionRead;
47217
+ };
46655
47218
  /**
46656
47219
  * Resolves whether an identifier is PROVABLY the positional array
46657
47220
  * index, by classifying its binding. Per the official rule prompt,
@@ -46702,6 +47265,12 @@ const resolvePositionalIndexBinding = (identifierNode, depth) => {
46702
47265
  }
46703
47266
  const declarator = binding.bindingIdentifier.parent;
46704
47267
  if (declarator && isNodeOfType(declarator, "VariableDeclarator") && declarator.id === binding.bindingIdentifier && declarator.init) {
47268
+ if (isDataIndexedWhileLoopCounter(identifierNode, binding.bindingIdentifier, identifierNode.name)) return {
47269
+ iteratorCall: null,
47270
+ bindingFunction: null,
47271
+ indexParameterPosition: null,
47272
+ isDataIndexedLoopCounter: true
47273
+ };
46705
47274
  const initializer = stripParenExpression(declarator.init);
46706
47275
  if (isNodeOfType(initializer, "Literal") && typeof initializer.value === "number") {
46707
47276
  if (!INDEX_PARAMETER_NAMES.has(identifierNode.name)) return null;
@@ -46736,6 +47305,101 @@ const iteratorCallExemptsIndexKey = (iteratorCall) => {
46736
47305
  const receiver = iteratorCall.callee.object;
46737
47306
  return isStaticPlaceholderReceiver(receiver) || isFixedMemoReceiver(receiver) || isStaticDefaultLiteralReceiver(receiver) || isStringDerivedReceiver(receiver);
46738
47307
  };
47308
+ const isReactNamespaceIdentifier = (node) => {
47309
+ if (!isNodeOfType(node, "Identifier")) return false;
47310
+ const importBinding = getImportBindingForName(node, node.name);
47311
+ const visibleBinding = findVariableInitializer(node, node.name);
47312
+ if (!importBinding) return node.name === "React" && !visibleBinding;
47313
+ return visibleBinding?.initializer?.type.startsWith("Import") === true && importBinding.source === "react" && (importBinding.isNamespace || importBinding.exportedName === "default");
47314
+ };
47315
+ const isReactChildrenObject = (node) => {
47316
+ const candidate = stripParenExpression(node);
47317
+ if (isNodeOfType(candidate, "Identifier")) {
47318
+ const importBinding = getImportBindingForName(candidate, candidate.name);
47319
+ const visibleBinding = findVariableInitializer(candidate, candidate.name);
47320
+ return Boolean(visibleBinding?.initializer?.type === "ImportSpecifier" && importBinding?.source === "react" && importBinding.exportedName === "Children");
47321
+ }
47322
+ return Boolean(isNodeOfType(candidate, "MemberExpression") && !candidate.computed && isReactNamespaceIdentifier(candidate.object) && isNodeOfType(candidate.property, "Identifier") && candidate.property.name === "Children");
47323
+ };
47324
+ const isReactChildrenToArrayCall = (node) => {
47325
+ const candidate = stripParenExpression(node);
47326
+ 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;
47327
+ const normalizedValueNode = candidate.arguments?.[0];
47328
+ const normalizedValue = normalizedValueNode ? stripParenExpression(normalizedValueNode) : null;
47329
+ if (!normalizedValue || !isNodeOfType(normalizedValue, "Identifier") || normalizedValue.name !== "children") return false;
47330
+ const normalizedBinding = findVariableInitializer(normalizedValue, normalizedValue.name);
47331
+ return Boolean(normalizedBinding && findEnclosingParameter(normalizedBinding.bindingIdentifier));
47332
+ };
47333
+ const isSameIdentifier = (first, second) => {
47334
+ const firstIdentifier = stripParenExpression(first);
47335
+ const secondIdentifier = stripParenExpression(second);
47336
+ if (!isNodeOfType(firstIdentifier, "Identifier") || !isNodeOfType(secondIdentifier, "Identifier") || firstIdentifier.name !== secondIdentifier.name) return false;
47337
+ const firstBinding = findVariableInitializer(firstIdentifier, firstIdentifier.name);
47338
+ const secondBinding = findVariableInitializer(secondIdentifier, secondIdentifier.name);
47339
+ return firstBinding?.bindingIdentifier === secondBinding?.bindingIdentifier;
47340
+ };
47341
+ const isReactChildrenArrayNormalization = (node) => {
47342
+ const candidate = stripParenExpression(node);
47343
+ if (!isNodeOfType(candidate, "ConditionalExpression")) return false;
47344
+ const test = stripParenExpression(candidate.test);
47345
+ 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;
47346
+ const testedValueNode = test.arguments?.[0];
47347
+ if (!testedValueNode) return false;
47348
+ const testedValue = stripParenExpression(testedValueNode);
47349
+ if (!isNodeOfType(testedValue, "Identifier")) return false;
47350
+ const testedBinding = findVariableInitializer(testedValue, testedValue.name);
47351
+ if (testedValue.name !== "children" || !testedBinding || !findEnclosingParameter(testedBinding.bindingIdentifier)) return false;
47352
+ const branchWrapsTestedValue = (branch) => {
47353
+ const unwrappedBranch = stripParenExpression(branch);
47354
+ return isNodeOfType(unwrappedBranch, "ArrayExpression") && unwrappedBranch.elements?.length === 1 && Boolean(unwrappedBranch.elements[0] && isSameIdentifier(unwrappedBranch.elements[0], testedValue));
47355
+ };
47356
+ return isSameIdentifier(candidate.consequent, testedValue) && branchWrapsTestedValue(candidate.alternate) || isSameIdentifier(candidate.alternate, testedValue) && branchWrapsTestedValue(candidate.consequent);
47357
+ };
47358
+ const isMutatedEmptyArrayBinding = (identifierNode, depth) => {
47359
+ const binding = findVariableInitializer(identifierNode, identifierNode.name);
47360
+ const initializer = binding?.initializer ? stripParenExpression(binding.initializer) : null;
47361
+ if (!binding || !initializer || !isNodeOfType(initializer, "ArrayExpression") || initializer.elements?.length !== 0) return false;
47362
+ const program = findProgramRoot(identifierNode);
47363
+ if (!program) return false;
47364
+ let didFindReactChildPush = false;
47365
+ walkAst(program, (child) => {
47366
+ if (didFindReactChildPush) return false;
47367
+ 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;
47368
+ if (findVariableInitializer(child.callee.object, identifierNode.name)?.bindingIdentifier === binding.bindingIdentifier && (child.arguments ?? []).some((argument) => {
47369
+ const candidate = stripParenExpression(argument);
47370
+ if (!(isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "JSXElement") || isNodeOfType(candidate, "JSXFragment"))) return false;
47371
+ let doesCarryReactChild = false;
47372
+ walkAst(candidate, (argumentChild) => {
47373
+ if (doesCarryReactChild) return false;
47374
+ if (!isNodeOfType(argumentChild, "Identifier")) return;
47375
+ const declarator = findVariableInitializer(argumentChild, argumentChild.name)?.bindingIdentifier.parent;
47376
+ const declaration = declarator?.parent;
47377
+ const forOfStatement = declaration?.parent;
47378
+ if (declarator && isNodeOfType(declarator, "VariableDeclarator") && declaration && isNodeOfType(declaration, "VariableDeclaration") && forOfStatement && isNodeOfType(forOfStatement, "ForOfStatement") && forOfStatement.left === declaration && isDynamicReactChildrenExpression(forOfStatement.right, depth + 1)) {
47379
+ doesCarryReactChild = true;
47380
+ return false;
47381
+ }
47382
+ });
47383
+ return doesCarryReactChild;
47384
+ })) {
47385
+ didFindReactChildPush = true;
47386
+ return false;
47387
+ }
47388
+ });
47389
+ return didFindReactChildPush;
47390
+ };
47391
+ const isDynamicReactChildrenExpression = (expression, depth) => {
47392
+ if (depth > TYPE_RESOLUTION_DEPTH_LIMIT$2) return false;
47393
+ const candidate = stripParenExpression(expression);
47394
+ if (isReactChildrenToArrayCall(candidate) || isReactChildrenArrayNormalization(candidate)) return true;
47395
+ if (isNodeOfType(candidate, "Identifier")) {
47396
+ if (isMutatedEmptyArrayBinding(candidate, depth)) return true;
47397
+ const binding = findVariableInitializer(candidate, candidate.name);
47398
+ return Boolean(binding?.initializer && isDynamicReactChildrenExpression(binding.initializer, depth + 1));
47399
+ }
47400
+ 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);
47401
+ return false;
47402
+ };
46739
47403
  const resolveKeyTemplateLiteral = (expression) => {
46740
47404
  const node = stripParenExpression(expression);
46741
47405
  if (isNodeOfType(node, "TemplateLiteral")) return node;
@@ -46788,28 +47452,19 @@ const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
46788
47452
  }
46789
47453
  return referencedItemNames;
46790
47454
  };
46791
- const forLoopTestReadsDataLength = (test) => {
46792
- let didFindLengthRead = false;
46793
- walkAst(test, (child) => {
46794
- if (didFindLengthRead) return false;
46795
- if (isNodeOfType(child, "MemberExpression") && isNodeOfType(child.property, "Identifier") && child.property.name === "length") {
46796
- didFindLengthRead = true;
46797
- return false;
46798
- }
46799
- });
46800
- return didFindLengthRead;
46801
- };
46802
- const isNumericForLoopCounter = (attributeNode, indexName) => {
47455
+ const isNumericPlaceholderLoopCounter = (attributeNode, indexName) => {
46803
47456
  const binding = findVariableInitializer(attributeNode, indexName);
46804
47457
  if (!binding) return false;
46805
47458
  const declarator = binding.bindingIdentifier.parent;
46806
47459
  if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return false;
46807
47460
  const declaration = declarator.parent;
46808
47461
  if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) return false;
47462
+ if (!declarator.init || !isNodeOfType(declarator.init, "Literal") || typeof declarator.init.value !== "number") return false;
46809
47463
  const forStatement = declaration.parent;
46810
- if (!forStatement || !isNodeOfType(forStatement, "ForStatement") || forStatement.init !== declaration || !declarator.init || !isNodeOfType(declarator.init, "Literal") || typeof declarator.init.value !== "number") return false;
46811
- if (forStatement.test && forLoopTestReadsDataLength(forStatement.test)) return false;
46812
- return true;
47464
+ if (forStatement && isNodeOfType(forStatement, "ForStatement") && forStatement.init === declaration) return !(forStatement.test && loopTestBoundsCounterByLength(forStatement.test, indexName, binding.bindingIdentifier));
47465
+ const whileLoop = findEnclosingWhileLoop(attributeNode);
47466
+ if (!whileLoop) return false;
47467
+ return !loopTestBoundsCounterByLength(whileLoop.test, indexName, binding.bindingIdentifier);
46813
47468
  };
46814
47469
  const EMPTY_NAME_SET$1 = /* @__PURE__ */ new Set();
46815
47470
  const findIteratorItemNamesOfBinding = (binding) => {
@@ -46843,11 +47498,11 @@ const collectDerivedRowContentNames = (bindingFunction, itemNames) => {
46843
47498
  * observable harm; anything stateful — form controls, media, custom
46844
47499
  * components, unknown calls — keeps the diagnostic.
46845
47500
  */
46846
- const fragmentHasStatefulChildren = (openingElement, itemNames, derivedNames) => {
47501
+ const fragmentHasStatefulChildren = (openingElement, itemNames, derivedNames, areBareItemsDynamicReactChildren) => {
46847
47502
  const jsxElement = openingElement.parent;
46848
47503
  if (!jsxElement || !isNodeOfType(jsxElement, "JSXElement")) return false;
46849
47504
  const children = jsxElement.children ?? [];
46850
- const bareIdentifierNames = children.some((child) => isNodeOfType(child, "JSXElement")) ? derivedNames : new Set([...derivedNames, ...itemNames]);
47505
+ const bareIdentifierNames = children.some((child) => isNodeOfType(child, "JSXElement")) ? derivedNames : areBareItemsDynamicReactChildren ? derivedNames : new Set([...derivedNames, ...itemNames]);
46851
47506
  return children.some((child) => containsStatefulDescendant(child, {
46852
47507
  memberRootNames: itemNames,
46853
47508
  allowAnyMemberRead: true,
@@ -46855,6 +47510,15 @@ const fragmentHasStatefulChildren = (openingElement, itemNames, derivedNames) =>
46855
47510
  callCalleeRootNames: itemNames
46856
47511
  }));
46857
47512
  };
47513
+ const elementHasDirectItemChild = (openingElement, itemNames) => {
47514
+ const jsxElement = openingElement.parent;
47515
+ if (!jsxElement || !isNodeOfType(jsxElement, "JSXElement")) return false;
47516
+ return (jsxElement.children ?? []).some((child) => {
47517
+ if (!isNodeOfType(child, "JSXExpressionContainer")) return false;
47518
+ const expression = stripParenExpression(child.expression);
47519
+ return isNodeOfType(expression, "Identifier") && itemNames.has(expression.name);
47520
+ });
47521
+ };
46858
47522
  const callbackFiltersRows = (bindingFunction) => {
46859
47523
  if (!bindingFunction) return false;
46860
47524
  let didFindNullReturn = false;
@@ -46906,19 +47570,21 @@ const noArrayIndexAsKey = defineRule({
46906
47570
  const indexUse = findPositionalIndexUse(node.value.expression, 0);
46907
47571
  if (!indexUse) return;
46908
47572
  const indexName = indexUse.identifier.name;
46909
- if (isNumericForLoopCounter(node, indexName)) return;
47573
+ if (isNumericPlaceholderLoopCounter(node, indexName)) return;
46910
47574
  if (indexUse.binding.iteratorCall && iteratorCallExemptsIndexKey(indexUse.binding.iteratorCall)) return;
46911
47575
  const keyTemplate = resolveKeyTemplateLiteral(node.value.expression);
46912
47576
  if (keyTemplate && templateHasOuterMemberIdentity(keyTemplate, indexUse.binding.bindingFunction)) return;
46913
- if (hasAriaHiddenAncestor(node)) return;
47577
+ if (hasAriaHiddenAncestor(node) && !indexUse.binding.isDataIndexedLoopCounter) return;
46914
47578
  const itemNames = findIteratorItemNamesOfBinding(indexUse.binding);
46915
47579
  const derivedNames = collectDerivedRowContentNames(indexUse.binding.bindingFunction, itemNames);
47580
+ const iteratorCallee = indexUse.binding.iteratorCall?.callee;
47581
+ const hasDynamicReactChildren = Boolean(iteratorCallee && isNodeOfType(iteratorCallee, "MemberExpression") && isDynamicReactChildrenExpression(iteratorCallee.object, 0));
46916
47582
  const openingElement = node.parent;
46917
47583
  if (openingElement && isNodeOfType(openingElement, "JSXOpeningElement")) {
46918
47584
  const elementName = openingElement.name;
46919
47585
  if (isNodeOfType(elementName, "JSXIdentifier")) {
46920
47586
  if (elementName.name === "Fragment") {
46921
- if (!fragmentHasStatefulChildren(openingElement, itemNames, derivedNames)) return;
47587
+ if (!fragmentHasStatefulChildren(openingElement, itemNames, derivedNames, hasDynamicReactChildren)) return;
46922
47588
  } else if (PURE_SVG_PRIMITIVE_TAGS.has(elementName.name)) {
46923
47589
  if (!callbackFiltersRows(indexUse.binding.bindingFunction)) return;
46924
47590
  } else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
@@ -46926,14 +47592,14 @@ const noArrayIndexAsKey = defineRule({
46926
47592
  if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
46927
47593
  const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
46928
47594
  const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET$1;
46929
- if (!containsStatefulDescendant(jsxElement, {
47595
+ if (!(hasDynamicReactChildren && elementHasDirectItemChild(openingElement, itemNames) || containsStatefulDescendant(jsxElement, {
46930
47596
  memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET$1,
46931
47597
  bareIdentifierNames: primitiveItemNames.size > 0 ? new Set([...derivedNames, ...primitiveItemNames]) : derivedNames
46932
- })) return;
47598
+ }))) return;
46933
47599
  }
46934
47600
  }
46935
47601
  }
46936
- 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;
47602
+ 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;
46937
47603
  }
46938
47604
  context.report({
46939
47605
  node,
@@ -55421,6 +56087,9 @@ const isInitialOnlyPropName = (propName) => {
55421
56087
  return /^initial[A-Z]/.test(propName) || /^default[A-Z]/.test(propName) || /^seed[A-Z]/.test(propName) || /^starting[A-Z]/.test(propName) || /^baseline[A-Z]/.test(propName) || /^preset[A-Z]/.test(propName);
55422
56088
  };
55423
56089
  //#endregion
56090
+ //#region src/plugin/utils/nextjs-page-data-export-names.ts
56091
+ const NEXTJS_PAGE_DATA_EXPORT_NAMES = new Set(["getServerSideProps", "getStaticProps"]);
56092
+ //#endregion
55424
56093
  //#region src/plugin/rules/state-and-effects/no-derived-use-state.ts
55425
56094
  const isInitialOnlySeedName = (propName) => isInitialOnlyPropName(propName) || propName === "initial" || propName === "autoFocus" || propName === "autoPlay" || propName === "startOpen" || /^initially[A-Z]/.test(propName) || /Initial([A-Z]|$)/.test(propName);
55426
56095
  const SNAPSHOT_STATE_NAME_PATTERN = /^(initial|previous|prev|preserved|saved|original|cached|snapshot|prior|debounced|deferred)([A-Z_]|$)/;
@@ -55577,7 +56246,6 @@ const isDraftCommittedToParent = (componentFunction, stateValueName, isPropName)
55577
56246
  });
55578
56247
  return isCommitted;
55579
56248
  };
55580
- const NEXTJS_PAGE_DATA_EXPORT_NAMES = new Set(["getServerSideProps", "getStaticProps"]);
55581
56249
  const isNextjsDataFetchingPage = (node) => {
55582
56250
  const program = findProgramRoot(node);
55583
56251
  if (!program) return false;
@@ -63829,6 +64497,106 @@ const isGatedByFalsyInitialState = (node, scopes) => {
63829
64497
  };
63830
64498
  //#endregion
63831
64499
  //#region src/plugin/rules/performance/no-hydration-branch-on-browser-global.ts
64500
+ const findGuardingIfStatements = (node, functionBoundary) => {
64501
+ const guardingIfStatements = [];
64502
+ let currentNode = node.parent;
64503
+ while (currentNode && currentNode !== functionBoundary) {
64504
+ if (isNodeOfType(currentNode, "IfStatement")) guardingIfStatements.push(currentNode);
64505
+ currentNode = currentNode.parent;
64506
+ }
64507
+ return guardingIfStatements;
64508
+ };
64509
+ const doesNodeReadSymbol = (node, symbol) => {
64510
+ let doesReadSymbol = false;
64511
+ walkAst(node, (childNode) => {
64512
+ if (isNodeOfType(childNode, "Identifier") && symbol.references.some((reference) => reference.identifier === childNode && reference.flag !== "write")) {
64513
+ doesReadSymbol = true;
64514
+ return false;
64515
+ }
64516
+ });
64517
+ return doesReadSymbol;
64518
+ };
64519
+ const collectWrittenSymbols = (node, scopes) => {
64520
+ const writtenSymbols = /* @__PURE__ */ new Set();
64521
+ walkAst(node, (childNode) => {
64522
+ if (childNode !== node && isFunctionLike$1(childNode)) return false;
64523
+ if (!isNodeOfType(childNode, "Identifier")) return;
64524
+ const reference = scopes.referenceFor(childNode);
64525
+ if (!reference || reference.flag === "read" || !reference.resolvedSymbol) return;
64526
+ writtenSymbols.add(reference.resolvedSymbol);
64527
+ });
64528
+ return writtenSymbols;
64529
+ };
64530
+ const isDescendantOf = (node, ancestorNode) => {
64531
+ let currentNode = node.parent;
64532
+ while (currentNode) {
64533
+ if (currentNode === ancestorNode) return true;
64534
+ currentNode = currentNode.parent;
64535
+ }
64536
+ return false;
64537
+ };
64538
+ const getAssignedValue = (identifier) => {
64539
+ const assignmentExpression = identifier.parent;
64540
+ return isNodeOfType(assignmentExpression, "AssignmentExpression") && assignmentExpression.operator === "=" && assignmentExpression.left === identifier ? assignmentExpression.right : null;
64541
+ };
64542
+ const doesGuardPreserveInitialSymbolValue = (symbol, guardingIfStatement, scopes) => {
64543
+ const initialValue = symbol.initializer;
64544
+ if (!initialValue) return false;
64545
+ const guardedWrites = symbol.references.filter((reference) => reference.flag !== "read" && isDescendantOf(reference.identifier, guardingIfStatement));
64546
+ return guardedWrites.length > 0 && guardedWrites.every((reference) => {
64547
+ const assignedValue = getAssignedValue(reference.identifier);
64548
+ return Boolean(assignedValue && areExpressionsStructurallyEqual(initialValue, assignedValue) && doEquivalentExpressionBindingsMatch(initialValue, assignedValue, scopes));
64549
+ });
64550
+ };
64551
+ 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));
64552
+ const isUnconditionalOrStaticallySelected = (node, context) => {
64553
+ if (context.cfg.isUnconditionalFromEntry(node)) return true;
64554
+ let currentNode = node;
64555
+ let outermostStaticIfStatement = null;
64556
+ let parentNode = currentNode.parent;
64557
+ while (parentNode) {
64558
+ if (isFunctionLike$1(parentNode)) break;
64559
+ if (isNodeOfType(parentNode, "IfStatement")) {
64560
+ const staticResult = readInitialStateBoolean(parentNode.test, context.scopes);
64561
+ let selectedBranch = null;
64562
+ if (staticResult === true) selectedBranch = parentNode.consequent;
64563
+ if (staticResult === false) selectedBranch = parentNode.alternate;
64564
+ if (!selectedBranch || currentNode !== selectedBranch && !isDescendantOf(currentNode, selectedBranch)) return false;
64565
+ outermostStaticIfStatement = parentNode;
64566
+ }
64567
+ currentNode = parentNode;
64568
+ parentNode = currentNode.parent;
64569
+ }
64570
+ return Boolean(outermostStaticIfStatement && context.cfg.isUnconditionalFromEntry(outermostStaticIfStatement));
64571
+ };
64572
+ const containsExplicitReactRuntimeReference = (node, scopes) => {
64573
+ let hasRuntimeReference = false;
64574
+ walkAst(node, (childNode) => {
64575
+ if (isNodeOfType(childNode, "ImportDeclaration") && typeof childNode.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(childNode.source.value)) {
64576
+ hasRuntimeReference = true;
64577
+ return false;
64578
+ }
64579
+ if (!isNodeOfType(childNode, "CallExpression")) return;
64580
+ const sourceArgument = (childNode.arguments ?? [])[0];
64581
+ 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;
64582
+ hasRuntimeReference = true;
64583
+ return false;
64584
+ });
64585
+ return hasRuntimeReference;
64586
+ };
64587
+ const findComponentRenderingLocalFunctionResult = (functionNode, scopes) => {
64588
+ const bindingIdentifier = getDirectFunctionBindingIdentifier(functionNode);
64589
+ if (!isNodeOfType(bindingIdentifier, "Identifier")) return null;
64590
+ const functionSymbol = scopes.symbolFor(bindingIdentifier);
64591
+ if (!functionSymbol) return null;
64592
+ for (const reference of functionSymbol.references) {
64593
+ const callExpression = reference.identifier.parent;
64594
+ if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== reference.identifier) continue;
64595
+ const componentOrHookNode = findRenderPhaseComponentOrHook(callExpression, scopes);
64596
+ if (componentOrHookNode && isInRenderedOutput(callExpression, componentOrHookNode, scopes)) return componentOrHookNode;
64597
+ }
64598
+ return null;
64599
+ };
63832
64600
  const evaluateEquality$1 = (operator, left, right) => {
63833
64601
  if (operator === "===" || operator === "==") return left === right;
63834
64602
  if (operator === "!==" || operator === "!=") return left !== right;
@@ -63879,6 +64647,107 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
63879
64647
  if (leftResult === false && rightResult === false) return false;
63880
64648
  return null;
63881
64649
  };
64650
+ const areLooselyEqualPrimitiveResults = (left, right) => {
64651
+ if (left.kind === right.kind) return left.value === right.value;
64652
+ if (left.kind === "null" && right.kind === "undefined" || left.kind === "undefined" && right.kind === "null") return true;
64653
+ if (left.kind === "boolean") return areLooselyEqualPrimitiveResults({
64654
+ kind: "number",
64655
+ value: left.value ? 1 : 0
64656
+ }, right);
64657
+ if (right.kind === "boolean") return areLooselyEqualPrimitiveResults(left, {
64658
+ kind: "number",
64659
+ value: right.value ? 1 : 0
64660
+ });
64661
+ if (left.kind === "number" && right.kind === "string") return left.value === Number(right.value);
64662
+ if (left.kind === "string" && right.kind === "number") return Number(left.value) === right.value;
64663
+ return false;
64664
+ };
64665
+ const readHydrationPrimitiveResult = (expression, context, runtime, state) => {
64666
+ const unwrappedExpression = stripParenExpression(expression);
64667
+ const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
64668
+ if (predicateMatch) return {
64669
+ kind: "boolean",
64670
+ value: predicateMatch[`${runtime}Result`]
64671
+ };
64672
+ if (isNodeOfType(unwrappedExpression, "Literal")) {
64673
+ const value = unwrappedExpression.value;
64674
+ if (value === null) return {
64675
+ kind: "null",
64676
+ value
64677
+ };
64678
+ if (typeof value === "boolean") return {
64679
+ kind: "boolean",
64680
+ value
64681
+ };
64682
+ if (typeof value === "number") return {
64683
+ kind: "number",
64684
+ value
64685
+ };
64686
+ if (typeof value === "string") return {
64687
+ kind: "string",
64688
+ value
64689
+ };
64690
+ return null;
64691
+ }
64692
+ if (isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "undefined" && context.scopes.isGlobalReference(unwrappedExpression)) return {
64693
+ kind: "undefined",
64694
+ value: void 0
64695
+ };
64696
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
64697
+ const symbol = context.scopes.symbolFor(unwrappedExpression);
64698
+ const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
64699
+ if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
64700
+ state.visitedSymbolIds.add(symbol.id);
64701
+ const result = readHydrationPrimitiveResult(parameterValue, context, runtime, state);
64702
+ state.visitedSymbolIds.delete(symbol.id);
64703
+ return result;
64704
+ }
64705
+ if (symbol && symbol.kind === "const" && symbol.initializer && symbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(symbol.id)) {
64706
+ state.visitedSymbolIds.add(symbol.id);
64707
+ const result = readHydrationPrimitiveResult(symbol.initializer, context, runtime, state);
64708
+ state.visitedSymbolIds.delete(symbol.id);
64709
+ return result;
64710
+ }
64711
+ }
64712
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
64713
+ const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
64714
+ return argumentResult === null ? null : {
64715
+ kind: "boolean",
64716
+ value: !argumentResult
64717
+ };
64718
+ }
64719
+ if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
64720
+ const leftResult = readHydrationPrimitiveResult(unwrappedExpression.left, context, runtime, state);
64721
+ const rightResult = readHydrationPrimitiveResult(unwrappedExpression.right, context, runtime, state);
64722
+ if (!leftResult || !rightResult) return null;
64723
+ if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==") {
64724
+ const areEqual = leftResult.kind === rightResult.kind && leftResult.value === rightResult.value;
64725
+ return {
64726
+ kind: "boolean",
64727
+ value: unwrappedExpression.operator === "===" ? areEqual : !areEqual
64728
+ };
64729
+ }
64730
+ if (unwrappedExpression.operator === "==" || unwrappedExpression.operator === "!=") {
64731
+ const areEqual = areLooselyEqualPrimitiveResults(leftResult, rightResult);
64732
+ return {
64733
+ kind: "boolean",
64734
+ value: unwrappedExpression.operator === "==" ? areEqual : !areEqual
64735
+ };
64736
+ }
64737
+ }
64738
+ if (isNodeOfType(unwrappedExpression, "CallExpression")) {
64739
+ const callArguments = unwrappedExpression.arguments ?? [];
64740
+ const callee = stripParenExpression(unwrappedExpression.callee);
64741
+ if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) {
64742
+ const argumentResult = readHydrationConditionResult(callArguments[0], context, runtime, state);
64743
+ return argumentResult === null ? null : {
64744
+ kind: "boolean",
64745
+ value: argumentResult
64746
+ };
64747
+ }
64748
+ }
64749
+ return null;
64750
+ };
63882
64751
  const readHydrationConditionResult = (expression, context, runtime, state) => {
63883
64752
  const unwrappedExpression = stripParenExpression(expression);
63884
64753
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
@@ -63927,6 +64796,10 @@ const readHydrationConditionResult = (expression, context, runtime, state) => {
63927
64796
  parameterValuesBySymbolId
63928
64797
  });
63929
64798
  }
64799
+ if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
64800
+ const result = readHydrationPrimitiveResult(unwrappedExpression, context, runtime, state);
64801
+ return result?.kind === "boolean" && typeof result.value === "boolean" ? result.value : null;
64802
+ }
63930
64803
  if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
63931
64804
  const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
63932
64805
  return argumentResult === null ? null : !argumentResult;
@@ -63987,13 +64860,19 @@ const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, sc
63987
64860
  const rightSymbol = scopes.symbolFor(right);
63988
64861
  return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
63989
64862
  }
63990
- if (isNodeOfType(left, "MemberExpression") && isNodeOfType(right, "MemberExpression")) return doEquivalentExpressionBindingsMatch(left.object, right.object, scopes) && (!left.computed || doEquivalentExpressionBindingsMatch(left.property, right.property, scopes));
63991
- if (isNodeOfType(left, "CallExpression") && isNodeOfType(right, "CallExpression")) {
63992
- const rightArguments = right.arguments ?? [];
63993
- return doEquivalentExpressionBindingsMatch(left.callee, right.callee, scopes) && (left.arguments ?? []).every((argument, index) => {
63994
- const rightArgument = rightArguments[index];
63995
- return Boolean(rightArgument && doEquivalentExpressionBindingsMatch(argument, rightArgument, scopes));
63996
- });
64863
+ const rightEntries = new Map(Object.entries(right));
64864
+ for (const [key, leftValue] of Object.entries(left)) {
64865
+ if (key === "parent") continue;
64866
+ const rightValue = rightEntries.get(key);
64867
+ if (isAstNode(leftValue)) {
64868
+ if (!isAstNode(rightValue) || !doEquivalentExpressionBindingsMatch(leftValue, rightValue, scopes)) return false;
64869
+ continue;
64870
+ }
64871
+ if (!Array.isArray(leftValue)) continue;
64872
+ if (!Array.isArray(rightValue)) return false;
64873
+ const leftNodes = leftValue.filter(isAstNode);
64874
+ const rightNodes = rightValue.filter(isAstNode);
64875
+ if (leftNodes.length !== rightNodes.length || leftNodes.some((leftNode, index) => !rightNodes[index] || !doEquivalentExpressionBindingsMatch(leftNode, rightNodes[index], scopes))) return false;
63997
64876
  }
63998
64877
  return true;
63999
64878
  };
@@ -64007,6 +64886,57 @@ const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
64007
64886
  const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
64008
64887
  return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
64009
64888
  };
64889
+ const isExpressionProvablyReflexive = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
64890
+ const unwrappedExpression = stripParenExpression(expression);
64891
+ if (matchBrowserPredicate(unwrappedExpression, context)) return true;
64892
+ if (isNodeOfType(unwrappedExpression, "Literal")) return typeof unwrappedExpression.value !== "number" || !Number.isNaN(unwrappedExpression.value);
64893
+ if (isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "undefined" && context.scopes.isGlobalReference(unwrappedExpression)) return true;
64894
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
64895
+ const symbol = context.scopes.symbolFor(unwrappedExpression);
64896
+ if (!symbol || visitedSymbolIds.has(symbol.id) || !symbol.initializer) return false;
64897
+ visitedSymbolIds.add(symbol.id);
64898
+ const assignedValues = symbol.references.filter((reference) => reference.flag !== "read").map((reference) => getAssignedValue(reference.identifier));
64899
+ const isReflexive = isExpressionProvablyReflexive(symbol.initializer, context, visitedSymbolIds) && assignedValues.every((assignedValue) => Boolean(assignedValue && isExpressionProvablyReflexive(assignedValue, context, visitedSymbolIds)));
64900
+ visitedSymbolIds.delete(symbol.id);
64901
+ return isReflexive;
64902
+ }
64903
+ if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return isExpressionProvablyReflexive(unwrappedExpression.consequent, context, visitedSymbolIds) && isExpressionProvablyReflexive(unwrappedExpression.alternate, context, visitedSymbolIds);
64904
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && (unwrappedExpression.operator === "!" || unwrappedExpression.operator === "typeof" || unwrappedExpression.operator === "void")) return true;
64905
+ if (isNodeOfType(unwrappedExpression, "BinaryExpression")) return unwrappedExpression.operator === "===" || unwrappedExpression.operator === "!==" || unwrappedExpression.operator === "==" || unwrappedExpression.operator === "!=";
64906
+ if (isNodeOfType(unwrappedExpression, "ArrayExpression") || isNodeOfType(unwrappedExpression, "ObjectExpression") || isNodeOfType(unwrappedExpression, "FunctionExpression") || isNodeOfType(unwrappedExpression, "ArrowFunctionExpression") || isNodeOfType(unwrappedExpression, "TemplateLiteral")) return true;
64907
+ if (!isNodeOfType(unwrappedExpression, "CallExpression")) return false;
64908
+ const callee = stripParenExpression(unwrappedExpression.callee);
64909
+ return isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee);
64910
+ };
64911
+ const getReturnedObjectPropertyValues = (node, propertyName, scopes) => {
64912
+ if (isNodeOfType(node, "ReturnStatement")) return node.argument ? getReturnedObjectPropertyValues(node.argument, propertyName, scopes) : [];
64913
+ if (isNodeOfType(node, "ObjectExpression")) return node.properties.flatMap((property) => isNodeOfType(property, "Property") && property.kind === "init" && getResolvedStaticPropertyName(property, scopes) === propertyName ? [property.value] : []);
64914
+ if (isNodeOfType(node, "IfStatement")) return [...getReturnedObjectPropertyValues(node.consequent, propertyName, scopes), ...node.alternate ? getReturnedObjectPropertyValues(node.alternate, propertyName, scopes) : []];
64915
+ if (isNodeOfType(node, "TryStatement")) return [
64916
+ ...getReturnedObjectPropertyValues(node.block, propertyName, scopes),
64917
+ ...node.handler ? getReturnedObjectPropertyValues(node.handler.body, propertyName, scopes) : [],
64918
+ ...node.finalizer ? getReturnedObjectPropertyValues(node.finalizer, propertyName, scopes) : []
64919
+ ];
64920
+ if (!isNodeOfType(node, "BlockStatement")) return [];
64921
+ const propertyValues = [];
64922
+ for (const childStatement of node.body) {
64923
+ propertyValues.push(...getReturnedObjectPropertyValues(childStatement, propertyName, scopes));
64924
+ if (statementAlwaysExits$1(childStatement)) break;
64925
+ }
64926
+ return propertyValues;
64927
+ };
64928
+ const matchHydrationFunctionPropertyResult = (functionNode, propertyName, context, state) => {
64929
+ if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
64930
+ state.visitedFunctionNodes.add(functionNode);
64931
+ const propertyValues = getReturnedObjectPropertyValues(functionNode.body, propertyName, context.scopes);
64932
+ let match = null;
64933
+ for (const propertyValue of propertyValues) {
64934
+ match = matchHydrationConditionInternal(propertyValue, context, state);
64935
+ if (match) break;
64936
+ }
64937
+ state.visitedFunctionNodes.delete(functionNode);
64938
+ return match;
64939
+ };
64010
64940
  const matchHydrationConditionInternal = (expression, context, state) => {
64011
64941
  const unwrappedExpression = stripParenExpression(expression);
64012
64942
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
@@ -64023,14 +64953,90 @@ const matchHydrationConditionInternal = (expression, context, state) => {
64023
64953
  state.visitedSymbolIds.delete(symbol.id);
64024
64954
  return match;
64025
64955
  }
64956
+ if (symbol && (symbol.kind === "let" || symbol.kind === "var") && !state.visitedSymbolIds.has(symbol.id)) {
64957
+ state.visitedSymbolIds.add(symbol.id);
64958
+ if (symbol.initializer && symbol.references.every((reference) => reference.flag === "read")) {
64959
+ const match = matchHydrationConditionInternal(symbol.initializer, context, state);
64960
+ state.visitedSymbolIds.delete(symbol.id);
64961
+ return match;
64962
+ }
64963
+ for (const reference of symbol.references) {
64964
+ if (reference.flag === "read") continue;
64965
+ if (!isNodeReachableWithinFunction(reference.identifier, context)) continue;
64966
+ const enclosingFunction = findEnclosingFunction$1(reference.identifier);
64967
+ if (!enclosingFunction) continue;
64968
+ for (const guardingIfStatement of findGuardingIfStatements(reference.identifier, enclosingFunction)) {
64969
+ if (doesGuardPreserveInitialSymbolValue(symbol, guardingIfStatement, context.scopes) || isWriteOverwrittenBefore(symbol, reference.identifier, guardingIfStatement, unwrappedExpression, context)) continue;
64970
+ const match = matchHydrationConditionInternal(guardingIfStatement.test, context, state);
64971
+ if (match) {
64972
+ state.visitedSymbolIds.delete(symbol.id);
64973
+ return match;
64974
+ }
64975
+ }
64976
+ }
64977
+ const readingFunction = findEnclosingFunction$1(unwrappedExpression);
64978
+ for (const reference of symbol.references) {
64979
+ if (reference.flag === "read") continue;
64980
+ const writingFunction = findEnclosingFunction$1(reference.identifier);
64981
+ if (!readingFunction || !isFunctionLike$1(writingFunction) || writingFunction === readingFunction || writingFunction.async || writingFunction.params.length > 0 || isNodeOfType(writingFunction, "FunctionDeclaration") && writingFunction.generator || isNodeOfType(writingFunction, "FunctionExpression") && writingFunction.generator) continue;
64982
+ const assignedValue = getAssignedValue(reference.identifier);
64983
+ if (symbol.initializer && assignedValue && areExpressionsStructurallyEqual(symbol.initializer, assignedValue) && doEquivalentExpressionBindingsMatch(symbol.initializer, assignedValue, context.scopes)) continue;
64984
+ const functionBinding = getDirectFunctionBindingIdentifier(writingFunction);
64985
+ if (!isNodeOfType(functionBinding, "Identifier")) continue;
64986
+ const functionSymbol = context.scopes.symbolFor(functionBinding);
64987
+ if (!functionSymbol) continue;
64988
+ for (const functionReference of functionSymbol.references) {
64989
+ const callExpression = functionReference.identifier.parent;
64990
+ if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== functionReference.identifier || (callExpression.arguments ?? []).length > 0 || findEnclosingFunction$1(callExpression) !== readingFunction || !isNodeReachableWithinFunction(callExpression, context) || getNodeStartIndex(callExpression) >= getNodeStartIndex(unwrappedExpression)) continue;
64991
+ for (const guardingIfStatement of findGuardingIfStatements(callExpression, readingFunction)) {
64992
+ if (isWriteOverwrittenBefore(symbol, callExpression, guardingIfStatement, unwrappedExpression, context)) continue;
64993
+ const match = matchHydrationConditionInternal(guardingIfStatement.test, context, state);
64994
+ if (match) {
64995
+ state.visitedSymbolIds.delete(symbol.id);
64996
+ return match;
64997
+ }
64998
+ }
64999
+ }
65000
+ }
65001
+ state.visitedSymbolIds.delete(symbol.id);
65002
+ }
64026
65003
  if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
64027
65004
  state.visitedSymbolIds.add(symbol.id);
64028
65005
  const match = matchHydrationConditionInternal(symbol.initializer, context, state);
64029
65006
  state.visitedSymbolIds.delete(symbol.id);
64030
65007
  return match;
64031
65008
  }
65009
+ if (isNodeOfType(unwrappedExpression, "MemberExpression")) {
65010
+ const propertyName = getResolvedStaticPropertyName(unwrappedExpression, context.scopes, {
65011
+ allowConstNumericLiteral: true,
65012
+ stringifyNonStringLiterals: true
65013
+ });
65014
+ const object = stripParenExpression(unwrappedExpression.object);
65015
+ if (propertyName === null || !isNodeOfType(object, "CallExpression")) return null;
65016
+ const callArguments = object.arguments ?? [];
65017
+ if (isReactApiCall(object, "useMemo", context.scopes, {
65018
+ allowGlobalReactNamespace: true,
65019
+ resolveNamedAliases: true
65020
+ })) {
65021
+ const callbackArgument = callArguments[0];
65022
+ if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
65023
+ const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
65024
+ return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionPropertyResult(callbackFunction, propertyName, context, state) : null;
65025
+ }
65026
+ const helperFunction = resolveExactLocalFunction(object.callee, context.scopes);
65027
+ return isFunctionLike$1(helperFunction) && helperFunction.params.length === 0 && callArguments.length === 0 ? matchHydrationFunctionPropertyResult(helperFunction, propertyName, context, state) : null;
65028
+ }
64032
65029
  if (isNodeOfType(unwrappedExpression, "CallExpression")) {
64033
65030
  const callArguments = unwrappedExpression.arguments ?? [];
65031
+ if (isReactApiCall(unwrappedExpression, "useState", context.scopes, {
65032
+ allowGlobalReactNamespace: true,
65033
+ resolveNamedAliases: true
65034
+ })) {
65035
+ const initialState = callArguments[0];
65036
+ if (!initialState || isNodeOfType(initialState, "SpreadElement")) return null;
65037
+ const lazyInitializer = resolveExactLocalFunction(initialState, context.scopes);
65038
+ return isFunctionLike$1(lazyInitializer) && lazyInitializer.params.length === 0 ? matchHydrationFunctionResult(lazyInitializer, context, state) : matchHydrationConditionInternal(initialState, context, state);
65039
+ }
64034
65040
  if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
64035
65041
  allowGlobalReactNamespace: true,
64036
65042
  resolveNamedAliases: true
@@ -64058,6 +65064,22 @@ const matchHydrationConditionInternal = (expression, context, state) => {
64058
65064
  });
64059
65065
  }
64060
65066
  if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
65067
+ if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
65068
+ const staticTestResult = readInitialStateBoolean(unwrappedExpression.test, context.scopes);
65069
+ if (staticTestResult !== null) return matchHydrationConditionInternal(staticTestResult ? unwrappedExpression.consequent : unwrappedExpression.alternate, context, state);
65070
+ return matchHydrationConditionInternal(unwrappedExpression.test, context, state) ?? matchHydrationConditionInternal(unwrappedExpression.consequent, context, state) ?? matchHydrationConditionInternal(unwrappedExpression.alternate, context, state);
65071
+ }
65072
+ if (isNodeOfType(unwrappedExpression, "BinaryExpression")) {
65073
+ if (unwrappedExpression.operator !== "===" && unwrappedExpression.operator !== "!==" && unwrappedExpression.operator !== "==" && unwrappedExpression.operator !== "!=") return null;
65074
+ const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
65075
+ const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
65076
+ const nestedMatch = leftMatch ?? rightMatch;
65077
+ if (!nestedMatch) return null;
65078
+ const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client", state);
65079
+ const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server", state);
65080
+ if (clientResult !== null && serverResult !== null) return clientResult !== serverResult ? nestedMatch : null;
65081
+ return leftMatch && rightMatch && areExpressionsStructurallyEqual(unwrappedExpression.left, unwrappedExpression.right) && doEquivalentExpressionBindingsMatch(unwrappedExpression.left, unwrappedExpression.right, context.scopes) && isExpressionProvablyReflexive(unwrappedExpression.left, context) ? null : nestedMatch;
65082
+ }
64061
65083
  if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
64062
65084
  const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
64063
65085
  const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
@@ -64074,8 +65096,13 @@ const matchHydrationReturningStatement = (statement, context, state) => {
64074
65096
  const consequentValues = getReturnedValues(statement.consequent);
64075
65097
  const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
64076
65098
  if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
65099
+ if (conditionMatch) {
65100
+ const followingReturnedValues = findFollowingReturnedValues(statement);
65101
+ 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;
65102
+ }
64077
65103
  return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
64078
65104
  }
65105
+ 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);
64079
65106
  if (!isNodeOfType(statement, "BlockStatement")) return null;
64080
65107
  for (const childStatement of statement.body) {
64081
65108
  const match = matchHydrationReturningStatement(childStatement, context, state);
@@ -64096,47 +65123,62 @@ const matchHydrationCondition = (expression, context) => matchHydrationCondition
64096
65123
  visitedFunctionNodes: /* @__PURE__ */ new Set(),
64097
65124
  visitedSymbolIds: /* @__PURE__ */ new Set()
64098
65125
  });
64099
- const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
64100
- const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
65126
+ const areNodeArraysEquivalent = (leftNodes, rightNodes, scopes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index], scopes));
65127
+ const areRenderedBranchesEquivalent = (leftNode, rightNode, scopes) => {
64101
65128
  if (!leftNode || !rightNode) return leftNode === rightNode;
64102
65129
  const left = stripParenExpression(leftNode);
64103
65130
  const right = stripParenExpression(rightNode);
64104
- if (areExpressionsStructurallyEqual(left, right)) return true;
65131
+ if (areExpressionsStructurallyEqual(left, right)) return doEquivalentExpressionBindingsMatch(left, right, scopes);
64105
65132
  if (left.type !== right.type) return false;
64106
65133
  if (isNodeOfType(left, "JSXText") && isNodeOfType(right, "JSXText")) return left.value === right.value;
64107
65134
  if (isNodeOfType(left, "JSXExpressionContainer") && isNodeOfType(right, "JSXExpressionContainer")) {
64108
65135
  if (!isAstNode(left.expression) || !isAstNode(right.expression)) return left.expression.type === right.expression.type;
64109
- return areRenderedBranchesEquivalent(left.expression, right.expression);
65136
+ return areRenderedBranchesEquivalent(left.expression, right.expression, scopes);
64110
65137
  }
64111
65138
  if (isNodeOfType(left, "JSXElement") && isNodeOfType(right, "JSXElement")) {
64112
65139
  if (flattenJsxName$1(left.openingElement.name) !== flattenJsxName$1(right.openingElement.name)) return false;
64113
- if (!areNodeArraysEquivalent(left.openingElement.attributes, right.openingElement.attributes)) return false;
64114
- return areNodeArraysEquivalent(left.children, right.children);
65140
+ if (!areNodeArraysEquivalent(left.openingElement.attributes, right.openingElement.attributes, scopes)) return false;
65141
+ return areNodeArraysEquivalent(left.children, right.children, scopes);
64115
65142
  }
64116
- if (isNodeOfType(left, "JSXFragment") && isNodeOfType(right, "JSXFragment")) return areNodeArraysEquivalent(left.children, right.children);
65143
+ if (isNodeOfType(left, "JSXFragment") && isNodeOfType(right, "JSXFragment")) return areNodeArraysEquivalent(left.children, right.children, scopes);
64117
65144
  if (isNodeOfType(left, "JSXAttribute") && isNodeOfType(right, "JSXAttribute")) {
64118
65145
  if (flattenJsxName$1(left.name) !== flattenJsxName$1(right.name)) return false;
64119
- return areRenderedBranchesEquivalent(left.value, right.value);
65146
+ return areRenderedBranchesEquivalent(left.value, right.value, scopes);
64120
65147
  }
64121
- if (isNodeOfType(left, "JSXSpreadAttribute") && isNodeOfType(right, "JSXSpreadAttribute")) return areRenderedBranchesEquivalent(left.argument, right.argument);
65148
+ if (isNodeOfType(left, "JSXSpreadAttribute") && isNodeOfType(right, "JSXSpreadAttribute")) return areRenderedBranchesEquivalent(left.argument, right.argument, scopes);
64122
65149
  if (isNodeOfType(left, "TemplateLiteral") && isNodeOfType(right, "TemplateLiteral")) {
64123
65150
  if (left.quasis.length !== right.quasis.length) return false;
64124
65151
  if (!left.quasis.every((quasi, index) => quasi.value.cooked === right.quasis[index]?.value.cooked && quasi.value.raw === right.quasis[index]?.value.raw)) return false;
64125
- return areNodeArraysEquivalent(left.expressions, right.expressions);
65152
+ return areNodeArraysEquivalent(left.expressions, right.expressions, scopes);
64126
65153
  }
64127
65154
  return false;
64128
65155
  };
64129
- const isRenderedValue = (node) => {
65156
+ const isProvenReactCreateElementCall = (node, scopes) => {
65157
+ if (isReactApiCall(node, "createElement", scopes, {
65158
+ allowGlobalReactNamespace: true,
65159
+ resolveNamedAliases: true
65160
+ })) return true;
65161
+ if (!isNodeOfType(node, "CallExpression")) return false;
65162
+ const callee = stripParenExpression(node.callee);
65163
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "createElement") return false;
65164
+ const receiver = stripParenExpression(callee.object);
65165
+ const namespaceIdentifier = isNodeOfType(receiver, "MemberExpression") && !receiver.computed && isNodeOfType(receiver.property, "Identifier") && receiver.property.name === "default" ? stripParenExpression(receiver.object) : receiver;
65166
+ if (!isNodeOfType(namespaceIdentifier, "Identifier")) return false;
65167
+ const namespaceSymbol = scopes.symbolFor(namespaceIdentifier);
65168
+ return Boolean(namespaceSymbol?.initializer && namespaceSymbol.references.every((reference) => reference.flag === "read") && containsExplicitReactRuntimeReference(namespaceSymbol.initializer, scopes));
65169
+ };
65170
+ const isRenderedValue = (node, scopes) => {
64130
65171
  const unwrappedNode = stripParenExpression(node);
64131
65172
  if (isNodeOfType(unwrappedNode, "Literal")) return unwrappedNode.value !== null && unwrappedNode.value !== true && unwrappedNode.value !== false && unwrappedNode.value !== "";
64132
65173
  if (isNodeOfType(unwrappedNode, "TemplateLiteral")) return unwrappedNode.expressions.length > 0 || unwrappedNode.quasis[0]?.value.cooked !== "";
65174
+ if (isNodeOfType(unwrappedNode, "CallExpression")) return isProvenReactCreateElementCall(unwrappedNode, scopes);
64133
65175
  return isNodeOfType(unwrappedNode, "JSXElement") || isNodeOfType(unwrappedNode, "JSXFragment");
64134
65176
  };
64135
- const findRenderedValueInAndBranch = (node) => {
65177
+ const findRenderedValueInAndBranch = (node, scopes) => {
64136
65178
  const unwrappedNode = stripParenExpression(node);
64137
- if (isRenderedValue(unwrappedNode)) return unwrappedNode;
65179
+ if (isPotentiallyRenderedValue(unwrappedNode, scopes)) return unwrappedNode;
64138
65180
  if (!isNodeOfType(unwrappedNode, "LogicalExpression") || unwrappedNode.operator !== "&&") return null;
64139
- return findRenderedValueInAndBranch(unwrappedNode.right);
65181
+ return findRenderedValueInAndBranch(unwrappedNode.right, scopes);
64140
65182
  };
64141
65183
  const findEnclosingJsxAttribute = (node) => {
64142
65184
  let currentNode = node.parent;
@@ -64169,6 +65211,11 @@ const getReturnedValues = (statement) => {
64169
65211
  if (!statement) return [];
64170
65212
  if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? [statement.argument] : [];
64171
65213
  if (isNodeOfType(statement, "IfStatement")) return [...getReturnedValues(statement.consequent), ...getReturnedValues(statement.alternate)];
65214
+ if (isNodeOfType(statement, "TryStatement")) return [
65215
+ ...getReturnedValues(statement.block),
65216
+ ...getReturnedValues(statement.handler?.body),
65217
+ ...getReturnedValues(statement.finalizer)
65218
+ ];
64172
65219
  if (!isNodeOfType(statement, "BlockStatement")) return [];
64173
65220
  const returnedValues = [];
64174
65221
  for (const childStatement of statement.body) {
@@ -64177,6 +65224,127 @@ const getReturnedValues = (statement) => {
64177
65224
  }
64178
65225
  return returnedValues;
64179
65226
  };
65227
+ const isPotentiallyRenderedValueInternal = (node, scopes, visitedFunctionNodes) => {
65228
+ const unwrappedNode = stripParenExpression(node);
65229
+ if (isRenderedValue(unwrappedNode, scopes)) return true;
65230
+ if (isNodeOfType(unwrappedNode, "ConditionalExpression")) return isPotentiallyRenderedValueInternal(unwrappedNode.consequent, scopes, visitedFunctionNodes) && isPotentiallyRenderedValueInternal(unwrappedNode.alternate, scopes, visitedFunctionNodes);
65231
+ if (isNodeOfType(unwrappedNode, "LogicalExpression")) return isPotentiallyRenderedValueInternal(unwrappedNode.right, scopes, visitedFunctionNodes);
65232
+ if (!isNodeOfType(unwrappedNode, "CallExpression")) return false;
65233
+ const calledFunction = resolveExactLocalFunction(unwrappedNode.callee, scopes);
65234
+ if (!isFunctionLike$1(calledFunction) || visitedFunctionNodes.has(calledFunction)) return false;
65235
+ visitedFunctionNodes.add(calledFunction);
65236
+ const returnedValues = isNodeOfType(calledFunction.body, "BlockStatement") ? getReturnedValues(calledFunction.body) : [calledFunction.body];
65237
+ const isPotentiallyRendered = returnedValues.length > 0 && returnedValues.every((returnedValue) => isPotentiallyRenderedValueInternal(returnedValue, scopes, visitedFunctionNodes));
65238
+ visitedFunctionNodes.delete(calledFunction);
65239
+ return isPotentiallyRendered;
65240
+ };
65241
+ const isPotentiallyRenderedValue = (node, scopes) => isPotentiallyRenderedValueInternal(node, scopes, /* @__PURE__ */ new Set());
65242
+ const findUseStateBindingSymbol = (node, componentOrHookNode, scopes) => {
65243
+ let currentNode = node.parent;
65244
+ while (currentNode && currentNode !== componentOrHookNode) {
65245
+ if (isNodeOfType(currentNode, "CallExpression") && isReactApiCall(currentNode, "useState", scopes, {
65246
+ allowGlobalReactNamespace: true,
65247
+ resolveNamedAliases: true
65248
+ }) && isNodeOfType(currentNode.parent, "VariableDeclarator") && isNodeOfType(currentNode.parent.id, "ArrayPattern")) {
65249
+ const stateBinding = currentNode.parent.id.elements?.[0];
65250
+ return isNodeOfType(stateBinding, "Identifier") ? scopes.symbolFor(stateBinding) : null;
65251
+ }
65252
+ if (isFunctionLike$1(currentNode)) return null;
65253
+ currentNode = currentNode.parent;
65254
+ }
65255
+ return null;
65256
+ };
65257
+ const doesReferenceControlStructuralRenderedValue = (referenceIdentifier) => {
65258
+ let currentNode = referenceIdentifier;
65259
+ let parentNode = currentNode.parent;
65260
+ while (parentNode) {
65261
+ if (isNodeOfType(parentNode, "ConditionalExpression") && parentNode.test === currentNode && (isStructuralRenderedValue(parentNode.consequent) || isStructuralRenderedValue(parentNode.alternate))) return true;
65262
+ if (isNodeOfType(parentNode, "LogicalExpression") && parentNode.left === currentNode && isStructuralRenderedValue(parentNode.right)) return true;
65263
+ if (isNodeOfType(parentNode, "JSXExpressionContainer") || isFunctionLike$1(parentNode)) return false;
65264
+ currentNode = parentNode;
65265
+ parentNode = currentNode.parent;
65266
+ }
65267
+ return false;
65268
+ };
65269
+ const isRenderedHydrationConsumer = (node, producerHookNode, scopes) => {
65270
+ const renderingComponent = findRenderPhaseComponentOrHook(node, scopes);
65271
+ return Boolean(renderingComponent && renderingComponent !== producerHookNode && isInRenderedOutput(node, renderingComponent, scopes) && !isGatedByFalsyInitialState(node, scopes) && !isAfterClientOnlyEarlyReturn(node, renderingComponent, scopes) && (!hasSuppressHydrationWarningAttribute(findEnclosingJsxOpeningElement(node)) || doesReferenceControlStructuralRenderedValue(node)));
65272
+ };
65273
+ const doesConsumerExpressionReachRenderedOutput = (node, producerHookNode, scopes, visitedSymbolIds) => {
65274
+ if (isRenderedHydrationConsumer(node, producerHookNode, scopes)) return true;
65275
+ const parentNode = node.parent;
65276
+ if (!isNodeOfType(parentNode, "VariableDeclarator") || parentNode.init !== node || !isNodeOfType(parentNode.id, "Identifier")) return false;
65277
+ const aliasSymbol = scopes.symbolFor(parentNode.id);
65278
+ if (!aliasSymbol || visitedSymbolIds.has(aliasSymbol.id)) return false;
65279
+ visitedSymbolIds.add(aliasSymbol.id);
65280
+ const doesReachRenderedOutput = aliasSymbol.references.some((reference) => doesConsumerExpressionReachRenderedOutput(reference.identifier, producerHookNode, scopes, visitedSymbolIds));
65281
+ visitedSymbolIds.delete(aliasSymbol.id);
65282
+ return doesReachRenderedOutput;
65283
+ };
65284
+ const doesConsumerBindingReachRenderedOutput = (bindingIdentifier, producerHookNode, scopes) => {
65285
+ if (!isNodeOfType(bindingIdentifier, "Identifier")) return false;
65286
+ const consumerSymbol = scopes.symbolFor(bindingIdentifier);
65287
+ if (!consumerSymbol) return false;
65288
+ return consumerSymbol.references.some((reference) => doesConsumerExpressionReachRenderedOutput(reference.identifier, producerHookNode, scopes, new Set([consumerSymbol.id])));
65289
+ };
65290
+ const getReturnedStatePaths = (returnedValue, stateSymbol, scopes) => {
65291
+ const unwrappedValue = stripParenExpression(returnedValue);
65292
+ if (isNodeOfType(unwrappedValue, "ObjectExpression")) return unwrappedValue.properties.flatMap((property) => {
65293
+ if (!isNodeOfType(property, "Property") || property.kind !== "init" || !doesNodeReadSymbol(property.value, stateSymbol)) return [];
65294
+ const propertyName = getResolvedStaticPropertyName(property, scopes);
65295
+ return propertyName === null ? [] : [{
65296
+ kind: "property",
65297
+ key: propertyName
65298
+ }];
65299
+ });
65300
+ if (isNodeOfType(unwrappedValue, "ArrayExpression")) return (unwrappedValue.elements ?? []).flatMap((element, index) => element && isAstNode(element) && doesNodeReadSymbol(element, stateSymbol) ? [{
65301
+ kind: "index",
65302
+ key: String(index)
65303
+ }] : []);
65304
+ return doesNodeReadSymbol(unwrappedValue, stateSymbol) ? [{
65305
+ kind: "direct",
65306
+ key: null
65307
+ }] : [];
65308
+ };
65309
+ const doesCallResultPathReachRenderedOutput = (callExpression, returnedStatePath, producerHookNode, scopes) => {
65310
+ const callParent = callExpression.parent;
65311
+ if (returnedStatePath.kind === "direct") return doesConsumerExpressionReachRenderedOutput(callExpression, producerHookNode, scopes, /* @__PURE__ */ new Set());
65312
+ if (isNodeOfType(callParent, "MemberExpression") && callParent.object === callExpression && getResolvedStaticPropertyName(callParent, scopes, {
65313
+ allowConstNumericLiteral: true,
65314
+ stringifyNonStringLiterals: true
65315
+ }) === returnedStatePath.key) return doesConsumerExpressionReachRenderedOutput(callParent, producerHookNode, scopes, /* @__PURE__ */ new Set());
65316
+ if (!isNodeOfType(callParent, "VariableDeclarator") || callParent.init !== callExpression) return false;
65317
+ 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));
65318
+ if (returnedStatePath.kind === "index" && isNodeOfType(callParent.id, "ArrayPattern")) {
65319
+ const element = callParent.id.elements?.[Number(returnedStatePath.key)];
65320
+ return Boolean(element && doesConsumerBindingReachRenderedOutput(element, producerHookNode, scopes));
65321
+ }
65322
+ if (!isNodeOfType(callParent.id, "Identifier")) return false;
65323
+ const resultSymbol = scopes.symbolFor(callParent.id);
65324
+ if (!resultSymbol) return false;
65325
+ return resultSymbol.references.some((reference) => {
65326
+ const memberExpression = reference.identifier.parent;
65327
+ return Boolean(isNodeOfType(memberExpression, "MemberExpression") && memberExpression.object === reference.identifier && getResolvedStaticPropertyName(memberExpression, scopes, {
65328
+ allowConstNumericLiteral: true,
65329
+ stringifyNonStringLiterals: true
65330
+ }) === returnedStatePath.key && doesConsumerExpressionReachRenderedOutput(memberExpression, producerHookNode, scopes, new Set([resultSymbol.id])));
65331
+ });
65332
+ };
65333
+ const isReturnedUseStateInitializerRendered = (node, componentOrHookNode, scopes) => {
65334
+ if (!isFunctionLike$1(componentOrHookNode)) return false;
65335
+ const stateSymbol = findUseStateBindingSymbol(node, componentOrHookNode, scopes);
65336
+ if (!stateSymbol) return false;
65337
+ const returnedStatePaths = (isNodeOfType(componentOrHookNode.body, "BlockStatement") ? getReturnedValues(componentOrHookNode.body) : [componentOrHookNode.body]).flatMap((returnedValue) => getReturnedStatePaths(returnedValue, stateSymbol, scopes));
65338
+ if (returnedStatePaths.length === 0) return false;
65339
+ const functionBinding = getDirectFunctionBindingIdentifier(componentOrHookNode);
65340
+ if (!isNodeOfType(functionBinding, "Identifier")) return false;
65341
+ const functionSymbol = scopes.symbolFor(functionBinding);
65342
+ if (!functionSymbol) return false;
65343
+ return functionSymbol.references.some((functionReference) => {
65344
+ const callExpression = functionReference.identifier.parent;
65345
+ return Boolean(isNodeOfType(callExpression, "CallExpression") && callExpression.callee === functionReference.identifier && returnedStatePaths.some((returnedStatePath) => doesCallResultPathReachRenderedOutput(callExpression, returnedStatePath, componentOrHookNode, scopes)));
65346
+ });
65347
+ };
64180
65348
  const findFollowingReturnedValues = (ifStatement) => {
64181
65349
  const parentNode = ifStatement.parent;
64182
65350
  if (!isNodeOfType(parentNode, "BlockStatement")) return [];
@@ -64189,24 +65357,24 @@ const findFollowingReturnedValues = (ifStatement) => {
64189
65357
  }
64190
65358
  return returnedValues;
64191
65359
  };
64192
- const areConditionExpressionsEquivalent = (leftExpression, rightExpression) => {
65360
+ const areConditionExpressionsEquivalent = (leftExpression, rightExpression, scopes) => {
64193
65361
  const left = stripParenExpression(leftExpression);
64194
65362
  const right = stripParenExpression(rightExpression);
64195
- if (areExpressionsStructurallyEqual(left, right)) return true;
65363
+ if (areExpressionsStructurallyEqual(left, right)) return doEquivalentExpressionBindingsMatch(left, right, scopes);
64196
65364
  if (left.type !== right.type) return false;
64197
- if (isNodeOfType(left, "UnaryExpression") && isNodeOfType(right, "UnaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.argument, right.argument);
64198
- if (isNodeOfType(left, "LogicalExpression") && isNodeOfType(right, "LogicalExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left) && areConditionExpressionsEquivalent(left.right, right.right);
64199
- if (isNodeOfType(left, "BinaryExpression") && isNodeOfType(right, "BinaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left) && areConditionExpressionsEquivalent(left.right, right.right);
65365
+ if (isNodeOfType(left, "UnaryExpression") && isNodeOfType(right, "UnaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.argument, right.argument, scopes);
65366
+ if (isNodeOfType(left, "LogicalExpression") && isNodeOfType(right, "LogicalExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left, scopes) && areConditionExpressionsEquivalent(left.right, right.right, scopes);
65367
+ if (isNodeOfType(left, "BinaryExpression") && isNodeOfType(right, "BinaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left, scopes) && areConditionExpressionsEquivalent(left.right, right.right, scopes);
64200
65368
  return false;
64201
65369
  };
64202
- const areReturnTreesEquivalent = (leftStatement, rightStatement) => {
65370
+ const areReturnTreesEquivalent = (leftStatement, rightStatement, scopes) => {
64203
65371
  if (!leftStatement || !rightStatement) return leftStatement === rightStatement;
64204
- if (isNodeOfType(leftStatement, "ReturnStatement") && isNodeOfType(rightStatement, "ReturnStatement")) return areRenderedBranchesEquivalent(leftStatement.argument, rightStatement.argument);
64205
- if (isNodeOfType(leftStatement, "IfStatement") && isNodeOfType(rightStatement, "IfStatement")) return areConditionExpressionsEquivalent(leftStatement.test, rightStatement.test) && areReturnTreesEquivalent(leftStatement.consequent, rightStatement.consequent) && areReturnTreesEquivalent(leftStatement.alternate, rightStatement.alternate);
65372
+ if (isNodeOfType(leftStatement, "ReturnStatement") && isNodeOfType(rightStatement, "ReturnStatement")) return areRenderedBranchesEquivalent(leftStatement.argument, rightStatement.argument, scopes);
65373
+ 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);
64206
65374
  if (!isNodeOfType(leftStatement, "BlockStatement") || !isNodeOfType(rightStatement, "BlockStatement")) return false;
64207
65375
  const leftReturningStatements = leftStatement.body.filter((statement) => getReturnedValues(statement).length > 0);
64208
65376
  const rightReturningStatements = rightStatement.body.filter((statement) => getReturnedValues(statement).length > 0);
64209
- return leftReturningStatements.length === rightReturningStatements.length && leftReturningStatements.every((statement, index) => areReturnTreesEquivalent(statement, rightReturningStatements[index]));
65377
+ return leftReturningStatements.length === rightReturningStatements.length && leftReturningStatements.every((statement, index) => areReturnTreesEquivalent(statement, rightReturningStatements[index], scopes));
64210
65378
  };
64211
65379
  const isStructuralRenderedValue = (node) => {
64212
65380
  if (!node) return false;
@@ -64230,19 +65398,22 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
64230
65398
  if (isTestlikeFilename(context.filename)) return {};
64231
65399
  if (classifyReactNativeFileTarget(context) === "react-native") return {};
64232
65400
  let fileHasUseClientDirective = false;
65401
+ let fileHasExplicitReactRuntimeReference = false;
64233
65402
  let fileIsEmailTemplate = false;
64234
65403
  const reportedNodes = /* @__PURE__ */ new Set();
64235
- const reportHydrationBranch = (conditionNode, leftBranch, rightBranch, requiresRenderedContext) => {
65404
+ const reportHydrationBranch = (conditionNode, leftBranch, rightBranch, requiresRenderedContext, hasProvenRenderedConsumer = false) => {
64236
65405
  const conditionMatch = matchHydrationCondition(conditionNode, context);
64237
65406
  if (!conditionMatch) return;
64238
65407
  const { predicateMatch, predicateNode } = conditionMatch;
64239
65408
  if (reportedNodes.has(predicateNode)) return;
64240
- if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
64241
- const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes);
65409
+ if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch, context.scopes)) return;
65410
+ const enclosingFunction = findEnclosingFunction$1(conditionNode);
65411
+ const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes) ?? (enclosingFunction ? findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) : null);
64242
65412
  if (!componentOrHookNode) return;
64243
- if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
64244
- if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes)) return;
64245
- if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
65413
+ const hasRenderedLocalFunctionConsumer = Boolean(enclosingFunction && enclosingFunction !== componentOrHookNode && findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) === componentOrHookNode);
65414
+ if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective) && !fileHasExplicitReactRuntimeReference) return;
65415
+ if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes) && !hasRenderedLocalFunctionConsumer) return;
65416
+ if (!hasProvenRenderedConsumer && !(requiresRenderedContext ? isPotentiallyRenderedValue(leftBranch, context.scopes) : isRenderedValue(leftBranch, context.scopes)) && (!rightBranch || !(requiresRenderedContext ? isPotentiallyRenderedValue(rightBranch, context.scopes) : isRenderedValue(rightBranch, context.scopes)))) {
64246
65417
  const attribute = findEnclosingJsxAttribute(conditionNode);
64247
65418
  if (!attribute || isEventHandlerAttribute(attribute)) return;
64248
65419
  }
@@ -64261,28 +65432,31 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
64261
65432
  return {
64262
65433
  Program(node) {
64263
65434
  fileHasUseClientDirective = hasDirective(node, "use client");
65435
+ fileHasExplicitReactRuntimeReference = containsExplicitReactRuntimeReference(node, context.scopes);
64264
65436
  fileIsEmailTemplate = hasEmailTemplateImport(node);
64265
65437
  },
64266
65438
  ConditionalExpression(node) {
64267
65439
  reportHydrationBranch(node.test, node.consequent, node.alternate, true);
65440
+ const componentOrHookNode = findRenderPhaseComponentOrHook(node, context.scopes);
65441
+ if (componentOrHookNode && isReturnedUseStateInitializerRendered(node, componentOrHookNode, context.scopes)) reportHydrationBranch(node.test, node.consequent, node.alternate, false, true);
64268
65442
  },
64269
65443
  LogicalExpression(node) {
64270
65444
  if (node.operator !== "&&" && node.operator !== "||") return;
64271
- const renderedValue = node.operator === "&&" ? findRenderedValueInAndBranch(node.right) : isRenderedValue(node.right) ? node.right : null;
65445
+ const renderedValue = node.operator === "&&" ? findRenderedValueInAndBranch(node.right, context.scopes) : isPotentiallyRenderedValue(node.right, context.scopes) ? node.right : null;
64272
65446
  if (!renderedValue) return;
64273
65447
  reportHydrationBranch(node, renderedValue, null, true);
64274
65448
  },
64275
65449
  IfStatement(node) {
64276
- if (node.alternate && areReturnTreesEquivalent(node.consequent, node.alternate)) return;
65450
+ if (node.alternate && areReturnTreesEquivalent(node.consequent, node.alternate, context.scopes)) return;
64277
65451
  const consequentValues = getReturnedValues(node.consequent);
64278
65452
  const alternateValues = node.alternate ? getReturnedValues(node.alternate) : findFollowingReturnedValues(node);
64279
65453
  if (consequentValues.length === 0 || alternateValues.length === 0) return;
64280
- const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes);
64281
- if (!componentOrHookNode) return;
64282
65454
  const enclosingFunction = findEnclosingFunction$1(node);
64283
- if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes))) return;
65455
+ const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes) ?? (enclosingFunction ? findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) : null);
65456
+ if (!componentOrHookNode) return;
65457
+ if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes) && findComponentRenderingLocalFunctionResult(enclosingFunction, context.scopes) !== componentOrHookNode)) return;
64284
65458
  for (const consequentValue of consequentValues) for (const alternateValue of alternateValues) {
64285
- if (!isRenderedValue(consequentValue) && !isRenderedValue(alternateValue)) continue;
65459
+ if (!isRenderedValue(consequentValue, context.scopes) && !isRenderedValue(alternateValue, context.scopes)) continue;
64286
65460
  reportHydrationBranch(node.test, consequentValue, alternateValue, false);
64287
65461
  }
64288
65462
  }
@@ -66016,6 +67190,124 @@ const isInsideSnapshotHelper = (node) => {
66016
67190
  }
66017
67191
  return false;
66018
67192
  };
67193
+ const findEnclosingNextjsPageDataFunction = (node) => {
67194
+ let outermostFunction = null;
67195
+ let cursor = node.parent;
67196
+ while (cursor) {
67197
+ if (isFunctionLike$1(cursor)) outermostFunction = cursor;
67198
+ if (isNodeOfType(cursor, "Program")) {
67199
+ if (!outermostFunction) return null;
67200
+ for (const exportName of NEXTJS_PAGE_DATA_EXPORT_NAMES) {
67201
+ const exportedValue = findExportedValue(cursor, exportName);
67202
+ if (exportedValue && isAstDescendant(outermostFunction, exportedValue)) return outermostFunction;
67203
+ }
67204
+ return null;
67205
+ }
67206
+ cursor = cursor.parent ?? null;
67207
+ }
67208
+ return null;
67209
+ };
67210
+ const findConditionalReturnExpressionRoot = (node) => {
67211
+ let expressionRoot = findTransparentExpressionRoot(node);
67212
+ while (expressionRoot.parent && isNodeOfType(expressionRoot.parent, "ConditionalExpression") && (expressionRoot.parent.consequent === expressionRoot || expressionRoot.parent.alternate === expressionRoot)) expressionRoot = findTransparentExpressionRoot(expressionRoot.parent);
67213
+ return expressionRoot;
67214
+ };
67215
+ const isReturnedPageDataResultBinding = (returnExpression, pageDataFunction, context) => {
67216
+ const declarator = returnExpression.parent;
67217
+ if (!isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== returnExpression || !isNodeOfType(declarator.id, "Identifier") || findEnclosingFunction$1(declarator) !== pageDataFunction) return false;
67218
+ const bindingSymbol = context.scopes.symbolFor(declarator.id);
67219
+ if (!bindingSymbol || bindingSymbol.references.length !== 1) return false;
67220
+ const referenceRoot = findTransparentExpressionRoot(bindingSymbol.references[0].identifier);
67221
+ const returnStatement = referenceRoot.parent;
67222
+ return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === referenceRoot && findEnclosingFunction$1(returnStatement) === pageDataFunction;
67223
+ };
67224
+ const isSameShorthandPropertyValue = (node, property) => property.shorthand && (node === property.key || node === property.value);
67225
+ const isValueForwardedThroughLiteralStructure = (node, structure) => {
67226
+ const strippedNode = stripParenExpression(node);
67227
+ const strippedStructure = stripParenExpression(structure);
67228
+ if (strippedNode === strippedStructure) return true;
67229
+ if (isNodeOfType(strippedStructure, "ConditionalExpression")) return isValueForwardedThroughLiteralStructure(strippedNode, strippedStructure.consequent) || isValueForwardedThroughLiteralStructure(strippedNode, strippedStructure.alternate);
67230
+ if (isNodeOfType(strippedStructure, "ArrayExpression")) return strippedStructure.elements.some((element) => element && !isNodeOfType(element, "SpreadElement") && isValueForwardedThroughLiteralStructure(strippedNode, element));
67231
+ if (!isNodeOfType(strippedStructure, "ObjectExpression")) return false;
67232
+ return strippedStructure.properties.some((property) => {
67233
+ if (isNodeOfType(property, "SpreadElement")) return isValueForwardedThroughLiteralStructure(strippedNode, property.argument);
67234
+ if (!isNodeOfType(property, "Property")) return false;
67235
+ if (isValueForwardedThroughLiteralStructure(strippedNode, property.value)) return true;
67236
+ return isSameShorthandPropertyValue(strippedNode, property);
67237
+ });
67238
+ };
67239
+ const isValueForwardedToPropertyValue = (node, property) => {
67240
+ const directValue = findConditionalReturnExpressionRoot(node);
67241
+ if (isValueForwardedThroughLiteralStructure(directValue, property.value)) return true;
67242
+ return isSameShorthandPropertyValue(directValue, property);
67243
+ };
67244
+ const isInsideReturnedNextjsProps = (node, pageDataFunction, context) => {
67245
+ let cursor = node.parent;
67246
+ while (cursor && cursor !== pageDataFunction) {
67247
+ if (isNodeOfType(cursor, "Property") && getStaticPropertyKeyName(cursor, { allowComputedString: true }) === "props" && isValueForwardedToPropertyValue(node, cursor)) {
67248
+ const propertyContainer = cursor.parent;
67249
+ if (!propertyContainer) return false;
67250
+ const returnExpression = findConditionalReturnExpressionRoot(propertyContainer);
67251
+ const returnStatement = returnExpression.parent;
67252
+ if (isNodeOfType(returnStatement, "ReturnStatement") && findEnclosingFunction$1(returnStatement) === pageDataFunction) return true;
67253
+ if (isNodeOfType(pageDataFunction, "ArrowFunctionExpression") && !isNodeOfType(pageDataFunction.body, "BlockStatement") && stripParenExpression(pageDataFunction.body) === stripParenExpression(returnExpression)) return true;
67254
+ if (isReturnedPageDataResultBinding(returnExpression, pageDataFunction, context)) return true;
67255
+ }
67256
+ cursor = cursor.parent ?? null;
67257
+ }
67258
+ return false;
67259
+ };
67260
+ const isExpressionReturnedByFunction = (node, functionNode) => {
67261
+ const returnExpression = findConditionalReturnExpressionRoot(node);
67262
+ if (isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode.body, "BlockStatement")) return stripParenExpression(functionNode.body) === stripParenExpression(returnExpression);
67263
+ const returnStatement = returnExpression.parent;
67264
+ return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === returnExpression && findEnclosingFunction$1(returnStatement) === functionNode;
67265
+ };
67266
+ const isValueForwardedToBindingInitializer = (node, bindingInitializer) => {
67267
+ if (isValueForwardedThroughLiteralStructure(findConditionalReturnExpressionRoot(node), bindingInitializer)) return true;
67268
+ const initializer = stripParenExpression(bindingInitializer);
67269
+ if (!isNodeOfType(initializer, "CallExpression")) return false;
67270
+ const callee = stripParenExpression(initializer.callee);
67271
+ return isFunctionLike$1(callee) && isExpressionReturnedByFunction(node, callee);
67272
+ };
67273
+ const findPageDataResultBinding = (node) => {
67274
+ let cursor = node.parent;
67275
+ while (cursor) {
67276
+ if (isNodeOfType(cursor, "VariableDeclarator")) {
67277
+ if (cursor.init && isNodeOfType(cursor.id, "Identifier") && isValueForwardedToBindingInitializer(node, cursor.init)) return cursor.id;
67278
+ return null;
67279
+ }
67280
+ cursor = cursor.parent ?? null;
67281
+ }
67282
+ return null;
67283
+ };
67284
+ const isUsedToSerializeNextjsPageProps = (node, context) => {
67285
+ if (!isInProjectDirectory(context, "pages") || isInProjectDirectory(context, "pages/api")) return false;
67286
+ const pageDataFunction = findEnclosingNextjsPageDataFunction(node);
67287
+ if (!pageDataFunction) return false;
67288
+ if (isInsideReturnedNextjsProps(node, pageDataFunction, context)) return true;
67289
+ const bindingIdentifier = findPageDataResultBinding(node);
67290
+ const bindingSymbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
67291
+ if (!bindingSymbol) return false;
67292
+ const aliasSymbols = collectConstAliasSymbols(bindingSymbol, context.scopes);
67293
+ const aliasSymbolIds = new Set(aliasSymbols.map((aliasSymbol) => aliasSymbol.id));
67294
+ let hasPagePropsReference = false;
67295
+ for (const aliasSymbol of aliasSymbols) for (const reference of aliasSymbol.references) {
67296
+ if (findEnclosingFunction$1(reference.identifier) !== pageDataFunction) return false;
67297
+ if (isInsideReturnedNextjsProps(reference.identifier, pageDataFunction, context)) {
67298
+ hasPagePropsReference = true;
67299
+ continue;
67300
+ }
67301
+ const referenceRoot = findTransparentExpressionRoot(reference.identifier);
67302
+ const declarator = referenceRoot.parent;
67303
+ if (isNodeOfType(declarator, "VariableDeclarator") && declarator.init === referenceRoot && isNodeOfType(declarator.id, "Identifier")) {
67304
+ const aliasSymbolForReference = context.scopes.symbolFor(declarator.id);
67305
+ if (aliasSymbolForReference && aliasSymbolIds.has(aliasSymbolForReference.id)) continue;
67306
+ }
67307
+ return false;
67308
+ }
67309
+ return hasPagePropsReference;
67310
+ };
66019
67311
  const noJsonParseStringifyClone = defineRule({
66020
67312
  id: "no-json-parse-stringify-clone",
66021
67313
  title: "JSON parse/stringify deep clone",
@@ -66033,6 +67325,7 @@ const noJsonParseStringifyClone = defineRule({
66033
67325
  if (isInsideSnapshotHelper(node)) return;
66034
67326
  if (isAssignedToNormalizationBinding(node)) return;
66035
67327
  if (isCatchParameterRoundTrip(firstArgument)) return;
67328
+ if (isUsedToSerializeNextjsPageProps(node, context)) return;
66036
67329
  context.report({
66037
67330
  node,
66038
67331
  message: MESSAGE$35
@@ -67448,6 +68741,77 @@ const isCancellationGuardTest = (test) => {
67448
68741
  });
67449
68742
  return matches;
67450
68743
  };
68744
+ const getReactRefCurrent = (expression, context) => {
68745
+ const stripped = stripParenExpression(expression);
68746
+ if (!isNodeOfType(stripped, "MemberExpression") || getStaticPropertyName(stripped) !== "current") return null;
68747
+ const receiver = stripParenExpression(stripped.object);
68748
+ if (!isNodeOfType(receiver, "Identifier")) return null;
68749
+ const binding = findVariableInitializer(receiver, receiver.name);
68750
+ const initializer = binding?.initializer ? stripParenExpression(binding.initializer) : null;
68751
+ return initializer && isNodeOfType(initializer, "CallExpression") && isReactApiCall(initializer, USE_REF_HOOK_NAMES$1, context.scopes, {
68752
+ allowGlobalReactNamespace: true,
68753
+ allowUnboundBareCalls: true
68754
+ }) ? stripped : null;
68755
+ };
68756
+ const getStableOwnershipToken = (expression, context) => {
68757
+ const stripped = stripParenExpression(expression);
68758
+ if (!isNodeOfType(stripped, "Identifier")) return null;
68759
+ const symbol = context.scopes.symbolFor(stripped);
68760
+ const initializer = symbol?.initializer ? stripParenExpression(symbol.initializer) : null;
68761
+ const isStableAsyncIdentity = Boolean(initializer && isNodeOfType(initializer, "ObjectExpression")) || Boolean(initializer && getReactRefCurrent(initializer, context)) || Boolean(initializer && isNodeOfType(initializer, "UpdateExpression") && initializer.operator === "++" && getReactRefCurrent(initializer.argument, context));
68762
+ return symbol && symbol.kind === "const" && symbol.references.every((reference) => reference.flag === "read") && isStableAsyncIdentity ? stripped : null;
68763
+ };
68764
+ const getAsyncOwnershipComparison = (test, context) => {
68765
+ const stripped = stripParenExpression(test);
68766
+ if (!isNodeOfType(stripped, "BinaryExpression")) return null;
68767
+ const leftRef = getReactRefCurrent(stripped.left, context);
68768
+ const rightRef = getReactRefCurrent(stripped.right, context);
68769
+ const leftToken = getStableOwnershipToken(stripped.left, context);
68770
+ const rightToken = getStableOwnershipToken(stripped.right, context);
68771
+ if (stripped.operator === "===" || stripped.operator === "==") {
68772
+ if (leftRef && rightToken) return {
68773
+ refCurrent: leftRef,
68774
+ token: rightToken,
68775
+ mode: "owns",
68776
+ isOrdered: false
68777
+ };
68778
+ if (rightRef && leftToken) return {
68779
+ refCurrent: rightRef,
68780
+ token: leftToken,
68781
+ mode: "owns",
68782
+ isOrdered: false
68783
+ };
68784
+ return null;
68785
+ }
68786
+ if (stripped.operator === "!==" || stripped.operator === "!=") {
68787
+ if (leftRef && rightToken) return {
68788
+ refCurrent: leftRef,
68789
+ token: rightToken,
68790
+ mode: "lost",
68791
+ isOrdered: false
68792
+ };
68793
+ if (rightRef && leftToken) return {
68794
+ refCurrent: rightRef,
68795
+ token: leftToken,
68796
+ mode: "lost",
68797
+ isOrdered: false
68798
+ };
68799
+ return null;
68800
+ }
68801
+ if (stripped.operator === "<=" && leftRef && rightToken) return {
68802
+ refCurrent: leftRef,
68803
+ token: rightToken,
68804
+ mode: "owns",
68805
+ isOrdered: true
68806
+ };
68807
+ if (stripped.operator === ">=" && rightRef && leftToken) return {
68808
+ refCurrent: rightRef,
68809
+ token: leftToken,
68810
+ mode: "owns",
68811
+ isOrdered: true
68812
+ };
68813
+ return null;
68814
+ };
67451
68815
  const dedupeCatchPathStates = (states) => {
67452
68816
  const statesByKey = /* @__PURE__ */ new Map();
67453
68817
  for (const state of states) statesByKey.set(`${Number(state.isCleared)}:${Number(state.isCancellationPath)}`, state);
@@ -67780,7 +69144,221 @@ const isInsideTryFinalizer = (node, tryStatement) => {
67780
69144
  }
67781
69145
  return false;
67782
69146
  };
67783
- const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, acceptedCleanupAssignments, context) => {
69147
+ const getDirectBlockEntry = (node, functionNode) => {
69148
+ let entry = node;
69149
+ let cursor = node.parent;
69150
+ while (cursor && cursor !== functionNode) {
69151
+ if (isNodeOfType(cursor, "BlockStatement")) return {
69152
+ block: cursor,
69153
+ entry
69154
+ };
69155
+ entry = cursor;
69156
+ cursor = cursor.parent ?? null;
69157
+ }
69158
+ return null;
69159
+ };
69160
+ const claimPrecedesTruthySet = (claimNode, truthySet, firstRiskyAwait, functionNode, context) => {
69161
+ const claimStart = getNodeStart$1(claimNode);
69162
+ if (claimStart === null || claimStart >= firstRiskyAwait.start || truthySet.start >= firstRiskyAwait.start) return false;
69163
+ const claimEntry = getDirectBlockEntry(claimNode, functionNode);
69164
+ const truthyEntry = getDirectBlockEntry(truthySet.node, functionNode);
69165
+ if (!claimEntry || !truthyEntry || claimEntry.block !== truthyEntry.block) return false;
69166
+ let claimCursor = claimNode.parent;
69167
+ while (claimCursor && claimCursor !== claimEntry.block) {
69168
+ if (isNodeOfType(claimCursor, "IfStatement") || isNodeOfType(claimCursor, "SwitchCase") || isNodeOfType(claimCursor, "ConditionalExpression") || isNodeOfType(claimCursor, "LogicalExpression") || isNodeOfType(claimCursor, "ForStatement") || isNodeOfType(claimCursor, "ForInStatement") || isNodeOfType(claimCursor, "ForOfStatement") || isNodeOfType(claimCursor, "WhileStatement") || isNodeOfType(claimCursor, "DoWhileStatement")) return false;
69169
+ claimCursor = claimCursor.parent ?? null;
69170
+ }
69171
+ const claimIndex = claimEntry.block.body.findIndex((statement) => statement === claimEntry.entry);
69172
+ const truthyIndex = claimEntry.block.body.findIndex((statement) => statement === truthyEntry.entry);
69173
+ if (claimIndex === -1 || truthyIndex === -1 || claimIndex >= truthyIndex) return false;
69174
+ return claimEntry.block.body.slice(claimIndex + 1, truthyIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, functionNode, context));
69175
+ };
69176
+ const getOwningFunction = (functionNode) => {
69177
+ let ownerFunction = functionNode;
69178
+ let cursor = functionNode.parent;
69179
+ while (cursor) {
69180
+ if (isFunctionLike$1(cursor)) ownerFunction = cursor;
69181
+ cursor = cursor.parent ?? null;
69182
+ }
69183
+ return ownerFunction;
69184
+ };
69185
+ const isEffectInvalidationPairedWithReset = (writeNode, truthySets, context) => {
69186
+ const truthyCall = truthySets[0]?.node;
69187
+ if (!truthyCall || !isNodeOfType(truthyCall, "CallExpression")) return false;
69188
+ const setter = getSetterBooleanValue(truthyCall, context);
69189
+ if (!setter) return false;
69190
+ let effectCallback = writeNode.parent;
69191
+ while (effectCallback && !isFunctionLike$1(effectCallback)) effectCallback = effectCallback.parent ?? null;
69192
+ if (!effectCallback || !isEffectCallback(effectCallback, context)) return false;
69193
+ if (!isUnconditionallyExecutedWithinFunction(writeNode, effectCallback, context)) return false;
69194
+ const writeEntry = getDirectBlockEntry(writeNode, effectCallback);
69195
+ if (!writeEntry) return false;
69196
+ let isPaired = false;
69197
+ walkOwnFunctionScope(effectCallback, (candidate) => {
69198
+ if (isPaired || !isNodeOfType(candidate, "CallExpression")) return;
69199
+ const candidateSetter = getSetterBooleanValue(candidate, context);
69200
+ if (candidateSetter?.setterKey !== setter.setterKey || candidateSetter.value || !isUnconditionallyExecutedWithinFunction(candidate, effectCallback, context)) return;
69201
+ const resetEntry = getDirectBlockEntry(candidate, effectCallback);
69202
+ if (!resetEntry || resetEntry.block !== writeEntry.block) return;
69203
+ const writeIndex = writeEntry.block.body.findIndex((statement) => statement === writeEntry.entry);
69204
+ const resetIndex = resetEntry.block.body.findIndex((statement) => statement === resetEntry.entry);
69205
+ if (writeIndex === -1 || resetIndex === -1) return;
69206
+ if (resetIndex <= writeIndex) {
69207
+ isPaired = true;
69208
+ return false;
69209
+ }
69210
+ isPaired = writeEntry.block.body.slice(writeIndex + 1, resetIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, effectCallback, context));
69211
+ return isPaired ? false : void 0;
69212
+ });
69213
+ return isPaired;
69214
+ };
69215
+ const isUnconditionalReturnBranch = (statement) => {
69216
+ if (isNodeOfType(statement, "ReturnStatement")) return true;
69217
+ return Boolean(isNodeOfType(statement, "BlockStatement") && statement.body.length === 1 && isNodeOfType(statement.body[0], "ReturnStatement"));
69218
+ };
69219
+ const findSingleFlightSnapshotClaim = (tokenInitializer, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
69220
+ const snapshotEntry = getDirectBlockEntry(tokenInitializer, functionNode);
69221
+ const resetEntry = getDirectBlockEntry(resetNode, functionNode);
69222
+ if (!snapshotEntry || !resetEntry) return null;
69223
+ const claimCandidates = [];
69224
+ const releaseCandidates = [];
69225
+ walkOwnFunctionScope(functionNode, (candidate) => {
69226
+ if (!isNodeOfType(candidate, "AssignmentExpression") || candidate.operator !== "=" || !getReactRefCurrent(candidate.left, context)) return;
69227
+ const assignedValue = stripParenExpression(candidate.right);
69228
+ if (!isNodeOfType(assignedValue, "Literal") || typeof assignedValue.value !== "boolean") return;
69229
+ const candidateKey = serializeReferenceKey({
69230
+ node: candidate.left,
69231
+ scopes: context.scopes
69232
+ });
69233
+ if (!candidateKey) return;
69234
+ if (!assignedValue.value) {
69235
+ if (getDirectBlockEntry(candidate, functionNode)?.block === resetEntry.block) releaseCandidates.push(candidate);
69236
+ return;
69237
+ }
69238
+ if (!truthySets.some((truthySet) => claimPrecedesTruthySet(candidate, truthySet, firstRiskyAwait, functionNode, context))) return;
69239
+ const candidateEntry = getDirectBlockEntry(candidate, functionNode);
69240
+ if (!candidateEntry || candidateEntry.block !== snapshotEntry.block) return;
69241
+ const candidateIndex = candidateEntry.block.body.findIndex((statement) => statement === candidateEntry.entry);
69242
+ const snapshotIndex = candidateEntry.block.body.findIndex((statement) => statement === snapshotEntry.entry);
69243
+ if (candidateIndex === -1 || snapshotIndex === -1 || candidateIndex >= snapshotIndex) return;
69244
+ const guardIndex = candidateEntry.block.body.findLastIndex((statement, statementIndex) => {
69245
+ if (statementIndex >= candidateIndex || !isNodeOfType(statement, "IfStatement") || statement.alternate !== null || !isUnconditionalReturnBranch(statement.consequent)) return false;
69246
+ return serializeReferenceKey({
69247
+ node: stripParenExpression(statement.test),
69248
+ scopes: context.scopes
69249
+ }) === candidateKey;
69250
+ });
69251
+ if (guardIndex === -1 || !candidateEntry.block.body.slice(guardIndex + 1, candidateIndex).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, functionNode, context))) return;
69252
+ claimCandidates.push(candidate);
69253
+ });
69254
+ const claim = claimCandidates.find((claimCandidate) => {
69255
+ const candidateKey = serializeReferenceKey({
69256
+ node: claimCandidate.left,
69257
+ scopes: context.scopes
69258
+ });
69259
+ return releaseCandidates.some((releaseCandidate) => serializeReferenceKey({
69260
+ node: releaseCandidate.left,
69261
+ scopes: context.scopes
69262
+ }) === candidateKey);
69263
+ });
69264
+ if (!claim) return null;
69265
+ const claimKey = serializeReferenceKey({
69266
+ node: claim.left,
69267
+ scopes: context.scopes
69268
+ });
69269
+ const release = releaseCandidates.find((releaseCandidate) => serializeReferenceKey({
69270
+ node: releaseCandidate.left,
69271
+ scopes: context.scopes
69272
+ }) === claimKey);
69273
+ if (!claimKey || !release) return null;
69274
+ const releaseEntry = getDirectBlockEntry(release, functionNode);
69275
+ if (!releaseEntry || releaseEntry.block !== resetEntry.block) return null;
69276
+ const releaseIndex = resetEntry.block.body.findIndex((statement) => statement === releaseEntry.entry);
69277
+ const resetIndex = resetEntry.block.body.findIndex((statement) => statement === resetEntry.entry);
69278
+ if (releaseIndex === -1 || resetIndex === -1 || !resetEntry.block.body.slice(Math.min(releaseIndex, resetIndex) + 1, Math.max(releaseIndex, resetIndex)).every((statement) => !subtreeHasAbruptSynchronousOperation(statement, functionNode, context))) return null;
69279
+ let didFindUnsafeWrite = false;
69280
+ walkAst(getOwningFunction(functionNode), (candidate) => {
69281
+ if (didFindUnsafeWrite || candidate === claim || candidate === release) return;
69282
+ const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
69283
+ if (writeTarget && serializeReferenceKey({
69284
+ node: writeTarget,
69285
+ scopes: context.scopes
69286
+ }) === claimKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindUnsafeWrite = true;
69287
+ });
69288
+ return didFindUnsafeWrite ? null : claim;
69289
+ };
69290
+ const findOwnershipClaim = (comparison, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
69291
+ const refKey = serializeReferenceKey({
69292
+ node: comparison.refCurrent,
69293
+ scopes: context.scopes
69294
+ });
69295
+ const tokenKey = serializeReferenceKey({
69296
+ node: comparison.token,
69297
+ scopes: context.scopes
69298
+ });
69299
+ if (!refKey || !tokenKey) return null;
69300
+ const candidates = [];
69301
+ const tokenSymbol = context.scopes.symbolFor(comparison.token);
69302
+ const tokenInitializer = tokenSymbol?.initializer ? stripParenExpression(tokenSymbol.initializer) : null;
69303
+ if (comparison.isOrdered && !isNodeOfType(tokenInitializer, "UpdateExpression")) return null;
69304
+ if (tokenInitializer && isNodeOfType(tokenInitializer, "UpdateExpression") && tokenInitializer.operator === "++" && serializeReferenceKey({
69305
+ node: tokenInitializer.argument,
69306
+ scopes: context.scopes
69307
+ }) === refKey) candidates.push(tokenInitializer);
69308
+ if (tokenInitializer && getReactRefCurrent(tokenInitializer, context) && serializeReferenceKey({
69309
+ node: tokenInitializer,
69310
+ scopes: context.scopes
69311
+ }) === refKey) {
69312
+ const singleFlightClaim = findSingleFlightSnapshotClaim(tokenInitializer, functionNode, truthySets, firstRiskyAwait, resetNode, context);
69313
+ if (singleFlightClaim) candidates.push(singleFlightClaim);
69314
+ }
69315
+ if (tokenInitializer && isNodeOfType(tokenInitializer, "UpdateExpression")) {
69316
+ const generationKey = serializeReferenceKey({
69317
+ node: tokenInitializer.argument,
69318
+ scopes: context.scopes
69319
+ });
69320
+ if (generationKey && generationKey === refKey) {
69321
+ const ownerFunction = getOwningFunction(functionNode);
69322
+ let didFindOtherGenerationWrite = false;
69323
+ walkAst(ownerFunction, (candidate) => {
69324
+ if (didFindOtherGenerationWrite || candidate === tokenInitializer) return;
69325
+ const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
69326
+ if (writeTarget && serializeReferenceKey({
69327
+ node: writeTarget,
69328
+ scopes: context.scopes
69329
+ }) === generationKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindOtherGenerationWrite = true;
69330
+ });
69331
+ if (didFindOtherGenerationWrite) return null;
69332
+ }
69333
+ }
69334
+ walkOwnFunctionScope(functionNode, (candidate) => {
69335
+ if (!isNodeOfType(candidate, "AssignmentExpression") || candidate.operator !== "=") return;
69336
+ if (serializeReferenceKey({
69337
+ node: candidate.left,
69338
+ scopes: context.scopes
69339
+ }) === refKey && serializeReferenceKey({
69340
+ node: candidate.right,
69341
+ scopes: context.scopes
69342
+ }) === tokenKey) candidates.push(candidate);
69343
+ });
69344
+ const claim = candidates.find((candidate) => truthySets.some((truthySet) => claimPrecedesTruthySet(candidate, truthySet, firstRiskyAwait, functionNode, context)));
69345
+ if (!claim) return null;
69346
+ let didFindOtherWrite = false;
69347
+ walkAst(getOwningFunction(functionNode), (candidate) => {
69348
+ if (didFindOtherWrite || candidate === claim) return;
69349
+ const writeTarget = isNodeOfType(candidate, "AssignmentExpression") ? candidate.left : isNodeOfType(candidate, "UpdateExpression") || isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete" ? candidate.argument : null;
69350
+ if (writeTarget && serializeReferenceKey({
69351
+ node: writeTarget,
69352
+ scopes: context.scopes
69353
+ }) === refKey && !isEffectInvalidationPairedWithReset(candidate, truthySets, context)) didFindOtherWrite = true;
69354
+ });
69355
+ return didFindOtherWrite ? null : claim;
69356
+ };
69357
+ const isClaimedOwnershipComparison = (test, expectedMode, functionNode, truthySets, firstRiskyAwait, resetNode, context) => {
69358
+ const comparison = getAsyncOwnershipComparison(test, context);
69359
+ return Boolean(comparison && comparison.mode === expectedMode && findOwnershipClaim(comparison, functionNode, truthySets, firstRiskyAwait, resetNode, context));
69360
+ };
69361
+ const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, acceptedAssignments, context) => {
67784
69362
  let didFindOtherWrite = false;
67785
69363
  walkAst(effectCallback, (candidate) => {
67786
69364
  if (didFindOtherWrite) return false;
@@ -67788,7 +69366,7 @@ const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, accepted
67788
69366
  if (serializeReferenceKey({
67789
69367
  node: candidate.left,
67790
69368
  scopes: context.scopes
67791
- }) === guardKey && !acceptedCleanupAssignments.has(candidate)) {
69369
+ }) === guardKey && !acceptedAssignments.has(candidate)) {
67792
69370
  didFindOtherWrite = true;
67793
69371
  return false;
67794
69372
  }
@@ -67804,48 +69382,103 @@ const hasLifecycleGuardWriteOutsideCleanup = (effectCallback, guardKey, accepted
67804
69382
  });
67805
69383
  return didFindOtherWrite;
67806
69384
  };
67807
- const isResetGuardedByCleanupBackedLifecycle = (resetNode, functionNode, context) => {
69385
+ const collectCleanupBackedLifecycleAssignments = (effectCallback, guardKey, context) => {
69386
+ const acceptedAssignments = /* @__PURE__ */ new Set();
69387
+ for (const cleanupFunction of collectReturnedCleanupFunctions(effectCallback, context.scopes)) walkOwnFunctionScope(cleanupFunction, (cleanupNode) => {
69388
+ const assignedValue = isNodeOfType(cleanupNode, "AssignmentExpression") ? stripParenExpression(cleanupNode.right) : null;
69389
+ if (!isNodeOfType(cleanupNode, "AssignmentExpression") || cleanupNode.operator !== "=" || !isNodeOfType(assignedValue, "Literal") || assignedValue.value !== false || serializeReferenceKey({
69390
+ node: cleanupNode.left,
69391
+ scopes: context.scopes
69392
+ }) !== guardKey || !isUnconditionallyExecutedWithinFunction(cleanupNode, cleanupFunction, context)) return;
69393
+ acceptedAssignments.add(cleanupNode);
69394
+ });
69395
+ if (acceptedAssignments.size === 0) return null;
69396
+ walkOwnFunctionScope(effectCallback, (effectNode) => {
69397
+ const assignedValue = isNodeOfType(effectNode, "AssignmentExpression") ? stripParenExpression(effectNode.right) : null;
69398
+ if (isNodeOfType(effectNode, "AssignmentExpression") && effectNode.operator === "=" && isNodeOfType(assignedValue, "Literal") && assignedValue.value === true && serializeReferenceKey({
69399
+ node: effectNode.left,
69400
+ scopes: context.scopes
69401
+ }) === guardKey && isUnconditionallyExecutedWithinFunction(effectNode, effectCallback, context)) acceptedAssignments.add(effectNode);
69402
+ });
69403
+ return acceptedAssignments;
69404
+ };
69405
+ const isEffectCallback = (node, context) => {
69406
+ const callbackRoot = findTransparentExpressionRoot(node);
69407
+ const callbackCall = callbackRoot.parent;
69408
+ return Boolean(callbackCall && isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments[0] === callbackRoot && isReactApiCall(callbackCall, EFFECT_HOOK_NAMES$6, context.scopes, {
69409
+ allowGlobalReactNamespace: true,
69410
+ allowUnboundBareCalls: true
69411
+ }));
69412
+ };
69413
+ const isCleanupBackedLifecycleGuard = (guardExpression, functionNode, context) => {
69414
+ const guardKey = serializeReferenceKey({
69415
+ node: guardExpression,
69416
+ scopes: context.scopes
69417
+ });
69418
+ if (!guardKey || !isInitiallyActiveLifecycleGuard(guardExpression, context)) return false;
69419
+ let ownerFunction = functionNode.parent;
69420
+ while (ownerFunction && !isFunctionLike$1(ownerFunction)) ownerFunction = ownerFunction.parent ?? null;
69421
+ if (!ownerFunction) return false;
69422
+ const effectCallbacks = [];
69423
+ if (isEffectCallback(ownerFunction, context)) effectCallbacks.push(ownerFunction);
69424
+ walkOwnFunctionScope(ownerFunction, (candidate) => {
69425
+ if (!isNodeOfType(candidate, "CallExpression")) return;
69426
+ if (!isReactApiCall(candidate, EFFECT_HOOK_NAMES$6, context.scopes, {
69427
+ allowGlobalReactNamespace: true,
69428
+ allowUnboundBareCalls: true
69429
+ })) return;
69430
+ const effectCallback = candidate.arguments[0];
69431
+ if (effectCallback && isFunctionLike$1(effectCallback)) effectCallbacks.push(effectCallback);
69432
+ });
69433
+ const acceptedAssignments = /* @__PURE__ */ new Set();
69434
+ for (const effectCallback of effectCallbacks) {
69435
+ const effectAssignments = collectCleanupBackedLifecycleAssignments(effectCallback, guardKey, context);
69436
+ if (!effectAssignments) continue;
69437
+ for (const assignment of effectAssignments) acceptedAssignments.add(assignment);
69438
+ }
69439
+ return Boolean(acceptedAssignments.size > 0 && !hasLifecycleGuardWriteOutsideCleanup(ownerFunction, guardKey, acceptedAssignments, context));
69440
+ };
69441
+ const collectLogicalOperands = (expression, operator) => {
69442
+ const stripped = stripParenExpression(expression);
69443
+ if (isNodeOfType(stripped, "LogicalExpression") && stripped.operator === operator) return [...collectLogicalOperands(stripped.left, operator), ...collectLogicalOperands(stripped.right, operator)];
69444
+ return [stripped];
69445
+ };
69446
+ const collectFinalizerGuardExpressions = (resetNode, protectingTry) => {
69447
+ const positive = [];
69448
+ const negative = [];
67808
69449
  let child = resetNode;
67809
69450
  let cursor = resetNode.parent;
67810
- let guardKey = null;
67811
- let guardExpression = null;
67812
- while (cursor && cursor !== functionNode) {
67813
- if (isNodeOfType(cursor, "IfStatement") && cursor.consequent === child && cursor.alternate === null) {
67814
- guardExpression = cursor.test;
67815
- guardKey = serializeReferenceKey({
67816
- node: cursor.test,
67817
- scopes: context.scopes
67818
- });
67819
- break;
67820
- }
69451
+ while (cursor && cursor !== protectingTry) {
69452
+ if (isNodeOfType(cursor, "IfStatement")) {
69453
+ if (cursor.consequent !== child || cursor.alternate !== null) return null;
69454
+ positive.push(...collectLogicalOperands(cursor.test, "&&"));
69455
+ } else if (isNodeOfType(cursor, "LogicalExpression")) {
69456
+ if (cursor.operator !== "&&" || cursor.right !== child) return null;
69457
+ positive.push(...collectLogicalOperands(cursor.left, "&&"));
69458
+ } else if (isNodeOfType(cursor, "BlockStatement")) {
69459
+ const childIndex = cursor.body.findIndex((statement) => statement === child);
69460
+ if (childIndex !== -1) for (const statement of cursor.body.slice(0, childIndex)) {
69461
+ if (!isNodeOfType(statement, "IfStatement") || statement.alternate !== null || !isUnconditionalReturnBranch(statement.consequent)) continue;
69462
+ negative.push(...collectLogicalOperands(statement.test, "||"));
69463
+ }
69464
+ } else if (isNodeOfType(cursor, "SwitchCase") || isNodeOfType(cursor, "ConditionalExpression") || isNodeOfType(cursor, "ForStatement") || isNodeOfType(cursor, "ForInStatement") || isNodeOfType(cursor, "ForOfStatement") || isNodeOfType(cursor, "WhileStatement") || isNodeOfType(cursor, "DoWhileStatement")) return null;
67821
69465
  child = cursor;
67822
69466
  cursor = cursor.parent ?? null;
67823
69467
  }
67824
- if (!guardKey || !guardExpression || !isInitiallyActiveLifecycleGuard(guardExpression, context)) return false;
67825
- cursor = functionNode.parent;
67826
- while (cursor) {
67827
- if (isFunctionLike$1(cursor)) {
67828
- const callbackRoot = findTransparentExpressionRoot(cursor);
67829
- const callbackCall = callbackRoot.parent;
67830
- if (Boolean(callbackCall && isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments[0] === callbackRoot && isReactApiCall(callbackCall, EFFECT_HOOK_NAMES$6, context.scopes, {
67831
- allowGlobalReactNamespace: true,
67832
- allowUnboundBareCalls: true
67833
- }))) {
67834
- const acceptedCleanupAssignments = /* @__PURE__ */ new Set();
67835
- for (const cleanupFunction of collectReturnedCleanupFunctions(cursor, context.scopes)) walkOwnFunctionScope(cleanupFunction, (cleanupNode) => {
67836
- const assignedValue = isNodeOfType(cleanupNode, "AssignmentExpression") ? stripParenExpression(cleanupNode.right) : null;
67837
- if (!isNodeOfType(cleanupNode, "AssignmentExpression") || cleanupNode.operator !== "=" || !isNodeOfType(assignedValue, "Literal") || assignedValue.value !== false || serializeReferenceKey({
67838
- node: cleanupNode.left,
67839
- scopes: context.scopes
67840
- }) !== guardKey || !isUnconditionallyExecutedWithinFunction(cleanupNode, cleanupFunction, context)) return;
67841
- acceptedCleanupAssignments.add(cleanupNode);
67842
- });
67843
- if (acceptedCleanupAssignments.size > 0 && !hasLifecycleGuardWriteOutsideCleanup(cursor, guardKey, acceptedCleanupAssignments, context)) return true;
67844
- }
67845
- }
67846
- cursor = cursor.parent ?? null;
67847
- }
67848
- return false;
69468
+ return cursor === protectingTry && positive.length + negative.length > 0 ? {
69469
+ positive,
69470
+ negative
69471
+ } : null;
69472
+ };
69473
+ const isPositiveFinalizerGuard = (expression, resetNode, functionNode, truthySets, firstRiskyAwait, context) => isCleanupBackedLifecycleGuard(expression, functionNode, context) || isClaimedOwnershipComparison(expression, "owns", functionNode, truthySets, firstRiskyAwait, resetNode, context);
69474
+ const isNegativeFinalizerGuard = (expression, resetNode, functionNode, truthySets, firstRiskyAwait, context) => {
69475
+ const stripped = stripParenExpression(expression);
69476
+ if (isNodeOfType(stripped, "UnaryExpression") && stripped.operator === "!") return isPositiveFinalizerGuard(stripped.argument, resetNode, functionNode, truthySets, firstRiskyAwait, context);
69477
+ return isClaimedOwnershipComparison(stripped, "lost", functionNode, truthySets, firstRiskyAwait, resetNode, context);
69478
+ };
69479
+ const isFinalizerResetProvablyGuarded = (resetNode, protectingTry, functionNode, truthySets, firstRiskyAwait, context) => {
69480
+ const guards = collectFinalizerGuardExpressions(resetNode, protectingTry);
69481
+ return Boolean(guards && guards.positive.every((guard) => isPositiveFinalizerGuard(guard, resetNode, functionNode, truthySets, firstRiskyAwait, context)) && guards.negative.every((guard) => isNegativeFinalizerGuard(guard, resetNode, functionNode, truthySets, firstRiskyAwait, context)));
67849
69482
  };
67850
69483
  const isAwaitInsideProtectedTry = (awaitNode, tryStatement) => {
67851
69484
  let child = awaitNode;
@@ -67951,7 +69584,13 @@ const analyzeFunction = (functionNode, context) => {
67951
69584
  const exceptionallyProtectedAwaits = collectExceptionallyProtectedAwaits(awaitSites, calls);
67952
69585
  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)));
67953
69586
  if (riskyAwaitsWithTruthySet.length === 0) continue;
67954
- 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)));
69587
+ const conditionalExceptionalResets = calls.filter((call) => {
69588
+ if (call.value || call.context === "plain" || call.isUnconditional || call.protectingTry === null) return false;
69589
+ const protectingTry = call.protectingTry;
69590
+ if (!isInsideTryFinalizer(call.node, protectingTry)) return true;
69591
+ const firstRiskyAwait = riskyAwaitsWithTruthySet.find((awaitSite) => isAwaitInsideProtectedTry(awaitSite.node, protectingTry));
69592
+ return !(firstRiskyAwait && isFinalizerResetProvablyGuarded(call.node, protectingTry, functionNode, truthySets, firstRiskyAwait, context));
69593
+ });
67955
69594
  for (const reset of conditionalExceptionalResets) {
67956
69595
  const catchHandler = reset.protectingTry?.handler;
67957
69596
  if (catchHandler && !catchHandlerCanBypassReset(catchHandler, functionNode, setterKey, context, false)) continue;
@@ -73823,6 +75462,29 @@ const doesPredicateTruthRequireMatch = (matchCall, predicateFunction) => {
73823
75462
  }
73824
75463
  return !isNegated && predicateFunction.body === child;
73825
75464
  };
75465
+ const doesPredicateReturnNormalizedMatch = (matchCall, predicateFunction) => {
75466
+ if (!isFunctionLike$1(predicateFunction) || !isNodeOfType(predicateFunction.body, "BlockStatement") || predicateFunction.body.body.length !== 2 || !isNodeOfType(predicateFunction.body.body[0], "VariableDeclaration")) return false;
75467
+ const returnStatement = predicateFunction.body.body[1];
75468
+ if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
75469
+ let negationCount = 0;
75470
+ let expression = matchCall;
75471
+ let parent = expression.parent ?? null;
75472
+ while (parent && parent !== returnStatement) {
75473
+ if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "!") {
75474
+ negationCount += 1;
75475
+ expression = parent;
75476
+ parent = parent.parent ?? null;
75477
+ continue;
75478
+ }
75479
+ if (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) || isNodeOfType(parent, "ChainExpression")) {
75480
+ expression = parent;
75481
+ parent = parent.parent ?? null;
75482
+ continue;
75483
+ }
75484
+ return false;
75485
+ }
75486
+ return parent === returnStatement && returnStatement.argument === expression && negationCount % 2 === 0;
75487
+ };
73826
75488
  const isStringTypeofGuardForPath = (test, expectedPath) => {
73827
75489
  const target = stripParenExpression(test);
73828
75490
  if (!isNodeOfType(target, "BinaryExpression") || target.operator !== "===") return false;
@@ -73863,6 +75525,26 @@ const pathUsesOptionalAccess = (node) => {
73863
75525
  current = current.object;
73864
75526
  }
73865
75527
  };
75528
+ const getNormalizedClassNameRoot = (expression) => {
75529
+ const conditional = stripParenExpression(expression);
75530
+ if (!isNodeOfType(conditional, "ConditionalExpression")) return null;
75531
+ const consequent = stripParenExpression(conditional.consequent);
75532
+ const rootIdentifier = getRootIdentifier(consequent);
75533
+ if (!rootIdentifier || receiverPathKey(consequent) !== `${rootIdentifier.name}.className`) return null;
75534
+ const test = stripParenExpression(conditional.test);
75535
+ if (!isNodeOfType(test, "BinaryExpression") || test.operator !== "===") return null;
75536
+ const testOperands = [test.left, test.right].map((operand) => stripParenExpression(operand));
75537
+ const typeofOperand = testOperands.find((operand) => isNodeOfType(operand, "UnaryExpression"));
75538
+ const stringOperand = testOperands.find((operand) => isNodeOfType(operand, "Literal"));
75539
+ if (!typeofOperand || !isNodeOfType(typeofOperand, "UnaryExpression") || typeofOperand.operator !== "typeof" || receiverPathKey(typeofOperand.argument) !== `${rootIdentifier.name}.className` || !stringOperand || !isNodeOfType(stringOperand, "Literal") || stringOperand.value !== "string") return null;
75540
+ const alternate = stripParenExpression(conditional.alternate);
75541
+ if (!isNodeOfType(alternate, "LogicalExpression") || alternate.operator !== "??") return null;
75542
+ const fallback = stripParenExpression(alternate.right);
75543
+ const attributeCall = stripParenExpression(alternate.left);
75544
+ if (!isNodeOfType(fallback, "Literal") || fallback.value !== "" || !isNodeOfType(attributeCall, "CallExpression") || !isNodeOfType(attributeCall.callee, "MemberExpression") || getStaticPropertyName(attributeCall.callee) !== "getAttribute" || receiverPathKey(attributeCall.callee.object) !== rootIdentifier.name) return null;
75545
+ const attributeName = attributeCall.arguments[0] ? stripParenExpression(attributeCall.arguments[0]) : null;
75546
+ return attributeName && isNodeOfType(attributeName, "Literal") && attributeName.value === "class" ? rootIdentifier : null;
75547
+ };
73866
75548
  const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedPattern, context) => {
73867
75549
  const resultIdentifier = getRootIdentifier(matchReceiver);
73868
75550
  const resultPath = receiverPathKey(matchReceiver);
@@ -73871,11 +75553,17 @@ const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedP
73871
75553
  if (!isDirectFinderMatchReturn(assertion) && !isOptionalResultPath) return false;
73872
75554
  const resultSymbol = context.scopes.symbolFor(resultIdentifier);
73873
75555
  const initializer = resultSymbol?.initializer ? stripParenExpression(resultSymbol.initializer) : null;
73874
- if (resultSymbol?.kind !== "const" || !initializer || !isNodeOfType(initializer, "CallExpression")) return false;
75556
+ if (resultSymbol?.kind !== "const" || !initializer) return false;
75557
+ const finderCall = isNodeOfType(initializer, "CallExpression") ? initializer : isNodeOfType(initializer, "ConditionalExpression") ? (() => {
75558
+ const alternate = stripParenExpression(initializer.alternate);
75559
+ const consequent = stripParenExpression(initializer.consequent);
75560
+ return (isNodeOfType(alternate, "Literal") && alternate.value === null || isNodeOfType(alternate, "Identifier") && alternate.name === "undefined" && context.scopes.isGlobalReference(alternate)) && isNodeOfType(consequent, "CallExpression") ? consequent : null;
75561
+ })() : null;
75562
+ if (!finderCall || !isNodeOfType(finderCall, "CallExpression")) return false;
73875
75563
  if (!isOptionalResultPath && !isImmediatelyGuardedFinderResult(assertion, resultSymbol, resultPath, context)) return false;
73876
- const finderCallee = stripParenExpression(initializer.callee);
75564
+ const finderCallee = stripParenExpression(finderCall.callee);
73877
75565
  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;
73878
- const predicateArgument = initializer.arguments[1];
75566
+ const predicateArgument = finderCall.arguments[1];
73879
75567
  if (!predicateArgument) return false;
73880
75568
  const predicateFunction = resolveExactLocalFunction(predicateArgument, context.scopes);
73881
75569
  if (!predicateFunction || !isFunctionLike$1(predicateFunction)) return false;
@@ -73899,6 +75587,43 @@ const isMatchProvenByFindUpUntilPredicate = (assertion, matchReceiver, assertedP
73899
75587
  });
73900
75588
  return didProveMatch;
73901
75589
  };
75590
+ const isMatchProvenByNormalizedFindUpUntilPredicate = (assertion, matchReceiver, assertedPattern, context) => {
75591
+ const normalizedReceiver = stripParenExpression(matchReceiver);
75592
+ if (!isNodeOfType(normalizedReceiver, "Identifier")) return false;
75593
+ const normalizedReceiverSymbol = context.scopes.symbolFor(normalizedReceiver);
75594
+ const normalizedReceiverInitializer = normalizedReceiverSymbol?.initializer ? stripParenExpression(normalizedReceiverSymbol.initializer) : null;
75595
+ const resultIdentifier = normalizedReceiverInitializer ? getNormalizedClassNameRoot(normalizedReceiverInitializer) : null;
75596
+ if (normalizedReceiverSymbol?.kind !== "const" || normalizedReceiverSymbol.references.some((reference) => reference.flag !== "read") || !resultIdentifier) return false;
75597
+ const resultSymbol = context.scopes.symbolFor(resultIdentifier);
75598
+ const finderCall = resultSymbol?.initializer ? stripParenExpression(resultSymbol.initializer) : null;
75599
+ if (resultSymbol?.kind !== "const" || resultSymbol.references.some((reference) => reference.flag !== "read") || !finderCall || !isNodeOfType(finderCall, "CallExpression")) return false;
75600
+ const finderCallee = stripParenExpression(finderCall.callee);
75601
+ if (!isNodeOfType(finderCallee, "Identifier") || context.scopes.symbolFor(finderCallee)?.kind !== "import" || getImportedNameFromModule(assertion, finderCallee.name, CLOUDSCAPE_DOM_MODULE) !== "findUpUntil") return false;
75602
+ if (!isPresenceProvenBeforeNode(assertion, (test) => {
75603
+ const expression = stripParenExpression(test);
75604
+ return isNodeOfType(expression, "Identifier") && context.scopes.symbolFor(expression)?.id === resultSymbol.id;
75605
+ })) return false;
75606
+ const predicateArgument = finderCall.arguments[1];
75607
+ const predicateFunction = predicateArgument ? resolveExactLocalFunction(predicateArgument, context.scopes) : null;
75608
+ if (!predicateFunction || !isFunctionLike$1(predicateFunction) || predicateFunction.async || predicateFunction.generator) return false;
75609
+ const predicateParameter = predicateFunction.params[0];
75610
+ if (!isNodeOfType(predicateParameter, "Identifier")) return false;
75611
+ let didProveNormalizedMatch = false;
75612
+ walkAst(predicateFunction.body, (child) => {
75613
+ if (didProveNormalizedMatch || isFunctionLike$1(child)) return false;
75614
+ if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || getStaticPropertyName(child.callee) !== "match" || !child.arguments[0] || !areRegexPatternsEquivalent(child.arguments[0], assertedPattern, context) || !doesPredicateTruthRequireMatch(child, predicateFunction) && !doesPredicateReturnNormalizedMatch(child, predicateFunction)) return;
75615
+ const predicateReceiver = stripParenExpression(child.callee.object);
75616
+ if (!isNodeOfType(predicateReceiver, "Identifier")) return;
75617
+ const predicateReceiverSymbol = context.scopes.symbolFor(predicateReceiver);
75618
+ const predicateReceiverInitializer = predicateReceiverSymbol?.initializer ? stripParenExpression(predicateReceiverSymbol.initializer) : null;
75619
+ const predicateRoot = predicateReceiverInitializer ? getNormalizedClassNameRoot(predicateReceiverInitializer) : null;
75620
+ if (predicateReceiverSymbol?.kind === "const" && predicateReceiverSymbol.references.every((reference) => reference.flag === "read") && predicateRoot?.name === predicateParameter.name) {
75621
+ didProveNormalizedMatch = true;
75622
+ return false;
75623
+ }
75624
+ });
75625
+ return didProveNormalizedMatch;
75626
+ };
73902
75627
  const scopeProvesFindMatch = (assertion, findReceiver, findPredicate, context) => {
73903
75628
  if (!isStablePredicate(findPredicate, context)) return false;
73904
75629
  return isPresenceProvenBeforeNode(assertion, (test) => testPositivelyContainsCall(test, (call) => {
@@ -73999,6 +75724,123 @@ const isEnsureThenFind = (assertion, findReceiver, findPredicate) => {
73999
75724
  }
74000
75725
  return false;
74001
75726
  };
75727
+ const getReceiverRootIdentifier = (node) => {
75728
+ let target = stripParenExpression(node);
75729
+ while (isNodeOfType(target, "MemberExpression")) target = stripParenExpression(target.object);
75730
+ return isNodeOfType(target, "Identifier") ? target : null;
75731
+ };
75732
+ const getReceiverStatePath = (node) => {
75733
+ const target = stripParenExpression(node);
75734
+ if (isNodeOfType(target, "Identifier")) return target.name;
75735
+ if (!isNodeOfType(target, "MemberExpression")) return null;
75736
+ const objectPath = getReceiverStatePath(target.object);
75737
+ if (!objectPath) return null;
75738
+ return `${objectPath}.${getStaticPropertyName(target) ?? "*"}`;
75739
+ };
75740
+ const doesReceiverStateChangeBeforeAssertion = (ownerFunction, receiver, startOffset, assertion, context) => {
75741
+ const receiverRoot = getReceiverRootIdentifier(receiver);
75742
+ const receiverSymbol = receiverRoot ? context.scopes.symbolFor(receiverRoot) : null;
75743
+ const receiverPath = getReceiverStatePath(receiver);
75744
+ if (!receiverRoot || !receiverSymbol || !receiverPath) return true;
75745
+ const receiverAliasPaths = new Map([[receiverSymbol.id, receiverRoot.name]]);
75746
+ let didAddAlias = true;
75747
+ while (didAddAlias) {
75748
+ didAddAlias = false;
75749
+ walkAst(ownerFunction, (child) => {
75750
+ if (child !== ownerFunction && isFunctionLike$1(child)) return false;
75751
+ if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier") || !child.init) return;
75752
+ const initializer = stripParenExpression(child.init);
75753
+ if (!isNodeOfType(initializer, "Identifier") && !isNodeOfType(initializer, "MemberExpression")) return;
75754
+ const initializerRoot = getReceiverRootIdentifier(initializer);
75755
+ const initializerSymbol = initializerRoot ? context.scopes.symbolFor(initializerRoot) : null;
75756
+ const initializerBasePath = initializerSymbol ? receiverAliasPaths.get(initializerSymbol.id) : null;
75757
+ const initializerPath = getReceiverStatePath(initializer);
75758
+ if (!initializerRoot || !initializerBasePath || !initializerPath) return;
75759
+ const aliasSymbol = context.scopes.symbolFor(child.id);
75760
+ if (aliasSymbol && !receiverAliasPaths.has(aliasSymbol.id)) {
75761
+ const initializerSuffix = initializerPath.slice(initializerRoot.name.length);
75762
+ receiverAliasPaths.set(aliasSymbol.id, `${initializerBasePath}${initializerSuffix}`);
75763
+ didAddAlias = true;
75764
+ }
75765
+ });
75766
+ }
75767
+ let didChangeReceiverState = false;
75768
+ walkAst(ownerFunction, (child) => {
75769
+ if (didChangeReceiverState) return false;
75770
+ if (child !== ownerFunction && isFunctionLike$1(child)) return false;
75771
+ if (child.range[0] <= startOffset || child.range[0] >= assertion.range[0]) return;
75772
+ if (isNodeOfType(child, "CallExpression")) {
75773
+ didChangeReceiverState = true;
75774
+ return false;
75775
+ }
75776
+ const mutationTarget = isNodeOfType(child, "AssignmentExpression") || isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete" ? stripParenExpression(isNodeOfType(child, "AssignmentExpression") ? child.left : child.argument) : null;
75777
+ const mutationRoot = mutationTarget ? getReceiverRootIdentifier(mutationTarget) : null;
75778
+ const mutationSymbol = mutationRoot ? context.scopes.symbolFor(mutationRoot) : null;
75779
+ const mutationBasePath = mutationSymbol ? receiverAliasPaths.get(mutationSymbol.id) : null;
75780
+ const mutationPath = mutationTarget ? getReceiverStatePath(mutationTarget) : null;
75781
+ if (mutationRoot && mutationBasePath && mutationPath) {
75782
+ const canonicalMutationPath = `${mutationBasePath}${mutationPath.slice(mutationRoot.name.length)}`;
75783
+ if (canonicalMutationPath !== receiverPath && !canonicalMutationPath.startsWith(`${receiverPath}.`) && !receiverPath.startsWith(`${canonicalMutationPath}.`)) return;
75784
+ didChangeReceiverState = true;
75785
+ return false;
75786
+ }
75787
+ });
75788
+ return didChangeReceiverState;
75789
+ };
75790
+ const isFindProvenByGuardedMaximum = (assertion, findReceiver, findPredicate, context) => {
75791
+ const findLookup = findEqualityLookupParts(findPredicate);
75792
+ const maximumIdentifier = findLookup ? stripParenExpression(findLookup.comparedValue) : null;
75793
+ if (!findLookup || !maximumIdentifier || !isNodeOfType(maximumIdentifier, "Identifier")) return false;
75794
+ const maximumSymbol = context.scopes.symbolFor(maximumIdentifier);
75795
+ const maximumInitializer = maximumSymbol?.initializer ? stripParenExpression(maximumSymbol.initializer) : null;
75796
+ if (maximumSymbol?.kind !== "const" || maximumSymbol.references.some((reference) => reference.flag !== "read") || !maximumInitializer || !isNodeOfType(maximumInitializer, "CallExpression") || !isNodeOfType(maximumInitializer.callee, "MemberExpression") || getStaticPropertyName(maximumInitializer.callee) !== "reduce") return false;
75797
+ const filterCall = stripParenExpression(maximumInitializer.callee.object);
75798
+ if (!isNodeOfType(filterCall, "CallExpression") || !isNodeOfType(filterCall.callee, "MemberExpression") || getStaticPropertyName(filterCall.callee) !== "filter" || !areNodesLooselyEqual(filterCall.callee.object, findReceiver)) return false;
75799
+ const filterPredicate = filterCall.arguments[0] ? stripParenExpression(filterCall.arguments[0]) : null;
75800
+ if (!filterPredicate || !isStablePredicate(filterPredicate, context)) return false;
75801
+ const reducerArgument = maximumInitializer.arguments[0] ? stripParenExpression(maximumInitializer.arguments[0]) : null;
75802
+ const reducerFunction = reducerArgument ? resolveExactLocalFunction(reducerArgument, context.scopes) : null;
75803
+ const initialValue = maximumInitializer.arguments[1] ? stripParenExpression(maximumInitializer.arguments[1]) : null;
75804
+ if (!reducerFunction || !isFunctionLike$1(reducerFunction) || reducerFunction.async || reducerFunction.generator || !initialValue || !isNodeOfType(initialValue, "Literal") || typeof initialValue.value !== "number") return false;
75805
+ const accumulatorParameter = reducerFunction.params[0];
75806
+ const itemParameter = reducerFunction.params[1];
75807
+ const reducerBody = singleExpressionPredicateBody(reducerFunction);
75808
+ if (!isNodeOfType(accumulatorParameter, "Identifier") || !isNodeOfType(itemParameter, "Identifier") || !reducerBody || !isNodeOfType(reducerBody, "CallExpression") || !isNodeOfType(reducerBody.callee, "MemberExpression") || getStaticPropertyName(reducerBody.callee) !== "max") return false;
75809
+ const mathReceiver = stripParenExpression(reducerBody.callee.object);
75810
+ if (!isNodeOfType(mathReceiver, "Identifier") || mathReceiver.name !== "Math" || !context.scopes.isGlobalReference(mathReceiver) || reducerBody.arguments.length !== 2) return false;
75811
+ const accumulatorArgument = reducerBody.arguments.find((argument) => {
75812
+ const expression = stripParenExpression(argument);
75813
+ return isNodeOfType(expression, "Identifier") && expression.name === accumulatorParameter.name;
75814
+ });
75815
+ const itemMemberArgument = reducerBody.arguments.find((argument) => {
75816
+ const expression = stripParenExpression(argument);
75817
+ const rootIdentifier = getRootIdentifier(expression);
75818
+ return isNodeOfType(expression, "MemberExpression") && rootIdentifier?.name === itemParameter.name && receiverPathKey(expression)?.slice(itemParameter.name.length + 1) === findLookup.propertyName;
75819
+ });
75820
+ if (!accumulatorArgument || !itemMemberArgument) return false;
75821
+ if (!receiverPathKey(findReceiver)) return false;
75822
+ let ownerFunction = assertion.parent ?? null;
75823
+ while (ownerFunction && !isFunctionLike$1(ownerFunction)) ownerFunction = ownerFunction.parent ?? null;
75824
+ if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
75825
+ const maximumEnd = maximumInitializer.range[1];
75826
+ if (doesReceiverStateChangeBeforeAssertion(ownerFunction.body, findReceiver, maximumEnd, assertion, context)) return false;
75827
+ return isPresenceProvenBeforeNode(assertion, (test) => {
75828
+ const comparison = stripParenExpression(test);
75829
+ if (!isNodeOfType(comparison, "BinaryExpression")) return false;
75830
+ return [[
75831
+ comparison.left,
75832
+ comparison.right,
75833
+ comparison.operator
75834
+ ], [
75835
+ comparison.right,
75836
+ comparison.left,
75837
+ comparison.operator === "<" ? ">" : comparison.operator === ">" ? "<" : comparison.operator
75838
+ ]].some(([candidateMaximum, candidateInitial, operator]) => {
75839
+ const candidateMaximumIdentifier = stripParenExpression(candidateMaximum);
75840
+ return operator === ">" && isNodeOfType(candidateMaximumIdentifier, "Identifier") && context.scopes.symbolFor(candidateMaximumIdentifier)?.id === maximumSymbol.id && areNodesLooselyEqual(stripParenExpression(candidateInitial), initialValue);
75841
+ });
75842
+ });
75843
+ };
74002
75844
  const isDefinitelyNonNullishMapValue = (value) => {
74003
75845
  if (!value) return false;
74004
75846
  const expression = stripParenExpression(value);
@@ -74022,15 +75864,15 @@ const unwrapFalseBooleanGuard = (test) => {
74022
75864
  };
74023
75865
  const isEnsureThenMapGet = (assertion, receiver, lookupKey, context) => {
74024
75866
  const stableLookupKey = stripParenExpression(lookupKey);
74025
- if (!isNodeOfType(stableLookupKey, "Identifier") && !isNodeOfType(stableLookupKey, "Literal")) return false;
75867
+ const lookupKeyRoot = isNodeOfType(stableLookupKey, "MemberExpression") ? getRootIdentifier(stableLookupKey) : null;
75868
+ const lookupKeySymbol = isNodeOfType(stableLookupKey, "Identifier") ? context.scopes.symbolFor(stableLookupKey) : lookupKeyRoot ? context.scopes.symbolFor(lookupKeyRoot) : null;
75869
+ if (!isNodeOfType(stableLookupKey, "Identifier") && !isNodeOfType(stableLookupKey, "Literal") && (!isNodeOfType(stableLookupKey, "MemberExpression") || !lookupKeyRoot || lookupKeySymbol?.kind !== "const")) return false;
74026
75870
  const receiverSymbol = context.scopes.symbolFor(receiver);
74027
75871
  if (!receiverSymbol) return false;
74028
75872
  const receiverMatches = (candidate) => {
74029
75873
  const target = stripParenExpression(candidate);
74030
75874
  return isNodeOfType(target, "Identifier") && context.scopes.symbolFor(target)?.id === receiverSymbol.id;
74031
75875
  };
74032
- const lookupKeyExpression = stripParenExpression(lookupKey);
74033
- const lookupKeySymbol = isNodeOfType(lookupKeyExpression, "Identifier") ? context.scopes.symbolFor(lookupKeyExpression) : null;
74034
75876
  let child = assertion;
74035
75877
  let ancestor = assertion.parent ?? null;
74036
75878
  while (ancestor && !isFunctionLike$1(ancestor)) {
@@ -74050,7 +75892,7 @@ const isEnsureThenMapGet = (assertion, receiver, lookupKey, context) => {
74050
75892
  const populationCall = populationCalls[0];
74051
75893
  if (!populationCall) continue;
74052
75894
  const populationCallStart = populationCall.range[0];
74053
- if (Boolean(lookupKeySymbol?.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] > populationCallStart && reference.identifier.range[0] < assertion.range[0]))) continue;
75895
+ if (Boolean(lookupKeySymbol?.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] > populationCallStart && reference.identifier.range[0] < assertion.range[0])) || Boolean(lookupKeySymbol && isNodeOfType(stableLookupKey, "MemberExpression") && subtreeWritesSymbol(ancestor, new Set([lookupKeySymbol.id]), context, void 0, assertion))) continue;
74054
75896
  if (receiverSymbol.references.some((reference) => reference.flag !== "read" && reference.identifier.range[0] > populationCallStart && reference.identifier.range[0] < assertion.range[0])) continue;
74055
75897
  if (!indexedRelevantCalls(ancestor).some((laterCall) => {
74056
75898
  if (laterCall.range[0] <= populationCallStart || laterCall.range[0] >= assertion.range[0] || !isNodeOfType(laterCall.callee, "MemberExpression") || !receiverMatches(laterCall.callee.object)) return false;
@@ -74160,6 +76002,7 @@ const noNonNullAssertionOnMaybeUndefinedResult = defineRule({
74160
76002
  const findReceiver = callee.object;
74161
76003
  if (predicate && isExhaustiveLiteralTupleMapping(findReceiver, predicate, context)) return;
74162
76004
  if (predicate && scopeProvesFindMatch(node, findReceiver, predicate, context)) return;
76005
+ if (predicate && isFindProvenByGuardedMaximum(node, findReceiver, predicate, context)) return;
74163
76006
  if (predicate && isEnsureThenFind(node, findReceiver, predicate)) return;
74164
76007
  }
74165
76008
  if (methodName === "match") {
@@ -74169,6 +76012,7 @@ const noNonNullAssertionOnMaybeUndefinedResult = defineRule({
74169
76012
  const regexKey = pattern ? regexComparableKey(pattern, context) : null;
74170
76013
  if (pattern && isGuardedAnchoredCharacterMatch(node, matchReceiver, pattern)) return;
74171
76014
  if (pattern && regexKey && isMatchProvenByFindUpUntilPredicate(node, matchReceiver, pattern, context)) return;
76015
+ if (pattern && regexKey && isMatchProvenByNormalizedFindUpUntilPredicate(node, matchReceiver, pattern, context)) return;
74172
76016
  if (regexKey && scopeProvesMatchTested(node, regexKey, matchReceiver, context)) return;
74173
76017
  }
74174
76018
  if (methodName === "get") {
@@ -78107,16 +79951,23 @@ const MAX_INITIATOR_RESOLUTION_DEPTH = 3;
78107
79951
  const STATE_DISPATCHER_HOOK_NAMES = new Set(["useState", "useReducer"]);
78108
79952
  const REF_HOOK_NAMES = new Set(["useRef"]);
78109
79953
  const MESSAGE$26 = "This promise chain runs in an effect, ends in a `.then` that sets state or mutates a ref, and has no `.catch` or enclosing try/catch, so a rejection leaves the state unset and surfaces as an unhandled rejection. Add a `.catch` handler on the chain (`.finally` does not count).";
78110
- const isKnownNonThenableHandlerReturn = (expression, context, visitedBindingIdentifiers = /* @__PURE__ */ new Set()) => {
79954
+ const isKnownNonRejectingHandlerReturn = (expression, context, visitedBindingIdentifiers = /* @__PURE__ */ new Set()) => {
78111
79955
  const strippedExpression = stripParenExpression(expression);
78112
79956
  if (isDefinitelyNonThenableValue(strippedExpression)) return true;
79957
+ if (isNodeOfType(strippedExpression, "CallExpression") && isNodeOfType(strippedExpression.callee, "MemberExpression")) {
79958
+ const receiver = stripParenExpression(strippedExpression.callee.object);
79959
+ if (isNodeOfType(receiver, "Identifier") && receiver.name === "Promise" && context.scopes.isGlobalReference(receiver) && getStaticPropertyName(strippedExpression.callee) === "resolve") {
79960
+ const resolvedValue = strippedExpression.arguments[0];
79961
+ return !resolvedValue || !isNodeOfType(resolvedValue, "SpreadElement") && isKnownNonRejectingHandlerReturn(resolvedValue, context, visitedBindingIdentifiers);
79962
+ }
79963
+ }
78113
79964
  if (!isNodeOfType(strippedExpression, "Identifier")) return false;
78114
79965
  if (strippedExpression.name === "undefined" && context.scopes.isGlobalReference(strippedExpression)) return true;
78115
79966
  const symbol = context.scopes.symbolFor(strippedExpression);
78116
79967
  if (!symbol || visitedBindingIdentifiers.has(symbol.bindingIdentifier)) return false;
78117
79968
  visitedBindingIdentifiers.add(symbol.bindingIdentifier);
78118
79969
  const initializer = getDirectUnreassignedInitializer(symbol);
78119
- return Boolean(initializer && isKnownNonThenableHandlerReturn(initializer, context, visitedBindingIdentifiers));
79970
+ return Boolean(initializer && isKnownNonRejectingHandlerReturn(initializer, context, visitedBindingIdentifiers));
78120
79971
  };
78121
79972
  const isKnownNonRejectingHandler = (argument, context) => {
78122
79973
  if (!argument) return false;
@@ -78131,7 +79982,7 @@ const isKnownNonRejectingHandler = (argument, context) => {
78131
79982
  canReject = true;
78132
79983
  return false;
78133
79984
  }
78134
- if (isNodeOfType(child, "ReturnStatement") && child.argument && !isKnownNonThenableHandlerReturn(child.argument, context)) {
79985
+ if (isNodeOfType(child, "ReturnStatement") && child.argument && !isKnownNonRejectingHandlerReturn(child.argument, context)) {
78135
79986
  if (!isNodeOfType(stripParenExpression(child.argument), "CallExpression")) {
78136
79987
  canReject = true;
78137
79988
  return false;
@@ -78180,6 +80031,28 @@ const handlerHasPotentiallyThrowingMemberRead = (argument, context) => {
78180
80031
  });
78181
80032
  return hasPotentiallyThrowingMemberRead;
78182
80033
  };
80034
+ const hasRejectionHandler = (chain, argument, context, allowTerminalCatchBlock) => {
80035
+ if (!argument) return false;
80036
+ if (!handlerHasPotentiallyThrowingMemberRead(argument, context) && (chainCarriesRejectionHandler(chain, context.scopes) || isKnownNonRejectingHandler(argument, context))) return true;
80037
+ if (!allowTerminalCatchBlock) return false;
80038
+ const candidate = stripParenExpression(argument);
80039
+ const handler = isNodeOfType(candidate, "Identifier") ? resolveExactLocalFunction(candidate, context.scopes) : candidate;
80040
+ if (!handler || !isFunctionLike$1(handler)) return isNodeOfType(candidate, "MemberExpression") || isNodeOfType(candidate, "Identifier") && candidate.name !== "undefined";
80041
+ if (!isNodeOfType(handler.body, "BlockStatement")) return false;
80042
+ let doesExplicitlyReject = false;
80043
+ walkOwnFunctionScope(handler, (child) => {
80044
+ if (doesExplicitlyReject) return false;
80045
+ if (isNodeOfType(child, "ThrowStatement") || isNodeOfType(child, "AwaitExpression")) {
80046
+ doesExplicitlyReject = true;
80047
+ return false;
80048
+ }
80049
+ if (isNodeOfType(child, "ReturnStatement") && child.argument && !isKnownNonRejectingHandlerReturn(child.argument, context)) {
80050
+ doesExplicitlyReject = true;
80051
+ return false;
80052
+ }
80053
+ });
80054
+ return !doesExplicitlyReject;
80055
+ };
78183
80056
  const walkPromiseChain = (chainExpression, context) => {
78184
80057
  let cursor = stripParenExpression(chainExpression);
78185
80058
  let hasCatch = false;
@@ -78191,10 +80064,9 @@ const walkPromiseChain = (chainExpression, context) => {
78191
80064
  while (isNodeOfType(cursor, "CallExpression") && isNodeOfType(cursor.callee, "MemberExpression") && PROMISE_METHOD_NAMES.has(getStaticPropertyName(cursor.callee) ?? "")) {
78192
80065
  const methodName = getStaticPropertyName(cursor.callee);
78193
80066
  const rejectionHandlerArgument = methodName === "catch" ? cursor.arguments[0] : cursor.arguments[1];
78194
- const hasAbsorbingRejectionHandler = !handlerHasPotentiallyThrowingMemberRead(rejectionHandlerArgument, context) && (chainCarriesRejectionHandler(cursor, context.scopes) || isKnownNonRejectingHandler(rejectionHandlerArgument, context));
78195
- if (!didReachTerminalThen && methodName === "catch" && hasAbsorbingRejectionHandler) hasCatch = true;
80067
+ if (!didReachTerminalThen && methodName === "catch" && hasRejectionHandler(cursor, rejectionHandlerArgument, context, true)) hasCatch = true;
78196
80068
  if (methodName === "then") {
78197
- if (!didReachTerminalThen && hasAbsorbingRejectionHandler) hasRejectionHandlerArgument = true;
80069
+ if (!didReachTerminalThen && hasRejectionHandler(cursor, rejectionHandlerArgument, context, false)) hasRejectionHandlerArgument = true;
78198
80070
  didReachTerminalThen = true;
78199
80071
  sawThen = true;
78200
80072
  const callbackArgument = cursor.arguments[0];
@@ -80521,6 +82393,45 @@ const noRefCallbackCleanupBeforeReact19 = defineRule({
80521
82393
  } })
80522
82394
  });
80523
82395
  //#endregion
82396
+ //#region src/plugin/utils/contains-non-deterministic-source.ts
82397
+ const NON_DETERMINISTIC_MEMBER_CALLS = new Set([
82398
+ "Math.random",
82399
+ "Date.now",
82400
+ "performance.now",
82401
+ "crypto.randomUUID",
82402
+ "crypto.getRandomValues"
82403
+ ]);
82404
+ const NON_DETERMINISTIC_ID_GENERATOR_NAMES = new Set([
82405
+ "nanoid",
82406
+ "uuid",
82407
+ "cuid",
82408
+ "ulid",
82409
+ "createId"
82410
+ ]);
82411
+ const isZeroArgDateConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Date" && (node.arguments?.length ?? 0) === 0;
82412
+ const containsNonDeterministicSource = (root) => {
82413
+ let found = false;
82414
+ walkAst(root, (child) => {
82415
+ if (found) return false;
82416
+ if (isFunctionLike$1(child)) return false;
82417
+ if (isZeroArgDateConstruction(child)) {
82418
+ found = true;
82419
+ return false;
82420
+ }
82421
+ if (!isNodeOfType(child, "CallExpression")) return;
82422
+ const callee = child.callee;
82423
+ if (isNodeOfType(callee, "Identifier") && NON_DETERMINISTIC_ID_GENERATOR_NAMES.has(callee.name)) {
82424
+ found = true;
82425
+ return false;
82426
+ }
82427
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && isNodeOfType(callee.property, "Identifier") && NON_DETERMINISTIC_MEMBER_CALLS.has(`${callee.object.name}.${callee.property.name}`)) {
82428
+ found = true;
82429
+ return false;
82430
+ }
82431
+ });
82432
+ return found;
82433
+ };
82434
+ //#endregion
80524
82435
  //#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
80525
82436
  const REPEATED_ANCESTOR_TYPES = new Set([
80526
82437
  "DoWhileStatement",
@@ -80551,45 +82462,75 @@ const resolveImmutableInitializationValue = (node, scopes, visitedSymbolIds = /*
80551
82462
  };
80552
82463
  const isProvablyTruthyInitializationValue = (node, scopes) => {
80553
82464
  const expression = resolveImmutableInitializationValue(node, scopes);
80554
- return Boolean(expression && (isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "ArrayExpression") || isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "ClassExpression")));
82465
+ if (!expression) return false;
82466
+ if (isNodeOfType(expression, "CallExpression")) {
82467
+ const callee = stripParenExpression(expression.callee);
82468
+ return isNodeOfType(callee, "Identifier") && callee.name.startsWith("create");
82469
+ }
82470
+ return isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "ArrayExpression") || isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "ClassExpression");
80555
82471
  };
80556
- const getInitializationConstructorName = (node, scopes) => {
82472
+ const getInitializationValueName = (node, scopes) => {
80557
82473
  const expression = resolveImmutableInitializationValue(node, scopes);
80558
82474
  if (!expression) return null;
80559
- if (isNodeOfType(expression, "NewExpression")) {
82475
+ if (isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "CallExpression")) {
80560
82476
  const callee = stripParenExpression(expression.callee);
80561
- return isNodeOfType(callee, "Identifier") ? callee.name : null;
82477
+ if (!isNodeOfType(callee, "Identifier")) return null;
82478
+ return callee.name.startsWith("create") && callee.name.length > 6 ? callee.name.slice(6) : callee.name;
80562
82479
  }
80563
82480
  return null;
80564
82481
  };
82482
+ const isMatchingReturnType = (typeNode, initializationValue, scopes) => {
82483
+ if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
82484
+ const typeName = typeNode.typeName;
82485
+ if (!isNodeOfType(typeName, "Identifier") || typeName.name !== "ReturnType") return false;
82486
+ const [returnTypeArgument] = typeNode.typeArguments?.params ?? [];
82487
+ if (!returnTypeArgument || !isNodeOfType(returnTypeArgument, "TSTypeQuery")) return false;
82488
+ const queriedName = returnTypeArgument.exprName;
82489
+ const expression = stripParenExpression(initializationValue);
82490
+ if (!isNodeOfType(queriedName, "Identifier") || !isNodeOfType(expression, "CallExpression")) return false;
82491
+ const callee = stripParenExpression(expression.callee);
82492
+ if (!isNodeOfType(callee, "Identifier")) return false;
82493
+ const queriedSymbol = scopes.symbolFor(queriedName);
82494
+ const calleeSymbol = scopes.symbolFor(callee);
82495
+ return queriedSymbol && calleeSymbol ? queriedSymbol.id === calleeSymbol.id : queriedName.name === callee.name;
82496
+ };
80565
82497
  const isClosedTruthyTypeDomain = (typeNode, initializationValue, scopes) => {
80566
82498
  const initializationExpression = stripParenExpression(initializationValue);
80567
82499
  if (isNodeOfType(typeNode, "TSTypeLiteral")) return isNodeOfType(initializationExpression, "ObjectExpression");
80568
82500
  if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return isNodeOfType(initializationExpression, "ArrayExpression");
80569
82501
  if (isNodeOfType(typeNode, "TSFunctionType") || isNodeOfType(typeNode, "TSConstructorType")) return isNodeOfType(initializationExpression, "ArrowFunctionExpression") || isNodeOfType(initializationExpression, "FunctionExpression") || isNodeOfType(initializationExpression, "ClassExpression");
80570
82502
  if (isNodeOfType(typeNode, "TSObjectKeyword")) return true;
82503
+ if (isNodeOfType(typeNode, "TSIndexedAccessType")) return isNodeOfType(initializationExpression, "ObjectExpression");
80571
82504
  if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
80572
82505
  const typeName = typeNode.typeName;
80573
- return isNodeOfType(typeName, "Identifier") && typeName.name === getInitializationConstructorName(initializationExpression, scopes);
82506
+ if (isNodeOfType(initializationExpression, "ObjectExpression") || isMatchingReturnType(typeNode, initializationExpression, scopes)) return true;
82507
+ return isNodeOfType(typeName, "Identifier") && typeName.name === getInitializationValueName(initializationExpression, scopes);
80574
82508
  };
80575
82509
  const refHasClosedFalsySentinelDomain = (refSymbol, initializationValue, scopes) => {
80576
82510
  const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
80577
82511
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
80578
82512
  const [initialValue] = initializer.arguments ?? [];
80579
- if (!initialValue || isNodeOfType(initialValue, "SpreadElement") || !isEmptySentinel(initialValue, scopes)) return false;
82513
+ if (initialValue && isNodeOfType(initialValue, "SpreadElement") || initialValue && !isEmptySentinel(initialValue, scopes)) return false;
80580
82514
  const [declaredType] = initializer.typeArguments?.params ?? [];
80581
- if (!declaredType || !isNodeOfType(declaredType, "TSUnionType")) return false;
80582
- let hasEmptySentinel = false;
82515
+ if (!declaredType) return false;
82516
+ const domainTypes = isNodeOfType(declaredType, "TSUnionType") ? declaredType.types : [declaredType];
80583
82517
  let hasTruthyDomain = false;
80584
- for (const memberType of declaredType.types ?? []) {
80585
- if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword")) {
80586
- hasEmptySentinel = true;
80587
- continue;
80588
- }
82518
+ for (const memberType of domainTypes) {
82519
+ if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword")) continue;
80589
82520
  if (!isClosedTruthyTypeDomain(memberType, initializationValue, scopes)) return false;
80590
82521
  hasTruthyDomain = true;
80591
82522
  }
80592
- return hasEmptySentinel && hasTruthyDomain;
82523
+ return hasTruthyDomain;
82524
+ };
82525
+ const refHasEmptySentinelInitializer = (refSymbol, scopes) => {
82526
+ const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
82527
+ if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
82528
+ const [initialValue] = initializer.arguments ?? [];
82529
+ return Boolean(!initialValue || !isNodeOfType(initialValue, "SpreadElement") && isEmptySentinel(initialValue, scopes));
82530
+ };
82531
+ const refHasDeclaredType = (refSymbol) => {
82532
+ const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
82533
+ return Boolean(initializer && isNodeOfType(initializer, "CallExpression") && (initializer.typeArguments?.params.length ?? 0) > 0);
80593
82534
  };
80594
82535
  const isSafeRefIdentifierUse = (identifier) => {
80595
82536
  const expressionRoot = findTransparentExpressionRoot(identifier);
@@ -80621,25 +82562,40 @@ const expressionContainsRefCurrent = (expression, refSymbol, scopes) => {
80621
82562
  });
80622
82563
  return didFindRefCurrent;
80623
82564
  };
80624
- const hasNoCompetingRefCurrentWrite = (branchRoot, assignmentExpression, refSymbol, scopes) => {
80625
- let writeCount = 0;
80626
- walkAst(branchRoot, (child) => {
80627
- if (writeCount > 1) return false;
80628
- if (isNodeOfType(child, "AssignmentExpression")) {
80629
- if (expressionContainsRefCurrent(child.left, refSymbol, scopes)) writeCount++;
80630
- return;
82565
+ const isEmptySentinel = (node, scopes) => {
82566
+ const expression = stripParenExpression(node);
82567
+ return isNodeOfType(expression, "Literal") && expression.value === null || isNodeOfType(expression, "Identifier") && expression.name === "undefined" && scopes.isGlobalReference(expression);
82568
+ };
82569
+ const isInitializationInputIndependent = (node, renderOwner, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
82570
+ let isInputIndependent = true;
82571
+ walkAst(node, (child) => {
82572
+ if (!isInputIndependent) return false;
82573
+ if (resolveReactRefSymbol(child, scopes)) return false;
82574
+ if (!isNodeOfType(child, "Identifier")) return;
82575
+ const symbol = scopes.symbolFor(child);
82576
+ if (!symbol) return;
82577
+ if (symbol.kind === "import") return false;
82578
+ if (symbol.kind === "let" || symbol.kind === "var" || symbol.kind === "using") {
82579
+ isInputIndependent = false;
82580
+ return false;
80631
82581
  }
80632
- if (isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") {
80633
- if (expressionContainsRefCurrent(child.argument, refSymbol, scopes)) writeCount++;
80634
- return;
82582
+ if (isOutsideAllFunctions(symbol)) return false;
82583
+ if (symbol.kind === "parameter") {
82584
+ if (symbol.scope.node === renderOwner) isInputIndependent = false;
82585
+ return false;
80635
82586
  }
80636
- if (isNodeOfType(child, "ForInStatement") || isNodeOfType(child, "ForOfStatement")) {
80637
- if (expressionContainsRefCurrent(child.left, refSymbol, scopes)) writeCount++;
82587
+ if (!symbol.initializer || symbol.references.some((reference) => reference.flag !== "read")) {
82588
+ isInputIndependent = false;
82589
+ return false;
80638
82590
  }
82591
+ if (visitedSymbolIds.has(symbol.id)) return false;
82592
+ visitedSymbolIds.add(symbol.id);
82593
+ if (!isInitializationInputIndependent(symbol.initializer, renderOwner, scopes, visitedSymbolIds)) isInputIndependent = false;
82594
+ return false;
80639
82595
  });
80640
- return writeCount === 1 && expressionContainsRefCurrent(assignmentExpression.left, refSymbol, scopes);
82596
+ return isInputIndependent;
80641
82597
  };
80642
- const isEmptySentinel = (node, scopes) => isNodeOfType(node, "Literal") && node.value === null || isNodeOfType(node, "Identifier") && node.name === "undefined" && scopes.isGlobalReference(node);
82598
+ const isPredictableInitializationValue = (node, refSymbol, renderOwner, scopes, requiresClosedTruthyDomain) => isInitializationInputIndependent(node, renderOwner, scopes) && !containsNonDeterministicSource(node) && (isProvablyTruthyInitializationValue(node, scopes) && (!requiresClosedTruthyDomain || !refHasDeclaredType(refSymbol)) || refHasClosedFalsySentinelDomain(refSymbol, node, scopes));
80643
82599
  const hasRepeatedExecutionAncestor = (node, stop) => {
80644
82600
  let ancestor = node.parent;
80645
82601
  while (ancestor && ancestor !== stop) {
@@ -80669,6 +82625,27 @@ const canExecuteTogether = (firstConstraints, secondConstraints) => {
80669
82625
  }
80670
82626
  return true;
80671
82627
  };
82628
+ const hasNoCoExecutableCompetingWrite = (assignmentExpression, renderOwner, refSymbol, scopes) => {
82629
+ const assignmentConstraints = getBranchConstraints(assignmentExpression, renderOwner);
82630
+ const synchronouslyInvokedFunctions = collectSynchronouslyEffectInvokedFunctions(renderOwner, scopes);
82631
+ let hasCompetingWrite = false;
82632
+ walkAst(renderOwner, (child) => {
82633
+ if (hasCompetingWrite) return false;
82634
+ let writtenExpression = null;
82635
+ if (isNodeOfType(child, "AssignmentExpression")) {
82636
+ if (child === assignmentExpression) return;
82637
+ writtenExpression = child.left;
82638
+ } else if (isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") writtenExpression = child.argument;
82639
+ else if (isNodeOfType(child, "ForInStatement") || isNodeOfType(child, "ForOfStatement")) writtenExpression = child.left;
82640
+ const deferredExecutionBoundary = findDeferredExecutionBoundary(child);
82641
+ const deferredWriteValue = isNodeOfType(child, "AssignmentExpression") && child.operator === "=" ? resolveImmutableInitializationValue(child.right, scopes) : null;
82642
+ const isDeferredTruthyWrite = deferredExecutionBoundary !== null && deferredExecutionBoundary !== renderOwner && !synchronouslyInvokedFunctions.has(deferredExecutionBoundary) && !executesDuringRender(deferredExecutionBoundary, scopes) && deferredWriteValue !== null && !isNodeOfType(deferredWriteValue, "CallExpression") && isProvablyTruthyInitializationValue(deferredWriteValue, scopes);
82643
+ if (!writtenExpression || isDeferredTruthyWrite || !expressionContainsRefCurrent(writtenExpression, refSymbol, scopes) || !canExecuteTogether(assignmentConstraints, getBranchConstraints(child, renderOwner))) return;
82644
+ hasCompetingWrite = true;
82645
+ return false;
82646
+ });
82647
+ return !hasCompetingWrite;
82648
+ };
80672
82649
  const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol, scopes) => {
80673
82650
  const assignmentConstraints = getBranchConstraints(assignmentExpression, branchRoot);
80674
82651
  const assignmentStart = getRangeStart(assignmentExpression);
@@ -80684,16 +82661,17 @@ const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol
80684
82661
  });
80685
82662
  return !hasCoExecutableWrite;
80686
82663
  };
82664
+ const isPredictableGuardedInitialization = (assignmentExpression, guardedBranch, renderOwner, refSymbol, scopes, requiresClosedTruthyDomain) => refHasEmptySentinelInitializer(refSymbol, scopes) && isPredictableInitializationValue(assignmentExpression.right, refSymbol, renderOwner, scopes, requiresClosedTruthyDomain) && !hasRepeatedExecutionAncestor(assignmentExpression, guardedBranch) && (guardedBranch === renderOwner || !hasRepeatedExecutionAncestor(guardedBranch, renderOwner)) && hasNoPriorCoExecutableWrite(assignmentExpression, renderOwner, refSymbol, scopes) && hasNoCoExecutableCompetingWrite(assignmentExpression, renderOwner, refSymbol, scopes) && refDoesNotEscape(renderOwner, refSymbol, scopes);
80687
82665
  const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
80688
- if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return true;
80689
- if (assignmentExpression.operator !== "=") return false;
80690
82666
  const renderOwner = findRenderPhaseComponentOrHook(assignmentExpression, scopes);
80691
82667
  if (!renderOwner) return false;
82668
+ if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return isPredictableGuardedInitialization(assignmentExpression, renderOwner, renderOwner, refSymbol, scopes, assignmentExpression.operator === "||=");
82669
+ if (assignmentExpression.operator !== "=") return false;
80692
82670
  let descendant = assignmentExpression;
80693
82671
  let ancestor = descendant.parent;
80694
82672
  while (ancestor) {
80695
82673
  const test = isNodeOfType(ancestor, "IfStatement") ? stripParenExpression(ancestor.test) : null;
80696
- if (isNodeOfType(ancestor, "IfStatement") && test && isNodeOfType(test, "UnaryExpression") && test.operator === "!" && isSameRefCurrentAlias(test.argument, refSymbol, scopes) && ancestor.consequent === descendant && isProvablyTruthyInitializationValue(assignmentExpression.right, scopes) && refHasClosedFalsySentinelDomain(refSymbol, assignmentExpression.right, scopes) && !hasRepeatedExecutionAncestor(assignmentExpression, ancestor.consequent) && !hasRepeatedExecutionAncestor(ancestor, renderOwner) && hasNoPriorCoExecutableWrite(assignmentExpression, ancestor.consequent, refSymbol, scopes) && hasNoCompetingRefCurrentWrite(renderOwner, assignmentExpression, refSymbol, scopes) && refDoesNotEscape(renderOwner, refSymbol, scopes)) return true;
82674
+ if (isNodeOfType(ancestor, "IfStatement") && test && isNodeOfType(test, "UnaryExpression") && test.operator === "!" && isSameRefCurrentAlias(test.argument, refSymbol, scopes) && ancestor.consequent === descendant && isPredictableGuardedInitialization(assignmentExpression, ancestor.consequent, renderOwner, refSymbol, scopes, true)) return true;
80697
82675
  if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(test, "BinaryExpression") && [
80698
82676
  "===",
80699
82677
  "==",
@@ -80703,7 +82681,7 @@ const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes)
80703
82681
  const { left, right } = test;
80704
82682
  const comparesEmptySentinel = isSameRefCurrentAlias(left, refSymbol, scopes) && isEmptySentinel(right, scopes) || isSameRefCurrentAlias(right, refSymbol, scopes) && isEmptySentinel(left, scopes);
80705
82683
  const guardedBranch = test.operator === "===" || test.operator === "==" ? ancestor.consequent : ancestor.alternate;
80706
- if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch && !hasRepeatedExecutionAncestor(assignmentExpression, guardedBranch) && hasNoPriorCoExecutableWrite(assignmentExpression, guardedBranch, refSymbol, scopes)) return true;
82684
+ if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch && isPredictableGuardedInitialization(assignmentExpression, guardedBranch, renderOwner, refSymbol, scopes, false)) return true;
80707
82685
  }
80708
82686
  descendant = ancestor;
80709
82687
  ancestor = descendant.parent;
@@ -88308,6 +90286,11 @@ const noUndersizedIconButton = defineRule({
88308
90286
  //#region src/plugin/rules/correctness/no-unescaped-dynamic-string-in-regexp.ts
88309
90287
  const TEST_CONTEXT_FILE_PATTERN = /(\.test\.|\.spec\.|__tests__|(^|\/)(test|tests|e2e|cypress|playwright)\/)/;
88310
90288
  const SEARCH_TERM_NAME_PATTERN = /search|query|highlight|filter|term(?!in)|keyword/i;
90289
+ const REGEX_SOURCE_WORDS = [
90290
+ "pattern",
90291
+ "regex",
90292
+ "regexp"
90293
+ ];
88311
90294
  const ESCAPE_HELPER_NAME_PATTERN = /escape.*reg|safe.*reg/i;
88312
90295
  const SANITIZED_NAME_PATTERN = /escap|sanitiz/i;
88313
90296
  const INITIALIZER_RESOLUTION_HOPS = 2;
@@ -88461,14 +90444,70 @@ const isTypePositionIdentifier = (identifier) => {
88461
90444
  }
88462
90445
  return false;
88463
90446
  };
88464
- const collectRawSearchTermIdentifiers = (argument) => {
88465
- const rawSearchTermIdentifiers = [];
90447
+ const identifierNameHasPathSegmentSemantics = (identifierName) => {
90448
+ const identifierWords = identifierName.replaceAll(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[\s_]+/).map((word) => word.toLowerCase());
90449
+ if (identifierWords.some((word) => REGEX_SOURCE_WORDS.includes(word))) return false;
90450
+ return identifierWords.some((word) => [
90451
+ "path",
90452
+ "folder",
90453
+ "directory",
90454
+ "root"
90455
+ ].includes(word)) || identifierWords.includes("top") && identifierWords.includes("level");
90456
+ };
90457
+ const flattenPatternParts = (expression) => {
90458
+ const inner = stripParenExpression(expression);
90459
+ const staticValue = literalStringValue(inner);
90460
+ if (staticValue !== null) return [staticValue];
90461
+ if (isNodeOfType(inner, "Identifier")) return [inner];
90462
+ if (isNodeOfType(inner, "TemplateLiteral")) {
90463
+ const parts = [];
90464
+ for (let index = 0; index < inner.quasis.length; index += 1) {
90465
+ const quasi = inner.quasis[index];
90466
+ if (quasi) parts.push(quasi.value.cooked ?? quasi.value.raw);
90467
+ const templateExpression = inner.expressions[index];
90468
+ if (templateExpression) parts.push(stripParenExpression(templateExpression));
90469
+ }
90470
+ return parts;
90471
+ }
90472
+ if (isNodeOfType(inner, "BinaryExpression") && inner.operator === "+") {
90473
+ const leftParts = flattenPatternParts(inner.left);
90474
+ const rightParts = flattenPatternParts(inner.right);
90475
+ return leftParts && rightParts ? [...leftParts, ...rightParts] : null;
90476
+ }
90477
+ if (isNodeOfType(inner, "CallExpression") && isNodeOfType(inner.callee, "MemberExpression") && getStaticPropertyName(inner.callee) === "concat") {
90478
+ const receiverParts = flattenPatternParts(inner.callee.object);
90479
+ if (!receiverParts) return null;
90480
+ const parts = [...receiverParts];
90481
+ for (const argument of inner.arguments) {
90482
+ if (isNodeOfType(argument, "SpreadElement")) return null;
90483
+ const argumentParts = flattenPatternParts(argument);
90484
+ if (!argumentParts) return null;
90485
+ parts.push(...argumentParts);
90486
+ }
90487
+ return parts;
90488
+ }
90489
+ return null;
90490
+ };
90491
+ const isIdentifierAnAnchoredPathSegment = (argument, identifier) => {
90492
+ if (!identifierNameHasPathSegmentSemantics(identifier.name)) return false;
90493
+ const parts = flattenPatternParts(argument);
90494
+ if (!parts) return false;
90495
+ const identifierPartIndex = parts.findIndex((part) => part === identifier);
90496
+ if (identifierPartIndex < 0) return false;
90497
+ const precedingParts = parts.slice(0, identifierPartIndex);
90498
+ if (precedingParts.some((part) => typeof part !== "string")) return false;
90499
+ const staticPrefix = precedingParts.join("");
90500
+ const followingPart = parts[identifierPartIndex + 1];
90501
+ return staticPrefix === "^" && typeof followingPart === "string" && followingPart.startsWith("/");
90502
+ };
90503
+ const collectRawDynamicLiteralIdentifiers = (argument) => {
90504
+ const rawDynamicLiteralIdentifiers = [];
88466
90505
  walkAst(argument, (child) => {
88467
90506
  if (isEscapingCall(child) || isRegexSourceAccess(child)) return false;
88468
90507
  if (isLiteralReturningGetterCall(child)) return false;
88469
- if (isNodeOfType(child, "Identifier") && SEARCH_TERM_NAME_PATTERN.test(child.name) && !isPropertyNamePosition(child) && !isTypePositionIdentifier(child)) rawSearchTermIdentifiers.push(child);
90508
+ if (isNodeOfType(child, "Identifier") && (SEARCH_TERM_NAME_PATTERN.test(child.name) || isIdentifierAnAnchoredPathSegment(argument, child)) && !isPropertyNamePosition(child) && !isTypePositionIdentifier(child)) rawDynamicLiteralIdentifiers.push(child);
88470
90509
  });
88471
- return rawSearchTermIdentifiers;
90510
+ return rawDynamicLiteralIdentifiers;
88472
90511
  };
88473
90512
  const collectLeafIdentifiers = (node) => {
88474
90513
  const leafIdentifiers = [];
@@ -88479,8 +90518,11 @@ const collectLeafIdentifiers = (node) => {
88479
90518
  return leafIdentifiers;
88480
90519
  };
88481
90520
  const compositeInitializerResolvesEscaped = (strippedInitializer, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames) => {
90521
+ if (isNodeOfType(strippedInitializer, "ConditionalExpression")) return [strippedInitializer.consequent, strippedInitializer.alternate].every((branch) => initializerLooksEscaped(branch, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames));
90522
+ if (isNodeOfType(strippedInitializer, "BinaryExpression") || isNodeOfType(strippedInitializer, "LogicalExpression")) return [strippedInitializer.left, strippedInitializer.right].every((operand) => initializerLooksEscaped(operand, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames));
90523
+ if (isNodeOfType(strippedInitializer, "TemplateLiteral")) return strippedInitializer.expressions.every((expression) => initializerLooksEscaped(expression, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames));
88482
90524
  let didResolveAnyLeafEscaped = false;
88483
- for (const leafIdentifier of collectLeafIdentifiers(strippedInitializer)) if (identifierResolvesToEscapedValue(leafIdentifier, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames)) didResolveAnyLeafEscaped = true;
90525
+ for (const leafIdentifier of collectLeafIdentifiers(strippedInitializer)) if (identifierResolvesToEscapedValue(leafIdentifier, remainingHops - 1, scopes, regexpObjectSymbolIds, globalRegExpObjectNames)) didResolveAnyLeafEscaped = true;
88484
90526
  else if (SEARCH_TERM_NAME_PATTERN.test(leafIdentifier.name)) return false;
88485
90527
  return didResolveAnyLeafEscaped;
88486
90528
  };
@@ -88492,7 +90534,7 @@ const initializerLooksEscaped = (initializer, remainingHops, scopes, regexpObjec
88492
90534
  if (isNodeOfType(strippedInitializer, "CallExpression") && (isEscapingCall(strippedInitializer) || calleeBindingBodyEscapes(strippedInitializer))) return true;
88493
90535
  if (remainingHops > 0) {
88494
90536
  if (isNodeOfType(strippedInitializer, "Identifier")) return identifierResolvesToEscapedValue(strippedInitializer, remainingHops - 1, scopes, regexpObjectSymbolIds, globalRegExpObjectNames);
88495
- return compositeInitializerResolvesEscaped(strippedInitializer, remainingHops - 1, scopes, regexpObjectSymbolIds, globalRegExpObjectNames);
90537
+ return compositeInitializerResolvesEscaped(strippedInitializer, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames);
88496
90538
  }
88497
90539
  return false;
88498
90540
  };
@@ -88684,7 +90726,7 @@ const noUnescapedDynamicStringInRegexp = defineRule({
88684
90726
  severity: "warn",
88685
90727
  category: "Correctness",
88686
90728
  tags: ["test-noise"],
88687
- recommendation: "A search/filter/highlight term dropped straight into `new RegExp(...)` lets its regex metacharacters act as operators, so a user typing `.` or `(` over-matches or throws. Escape the value with an `escapeRegExp` helper before constructing the pattern.",
90729
+ recommendation: "A dynamic literal string such as a search term or path segment dropped straight into `new RegExp(...)` lets its regex metacharacters act as operators, so values containing `.` or `(` over-match or throw. Escape the value with an `escapeRegExp` helper before constructing the pattern.",
88688
90730
  create: (context) => {
88689
90731
  if (TEST_CONTEXT_FILE_PATTERN.test(context.filename ?? "")) return {};
88690
90732
  let regexpObjectIndex = null;
@@ -88701,10 +90743,10 @@ const noUnescapedDynamicStringInRegexp = defineRule({
88701
90743
  regexpObjectIndex = buildRegExpObjectIndex(programRoot, context.scopes);
88702
90744
  }
88703
90745
  const currentRegExpObjectIndex = regexpObjectIndex;
88704
- if (!collectRawSearchTermIdentifiers(firstArgument).some((identifier) => !identifierResolvesToEscapedValue(identifier, INITIALIZER_RESOLUTION_HOPS, context.scopes, currentRegExpObjectIndex.regexpObjectSymbolIds, currentRegExpObjectIndex.globalRegExpObjectNames) && !isShapeTestedByDominatingGuard(node, identifier, context.scopes) && !isParameterFedOnlyMetacharacterFreeLiterals(identifier, context.scopes))) return;
90746
+ if (!collectRawDynamicLiteralIdentifiers(firstArgument).some((identifier) => !identifierResolvesToEscapedValue(identifier, INITIALIZER_RESOLUTION_HOPS, context.scopes, currentRegExpObjectIndex.regexpObjectSymbolIds, currentRegExpObjectIndex.globalRegExpObjectNames) && !isShapeTestedByDominatingGuard(node, identifier, context.scopes) && !isParameterFedOnlyMetacharacterFreeLiterals(identifier, context.scopes))) return;
88705
90747
  context.report({
88706
90748
  node,
88707
- message: "This builds a `RegExp` from a dynamic search/filter term without escaping it, so regex metacharacters in the value act as operators and over-match or throw. Escape the value with an `escapeRegExp` helper first."
90749
+ message: "This builds a `RegExp` from a dynamic literal string without escaping it, so regex metacharacters in the value act as operators and over-match or throw. Escape the value with an `escapeRegExp` helper first."
88708
90750
  });
88709
90751
  };
88710
90752
  return {
@@ -108446,9 +110488,6 @@ const rerenderFunctionalSetstate = defineRule({
108446
110488
  } })
108447
110489
  });
108448
110490
  //#endregion
108449
- //#region src/plugin/utils/is-trivial-built-in-construction.ts
108450
- const isTrivialBuiltInConstruction = (expression) => isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && TRIVIAL_CONSTRUCTOR_NAMES.has(expression.callee.name) && (expression.arguments ?? []).length === 0;
108451
- //#endregion
108452
110491
  //#region src/plugin/rules/state-and-effects/rerender-lazy-ref-init.ts
108453
110492
  const rerenderLazyRefInit = defineRule({
108454
110493
  id: "rerender-lazy-ref-init",
@@ -108467,7 +110506,6 @@ const rerenderLazyRefInit = defineRule({
108467
110506
  const memberPropertyName = isNodeOfType(callee, "MemberExpression") && (isNodeOfType(callee.property, "Identifier") || isNodeOfType(callee.property, "PrivateIdentifier")) ? callee.property.name : null;
108468
110507
  const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : memberPropertyName ?? "fn";
108469
110508
  if (TRIVIAL_INITIALIZER_NAMES.has(calleeName)) return;
108470
- if (isTrivialBuiltInConstruction(initializer)) return;
108471
110509
  if (isPlainCall && isReactHookName(calleeName)) return;
108472
110510
  const callShape = isNewCall ? `new ${calleeName}()` : `${calleeName}()`;
108473
110511
  context.report({
@@ -140049,6 +142087,7 @@ const shouldReadSecurityScanContent = (relativePath, isGeneratedBundle) => isGen
140049
142087
  //#region src/plugin/utils/capability.ts
140050
142088
  const FRAMEWORK_TOKENS = [
140051
142089
  "nextjs",
142090
+ "astro",
140052
142091
  "vite",
140053
142092
  "cra",
140054
142093
  "remix",