oxlint-plugin-react-doctor 0.7.7-dev.30b7eeb → 0.7.7-dev.4190897

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +2424 -327
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4573,6 +4573,23 @@ const hasPossibleStaticPropertyWrite = (identifier, propertyName, scopes) => {
4573
4573
  return writtenPropertyName === null || writtenPropertyName === propertyName;
4574
4574
  }));
4575
4575
  };
4576
+ const canExecuteBefore = (candidateNode, referenceNode, scopes) => {
4577
+ const candidateBoundary = findExecutionBoundary(candidateNode);
4578
+ const referenceBoundary = findExecutionBoundary(referenceNode);
4579
+ if (!candidateBoundary || !referenceBoundary) return true;
4580
+ if (candidateBoundary === referenceBoundary) return candidateNode.range[0] < referenceNode.range[0];
4581
+ if (!isFunctionLike$1(candidateBoundary)) return true;
4582
+ return isFunctionSynchronouslyInvokedBefore(candidateBoundary, referenceNode, scopes);
4583
+ };
4584
+ const hasPossibleStaticPropertyWriteBefore = (identifier, propertyName, referenceNode, scopes) => {
4585
+ if (!isNodeOfType(identifier, "Identifier")) return false;
4586
+ return getPotentiallyAliasedSymbols(identifier, scopes).some((symbol) => symbol.references.some((reference) => {
4587
+ const writeTarget = getMemberWriteTarget(reference.identifier);
4588
+ if (!writeTarget || !canExecuteBefore(writeTarget, referenceNode, scopes)) return false;
4589
+ const writtenPropertyName = getResolvedStaticPropertyName(writeTarget, scopes);
4590
+ return writtenPropertyName === null || writtenPropertyName === propertyName;
4591
+ }));
4592
+ };
4576
4593
  const hasPossibleStaticPropertyMutationOrEscape = (identifier, propertyName, scopes) => {
4577
4594
  if (!isNodeOfType(identifier, "Identifier")) return false;
4578
4595
  if (hasPossibleStaticPropertyWrite(identifier, propertyName, scopes)) return true;
@@ -10558,7 +10575,7 @@ const getAssignedName = (node) => {
10558
10575
  }
10559
10576
  return null;
10560
10577
  };
10561
- const isModuleExportsAssignment = (node) => {
10578
+ const isModuleExportsAssignment$1 = (node) => {
10562
10579
  const parent = node.parent;
10563
10580
  if (!parent || !isNodeOfType(parent, "AssignmentExpression")) return false;
10564
10581
  const left = parent.left;
@@ -10717,7 +10734,7 @@ const displayName = defineRule({
10717
10734
  }
10718
10735
  const assignedName = getAssignedName(node);
10719
10736
  if (assignedName && isReactComponentName(assignedName) && ignoreNamed) return;
10720
- if (isModuleExportsAssignment(node) && !node.id) {
10737
+ if (isModuleExportsAssignment$1(node) && !node.id) {
10721
10738
  reportAt(node);
10722
10739
  return;
10723
10740
  }
@@ -10739,7 +10756,7 @@ const displayName = defineRule({
10739
10756
  reportAt(node);
10740
10757
  return;
10741
10758
  }
10742
- if (isModuleExportsAssignment(node)) {
10759
+ if (isModuleExportsAssignment$1(node)) {
10743
10760
  reportAt(node);
10744
10761
  return;
10745
10762
  }
@@ -11673,6 +11690,25 @@ const getFunctionBindingName$1 = (functionNode) => getFunctionBindingIdentifier(
11673
11690
  //#region src/plugin/utils/is-event-handler-attribute.ts
11674
11691
  const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && /^on[A-Z]/.test(node.name.name);
11675
11692
  //#endregion
11693
+ //#region src/plugin/utils/is-ast-descendant.ts
11694
+ /**
11695
+ * True when `inner` is `outer` itself or any descendant in the AST
11696
+ * `parent` chain. Walks `inner.parent` upward and stops at either a
11697
+ * match (`true`) or the chain's root (`false`).
11698
+ *
11699
+ * Was duplicated byte-identical in:
11700
+ * - exhaustive-deps-symbol-stability.ts
11701
+ * - semantic/closure-captures.ts
11702
+ */
11703
+ const isAstDescendant = (inner, outer) => {
11704
+ let current = inner;
11705
+ while (current) {
11706
+ if (current === outer) return true;
11707
+ current = current.parent ?? null;
11708
+ }
11709
+ return false;
11710
+ };
11711
+ //#endregion
11676
11712
  //#region src/plugin/utils/react-ref-origin.ts
11677
11713
  const resolveReactRefSymbol = (memberExpression, scopes) => {
11678
11714
  const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
@@ -12322,26 +12358,39 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
12322
12358
  });
12323
12359
  return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
12324
12360
  };
12361
+ const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => {
12362
+ if (usage.handleKey === null) return null;
12363
+ const doesTestRequireLiveHandle = (test) => {
12364
+ if (resolveExpressionKey(test, context) === usage.handleKey) return true;
12365
+ const unwrappedTest = stripParenExpression(test);
12366
+ if (!isNodeOfType(unwrappedTest, "BinaryExpression") || unwrappedTest.operator !== "!=" && unwrappedTest.operator !== "!==") return false;
12367
+ const isNullishOperand = (operand) => {
12368
+ const unwrappedOperand = stripParenExpression(operand);
12369
+ return isNodeOfType(unwrappedOperand, "Literal") && unwrappedOperand.value === null || isNodeOfType(unwrappedOperand, "Identifier") && unwrappedOperand.name === "undefined" && context.scopes.isGlobalReference(unwrappedOperand);
12370
+ };
12371
+ return resolveExpressionKey(unwrappedTest.left, context) === usage.handleKey && isNullishOperand(unwrappedTest.right) || resolveExpressionKey(unwrappedTest.right, context) === usage.handleKey && isNullishOperand(unwrappedTest.left);
12372
+ };
12373
+ let ancestor = releaseCall.parent;
12374
+ while (ancestor && ancestor !== owner) {
12375
+ if (isNodeOfType(ancestor, "IfStatement")) {
12376
+ if (ancestor.alternate !== null || !doesTestRequireLiveHandle(ancestor.test) || !doMatchingNodesCoverEveryPathAfterUsage(ancestor.consequent, [releaseCall], context)) return null;
12377
+ return ancestor;
12378
+ }
12379
+ ancestor = ancestor.parent;
12380
+ }
12381
+ return null;
12382
+ };
12325
12383
  const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
12326
12384
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
12327
12385
  const functionCfg = context.cfg.cfgFor(callback);
12328
12386
  const usageBlock = functionCfg?.blockOf(usage.node);
12329
12387
  const usageStart = getRangeStart(usage.node);
12330
12388
  if (!functionCfg || !usageBlock || usageStart === null) return false;
12331
- const findHandleGuard = (releaseCall) => {
12332
- if (usage.handleKey === null) return null;
12333
- let ancestor = releaseCall.parent;
12334
- while (ancestor && ancestor !== callback.body) {
12335
- if (isNodeOfType(ancestor, "IfStatement")) return ancestor.alternate === null && resolveExpressionKey(ancestor.test, context) === usage.handleKey && getRangeStart(ancestor) !== null && (getRangeStart(ancestor) ?? usageStart) < usageStart ? ancestor : null;
12336
- ancestor = ancestor.parent;
12337
- }
12338
- return null;
12339
- };
12340
12389
  const matchingReleaseAnchors = [];
12341
12390
  walkInsideStatementBlocks(callback.body, (child) => {
12342
12391
  if (!isNodeOfType(child, "CallExpression")) return;
12343
12392
  const releaseStart = getRangeStart(child);
12344
- const handleGuard = findHandleGuard(child);
12393
+ const handleGuard = findDirectHandleGuardForRelease(child, callback, usage, context);
12345
12394
  if (releaseStart === null || releaseStart >= usageStart || functionCfg.blockOf(child) !== usageBlock && !handleGuard) return;
12346
12395
  if (doesReleaseCallMatchUsage(child, usage, context)) {
12347
12396
  matchingReleaseAnchors.push(handleGuard ?? child);
@@ -12371,12 +12420,12 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
12371
12420
  return didFindUnmountCleanup;
12372
12421
  };
12373
12422
  const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
12374
- const collectBlockingBooleanStates = (expression, blockedExpressionValue, context) => {
12423
+ const collectBlockingBooleanStates = (expression, blockedExpressionValue, guardNode, context) => {
12375
12424
  const unwrappedExpression = stripParenExpression(expression);
12376
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, context);
12425
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, guardNode, context);
12377
12426
  if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
12378
12427
  if (!(unwrappedExpression.operator === "||" && blockedExpressionValue || unwrappedExpression.operator === "&&" && !blockedExpressionValue)) return [];
12379
- return [...collectBlockingBooleanStates(unwrappedExpression.left, blockedExpressionValue, context), ...collectBlockingBooleanStates(unwrappedExpression.right, blockedExpressionValue, context)];
12428
+ return [...collectBlockingBooleanStates(unwrappedExpression.left, blockedExpressionValue, guardNode, context), ...collectBlockingBooleanStates(unwrappedExpression.right, blockedExpressionValue, guardNode, context)];
12380
12429
  }
12381
12430
  if (isNodeOfType(unwrappedExpression, "BinaryExpression") && [
12382
12431
  "===",
@@ -12387,35 +12436,59 @@ const collectBlockingBooleanStates = (expression, blockedExpressionValue, contex
12387
12436
  const leftValue = readStaticBoolean(unwrappedExpression.left);
12388
12437
  const rightValue = readStaticBoolean(unwrappedExpression.right);
12389
12438
  const booleanValue = leftValue ?? rightValue;
12390
- const comparedKey = resolveExpressionKey(leftValue === null ? unwrappedExpression.left : unwrappedExpression.right, context);
12439
+ const comparedExpression = leftValue === null ? unwrappedExpression.left : unwrappedExpression.right;
12440
+ const comparedKey = resolveExpressionKey(comparedExpression, context);
12391
12441
  if (booleanValue === null || comparedKey === null) return [];
12442
+ const isEquality = unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==";
12392
12443
  return [{
12444
+ bindingIdentifier: isNodeOfType(comparedExpression, "Identifier") ? context.scopes.symbolFor(comparedExpression)?.bindingIdentifier ?? null : null,
12445
+ guardNode,
12393
12446
  key: comparedKey,
12394
- value: (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==") === blockedExpressionValue ? booleanValue : !booleanValue
12447
+ value: isEquality === blockedExpressionValue ? booleanValue : !booleanValue
12395
12448
  }];
12396
12449
  }
12397
12450
  const expressionKey = resolveExpressionKey(unwrappedExpression, context);
12398
12451
  return expressionKey === null ? [] : [{
12452
+ bindingIdentifier: isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression)?.bindingIdentifier ?? null : null,
12453
+ guardNode,
12399
12454
  key: expressionKey,
12400
12455
  value: blockedExpressionValue
12401
12456
  }];
12402
12457
  };
12403
- const isDirectEarlyReturnConsequent = (ifStatement) => {
12404
- if (!isNodeOfType(ifStatement, "IfStatement") || ifStatement.alternate) return false;
12405
- if (isNodeOfType(ifStatement.consequent, "ReturnStatement")) return true;
12406
- return isNodeOfType(ifStatement.consequent, "BlockStatement") && ifStatement.consequent.body.length === 1 && isNodeOfType(ifStatement.consequent.body[0], "ReturnStatement");
12458
+ const canNodeReachLaterNodeWithinFunction = (sourceNode, targetNode, owner, context) => {
12459
+ const functionCfg = context.cfg.cfgFor(owner);
12460
+ const sourceBlock = functionCfg?.blockOf(sourceNode);
12461
+ const targetBlock = functionCfg?.blockOf(targetNode);
12462
+ const sourceStart = getRangeStart(sourceNode);
12463
+ const targetStart = getRangeStart(targetNode);
12464
+ if (!functionCfg || !sourceBlock || !targetBlock || sourceStart === null || targetStart === null) return true;
12465
+ if (!isNodeReachableWithinFunction(sourceNode, context)) return false;
12466
+ if (sourceBlock === targetBlock) return sourceStart < targetStart;
12467
+ const visitedBlocks = new Set([sourceBlock]);
12468
+ const pendingBlocks = [sourceBlock];
12469
+ while (pendingBlocks.length > 0) {
12470
+ const currentBlock = pendingBlocks.pop();
12471
+ if (!currentBlock) break;
12472
+ for (const edge of currentBlock.successors) {
12473
+ if (edge.to === targetBlock) return true;
12474
+ if (visitedBlocks.has(edge.to)) continue;
12475
+ visitedBlocks.add(edge.to);
12476
+ pendingBlocks.push(edge.to);
12477
+ }
12478
+ }
12479
+ return false;
12407
12480
  };
12408
12481
  const collectDeferredUsageGuardStates = (callback, usageNode, context) => {
12409
12482
  if (!isFunctionLike$1(callback) || callback.async) return [];
12410
12483
  const guardStates = [];
12411
12484
  walkAst(callback.body, (child) => {
12412
12485
  if (child !== callback.body && isFunctionLike$1(child)) return false;
12413
- if (isNodeOfType(child, "IfStatement") && isDirectEarlyReturnConsequent(child) && doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], callback, context)) guardStates.push(...collectBlockingBooleanStates(child.test, true, context));
12486
+ if (isNodeOfType(child, "IfStatement") && !child.alternate && !canNodeReachLaterNodeWithinFunction(child.consequent, usageNode, callback, context) && doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], callback, context)) guardStates.push(...collectBlockingBooleanStates(child.test, true, child, context));
12414
12487
  });
12415
12488
  let descendant = usageNode;
12416
12489
  let ancestor = descendant.parent;
12417
12490
  while (ancestor && ancestor !== callback) {
12418
- if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant) guardStates.push(...collectBlockingBooleanStates(ancestor.test, false, context));
12491
+ if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant) guardStates.push(...collectBlockingBooleanStates(ancestor.test, false, ancestor, context));
12419
12492
  descendant = ancestor;
12420
12493
  ancestor = ancestor.parent;
12421
12494
  }
@@ -12424,7 +12497,7 @@ const collectDeferredUsageGuardStates = (callback, usageNode, context) => {
12424
12497
  const cleanupReturnInvalidatesGuard = (cleanupReturn, guardState, context) => {
12425
12498
  if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
12426
12499
  const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
12427
- if (!cleanupFunction || !isFunctionLike$1(cleanupFunction) || cleanupFunction.async) return false;
12500
+ if (!cleanupFunction || !isFunctionLike$1(cleanupFunction) || cleanupFunction.async || cleanupFunction.generator) return false;
12428
12501
  let didInvalidateGuard = false;
12429
12502
  walkAst(cleanupFunction.body, (child) => {
12430
12503
  if (didInvalidateGuard) return false;
@@ -12453,11 +12526,110 @@ const deferredUsageWritesGuardBeforeUsage = (callback, usageNode, guardState, co
12453
12526
  });
12454
12527
  return didWriteGuard;
12455
12528
  };
12529
+ const canInterruptionReachUsageThroughCatch = (interruptionNode, usageNode, owner, context) => {
12530
+ let descendant = interruptionNode;
12531
+ let ancestor = descendant.parent;
12532
+ while (ancestor && ancestor !== owner) {
12533
+ if (isNodeOfType(ancestor, "TryStatement") && ancestor.block === descendant && ancestor.handler && canNodeReachLaterNodeWithinFunction(ancestor.handler.body, usageNode, owner, context)) return true;
12534
+ descendant = ancestor;
12535
+ ancestor = ancestor.parent;
12536
+ }
12537
+ return false;
12538
+ };
12539
+ const isEffectLocalLifecycleGuard = (callback, guardState, cleanupFunctions, context) => {
12540
+ if (!guardState.bindingIdentifier) return false;
12541
+ const guardSymbol = context.scopes.symbolFor(guardState.bindingIdentifier);
12542
+ if (!guardSymbol || guardSymbol.kind !== "let" && guardSymbol.kind !== "var" || !isNodeOfType(guardSymbol.declarationNode, "VariableDeclarator") || findEnclosingFunction$1(guardSymbol.declarationNode) !== callback) return false;
12543
+ return guardSymbol.references.every((reference) => {
12544
+ if (!isWithinAssignmentTarget(reference.identifier)) return true;
12545
+ const assignmentTarget = findTransparentExpressionRoot(reference.identifier);
12546
+ const assignment = assignmentTarget.parent;
12547
+ return isNodeOfType(assignment, "AssignmentExpression") && assignment.operator === "=" && assignment.left === assignmentTarget && readStaticBoolean(assignment.right) === guardState.value && cleanupFunctions.includes(findEnclosingFunction$1(assignment) ?? assignment);
12548
+ });
12549
+ };
12550
+ const hasPotentialInterruptionAfterGuard = (callback, guardState, usageNode, context) => {
12551
+ if (!isFunctionLike$1(callback)) return true;
12552
+ const guardStart = getRangeStart(guardState.guardNode);
12553
+ const usageStart = getRangeStart(usageNode);
12554
+ if (guardStart === null || usageStart === null) return true;
12555
+ let hasPotentialInterruption = false;
12556
+ walkAst(callback.body, (child) => {
12557
+ if (hasPotentialInterruption) return false;
12558
+ if (child !== callback.body && isFunctionLike$1(child)) return false;
12559
+ const childStart = getRangeStart(child);
12560
+ if (childStart === null || childStart <= guardStart || childStart >= usageStart) return;
12561
+ if (isNodeOfType(child, "CallExpression") || isNodeOfType(child, "AwaitExpression") || isNodeOfType(child, "YieldExpression")) {
12562
+ if (canNodeReachLaterNodeWithinFunction(child, usageNode, callback, context) || canInterruptionReachUsageThroughCatch(child, usageNode, callback, context)) {
12563
+ hasPotentialInterruption = true;
12564
+ return false;
12565
+ }
12566
+ }
12567
+ });
12568
+ return hasPotentialInterruption;
12569
+ };
12456
12570
  const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
12457
12571
  const usageFunction = findEnclosingFunction$1(usage.node);
12458
12572
  const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
12459
- if (usage.handleKey === null || !usageFunction || usageFunction === callback || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
12460
- return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
12573
+ if (usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usage.node, "CallExpression") || !isNodeOfType(usage.node.callee, "Identifier") || !context.scopes.isGlobalReference(usage.node.callee) || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
12574
+ const usageExpression = findTransparentExpressionRoot(usage.node);
12575
+ const usageAssignment = usageExpression.parent;
12576
+ if (!isNodeOfType(usageAssignment, "AssignmentExpression") || usageAssignment.operator !== "=" || usageAssignment.right !== usageExpression || !isNodeOfType(usageAssignment.left, "Identifier")) return false;
12577
+ const handleSymbol = context.scopes.symbolFor(usageAssignment.left);
12578
+ if (!handleSymbol || handleSymbol.kind !== "let" && handleSymbol.kind !== "var" || !isNodeOfType(handleSymbol.declarationNode, "VariableDeclarator") || findEnclosingFunction$1(handleSymbol.declarationNode) !== callback) return false;
12579
+ const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
12580
+ if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
12581
+ const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
12582
+ return cleanupFunction && isFunctionLike$1(cleanupFunction) ? [cleanupFunction] : [];
12583
+ });
12584
+ if (cleanupFunctions.length !== cleanupReturns.length) return false;
12585
+ const globalReleaseProofsByCleanup = /* @__PURE__ */ new Map();
12586
+ for (const cleanupFunction of cleanupFunctions) {
12587
+ if (!isFunctionLike$1(cleanupFunction)) return false;
12588
+ const globalReleaseProofs = [];
12589
+ walkAst(cleanupFunction.body, (child) => {
12590
+ if (child !== cleanupFunction.body && isFunctionLike$1(child)) return false;
12591
+ if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && context.scopes.isGlobalReference(child.callee) && doesReleaseCallMatchUsage(child, usage, context)) {
12592
+ const handleGuard = findDirectHandleGuardForRelease(child, cleanupFunction, usage, context);
12593
+ globalReleaseProofs.push({
12594
+ anchor: handleGuard ?? child,
12595
+ call: child,
12596
+ handleGuard
12597
+ });
12598
+ }
12599
+ });
12600
+ if (!doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, globalReleaseProofs.map((releaseProof) => releaseProof.anchor), context)) return false;
12601
+ globalReleaseProofsByCleanup.set(cleanupFunction, globalReleaseProofs);
12602
+ }
12603
+ const handleAssignments = handleSymbol.references.filter((reference) => isWithinAssignmentTarget(reference.identifier));
12604
+ const hasUsageAssignment = handleAssignments.some((handleAssignment) => findTransparentExpressionRoot(handleAssignment.identifier).parent === usageAssignment);
12605
+ const hasUnsafeHandleAssignment = handleAssignments.some((handleAssignment) => {
12606
+ const assignmentTarget = findTransparentExpressionRoot(handleAssignment.identifier);
12607
+ const assignment = assignmentTarget.parent;
12608
+ if (assignment === usageAssignment) return false;
12609
+ if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== assignmentTarget) return true;
12610
+ const assignedValue = stripParenExpression(assignment.right);
12611
+ if (!(isNodeOfType(assignedValue, "Literal") && assignedValue.value === null || isNodeOfType(assignedValue, "Identifier") && assignedValue.name === "undefined" && context.scopes.isGlobalReference(assignedValue))) return true;
12612
+ const cleanupFunction = findEnclosingFunction$1(assignment);
12613
+ const globalReleaseProofs = cleanupFunction ? globalReleaseProofsByCleanup.get(cleanupFunction) : void 0;
12614
+ return !(cleanupFunction && globalReleaseProofs && doMatchingNodesCoverEveryPathBeforeUsage(assignment, globalReleaseProofs.map((releaseProof) => releaseProof.handleGuard && isAstDescendant(assignment, releaseProof.handleGuard.consequent) ? releaseProof.call : releaseProof.anchor), cleanupFunction, context));
12615
+ });
12616
+ if (!hasUsageAssignment || hasUnsafeHandleAssignment) return false;
12617
+ let usageAncestor = usage.node.parent;
12618
+ while (usageAncestor && usageAncestor !== usageFunction) {
12619
+ if (isNodeOfType(usageAncestor, "ForStatement") || isNodeOfType(usageAncestor, "ForInStatement") || isNodeOfType(usageAncestor, "ForOfStatement") || isNodeOfType(usageAncestor, "WhileStatement") || isNodeOfType(usageAncestor, "DoWhileStatement")) return false;
12620
+ usageAncestor = usageAncestor.parent;
12621
+ }
12622
+ let hasPotentialInterruption = false;
12623
+ for (const argument of usage.node.arguments ?? []) walkAst(argument, (argumentChild) => {
12624
+ if (hasPotentialInterruption) return false;
12625
+ if (isFunctionLike$1(argumentChild)) return false;
12626
+ if (isNodeOfType(argumentChild, "CallExpression") || isNodeOfType(argumentChild, "AwaitExpression") || isNodeOfType(argumentChild, "YieldExpression")) {
12627
+ hasPotentialInterruption = true;
12628
+ return false;
12629
+ }
12630
+ });
12631
+ if (hasPotentialInterruption) return false;
12632
+ return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => isEffectLocalLifecycleGuard(callback, guardState, cleanupFunctions, context) && !hasPotentialInterruptionAfterGuard(usageFunction, guardState, usage.node, context) && !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
12461
12633
  };
12462
12634
  const effectHasCleanupForUsage = (callback, usage, context) => {
12463
12635
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
@@ -13023,7 +13195,7 @@ const effectNeedsCleanup = defineRule({
13023
13195
  const hookName = getCalleeName$2(node) ?? "effect";
13024
13196
  context.report({
13025
13197
  node,
13026
- message: `\`${firstUsage.resourceName}\` creates a ${resourceNoun} in ${hookName} without returning cleanup. Return a cleanup function so it does not leak after unmount.`
13198
+ message: `\`${firstUsage.resourceName}\` creates a ${resourceNoun} in ${hookName} without guaranteed cleanup. Return a cleanup function that owns every allocation so it does not leak after unmount.`
13027
13199
  });
13028
13200
  },
13029
13201
  FunctionDeclaration(node) {
@@ -14368,6 +14540,22 @@ const isReactHocCallbackArgument = (functionNode) => {
14368
14540
  return calleeName !== null && REACT_HOC_NAMES.has(calleeName);
14369
14541
  };
14370
14542
  //#endregion
14543
+ //#region src/plugin/utils/is-outside-all-functions.ts
14544
+ const FUNCTION_SCOPE_KINDS = new Set([
14545
+ "function",
14546
+ "arrow-function",
14547
+ "method"
14548
+ ]);
14549
+ const isOutsideAllFunctions = (symbol) => {
14550
+ let scope = symbol.scope;
14551
+ while (scope) {
14552
+ if (FUNCTION_SCOPE_KINDS.has(scope.kind)) return false;
14553
+ if (scope.kind === "module") return true;
14554
+ scope = scope.parent;
14555
+ }
14556
+ return true;
14557
+ };
14558
+ //#endregion
14371
14559
  //#region src/plugin/rules/react-builtins/exhaustive-deps-low-level.ts
14372
14560
  /**
14373
14561
  * Lowest-level helpers consumed by both the main `exhaustive-deps`
@@ -14420,25 +14608,6 @@ const getHookName = (callee, scopes) => {
14420
14608
  if (isNodeOfType(strippedCallee, "MemberExpression") && !strippedCallee.computed && isNodeOfType(strippedCallee.property, "Identifier")) return strippedCallee.property.name;
14421
14609
  return null;
14422
14610
  };
14423
- const FUNCTION_SCOPE_KINDS = new Set([
14424
- "function",
14425
- "arrow-function",
14426
- "method"
14427
- ]);
14428
- /**
14429
- * True for symbols declared at module scope (outside any function
14430
- * scope). Module-scope bindings don't change between renders so they
14431
- * don't need to live in dependency arrays.
14432
- */
14433
- const isOutsideAllFunctions = (symbol) => {
14434
- let scope = symbol.scope;
14435
- while (scope) {
14436
- if (FUNCTION_SCOPE_KINDS.has(scope.kind)) return false;
14437
- if (scope.kind === "module") return true;
14438
- scope = scope.parent ?? null;
14439
- }
14440
- return true;
14441
- };
14442
14611
  //#endregion
14443
14612
  //#region src/plugin/rules/react-builtins/exhaustive-deps-messages.ts
14444
14613
  const buildMissingDepMessage = (hookName, depName) => `\`${hookName}\` can run with a stale \`${depName}\` & show your users old data.`;
@@ -14457,7 +14626,7 @@ const buildAsyncEffectMessage = (hookName) => `\`${hookName}\` was given an asyn
14457
14626
  const buildUnknownCallbackMessage = (hookName) => `\`${hookName}\`'s callback is defined elsewhere, so dependencies can't be checked and stale values can slip through.`;
14458
14627
  const buildUnstableDepMessage = (hookName, depName) => `\`${depName}\` is rebuilt every render, so \`${hookName}\` runs every time.`;
14459
14628
  const buildForwardedUnstableDepMessage = (depName) => `\`${depName}\` is rebuilt every render and reaches a Hook dependency inside this custom Hook.`;
14460
- const buildSetStateWithoutDepsMessage = (hookName, setterName) => `\`${hookName}\` calls \`${setterName}\` with no dependency array, so it can loop forever & freeze the component.`;
14629
+ const buildSetStateWithoutDepsMessage = (hookName, setterName) => `\`${hookName}\` calls \`${setterName}\` with a value that can change on every render and no dependency array, so it can keep triggering renders.`;
14461
14630
  const buildRefCleanupMessage = (depName) => `Your cleanup may read the wrong node since the ref \`${depName}\` can change before it runs.`;
14462
14631
  const buildAssignmentMessage = (name) => `Assigning to \`${name}\` inside a hook is thrown away after each render, so the next render reads the old value.`;
14463
14632
  //#endregion
@@ -14476,25 +14645,6 @@ const resolveExhaustiveDepsSettings = (settings) => {
14476
14645
  };
14477
14646
  };
14478
14647
  //#endregion
14479
- //#region src/plugin/utils/is-ast-descendant.ts
14480
- /**
14481
- * True when `inner` is `outer` itself or any descendant in the AST
14482
- * `parent` chain. Walks `inner.parent` upward and stops at either a
14483
- * match (`true`) or the chain's root (`false`).
14484
- *
14485
- * Was duplicated byte-identical in:
14486
- * - exhaustive-deps-symbol-stability.ts
14487
- * - semantic/closure-captures.ts
14488
- */
14489
- const isAstDescendant = (inner, outer) => {
14490
- let current = inner;
14491
- while (current) {
14492
- if (current === outer) return true;
14493
- current = current.parent ?? null;
14494
- }
14495
- return false;
14496
- };
14497
- //#endregion
14498
14648
  //#region src/plugin/rules/react-builtins/exhaustive-deps-sole-writer-guard.ts
14499
14649
  const EQUALITY_BINARY_OPERATORS = new Set(["===", "!=="]);
14500
14650
  const getUseStateSetterSymbol = (stateSymbol, scopes) => {
@@ -15236,26 +15386,125 @@ const isExtraDepAllowedForHook = (hookName, node, scopes) => {
15236
15386
  };
15237
15387
  const hasDirectIdentifierDeclarator = (symbol) => isNodeOfType(symbol.declarationNode, "VariableDeclarator") && isNodeOfType(symbol.declarationNode.id, "Identifier") || isNodeOfType(symbol.declarationNode, "ClassDeclaration");
15238
15388
  const isFunctionValueSymbol = (symbol) => getFunctionValueNode(symbol) !== null;
15239
- const isStableSetterLikeSymbol = (symbol, scopes) => {
15240
- if (!symbolHasStableHookOrigin(symbol, scopes)) return false;
15241
- return symbol.name.startsWith("set") || symbol.name.startsWith("dispatch") || symbol.name.startsWith("startTransition");
15389
+ const FRESH_STATE_CONSTRUCTOR_NAMES = new Set([
15390
+ "Array",
15391
+ "Date",
15392
+ "Error",
15393
+ "Map",
15394
+ "Object",
15395
+ "RegExp",
15396
+ "Set",
15397
+ "WeakMap",
15398
+ "WeakSet"
15399
+ ]);
15400
+ const getBindingIdentifier = (node) => {
15401
+ if (!node) return null;
15402
+ const candidate = isNodeOfType(node, "AssignmentPattern") ? node.left : node;
15403
+ return isNodeOfType(candidate, "Identifier") ? candidate : null;
15242
15404
  };
15243
- const isConvergingFunctionalUpdater = (node, scopes) => {
15244
- const updater = unwrapExpression$3(node);
15245
- if (!isNodeOfType(updater, "ArrowFunctionExpression") && !isNodeOfType(updater, "FunctionExpression")) return false;
15246
- const previousValueParameter = updater.params?.[0];
15247
- if (!previousValueParameter || !isNodeOfType(previousValueParameter, "Identifier")) return false;
15248
- let returnedExpression = updater.body;
15249
- if (isNodeOfType(updater.body, "BlockStatement")) {
15250
- returnedExpression = null;
15251
- for (const statement of updater.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument) {
15252
- returnedExpression = statement.argument;
15253
- break;
15405
+ const resolveStateSetterSymbol = (identifier, scopes) => {
15406
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
15407
+ let symbol = scopes.symbolFor(identifier);
15408
+ while (symbol?.kind === "const" && isNodeOfType(symbol.declarationNode, "VariableDeclarator") && symbol.declarationNode.id === symbol.bindingIdentifier && symbol.initializer) {
15409
+ if (visitedSymbolIds.has(symbol.id)) return null;
15410
+ visitedSymbolIds.add(symbol.id);
15411
+ const initializer = unwrapExpression$3(symbol.initializer);
15412
+ if (!isNodeOfType(initializer, "Identifier")) return symbol;
15413
+ symbol = scopes.symbolFor(initializer);
15414
+ }
15415
+ return symbol;
15416
+ };
15417
+ const getStateSetterDescriptor = (identifier, scopes) => {
15418
+ const setterSymbol = resolveStateSetterSymbol(identifier, scopes);
15419
+ if (!setterSymbol || !isNodeOfType(setterSymbol.declarationNode, "VariableDeclarator")) return null;
15420
+ const declarator = setterSymbol.declarationNode;
15421
+ if (!isNodeOfType(declarator.id, "ArrayPattern")) return null;
15422
+ const setterElement = declarator.id.elements?.[1];
15423
+ const setterBinding = isAstNode(setterElement) ? getBindingIdentifier(setterElement) : null;
15424
+ if (!setterBinding || setterBinding !== setterSymbol.bindingIdentifier) return null;
15425
+ const initializer = declarator.init ? unwrapExpression$3(declarator.init) : null;
15426
+ if (!initializer || !isNodeOfType(initializer, "CallExpression") || !isReactApiCall(initializer, "useState", scopes, {
15427
+ allowGlobalReactNamespace: true,
15428
+ allowUnboundBareCalls: true,
15429
+ resolveNamedAliases: true
15430
+ })) return null;
15431
+ const stateElement = declarator.id.elements?.[0];
15432
+ const stateBinding = isAstNode(stateElement) ? getBindingIdentifier(stateElement) : null;
15433
+ return {
15434
+ setterSymbol,
15435
+ stateSymbol: stateBinding ? scopes.symbolFor(stateBinding) : null
15436
+ };
15437
+ };
15438
+ const expressionReadsSymbol = (node, symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
15439
+ if (!symbol) return false;
15440
+ const candidate = unwrapExpression$3(node);
15441
+ if (isNodeOfType(candidate, "Identifier")) {
15442
+ const reference = scopes.referenceFor(candidate);
15443
+ if (reference?.flag !== "write" && reference?.resolvedSymbol?.id === symbol.id) return true;
15444
+ const candidateSymbol = reference?.resolvedSymbol;
15445
+ if (candidateSymbol?.kind === "const" && candidateSymbol.initializer && !isOutsideAllFunctions(candidateSymbol) && !visitedSymbolIds.has(candidateSymbol.id)) {
15446
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
15447
+ nextVisitedSymbolIds.add(candidateSymbol.id);
15448
+ return expressionReadsSymbol(candidateSymbol.initializer, symbol, scopes, nextVisitedSymbolIds);
15254
15449
  }
15450
+ return false;
15255
15451
  }
15256
- const conditional = returnedExpression ? unwrapExpression$3(returnedExpression) : null;
15257
- if (!conditional || !isNodeOfType(conditional, "ConditionalExpression")) return false;
15258
- const test = unwrapExpression$3(conditional.test);
15452
+ const record = candidate;
15453
+ for (const key of Object.keys(record)) {
15454
+ if (key === "parent") continue;
15455
+ const child = record[key];
15456
+ if (Array.isArray(child)) {
15457
+ if (child.some((item) => isAstNode(item) && expressionReadsSymbol(item, symbol, scopes))) return true;
15458
+ } else if (isAstNode(child) && expressionReadsSymbol(child, symbol, scopes)) return true;
15459
+ }
15460
+ return false;
15461
+ };
15462
+ const isGuaranteedFreshStateValue = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
15463
+ const candidate = unwrapExpression$3(node);
15464
+ if (isNodeOfType(candidate, "ObjectExpression") || isNodeOfType(candidate, "ArrayExpression") || isNodeOfType(candidate, "JSXElement") || isNodeOfType(candidate, "JSXFragment") || isRegExpLiteral(candidate)) return true;
15465
+ if (isNodeOfType(candidate, "SequenceExpression")) {
15466
+ const finalExpression = candidate.expressions.at(-1);
15467
+ return Boolean(finalExpression && isGuaranteedFreshStateValue(finalExpression, scopes));
15468
+ }
15469
+ if (isNodeOfType(candidate, "ConditionalExpression")) return isGuaranteedFreshStateValue(candidate.consequent, scopes, visitedSymbolIds) && isGuaranteedFreshStateValue(candidate.alternate, scopes, visitedSymbolIds);
15470
+ if (isNodeOfType(candidate, "LogicalExpression")) return isGuaranteedFreshStateValue(candidate.left, scopes, visitedSymbolIds) && isGuaranteedFreshStateValue(candidate.right, scopes, visitedSymbolIds);
15471
+ if (isNodeOfType(candidate, "CallExpression")) {
15472
+ const callee = unwrapExpression$3(candidate.callee);
15473
+ return isNodeOfType(callee, "Identifier") && scopes.isGlobalReference(callee) && (callee.name === "Array" || callee.name === "Object") && (callee.name === "Array" || candidate.arguments.length === 0);
15474
+ }
15475
+ if (isNodeOfType(candidate, "NewExpression")) {
15476
+ const callee = unwrapExpression$3(candidate.callee);
15477
+ return isNodeOfType(callee, "Identifier") && scopes.isGlobalReference(callee) && FRESH_STATE_CONSTRUCTOR_NAMES.has(callee.name) && (callee.name !== "Object" || candidate.arguments.length === 0);
15478
+ }
15479
+ if (!isNodeOfType(candidate, "Identifier")) return false;
15480
+ const symbol = scopes.symbolFor(candidate);
15481
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || isOutsideAllFunctions(symbol) || visitedSymbolIds.has(symbol.id)) return false;
15482
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
15483
+ nextVisitedSymbolIds.add(symbol.id);
15484
+ return isGuaranteedFreshStateValue(symbol.initializer, scopes, nextVisitedSymbolIds);
15485
+ };
15486
+ const isNonZeroLiteral = (node) => {
15487
+ const candidate = unwrapExpression$3(node);
15488
+ return isNodeOfType(candidate, "Literal") && (typeof candidate.value === "number" && candidate.value !== 0 || typeof candidate.value === "bigint" && candidate.value !== 0n || typeof candidate.value === "string" && candidate.value.length > 0);
15489
+ };
15490
+ const isProvablyChangingExpression = (node, previousValueSymbol, scopes) => {
15491
+ const candidate = unwrapExpression$3(node);
15492
+ if (isGuaranteedFreshStateValue(candidate, scopes)) return true;
15493
+ if (isNodeOfType(candidate, "ConditionalExpression")) {
15494
+ const isConsequentChanging = isProvablyChangingExpression(candidate.consequent, previousValueSymbol, scopes);
15495
+ const isAlternateChanging = isProvablyChangingExpression(candidate.alternate, previousValueSymbol, scopes);
15496
+ return expressionReadsSymbol(candidate.test, previousValueSymbol, scopes) ? isConsequentChanging && isAlternateChanging : isConsequentChanging || isAlternateChanging;
15497
+ }
15498
+ if (!expressionReadsSymbol(candidate, previousValueSymbol, scopes)) return false;
15499
+ if (isNodeOfType(candidate, "UnaryExpression")) return candidate.operator === "!";
15500
+ if (isNodeOfType(candidate, "UpdateExpression")) return true;
15501
+ if (!isNodeOfType(candidate, "BinaryExpression")) return false;
15502
+ return (candidate.operator === "+" || candidate.operator === "-") && expressionReadsSymbol(candidate.left, previousValueSymbol, scopes) && isNonZeroLiteral(candidate.right);
15503
+ };
15504
+ const isFreshEqualityGuardUpdater = (node, previousValueSymbol, scopes) => {
15505
+ const candidate = unwrapExpression$3(node);
15506
+ if (!isNodeOfType(candidate, "ConditionalExpression")) return false;
15507
+ const test = unwrapExpression$3(candidate.test);
15259
15508
  if (!isNodeOfType(test, "BinaryExpression") || ![
15260
15509
  "===",
15261
15510
  "!==",
@@ -15263,38 +15512,50 @@ const isConvergingFunctionalUpdater = (node, scopes) => {
15263
15512
  "!="
15264
15513
  ].includes(test.operator)) return false;
15265
15514
  const isPreviousValue = (expression) => {
15266
- const candidate = unwrapExpression$3(expression);
15267
- return isNodeOfType(candidate, "Identifier") && candidate.name === previousValueParameter.name;
15515
+ const expressionCandidate = unwrapExpression$3(expression);
15516
+ return isNodeOfType(expressionCandidate, "Identifier") && scopes.symbolFor(expressionCandidate)?.id === previousValueSymbol?.id;
15268
15517
  };
15269
15518
  let comparedValue = null;
15270
15519
  if (isPreviousValue(test.left)) comparedValue = test.right;
15271
- else if (isPreviousValue(test.right)) comparedValue = test.left;
15272
- if (!comparedValue) return false;
15273
- if (isPotentiallyFreshComparedValue(comparedValue, scopes)) return false;
15274
- const isSameComparedValue = (expression) => {
15275
- const candidate = unwrapExpression$3(expression);
15276
- const compared = unwrapExpression$3(comparedValue);
15277
- if (isNodeOfType(candidate, "Identifier") && isNodeOfType(compared, "Identifier")) return candidate.name === compared.name;
15278
- if (isNodeOfType(candidate, "Literal") && isNodeOfType(compared, "Literal")) return candidate.value === compared.value;
15279
- return false;
15520
+ if (isPreviousValue(test.right)) comparedValue = test.left;
15521
+ if (!comparedValue || !isPotentiallyFreshComparedValue(comparedValue, scopes)) return false;
15522
+ const isComparedValue = (expression) => {
15523
+ const expressionCandidate = unwrapExpression$3(expression);
15524
+ const comparedCandidate = unwrapExpression$3(comparedValue);
15525
+ if (isNodeOfType(expressionCandidate, "Identifier")) return isNodeOfType(comparedCandidate, "Identifier") && scopes.symbolFor(expressionCandidate)?.id === scopes.symbolFor(comparedCandidate)?.id;
15526
+ return isNodeOfType(expressionCandidate, "Literal") && isNodeOfType(comparedCandidate, "Literal") && expressionCandidate.value === comparedCandidate.value;
15280
15527
  };
15281
- return test.operator === "===" || test.operator === "==" ? isPreviousValue(conditional.consequent) && isSameComparedValue(conditional.alternate) : isSameComparedValue(conditional.consequent) && isPreviousValue(conditional.alternate);
15282
- };
15283
- const isGuardedStableSetterCall = (identifier, symbol, scopes) => {
15284
- const parent = identifier.parent;
15285
- if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier || !isStableSetterLikeSymbol(symbol, scopes)) return false;
15286
- const writtenValue = parent.arguments?.[0];
15287
- return Boolean(writtenValue && isConvergingFunctionalUpdater(writtenValue, scopes));
15528
+ return test.operator === "===" || test.operator === "==" ? isPreviousValue(candidate.consequent) && isComparedValue(candidate.alternate) : isComparedValue(candidate.consequent) && isPreviousValue(candidate.alternate);
15288
15529
  };
15289
- const findStableSetterReference = (node, scopes) => {
15530
+ const isProvablyChangingFunctionalUpdater = (node, scopes) => {
15531
+ const updater = unwrapExpression$3(node);
15532
+ if (!isNodeOfType(updater, "ArrowFunctionExpression") && !isNodeOfType(updater, "FunctionExpression")) return false;
15533
+ const previousValueParameter = updater.params?.[0];
15534
+ if (!previousValueParameter || !isNodeOfType(previousValueParameter, "Identifier")) return false;
15535
+ const previousValueSymbol = scopes.symbolFor(previousValueParameter);
15536
+ if (!isNodeOfType(updater.body, "BlockStatement")) return isFreshEqualityGuardUpdater(updater.body, previousValueSymbol, scopes) || isProvablyChangingExpression(updater.body, previousValueSymbol, scopes);
15537
+ if (updater.body.body.length !== 1) return false;
15538
+ const soleStatement = updater.body.body[0];
15539
+ if (!isNodeOfType(soleStatement, "ReturnStatement") || !isAstNode(soleStatement.argument)) return false;
15540
+ return isFreshEqualityGuardUpdater(soleStatement.argument, previousValueSymbol, scopes) || isProvablyChangingExpression(soleStatement.argument, previousValueSymbol, scopes);
15541
+ };
15542
+ const isProvablyRenderChangingSetterCall = (identifier, scopes) => {
15543
+ const setterCall = identifier.parent;
15544
+ if (!setterCall || !isNodeOfType(setterCall, "CallExpression") || setterCall.callee !== identifier) return false;
15545
+ const descriptor = getStateSetterDescriptor(identifier, scopes);
15546
+ const writtenValue = setterCall.arguments?.[0];
15547
+ if (!descriptor || !writtenValue || !isAstNode(writtenValue)) return false;
15548
+ return isProvablyChangingFunctionalUpdater(writtenValue, scopes) ? true : isProvablyChangingExpression(writtenValue, descriptor.stateSymbol, scopes);
15549
+ };
15550
+ const findRenderChangingStateSetterName = (node, scopes) => {
15290
15551
  let setterName = null;
15291
15552
  const visit = (current) => {
15292
15553
  if (setterName) return;
15293
15554
  if (current !== node && (isNodeOfType(current, "FunctionDeclaration") || isNodeOfType(current, "FunctionExpression") || isNodeOfType(current, "ArrowFunctionExpression"))) return;
15294
15555
  if (isNodeOfType(current, "Identifier")) {
15295
- const symbol = scopes.referenceFor(current)?.resolvedSymbol;
15296
- if (symbol && isStableSetterLikeSymbol(symbol, scopes) && !isGuardedStableSetterCall(current, symbol, scopes)) {
15297
- setterName = symbol.name;
15556
+ const descriptor = getStateSetterDescriptor(current, scopes);
15557
+ if (descriptor && isProvablyRenderChangingSetterCall(current, scopes)) {
15558
+ setterName = descriptor.setterSymbol.name;
15298
15559
  return;
15299
15560
  }
15300
15561
  }
@@ -15621,7 +15882,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
15621
15882
  }
15622
15883
  if (!depsArgumentRaw) {
15623
15884
  if (callbackToAnalyze && EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS.has(hookName)) {
15624
- const setterName = findStableSetterReference(callbackToAnalyze, context.scopes);
15885
+ const setterName = findRenderChangingStateSetterName(callbackToAnalyze, context.scopes);
15625
15886
  if (setterName) {
15626
15887
  context.report({
15627
15888
  node: callbackToAnalyze,
@@ -20014,14 +20275,14 @@ const isIndexOfResultUsedAsMembershipTest = (node) => {
20014
20275
  if (isNegativeOneLiteral(otherOperand)) return true;
20015
20276
  return isZeroLiteral(otherOperand) && (parent.operator === ">=" || parent.operator === "<");
20016
20277
  };
20017
- const getTypeAnnotation = (node) => {
20278
+ const getTypeAnnotation$1 = (node) => {
20018
20279
  if (!node || !("typeAnnotation" in node)) return null;
20019
20280
  const annotation = node.typeAnnotation;
20020
20281
  if (!annotation || !isNodeOfType(annotation, "TSTypeAnnotation")) return null;
20021
20282
  return annotation.typeAnnotation;
20022
20283
  };
20023
20284
  const getDeclaredPropertyType = (members, propertyName) => {
20024
- for (const member of members) if (isNodeOfType(member, "TSPropertySignature") && isNodeOfType(member.key, "Identifier") && member.key.name === propertyName) return getTypeAnnotation(member);
20285
+ for (const member of members) if (isNodeOfType(member, "TSPropertySignature") && isNodeOfType(member.key, "Identifier") && member.key.name === propertyName) return getTypeAnnotation$1(member);
20025
20286
  return null;
20026
20287
  };
20027
20288
  const getArrayElementType = (typeNode) => {
@@ -20040,7 +20301,7 @@ const getDestructuredDeclaredType = (identifier) => {
20040
20301
  const property = binding.bindingIdentifier.parent;
20041
20302
  const objectPattern = property?.parent;
20042
20303
  if (!isNodeOfType(property, "Property") || !isNodeOfType(property.key, "Identifier") || !isNodeOfType(objectPattern, "ObjectPattern")) return null;
20043
- const propsType = getTypeAnnotation(objectPattern);
20304
+ const propsType = getTypeAnnotation$1(objectPattern);
20044
20305
  if (!propsType) return null;
20045
20306
  if (isNodeOfType(propsType, "TSTypeLiteral")) return getDeclaredPropertyType(propsType.members ?? [], property.key.name);
20046
20307
  if (!isNodeOfType(propsType, "TSTypeReference") || !isNodeOfType(propsType.typeName, "Identifier")) return null;
@@ -20057,7 +20318,7 @@ const getIdentifierDeclaredType = (identifier, visitedBindingIdentifiers = /* @_
20057
20318
  const binding = findVariableInitializer(identifier, identifier.name);
20058
20319
  if (!binding || visitedBindingIdentifiers.has(binding.bindingIdentifier)) return null;
20059
20320
  visitedBindingIdentifiers.add(binding.bindingIdentifier);
20060
- const directType = getTypeAnnotation(binding.bindingIdentifier);
20321
+ const directType = getTypeAnnotation$1(binding.bindingIdentifier);
20061
20322
  if (directType) return directType;
20062
20323
  const initializer = binding.initializer;
20063
20324
  if (isNodeOfType(initializer, "TSAsExpression") || isNodeOfType(initializer, "TSTypeAssertion") || isNodeOfType(initializer, "TSSatisfiesExpression")) return initializer.typeAnnotation;
@@ -27222,6 +27483,211 @@ const computeExternallyDriven = (analysis, declarator) => {
27222
27483
  return hasDeferredCallSite;
27223
27484
  };
27224
27485
  //#endregion
27486
+ //#region src/plugin/rules/state-and-effects/utils/effect/is-proven-native-read-method.ts
27487
+ const ARRAY_READ_METHOD_NAMES$1 = new Set([
27488
+ "at",
27489
+ "concat",
27490
+ "entries",
27491
+ "every",
27492
+ "filter",
27493
+ "find",
27494
+ "findIndex",
27495
+ "findLast",
27496
+ "findLastIndex",
27497
+ "flat",
27498
+ "flatMap",
27499
+ "forEach",
27500
+ "includes",
27501
+ "indexOf",
27502
+ "join",
27503
+ "keys",
27504
+ "lastIndexOf",
27505
+ "map",
27506
+ "reduce",
27507
+ "reduceRight",
27508
+ "slice",
27509
+ "some",
27510
+ "toLocaleString",
27511
+ "toReversed",
27512
+ "toSorted",
27513
+ "toSpliced",
27514
+ "toString",
27515
+ "values",
27516
+ "with"
27517
+ ]);
27518
+ const MAP_READ_METHOD_NAMES = new Set([
27519
+ "entries",
27520
+ "forEach",
27521
+ "get",
27522
+ "has",
27523
+ "keys",
27524
+ "values"
27525
+ ]);
27526
+ const SET_READ_METHOD_NAMES = new Set([
27527
+ "difference",
27528
+ "entries",
27529
+ "forEach",
27530
+ "has",
27531
+ "intersection",
27532
+ "isDisjointFrom",
27533
+ "isSubsetOf",
27534
+ "isSupersetOf",
27535
+ "keys",
27536
+ "symmetricDifference",
27537
+ "union",
27538
+ "values"
27539
+ ]);
27540
+ const FUNCTION_READ_METHOD_NAMES = new Set(["bind"]);
27541
+ const PROMISE_READ_METHOD_NAMES = new Set([
27542
+ "catch",
27543
+ "finally",
27544
+ "then"
27545
+ ]);
27546
+ const STRING_READ_METHOD_NAMES$1 = new Set([
27547
+ "at",
27548
+ "charAt",
27549
+ "charCodeAt",
27550
+ "codePointAt",
27551
+ "concat",
27552
+ "endsWith",
27553
+ "includes",
27554
+ "indexOf",
27555
+ "isWellFormed",
27556
+ "lastIndexOf",
27557
+ "localeCompare",
27558
+ "match",
27559
+ "matchAll",
27560
+ "normalize",
27561
+ "padEnd",
27562
+ "padStart",
27563
+ "repeat",
27564
+ "replace",
27565
+ "replaceAll",
27566
+ "search",
27567
+ "slice",
27568
+ "split",
27569
+ "startsWith",
27570
+ "substring",
27571
+ "toLocaleLowerCase",
27572
+ "toLocaleUpperCase",
27573
+ "toLowerCase",
27574
+ "toString",
27575
+ "toUpperCase",
27576
+ "toWellFormed",
27577
+ "trim",
27578
+ "trimEnd",
27579
+ "trimStart",
27580
+ "valueOf"
27581
+ ]);
27582
+ const NATIVE_TYPE_NAMES = new Set([
27583
+ "Array",
27584
+ "Map",
27585
+ "Promise",
27586
+ "PromiseLike",
27587
+ "ReadonlyArray",
27588
+ "ReadonlyMap",
27589
+ "ReadonlySet",
27590
+ "Set"
27591
+ ]);
27592
+ const nativeTypeDeclarationsByProgram = /* @__PURE__ */ new WeakMap();
27593
+ const shadowedNativeTypeNamesByBinding = /* @__PURE__ */ new WeakMap();
27594
+ const isWithinNode = (node, ancestor) => {
27595
+ let current = node;
27596
+ while (current) {
27597
+ if (current === ancestor) return true;
27598
+ current = current.parent;
27599
+ }
27600
+ return false;
27601
+ };
27602
+ const findTypeDeclarationScope = (declaration) => {
27603
+ let current = declaration.parent;
27604
+ while (current) {
27605
+ if (isNodeOfType(current, "Program") || isNodeOfType(current, "BlockStatement") || isNodeOfType(current, "TSModuleBlock") || isNodeOfType(current, "StaticBlock")) return current;
27606
+ current = current.parent;
27607
+ }
27608
+ return null;
27609
+ };
27610
+ const getDeclaredTypeName = (declaration) => {
27611
+ if (!isNodeOfType(declaration, "ClassDeclaration") && !isNodeOfType(declaration, "TSEnumDeclaration") && !isNodeOfType(declaration, "TSImportEqualsDeclaration") && !isNodeOfType(declaration, "TSInterfaceDeclaration") && !isNodeOfType(declaration, "TSModuleDeclaration") && !isNodeOfType(declaration, "TSTypeAliasDeclaration")) return null;
27612
+ return declaration.id && isNodeOfType(declaration.id, "Identifier") ? declaration.id.name : null;
27613
+ };
27614
+ const getNativeTypeDeclarations = (program) => {
27615
+ const cachedDeclarations = nativeTypeDeclarationsByProgram.get(program);
27616
+ if (cachedDeclarations) return cachedDeclarations;
27617
+ const declarations = [];
27618
+ walkAst(program, (candidate) => {
27619
+ if (isNodeOfType(candidate, "ImportDeclaration")) {
27620
+ for (const specifier of candidate.specifiers) if (NATIVE_TYPE_NAMES.has(specifier.local.name)) declarations.push({
27621
+ name: specifier.local.name,
27622
+ scope: program
27623
+ });
27624
+ return;
27625
+ }
27626
+ const declaredTypeName = getDeclaredTypeName(candidate);
27627
+ if (!declaredTypeName || !NATIVE_TYPE_NAMES.has(declaredTypeName)) return;
27628
+ const declarationScope = findTypeDeclarationScope(candidate);
27629
+ if (declarationScope) declarations.push({
27630
+ name: declaredTypeName,
27631
+ scope: declarationScope
27632
+ });
27633
+ });
27634
+ nativeTypeDeclarationsByProgram.set(program, declarations);
27635
+ return declarations;
27636
+ };
27637
+ const getShadowedNativeTypeNames = (binding) => {
27638
+ const cachedNames = shadowedNativeTypeNamesByBinding.get(binding);
27639
+ if (cachedNames) return cachedNames;
27640
+ const shadowedNames = /* @__PURE__ */ new Set();
27641
+ for (const typeName of NATIVE_TYPE_NAMES) if (hasEnclosingTypeParameterNamed(binding, typeName)) shadowedNames.add(typeName);
27642
+ const program = findProgramRoot(binding);
27643
+ if (program) {
27644
+ for (const declaration of getNativeTypeDeclarations(program)) if (isWithinNode(binding, declaration.scope)) shadowedNames.add(declaration.name);
27645
+ }
27646
+ shadowedNativeTypeNamesByBinding.set(binding, shadowedNames);
27647
+ return shadowedNames;
27648
+ };
27649
+ const getParameterBinding = (definitionName) => {
27650
+ if (isNodeOfType(definitionName, "Identifier")) return definitionName;
27651
+ if (!isNodeOfType(definitionName, "AssignmentPattern")) return null;
27652
+ return isNodeOfType(definitionName.left, "Identifier") ? definitionName.left : null;
27653
+ };
27654
+ const getTypeAnnotation = (binding) => {
27655
+ if (!isNodeOfType(binding, "Identifier")) return null;
27656
+ const annotation = binding.typeAnnotation;
27657
+ return annotation && isNodeOfType(annotation, "TSTypeAnnotation") ? annotation.typeAnnotation : null;
27658
+ };
27659
+ const isNullishType = (typeNode) => isNodeOfType(typeNode, "TSNullKeyword") || isNodeOfType(typeNode, "TSUndefinedKeyword");
27660
+ const isNativeReadMethod = (typeNode, methodName, shadowedTypeNames) => {
27661
+ if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return ARRAY_READ_METHOD_NAMES$1.has(methodName);
27662
+ if (isNodeOfType(typeNode, "TSStringKeyword")) return STRING_READ_METHOD_NAMES$1.has(methodName);
27663
+ if (isNodeOfType(typeNode, "TSFunctionType")) return FUNCTION_READ_METHOD_NAMES.has(methodName);
27664
+ if (isNodeOfType(typeNode, "TSLiteralType") && isNodeOfType(typeNode.literal, "Literal") && typeof typeNode.literal.value === "string") return STRING_READ_METHOD_NAMES$1.has(methodName);
27665
+ if (isNodeOfType(typeNode, "TSTypeOperator") && typeNode.operator === "readonly" && typeNode.typeAnnotation) return isNativeReadMethod(typeNode.typeAnnotation, methodName, shadowedTypeNames);
27666
+ if (isNodeOfType(typeNode, "TSUnionType")) {
27667
+ let hasNonNullishMember = false;
27668
+ for (const memberType of typeNode.types) {
27669
+ if (isNullishType(memberType)) continue;
27670
+ hasNonNullishMember = true;
27671
+ if (!isNativeReadMethod(memberType, methodName, shadowedTypeNames)) return false;
27672
+ }
27673
+ return hasNonNullishMember;
27674
+ }
27675
+ if (!isNodeOfType(typeNode, "TSTypeReference") || !isNodeOfType(typeNode.typeName, "Identifier")) return false;
27676
+ if (shadowedTypeNames.has(typeNode.typeName.name)) return false;
27677
+ if (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray") return ARRAY_READ_METHOD_NAMES$1.has(methodName);
27678
+ if (typeNode.typeName.name === "Map" || typeNode.typeName.name === "ReadonlyMap") return MAP_READ_METHOD_NAMES.has(methodName);
27679
+ if (typeNode.typeName.name === "Set" || typeNode.typeName.name === "ReadonlySet") return SET_READ_METHOD_NAMES.has(methodName);
27680
+ if (typeNode.typeName.name === "Promise" || typeNode.typeName.name === "PromiseLike") return PROMISE_READ_METHOD_NAMES.has(methodName);
27681
+ return false;
27682
+ };
27683
+ const isProvenNativeReadMethod = (ref, methodName) => Boolean(ref.resolved?.defs.some((definition) => {
27684
+ if (definition.type !== "Parameter") return false;
27685
+ const parameterBinding = getParameterBinding(definition.name);
27686
+ if (!parameterBinding) return false;
27687
+ const typeAnnotation = getTypeAnnotation(parameterBinding);
27688
+ return Boolean(typeAnnotation && isNativeReadMethod(typeAnnotation, methodName, getShadowedNativeTypeNames(parameterBinding)));
27689
+ }));
27690
+ //#endregion
27225
27691
  //#region src/plugin/rules/state-and-effects/utils/effect/react.ts
27226
27692
  const KNOWN_PURE_HOC_NAMES = new Set(["memo", "forwardRef"]);
27227
27693
  const startsWithUppercase = (name) => Boolean(name && name.length > 0 && name[0] >= "A" && name[0] <= "Z");
@@ -27448,7 +27914,132 @@ const isRefCurrent = (ref) => {
27448
27914
  const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isStateSetter(analysis, innerRef));
27449
27915
  const isSyncStateSetterCall = (analysis, ref, effectFn) => isStateSetterCall(analysis, ref) && isSynchronous(ref.identifier, effectFn) && !resolvesToAsyncFunction(ref);
27450
27916
  const HANDLER_NAMED_METHOD_PATTERN = /^(on|handle)[A-Z]/;
27451
- const isPropCallbackInvocationRef = (analysis, ref) => {
27917
+ const SYNCHRONOUS_CALLBACK_ARGUMENT_INDEX_BY_METHOD = new Map([
27918
+ ["every", 0],
27919
+ ["filter", 0],
27920
+ ["find", 0],
27921
+ ["findIndex", 0],
27922
+ ["findLast", 0],
27923
+ ["findLastIndex", 0],
27924
+ ["flatMap", 0],
27925
+ ["forEach", 0],
27926
+ ["map", 0],
27927
+ ["reduce", 0],
27928
+ ["reduceRight", 0],
27929
+ ["replace", 1],
27930
+ ["replaceAll", 1],
27931
+ ["some", 0]
27932
+ ]);
27933
+ const addSimpleBindingNames = (pattern, names) => {
27934
+ if (isNodeOfType(pattern, "Identifier")) {
27935
+ if (names.has(pattern.name)) return false;
27936
+ names.add(pattern.name);
27937
+ return true;
27938
+ }
27939
+ if (isNodeOfType(pattern, "AssignmentPattern")) return addSimpleBindingNames(pattern.left, names);
27940
+ if (isNodeOfType(pattern, "RestElement")) return addSimpleBindingNames(pattern.argument, names);
27941
+ if (isNodeOfType(pattern, "ObjectPattern")) {
27942
+ let didAddName = false;
27943
+ for (const property of pattern.properties) didAddName = addSimpleBindingNames(isNodeOfType(property, "Property") ? property.value : property, names) || didAddName;
27944
+ return didAddName;
27945
+ }
27946
+ if (isNodeOfType(pattern, "ArrayPattern")) {
27947
+ let didAddName = false;
27948
+ for (const element of pattern.elements) if (element) didAddName = addSimpleBindingNames(element, names) || didAddName;
27949
+ return didAddName;
27950
+ }
27951
+ return false;
27952
+ };
27953
+ const getSimpleParameterNames = (callback) => {
27954
+ if (!isFunctionLike$1(callback)) return /* @__PURE__ */ new Set();
27955
+ const names = /* @__PURE__ */ new Set();
27956
+ for (const parameter of callback.params ?? []) addSimpleBindingNames(parameter, names);
27957
+ return names;
27958
+ };
27959
+ const inlineCallbackInvokesParameter = (callback) => {
27960
+ const parameterNames = getSimpleParameterNames(callback);
27961
+ if (parameterNames.size === 0 || !isFunctionLike$1(callback)) return false;
27962
+ let didAddAlias = true;
27963
+ while (didAddAlias) {
27964
+ didAddAlias = false;
27965
+ walkAst(callback.body, (candidate) => {
27966
+ if (candidate !== callback.body && isFunctionLike$1(candidate)) return false;
27967
+ if (isNodeOfType(candidate, "AssignmentExpression") && candidate.operator === "=" && isNodeOfType(candidate.left, "Identifier")) {
27968
+ const assignedValueRoot = getRootIdentifier$1(candidate.right);
27969
+ if (isNodeOfType(assignedValueRoot, "Identifier") && parameterNames.has(assignedValueRoot.name) && !parameterNames.has(candidate.left.name)) {
27970
+ parameterNames.add(candidate.left.name);
27971
+ didAddAlias = true;
27972
+ }
27973
+ return;
27974
+ }
27975
+ if (!isNodeOfType(candidate, "VariableDeclarator") || !candidate.init) return;
27976
+ const initializerRoot = getRootIdentifier$1(candidate.init);
27977
+ if (!isNodeOfType(initializerRoot, "Identifier") || !parameterNames.has(initializerRoot.name)) return;
27978
+ if (addSimpleBindingNames(candidate.id, parameterNames)) didAddAlias = true;
27979
+ });
27980
+ }
27981
+ let didInvokeParameter = false;
27982
+ walkAst(callback.body, (candidate) => {
27983
+ if (didInvokeParameter) return false;
27984
+ if (candidate !== callback.body && isFunctionLike$1(candidate)) return false;
27985
+ if (!isNodeOfType(candidate, "CallExpression")) return;
27986
+ const callee = stripParenExpression(candidate.callee);
27987
+ if (isNodeOfType(callee, "Identifier") && parameterNames.has(callee.name)) {
27988
+ didInvokeParameter = true;
27989
+ return false;
27990
+ }
27991
+ if (isNodeOfType(callee, "MemberExpression")) {
27992
+ const receiverRoot = getRootIdentifier$1(callee.object);
27993
+ if (receiverRoot && parameterNames.has(receiverRoot.name)) {
27994
+ didInvokeParameter = true;
27995
+ return false;
27996
+ }
27997
+ }
27998
+ });
27999
+ return didInvokeParameter;
28000
+ };
28001
+ const callbackInvokesPropCallback = (analysis, callback) => {
28002
+ if (!isFunctionLike$1(callback)) return false;
28003
+ let didInvokePropCallback = false;
28004
+ walkAst(callback.body, (candidate) => {
28005
+ if (didInvokePropCallback) return false;
28006
+ if (candidate !== callback.body && isFunctionLike$1(candidate)) return false;
28007
+ if (!isNodeOfType(candidate, "CallExpression")) return;
28008
+ if (getDownstreamRefs(analysis, stripParenExpression(candidate.callee)).some((reference) => isPropCallbackInvocationRef(analysis, reference))) {
28009
+ didInvokePropCallback = true;
28010
+ return false;
28011
+ }
28012
+ });
28013
+ return didInvokePropCallback;
28014
+ };
28015
+ const isNativeMethodSuppressionSafe = (analysis, callExpression, methodName, scopes) => {
28016
+ if (!isNodeOfType(callExpression, "CallExpression")) return false;
28017
+ const callee = stripParenExpression(callExpression.callee);
28018
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
28019
+ const receiver = stripParenExpression(callee.object);
28020
+ if (!isNodeOfType(receiver, "Identifier")) return false;
28021
+ const receiverSymbol = scopes.symbolFor(receiver);
28022
+ if (!receiverSymbol || hasSymbolWriteBefore(receiverSymbol, callExpression, scopes) || hasPossibleStaticPropertyWriteBefore(receiver, methodName, callExpression, scopes)) return false;
28023
+ let callResult = callExpression;
28024
+ let callResultConsumer = callResult.parent;
28025
+ while (callResultConsumer && (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(callResultConsumer.type) && "expression" in callResultConsumer && callResultConsumer.expression === callResult || isNodeOfType(callResultConsumer, "MemberExpression") && callResultConsumer.object === callResult)) {
28026
+ callResult = callResultConsumer;
28027
+ callResultConsumer = callResult.parent;
28028
+ }
28029
+ if (isNodeOfType(callResultConsumer, "CallExpression") && callResultConsumer.callee === callResult) return false;
28030
+ const callbackArgumentIndex = SYNCHRONOUS_CALLBACK_ARGUMENT_INDEX_BY_METHOD.get(methodName);
28031
+ if (callbackArgumentIndex === void 0) return true;
28032
+ const callbackArgument = callExpression.arguments?.[callbackArgumentIndex];
28033
+ if (!callbackArgument) return true;
28034
+ const callbackValue = stripParenExpression(callbackArgument);
28035
+ if (isFunctionLike$1(callbackValue)) return !inlineCallbackInvokesParameter(callbackValue) && !callbackInvokesPropCallback(analysis, callbackValue);
28036
+ if (!isNodeOfType(callbackValue, "Identifier")) return false;
28037
+ const callbackRef = getRef(analysis, callbackValue);
28038
+ if (!callbackRef || isPropAlias(analysis, callbackRef)) return false;
28039
+ const callbackFunction = resolveToFunction(callbackRef);
28040
+ return Boolean(callbackFunction && isFunctionLike$1(callbackFunction) && !inlineCallbackInvokesParameter(callbackFunction) && !callbackInvokesPropCallback(analysis, callbackFunction));
28041
+ };
28042
+ const isPropCallbackInvocationRef = (analysis, ref, options = {}) => {
27452
28043
  if (!isPropAlias(analysis, ref)) return false;
27453
28044
  let effectiveNode = ref.identifier;
27454
28045
  let parent = effectiveNode.parent;
@@ -27461,7 +28052,9 @@ const isPropCallbackInvocationRef = (analysis, ref) => {
27461
28052
  if (isNodeOfType(parent, "MemberExpression") && parent.object === effectiveNode) {
27462
28053
  const memberParent = parent.parent;
27463
28054
  if (isNodeOfType(memberParent, "CallExpression") && memberParent.callee === parent) {
27464
- if (!parent.computed && isNodeOfType(parent.property, "Identifier") && HANDLER_NAMED_METHOD_PATTERN.test(parent.property.name)) return true;
28055
+ const propertyName = getStaticPropertyName(parent);
28056
+ if (propertyName && HANDLER_NAMED_METHOD_PATTERN.test(propertyName)) return true;
28057
+ if (options.nativeMethodScopes && isCustomHookParameter(ref) && propertyName && isProvenNativeReadMethod(ref, propertyName) && isNativeMethodSuppressionSafe(analysis, memberParent, propertyName, options.nativeMethodScopes)) return false;
27465
28058
  return isWholePropsObjectReference(analysis, ref);
27466
28059
  }
27467
28060
  }
@@ -29496,7 +30089,7 @@ const isNumericForLoopCounter = (attributeNode, indexName) => {
29496
30089
  if (forStatement.test && forLoopTestReadsDataLength(forStatement.test)) return false;
29497
30090
  return true;
29498
30091
  };
29499
- const EMPTY_NAME_SET = /* @__PURE__ */ new Set();
30092
+ const EMPTY_NAME_SET$1 = /* @__PURE__ */ new Set();
29500
30093
  const findIteratorItemNamesOfBinding = (binding) => {
29501
30094
  const names = /* @__PURE__ */ new Set();
29502
30095
  if (!binding.bindingFunction || !isFunctionLike$1(binding.bindingFunction)) return names;
@@ -29610,9 +30203,9 @@ const noArrayIndexAsKey = defineRule({
29610
30203
  const jsxElement = openingElement.parent;
29611
30204
  if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
29612
30205
  const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
29613
- const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET;
30206
+ const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET$1;
29614
30207
  if (!containsStatefulDescendant(jsxElement, {
29615
- memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET,
30208
+ memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET$1,
29616
30209
  bareIdentifierNames: primitiveItemNames.size > 0 ? new Set([...derivedNames, ...primitiveItemNames]) : derivedNames
29617
30210
  })) return;
29618
30211
  }
@@ -30113,6 +30706,9 @@ const findNearestPackageDirectory = (filename) => {
30113
30706
  const readNearestPackageManifest = (filename) => {
30114
30707
  const packageDirectory = findNearestPackageDirectory(filename);
30115
30708
  if (!packageDirectory) return null;
30709
+ return readPackageManifest(packageDirectory);
30710
+ };
30711
+ const readPackageManifest = (packageDirectory) => {
30116
30712
  const packageJsonPath = path.join(packageDirectory, "package.json");
30117
30713
  recordContentProbe(packageJsonPath);
30118
30714
  const cached = cachedManifestByPackageDirectory.get(packageDirectory);
@@ -30491,7 +31087,16 @@ const isRenderPreservingCallArgumentFunction = (node, scopes) => {
30491
31087
  return parent.arguments.some((argumentNode) => argumentNode === node) && isProvenArrayMapCall(parent, scopes);
30492
31088
  };
30493
31089
  const isNestedRenderEvidenceBoundary = (node, scopes) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isRenderPreservingCallArgumentFunction(node, scopes);
30494
- const isRenderOutputExpression = (node, scopes) => node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS);
31090
+ const isRenderOutputExpression = (node, scopes) => node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS) || isReactDomCreatePortalCall(node, scopes);
31091
+ const isReactDomCreatePortalCall = (node, scopes) => {
31092
+ if (!isNodeOfType(node, "CallExpression")) return false;
31093
+ const callee = stripParenExpression(node.callee);
31094
+ if (isNodeOfType(callee, "Identifier")) return scopes.symbolFor(callee)?.kind === "import" && getImportedNameFromModule(callee, callee.name, "react-dom") === "createPortal";
31095
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "createPortal") return false;
31096
+ const symbol = scopes.symbolFor(callee.object);
31097
+ if (!symbol || symbol.kind !== "import") return false;
31098
+ return isDefaultImportFromModule(callee.object, callee.object.name, "react-dom") || isNamespaceImportFromModule(callee.object, callee.object.name, "react-dom");
31099
+ };
30495
31100
  const containsRenderOutput$1 = (rootNode, scopes) => {
30496
31101
  let hasRenderOutput = false;
30497
31102
  walkAst(rootNode, (node) => {
@@ -30643,6 +31248,514 @@ const noCascadingSetState = defineRetiredRule({
30643
31248
  recommendation: "Retired: React batches synchronous state updates from one effect into the same follow-up commit, so setter count does not prove repeated redraws."
30644
31249
  });
30645
31250
  //#endregion
31251
+ //#region src/plugin/rules/state-and-effects/utils/create-state-trigger-reachability.ts
31252
+ const getRefCurrentSymbol = (node, context) => {
31253
+ const expression = stripParenExpression(node);
31254
+ if (!isNodeOfType(expression, "MemberExpression") || getStaticMemberPropertyName(expression) !== "current" || !isNodeOfType(expression.object, "Identifier")) return null;
31255
+ return context.scopes.symbolFor(expression.object);
31256
+ };
31257
+ const expressionReadsState = (analysis, expression) => getDownstreamRefs(analysis, expression).some((reference) => getUpstreamRefs(analysis, reference).some((upstreamReference) => isState(analysis, upstreamReference)));
31258
+ const expressionReadsProp = (analysis, expression) => getDownstreamRefs(analysis, expression).some((reference) => getUpstreamRefs(analysis, reference).some((upstreamReference) => isProp(analysis, upstreamReference)));
31259
+ const hasNonInitializerWrite = (analysis, identifier) => {
31260
+ const reference = getRef(analysis, identifier);
31261
+ return Boolean(reference?.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
31262
+ };
31263
+ const isStableParameterDefault = (expression) => {
31264
+ const candidate = stripParenExpression(expression);
31265
+ if (isNodeOfType(candidate, "Literal")) return candidate.value === null || [
31266
+ "bigint",
31267
+ "boolean",
31268
+ "number",
31269
+ "string"
31270
+ ].includes(typeof candidate.value);
31271
+ return Boolean(isNodeOfType(candidate, "TemplateLiteral") && (candidate.expressions ?? []).length === 0);
31272
+ };
31273
+ const isDirectComponentPropBinding = (symbol) => {
31274
+ const parameter = symbol.declarationNode;
31275
+ if (parameter === symbol.bindingIdentifier) return true;
31276
+ if (isNodeOfType(parameter, "AssignmentPattern")) return parameter.left === symbol.bindingIdentifier && isStableParameterDefault(parameter.right);
31277
+ if (!isNodeOfType(parameter, "ObjectPattern")) return false;
31278
+ return parameter.properties.some((property) => {
31279
+ if (!isNodeOfType(property, "Property")) return false;
31280
+ const propertyValue = property.value;
31281
+ if (propertyValue === symbol.bindingIdentifier) return true;
31282
+ return Boolean(isNodeOfType(propertyValue, "AssignmentPattern") && propertyValue.left === symbol.bindingIdentifier && isStableParameterDefault(propertyValue.right));
31283
+ });
31284
+ };
31285
+ const REFLEXIVE_TYPE_NODE_TYPES = new Set([
31286
+ "TSBigIntKeyword",
31287
+ "TSBooleanKeyword",
31288
+ "TSNeverKeyword",
31289
+ "TSNullKeyword",
31290
+ "TSObjectKeyword",
31291
+ "TSStringKeyword",
31292
+ "TSSymbolKeyword",
31293
+ "TSUndefinedKeyword"
31294
+ ]);
31295
+ const PRIMITIVE_TYPE_NODE_TYPES = new Set([
31296
+ "TSBigIntKeyword",
31297
+ "TSBooleanKeyword",
31298
+ "TSNeverKeyword",
31299
+ "TSNullKeyword",
31300
+ "TSNumberKeyword",
31301
+ "TSStringKeyword",
31302
+ "TSSymbolKeyword",
31303
+ "TSUndefinedKeyword"
31304
+ ]);
31305
+ const isDefinitelyReflexiveType = (typeNode) => {
31306
+ if (REFLEXIVE_TYPE_NODE_TYPES.has(typeNode.type)) return true;
31307
+ if (isNodeOfType(typeNode, "TSLiteralType")) return Boolean(isNodeOfType(typeNode.literal, "Literal") && (typeof typeNode.literal.value !== "number" || !Number.isNaN(typeNode.literal.value)));
31308
+ if (isNodeOfType(typeNode, "TSUnionType")) return (typeNode.types ?? []).every((member) => isDefinitelyReflexiveType(member));
31309
+ return false;
31310
+ };
31311
+ const isDefinitelyPrimitiveType = (typeNode) => {
31312
+ if (PRIMITIVE_TYPE_NODE_TYPES.has(typeNode.type)) return true;
31313
+ if (isNodeOfType(typeNode, "TSLiteralType")) return true;
31314
+ if (isNodeOfType(typeNode, "TSUnionType")) return (typeNode.types ?? []).every((member) => isDefinitelyPrimitiveType(member));
31315
+ return false;
31316
+ };
31317
+ const isDefinitelyPrimitiveExpression = (expression, context) => {
31318
+ const candidate = stripParenExpression(expression);
31319
+ if (isNodeOfType(candidate, "Literal")) return true;
31320
+ if (!isNodeOfType(candidate, "Identifier")) return false;
31321
+ const symbol = context.scopes.symbolFor(candidate);
31322
+ const typeAnnotation = symbol ? getSymbolTypeAnnotation(symbol) : null;
31323
+ return Boolean(typeAnnotation && isDefinitelyPrimitiveType(typeAnnotation));
31324
+ };
31325
+ const isDefinitelyReflexiveExpression = (expression, analysis, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
31326
+ const candidate = stripParenExpression(expression);
31327
+ if (isNodeOfType(candidate, "Literal")) return typeof candidate.value !== "number" || !Number.isNaN(candidate.value);
31328
+ if (isNodeOfType(candidate, "TemplateLiteral") || isNodeOfType(candidate, "ArrayExpression") || isNodeOfType(candidate, "ObjectExpression") || isFunctionLike$1(candidate)) return true;
31329
+ if (isNodeOfType(candidate, "UnaryExpression")) return [
31330
+ "!",
31331
+ "typeof",
31332
+ "void",
31333
+ "~"
31334
+ ].includes(candidate.operator);
31335
+ if (isNodeOfType(candidate, "BinaryExpression")) return [
31336
+ "&",
31337
+ "|",
31338
+ "^",
31339
+ "<<",
31340
+ ">>",
31341
+ ">>>"
31342
+ ].includes(candidate.operator);
31343
+ if (isNodeOfType(candidate, "CallExpression")) {
31344
+ const callee = stripParenExpression(candidate.callee);
31345
+ return Boolean(isNodeOfType(callee, "Identifier") && ["Boolean", "String"].includes(callee.name) && context.scopes.isGlobalReference(callee));
31346
+ }
31347
+ if (!isNodeOfType(candidate, "Identifier")) return false;
31348
+ if (candidate.name === "undefined" && context.scopes.isGlobalReference(candidate)) return true;
31349
+ const symbol = context.scopes.symbolFor(candidate);
31350
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
31351
+ const typeAnnotation = getSymbolTypeAnnotation(symbol);
31352
+ if (typeAnnotation && isDefinitelyReflexiveType(typeAnnotation)) return true;
31353
+ if (!symbol.initializer || hasNonInitializerWrite(analysis, candidate)) return false;
31354
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
31355
+ nextVisitedSymbolIds.add(symbol.id);
31356
+ return isDefinitelyReflexiveExpression(symbol.initializer, analysis, context, nextVisitedSymbolIds);
31357
+ };
31358
+ const isSupportedPropProjection = (analysis, expression, context, visitedBindings = /* @__PURE__ */ new Set()) => {
31359
+ const candidate = stripParenExpression(expression);
31360
+ if (isNodeOfType(candidate, "Identifier")) {
31361
+ const reference = getRef(analysis, candidate);
31362
+ if (!reference) return false;
31363
+ if (isProp(analysis, reference)) {
31364
+ if (isCustomHookParameter(reference)) return false;
31365
+ const symbol = context.scopes.symbolFor(candidate);
31366
+ return Boolean(symbol && isDirectComponentPropBinding(symbol));
31367
+ }
31368
+ if (!reference.resolved || visitedBindings.has(reference.resolved)) return false;
31369
+ if (hasNonInitializerWrite(analysis, candidate)) return false;
31370
+ const declarator = reference.resolved.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
31371
+ if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.id, "Identifier") || !declarator.init) return false;
31372
+ const nextVisitedBindings = new Set(visitedBindings);
31373
+ nextVisitedBindings.add(reference.resolved);
31374
+ return isSupportedPropProjection(analysis, declarator.init, context, nextVisitedBindings);
31375
+ }
31376
+ if (isNodeOfType(candidate, "MemberExpression")) return false;
31377
+ if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "~") return Boolean(isDefinitelyPrimitiveExpression(candidate.argument, context) && isSupportedPropProjection(analysis, candidate.argument, context, visitedBindings));
31378
+ if (isNodeOfType(candidate, "BinaryExpression") && [
31379
+ "&",
31380
+ "|",
31381
+ "^",
31382
+ "<<",
31383
+ ">>",
31384
+ ">>>"
31385
+ ].includes(candidate.operator)) return [candidate.left, candidate.right].every((operand) => {
31386
+ const projectionOperand = stripParenExpression(operand);
31387
+ return isNodeOfType(projectionOperand, "Literal") || isDefinitelyPrimitiveExpression(projectionOperand, context) && isSupportedPropProjection(analysis, projectionOperand, context, visitedBindings);
31388
+ });
31389
+ if (isNodeOfType(candidate, "CallExpression")) {
31390
+ const callee = stripParenExpression(candidate.callee);
31391
+ if (isNodeOfType(callee, "Identifier") && ["Boolean", "String"].includes(callee.name) && context.scopes.isGlobalReference(callee) && (candidate.arguments ?? []).length === 1) {
31392
+ const argument = candidate.arguments?.[0];
31393
+ return Boolean(argument && isDefinitelyPrimitiveExpression(argument, context) && isSupportedPropProjection(analysis, argument, context, visitedBindings));
31394
+ }
31395
+ return false;
31396
+ }
31397
+ return false;
31398
+ };
31399
+ const getStableCurrentValueKey = (analysis, expression, context) => {
31400
+ if (expressionReadsState(analysis, expression)) return null;
31401
+ if (!expressionReadsProp(analysis, expression)) return null;
31402
+ if (!isSupportedPropProjection(analysis, expression, context)) return null;
31403
+ return resolveExpressionKey$1(expression, context);
31404
+ };
31405
+ const getSnapshotShape = (analysis, expression, context) => {
31406
+ const candidate = stripParenExpression(expression);
31407
+ if (isNodeOfType(candidate, "ArrayExpression")) {
31408
+ const elementKeys = [];
31409
+ for (const element of candidate.elements ?? []) {
31410
+ if (!element || isNodeOfType(element, "SpreadElement")) return null;
31411
+ const elementKey = getStableCurrentValueKey(analysis, element, context);
31412
+ if (!elementKey) return null;
31413
+ elementKeys.push(elementKey);
31414
+ }
31415
+ return {
31416
+ scalarKey: null,
31417
+ elementKeys
31418
+ };
31419
+ }
31420
+ const scalarKey = getStableCurrentValueKey(analysis, candidate, context);
31421
+ return scalarKey ? {
31422
+ scalarKey,
31423
+ elementKeys: null
31424
+ } : null;
31425
+ };
31426
+ const snapshotShapesMatch = (left, right) => {
31427
+ if (left.scalarKey !== right.scalarKey) return false;
31428
+ if (left.elementKeys === null || right.elementKeys === null) return left.elementKeys === right.elementKeys;
31429
+ return left.elementKeys.length === right.elementKeys.length && left.elementKeys.every((elementKey, index) => elementKey === right.elementKeys?.[index]);
31430
+ };
31431
+ const collectSnapshotEnvironment = (analysis, effectFunction, context) => {
31432
+ const assignmentsByRefSymbolId = /* @__PURE__ */ new Map();
31433
+ const refSymbolsById = /* @__PURE__ */ new Map();
31434
+ walkAst(effectFunction, (child) => {
31435
+ if (child !== effectFunction && isFunctionLike$1(child)) return false;
31436
+ if (!isNodeOfType(child, "AssignmentExpression") || child.operator !== "=") return;
31437
+ const refSymbol = getRefCurrentSymbol(child.left, context);
31438
+ if (!refSymbol) return;
31439
+ const assignments = assignmentsByRefSymbolId.get(refSymbol.id) ?? [];
31440
+ assignments.push(child);
31441
+ assignmentsByRefSymbolId.set(refSymbol.id, assignments);
31442
+ refSymbolsById.set(refSymbol.id, refSymbol);
31443
+ });
31444
+ const refShapes = /* @__PURE__ */ new Map();
31445
+ for (const [refSymbolId, assignments] of assignmentsByRefSymbolId) {
31446
+ if (assignments.length !== 1) continue;
31447
+ const assignment = assignments[0];
31448
+ if (!assignment || !isNodeOfType(assignment, "AssignmentExpression")) continue;
31449
+ if (context.cfg.enclosingFunction(assignment) !== effectFunction) continue;
31450
+ if (!context.cfg.isUnconditionalFromEntry(assignment)) continue;
31451
+ const declarator = refSymbolsById.get(refSymbolId)?.declarationNode ?? null;
31452
+ if (!declarator || !isGenuineReactHookDeclarator(analysis, declarator, "useRef") || !isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) continue;
31453
+ const refIdentifier = isNodeOfType(declarator.id, "Identifier") ? declarator.id : null;
31454
+ const declarationSymbol = refIdentifier ? context.scopes.symbolFor(refIdentifier) : null;
31455
+ if (!declarationSymbol || declarationSymbol.id !== refSymbolId) continue;
31456
+ if (declarationSymbol.references.some((reference) => {
31457
+ const member = reference.identifier.parent;
31458
+ if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== reference.identifier || getStaticMemberPropertyName(member) !== "current") return true;
31459
+ const parent = member.parent;
31460
+ if (!parent) return false;
31461
+ if (isNodeOfType(parent, "UpdateExpression")) return true;
31462
+ if (isNodeOfType(parent, "AssignmentExpression") && parent.left === member) return parent !== assignment;
31463
+ return false;
31464
+ })) continue;
31465
+ const initializer = declarator.init.arguments?.[0];
31466
+ if (!initializer) continue;
31467
+ const initialShape = getSnapshotShape(analysis, initializer, context);
31468
+ const assignedShape = getSnapshotShape(analysis, assignment.right, context);
31469
+ if (!initialShape || !assignedShape || !snapshotShapesMatch(initialShape, assignedShape)) continue;
31470
+ refShapes.set(refSymbolId, assignedShape);
31471
+ }
31472
+ const previousValueKeys = /* @__PURE__ */ new Map();
31473
+ walkAst(effectFunction, (child) => {
31474
+ if (child !== effectFunction && isFunctionLike$1(child)) return false;
31475
+ if (!isNodeOfType(child, "VariableDeclarator") || !child.init) return;
31476
+ const refSymbol = getRefCurrentSymbol(child.init, context);
31477
+ if (!refSymbol) return;
31478
+ const snapshotShape = refShapes.get(refSymbol.id);
31479
+ if (!snapshotShape) return;
31480
+ if (isNodeOfType(child.id, "Identifier") && snapshotShape.scalarKey) {
31481
+ const previousSymbol = context.scopes.symbolFor(child.id);
31482
+ if (previousSymbol && previousSymbol.kind === "const") previousValueKeys.set(previousSymbol.id, snapshotShape.scalarKey);
31483
+ return;
31484
+ }
31485
+ if (!isNodeOfType(child.id, "ArrayPattern") || !snapshotShape.elementKeys) return;
31486
+ for (let elementIndex = 0; elementIndex < child.id.elements.length; elementIndex += 1) {
31487
+ const element = child.id.elements[elementIndex];
31488
+ const elementKey = snapshotShape.elementKeys[elementIndex];
31489
+ if (!element || !elementKey || !isNodeOfType(element, "Identifier")) continue;
31490
+ const previousSymbol = context.scopes.symbolFor(element);
31491
+ if (previousSymbol && previousSymbol.kind === "const") previousValueKeys.set(previousSymbol.id, elementKey);
31492
+ }
31493
+ });
31494
+ return {
31495
+ refShapes,
31496
+ previousValueKeys
31497
+ };
31498
+ };
31499
+ const getSnapshotValueKey = (expression, environment) => {
31500
+ const candidate = stripParenExpression(expression);
31501
+ const refSymbol = getRefCurrentSymbol(candidate, environment.context);
31502
+ if (refSymbol) return environment.snapshotEnvironment.refShapes.get(refSymbol.id)?.scalarKey ?? null;
31503
+ if (!isNodeOfType(candidate, "Identifier")) return null;
31504
+ const symbol = environment.context.scopes.symbolFor(candidate);
31505
+ if (!symbol) return null;
31506
+ const substitutedExpression = environment.substitutions.get(symbol.id);
31507
+ if (substitutedExpression) return getSnapshotValueKey(substitutedExpression, environment);
31508
+ const directKey = environment.snapshotEnvironment.previousValueKeys.get(symbol.id);
31509
+ if (directKey) return directKey;
31510
+ if (symbol.kind !== "const" || !symbol.initializer || environment.visitedSymbolIds.has(symbol.id)) return null;
31511
+ const nextVisitedSymbolIds = new Set(environment.visitedSymbolIds);
31512
+ nextVisitedSymbolIds.add(symbol.id);
31513
+ return getSnapshotValueKey(symbol.initializer, {
31514
+ ...environment,
31515
+ visitedSymbolIds: nextVisitedSymbolIds
31516
+ });
31517
+ };
31518
+ const evaluateComparison = (expression, environment) => {
31519
+ if (!isNodeOfType(expression, "BinaryExpression")) return null;
31520
+ if (![
31521
+ "===",
31522
+ "!==",
31523
+ "==",
31524
+ "!="
31525
+ ].includes(expression.operator)) return null;
31526
+ const resolveSubstitutedExpression = (operand) => {
31527
+ const candidate = stripParenExpression(operand);
31528
+ if (!isNodeOfType(candidate, "Identifier")) return candidate;
31529
+ const symbol = environment.context.scopes.symbolFor(candidate);
31530
+ return (symbol && environment.substitutions.get(symbol.id)) ?? candidate;
31531
+ };
31532
+ const leftExpression = resolveSubstitutedExpression(expression.left);
31533
+ const rightExpression = resolveSubstitutedExpression(expression.right);
31534
+ const leftSnapshotKey = getSnapshotValueKey(leftExpression, environment);
31535
+ const rightSnapshotKey = getSnapshotValueKey(rightExpression, environment);
31536
+ const leftCurrentKey = getStableCurrentValueKey(environment.analysis, leftExpression, environment.context);
31537
+ const rightCurrentKey = getStableCurrentValueKey(environment.analysis, rightExpression, environment.context);
31538
+ const currentExpression = leftSnapshotKey !== null && leftSnapshotKey === rightCurrentKey ? rightExpression : rightSnapshotKey !== null && rightSnapshotKey === leftCurrentKey ? leftExpression : null;
31539
+ if (!currentExpression || !isDefinitelyReflexiveExpression(currentExpression, environment.analysis, environment.context)) return null;
31540
+ return expression.operator === "===" || expression.operator === "==";
31541
+ };
31542
+ const hasDirectGlobalObjectIsWrite = (objectIdentifier, context) => {
31543
+ const program = findProgramRoot(objectIdentifier);
31544
+ if (!program) return true;
31545
+ let didFindWrite = false;
31546
+ walkAst(program, (candidate) => {
31547
+ if (didFindWrite) return false;
31548
+ let writeTarget = null;
31549
+ if (isNodeOfType(candidate, "AssignmentExpression")) writeTarget = candidate.left;
31550
+ else if (isNodeOfType(candidate, "UpdateExpression")) writeTarget = candidate.argument;
31551
+ else if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "delete") writeTarget = candidate.argument;
31552
+ if (!writeTarget) return;
31553
+ const target = stripParenExpression(writeTarget);
31554
+ if (isNodeOfType(target, "Identifier")) {
31555
+ didFindWrite = target.name === "Object" && context.scopes.isGlobalReference(target);
31556
+ return;
31557
+ }
31558
+ if (isNodeOfType(target, "MemberExpression") && isNodeOfType(target.object, "Identifier") && target.object.name === "Object" && context.scopes.isGlobalReference(target.object)) {
31559
+ const propertyName = getStaticMemberPropertyName(target);
31560
+ didFindWrite = propertyName === null || propertyName === "is";
31561
+ }
31562
+ });
31563
+ return didFindWrite;
31564
+ };
31565
+ const evaluateObjectIs = (expression, environment) => {
31566
+ if (!isNodeOfType(expression, "CallExpression")) return null;
31567
+ const callee = stripParenExpression(expression.callee);
31568
+ if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.object, "Identifier") || callee.object.name !== "Object" || !environment.context.scopes.isGlobalReference(callee.object) || getStaticMemberPropertyName(callee) !== "is") return null;
31569
+ if (hasPossibleStaticPropertyWrite(callee.object, "is", environment.context.scopes) || hasDirectGlobalObjectIsWrite(callee.object, environment.context)) return null;
31570
+ const argumentsToCompare = expression.arguments ?? [];
31571
+ if (argumentsToCompare.length !== 2) return null;
31572
+ const resolveSubstitutedExpression = (operand) => {
31573
+ const candidate = stripParenExpression(operand);
31574
+ if (!isNodeOfType(candidate, "Identifier")) return candidate;
31575
+ const symbol = environment.context.scopes.symbolFor(candidate);
31576
+ return (symbol && environment.substitutions.get(symbol.id)) ?? candidate;
31577
+ };
31578
+ const leftExpression = resolveSubstitutedExpression(argumentsToCompare[0]);
31579
+ const rightExpression = resolveSubstitutedExpression(argumentsToCompare[1]);
31580
+ const leftSnapshotKey = getSnapshotValueKey(leftExpression, environment);
31581
+ const rightSnapshotKey = getSnapshotValueKey(rightExpression, environment);
31582
+ const leftCurrentKey = getStableCurrentValueKey(environment.analysis, leftExpression, environment.context);
31583
+ const rightCurrentKey = getStableCurrentValueKey(environment.analysis, rightExpression, environment.context);
31584
+ return leftSnapshotKey !== null && leftSnapshotKey === rightCurrentKey ? true : rightSnapshotKey !== null && rightSnapshotKey === leftCurrentKey ? true : null;
31585
+ };
31586
+ const getHelperReturnExpression = (functionNode) => {
31587
+ if (!isFunctionLike$1(functionNode)) return null;
31588
+ if (Boolean(functionNode.async) || Boolean(functionNode.generator)) return null;
31589
+ if (!isNodeOfType(functionNode.body, "BlockStatement")) return functionNode.body;
31590
+ const statements = functionNode.body.body ?? [];
31591
+ if (statements.length !== 1 || !isNodeOfType(statements[0], "ReturnStatement")) return null;
31592
+ return statements[0].argument ? statements[0].argument : null;
31593
+ };
31594
+ const evaluateBoolean = (expression, environment) => {
31595
+ const candidate = stripParenExpression(expression);
31596
+ if (isNodeOfType(candidate, "Literal") && typeof candidate.value === "boolean") return candidate.value;
31597
+ if (isNodeOfType(candidate, "Identifier")) {
31598
+ const symbol = environment.context.scopes.symbolFor(candidate);
31599
+ if (!symbol) return null;
31600
+ const substitutedExpression = environment.substitutions.get(symbol.id);
31601
+ if (substitutedExpression) return evaluateBoolean(substitutedExpression, environment);
31602
+ if (symbol.kind !== "const" || !symbol.initializer || environment.visitedSymbolIds.has(symbol.id)) return null;
31603
+ const nextVisitedSymbolIds = new Set(environment.visitedSymbolIds);
31604
+ nextVisitedSymbolIds.add(symbol.id);
31605
+ return evaluateBoolean(symbol.initializer, {
31606
+ ...environment,
31607
+ visitedSymbolIds: nextVisitedSymbolIds
31608
+ });
31609
+ }
31610
+ if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "!") {
31611
+ const argumentValue = evaluateBoolean(candidate.argument, environment);
31612
+ return argumentValue === null ? null : !argumentValue;
31613
+ }
31614
+ if (isNodeOfType(candidate, "LogicalExpression")) {
31615
+ const leftValue = evaluateBoolean(candidate.left, environment);
31616
+ if (candidate.operator === "&&") {
31617
+ if (leftValue === false) return false;
31618
+ const rightValue = evaluateBoolean(candidate.right, environment);
31619
+ if (leftValue === true) return rightValue;
31620
+ return rightValue === false ? false : null;
31621
+ }
31622
+ if (candidate.operator === "||") {
31623
+ if (leftValue === true) return true;
31624
+ const rightValue = evaluateBoolean(candidate.right, environment);
31625
+ if (leftValue === false) return rightValue;
31626
+ return rightValue === true ? true : null;
31627
+ }
31628
+ return null;
31629
+ }
31630
+ const objectIsValue = evaluateObjectIs(candidate, environment);
31631
+ if (objectIsValue !== null) return objectIsValue;
31632
+ const comparisonValue = evaluateComparison(candidate, environment);
31633
+ if (comparisonValue !== null) return comparisonValue;
31634
+ if (!environment.allowHelperCall || !isNodeOfType(candidate, "CallExpression")) return null;
31635
+ const callee = stripParenExpression(candidate.callee);
31636
+ if (!isNodeOfType(callee, "Identifier")) return null;
31637
+ const calleeReference = getRef(environment.analysis, callee);
31638
+ if (calleeReference?.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init)) return null;
31639
+ const helperFunction = calleeReference ? resolveToFunction(calleeReference) : null;
31640
+ const returnExpression = helperFunction ? getHelperReturnExpression(helperFunction) : null;
31641
+ if (!helperFunction || !returnExpression || !isFunctionLike$1(helperFunction)) return null;
31642
+ const parameters = helperFunction.params ?? [];
31643
+ const argumentsForHelper = candidate.arguments ?? [];
31644
+ if (parameters.length !== argumentsForHelper.length) return null;
31645
+ const substitutions = new Map(environment.substitutions);
31646
+ for (let parameterIndex = 0; parameterIndex < parameters.length; parameterIndex += 1) {
31647
+ const parameter = parameters[parameterIndex];
31648
+ const argument = argumentsForHelper[parameterIndex];
31649
+ if (!parameter || !argument || !isNodeOfType(parameter, "Identifier")) return null;
31650
+ const parameterSymbol = environment.context.scopes.symbolFor(parameter);
31651
+ if (!parameterSymbol) return null;
31652
+ substitutions.set(parameterSymbol.id, argument);
31653
+ }
31654
+ return evaluateBoolean(returnExpression, {
31655
+ ...environment,
31656
+ substitutions,
31657
+ visitedSymbolIds: /* @__PURE__ */ new Set(),
31658
+ allowHelperCall: false
31659
+ });
31660
+ };
31661
+ const statementCanCompleteNormally = (statement, environment) => {
31662
+ if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement") || isNodeOfType(statement, "BreakStatement") || isNodeOfType(statement, "ContinueStatement")) return false;
31663
+ if (isNodeOfType(statement, "BlockStatement")) return (statement.body ?? []).every((childStatement) => statementCanCompleteNormally(childStatement, environment));
31664
+ if (isNodeOfType(statement, "IfStatement")) {
31665
+ const testValue = evaluateBoolean(statement.test, environment);
31666
+ if (testValue === true) return statementCanCompleteNormally(statement.consequent, environment);
31667
+ if (testValue === false) return statement.alternate ? statementCanCompleteNormally(statement.alternate, environment) : true;
31668
+ const consequentCanComplete = statementCanCompleteNormally(statement.consequent, environment);
31669
+ const alternateCanComplete = statement.alternate ? statementCanCompleteNormally(statement.alternate, environment) : true;
31670
+ return consequentCanComplete || alternateCanComplete;
31671
+ }
31672
+ if (isNodeOfType(statement, "LabeledStatement")) {
31673
+ const labelName = isNodeOfType(statement.label, "Identifier") ? statement.label.name : null;
31674
+ if (labelName && statementCanCompleteWithBreakToLabel(statement.body, labelName, environment)) return true;
31675
+ return statementCanCompleteNormallyForLabel(statement.body, environment);
31676
+ }
31677
+ return true;
31678
+ };
31679
+ const statementCanCompleteNormallyForLabel = (statement, environment) => {
31680
+ if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement") || isNodeOfType(statement, "BreakStatement") || isNodeOfType(statement, "ContinueStatement")) return false;
31681
+ if (isNodeOfType(statement, "BlockStatement")) return (statement.body ?? []).every((childStatement) => statementCanCompleteNormallyForLabel(childStatement, environment));
31682
+ if (isNodeOfType(statement, "IfStatement")) {
31683
+ const testValue = evaluateBoolean(statement.test, environment);
31684
+ if (testValue === true) return statementCanCompleteNormallyForLabel(statement.consequent, environment);
31685
+ if (testValue === false) return statement.alternate ? statementCanCompleteNormallyForLabel(statement.alternate, environment) : true;
31686
+ const consequentCanComplete = statementCanCompleteNormallyForLabel(statement.consequent, environment);
31687
+ const alternateCanComplete = statement.alternate ? statementCanCompleteNormallyForLabel(statement.alternate, environment) : true;
31688
+ return consequentCanComplete || alternateCanComplete;
31689
+ }
31690
+ if (isNodeOfType(statement, "LabeledStatement")) return statementCanCompleteNormally(statement, environment);
31691
+ return isNodeOfType(statement, "ExpressionStatement") || isNodeOfType(statement, "VariableDeclaration") || isNodeOfType(statement, "EmptyStatement") || isNodeOfType(statement, "DebuggerStatement");
31692
+ };
31693
+ const statementCanCompleteWithBreakToLabel = (statement, labelName, environment) => {
31694
+ if (isNodeOfType(statement, "BreakStatement")) return Boolean(isNodeOfType(statement.label, "Identifier") && statement.label.name === labelName);
31695
+ if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement") || isNodeOfType(statement, "ContinueStatement")) return false;
31696
+ if (isNodeOfType(statement, "BlockStatement")) {
31697
+ for (const childStatement of statement.body ?? []) {
31698
+ if (statementCanCompleteWithBreakToLabel(childStatement, labelName, environment)) return true;
31699
+ if (!statementCanCompleteNormallyForLabel(childStatement, environment)) return false;
31700
+ }
31701
+ return false;
31702
+ }
31703
+ if (isNodeOfType(statement, "IfStatement")) {
31704
+ const testValue = evaluateBoolean(statement.test, environment);
31705
+ if (testValue === true) return statementCanCompleteWithBreakToLabel(statement.consequent, labelName, environment);
31706
+ if (testValue === false) return Boolean(statement.alternate && statementCanCompleteWithBreakToLabel(statement.alternate, labelName, environment));
31707
+ return statementCanCompleteWithBreakToLabel(statement.consequent, labelName, environment) || Boolean(statement.alternate && statementCanCompleteWithBreakToLabel(statement.alternate, labelName, environment));
31708
+ }
31709
+ if (isNodeOfType(statement, "LabeledStatement")) return statementCanCompleteWithBreakToLabel(statement.body, labelName, environment);
31710
+ return false;
31711
+ };
31712
+ const isReachableUnderSnapshotEnvironment = (target, effectFunction, environment) => {
31713
+ if (environment.context.cfg.enclosingFunction(target) !== effectFunction) return true;
31714
+ let current = target;
31715
+ while (current !== effectFunction) {
31716
+ const parent = current.parent;
31717
+ if (!parent) return true;
31718
+ if (isFunctionLike$1(parent) && parent !== effectFunction) return true;
31719
+ if (isNodeOfType(parent, "IfStatement")) {
31720
+ const testValue = evaluateBoolean(parent.test, environment);
31721
+ if (isAstDescendant(current, parent.consequent) && testValue === false) return false;
31722
+ if (parent.alternate && isAstDescendant(current, parent.alternate) && testValue === true) return false;
31723
+ }
31724
+ if (isNodeOfType(parent, "LogicalExpression") && parent.right === current) {
31725
+ const leftValue = evaluateBoolean(parent.left, environment);
31726
+ if (parent.operator === "&&" && leftValue === false) return false;
31727
+ if (parent.operator === "||" && leftValue === true) return false;
31728
+ }
31729
+ if (isNodeOfType(parent, "ConditionalExpression")) {
31730
+ const testValue = evaluateBoolean(parent.test, environment);
31731
+ if (parent.consequent === current && testValue === false) return false;
31732
+ if (parent.alternate === current && testValue === true) return false;
31733
+ }
31734
+ if (isNodeOfType(parent, "BlockStatement")) {
31735
+ const containingStatementIndex = (parent.body ?? []).findIndex((statement) => isAstDescendant(current, statement));
31736
+ if (containingStatementIndex >= 0) {
31737
+ if ((parent.body ?? []).slice(0, containingStatementIndex).some((statement) => !statementCanCompleteNormally(statement, environment))) return false;
31738
+ }
31739
+ }
31740
+ current = parent;
31741
+ }
31742
+ return true;
31743
+ };
31744
+ const createStateTriggerReachability = ({ analysis, context, effectFunction }) => {
31745
+ const snapshotEnvironment = collectSnapshotEnvironment(analysis, effectFunction, context);
31746
+ if (snapshotEnvironment.refShapes.size === 0) return () => true;
31747
+ const environment = {
31748
+ analysis,
31749
+ context,
31750
+ effectFunction,
31751
+ snapshotEnvironment,
31752
+ substitutions: /* @__PURE__ */ new Map(),
31753
+ visitedSymbolIds: /* @__PURE__ */ new Set(),
31754
+ allowHelperCall: true
31755
+ };
31756
+ return (target) => isReachableUnderSnapshotEnvironment(target, effectFunction, environment);
31757
+ };
31758
+ //#endregion
30646
31759
  //#region src/plugin/rules/state-and-effects/no-chain-state-updates.ts
30647
31760
  const getUseStateDeclarator = (ref) => (ref.resolved?.defs ?? []).map((def) => def.node).find((node) => isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "ArrayPattern")) ?? null;
30648
31761
  const isDeclaredWithin = (node, container) => {
@@ -30708,11 +31821,17 @@ const noChainStateUpdates = defineRule({
30708
31821
  if (stateDeps.length === 0) return;
30709
31822
  if (stateDeps.every((ref) => isExternallyDrivenState(analysis, ref))) return;
30710
31823
  const stateDepDeclarators = new Set(stateDeps.map((ref) => getUseStateDeclarator(ref)).filter((declarator) => declarator !== null));
31824
+ const isReachableFromStateTrigger = createStateTriggerReachability({
31825
+ analysis,
31826
+ context,
31827
+ effectFunction: effectFn
31828
+ });
30711
31829
  const domSyncedStateDeclarators = /* @__PURE__ */ new Set();
30712
31830
  for (const ref of effectFnRefs) {
30713
31831
  if (!isSyncStateSetterCall(analysis, ref, effectFn)) continue;
30714
31832
  const callExpr = getCallExpr(ref);
30715
31833
  if (!callExpr) continue;
31834
+ if (!isReachableFromStateTrigger(callExpr)) continue;
30716
31835
  if (!readsPostMountValueThroughLocals(callExpr, effectFn, { ignoreBareRefCurrent: true })) continue;
30717
31836
  const declarator = getUseStateDeclarator(ref);
30718
31837
  if (declarator) domSyncedStateDeclarators.add(declarator);
@@ -30721,6 +31840,7 @@ const noChainStateUpdates = defineRule({
30721
31840
  if (!isSyncStateSetterCall(analysis, ref, effectFn)) continue;
30722
31841
  const callExpr = getCallExpr(ref);
30723
31842
  if (!callExpr) continue;
31843
+ if (!isReachableFromStateTrigger(callExpr)) continue;
30724
31844
  if (getArgsUpstreamRefs(analysis, ref).some((argRef) => isState(analysis, argRef))) continue;
30725
31845
  const setterDeclarator = getUseStateDeclarator(ref);
30726
31846
  if (setterDeclarator && domSyncedStateDeclarators.has(setterDeclarator)) continue;
@@ -40920,7 +42040,7 @@ const noPropCallbackInRender = defineRule({
40920
42040
  if (!analysis) return;
40921
42041
  const callee = stripParenExpression(node.callee);
40922
42042
  if (isFunctionLike$1(callee)) return;
40923
- if (!getDownstreamRefs(analysis, callee).some((reference) => isPropCallbackInvocationRef(analysis, reference))) return;
42043
+ if (!getDownstreamRefs(analysis, callee).some((reference) => isPropCallbackInvocationRef(analysis, reference, { nativeMethodScopes: context.scopes }))) return;
40924
42044
  context.report({
40925
42045
  node,
40926
42046
  message: "This prop callback runs during render. React can replay or discard render work, so the callback can fire more than once or for UI that never commits."
@@ -41883,18 +43003,107 @@ const getNodeText$1 = (node) => {
41883
43003
  return value;
41884
43004
  });
41885
43005
  };
41886
- const isLiteralConstantIdentifier = (analysis, identifier) => {
41887
- const definitions = getRef(analysis, identifier)?.resolved?.defs;
41888
- if (!definitions || definitions.length !== 1) return false;
41889
- const definition = definitions[0];
41890
- if (definition.type !== "Variable") return false;
41891
- const declarator = definition.node;
41892
- if (!isNodeOfType(declarator, "VariableDeclarator")) return false;
41893
- const declaration = declarator.parent;
41894
- if (!isNodeOfType(declaration, "VariableDeclaration") || declaration.kind !== "const") return false;
41895
- return isNodeOfType(declarator.init, "Literal");
43006
+ const normalizeAsBoolean = (identity) => ({
43007
+ ...identity,
43008
+ booleanNormalization: identity.booleanNormalization === "identity" ? "normalized" : identity.booleanNormalization
43009
+ });
43010
+ const negateBoolean = (identity) => ({
43011
+ ...identity,
43012
+ booleanNormalization: identity.booleanNormalization === "negated" ? "normalized" : "negated"
43013
+ });
43014
+ const getLivePropExpressionIdentity = (analysis, context, node, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
43015
+ const expression = stripParenExpression(node);
43016
+ if (isNodeOfType(expression, "Identifier")) {
43017
+ const reference = getRef(analysis, expression);
43018
+ const symbol = context.scopes.symbolFor(expression);
43019
+ if (!reference || !symbol) return null;
43020
+ if (isProp(analysis, reference)) return {
43021
+ propSymbolId: symbol.id,
43022
+ memberPath: [],
43023
+ booleanNormalization: "identity"
43024
+ };
43025
+ if (visitedSymbolIds.has(symbol.id) || symbol.kind !== "const") return null;
43026
+ visitedSymbolIds.add(symbol.id);
43027
+ const initializer = getDirectConstInitializer(symbol);
43028
+ if (initializer) return getLivePropExpressionIdentity(analysis, context, initializer, visitedSymbolIds);
43029
+ return getUpstreamRefs(analysis, reference).some((upstreamReference) => isProp(analysis, upstreamReference)) ? {
43030
+ propSymbolId: symbol.id,
43031
+ memberPath: [],
43032
+ booleanNormalization: "identity"
43033
+ } : null;
43034
+ }
43035
+ if (isNodeOfType(expression, "MemberExpression")) {
43036
+ const propertyName = getStaticMemberPropertyName(expression);
43037
+ if (!propertyName) return null;
43038
+ const objectIdentity = getLivePropExpressionIdentity(analysis, context, expression.object, visitedSymbolIds);
43039
+ if (!objectIdentity) return null;
43040
+ return {
43041
+ ...objectIdentity,
43042
+ memberPath: [...objectIdentity.memberPath, propertyName]
43043
+ };
43044
+ }
43045
+ if (isNodeOfType(expression, "CallExpression") && isNodeOfType(expression.callee, "Identifier") && expression.callee.name === "Boolean" && context.scopes.isGlobalReference(expression.callee) && expression.arguments?.length === 1) {
43046
+ const argument = expression.arguments[0];
43047
+ const argumentIdentity = getLivePropExpressionIdentity(analysis, context, argument, visitedSymbolIds);
43048
+ return argumentIdentity ? normalizeAsBoolean(argumentIdentity) : null;
43049
+ }
43050
+ if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") {
43051
+ const argumentIdentity = getLivePropExpressionIdentity(analysis, context, expression.argument, visitedSymbolIds);
43052
+ return argumentIdentity ? negateBoolean(argumentIdentity) : null;
43053
+ }
43054
+ return null;
43055
+ };
43056
+ const isMountSnapshotInitializer = (context, node) => {
43057
+ if (!isReactApiCall(node, "useMemo", context.scopes, { resolveNamedAliases: true }) || !isNodeOfType(node, "CallExpression")) return false;
43058
+ const dependencies = node.arguments?.[1];
43059
+ return isNodeOfType(dependencies, "ArrayExpression") && dependencies.elements.length === 0;
43060
+ };
43061
+ const isMountSnapshotBinding = (context, identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
43062
+ const symbol = context.scopes.symbolFor(identifier);
43063
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
43064
+ visitedSymbolIds.add(symbol.id);
43065
+ const initializer = getDirectConstInitializer(symbol);
43066
+ if (!initializer) return false;
43067
+ if (isMountSnapshotInitializer(context, initializer)) return true;
43068
+ const expression = stripParenExpression(initializer);
43069
+ return isNodeOfType(expression, "Identifier") && isMountSnapshotBinding(context, expression, visitedSymbolIds);
41896
43070
  };
41897
- const isSetStateToInitialValue = (analysis, setterRef) => {
43071
+ const isConstantBooleanExpression = (context, node, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
43072
+ const expression = stripParenExpression(node);
43073
+ if (isNodeOfType(expression, "Literal")) return true;
43074
+ if (isNodeOfType(expression, "Identifier")) {
43075
+ const symbol = context.scopes.symbolFor(expression);
43076
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
43077
+ visitedSymbolIds.add(symbol.id);
43078
+ const initializer = getDirectConstInitializer(symbol);
43079
+ return Boolean(initializer && isConstantBooleanExpression(context, initializer, visitedSymbolIds));
43080
+ }
43081
+ if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") return isConstantBooleanExpression(context, expression.argument, visitedSymbolIds);
43082
+ if (isNodeOfType(expression, "CallExpression") && isNodeOfType(expression.callee, "Identifier") && expression.callee.name === "Boolean" && context.scopes.isGlobalReference(expression.callee) && expression.arguments?.length === 1) return isConstantBooleanExpression(context, expression.arguments[0], visitedSymbolIds);
43083
+ return false;
43084
+ };
43085
+ const isProvenLiveBinding = (analysis, context, identifier) => {
43086
+ const reference = getRef(analysis, identifier);
43087
+ const symbol = context.scopes.symbolFor(identifier);
43088
+ if (!reference || !symbol || symbol.kind !== "const" || isOutsideAllFunctions(symbol)) return false;
43089
+ const initializer = symbol.initializer;
43090
+ if (!initializer || isMountSnapshotBinding(context, identifier)) return false;
43091
+ if (getDownstreamRefs(analysis, initializer).some((initializerReference) => isRefCurrent(initializerReference))) return false;
43092
+ return !isConstantBooleanExpression(context, initializer);
43093
+ };
43094
+ const areSameProvenLiveBinding = (analysis, context, left, right) => {
43095
+ const leftExpression = stripParenExpression(left);
43096
+ const rightExpression = stripParenExpression(right);
43097
+ if (!isNodeOfType(leftExpression, "Identifier") || !isNodeOfType(rightExpression, "Identifier")) return false;
43098
+ const leftSymbol = context.scopes.symbolFor(leftExpression);
43099
+ const rightSymbol = context.scopes.symbolFor(rightExpression);
43100
+ return Boolean(leftSymbol && leftSymbol === rightSymbol && isProvenLiveBinding(analysis, context, leftExpression));
43101
+ };
43102
+ const haveSameLivePropExpressionIdentity = (left, right) => {
43103
+ if (left.propSymbolId !== right.propSymbolId || left.booleanNormalization !== right.booleanNormalization || left.memberPath.length !== right.memberPath.length) return false;
43104
+ return left.memberPath.every((propertyName, index) => propertyName === right.memberPath[index]);
43105
+ };
43106
+ const isSetStateToInitialValue = (analysis, context, setterRef) => {
41898
43107
  const callExpr = getCallExpr(setterRef);
41899
43108
  if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) return false;
41900
43109
  const setStateToValue = callExpr.arguments?.[0];
@@ -41905,7 +43114,12 @@ const isSetStateToInitialValue = (analysis, setterRef) => {
41905
43114
  if (isUndefinedNode(setStateToValue) && isUndefinedNode(stateInitialValue)) return true;
41906
43115
  if (setStateToValue == null && stateInitialValue == null) return true;
41907
43116
  if (setStateToValue && !stateInitialValue || !setStateToValue && stateInitialValue) return false;
41908
- if (stateInitialValue && isNodeOfType(stateInitialValue, "Identifier") && stateInitialValue.name !== "undefined" && !isLiteralConstantIdentifier(analysis, stateInitialValue)) return false;
43117
+ if (stateInitialValue && setStateToValue) {
43118
+ const initialLivePropIdentity = getLivePropExpressionIdentity(analysis, context, stateInitialValue);
43119
+ const nextLivePropIdentity = getLivePropExpressionIdentity(analysis, context, setStateToValue);
43120
+ if (initialLivePropIdentity && nextLivePropIdentity && haveSameLivePropExpressionIdentity(initialLivePropIdentity, nextLivePropIdentity)) return false;
43121
+ if (areSameProvenLiveBinding(analysis, context, stateInitialValue, setStateToValue)) return false;
43122
+ }
41909
43123
  return getNodeText$1(setStateToValue) === getNodeText$1(stateInitialValue);
41910
43124
  };
41911
43125
  const countUseStates = (analysis, componentNode) => {
@@ -41914,10 +43128,10 @@ const countUseStates = (analysis, componentNode) => {
41914
43128
  for (const ref of getDownstreamRefs(analysis, componentNode)) if (isState(analysis, ref)) stateVariables.add(ref.resolved);
41915
43129
  return stateVariables.size;
41916
43130
  };
41917
- const findPropUsedToResetAllState = (analysis, effectFnRefs, depsRefs, useEffectNode, effectFn) => {
43131
+ const findPropUsedToResetAllState = (analysis, context, effectFnRefs, depsRefs, useEffectNode, effectFn) => {
41918
43132
  const stateSetterRefs = effectFnRefs.filter((ref) => isSyncStateSetterCall(analysis, ref, effectFn));
41919
43133
  if (stateSetterRefs.length === 0) return null;
41920
- if (!stateSetterRefs.every((ref) => isSetStateToInitialValue(analysis, ref))) return null;
43134
+ if (!stateSetterRefs.every((ref) => isSetStateToInitialValue(analysis, context, ref))) return null;
41921
43135
  if (stateSetterRefs.every((setterRef) => effectFnRefs.some((otherRef) => otherRef !== setterRef && otherRef.resolved === setterRef.resolved && Boolean(getCallExpr(otherRef)) && !isSyncStateSetterCall(analysis, otherRef, effectFn)))) return null;
41922
43136
  const containing = findContainingNode(analysis, useEffectNode);
41923
43137
  if (new Set(stateSetterRefs.map((setterRef) => setterRef.resolved)).size !== countUseStates(analysis, containing)) return null;
@@ -41941,7 +43155,7 @@ const noResetAllStateOnPropChange = defineRule({
41941
43155
  if (containing && isCustomHook(containing)) return;
41942
43156
  const effectFn = getEffectFn(analysis, node);
41943
43157
  if (!effectFn) return;
41944
- if (!findPropUsedToResetAllState(analysis, effectFnRefs, depsRefs, node, effectFn)) return;
43158
+ if (!findPropUsedToResetAllState(analysis, context, effectFnRefs, depsRefs, node, effectFn)) return;
41945
43159
  context.report({
41946
43160
  node,
41947
43161
  message: `Your users briefly see stale state when a prop changes because this useEffect clears all state.`
@@ -45744,19 +46958,726 @@ const nosqlInjectionRisk = defineRule({
45744
46958
  })
45745
46959
  });
45746
46960
  //#endregion
46961
+ //#region src/plugin/utils/export-all-adds-runtime-values.ts
46962
+ const getExportedName = (node) => {
46963
+ if (!node) return null;
46964
+ if (isNodeOfType(node, "Identifier")) return node.name;
46965
+ if (isNodeOfType(node, "Literal") && typeof node.value === "string") return node.value;
46966
+ return null;
46967
+ };
46968
+ const programHasRuntimeNamedExports = (filePath, program, visitedFilePaths) => {
46969
+ if (!isNodeOfType(program, "Program")) return true;
46970
+ for (const statement of program.body) {
46971
+ if (isNodeOfType(statement, "ExportDefaultDeclaration")) continue;
46972
+ if (isNodeOfType(statement, "ExportAllDeclaration")) {
46973
+ if (statement.exportKind === "type") continue;
46974
+ if (statement.exported) return true;
46975
+ if (typeof statement.source.value !== "string") return true;
46976
+ if (exportAllAddsRuntimeValues(filePath, statement.source.value, visitedFilePaths)) return true;
46977
+ continue;
46978
+ }
46979
+ if (!isNodeOfType(statement, "ExportNamedDeclaration")) continue;
46980
+ if (statement.exportKind === "type") continue;
46981
+ if (statement.declaration) {
46982
+ if (statement.declaration.type !== "TSInterfaceDeclaration" && statement.declaration.type !== "TSTypeAliasDeclaration" && statement.declaration.type !== "TSDeclareFunction") return true;
46983
+ continue;
46984
+ }
46985
+ if (isEverySpecifierInlineType(statement.specifiers, "ExportSpecifier", "exportKind")) continue;
46986
+ for (const specifier of statement.specifiers) {
46987
+ if (!isNodeOfType(specifier, "ExportSpecifier") || specifier.exportKind === "type") continue;
46988
+ if (getExportedName(specifier.exported) !== "default") return true;
46989
+ }
46990
+ }
46991
+ return false;
46992
+ };
46993
+ const exportAllAddsRuntimeValues = (importerFilePath, source, visitedFilePaths = /* @__PURE__ */ new Set()) => {
46994
+ const resolvedFilePath = resolveRelativeImportPath(importerFilePath, source);
46995
+ if (!resolvedFilePath) return true;
46996
+ if (path.extname(resolvedFilePath) === ".cjs" || resolvedFilePath.endsWith(".cts")) return true;
46997
+ if (visitedFilePaths.has(resolvedFilePath)) return true;
46998
+ visitedFilePaths.add(resolvedFilePath);
46999
+ const program = parseSourceFile(resolvedFilePath);
47000
+ if (!program) return true;
47001
+ return programHasRuntimeNamedExports(resolvedFilePath, program, visitedFilePaths);
47002
+ };
47003
+ //#endregion
45747
47004
  //#region src/plugin/utils/is-framework-route-or-special-filename.ts
45748
- const sourceFileExtensionGroup = NEXTJS_SOURCE_FILE_EXTENSION_GROUP;
45749
- const FRAMEWORK_ROUTE_FILE_PATTERNS = [
45750
- new RegExp(`^(page|layout|loading|error|not-found|template|default|global-error|route|_app|_document|_error)\\.${sourceFileExtensionGroup}$`),
45751
- new RegExp(`^(_layout|\\+html|\\+not-found|\\+native-intent)\\.${sourceFileExtensionGroup}$`),
45752
- new RegExp(`(?:^__root|\\.lazy)\\.${sourceFileExtensionGroup}$`),
45753
- new RegExp(`^(root|entry\\.client|entry\\.server)\\.${sourceFileExtensionGroup}$`)
45754
- ];
45755
- const isFrameworkRouteOrSpecialFilename = (rawFilename) => {
47005
+ const NEXT_APP_ROUTE_FILE_PATTERN = new RegExp(`^(page|layout|loading|error|not-found|template|default|global-error|route)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
47006
+ const NEXT_PAGES_ROUTE_FILE_PATTERN = new RegExp(`^(_app|_document|_error|_meta)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
47007
+ const EXPO_ROUTE_FILE_PATTERN = new RegExp(`^(_layout|\\+html|\\+not-found|\\+native-intent)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
47008
+ const TANSTACK_ROUTE_FILE_PATTERN = new RegExp(`(?:^__root|\\.lazy)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
47009
+ const REACT_ROUTER_FILE_PATTERN = new RegExp(`^(root|entry\\.client|entry\\.server)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
47010
+ const isInNextDirectory = (context, directoryPath) => {
47011
+ if (isInProjectDirectory(context, directoryPath)) return true;
47012
+ const filename = normalizeFilename(context.filename ?? "");
47013
+ if (path.isAbsolute(filename)) return false;
47014
+ return filename.startsWith(`${directoryPath}/`) || filename.includes(`/${directoryPath}/`);
47015
+ };
47016
+ const isInExpoRouteDirectory = (context) => {
47017
+ const filename = normalizeFilename(context.filename ?? "");
47018
+ const configuredRootDirectory = getReactDoctorStringSetting(context.settings, "rootDirectory");
47019
+ const packageDirectory = path.isAbsolute(filename) ? findNearestPackageDirectory(filename) : null;
47020
+ const projectRelativeFilename = getProjectRelativeFilename(filename, configuredRootDirectory ?? packageDirectory ?? void 0);
47021
+ const relativeFilename = projectRelativeFilename === filename && path.isAbsolute(filename) ? filename.split("/").slice(2).join("/") : projectRelativeFilename;
47022
+ return relativeFilename.startsWith("app/") || relativeFilename.startsWith("src/app/");
47023
+ };
47024
+ const isFrameworkRouteOrSpecialFilename = (context, runtime) => {
47025
+ const rawFilename = context.filename;
45756
47026
  if (!rawFilename) return false;
45757
- if (isNextjsMetadataImageRouteFilename(rawFilename)) return true;
45758
47027
  const basename = path.basename(rawFilename);
45759
- return FRAMEWORK_ROUTE_FILE_PATTERNS.some((pattern) => pattern.test(basename));
47028
+ if (runtime === "next") return isInNextDirectory(context, "app") && (isNextjsMetadataImageRouteFilename(rawFilename) || NEXT_APP_ROUTE_FILE_PATTERN.test(basename)) || isInNextDirectory(context, "pages") && NEXT_PAGES_ROUTE_FILE_PATTERN.test(basename);
47029
+ if (runtime === "expo") return isInExpoRouteDirectory(context) && EXPO_ROUTE_FILE_PATTERN.test(basename);
47030
+ if (runtime === "tanstack") return TANSTACK_ROUTE_FILE_PATTERN.test(basename);
47031
+ if (runtime === "react-router" || runtime === "remix") return REACT_ROUTER_FILE_PATTERN.test(basename);
47032
+ return false;
47033
+ };
47034
+ //#endregion
47035
+ //#region src/plugin/utils/function-has-react-element-return-type.ts
47036
+ const isReactNamespaceBinding = (node) => {
47037
+ if (!isNodeOfType(node, "Identifier")) return false;
47038
+ const binding = getImportBindingForName(node, node.name);
47039
+ return Boolean(binding && binding.source === "react" && (binding.isNamespace || binding.exportedName === "default"));
47040
+ };
47041
+ const hasLocalJsxNamespace = (node) => {
47042
+ const program = findProgramRoot(node);
47043
+ if (!program || !isNodeOfType(program, "Program")) return false;
47044
+ return program.body.some((statement) => isNodeOfType(statement, "TSModuleDeclaration") && isNodeOfType(statement.id, "Identifier") && statement.id.name === "JSX");
47045
+ };
47046
+ const isReactElementTypeReference = (node) => {
47047
+ if (!isNodeOfType(node, "TSTypeReference")) return false;
47048
+ const typeName = node.typeName;
47049
+ if (isNodeOfType(typeName, "Identifier")) return getImportedNameFromModule(typeName, typeName.name, "react") === "ReactElement";
47050
+ if (!isNodeOfType(typeName, "TSQualifiedName")) return false;
47051
+ if (!isNodeOfType(typeName.right, "Identifier")) return false;
47052
+ if (typeName.right.name === "ReactElement" && isReactNamespaceBinding(typeName.left)) return true;
47053
+ if (typeName.right.name !== "Element") return false;
47054
+ if (isNodeOfType(typeName.left, "Identifier")) {
47055
+ if (typeName.left.name !== "JSX") return false;
47056
+ const binding = getImportBindingForName(typeName.left, typeName.left.name);
47057
+ if (!binding) return !hasLocalJsxNamespace(typeName.left);
47058
+ return binding.source === "react" && binding.exportedName === "JSX";
47059
+ }
47060
+ return isNodeOfType(typeName.left, "TSQualifiedName") && isNodeOfType(typeName.left.right, "Identifier") && typeName.left.right.name === "JSX" && isReactNamespaceBinding(typeName.left.left);
47061
+ };
47062
+ const containsReactElementType = (node) => {
47063
+ if (isNodeOfType(node, "TSUnionType")) return node.types.some((member) => containsReactElementType(member));
47064
+ return isReactElementTypeReference(node);
47065
+ };
47066
+ const functionHasReactElementReturnType = (functionNode) => {
47067
+ const returnType = Reflect.get(functionNode, "returnType");
47068
+ if (!isNodeOfType(returnType, "TSTypeAnnotation")) return false;
47069
+ return containsReactElementType(returnType.typeAnnotation);
47070
+ };
47071
+ //#endregion
47072
+ //#region src/plugin/constants/fast-refresh.ts
47073
+ const FAST_REFRESH_CONFIG_FILENAMES = [
47074
+ "vite.config.ts",
47075
+ "vite.config.mts",
47076
+ "vite.config.cts",
47077
+ "vite.config.js",
47078
+ "vite.config.mjs",
47079
+ "vite.config.cjs",
47080
+ "webpack.config.ts",
47081
+ "webpack.config.js",
47082
+ "webpack.config.mjs",
47083
+ "webpack.config.cjs",
47084
+ "rsbuild.config.ts",
47085
+ "rsbuild.config.js",
47086
+ "rspack.config.ts",
47087
+ "rspack.config.js"
47088
+ ];
47089
+ const MINIMUM_FAST_REFRESH_VERSIONS = {
47090
+ dumi: {
47091
+ major: 2,
47092
+ minor: 0
47093
+ },
47094
+ expo: {
47095
+ major: 36,
47096
+ minor: 0
47097
+ },
47098
+ gatsby: {
47099
+ major: 2,
47100
+ minor: 31
47101
+ },
47102
+ next: {
47103
+ major: 9,
47104
+ minor: 4
47105
+ },
47106
+ parcel: {
47107
+ major: 2,
47108
+ minor: 0
47109
+ },
47110
+ reactForGatsbyTwo: {
47111
+ major: 17,
47112
+ minor: 0
47113
+ },
47114
+ reactNative: {
47115
+ major: 0,
47116
+ minor: 61
47117
+ },
47118
+ reactScripts: {
47119
+ major: 4,
47120
+ minor: 0
47121
+ },
47122
+ storybookReact: {
47123
+ major: 6,
47124
+ minor: 1
47125
+ }
47126
+ };
47127
+ //#endregion
47128
+ //#region src/plugin/utils/get-fast-refresh-file-status.ts
47129
+ const INTEGRATION_IMPORTS = new Map([
47130
+ ["@vitejs/plugin-react", {
47131
+ importedNames: null,
47132
+ requiresViteDevelopmentServer: true,
47133
+ runtime: "generic"
47134
+ }],
47135
+ ["@vitejs/plugin-react-swc", {
47136
+ importedNames: null,
47137
+ requiresViteDevelopmentServer: true,
47138
+ runtime: "generic"
47139
+ }],
47140
+ ["@pmmmwh/react-refresh-webpack-plugin", {
47141
+ importedNames: null,
47142
+ runtime: "generic"
47143
+ }],
47144
+ ["@react-router/dev/vite", {
47145
+ importedNames: new Set(["reactRouter"]),
47146
+ requiresViteDevelopmentServer: true,
47147
+ runtime: "react-router"
47148
+ }],
47149
+ ["@remix-run/dev", {
47150
+ importedNames: new Set(["vitePlugin"]),
47151
+ requiresViteDevelopmentServer: true,
47152
+ runtime: "remix"
47153
+ }],
47154
+ ["@rsbuild/plugin-react", {
47155
+ importedNames: new Set(["pluginReact"]),
47156
+ runtime: "generic"
47157
+ }],
47158
+ ["@rspack/plugin-react-refresh", {
47159
+ importedNames: null,
47160
+ runtime: "generic"
47161
+ }],
47162
+ ["@rozenite/vite-plugin", {
47163
+ importedNames: new Set(["rozenitePlugin"]),
47164
+ requiresViteDevelopmentServer: true,
47165
+ runtime: "generic"
47166
+ }],
47167
+ ["@tanstack/react-start/plugin/vite", {
47168
+ importedNames: new Set(["tanstackStart"]),
47169
+ requiresViteDevelopmentServer: true,
47170
+ runtime: "tanstack"
47171
+ }]
47172
+ ]);
47173
+ const REGISTERED_INTEGRATION_RUNTIME_PRECEDENCE = [
47174
+ "tanstack",
47175
+ "remix",
47176
+ "react-router",
47177
+ "generic"
47178
+ ];
47179
+ const cachedLocalStatusByManifest = /* @__PURE__ */ new WeakMap();
47180
+ const cachedWorkspaceIndexByManifest = /* @__PURE__ */ new WeakMap();
47181
+ const INACTIVE_STATUS = {
47182
+ isActive: false,
47183
+ runtime: "generic"
47184
+ };
47185
+ const WORKSPACE_IGNORED_DIRECTORY_NAMES = new Set([
47186
+ ".git",
47187
+ ".next",
47188
+ "build",
47189
+ "coverage",
47190
+ "dist",
47191
+ "node_modules",
47192
+ "out"
47193
+ ]);
47194
+ const parseVersion = (version) => {
47195
+ if (typeof version !== "string") return null;
47196
+ const match = version.match(/(?:^|[^\d])(\d+)(?:\.(\d+))?/);
47197
+ if (!match) return null;
47198
+ return {
47199
+ major: Number(match[1]),
47200
+ minor: Number(match[2] ?? 0)
47201
+ };
47202
+ };
47203
+ const isVersionAtLeast = (version, minimum) => {
47204
+ const parsed = parseVersion(version);
47205
+ if (!parsed) return false;
47206
+ return parsed.major > minimum.major || parsed.major === minimum.major && parsed.minor >= minimum.minor;
47207
+ };
47208
+ const getDependencyVersion = (manifest, dependencyName) => manifest.dependencies?.[dependencyName] ?? manifest.devDependencies?.[dependencyName] ?? manifest.peerDependencies?.[dependencyName] ?? manifest.optionalDependencies?.[dependencyName];
47209
+ const getRuntimeDependencyVersion = (manifest, dependencyName) => manifest.dependencies?.[dependencyName] ?? manifest.optionalDependencies?.[dependencyName];
47210
+ const getOwnedDependencyVersion = (manifest, dependencyName, developmentCommandPattern) => {
47211
+ const runtimeVersion = getRuntimeDependencyVersion(manifest, dependencyName);
47212
+ if (runtimeVersion !== void 0) return runtimeVersion;
47213
+ return Object.values(manifest.scripts ?? {}).some((script) => typeof script === "string" && developmentCommandPattern.test(script)) ? manifest.devDependencies?.[dependencyName] : void 0;
47214
+ };
47215
+ const hasParcelBrowserEntry = (manifest) => {
47216
+ if (typeof manifest.source === "string" && manifest.source.endsWith(".html")) return true;
47217
+ return Object.values(manifest.scripts ?? {}).some((script) => typeof script === "string" && /(?:^|\s)parcel(?:\s+serve)?\s+[^\n]*\.html(?:\s|$)/.test(script));
47218
+ };
47219
+ const hasParcelDevelopmentCommand = (manifest) => Object.values(manifest.scripts ?? {}).some((script) => typeof script === "string" && /(?:^|[\s;&|'\\"])parcel(?=\s|$)(?!\s+(?:build|help|watch|--help|--version|-h|-V)(?:\s|$))/.test(script) && !/(?:^|\s)--no-hmr(?:\s|$)/.test(script));
47220
+ const hasOwnedDevelopmentCommand = (manifest, commandPattern) => Object.values(manifest.scripts ?? {}).some((script) => typeof script === "string" && commandPattern.test(script));
47221
+ const hasViteDevelopmentCommand = (manifest) => Object.values(manifest.scripts ?? {}).some((script) => {
47222
+ if (typeof script !== "string") return false;
47223
+ return [...script.matchAll(/(?:^|[\s;&|'\\"])vite(?:\s+(build|preview|test)\b)?/g)].some((match) => match[1] === void 0);
47224
+ });
47225
+ const hasViteBrowserEntry = (manifest, packageDirectory) => {
47226
+ if (typeof manifest.source === "string" && manifest.source.endsWith(".html")) return true;
47227
+ const browserEntryPath = path.join(packageDirectory, "index.html");
47228
+ recordExistenceProbe(browserEntryPath);
47229
+ return fs.existsSync(browserEntryPath);
47230
+ };
47231
+ const getBuiltInStatus = (manifest) => {
47232
+ if (isVersionAtLeast(getOwnedDependencyVersion(manifest, "next", /(?:^|\s)next\s+dev(?:\s|$)/), MINIMUM_FAST_REFRESH_VERSIONS.next)) return {
47233
+ isActive: true,
47234
+ runtime: "next"
47235
+ };
47236
+ const gatsbyDependency = getOwnedDependencyVersion(manifest, "gatsby", /(?:^|\s)gatsby\s+develop(?:\s|$)/);
47237
+ const gatsbyVersion = parseVersion(gatsbyDependency);
47238
+ const hasGatsbyFastRefresh = gatsbyVersion !== null && (gatsbyVersion.major >= 3 || isVersionAtLeast(gatsbyDependency, MINIMUM_FAST_REFRESH_VERSIONS.gatsby) && isVersionAtLeast(getDependencyVersion(manifest, "react"), MINIMUM_FAST_REFRESH_VERSIONS.reactForGatsbyTwo));
47239
+ if (isVersionAtLeast(getOwnedDependencyVersion(manifest, "react-scripts", /(?:^|\s)react-scripts\s+start(?:\s|$)/), MINIMUM_FAST_REFRESH_VERSIONS.reactScripts) || hasGatsbyFastRefresh || isVersionAtLeast(getDependencyVersion(manifest, "parcel"), MINIMUM_FAST_REFRESH_VERSIONS.parcel) && declaresDependency(manifest, "react") && hasParcelDevelopmentCommand(manifest) && hasParcelBrowserEntry(manifest)) return {
47240
+ isActive: true,
47241
+ runtime: "generic"
47242
+ };
47243
+ if (isVersionAtLeast(getOwnedDependencyVersion(manifest, "expo", /(?:^|\s)expo\s+start(?:\s|$)/), MINIMUM_FAST_REFRESH_VERSIONS.expo) || isVersionAtLeast(getOwnedDependencyVersion(manifest, "react-native", /(?:^|\s)react-native\s+start(?:\s|$)/), MINIMUM_FAST_REFRESH_VERSIONS.reactNative)) return {
47244
+ isActive: true,
47245
+ runtime: "expo"
47246
+ };
47247
+ if (isVersionAtLeast(getOwnedDependencyVersion(manifest, "dumi", /(?:^|\s)dumi\s+dev(?:\s|$)/), MINIMUM_FAST_REFRESH_VERSIONS.dumi)) return {
47248
+ isActive: true,
47249
+ runtime: "generic"
47250
+ };
47251
+ return null;
47252
+ };
47253
+ const getIntegrationLocalNames = (program, scopes) => {
47254
+ const localBindings = /* @__PURE__ */ new Map();
47255
+ const registerLocalName = (source, importedName, localIdentifier) => {
47256
+ const integration = INTEGRATION_IMPORTS.get(source);
47257
+ if (!integration) return;
47258
+ if (integration.importedNames === null ? importedName === null || importedName === "default" : importedName !== null && integration.importedNames.has(importedName)) {
47259
+ const symbol = scopes.symbolFor(localIdentifier);
47260
+ if (symbol) localBindings.set(symbol, integration);
47261
+ }
47262
+ };
47263
+ walkAst(program, (node) => {
47264
+ if (isNodeOfType(node, "ImportDeclaration")) {
47265
+ const source = node.source.value;
47266
+ if (typeof source !== "string") return;
47267
+ for (const specifier of node.specifiers) {
47268
+ if (isNodeOfType(specifier, "ImportDefaultSpecifier") || isNodeOfType(specifier, "ImportNamespaceSpecifier")) {
47269
+ registerLocalName(source, null, specifier.local);
47270
+ continue;
47271
+ }
47272
+ registerLocalName(source, getImportedName(specifier) ?? null, specifier.local);
47273
+ }
47274
+ return;
47275
+ }
47276
+ if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return;
47277
+ const initializer = node.init;
47278
+ if (!isNodeOfType(initializer, "CallExpression") || !isNodeOfType(initializer.callee, "Identifier") || initializer.callee.name !== "require") return;
47279
+ const sourceArgument = initializer.arguments[0];
47280
+ if (!sourceArgument || !isNodeOfType(sourceArgument, "Literal")) return;
47281
+ const source = sourceArgument.value;
47282
+ if (typeof source !== "string") return;
47283
+ if (isNodeOfType(node.id, "Identifier")) {
47284
+ registerLocalName(source, null, node.id);
47285
+ return;
47286
+ }
47287
+ if (!isNodeOfType(node.id, "ObjectPattern")) return;
47288
+ for (const property of node.id.properties) {
47289
+ if (!isNodeOfType(property, "Property") || !isNodeOfType(property.value, "Identifier")) continue;
47290
+ registerLocalName(source, isNodeOfType(property.key, "Identifier") ? property.key.name : isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" ? property.key.value : null, property.value);
47291
+ }
47292
+ });
47293
+ return localBindings;
47294
+ };
47295
+ const isModuleExportsAssignment = (node) => isNodeOfType(node, "AssignmentExpression") && isNodeOfType(node.left, "MemberExpression") && isNodeOfType(node.left.object, "Identifier") && node.left.object.name === "module" && isNodeOfType(node.left.property, "Identifier") && node.left.property.name === "exports";
47296
+ const getExportedBindings = (program, scopes) => {
47297
+ const exportedBindings = /* @__PURE__ */ new Set();
47298
+ walkAst(program, (node) => {
47299
+ let exportRoot = null;
47300
+ if (isNodeOfType(node, "ExportDefaultDeclaration")) exportRoot = node.declaration;
47301
+ else if (isNodeOfType(node, "AssignmentExpression") && isModuleExportsAssignment(node)) exportRoot = node.right;
47302
+ if (!exportRoot) return;
47303
+ if (isFunctionLike$1(exportRoot) || isNodeOfType(exportRoot, "ObjectExpression")) return;
47304
+ walkAst(exportRoot, (exportNode) => {
47305
+ if (isFunctionLike$1(exportNode) || isNodeOfType(exportNode, "ObjectExpression")) return false;
47306
+ if (!isNodeOfType(exportNode, "Identifier")) return;
47307
+ const symbol = scopes.symbolFor(exportNode);
47308
+ if (symbol) exportedBindings.add(symbol);
47309
+ });
47310
+ });
47311
+ return exportedBindings;
47312
+ };
47313
+ const isExportedConfigProperty = (property, exportedBindings, scopes) => {
47314
+ let ancestor = property.parent;
47315
+ let didFindContainingObject = false;
47316
+ let didCrossNestedProperty = false;
47317
+ let didCrossFunctionBoundary = false;
47318
+ let containingFunction = null;
47319
+ let containingReturn = null;
47320
+ while (ancestor) {
47321
+ if (isNodeOfType(ancestor, "ObjectExpression") && !didFindContainingObject) didFindContainingObject = true;
47322
+ else if (didFindContainingObject && isNodeOfType(ancestor, "Property")) didCrossNestedProperty = true;
47323
+ if (!containingReturn && isNodeOfType(ancestor, "ReturnStatement")) containingReturn = ancestor;
47324
+ if (isFunctionLike$1(ancestor)) if (containingFunction) didCrossFunctionBoundary = true;
47325
+ else containingFunction = ancestor;
47326
+ if (isNodeOfType(ancestor, "ExportDefaultDeclaration") || isModuleExportsAssignment(ancestor)) return !didCrossNestedProperty && !didCrossFunctionBoundary && (!containingFunction || Boolean(containingReturn) || !isNodeOfType(containingFunction.body, "BlockStatement"));
47327
+ if (isNodeOfType(ancestor, "VariableDeclarator") && isNodeOfType(ancestor.id, "Identifier")) {
47328
+ const binding = scopes.symbolFor(ancestor.id);
47329
+ if (binding && exportedBindings.has(binding)) return !didCrossNestedProperty && !containingFunction;
47330
+ }
47331
+ ancestor = ancestor.parent;
47332
+ }
47333
+ return false;
47334
+ };
47335
+ const getExplicitConfigPaths = (packageDirectory, manifest) => {
47336
+ const configPaths = new Set(FAST_REFRESH_CONFIG_FILENAMES.map((configFilename) => path.join(packageDirectory, configFilename)));
47337
+ for (const script of Object.values(manifest.scripts ?? {})) {
47338
+ if (typeof script !== "string") continue;
47339
+ for (const match of script.matchAll(/(?:^|\s)--config(?:=|\s+)(?:"([^"]+)"|'([^']+)'|([^\s;&|]+))/g)) {
47340
+ const relativeConfigPath = match[1] ?? match[2] ?? match[3];
47341
+ if (!relativeConfigPath) continue;
47342
+ const configPath = path.resolve(packageDirectory, relativeConfigPath);
47343
+ const relativePath = path.relative(packageDirectory, configPath);
47344
+ if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) continue;
47345
+ configPaths.add(configPath);
47346
+ }
47347
+ }
47348
+ return [...configPaths];
47349
+ };
47350
+ const getRegisteredIntegration = (packageDirectory, manifest) => {
47351
+ for (const configPath of getExplicitConfigPaths(packageDirectory, manifest)) {
47352
+ const program = parseSourceFile(configPath);
47353
+ if (!program) continue;
47354
+ const scopes = analyzeScopes(program);
47355
+ const integrationLocalBindings = getIntegrationLocalNames(program, scopes);
47356
+ if (integrationLocalBindings.size === 0) continue;
47357
+ const exportedBindings = getExportedBindings(program, scopes);
47358
+ const registeredIntegrations = [];
47359
+ walkAst(program, (node) => {
47360
+ if (!isNodeOfType(node, "Property")) return;
47361
+ if (!(isNodeOfType(node.key, "Identifier") && node.key.name === "plugins" || isNodeOfType(node.key, "Literal") && node.key.value === "plugins")) return;
47362
+ if (!isExportedConfigProperty(node, exportedBindings, scopes)) return;
47363
+ const inspectedBindings = /* @__PURE__ */ new Set();
47364
+ const inspectValue = (value) => {
47365
+ walkAst(value, (valueNode) => {
47366
+ if (isNodeOfType(valueNode, "Identifier")) {
47367
+ const symbol = scopes.symbolFor(valueNode);
47368
+ const initializer = symbol ? getDirectUnreassignedInitializer(symbol) : null;
47369
+ if (symbol && initializer && !inspectedBindings.has(symbol.id)) {
47370
+ inspectedBindings.add(symbol.id);
47371
+ inspectValue(initializer);
47372
+ }
47373
+ }
47374
+ if (!isNodeOfType(valueNode, "CallExpression") && !isNodeOfType(valueNode, "NewExpression")) return;
47375
+ if (!isNodeOfType(valueNode.callee, "Identifier")) return;
47376
+ const symbol = scopes.symbolFor(valueNode.callee);
47377
+ const integration = symbol ? integrationLocalBindings.get(symbol) : null;
47378
+ if (integration) registeredIntegrations.push(integration);
47379
+ });
47380
+ };
47381
+ inspectValue(node.value);
47382
+ return false;
47383
+ });
47384
+ const hasViteDevelopmentRuntime = hasViteDevelopmentCommand(manifest) || hasViteBrowserEntry(manifest, packageDirectory);
47385
+ const registeredIntegration = REGISTERED_INTEGRATION_RUNTIME_PRECEDENCE.flatMap((runtime) => registeredIntegrations.filter((integration) => integration.runtime === runtime && (!integration.requiresViteDevelopmentServer || hasViteDevelopmentRuntime)))[0];
47386
+ if (registeredIntegration) return {
47387
+ isActive: true,
47388
+ runtime: registeredIntegration.runtime
47389
+ };
47390
+ }
47391
+ return null;
47392
+ };
47393
+ const hasStorybookReactViteConfig = (packageDirectory) => {
47394
+ for (const configFilename of [
47395
+ "main.ts",
47396
+ "main.js",
47397
+ "main.mjs",
47398
+ "main.cjs"
47399
+ ]) {
47400
+ const program = parseSourceFile(path.join(packageDirectory, ".storybook", configFilename));
47401
+ if (!program) continue;
47402
+ const scopes = analyzeScopes(program);
47403
+ const exportedBindings = getExportedBindings(program, scopes);
47404
+ let hasReactViteFramework = false;
47405
+ walkAst(program, (node) => {
47406
+ if (!isNodeOfType(node, "Property")) return;
47407
+ if (!(isNodeOfType(node.key, "Identifier") && node.key.name === "framework" || isNodeOfType(node.key, "Literal") && node.key.value === "framework")) return;
47408
+ if (!isExportedConfigProperty(node, exportedBindings, scopes)) return;
47409
+ walkAst(node.value, (valueNode) => {
47410
+ if (isNodeOfType(valueNode, "Literal") && valueNode.value === "@storybook/react-vite") {
47411
+ hasReactViteFramework = true;
47412
+ return false;
47413
+ }
47414
+ });
47415
+ if (hasReactViteFramework) return false;
47416
+ });
47417
+ if (hasReactViteFramework) return true;
47418
+ }
47419
+ return false;
47420
+ };
47421
+ const resolveDirectUnreassignedValue = (value, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
47422
+ const unwrappedValue = stripParenExpression(value);
47423
+ if (!isNodeOfType(unwrappedValue, "Identifier")) return unwrappedValue;
47424
+ const symbol = scopes.symbolFor(unwrappedValue);
47425
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return unwrappedValue;
47426
+ const initializer = getDirectUnreassignedInitializer(symbol);
47427
+ if (!initializer) return unwrappedValue;
47428
+ visitedSymbolIds.add(symbol.id);
47429
+ return resolveDirectUnreassignedValue(initializer, scopes, visitedSymbolIds);
47430
+ };
47431
+ const isStaticPropertyNamed = (node, propertyName) => isNodeOfType(node, "Property") && (!node.computed && isNodeOfType(node.key, "Identifier") && node.key.name === propertyName || isNodeOfType(node.key, "Literal") && node.key.value === propertyName);
47432
+ const readLastStaticBooleanProperty = (value, propertyName, scopes) => {
47433
+ const resolvedValue = resolveDirectUnreassignedValue(value, scopes);
47434
+ if (!isNodeOfType(resolvedValue, "ObjectExpression")) return null;
47435
+ let propertyValue = null;
47436
+ for (const property of resolvedValue.properties) {
47437
+ if (isNodeOfType(property, "SpreadElement")) {
47438
+ propertyValue = null;
47439
+ continue;
47440
+ }
47441
+ if (!isStaticPropertyNamed(property, propertyName) || !isNodeOfType(property, "Property")) continue;
47442
+ propertyValue = readStaticBoolean(resolveDirectUnreassignedValue(property.value, scopes));
47443
+ }
47444
+ return propertyValue;
47445
+ };
47446
+ const isLastStaticPropertyInObject = (property, propertyName) => {
47447
+ if (!isNodeOfType(property.parent, "ObjectExpression")) return false;
47448
+ const propertyIndex = property.parent.properties.findIndex((objectProperty) => objectProperty === property);
47449
+ return property.parent.properties.slice(propertyIndex + 1).every((laterProperty) => {
47450
+ if (isNodeOfType(laterProperty, "SpreadElement")) return false;
47451
+ return !isStaticPropertyNamed(laterProperty, propertyName);
47452
+ });
47453
+ };
47454
+ const hasStorybookReactWebpackFastRefreshConfig = (packageDirectory) => {
47455
+ for (const configFilename of [
47456
+ "main.ts",
47457
+ "main.js",
47458
+ "main.mjs",
47459
+ "main.cjs"
47460
+ ]) {
47461
+ const program = parseSourceFile(path.join(packageDirectory, ".storybook", configFilename));
47462
+ if (!program) continue;
47463
+ const scopes = analyzeScopes(program);
47464
+ const exportedBindings = getExportedBindings(program, scopes);
47465
+ let hasFastRefresh = false;
47466
+ walkAst(program, (node) => {
47467
+ if (!isStaticPropertyNamed(node, "reactOptions") || !isNodeOfType(node, "Property")) return;
47468
+ if (!isExportedConfigProperty(node, exportedBindings, scopes)) return;
47469
+ if (!isLastStaticPropertyInObject(node, "reactOptions")) return;
47470
+ if (readLastStaticBooleanProperty(node.value, "fastRefresh", scopes) === true) {
47471
+ hasFastRefresh = true;
47472
+ return false;
47473
+ }
47474
+ });
47475
+ if (hasFastRefresh) return true;
47476
+ }
47477
+ return false;
47478
+ };
47479
+ const hasStorybookReactViteIntegration = (packageDirectory, manifest) => hasOwnedDevelopmentCommand(manifest, /(?:^|\s)(?:storybook\s+dev|start-storybook)(?:\s|$)/) && hasStorybookReactViteConfig(packageDirectory);
47480
+ const hasStorybookReactWebpackFastRefreshIntegration = (packageDirectory, manifest) => {
47481
+ const developmentCommandPattern = /(?:^|\s)(?:storybook\s+dev|start-storybook)(?:\s|$)/;
47482
+ if (!hasOwnedDevelopmentCommand(manifest, developmentCommandPattern)) return false;
47483
+ return isVersionAtLeast(getOwnedDependencyVersion(manifest, "@storybook/react", developmentCommandPattern) ?? getOwnedDependencyVersion(manifest, "@storybook/react-webpack5", developmentCommandPattern), MINIMUM_FAST_REFRESH_VERSIONS.storybookReact) && hasStorybookReactWebpackFastRefreshConfig(packageDirectory);
47484
+ };
47485
+ const hasNxStorybookReactViteIntegration = (packageDirectory) => {
47486
+ if (!hasStorybookReactViteConfig(packageDirectory)) return false;
47487
+ try {
47488
+ const project = JSON.parse(fs.readFileSync(path.join(packageDirectory, "project.json"), "utf8"));
47489
+ if (typeof project !== "object" || project === null) return false;
47490
+ const targets = Reflect.get(project, "targets");
47491
+ if (typeof targets !== "object" || targets === null) return false;
47492
+ return Object.keys(targets).some((targetName) => targetName === "storybook:serve:dev");
47493
+ } catch {
47494
+ return false;
47495
+ }
47496
+ };
47497
+ const getLocalFastRefreshStatus = (packageDirectory, manifest) => {
47498
+ const cachedStatus = cachedLocalStatusByManifest.get(manifest);
47499
+ if (cachedStatus) return cachedStatus;
47500
+ const status = getBuiltInStatus(manifest) ?? (hasStorybookReactViteIntegration(packageDirectory, manifest) || hasNxStorybookReactViteIntegration(packageDirectory) || hasStorybookReactWebpackFastRefreshIntegration(packageDirectory, manifest) ? {
47501
+ isActive: true,
47502
+ runtime: "generic"
47503
+ } : null) ?? getRegisteredIntegration(packageDirectory, manifest) ?? INACTIVE_STATUS;
47504
+ cachedLocalStatusByManifest.set(manifest, status);
47505
+ return status;
47506
+ };
47507
+ const isWorkspaceRoot = (directory, manifest) => {
47508
+ if (manifest?.workspaces !== void 0) return true;
47509
+ return [
47510
+ "pnpm-workspace.yaml",
47511
+ "pnpm-workspace.yml",
47512
+ "nx.json"
47513
+ ].some((filename) => fs.existsSync(path.join(directory, filename)));
47514
+ };
47515
+ const findWorkspaceRoot = (packageDirectory) => {
47516
+ let currentDirectory = packageDirectory;
47517
+ let workspaceRoot = null;
47518
+ while (true) {
47519
+ const manifest = readPackageManifest(currentDirectory);
47520
+ if (isWorkspaceRoot(currentDirectory, manifest)) workspaceRoot = currentDirectory;
47521
+ const parentDirectory = path.dirname(currentDirectory);
47522
+ if (parentDirectory === currentDirectory) return workspaceRoot;
47523
+ currentDirectory = parentDirectory;
47524
+ }
47525
+ };
47526
+ const collectWorkspacePackages = (workspaceRoot) => {
47527
+ const packages = [];
47528
+ const pendingDirectories = [workspaceRoot];
47529
+ while (pendingDirectories.length > 0) {
47530
+ const directory = pendingDirectories.pop();
47531
+ if (!directory) continue;
47532
+ const manifest = readPackageManifest(directory);
47533
+ if (manifest) packages.push({
47534
+ directory,
47535
+ manifest,
47536
+ status: getLocalFastRefreshStatus(directory, manifest)
47537
+ });
47538
+ let entries;
47539
+ try {
47540
+ entries = fs.readdirSync(directory, { withFileTypes: true });
47541
+ } catch {
47542
+ continue;
47543
+ }
47544
+ for (const entry of entries) {
47545
+ if (!entry.isDirectory()) continue;
47546
+ if (entry.name.startsWith(".") || WORKSPACE_IGNORED_DIRECTORY_NAMES.has(entry.name)) continue;
47547
+ pendingDirectories.push(path.join(directory, entry.name));
47548
+ }
47549
+ }
47550
+ return packages;
47551
+ };
47552
+ const isPropertyNamed = (node, name) => isNodeOfType(node, "Property") && (isNodeOfType(node.key, "Identifier") && node.key.name === name || isNodeOfType(node.key, "Literal") && node.key.value === name);
47553
+ const normalizeAliasRoot = (configDirectory, aliasPath) => {
47554
+ if (!aliasPath.startsWith(".") && !path.isAbsolute(aliasPath)) return null;
47555
+ const withoutPlaceholder = aliasPath.replace(/(?:\/)?(?:\*|\$\d+).*$/, "");
47556
+ return path.resolve(configDirectory, withoutPlaceholder);
47557
+ };
47558
+ const collectAliasRootsFromValue = (value, configDirectory, aliasRoots) => {
47559
+ walkAst(value, (node) => {
47560
+ if (!isPropertyNamed(node, "alias")) return;
47561
+ if (!isNodeOfType(node, "Property")) return;
47562
+ walkAst(node.value, (aliasNode) => {
47563
+ if (!isNodeOfType(aliasNode, "Literal") || typeof aliasNode.value !== "string") return;
47564
+ const aliasRoot = normalizeAliasRoot(configDirectory, aliasNode.value);
47565
+ if (aliasRoot) aliasRoots.add(aliasRoot);
47566
+ });
47567
+ return false;
47568
+ });
47569
+ };
47570
+ const getActivePackageAliasRoots = (packageDirectory, manifest) => {
47571
+ const aliasRoots = /* @__PURE__ */ new Set();
47572
+ const configPaths = [...getExplicitConfigPaths(packageDirectory, manifest), ...[
47573
+ "main.ts",
47574
+ "main.js",
47575
+ "main.mjs",
47576
+ "main.cjs"
47577
+ ].map((filename) => path.join(packageDirectory, ".storybook", filename))];
47578
+ for (const configPath of configPaths) {
47579
+ const program = parseSourceFile(configPath);
47580
+ if (!program) continue;
47581
+ const scopes = analyzeScopes(program);
47582
+ const exportedBindings = getExportedBindings(program, scopes);
47583
+ walkAst(program, (node) => {
47584
+ if (!isPropertyNamed(node, "resolve") && !isPropertyNamed(node, "viteFinal")) return;
47585
+ if (!isNodeOfType(node, "Property")) return;
47586
+ if (!isExportedConfigProperty(node, exportedBindings, scopes)) return;
47587
+ collectAliasRootsFromValue(node.value, path.dirname(configPath), aliasRoots);
47588
+ return false;
47589
+ });
47590
+ }
47591
+ return aliasRoots;
47592
+ };
47593
+ const collectStringValues = (value, values) => {
47594
+ if (typeof value === "string") {
47595
+ values.push(value);
47596
+ return;
47597
+ }
47598
+ if (Array.isArray(value)) {
47599
+ for (const entry of value) collectStringValues(entry, values);
47600
+ return;
47601
+ }
47602
+ if (typeof value !== "object" || value === null) return;
47603
+ for (const entry of Object.values(value)) collectStringValues(entry, values);
47604
+ };
47605
+ const hasSourceRuntimeEntry = (manifest) => {
47606
+ const entryValues = [];
47607
+ collectStringValues(manifest.exports, entryValues);
47608
+ collectStringValues(manifest.main, entryValues);
47609
+ collectStringValues(manifest.module, entryValues);
47610
+ return entryValues.some((entry) => /^\.\/?src(?:\/|\.|$)/.test(entry));
47611
+ };
47612
+ const getWorkspaceDependencyVersion = (manifest, dependencyName) => manifest.dependencies?.[dependencyName] ?? manifest.optionalDependencies?.[dependencyName];
47613
+ const buildWorkspaceFastRefreshIndex = (workspaceRoot, rootManifest) => {
47614
+ const cached = cachedWorkspaceIndexByManifest.get(rootManifest);
47615
+ if (cached) return cached;
47616
+ const workspacePackages = collectWorkspacePackages(workspaceRoot);
47617
+ const activePackages = workspacePackages.filter((workspacePackage) => workspacePackage.status.isActive);
47618
+ const aliasOwners = [];
47619
+ for (const activePackage of activePackages) for (const rootDirectory of getActivePackageAliasRoots(activePackage.directory, activePackage.manifest)) aliasOwners.push({
47620
+ rootDirectory,
47621
+ status: activePackage.status
47622
+ });
47623
+ const sourceEntryOwners = /* @__PURE__ */ new Map();
47624
+ for (const producerPackage of workspacePackages) {
47625
+ const packageName = producerPackage.manifest.name;
47626
+ if (typeof packageName !== "string" || !hasSourceRuntimeEntry(producerPackage.manifest)) continue;
47627
+ const owner = activePackages.find((activePackage) => {
47628
+ const dependencyVersion = getWorkspaceDependencyVersion(activePackage.manifest, packageName);
47629
+ return typeof dependencyVersion === "string" && dependencyVersion.startsWith("workspace:");
47630
+ });
47631
+ if (owner) sourceEntryOwners.set(producerPackage.directory, owner.status);
47632
+ }
47633
+ const index = {
47634
+ aliasOwners,
47635
+ sourceEntryOwners
47636
+ };
47637
+ cachedWorkspaceIndexByManifest.set(rootManifest, index);
47638
+ return index;
47639
+ };
47640
+ const isPathInside = (filePath, directory) => {
47641
+ const relativePath = path.relative(directory, filePath);
47642
+ return relativePath === "" || !relativePath.startsWith(`..${path.sep}`) && relativePath !== ".." && !path.isAbsolute(relativePath);
47643
+ };
47644
+ const getWorkspaceOwnedStatus = (filename, packageDirectory) => {
47645
+ const workspaceRoot = findWorkspaceRoot(packageDirectory);
47646
+ if (!workspaceRoot) return null;
47647
+ const rootManifest = readPackageManifest(workspaceRoot);
47648
+ if (!rootManifest) return null;
47649
+ const index = buildWorkspaceFastRefreshIndex(workspaceRoot, rootManifest);
47650
+ const aliasOwner = index.aliasOwners.find((owner) => isPathInside(filename, owner.rootDirectory));
47651
+ if (aliasOwner) return aliasOwner.status;
47652
+ for (const [producerDirectory, status] of index.sourceEntryOwners) if (isPathInside(filename, producerDirectory)) return status;
47653
+ return null;
47654
+ };
47655
+ const resolveFastRefreshFileStatus = (filename) => {
47656
+ const manifest = readNearestPackageManifest(filename);
47657
+ if (!manifest) return INACTIVE_STATUS;
47658
+ const packageDirectory = findNearestPackageDirectory(filename);
47659
+ if (!packageDirectory) return INACTIVE_STATUS;
47660
+ const localStatus = getLocalFastRefreshStatus(packageDirectory, manifest);
47661
+ if (localStatus.isActive) return localStatus;
47662
+ return getWorkspaceOwnedStatus(filename, packageDirectory) ?? INACTIVE_STATUS;
47663
+ };
47664
+ const getConfiguredFallbackStatus = (context) => {
47665
+ const framework = getReactDoctorStringSetting(context.settings, "framework");
47666
+ return {
47667
+ isActive: true,
47668
+ runtime: framework === "nextjs" ? "next" : framework === "expo" || framework === "react-native" ? "expo" : framework === "remix" ? "remix" : framework === "tanstack-start" ? "tanstack" : "generic"
47669
+ };
47670
+ };
47671
+ const getFastRefreshFileStatus = (context) => {
47672
+ let filename = context.filename;
47673
+ if (!filename) return getConfiguredFallbackStatus(context);
47674
+ if (!path.isAbsolute(filename)) {
47675
+ const absoluteFilename = path.resolve(filename);
47676
+ if (!fs.existsSync(absoluteFilename)) return getConfiguredFallbackStatus(context);
47677
+ filename = absoluteFilename;
47678
+ }
47679
+ if (!readNearestPackageManifest(filename)) return INACTIVE_STATUS;
47680
+ return resolveFastRefreshFileStatus(filename);
45760
47681
  };
45761
47682
  //#endregion
45762
47683
  //#region src/plugin/rules/react-builtins/only-export-components-tables.ts
@@ -45768,6 +47689,7 @@ const NOT_REACT_COMPONENT_EXPRESSION_TYPES = new Set([
45768
47689
  "ConditionalExpression",
45769
47690
  "Literal",
45770
47691
  "LogicalExpression",
47692
+ "NewExpression",
45771
47693
  "ObjectExpression",
45772
47694
  "TemplateLiteral",
45773
47695
  "ThisExpression",
@@ -45786,124 +47708,25 @@ const NON_FAST_REFRESH_PATH_SEGMENTS = [
45786
47708
  "/cypress/",
45787
47709
  "/.storybook/",
45788
47710
  "/stories/",
45789
- "/__stories__/",
45790
- "/playground/",
45791
- "/playgrounds/",
45792
- "/examples/",
45793
- "/example/",
45794
- "/demo/",
45795
- "/demos/",
45796
- "/sandbox/",
45797
- "/sandboxes/"
47711
+ "/__stories__/"
45798
47712
  ];
45799
- const ENTRY_POINT_BASENAMES = new Set([
45800
- "main.tsx",
45801
- "main.jsx",
45802
- "main.js",
45803
- "index.tsx",
45804
- "index.jsx",
45805
- "entry.tsx",
45806
- "entry.jsx",
45807
- "bootstrap.tsx",
45808
- "bootstrap.jsx",
45809
- "client.tsx",
45810
- "client.jsx",
45811
- "server.tsx",
45812
- "server.jsx",
45813
- "app.tsx",
45814
- "app.jsx",
45815
- "App.tsx",
45816
- "App.jsx"
45817
- ]);
45818
- const UTILITY_FILE_BASENAMES = new Set([
45819
- "utils.tsx",
45820
- "utils.jsx",
45821
- "util.tsx",
45822
- "util.jsx",
45823
- "helpers.tsx",
45824
- "helpers.jsx",
45825
- "helper.tsx",
45826
- "helper.jsx",
45827
- "shared.tsx",
45828
- "shared.jsx",
45829
- "common.tsx",
45830
- "common.jsx",
45831
- "lib.tsx",
45832
- "lib.jsx",
45833
- "nodeTypes.tsx",
45834
- "nodeTypes.jsx",
45835
- "node-types.tsx",
45836
- "node-types.jsx",
45837
- "edgeTypes.tsx",
45838
- "edgeTypes.jsx",
45839
- "edge-types.tsx",
45840
- "edge-types.jsx",
45841
- "cellTypes.tsx",
45842
- "cellTypes.jsx",
45843
- "columnTypes.tsx",
45844
- "columnTypes.jsx",
45845
- "columnDefs.tsx",
45846
- "columnDefs.jsx",
45847
- "columnRenderers.tsx",
45848
- "columnRenderers.jsx",
45849
- "columns.tsx",
45850
- "columns.jsx",
45851
- "mappings.tsx",
45852
- "mappings.jsx",
45853
- "mapping.tsx",
45854
- "mapping.jsx",
45855
- "lookups.tsx",
45856
- "lookups.jsx",
45857
- "lookup.tsx",
45858
- "lookup.jsx",
45859
- "registry.tsx",
45860
- "registry.jsx",
45861
- "toast.tsx",
45862
- "toast.jsx",
45863
- "toaster.tsx",
45864
- "toaster.jsx",
45865
- "theme.tsx",
45866
- "theme.jsx",
45867
- "tokens.tsx",
45868
- "tokens.jsx",
45869
- "palette.tsx",
45870
- "palette.jsx",
45871
- "colors.tsx",
45872
- "colors.jsx",
45873
- "colours.tsx",
45874
- "colours.jsx",
45875
- "constants.tsx",
45876
- "constants.jsx",
45877
- "enums.tsx",
45878
- "enums.jsx",
45879
- "types.tsx",
45880
- "types.jsx",
45881
- "schemas.tsx",
45882
- "schemas.jsx",
45883
- "schema.tsx",
45884
- "schema.jsx",
45885
- "definitions.tsx",
45886
- "definitions.jsx",
45887
- "config.tsx",
45888
- "config.jsx",
45889
- "defaults.tsx",
45890
- "defaults.jsx"
45891
- ]);
45892
- const ROUTE_FACTORY_CALLEE_NAMES = new Set([
47713
+ const TANSTACK_ROUTE_FACTORY_CALLEE_NAMES = new Set([
45893
47714
  ...TANSTACK_ROUTE_CREATION_FUNCTIONS,
45894
47715
  "createLazyFileRoute",
45895
47716
  "createLazyRoute",
45896
47717
  "createAPIFileRoute",
45897
47718
  "createServerFileRoute",
45898
47719
  "createServerRootRoute",
45899
- "createServerRoute",
47720
+ "createServerRoute"
47721
+ ]);
47722
+ const REACT_ROUTER_FACTORY_CALLEE_NAMES = new Set([
45900
47723
  "createBrowserRouter",
45901
47724
  "createHashRouter",
45902
47725
  "createMemoryRouter",
45903
47726
  "createStaticRouter",
45904
47727
  "createRouter"
45905
47728
  ]);
45906
- const ROUTE_MODULE_ALLOWED_EXPORT_NAMES = new Set([
47729
+ const REACT_ROUTER_ALLOWED_EXPORT_NAMES = new Set([
45907
47730
  "loader",
45908
47731
  "clientLoader",
45909
47732
  "action",
@@ -45914,7 +47737,9 @@ const ROUTE_MODULE_ALLOWED_EXPORT_NAMES = new Set([
45914
47737
  "handle",
45915
47738
  "shouldRevalidate",
45916
47739
  "middleware",
45917
- "unstable_middleware",
47740
+ "unstable_middleware"
47741
+ ]);
47742
+ const NEXT_ALLOWED_EXPORT_NAMES = new Set([
45918
47743
  "getServerSideProps",
45919
47744
  "getStaticProps",
45920
47745
  "getStaticPaths",
@@ -45934,9 +47759,9 @@ const ROUTE_MODULE_ALLOWED_EXPORT_NAMES = new Set([
45934
47759
  "runtime",
45935
47760
  "preferredRegion",
45936
47761
  "maxDuration",
45937
- "experimental_ppr",
45938
- "unstable_settings"
47762
+ "experimental_ppr"
45939
47763
  ]);
47764
+ const EXPO_ALLOWED_EXPORT_NAMES = new Set(["unstable_settings"]);
45940
47765
  //#endregion
45941
47766
  //#region src/plugin/rules/react-builtins/only-export-components.ts
45942
47767
  const NAMED_EXPORT_MESSAGE = "This file exports non-components, so Fast Refresh can't safely preserve component state.";
@@ -45949,6 +47774,8 @@ const DEFAULT_REACT_HOCS = [
45949
47774
  "forwardRef",
45950
47775
  "lazy"
45951
47776
  ];
47777
+ const EMPTY_NAME_SET = /* @__PURE__ */ new Set();
47778
+ const TEST_SUPPORT_FILE_PATTERN = /(?:^|\/)(?:test|spec)(?:[-_.]?(?:utils?|helpers?|setup|fixtures?))?\.(?:jsx?|tsx?)$/i;
45952
47779
  const resolveSettings$6 = (settings) => {
45953
47780
  const reactDoctor = settings?.["react-doctor"];
45954
47781
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.onlyExportComponents ?? {} : {};
@@ -45972,13 +47799,12 @@ const isReactCreateContext = (initializer) => {
45972
47799
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "createContext") return true;
45973
47800
  return false;
45974
47801
  };
45975
- const isRouteFactoryName = (name) => ROUTE_FACTORY_CALLEE_NAMES.has(name);
45976
- const isRouteFactoryCall = (expression) => {
47802
+ const isRouteFactoryCall = (expression, bindings) => {
45977
47803
  let currentCall = expression;
45978
47804
  while (isNodeOfType(currentCall, "CallExpression")) {
45979
47805
  const callee = currentCall.callee;
45980
- if (isNodeOfType(callee, "Identifier") && isRouteFactoryName(callee.name)) return true;
45981
- if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && isRouteFactoryName(callee.property.name)) return true;
47806
+ if (isNodeOfType(callee, "Identifier") && bindings.localNames.has(callee.name)) return true;
47807
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && bindings.namespaceNames.has(callee.object.name) && isNodeOfType(callee.property, "Identifier") && bindings.memberNames.has(callee.property.name)) return true;
45982
47808
  if (!isNodeOfType(callee, "CallExpression")) return false;
45983
47809
  currentCall = callee;
45984
47810
  }
@@ -46006,10 +47832,11 @@ const isHocCallee = (callee, state) => {
46006
47832
  const canBeReactFunctionComponent = (initializer, state) => {
46007
47833
  if (!initializer) return false;
46008
47834
  const expression = skipTsExpression(initializer);
46009
- if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression")) return true;
47835
+ if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression")) return functionHasReactRenderSemantics(expression, state);
46010
47836
  if (isNodeOfType(expression, "CallExpression")) return isHocCallee(expression.callee, state);
46011
47837
  return false;
46012
47838
  };
47839
+ const functionHasReactRenderSemantics = (functionNode, state) => functionContainsReactRenderOutput(functionNode, state.scopes, state.controlFlow) || functionHasReactElementReturnType(functionNode) || functionReturnsOnlyNull(functionNode);
46013
47840
  const isReactComponentInitializer = (expression, state) => {
46014
47841
  const stripped = skipTsExpression(expression);
46015
47842
  if (isNodeOfType(stripped, "ArrowFunctionExpression")) return true;
@@ -46018,6 +47845,60 @@ const isReactComponentInitializer = (expression, state) => {
46018
47845
  if (isNodeOfType(stripped, "CallExpression") && isHocCallee(stripped.callee, state) && stripped.arguments.length > 0) return true;
46019
47846
  return false;
46020
47847
  };
47848
+ const isNextDynamicCall = (expression, nextDynamicImportSymbolIds, scopes) => {
47849
+ const stripped = skipTsExpression(expression);
47850
+ if (!isNodeOfType(stripped, "CallExpression")) return false;
47851
+ const callee = skipTsExpression(stripped.callee);
47852
+ if (!isNodeOfType(callee, "Identifier")) return false;
47853
+ const symbol = scopes.symbolFor(callee);
47854
+ return symbol !== null && nextDynamicImportSymbolIds.has(symbol.id);
47855
+ };
47856
+ const functionReturnsNextDynamicComponent = (expression, nextDynamicImportSymbolIds, scopes) => {
47857
+ const stripped = skipTsExpression(expression);
47858
+ if (!isNodeOfType(stripped, "ArrowFunctionExpression") && !isNodeOfType(stripped, "FunctionExpression") && !isNodeOfType(stripped, "FunctionDeclaration")) return false;
47859
+ const body = stripped.body;
47860
+ if (!isNodeOfType(body, "BlockStatement")) return isNextDynamicCall(body, nextDynamicImportSymbolIds, scopes);
47861
+ if (body.body.length !== 1) return false;
47862
+ const statement = body.body[0];
47863
+ return Boolean(statement) && isNodeOfType(statement, "ReturnStatement") && Boolean(statement.argument) && isNextDynamicCall(statement.argument, nextDynamicImportSymbolIds, scopes);
47864
+ };
47865
+ const isComponentFactoryCall = (expression, state) => {
47866
+ const stripped = skipTsExpression(expression);
47867
+ if (!isNodeOfType(stripped, "CallExpression")) return false;
47868
+ const callee = skipTsExpression(stripped.callee);
47869
+ if (!isNodeOfType(callee, "Identifier")) return false;
47870
+ const symbol = state.scopes.symbolFor(callee);
47871
+ return symbol !== null && state.componentFactorySymbolIds.has(symbol.id);
47872
+ };
47873
+ const isProvenComponentValue = (expression, state, inspectedSymbolIds = /* @__PURE__ */ new Set()) => {
47874
+ const stripped = skipTsExpression(expression);
47875
+ if (isNodeOfType(stripped, "Identifier")) {
47876
+ const symbol = state.scopes.symbolFor(stripped);
47877
+ if (!symbol) return false;
47878
+ if (state.localComponentNames.has(stripped.name)) return symbol.references.every((reference) => reference.flag === "read");
47879
+ if (isReactComponentName(stripped.name) && symbol.kind === "import") return true;
47880
+ if (inspectedSymbolIds.has(symbol.id)) return false;
47881
+ const initializer = getDirectUnreassignedInitializer(symbol);
47882
+ if (!initializer) return false;
47883
+ const strippedInitializer = skipTsExpression(initializer);
47884
+ if (isNodeOfType(strippedInitializer, "ArrowFunctionExpression") || isNodeOfType(strippedInitializer, "FunctionExpression")) return false;
47885
+ return isProvenComponentValue(initializer, state, new Set([...inspectedSymbolIds, symbol.id]));
47886
+ }
47887
+ if (isNodeOfType(stripped, "MemberExpression") && !stripped.computed && isNodeOfType(stripped.object, "Identifier") && isNodeOfType(stripped.property, "Identifier") && isReactComponentName(stripped.property.name)) {
47888
+ const objectSymbol = state.scopes.symbolFor(stripped.object);
47889
+ return objectSymbol !== null && state.importSymbolIds.has(objectSymbol.id);
47890
+ }
47891
+ if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "FunctionExpression")) return functionHasReactRenderSemantics(stripped, state);
47892
+ if (!isNodeOfType(stripped, "CallExpression")) return false;
47893
+ if (!isHocCallee(stripped.callee, state)) return false;
47894
+ return stripped.arguments.some((argument) => isProvenComponentValue(argument, state));
47895
+ };
47896
+ const isDirectRefreshWrapperCall = (call, state) => {
47897
+ const callee = skipTsExpression(call.callee);
47898
+ if (isHocCallee(callee, state)) return call.arguments.length > 0;
47899
+ if (!isNodeOfType(callee, "Identifier") && !isNodeOfType(callee, "MemberExpression")) return false;
47900
+ return call.arguments.some((argument) => isProvenComponentValue(argument, state));
47901
+ };
46021
47902
  const objectExpressionBundlesComponents = (objectExpression, state) => {
46022
47903
  for (const property of objectExpression.properties ?? []) {
46023
47904
  if (!isNodeOfType(property, "Property")) continue;
@@ -46027,20 +47908,22 @@ const objectExpressionBundlesComponents = (objectExpression, state) => {
46027
47908
  continue;
46028
47909
  }
46029
47910
  if (!(!property.computed && isNodeOfType(property.key, "Identifier") && isReactComponentName(property.key.name))) continue;
46030
- if ((isNodeOfType(value, "ArrowFunctionExpression") || isNodeOfType(value, "FunctionExpression")) && functionContainsReactRenderOutput(value, state.scopes, state.controlFlow)) return true;
47911
+ if ((isNodeOfType(value, "ArrowFunctionExpression") || isNodeOfType(value, "FunctionExpression")) && functionHasReactRenderSemantics(value, state)) return true;
46031
47912
  if (isNodeOfType(value, "CallExpression") && isHocCallee(value.callee, state) && value.arguments.length > 0) return true;
46032
47913
  }
46033
47914
  return false;
46034
47915
  };
46035
47916
  const classifyExport = (name, reportNode, isFunction, initializer, state) => {
47917
+ if (isNodeOfType(reportNode, "Identifier") && isProvenComponentValue(reportNode, state)) return { kind: "react-component" };
46036
47918
  if (initializer) {
46037
47919
  const expression = skipTsExpression(initializer);
46038
- if (isRouteFactoryCall(expression)) return { kind: "react-component" };
47920
+ if (isNodeOfType(expression, "CallExpression") && isReactComponentName(name) && isComponentFactoryCall(expression, state)) return { kind: "react-component" };
47921
+ if (isRouteFactoryCall(expression, state.routeFactoryBindings)) return { kind: "react-component" };
46039
47922
  if (isNodeOfType(expression, "CallExpression") && isHocCallee(expression.callee, state) && expression.arguments.length > 0 && isReactComponentName(name)) return { kind: "react-component" };
46040
47923
  if (isNodeOfType(expression, "ConditionalExpression") && isReactComponentName(name) && isReactComponentInitializer(expression.consequent, state) && isReactComponentInitializer(expression.alternate, state)) return { kind: "react-component" };
46041
47924
  }
46042
47925
  if (state.allowExportNames.has(name)) return { kind: "allowed" };
46043
- if (ROUTE_MODULE_ALLOWED_EXPORT_NAMES.has(name)) return { kind: "allowed" };
47926
+ if (state.allowedRouteExportNames.has(name)) return { kind: "allowed" };
46044
47927
  if (/^use[A-Z]/.test(name)) return { kind: "allowed" };
46045
47928
  if (state.allowConstantExport && initializer) {
46046
47929
  const expression = skipTsExpression(initializer);
@@ -46066,42 +47949,33 @@ const classifyExport = (name, reportNode, isFunction, initializer, state) => {
46066
47949
  kind: "namespace-object",
46067
47950
  reportNode
46068
47951
  };
47952
+ if (isNodeOfType(stripped, "MemberExpression")) return isProvenComponentValue(stripped, state) ? { kind: "react-component" } : {
47953
+ kind: "non-component",
47954
+ reportNode
47955
+ };
47956
+ if (isNodeOfType(stripped, "Identifier")) return isProvenComponentValue(stripped, state) ? { kind: "react-component" } : {
47957
+ kind: "non-component",
47958
+ reportNode
47959
+ };
46069
47960
  if (NOT_REACT_COMPONENT_EXPRESSION_TYPES.has(stripped.type)) return {
46070
47961
  kind: "non-component",
46071
47962
  reportNode
46072
47963
  };
47964
+ if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "FunctionExpression")) return {
47965
+ kind: "non-component",
47966
+ reportNode
47967
+ };
46073
47968
  }
46074
47969
  return isReactComponentName(name) ? { kind: "react-component" } : {
46075
47970
  kind: "non-component",
46076
47971
  reportNode
46077
47972
  };
46078
47973
  };
46079
- const isEntryPointFile = (filename) => {
46080
- const lastSlash = Math.max(filename.lastIndexOf("/"), filename.lastIndexOf("\\"));
46081
- const basename = lastSlash === -1 ? filename : filename.slice(lastSlash + 1);
46082
- return ENTRY_POINT_BASENAMES.has(basename);
46083
- };
46084
- const ASSET_FILE_BASENAME_PATTERN = /^([A-Za-z][\w-]*[-._])?(icons?|svgs?|svg[-_]?sprites?|sprites?|emojis?|flags?|logos?|lockups?|illustrations?|glyphs?|stickers?|emotes?|avatars?|backgrounds?|patterns?|assets?|gradients?|countryVectors?|paymentVectors?|brandVectors?|brandLogos?)\.(t|j)sx?$/;
46085
- const UTILITY_BASENAME_SUFFIX_PATTERN = /^[A-Za-z][\w-]*(Utils|Util|Helpers|Helper|Shared|Constants|Constant|Types|Type|Mappings|Mapping|Lookups|Lookup|Registry|Renderers|Renderer|NodeTypes|EdgeTypes|CellTypes|ColumnDefs|ColumnTypes|ColumnRenderers|Schemas|Schema|Definitions|Definition|Config|Configuration|Defaults|Default|Tokens|Palette|Context|Provider|Providers|Logic|Scene|Page|Layout)\.(t|j)sx?$/;
46086
- const HOOK_FILE_BASENAME_PATTERN = /^use[A-Z][\w-]*\.(t|j)sx?$/;
46087
- const NODE_DEFINITION_BASENAME_PATTERN = /^[A-Z][\w-]*(NodeUtil|ShapeUtil|EdgeUtil|BindingUtil|InlineNode|BlockNode|NotebookNode)\.(t|j)sx?$/;
46088
- const isAssetOrUtilityFile = (filename) => {
46089
- const lastSlash = Math.max(filename.lastIndexOf("/"), filename.lastIndexOf("\\"));
46090
- const basename = lastSlash === -1 ? filename : filename.slice(lastSlash + 1);
46091
- if (ASSET_FILE_BASENAME_PATTERN.test(basename)) return true;
46092
- if (UTILITY_FILE_BASENAMES.has(basename)) return true;
46093
- if (UTILITY_BASENAME_SUFFIX_PATTERN.test(basename)) return true;
46094
- if (HOOK_FILE_BASENAME_PATTERN.test(basename)) return true;
46095
- if (NODE_DEFINITION_BASENAME_PATTERN.test(basename)) return true;
46096
- return false;
46097
- };
46098
47974
  const isFileNameAllowed = (filename, checkJS) => {
46099
47975
  if (!filename) return true;
47976
+ if (TEST_SUPPORT_FILE_PATTERN.test(filename)) return false;
46100
47977
  if (filename.includes(".test.") || filename.includes(".spec.") || filename.includes(".cy.") || filename.includes(".stories.")) return false;
46101
47978
  for (const segment of NON_FAST_REFRESH_PATH_SEGMENTS) if (filename.includes(segment)) return false;
46102
- if (isEntryPointFile(filename)) return false;
46103
- if (isFrameworkRouteOrSpecialFilename(filename)) return false;
46104
- if (isAssetOrUtilityFile(filename)) return false;
46105
47979
  if (filename.endsWith(".tsx") || filename.endsWith(".jsx")) return true;
46106
47980
  if (checkJS && filename.endsWith(".js")) return true;
46107
47981
  return false;
@@ -46114,16 +47988,100 @@ const onlyExportComponents = defineRule({
46114
47988
  category: "Architecture",
46115
47989
  create: (context) => {
46116
47990
  const settings = resolveSettings$6(context.settings);
46117
- if (!isFileNameAllowed(normalizeFilename(context.filename ?? ""), settings.checkJS)) return {};
47991
+ const filename = normalizeFilename(context.filename ?? "");
47992
+ const fastRefreshStatus = getFastRefreshFileStatus(context);
47993
+ if (!fastRefreshStatus.isActive) return {};
47994
+ if (isFrameworkRouteOrSpecialFilename(context, fastRefreshStatus.runtime)) return {};
47995
+ if (!isFileNameAllowed(filename, settings.checkJS)) return {};
47996
+ const allowedRouteExportNames = fastRefreshStatus.runtime === "next" ? NEXT_ALLOWED_EXPORT_NAMES : fastRefreshStatus.runtime === "expo" ? EXPO_ALLOWED_EXPORT_NAMES : fastRefreshStatus.runtime === "react-router" || fastRefreshStatus.runtime === "remix" ? REACT_ROUTER_ALLOWED_EXPORT_NAMES : EMPTY_NAME_SET;
47997
+ const routeFactoryMemberNames = fastRefreshStatus.runtime === "tanstack" ? TANSTACK_ROUTE_FACTORY_CALLEE_NAMES : fastRefreshStatus.runtime === "react-router" || fastRefreshStatus.runtime === "remix" ? REACT_ROUTER_FACTORY_CALLEE_NAMES : EMPTY_NAME_SET;
47998
+ const routeFactoryLocalNames = /* @__PURE__ */ new Set();
47999
+ const routeFactoryNamespaceNames = /* @__PURE__ */ new Set();
48000
+ const nextDynamicImportSymbolIds = /* @__PURE__ */ new Set();
48001
+ const importSymbolIds = /* @__PURE__ */ new Set();
48002
+ const routeFactoryBindings = {
48003
+ localNames: routeFactoryLocalNames,
48004
+ memberNames: routeFactoryMemberNames,
48005
+ namespaceNames: routeFactoryNamespaceNames
48006
+ };
46118
48007
  const exportNodes = [];
46119
48008
  const componentCandidates = [];
48009
+ const createRootNames = /* @__PURE__ */ new Set();
48010
+ const hydrateRootNames = /* @__PURE__ */ new Set();
48011
+ const legacyRenderNames = /* @__PURE__ */ new Set();
48012
+ const reactDomNamespaceNames = /* @__PURE__ */ new Set();
48013
+ const rootNames = /* @__PURE__ */ new Set();
48014
+ let hasRootMount = false;
48015
+ const isCreateRootCall = (node) => {
48016
+ if (!isNodeOfType(node, "CallExpression")) return false;
48017
+ if (isNodeOfType(node.callee, "Identifier")) return createRootNames.has(node.callee.name);
48018
+ return isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && reactDomNamespaceNames.has(node.callee.object.name) && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "createRoot";
48019
+ };
48020
+ const visitImportDeclaration = (node) => {
48021
+ if (!isNodeOfType(node, "ImportDeclaration")) return;
48022
+ const source = node.source.value;
48023
+ for (const specifier of node.specifiers) {
48024
+ const symbol = context.scopes.symbolFor(specifier.local);
48025
+ if (symbol) importSymbolIds.add(symbol.id);
48026
+ }
48027
+ if (typeof source === "string" && (fastRefreshStatus.runtime === "tanstack" && (source.startsWith("@tanstack/react-router") || source.startsWith("@tanstack/react-start")) || (fastRefreshStatus.runtime === "react-router" || fastRefreshStatus.runtime === "remix") && (source === "react-router" || source === "react-router-dom" || source === "@remix-run/react"))) for (const specifier of node.specifiers) {
48028
+ if (isNodeOfType(specifier, "ImportNamespaceSpecifier")) {
48029
+ routeFactoryNamespaceNames.add(specifier.local.name);
48030
+ continue;
48031
+ }
48032
+ const importedName = getImportedName(specifier);
48033
+ if (importedName && routeFactoryMemberNames.has(importedName)) routeFactoryLocalNames.add(specifier.local.name);
48034
+ }
48035
+ if (source === "next/dynamic") for (const specifier of node.specifiers) {
48036
+ if (!isNodeOfType(specifier, "ImportDefaultSpecifier")) continue;
48037
+ const symbol = context.scopes.symbolFor(specifier.local);
48038
+ if (symbol) nextDynamicImportSymbolIds.add(symbol.id);
48039
+ }
48040
+ if (source !== "react-dom" && source !== "react-dom/client") return;
48041
+ for (const specifier of node.specifiers) {
48042
+ if (isNodeOfType(specifier, "ImportDefaultSpecifier") || isNodeOfType(specifier, "ImportNamespaceSpecifier")) {
48043
+ reactDomNamespaceNames.add(specifier.local.name);
48044
+ continue;
48045
+ }
48046
+ const importedName = getImportedName(specifier);
48047
+ if (importedName === "createRoot") createRootNames.add(specifier.local.name);
48048
+ if (importedName === "hydrateRoot") hydrateRootNames.add(specifier.local.name);
48049
+ if (source === "react-dom" && (importedName === "render" || importedName === "hydrate")) legacyRenderNames.add(specifier.local.name);
48050
+ }
48051
+ };
48052
+ const visitCallExpression = (node) => {
48053
+ if (!isNodeOfType(node, "CallExpression")) return;
48054
+ if (isInsideFunctionScope(node)) return;
48055
+ if (isNodeOfType(node.callee, "Identifier")) {
48056
+ if (hydrateRootNames.has(node.callee.name) || legacyRenderNames.has(node.callee.name)) hasRootMount = true;
48057
+ return;
48058
+ }
48059
+ if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier")) return;
48060
+ const methodName = node.callee.property.name;
48061
+ if (isNodeOfType(node.callee.object, "Identifier") && reactDomNamespaceNames.has(node.callee.object.name) && (methodName === "render" || methodName === "hydrate" || methodName === "hydrateRoot")) {
48062
+ hasRootMount = true;
48063
+ return;
48064
+ }
48065
+ if (methodName !== "render") return;
48066
+ if (isCreateRootCall(node.callee.object)) {
48067
+ hasRootMount = true;
48068
+ return;
48069
+ }
48070
+ if (isNodeOfType(node.callee.object, "Identifier") && rootNames.has(node.callee.object.name)) hasRootMount = true;
48071
+ };
46120
48072
  const pushExportNode = (node) => {
46121
48073
  exportNodes.push(node);
46122
48074
  };
46123
48075
  const pushComponentCandidate = (node) => {
46124
48076
  componentCandidates.push(node);
48077
+ if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.init && isCreateRootCall(node.init) && !isInsideFunctionScope(node)) rootNames.add(node.id.name);
46125
48078
  };
46126
48079
  return {
48080
+ ImportDeclaration: visitImportDeclaration,
48081
+ CallExpression: visitCallExpression,
48082
+ AssignmentExpression(node) {
48083
+ if (isNodeOfType(node.left, "Identifier") && rootNames.has(node.left.name)) rootNames.delete(node.left.name);
48084
+ },
46127
48085
  ExportAllDeclaration: pushExportNode,
46128
48086
  ExportDefaultDeclaration: pushExportNode,
46129
48087
  ExportNamedDeclaration: pushExportNode,
@@ -46131,31 +48089,53 @@ const onlyExportComponents = defineRule({
46131
48089
  VariableDeclarator: pushComponentCandidate,
46132
48090
  ClassDeclaration: pushComponentCandidate,
46133
48091
  "Program:exit"() {
48092
+ if (hasRootMount) return;
46134
48093
  const localComponentNames = /* @__PURE__ */ new Set();
48094
+ const componentFactorySymbolIds = /* @__PURE__ */ new Set();
48095
+ for (const child of componentCandidates) {
48096
+ if (isInsideFunctionScope(child)) continue;
48097
+ if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
48098
+ if (functionReturnsNextDynamicComponent(child, nextDynamicImportSymbolIds, context.scopes)) {
48099
+ const symbol = context.scopes.symbolFor(child.id);
48100
+ if (symbol?.references.every((reference) => reference.flag === "read")) componentFactorySymbolIds.add(symbol.id);
48101
+ }
48102
+ continue;
48103
+ }
48104
+ if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier") && child.init && functionReturnsNextDynamicComponent(child.init, nextDynamicImportSymbolIds, context.scopes)) {
48105
+ const symbol = context.scopes.symbolFor(child.id);
48106
+ if (symbol?.references.every((reference) => reference.flag === "read")) componentFactorySymbolIds.add(symbol.id);
48107
+ }
48108
+ }
46135
48109
  const state = {
46136
48110
  customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
46137
48111
  allowExportNames: new Set(settings.allowExportNames),
46138
48112
  allowConstantExport: settings.allowConstantExport,
48113
+ allowedRouteExportNames,
48114
+ routeFactoryBindings,
48115
+ componentFactorySymbolIds,
48116
+ importSymbolIds,
46139
48117
  localComponentNames,
46140
48118
  scopes: context.scopes,
46141
48119
  controlFlow: context.cfg
46142
48120
  };
46143
48121
  for (const child of componentCandidates) {
46144
48122
  if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
46145
- if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) && functionContainsReactRenderOutput(child, context.scopes, context.cfg)) localComponentNames.add(child.id.name);
48123
+ if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) && functionHasReactRenderSemantics(child, state)) localComponentNames.add(child.id.name);
46146
48124
  }
46147
48125
  if (isNodeOfType(child, "ClassDeclaration") && child.id) {
46148
48126
  if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
46149
48127
  }
46150
48128
  if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
46151
48129
  const initializer = child.init;
46152
- if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) {
46153
- const expression = initializer ? skipTsExpression(initializer) : null;
46154
- if (!(expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"))) || functionContainsReactRenderOutput(expression, context.scopes, context.cfg)) localComponentNames.add(child.id.name);
48130
+ const expression = initializer ? skipTsExpression(initializer) : null;
48131
+ const isDirectFunction = expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"));
48132
+ if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (expression ? isEs6Component(expression) : false)) && !isInsideFunctionScope(child)) {
48133
+ if (!isDirectFunction || functionHasReactRenderSemantics(expression, state)) localComponentNames.add(child.id.name);
46155
48134
  }
46156
48135
  }
46157
48136
  }
46158
48137
  const exports = [];
48138
+ const exportAllNodes = [];
46159
48139
  let hasReactExport = false;
46160
48140
  let hasAnyExports = false;
46161
48141
  const isExportedNodeIds = /* @__PURE__ */ new WeakSet();
@@ -46163,10 +48143,8 @@ const onlyExportComponents = defineRule({
46163
48143
  if (isNodeOfType(child, "ExportAllDeclaration")) {
46164
48144
  if (child.exportKind === "type") continue;
46165
48145
  hasAnyExports = true;
46166
- context.report({
46167
- node: child,
46168
- message: EXPORT_ALL_MESSAGE
46169
- });
48146
+ const source = child.source.value;
48147
+ if (typeof source !== "string" || exportAllAddsRuntimeValues(filename, source)) exportAllNodes.push(child);
46170
48148
  continue;
46171
48149
  }
46172
48150
  if (isNodeOfType(child, "ExportDefaultDeclaration")) {
@@ -46174,17 +48152,24 @@ const onlyExportComponents = defineRule({
46174
48152
  const declaration = child.declaration;
46175
48153
  const stripped = skipTsExpression(declaration);
46176
48154
  if (isNodeOfType(stripped, "FunctionDeclaration") || isNodeOfType(stripped, "FunctionExpression")) {
48155
+ const hasRenderOutput = functionHasReactRenderSemantics(stripped, state);
46177
48156
  if (stripped.id) {
46178
48157
  const idNode = stripped.id;
46179
48158
  isExportedNodeIds.add(stripped);
46180
- exports.push(classifyExport(idNode.name, idNode, true, null, state));
46181
- } else {
48159
+ exports.push(hasRenderOutput ? classifyExport(idNode.name, idNode, true, null, state) : {
48160
+ kind: "non-component",
48161
+ reportNode: idNode
48162
+ });
48163
+ } else if (hasRenderOutput) {
46182
48164
  context.report({
46183
48165
  node: stripped,
46184
48166
  message: ANONYMOUS_MESSAGE
46185
48167
  });
46186
48168
  hasReactExport = true;
46187
- }
48169
+ } else exports.push({
48170
+ kind: "non-component",
48171
+ reportNode: stripped
48172
+ });
46188
48173
  continue;
46189
48174
  }
46190
48175
  if (isNodeOfType(stripped, "ClassDeclaration") || isNodeOfType(stripped, "ClassExpression")) {
@@ -46203,26 +48188,34 @@ const onlyExportComponents = defineRule({
46203
48188
  continue;
46204
48189
  }
46205
48190
  if (isNodeOfType(stripped, "Identifier")) {
46206
- exports.push(classifyExport(stripped.name, stripped, false, null, state));
48191
+ exports.push(isProvenComponentValue(stripped, state) ? { kind: "react-component" } : {
48192
+ kind: "non-component",
48193
+ reportNode: stripped
48194
+ });
48195
+ continue;
48196
+ }
48197
+ if (isNodeOfType(stripped, "MemberExpression")) {
48198
+ if (isProvenComponentValue(stripped, state)) hasReactExport = true;
48199
+ else exports.push({
48200
+ kind: "non-component",
48201
+ reportNode: stripped
48202
+ });
46207
48203
  continue;
46208
48204
  }
46209
48205
  if (isNodeOfType(stripped, "CallExpression")) {
46210
- if (isRouteFactoryCall(stripped)) {
48206
+ if (isRouteFactoryCall(stripped, state.routeFactoryBindings)) {
46211
48207
  hasReactExport = true;
46212
48208
  continue;
46213
48209
  }
46214
- const isHoc = isHocCallee(stripped.callee, state);
46215
- const firstArg = stripped.arguments[0];
46216
- const firstArgIsValid = Boolean(firstArg) && (() => {
46217
- if (!firstArg) return false;
46218
- const expression = skipTsExpression(firstArg);
46219
- if (isNodeOfType(expression, "Identifier")) return true;
46220
- if (isNodeOfType(expression, "FunctionExpression") && expression.id) return true;
46221
- if (isNodeOfType(expression, "CallExpression") && isHocCallee(expression.callee, state)) return true;
46222
- return false;
46223
- })();
46224
- if (isHoc && firstArgIsValid) hasReactExport = true;
46225
- else if (!isHoc && isConfigOnlyFactoryCall(stripped)) exports.push({
48210
+ if (isReactCreateContext(stripped)) {
48211
+ exports.push({
48212
+ kind: "react-context",
48213
+ reportNode: stripped
48214
+ });
48215
+ continue;
48216
+ }
48217
+ if (isDirectRefreshWrapperCall(stripped, state)) hasReactExport = true;
48218
+ else if (isConfigOnlyFactoryCall(stripped)) exports.push({
46226
48219
  kind: "non-component",
46227
48220
  reportNode: stripped
46228
48221
  });
@@ -46233,16 +48226,32 @@ const onlyExportComponents = defineRule({
46233
48226
  continue;
46234
48227
  }
46235
48228
  if (isNodeOfType(stripped, "ObjectExpression")) {
46236
- context.report({
46237
- node: stripped,
46238
- message: objectExpressionBundlesComponents(stripped, state) ? NAMESPACE_OBJECT_MESSAGE : ANONYMOUS_MESSAGE
48229
+ exports.push(objectExpressionBundlesComponents(stripped, state) ? {
48230
+ kind: "namespace-object",
48231
+ reportNode: stripped
48232
+ } : {
48233
+ kind: "non-component",
48234
+ reportNode: stripped
46239
48235
  });
46240
48236
  continue;
46241
48237
  }
46242
- if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "Literal")) {
46243
- context.report({
46244
- node: stripped,
46245
- message: ANONYMOUS_MESSAGE
48238
+ if (isNodeOfType(stripped, "ArrowFunctionExpression")) {
48239
+ if (functionHasReactRenderSemantics(stripped, state)) {
48240
+ context.report({
48241
+ node: stripped,
48242
+ message: ANONYMOUS_MESSAGE
48243
+ });
48244
+ hasReactExport = true;
48245
+ } else exports.push({
48246
+ kind: "non-component",
48247
+ reportNode: stripped
48248
+ });
48249
+ continue;
48250
+ }
48251
+ if (isNodeOfType(stripped, "Literal") || isNodeOfType(stripped, "NewExpression")) {
48252
+ exports.push({
48253
+ kind: "non-component",
48254
+ reportNode: stripped
46246
48255
  });
46247
48256
  continue;
46248
48257
  }
@@ -46259,7 +48268,10 @@ const onlyExportComponents = defineRule({
46259
48268
  const declaration = child.declaration;
46260
48269
  if (isNodeOfType(declaration, "FunctionDeclaration") && declaration.id) {
46261
48270
  isExportedNodeIds.add(declaration);
46262
- exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state));
48271
+ exports.push(functionHasReactRenderSemantics(declaration, state) || localComponentNames.has(declaration.id.name) ? classifyExport(declaration.id.name, declaration.id, true, null, state) : {
48272
+ kind: "non-component",
48273
+ reportNode: declaration.id
48274
+ });
46263
48275
  } else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
46264
48276
  isExportedNodeIds.add(declaration);
46265
48277
  if (isReactComponentName(declaration.id.name) && isEs6Component(declaration)) exports.push({ kind: "react-component" });
@@ -46283,6 +48295,7 @@ const onlyExportComponents = defineRule({
46283
48295
  const isReExportFromSource = Boolean(child.source);
46284
48296
  for (const specifier of child.specifiers ?? []) {
46285
48297
  if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
48298
+ if (specifier.exportKind === "type") continue;
46286
48299
  const exported = specifier.exported;
46287
48300
  const local = specifier.local;
46288
48301
  let exportedName = null;
@@ -46290,8 +48303,13 @@ const onlyExportComponents = defineRule({
46290
48303
  const localName = local && isNodeOfType(local, "Identifier") ? local.name : null;
46291
48304
  const reportNode = specifier;
46292
48305
  let entry;
46293
- if (exportedName === "default" && localName) entry = classifyExport(localName, reportNode, false, null, state);
46294
- else if (exportedName) entry = classifyExport(exportedName, reportNode, false, null, state);
48306
+ if (localName && localComponentNames.has(localName)) entry = { kind: "react-component" };
48307
+ else if (!isReExportFromSource && localName === exportedName && localName !== null && isReactComponentName(localName)) entry = { kind: "react-component" };
48308
+ else if (exportedName === "default" && localName && local) entry = isProvenComponentValue(local, state) ? { kind: "react-component" } : {
48309
+ kind: "non-component",
48310
+ reportNode
48311
+ };
48312
+ else if (exportedName) entry = classifyExport(exportedName, reportNode, false, isReExportFromSource ? null : local, state);
46295
48313
  else {
46296
48314
  entry = {
46297
48315
  kind: "non-component",
@@ -46309,15 +48327,21 @@ const onlyExportComponents = defineRule({
46309
48327
  node: entry.reportNode,
46310
48328
  message: NAMESPACE_OBJECT_MESSAGE
46311
48329
  });
46312
- if (hasAnyExports && hasReactExport) for (const entry of exports) {
46313
- if (entry.kind === "non-component") context.report({
46314
- node: entry.reportNode,
46315
- message: NAMED_EXPORT_MESSAGE
46316
- });
46317
- if (entry.kind === "react-context") context.report({
46318
- node: entry.reportNode,
46319
- message: REACT_CONTEXT_MESSAGE
48330
+ if (hasAnyExports && hasReactExport) {
48331
+ for (const exportAllNode of exportAllNodes) context.report({
48332
+ node: exportAllNode,
48333
+ message: EXPORT_ALL_MESSAGE
46320
48334
  });
48335
+ for (const entry of exports) {
48336
+ if (entry.kind === "non-component") context.report({
48337
+ node: entry.reportNode,
48338
+ message: NAMED_EXPORT_MESSAGE
48339
+ });
48340
+ if (entry.kind === "react-context") context.report({
48341
+ node: entry.reportNode,
48342
+ message: REACT_CONTEXT_MESSAGE
48343
+ });
48344
+ }
46321
48345
  }
46322
48346
  }
46323
48347
  };
@@ -57980,6 +60004,78 @@ const inferDestructureSourceKey = (bindingIdentifier) => {
57980
60004
  return null;
57981
60005
  };
57982
60006
  const isReactUseHook = (hookName) => hookName === "use";
60007
+ const isReactHookCapabilityValue = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
60008
+ const unwrappedExpression = stripParenExpression(expression);
60009
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
60010
+ const symbol = scopes.symbolFor(unwrappedExpression);
60011
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
60012
+ visitedSymbolIds.add(symbol.id);
60013
+ if (symbol.kind === "import") return isReactImport(symbol) && isReactHookName(getImportedName(symbol.declarationNode) ?? "");
60014
+ if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
60015
+ const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
60016
+ if (destructuredPropertyName) {
60017
+ const namespaceExpression = stripParenExpression(symbol.initializer);
60018
+ return isReactHookName(destructuredPropertyName) && isNodeOfType(namespaceExpression, "Identifier") && isReactNamespaceImport(namespaceExpression, scopes) && !hasPossibleStaticPropertyMutationOrEscape(namespaceExpression, destructuredPropertyName, scopes);
60019
+ }
60020
+ if (symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
60021
+ return isReactHookCapabilityValue(symbol.initializer, scopes, visitedSymbolIds);
60022
+ }
60023
+ if (!isNodeOfType(unwrappedExpression, "MemberExpression")) return false;
60024
+ const propertyName = getStaticPropertyName(unwrappedExpression);
60025
+ if (!propertyName || !isReactHookName(propertyName)) return false;
60026
+ const namespaceExpression = stripParenExpression(unwrappedExpression.object);
60027
+ return isNodeOfType(namespaceExpression, "Identifier") && isReactNamespaceImport(namespaceExpression, scopes) && !hasPossibleStaticPropertyMutationOrEscape(namespaceExpression, propertyName, scopes);
60028
+ };
60029
+ const isReactHookCapabilityComparisonOperand = (expression, scopes) => {
60030
+ const unwrappedExpression = stripParenExpression(expression);
60031
+ if (isReactHookCapabilityValue(unwrappedExpression, scopes)) return true;
60032
+ return isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "typeof" && isReactHookCapabilityValue(unwrappedExpression.argument, scopes);
60033
+ };
60034
+ const isStaticCapabilityComparisonValue = (expression, scopes) => {
60035
+ const unwrappedExpression = stripParenExpression(expression);
60036
+ if (isNodeOfType(unwrappedExpression, "Literal")) return true;
60037
+ return isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "undefined" && scopes.isGlobalReference(unwrappedExpression);
60038
+ };
60039
+ const CAPABILITY_COMPARISON_OPERATORS = new Set([
60040
+ "==",
60041
+ "!=",
60042
+ "===",
60043
+ "!=="
60044
+ ]);
60045
+ const isInvariantReactHookCapabilityCondition = (expression, scopes) => {
60046
+ const unwrappedExpression = stripParenExpression(expression);
60047
+ if (isReactHookCapabilityValue(unwrappedExpression, scopes)) return true;
60048
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return isInvariantReactHookCapabilityCondition(unwrappedExpression.argument, scopes);
60049
+ if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return isInvariantReactHookCapabilityCondition(unwrappedExpression.left, scopes) && isInvariantReactHookCapabilityCondition(unwrappedExpression.right, scopes);
60050
+ if (!isNodeOfType(unwrappedExpression, "BinaryExpression") || !CAPABILITY_COMPARISON_OPERATORS.has(unwrappedExpression.operator)) return false;
60051
+ return isReactHookCapabilityComparisonOperand(unwrappedExpression.left, scopes) && isStaticCapabilityComparisonValue(unwrappedExpression.right, scopes) || isReactHookCapabilityComparisonOperand(unwrappedExpression.right, scopes) && isStaticCapabilityComparisonValue(unwrappedExpression.left, scopes);
60052
+ };
60053
+ const statementContainsOwnAbruptCompletion = (statement) => {
60054
+ let doesContainAbruptCompletion = false;
60055
+ walkAst(statement, (child) => {
60056
+ if (child !== statement && isFunctionLike$1(child)) return false;
60057
+ if (isNodeOfType(child, "ReturnStatement") || isNodeOfType(child, "ThrowStatement")) {
60058
+ doesContainAbruptCompletion = true;
60059
+ return false;
60060
+ }
60061
+ });
60062
+ return doesContainAbruptCompletion;
60063
+ };
60064
+ const isInvariantReactHookCapabilityExit = (statement, scopes) => isNodeOfType(statement, "IfStatement") && statement.alternate === null && statementAlwaysExits(statement.consequent) && isInvariantReactHookCapabilityCondition(statement.test, scopes);
60065
+ const isAfterOnlyInvariantReactHookCapabilityExits = (node, enclosingFunction, scopes) => {
60066
+ if (!isFunctionLike$1(enclosingFunction) || !isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
60067
+ let containingStatement = node;
60068
+ while (containingStatement.parent && containingStatement.parent !== enclosingFunction.body) {
60069
+ if (isFunctionLike$1(containingStatement.parent)) return false;
60070
+ if (isNodeOfType(containingStatement.parent, "IfStatement") || isNodeOfType(containingStatement.parent, "SwitchStatement") || isNodeOfType(containingStatement.parent, "SwitchCase") || isNodeOfType(containingStatement.parent, "ConditionalExpression") || isNodeOfType(containingStatement.parent, "LogicalExpression")) return false;
60071
+ containingStatement = containingStatement.parent;
60072
+ }
60073
+ if (containingStatement.parent !== enclosingFunction.body) return false;
60074
+ const statementIndex = enclosingFunction.body.body.findIndex((statement) => statement === containingStatement);
60075
+ if (statementIndex <= 0) return false;
60076
+ const bypassingStatements = enclosingFunction.body.body.slice(0, statementIndex).filter((statement) => statementContainsOwnAbruptCompletion(statement));
60077
+ return bypassingStatements.length > 0 && bypassingStatements.every((statement) => isInvariantReactHookCapabilityExit(statement, scopes));
60078
+ };
57983
60079
  const getCallExpressionCalleeName = (callExpression) => {
57984
60080
  const callee = callExpression.callee;
57985
60081
  if (isNodeOfType(callee, "Identifier")) return callee.name;
@@ -58275,7 +60371,7 @@ const rulesOfHooks = defineRule({
58275
60371
  });
58276
60372
  return;
58277
60373
  }
58278
- if (!context.cfg.isUnconditionalFromEntry(node)) context.report({
60374
+ if (!context.cfg.isUnconditionalFromEntry(node) && !isAfterOnlyInvariantReactHookCapabilityExits(node, enclosing.node, context.scopes)) context.report({
58279
60375
  node: node.callee,
58280
60376
  message: buildConditionalMessage(hookName)
58281
60377
  });
@@ -66414,6 +68510,7 @@ const CROSS_FILE_RULE_IDS = new Set([
66414
68510
  "no-effect-with-fresh-deps",
66415
68511
  "no-initialize-state",
66416
68512
  "no-mutating-reducer-state",
68513
+ "only-export-components",
66417
68514
  "no-unguarded-browser-global-in-render-or-hook-init",
66418
68515
  "prefer-dynamic-import",
66419
68516
  "rendering-hydration-mismatch-time",
@@ -66621,7 +68718,7 @@ const CROSS_FILE_DEPENDENCY_COLLECTORS = new Map([
66621
68718
  * `CROSS_FILE_DEPENDENCY_COLLECTORS` (the core guard test enforces the
66622
68719
  * partition), forcing a conscious classification.
66623
68720
  */
66624
- const UNBOUNDED_CROSS_FILE_RULE_IDS = /* @__PURE__ */ new Set();
68721
+ const UNBOUNDED_CROSS_FILE_RULE_IDS = new Set(["only-export-components"]);
66625
68722
  /**
66626
68723
  * Runs the collectors for `ruleIds` over one file and returns every
66627
68724
  * filesystem probe they made — the file's cross-file dependency set.