oxlint-plugin-react-doctor 0.7.7-dev.517f87a → 0.7.7-dev.6e5c240

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 +1706 -294
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10558,7 +10558,7 @@ const getAssignedName = (node) => {
10558
10558
  }
10559
10559
  return null;
10560
10560
  };
10561
- const isModuleExportsAssignment = (node) => {
10561
+ const isModuleExportsAssignment$1 = (node) => {
10562
10562
  const parent = node.parent;
10563
10563
  if (!parent || !isNodeOfType(parent, "AssignmentExpression")) return false;
10564
10564
  const left = parent.left;
@@ -10717,7 +10717,7 @@ const displayName = defineRule({
10717
10717
  }
10718
10718
  const assignedName = getAssignedName(node);
10719
10719
  if (assignedName && isReactComponentName(assignedName) && ignoreNamed) return;
10720
- if (isModuleExportsAssignment(node) && !node.id) {
10720
+ if (isModuleExportsAssignment$1(node) && !node.id) {
10721
10721
  reportAt(node);
10722
10722
  return;
10723
10723
  }
@@ -10739,7 +10739,7 @@ const displayName = defineRule({
10739
10739
  reportAt(node);
10740
10740
  return;
10741
10741
  }
10742
- if (isModuleExportsAssignment(node)) {
10742
+ if (isModuleExportsAssignment$1(node)) {
10743
10743
  reportAt(node);
10744
10744
  return;
10745
10745
  }
@@ -11673,6 +11673,25 @@ const getFunctionBindingName$1 = (functionNode) => getFunctionBindingIdentifier(
11673
11673
  //#region src/plugin/utils/is-event-handler-attribute.ts
11674
11674
  const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && /^on[A-Z]/.test(node.name.name);
11675
11675
  //#endregion
11676
+ //#region src/plugin/utils/is-ast-descendant.ts
11677
+ /**
11678
+ * True when `inner` is `outer` itself or any descendant in the AST
11679
+ * `parent` chain. Walks `inner.parent` upward and stops at either a
11680
+ * match (`true`) or the chain's root (`false`).
11681
+ *
11682
+ * Was duplicated byte-identical in:
11683
+ * - exhaustive-deps-symbol-stability.ts
11684
+ * - semantic/closure-captures.ts
11685
+ */
11686
+ const isAstDescendant = (inner, outer) => {
11687
+ let current = inner;
11688
+ while (current) {
11689
+ if (current === outer) return true;
11690
+ current = current.parent ?? null;
11691
+ }
11692
+ return false;
11693
+ };
11694
+ //#endregion
11676
11695
  //#region src/plugin/utils/react-ref-origin.ts
11677
11696
  const resolveReactRefSymbol = (memberExpression, scopes) => {
11678
11697
  const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
@@ -12322,26 +12341,39 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
12322
12341
  });
12323
12342
  return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
12324
12343
  };
12344
+ const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => {
12345
+ if (usage.handleKey === null) return null;
12346
+ const doesTestRequireLiveHandle = (test) => {
12347
+ if (resolveExpressionKey(test, context) === usage.handleKey) return true;
12348
+ const unwrappedTest = stripParenExpression(test);
12349
+ if (!isNodeOfType(unwrappedTest, "BinaryExpression") || unwrappedTest.operator !== "!=" && unwrappedTest.operator !== "!==") return false;
12350
+ const isNullishOperand = (operand) => {
12351
+ const unwrappedOperand = stripParenExpression(operand);
12352
+ return isNodeOfType(unwrappedOperand, "Literal") && unwrappedOperand.value === null || isNodeOfType(unwrappedOperand, "Identifier") && unwrappedOperand.name === "undefined" && context.scopes.isGlobalReference(unwrappedOperand);
12353
+ };
12354
+ return resolveExpressionKey(unwrappedTest.left, context) === usage.handleKey && isNullishOperand(unwrappedTest.right) || resolveExpressionKey(unwrappedTest.right, context) === usage.handleKey && isNullishOperand(unwrappedTest.left);
12355
+ };
12356
+ let ancestor = releaseCall.parent;
12357
+ while (ancestor && ancestor !== owner) {
12358
+ if (isNodeOfType(ancestor, "IfStatement")) {
12359
+ if (ancestor.alternate !== null || !doesTestRequireLiveHandle(ancestor.test) || !doMatchingNodesCoverEveryPathAfterUsage(ancestor.consequent, [releaseCall], context)) return null;
12360
+ return ancestor;
12361
+ }
12362
+ ancestor = ancestor.parent;
12363
+ }
12364
+ return null;
12365
+ };
12325
12366
  const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
12326
12367
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
12327
12368
  const functionCfg = context.cfg.cfgFor(callback);
12328
12369
  const usageBlock = functionCfg?.blockOf(usage.node);
12329
12370
  const usageStart = getRangeStart(usage.node);
12330
12371
  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
12372
  const matchingReleaseAnchors = [];
12341
12373
  walkInsideStatementBlocks(callback.body, (child) => {
12342
12374
  if (!isNodeOfType(child, "CallExpression")) return;
12343
12375
  const releaseStart = getRangeStart(child);
12344
- const handleGuard = findHandleGuard(child);
12376
+ const handleGuard = findDirectHandleGuardForRelease(child, callback, usage, context);
12345
12377
  if (releaseStart === null || releaseStart >= usageStart || functionCfg.blockOf(child) !== usageBlock && !handleGuard) return;
12346
12378
  if (doesReleaseCallMatchUsage(child, usage, context)) {
12347
12379
  matchingReleaseAnchors.push(handleGuard ?? child);
@@ -12371,12 +12403,12 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
12371
12403
  return didFindUnmountCleanup;
12372
12404
  };
12373
12405
  const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
12374
- const collectBlockingBooleanStates = (expression, blockedExpressionValue, context) => {
12406
+ const collectBlockingBooleanStates = (expression, blockedExpressionValue, guardNode, context) => {
12375
12407
  const unwrappedExpression = stripParenExpression(expression);
12376
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, context);
12408
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, guardNode, context);
12377
12409
  if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
12378
12410
  if (!(unwrappedExpression.operator === "||" && blockedExpressionValue || unwrappedExpression.operator === "&&" && !blockedExpressionValue)) return [];
12379
- return [...collectBlockingBooleanStates(unwrappedExpression.left, blockedExpressionValue, context), ...collectBlockingBooleanStates(unwrappedExpression.right, blockedExpressionValue, context)];
12411
+ return [...collectBlockingBooleanStates(unwrappedExpression.left, blockedExpressionValue, guardNode, context), ...collectBlockingBooleanStates(unwrappedExpression.right, blockedExpressionValue, guardNode, context)];
12380
12412
  }
12381
12413
  if (isNodeOfType(unwrappedExpression, "BinaryExpression") && [
12382
12414
  "===",
@@ -12387,35 +12419,59 @@ const collectBlockingBooleanStates = (expression, blockedExpressionValue, contex
12387
12419
  const leftValue = readStaticBoolean(unwrappedExpression.left);
12388
12420
  const rightValue = readStaticBoolean(unwrappedExpression.right);
12389
12421
  const booleanValue = leftValue ?? rightValue;
12390
- const comparedKey = resolveExpressionKey(leftValue === null ? unwrappedExpression.left : unwrappedExpression.right, context);
12422
+ const comparedExpression = leftValue === null ? unwrappedExpression.left : unwrappedExpression.right;
12423
+ const comparedKey = resolveExpressionKey(comparedExpression, context);
12391
12424
  if (booleanValue === null || comparedKey === null) return [];
12425
+ const isEquality = unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==";
12392
12426
  return [{
12427
+ bindingIdentifier: isNodeOfType(comparedExpression, "Identifier") ? context.scopes.symbolFor(comparedExpression)?.bindingIdentifier ?? null : null,
12428
+ guardNode,
12393
12429
  key: comparedKey,
12394
- value: (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==") === blockedExpressionValue ? booleanValue : !booleanValue
12430
+ value: isEquality === blockedExpressionValue ? booleanValue : !booleanValue
12395
12431
  }];
12396
12432
  }
12397
12433
  const expressionKey = resolveExpressionKey(unwrappedExpression, context);
12398
12434
  return expressionKey === null ? [] : [{
12435
+ bindingIdentifier: isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression)?.bindingIdentifier ?? null : null,
12436
+ guardNode,
12399
12437
  key: expressionKey,
12400
12438
  value: blockedExpressionValue
12401
12439
  }];
12402
12440
  };
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");
12441
+ const canNodeReachLaterNodeWithinFunction = (sourceNode, targetNode, owner, context) => {
12442
+ const functionCfg = context.cfg.cfgFor(owner);
12443
+ const sourceBlock = functionCfg?.blockOf(sourceNode);
12444
+ const targetBlock = functionCfg?.blockOf(targetNode);
12445
+ const sourceStart = getRangeStart(sourceNode);
12446
+ const targetStart = getRangeStart(targetNode);
12447
+ if (!functionCfg || !sourceBlock || !targetBlock || sourceStart === null || targetStart === null) return true;
12448
+ if (!isNodeReachableWithinFunction(sourceNode, context)) return false;
12449
+ if (sourceBlock === targetBlock) return sourceStart < targetStart;
12450
+ const visitedBlocks = new Set([sourceBlock]);
12451
+ const pendingBlocks = [sourceBlock];
12452
+ while (pendingBlocks.length > 0) {
12453
+ const currentBlock = pendingBlocks.pop();
12454
+ if (!currentBlock) break;
12455
+ for (const edge of currentBlock.successors) {
12456
+ if (edge.to === targetBlock) return true;
12457
+ if (visitedBlocks.has(edge.to)) continue;
12458
+ visitedBlocks.add(edge.to);
12459
+ pendingBlocks.push(edge.to);
12460
+ }
12461
+ }
12462
+ return false;
12407
12463
  };
12408
12464
  const collectDeferredUsageGuardStates = (callback, usageNode, context) => {
12409
12465
  if (!isFunctionLike$1(callback) || callback.async) return [];
12410
12466
  const guardStates = [];
12411
12467
  walkAst(callback.body, (child) => {
12412
12468
  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));
12469
+ 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
12470
  });
12415
12471
  let descendant = usageNode;
12416
12472
  let ancestor = descendant.parent;
12417
12473
  while (ancestor && ancestor !== callback) {
12418
- if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant) guardStates.push(...collectBlockingBooleanStates(ancestor.test, false, context));
12474
+ if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant) guardStates.push(...collectBlockingBooleanStates(ancestor.test, false, ancestor, context));
12419
12475
  descendant = ancestor;
12420
12476
  ancestor = ancestor.parent;
12421
12477
  }
@@ -12424,7 +12480,7 @@ const collectDeferredUsageGuardStates = (callback, usageNode, context) => {
12424
12480
  const cleanupReturnInvalidatesGuard = (cleanupReturn, guardState, context) => {
12425
12481
  if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
12426
12482
  const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
12427
- if (!cleanupFunction || !isFunctionLike$1(cleanupFunction) || cleanupFunction.async) return false;
12483
+ if (!cleanupFunction || !isFunctionLike$1(cleanupFunction) || cleanupFunction.async || cleanupFunction.generator) return false;
12428
12484
  let didInvalidateGuard = false;
12429
12485
  walkAst(cleanupFunction.body, (child) => {
12430
12486
  if (didInvalidateGuard) return false;
@@ -12453,11 +12509,110 @@ const deferredUsageWritesGuardBeforeUsage = (callback, usageNode, guardState, co
12453
12509
  });
12454
12510
  return didWriteGuard;
12455
12511
  };
12512
+ const canInterruptionReachUsageThroughCatch = (interruptionNode, usageNode, owner, context) => {
12513
+ let descendant = interruptionNode;
12514
+ let ancestor = descendant.parent;
12515
+ while (ancestor && ancestor !== owner) {
12516
+ if (isNodeOfType(ancestor, "TryStatement") && ancestor.block === descendant && ancestor.handler && canNodeReachLaterNodeWithinFunction(ancestor.handler.body, usageNode, owner, context)) return true;
12517
+ descendant = ancestor;
12518
+ ancestor = ancestor.parent;
12519
+ }
12520
+ return false;
12521
+ };
12522
+ const isEffectLocalLifecycleGuard = (callback, guardState, cleanupFunctions, context) => {
12523
+ if (!guardState.bindingIdentifier) return false;
12524
+ const guardSymbol = context.scopes.symbolFor(guardState.bindingIdentifier);
12525
+ if (!guardSymbol || guardSymbol.kind !== "let" && guardSymbol.kind !== "var" || !isNodeOfType(guardSymbol.declarationNode, "VariableDeclarator") || findEnclosingFunction$1(guardSymbol.declarationNode) !== callback) return false;
12526
+ return guardSymbol.references.every((reference) => {
12527
+ if (!isWithinAssignmentTarget(reference.identifier)) return true;
12528
+ const assignmentTarget = findTransparentExpressionRoot(reference.identifier);
12529
+ const assignment = assignmentTarget.parent;
12530
+ return isNodeOfType(assignment, "AssignmentExpression") && assignment.operator === "=" && assignment.left === assignmentTarget && readStaticBoolean(assignment.right) === guardState.value && cleanupFunctions.includes(findEnclosingFunction$1(assignment) ?? assignment);
12531
+ });
12532
+ };
12533
+ const hasPotentialInterruptionAfterGuard = (callback, guardState, usageNode, context) => {
12534
+ if (!isFunctionLike$1(callback)) return true;
12535
+ const guardStart = getRangeStart(guardState.guardNode);
12536
+ const usageStart = getRangeStart(usageNode);
12537
+ if (guardStart === null || usageStart === null) return true;
12538
+ let hasPotentialInterruption = false;
12539
+ walkAst(callback.body, (child) => {
12540
+ if (hasPotentialInterruption) return false;
12541
+ if (child !== callback.body && isFunctionLike$1(child)) return false;
12542
+ const childStart = getRangeStart(child);
12543
+ if (childStart === null || childStart <= guardStart || childStart >= usageStart) return;
12544
+ if (isNodeOfType(child, "CallExpression") || isNodeOfType(child, "AwaitExpression") || isNodeOfType(child, "YieldExpression")) {
12545
+ if (canNodeReachLaterNodeWithinFunction(child, usageNode, callback, context) || canInterruptionReachUsageThroughCatch(child, usageNode, callback, context)) {
12546
+ hasPotentialInterruption = true;
12547
+ return false;
12548
+ }
12549
+ }
12550
+ });
12551
+ return hasPotentialInterruption;
12552
+ };
12456
12553
  const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
12457
12554
  const usageFunction = findEnclosingFunction$1(usage.node);
12458
12555
  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)));
12556
+ 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;
12557
+ const usageExpression = findTransparentExpressionRoot(usage.node);
12558
+ const usageAssignment = usageExpression.parent;
12559
+ if (!isNodeOfType(usageAssignment, "AssignmentExpression") || usageAssignment.operator !== "=" || usageAssignment.right !== usageExpression || !isNodeOfType(usageAssignment.left, "Identifier")) return false;
12560
+ const handleSymbol = context.scopes.symbolFor(usageAssignment.left);
12561
+ if (!handleSymbol || handleSymbol.kind !== "let" && handleSymbol.kind !== "var" || !isNodeOfType(handleSymbol.declarationNode, "VariableDeclarator") || findEnclosingFunction$1(handleSymbol.declarationNode) !== callback) return false;
12562
+ const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
12563
+ if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
12564
+ const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
12565
+ return cleanupFunction && isFunctionLike$1(cleanupFunction) ? [cleanupFunction] : [];
12566
+ });
12567
+ if (cleanupFunctions.length !== cleanupReturns.length) return false;
12568
+ const globalReleaseProofsByCleanup = /* @__PURE__ */ new Map();
12569
+ for (const cleanupFunction of cleanupFunctions) {
12570
+ if (!isFunctionLike$1(cleanupFunction)) return false;
12571
+ const globalReleaseProofs = [];
12572
+ walkAst(cleanupFunction.body, (child) => {
12573
+ if (child !== cleanupFunction.body && isFunctionLike$1(child)) return false;
12574
+ if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && context.scopes.isGlobalReference(child.callee) && doesReleaseCallMatchUsage(child, usage, context)) {
12575
+ const handleGuard = findDirectHandleGuardForRelease(child, cleanupFunction, usage, context);
12576
+ globalReleaseProofs.push({
12577
+ anchor: handleGuard ?? child,
12578
+ call: child,
12579
+ handleGuard
12580
+ });
12581
+ }
12582
+ });
12583
+ if (!doMatchingNodesCoverEveryPathFromFunctionEntry(cleanupFunction, globalReleaseProofs.map((releaseProof) => releaseProof.anchor), context)) return false;
12584
+ globalReleaseProofsByCleanup.set(cleanupFunction, globalReleaseProofs);
12585
+ }
12586
+ const handleAssignments = handleSymbol.references.filter((reference) => isWithinAssignmentTarget(reference.identifier));
12587
+ const hasUsageAssignment = handleAssignments.some((handleAssignment) => findTransparentExpressionRoot(handleAssignment.identifier).parent === usageAssignment);
12588
+ const hasUnsafeHandleAssignment = handleAssignments.some((handleAssignment) => {
12589
+ const assignmentTarget = findTransparentExpressionRoot(handleAssignment.identifier);
12590
+ const assignment = assignmentTarget.parent;
12591
+ if (assignment === usageAssignment) return false;
12592
+ if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== assignmentTarget) return true;
12593
+ const assignedValue = stripParenExpression(assignment.right);
12594
+ if (!(isNodeOfType(assignedValue, "Literal") && assignedValue.value === null || isNodeOfType(assignedValue, "Identifier") && assignedValue.name === "undefined" && context.scopes.isGlobalReference(assignedValue))) return true;
12595
+ const cleanupFunction = findEnclosingFunction$1(assignment);
12596
+ const globalReleaseProofs = cleanupFunction ? globalReleaseProofsByCleanup.get(cleanupFunction) : void 0;
12597
+ return !(cleanupFunction && globalReleaseProofs && doMatchingNodesCoverEveryPathBeforeUsage(assignment, globalReleaseProofs.map((releaseProof) => releaseProof.handleGuard && isAstDescendant(assignment, releaseProof.handleGuard.consequent) ? releaseProof.call : releaseProof.anchor), cleanupFunction, context));
12598
+ });
12599
+ if (!hasUsageAssignment || hasUnsafeHandleAssignment) return false;
12600
+ let usageAncestor = usage.node.parent;
12601
+ while (usageAncestor && usageAncestor !== usageFunction) {
12602
+ if (isNodeOfType(usageAncestor, "ForStatement") || isNodeOfType(usageAncestor, "ForInStatement") || isNodeOfType(usageAncestor, "ForOfStatement") || isNodeOfType(usageAncestor, "WhileStatement") || isNodeOfType(usageAncestor, "DoWhileStatement")) return false;
12603
+ usageAncestor = usageAncestor.parent;
12604
+ }
12605
+ let hasPotentialInterruption = false;
12606
+ for (const argument of usage.node.arguments ?? []) walkAst(argument, (argumentChild) => {
12607
+ if (hasPotentialInterruption) return false;
12608
+ if (isFunctionLike$1(argumentChild)) return false;
12609
+ if (isNodeOfType(argumentChild, "CallExpression") || isNodeOfType(argumentChild, "AwaitExpression") || isNodeOfType(argumentChild, "YieldExpression")) {
12610
+ hasPotentialInterruption = true;
12611
+ return false;
12612
+ }
12613
+ });
12614
+ if (hasPotentialInterruption) return false;
12615
+ 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
12616
  };
12462
12617
  const effectHasCleanupForUsage = (callback, usage, context) => {
12463
12618
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
@@ -13023,7 +13178,7 @@ const effectNeedsCleanup = defineRule({
13023
13178
  const hookName = getCalleeName$2(node) ?? "effect";
13024
13179
  context.report({
13025
13180
  node,
13026
- message: `\`${firstUsage.resourceName}\` creates a ${resourceNoun} in ${hookName} without returning cleanup. Return a cleanup function so it does not leak after unmount.`
13181
+ 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
13182
  });
13028
13183
  },
13029
13184
  FunctionDeclaration(node) {
@@ -14368,6 +14523,22 @@ const isReactHocCallbackArgument = (functionNode) => {
14368
14523
  return calleeName !== null && REACT_HOC_NAMES.has(calleeName);
14369
14524
  };
14370
14525
  //#endregion
14526
+ //#region src/plugin/utils/is-outside-all-functions.ts
14527
+ const FUNCTION_SCOPE_KINDS = new Set([
14528
+ "function",
14529
+ "arrow-function",
14530
+ "method"
14531
+ ]);
14532
+ const isOutsideAllFunctions = (symbol) => {
14533
+ let scope = symbol.scope;
14534
+ while (scope) {
14535
+ if (FUNCTION_SCOPE_KINDS.has(scope.kind)) return false;
14536
+ if (scope.kind === "module") return true;
14537
+ scope = scope.parent;
14538
+ }
14539
+ return true;
14540
+ };
14541
+ //#endregion
14371
14542
  //#region src/plugin/rules/react-builtins/exhaustive-deps-low-level.ts
14372
14543
  /**
14373
14544
  * Lowest-level helpers consumed by both the main `exhaustive-deps`
@@ -14420,25 +14591,6 @@ const getHookName = (callee, scopes) => {
14420
14591
  if (isNodeOfType(strippedCallee, "MemberExpression") && !strippedCallee.computed && isNodeOfType(strippedCallee.property, "Identifier")) return strippedCallee.property.name;
14421
14592
  return null;
14422
14593
  };
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
14594
  //#endregion
14443
14595
  //#region src/plugin/rules/react-builtins/exhaustive-deps-messages.ts
14444
14596
  const buildMissingDepMessage = (hookName, depName) => `\`${hookName}\` can run with a stale \`${depName}\` & show your users old data.`;
@@ -14476,25 +14628,6 @@ const resolveExhaustiveDepsSettings = (settings) => {
14476
14628
  };
14477
14629
  };
14478
14630
  //#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
14631
  //#region src/plugin/rules/react-builtins/exhaustive-deps-sole-writer-guard.ts
14499
14632
  const EQUALITY_BINARY_OPERATORS = new Set(["===", "!=="]);
14500
14633
  const getUseStateSetterSymbol = (stateSymbol, scopes) => {
@@ -21976,7 +22109,7 @@ const unwrapReactHocFunction = (node, scopes) => resolveReactHocFunction(node, s
21976
22109
  //#endregion
21977
22110
  //#region src/plugin/utils/build-same-file-jsx-slot-prop-registry.ts
21978
22111
  const REACT_SLOT_TYPE_NAMES = new Set(["ReactNode", "ReactElement"]);
21979
- const unwrapTopLevelDeclaration = (statement) => {
22112
+ const unwrapTopLevelDeclaration$1 = (statement) => {
21980
22113
  if (isNodeOfType(statement, "ExportNamedDeclaration")) return statement.declaration;
21981
22114
  if (isNodeOfType(statement, "ExportDefaultDeclaration")) return statement.declaration;
21982
22115
  return statement;
@@ -22013,7 +22146,7 @@ const buildReactSlotTypeEnvironment = (program) => {
22013
22146
  }
22014
22147
  continue;
22015
22148
  }
22016
- const declaration = unwrapTopLevelDeclaration(statement);
22149
+ const declaration = unwrapTopLevelDeclaration$1(statement);
22017
22150
  if (!declaration) continue;
22018
22151
  if ((isNodeOfType(declaration, "TSInterfaceDeclaration") || isNodeOfType(declaration, "TSTypeAliasDeclaration")) && isNodeOfType(declaration.id, "Identifier")) sameFileTypeDeclarations.set(declaration.id.name, declaration);
22019
22152
  if (isNodeOfType(declaration, "TSModuleDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === "JSX") hasLocalJsxNamespace = true;
@@ -22105,7 +22238,7 @@ const buildSameFileJsxSlotPropRegistry = (program, memoRegistry, scopes) => {
22105
22238
  if (!isNodeOfType(program, "Program")) return registry;
22106
22239
  const environment = buildReactSlotTypeEnvironment(program);
22107
22240
  for (const statement of program.body) {
22108
- const declaration = unwrapTopLevelDeclaration(statement);
22241
+ const declaration = unwrapTopLevelDeclaration$1(statement);
22109
22242
  if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) continue;
22110
22243
  for (const declarator of declaration.declarations) {
22111
22244
  if (!isNodeOfType(declarator.id, "Identifier") || memoRegistry.get(declarator.id.name) !== "memoised" || !declarator.init) continue;
@@ -29496,7 +29629,7 @@ const isNumericForLoopCounter = (attributeNode, indexName) => {
29496
29629
  if (forStatement.test && forLoopTestReadsDataLength(forStatement.test)) return false;
29497
29630
  return true;
29498
29631
  };
29499
- const EMPTY_NAME_SET = /* @__PURE__ */ new Set();
29632
+ const EMPTY_NAME_SET$1 = /* @__PURE__ */ new Set();
29500
29633
  const findIteratorItemNamesOfBinding = (binding) => {
29501
29634
  const names = /* @__PURE__ */ new Set();
29502
29635
  if (!binding.bindingFunction || !isFunctionLike$1(binding.bindingFunction)) return names;
@@ -29610,9 +29743,9 @@ const noArrayIndexAsKey = defineRule({
29610
29743
  const jsxElement = openingElement.parent;
29611
29744
  if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
29612
29745
  const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
29613
- const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET;
29746
+ const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET$1;
29614
29747
  if (!containsStatefulDescendant(jsxElement, {
29615
- memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET,
29748
+ memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET$1,
29616
29749
  bareIdentifierNames: primitiveItemNames.size > 0 ? new Set([...derivedNames, ...primitiveItemNames]) : derivedNames
29617
29750
  })) return;
29618
29751
  }
@@ -30113,6 +30246,9 @@ const findNearestPackageDirectory = (filename) => {
30113
30246
  const readNearestPackageManifest = (filename) => {
30114
30247
  const packageDirectory = findNearestPackageDirectory(filename);
30115
30248
  if (!packageDirectory) return null;
30249
+ return readPackageManifest(packageDirectory);
30250
+ };
30251
+ const readPackageManifest = (packageDirectory) => {
30116
30252
  const packageJsonPath = path.join(packageDirectory, "package.json");
30117
30253
  recordContentProbe(packageJsonPath);
30118
30254
  const cached = cachedManifestByPackageDirectory.get(packageDirectory);
@@ -30491,7 +30627,16 @@ const isRenderPreservingCallArgumentFunction = (node, scopes) => {
30491
30627
  return parent.arguments.some((argumentNode) => argumentNode === node) && isProvenArrayMapCall(parent, scopes);
30492
30628
  };
30493
30629
  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);
30630
+ const isRenderOutputExpression = (node, scopes) => node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS) || isReactDomCreatePortalCall(node, scopes);
30631
+ const isReactDomCreatePortalCall = (node, scopes) => {
30632
+ if (!isNodeOfType(node, "CallExpression")) return false;
30633
+ const callee = stripParenExpression(node.callee);
30634
+ if (isNodeOfType(callee, "Identifier")) return scopes.symbolFor(callee)?.kind === "import" && getImportedNameFromModule(callee, callee.name, "react-dom") === "createPortal";
30635
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "createPortal") return false;
30636
+ const symbol = scopes.symbolFor(callee.object);
30637
+ if (!symbol || symbol.kind !== "import") return false;
30638
+ return isDefaultImportFromModule(callee.object, callee.object.name, "react-dom") || isNamespaceImportFromModule(callee.object, callee.object.name, "react-dom");
30639
+ };
30495
30640
  const containsRenderOutput$1 = (rootNode, scopes) => {
30496
30641
  let hasRenderOutput = false;
30497
30642
  walkAst(rootNode, (node) => {
@@ -35838,7 +35983,7 @@ const isGatedByFalsyInitialState = (node, scopes) => {
35838
35983
  };
35839
35984
  //#endregion
35840
35985
  //#region src/plugin/rules/performance/no-hydration-branch-on-browser-global.ts
35841
- const evaluateEquality = (operator, left, right) => {
35986
+ const evaluateEquality$1 = (operator, left, right) => {
35842
35987
  if (operator === "===" || operator === "==") return left === right;
35843
35988
  if (operator === "!==" || operator === "!=") return left !== right;
35844
35989
  return null;
@@ -35869,8 +36014,8 @@ const matchBrowserPredicate = (expression, context) => {
35869
36014
  const browserGlobalName = leftGlobalName && typeof rightString === "string" ? leftGlobalName : rightGlobalName && typeof leftString === "string" ? rightGlobalName : null;
35870
36015
  const comparedType = leftGlobalName && typeof rightString === "string" ? rightString : rightGlobalName && typeof leftString === "string" ? leftString : null;
35871
36016
  if (!browserGlobalName || !comparedType) return null;
35872
- const clientResult = evaluateEquality(unwrappedExpression.operator, "object", comparedType);
35873
- const serverResult = evaluateEquality(unwrappedExpression.operator, "undefined", comparedType);
36017
+ const clientResult = evaluateEquality$1(unwrappedExpression.operator, "object", comparedType);
36018
+ const serverResult = evaluateEquality$1(unwrappedExpression.operator, "undefined", comparedType);
35874
36019
  if (clientResult === null || serverResult === null || clientResult === serverResult) return null;
35875
36020
  return {
35876
36021
  browserGlobalName,
@@ -41883,18 +42028,107 @@ const getNodeText$1 = (node) => {
41883
42028
  return value;
41884
42029
  });
41885
42030
  };
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");
42031
+ const normalizeAsBoolean = (identity) => ({
42032
+ ...identity,
42033
+ booleanNormalization: identity.booleanNormalization === "identity" ? "normalized" : identity.booleanNormalization
42034
+ });
42035
+ const negateBoolean = (identity) => ({
42036
+ ...identity,
42037
+ booleanNormalization: identity.booleanNormalization === "negated" ? "normalized" : "negated"
42038
+ });
42039
+ const getLivePropExpressionIdentity = (analysis, context, node, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
42040
+ const expression = stripParenExpression(node);
42041
+ if (isNodeOfType(expression, "Identifier")) {
42042
+ const reference = getRef(analysis, expression);
42043
+ const symbol = context.scopes.symbolFor(expression);
42044
+ if (!reference || !symbol) return null;
42045
+ if (isProp(analysis, reference)) return {
42046
+ propSymbolId: symbol.id,
42047
+ memberPath: [],
42048
+ booleanNormalization: "identity"
42049
+ };
42050
+ if (visitedSymbolIds.has(symbol.id) || symbol.kind !== "const") return null;
42051
+ visitedSymbolIds.add(symbol.id);
42052
+ const initializer = getDirectConstInitializer(symbol);
42053
+ if (initializer) return getLivePropExpressionIdentity(analysis, context, initializer, visitedSymbolIds);
42054
+ return getUpstreamRefs(analysis, reference).some((upstreamReference) => isProp(analysis, upstreamReference)) ? {
42055
+ propSymbolId: symbol.id,
42056
+ memberPath: [],
42057
+ booleanNormalization: "identity"
42058
+ } : null;
42059
+ }
42060
+ if (isNodeOfType(expression, "MemberExpression")) {
42061
+ const propertyName = getStaticMemberPropertyName(expression);
42062
+ if (!propertyName) return null;
42063
+ const objectIdentity = getLivePropExpressionIdentity(analysis, context, expression.object, visitedSymbolIds);
42064
+ if (!objectIdentity) return null;
42065
+ return {
42066
+ ...objectIdentity,
42067
+ memberPath: [...objectIdentity.memberPath, propertyName]
42068
+ };
42069
+ }
42070
+ if (isNodeOfType(expression, "CallExpression") && isNodeOfType(expression.callee, "Identifier") && expression.callee.name === "Boolean" && context.scopes.isGlobalReference(expression.callee) && expression.arguments?.length === 1) {
42071
+ const argument = expression.arguments[0];
42072
+ const argumentIdentity = getLivePropExpressionIdentity(analysis, context, argument, visitedSymbolIds);
42073
+ return argumentIdentity ? normalizeAsBoolean(argumentIdentity) : null;
42074
+ }
42075
+ if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") {
42076
+ const argumentIdentity = getLivePropExpressionIdentity(analysis, context, expression.argument, visitedSymbolIds);
42077
+ return argumentIdentity ? negateBoolean(argumentIdentity) : null;
42078
+ }
42079
+ return null;
42080
+ };
42081
+ const isMountSnapshotInitializer = (context, node) => {
42082
+ if (!isReactApiCall(node, "useMemo", context.scopes, { resolveNamedAliases: true }) || !isNodeOfType(node, "CallExpression")) return false;
42083
+ const dependencies = node.arguments?.[1];
42084
+ return isNodeOfType(dependencies, "ArrayExpression") && dependencies.elements.length === 0;
41896
42085
  };
41897
- const isSetStateToInitialValue = (analysis, setterRef) => {
42086
+ const isMountSnapshotBinding = (context, identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
42087
+ const symbol = context.scopes.symbolFor(identifier);
42088
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
42089
+ visitedSymbolIds.add(symbol.id);
42090
+ const initializer = getDirectConstInitializer(symbol);
42091
+ if (!initializer) return false;
42092
+ if (isMountSnapshotInitializer(context, initializer)) return true;
42093
+ const expression = stripParenExpression(initializer);
42094
+ return isNodeOfType(expression, "Identifier") && isMountSnapshotBinding(context, expression, visitedSymbolIds);
42095
+ };
42096
+ const isConstantBooleanExpression = (context, node, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
42097
+ const expression = stripParenExpression(node);
42098
+ if (isNodeOfType(expression, "Literal")) return true;
42099
+ if (isNodeOfType(expression, "Identifier")) {
42100
+ const symbol = context.scopes.symbolFor(expression);
42101
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
42102
+ visitedSymbolIds.add(symbol.id);
42103
+ const initializer = getDirectConstInitializer(symbol);
42104
+ return Boolean(initializer && isConstantBooleanExpression(context, initializer, visitedSymbolIds));
42105
+ }
42106
+ if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") return isConstantBooleanExpression(context, expression.argument, visitedSymbolIds);
42107
+ 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);
42108
+ return false;
42109
+ };
42110
+ const isProvenLiveBinding = (analysis, context, identifier) => {
42111
+ const reference = getRef(analysis, identifier);
42112
+ const symbol = context.scopes.symbolFor(identifier);
42113
+ if (!reference || !symbol || symbol.kind !== "const" || isOutsideAllFunctions(symbol)) return false;
42114
+ const initializer = symbol.initializer;
42115
+ if (!initializer || isMountSnapshotBinding(context, identifier)) return false;
42116
+ if (getDownstreamRefs(analysis, initializer).some((initializerReference) => isRefCurrent(initializerReference))) return false;
42117
+ return !isConstantBooleanExpression(context, initializer);
42118
+ };
42119
+ const areSameProvenLiveBinding = (analysis, context, left, right) => {
42120
+ const leftExpression = stripParenExpression(left);
42121
+ const rightExpression = stripParenExpression(right);
42122
+ if (!isNodeOfType(leftExpression, "Identifier") || !isNodeOfType(rightExpression, "Identifier")) return false;
42123
+ const leftSymbol = context.scopes.symbolFor(leftExpression);
42124
+ const rightSymbol = context.scopes.symbolFor(rightExpression);
42125
+ return Boolean(leftSymbol && leftSymbol === rightSymbol && isProvenLiveBinding(analysis, context, leftExpression));
42126
+ };
42127
+ const haveSameLivePropExpressionIdentity = (left, right) => {
42128
+ if (left.propSymbolId !== right.propSymbolId || left.booleanNormalization !== right.booleanNormalization || left.memberPath.length !== right.memberPath.length) return false;
42129
+ return left.memberPath.every((propertyName, index) => propertyName === right.memberPath[index]);
42130
+ };
42131
+ const isSetStateToInitialValue = (analysis, context, setterRef) => {
41898
42132
  const callExpr = getCallExpr(setterRef);
41899
42133
  if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) return false;
41900
42134
  const setStateToValue = callExpr.arguments?.[0];
@@ -41905,7 +42139,12 @@ const isSetStateToInitialValue = (analysis, setterRef) => {
41905
42139
  if (isUndefinedNode(setStateToValue) && isUndefinedNode(stateInitialValue)) return true;
41906
42140
  if (setStateToValue == null && stateInitialValue == null) return true;
41907
42141
  if (setStateToValue && !stateInitialValue || !setStateToValue && stateInitialValue) return false;
41908
- if (stateInitialValue && isNodeOfType(stateInitialValue, "Identifier") && stateInitialValue.name !== "undefined" && !isLiteralConstantIdentifier(analysis, stateInitialValue)) return false;
42142
+ if (stateInitialValue && setStateToValue) {
42143
+ const initialLivePropIdentity = getLivePropExpressionIdentity(analysis, context, stateInitialValue);
42144
+ const nextLivePropIdentity = getLivePropExpressionIdentity(analysis, context, setStateToValue);
42145
+ if (initialLivePropIdentity && nextLivePropIdentity && haveSameLivePropExpressionIdentity(initialLivePropIdentity, nextLivePropIdentity)) return false;
42146
+ if (areSameProvenLiveBinding(analysis, context, stateInitialValue, setStateToValue)) return false;
42147
+ }
41909
42148
  return getNodeText$1(setStateToValue) === getNodeText$1(stateInitialValue);
41910
42149
  };
41911
42150
  const countUseStates = (analysis, componentNode) => {
@@ -41914,10 +42153,10 @@ const countUseStates = (analysis, componentNode) => {
41914
42153
  for (const ref of getDownstreamRefs(analysis, componentNode)) if (isState(analysis, ref)) stateVariables.add(ref.resolved);
41915
42154
  return stateVariables.size;
41916
42155
  };
41917
- const findPropUsedToResetAllState = (analysis, effectFnRefs, depsRefs, useEffectNode, effectFn) => {
42156
+ const findPropUsedToResetAllState = (analysis, context, effectFnRefs, depsRefs, useEffectNode, effectFn) => {
41918
42157
  const stateSetterRefs = effectFnRefs.filter((ref) => isSyncStateSetterCall(analysis, ref, effectFn));
41919
42158
  if (stateSetterRefs.length === 0) return null;
41920
- if (!stateSetterRefs.every((ref) => isSetStateToInitialValue(analysis, ref))) return null;
42159
+ if (!stateSetterRefs.every((ref) => isSetStateToInitialValue(analysis, context, ref))) return null;
41921
42160
  if (stateSetterRefs.every((setterRef) => effectFnRefs.some((otherRef) => otherRef !== setterRef && otherRef.resolved === setterRef.resolved && Boolean(getCallExpr(otherRef)) && !isSyncStateSetterCall(analysis, otherRef, effectFn)))) return null;
41922
42161
  const containing = findContainingNode(analysis, useEffectNode);
41923
42162
  if (new Set(stateSetterRefs.map((setterRef) => setterRef.resolved)).size !== countUseStates(analysis, containing)) return null;
@@ -41941,7 +42180,7 @@ const noResetAllStateOnPropChange = defineRule({
41941
42180
  if (containing && isCustomHook(containing)) return;
41942
42181
  const effectFn = getEffectFn(analysis, node);
41943
42182
  if (!effectFn) return;
41944
- if (!findPropUsedToResetAllState(analysis, effectFnRefs, depsRefs, node, effectFn)) return;
42183
+ if (!findPropUsedToResetAllState(analysis, context, effectFnRefs, depsRefs, node, effectFn)) return;
41945
42184
  context.report({
41946
42185
  node,
41947
42186
  message: `Your users briefly see stale state when a prop changes because this useEffect clears all state.`
@@ -45744,19 +45983,726 @@ const nosqlInjectionRisk = defineRule({
45744
45983
  })
45745
45984
  });
45746
45985
  //#endregion
45986
+ //#region src/plugin/utils/export-all-adds-runtime-values.ts
45987
+ const getExportedName = (node) => {
45988
+ if (!node) return null;
45989
+ if (isNodeOfType(node, "Identifier")) return node.name;
45990
+ if (isNodeOfType(node, "Literal") && typeof node.value === "string") return node.value;
45991
+ return null;
45992
+ };
45993
+ const programHasRuntimeNamedExports = (filePath, program, visitedFilePaths) => {
45994
+ if (!isNodeOfType(program, "Program")) return true;
45995
+ for (const statement of program.body) {
45996
+ if (isNodeOfType(statement, "ExportDefaultDeclaration")) continue;
45997
+ if (isNodeOfType(statement, "ExportAllDeclaration")) {
45998
+ if (statement.exportKind === "type") continue;
45999
+ if (statement.exported) return true;
46000
+ if (typeof statement.source.value !== "string") return true;
46001
+ if (exportAllAddsRuntimeValues(filePath, statement.source.value, visitedFilePaths)) return true;
46002
+ continue;
46003
+ }
46004
+ if (!isNodeOfType(statement, "ExportNamedDeclaration")) continue;
46005
+ if (statement.exportKind === "type") continue;
46006
+ if (statement.declaration) {
46007
+ if (statement.declaration.type !== "TSInterfaceDeclaration" && statement.declaration.type !== "TSTypeAliasDeclaration" && statement.declaration.type !== "TSDeclareFunction") return true;
46008
+ continue;
46009
+ }
46010
+ if (isEverySpecifierInlineType(statement.specifiers, "ExportSpecifier", "exportKind")) continue;
46011
+ for (const specifier of statement.specifiers) {
46012
+ if (!isNodeOfType(specifier, "ExportSpecifier") || specifier.exportKind === "type") continue;
46013
+ if (getExportedName(specifier.exported) !== "default") return true;
46014
+ }
46015
+ }
46016
+ return false;
46017
+ };
46018
+ const exportAllAddsRuntimeValues = (importerFilePath, source, visitedFilePaths = /* @__PURE__ */ new Set()) => {
46019
+ const resolvedFilePath = resolveRelativeImportPath(importerFilePath, source);
46020
+ if (!resolvedFilePath) return true;
46021
+ if (path.extname(resolvedFilePath) === ".cjs" || resolvedFilePath.endsWith(".cts")) return true;
46022
+ if (visitedFilePaths.has(resolvedFilePath)) return true;
46023
+ visitedFilePaths.add(resolvedFilePath);
46024
+ const program = parseSourceFile(resolvedFilePath);
46025
+ if (!program) return true;
46026
+ return programHasRuntimeNamedExports(resolvedFilePath, program, visitedFilePaths);
46027
+ };
46028
+ //#endregion
45747
46029
  //#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) => {
46030
+ const NEXT_APP_ROUTE_FILE_PATTERN = new RegExp(`^(page|layout|loading|error|not-found|template|default|global-error|route)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
46031
+ const NEXT_PAGES_ROUTE_FILE_PATTERN = new RegExp(`^(_app|_document|_error|_meta)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
46032
+ const EXPO_ROUTE_FILE_PATTERN = new RegExp(`^(_layout|\\+html|\\+not-found|\\+native-intent)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
46033
+ const TANSTACK_ROUTE_FILE_PATTERN = new RegExp(`(?:^__root|\\.lazy)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
46034
+ const REACT_ROUTER_FILE_PATTERN = new RegExp(`^(root|entry\\.client|entry\\.server)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
46035
+ const isInNextDirectory = (context, directoryPath) => {
46036
+ if (isInProjectDirectory(context, directoryPath)) return true;
46037
+ const filename = normalizeFilename(context.filename ?? "");
46038
+ if (path.isAbsolute(filename)) return false;
46039
+ return filename.startsWith(`${directoryPath}/`) || filename.includes(`/${directoryPath}/`);
46040
+ };
46041
+ const isInExpoRouteDirectory = (context) => {
46042
+ const filename = normalizeFilename(context.filename ?? "");
46043
+ const configuredRootDirectory = getReactDoctorStringSetting(context.settings, "rootDirectory");
46044
+ const packageDirectory = path.isAbsolute(filename) ? findNearestPackageDirectory(filename) : null;
46045
+ const projectRelativeFilename = getProjectRelativeFilename(filename, configuredRootDirectory ?? packageDirectory ?? void 0);
46046
+ const relativeFilename = projectRelativeFilename === filename && path.isAbsolute(filename) ? filename.split("/").slice(2).join("/") : projectRelativeFilename;
46047
+ return relativeFilename.startsWith("app/") || relativeFilename.startsWith("src/app/");
46048
+ };
46049
+ const isFrameworkRouteOrSpecialFilename = (context, runtime) => {
46050
+ const rawFilename = context.filename;
45756
46051
  if (!rawFilename) return false;
45757
- if (isNextjsMetadataImageRouteFilename(rawFilename)) return true;
45758
46052
  const basename = path.basename(rawFilename);
45759
- return FRAMEWORK_ROUTE_FILE_PATTERNS.some((pattern) => pattern.test(basename));
46053
+ 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);
46054
+ if (runtime === "expo") return isInExpoRouteDirectory(context) && EXPO_ROUTE_FILE_PATTERN.test(basename);
46055
+ if (runtime === "tanstack") return TANSTACK_ROUTE_FILE_PATTERN.test(basename);
46056
+ if (runtime === "react-router" || runtime === "remix") return REACT_ROUTER_FILE_PATTERN.test(basename);
46057
+ return false;
46058
+ };
46059
+ //#endregion
46060
+ //#region src/plugin/utils/function-has-react-element-return-type.ts
46061
+ const isReactNamespaceBinding = (node) => {
46062
+ if (!isNodeOfType(node, "Identifier")) return false;
46063
+ const binding = getImportBindingForName(node, node.name);
46064
+ return Boolean(binding && binding.source === "react" && (binding.isNamespace || binding.exportedName === "default"));
46065
+ };
46066
+ const hasLocalJsxNamespace = (node) => {
46067
+ const program = findProgramRoot(node);
46068
+ if (!program || !isNodeOfType(program, "Program")) return false;
46069
+ return program.body.some((statement) => isNodeOfType(statement, "TSModuleDeclaration") && isNodeOfType(statement.id, "Identifier") && statement.id.name === "JSX");
46070
+ };
46071
+ const isReactElementTypeReference = (node) => {
46072
+ if (!isNodeOfType(node, "TSTypeReference")) return false;
46073
+ const typeName = node.typeName;
46074
+ if (isNodeOfType(typeName, "Identifier")) return getImportedNameFromModule(typeName, typeName.name, "react") === "ReactElement";
46075
+ if (!isNodeOfType(typeName, "TSQualifiedName")) return false;
46076
+ if (!isNodeOfType(typeName.right, "Identifier")) return false;
46077
+ if (typeName.right.name === "ReactElement" && isReactNamespaceBinding(typeName.left)) return true;
46078
+ if (typeName.right.name !== "Element") return false;
46079
+ if (isNodeOfType(typeName.left, "Identifier")) {
46080
+ if (typeName.left.name !== "JSX") return false;
46081
+ const binding = getImportBindingForName(typeName.left, typeName.left.name);
46082
+ if (!binding) return !hasLocalJsxNamespace(typeName.left);
46083
+ return binding.source === "react" && binding.exportedName === "JSX";
46084
+ }
46085
+ return isNodeOfType(typeName.left, "TSQualifiedName") && isNodeOfType(typeName.left.right, "Identifier") && typeName.left.right.name === "JSX" && isReactNamespaceBinding(typeName.left.left);
46086
+ };
46087
+ const containsReactElementType = (node) => {
46088
+ if (isNodeOfType(node, "TSUnionType")) return node.types.some((member) => containsReactElementType(member));
46089
+ return isReactElementTypeReference(node);
46090
+ };
46091
+ const functionHasReactElementReturnType = (functionNode) => {
46092
+ const returnType = Reflect.get(functionNode, "returnType");
46093
+ if (!isNodeOfType(returnType, "TSTypeAnnotation")) return false;
46094
+ return containsReactElementType(returnType.typeAnnotation);
46095
+ };
46096
+ //#endregion
46097
+ //#region src/plugin/constants/fast-refresh.ts
46098
+ const FAST_REFRESH_CONFIG_FILENAMES = [
46099
+ "vite.config.ts",
46100
+ "vite.config.mts",
46101
+ "vite.config.cts",
46102
+ "vite.config.js",
46103
+ "vite.config.mjs",
46104
+ "vite.config.cjs",
46105
+ "webpack.config.ts",
46106
+ "webpack.config.js",
46107
+ "webpack.config.mjs",
46108
+ "webpack.config.cjs",
46109
+ "rsbuild.config.ts",
46110
+ "rsbuild.config.js",
46111
+ "rspack.config.ts",
46112
+ "rspack.config.js"
46113
+ ];
46114
+ const MINIMUM_FAST_REFRESH_VERSIONS = {
46115
+ dumi: {
46116
+ major: 2,
46117
+ minor: 0
46118
+ },
46119
+ expo: {
46120
+ major: 36,
46121
+ minor: 0
46122
+ },
46123
+ gatsby: {
46124
+ major: 2,
46125
+ minor: 31
46126
+ },
46127
+ next: {
46128
+ major: 9,
46129
+ minor: 4
46130
+ },
46131
+ parcel: {
46132
+ major: 2,
46133
+ minor: 0
46134
+ },
46135
+ reactForGatsbyTwo: {
46136
+ major: 17,
46137
+ minor: 0
46138
+ },
46139
+ reactNative: {
46140
+ major: 0,
46141
+ minor: 61
46142
+ },
46143
+ reactScripts: {
46144
+ major: 4,
46145
+ minor: 0
46146
+ },
46147
+ storybookReact: {
46148
+ major: 6,
46149
+ minor: 1
46150
+ }
46151
+ };
46152
+ //#endregion
46153
+ //#region src/plugin/utils/get-fast-refresh-file-status.ts
46154
+ const INTEGRATION_IMPORTS = new Map([
46155
+ ["@vitejs/plugin-react", {
46156
+ importedNames: null,
46157
+ requiresViteDevelopmentServer: true,
46158
+ runtime: "generic"
46159
+ }],
46160
+ ["@vitejs/plugin-react-swc", {
46161
+ importedNames: null,
46162
+ requiresViteDevelopmentServer: true,
46163
+ runtime: "generic"
46164
+ }],
46165
+ ["@pmmmwh/react-refresh-webpack-plugin", {
46166
+ importedNames: null,
46167
+ runtime: "generic"
46168
+ }],
46169
+ ["@react-router/dev/vite", {
46170
+ importedNames: new Set(["reactRouter"]),
46171
+ requiresViteDevelopmentServer: true,
46172
+ runtime: "react-router"
46173
+ }],
46174
+ ["@remix-run/dev", {
46175
+ importedNames: new Set(["vitePlugin"]),
46176
+ requiresViteDevelopmentServer: true,
46177
+ runtime: "remix"
46178
+ }],
46179
+ ["@rsbuild/plugin-react", {
46180
+ importedNames: new Set(["pluginReact"]),
46181
+ runtime: "generic"
46182
+ }],
46183
+ ["@rspack/plugin-react-refresh", {
46184
+ importedNames: null,
46185
+ runtime: "generic"
46186
+ }],
46187
+ ["@rozenite/vite-plugin", {
46188
+ importedNames: new Set(["rozenitePlugin"]),
46189
+ requiresViteDevelopmentServer: true,
46190
+ runtime: "generic"
46191
+ }],
46192
+ ["@tanstack/react-start/plugin/vite", {
46193
+ importedNames: new Set(["tanstackStart"]),
46194
+ requiresViteDevelopmentServer: true,
46195
+ runtime: "tanstack"
46196
+ }]
46197
+ ]);
46198
+ const REGISTERED_INTEGRATION_RUNTIME_PRECEDENCE = [
46199
+ "tanstack",
46200
+ "remix",
46201
+ "react-router",
46202
+ "generic"
46203
+ ];
46204
+ const cachedLocalStatusByManifest = /* @__PURE__ */ new WeakMap();
46205
+ const cachedWorkspaceIndexByManifest = /* @__PURE__ */ new WeakMap();
46206
+ const INACTIVE_STATUS = {
46207
+ isActive: false,
46208
+ runtime: "generic"
46209
+ };
46210
+ const WORKSPACE_IGNORED_DIRECTORY_NAMES = new Set([
46211
+ ".git",
46212
+ ".next",
46213
+ "build",
46214
+ "coverage",
46215
+ "dist",
46216
+ "node_modules",
46217
+ "out"
46218
+ ]);
46219
+ const parseVersion = (version) => {
46220
+ if (typeof version !== "string") return null;
46221
+ const match = version.match(/(?:^|[^\d])(\d+)(?:\.(\d+))?/);
46222
+ if (!match) return null;
46223
+ return {
46224
+ major: Number(match[1]),
46225
+ minor: Number(match[2] ?? 0)
46226
+ };
46227
+ };
46228
+ const isVersionAtLeast = (version, minimum) => {
46229
+ const parsed = parseVersion(version);
46230
+ if (!parsed) return false;
46231
+ return parsed.major > minimum.major || parsed.major === minimum.major && parsed.minor >= minimum.minor;
46232
+ };
46233
+ const getDependencyVersion = (manifest, dependencyName) => manifest.dependencies?.[dependencyName] ?? manifest.devDependencies?.[dependencyName] ?? manifest.peerDependencies?.[dependencyName] ?? manifest.optionalDependencies?.[dependencyName];
46234
+ const getRuntimeDependencyVersion = (manifest, dependencyName) => manifest.dependencies?.[dependencyName] ?? manifest.optionalDependencies?.[dependencyName];
46235
+ const getOwnedDependencyVersion = (manifest, dependencyName, developmentCommandPattern) => {
46236
+ const runtimeVersion = getRuntimeDependencyVersion(manifest, dependencyName);
46237
+ if (runtimeVersion !== void 0) return runtimeVersion;
46238
+ return Object.values(manifest.scripts ?? {}).some((script) => typeof script === "string" && developmentCommandPattern.test(script)) ? manifest.devDependencies?.[dependencyName] : void 0;
46239
+ };
46240
+ const hasParcelBrowserEntry = (manifest) => {
46241
+ if (typeof manifest.source === "string" && manifest.source.endsWith(".html")) return true;
46242
+ return Object.values(manifest.scripts ?? {}).some((script) => typeof script === "string" && /(?:^|\s)parcel(?:\s+serve)?\s+[^\n]*\.html(?:\s|$)/.test(script));
46243
+ };
46244
+ 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));
46245
+ const hasOwnedDevelopmentCommand = (manifest, commandPattern) => Object.values(manifest.scripts ?? {}).some((script) => typeof script === "string" && commandPattern.test(script));
46246
+ const hasViteDevelopmentCommand = (manifest) => Object.values(manifest.scripts ?? {}).some((script) => {
46247
+ if (typeof script !== "string") return false;
46248
+ return [...script.matchAll(/(?:^|[\s;&|'\\"])vite(?:\s+(build|preview|test)\b)?/g)].some((match) => match[1] === void 0);
46249
+ });
46250
+ const hasViteBrowserEntry = (manifest, packageDirectory) => {
46251
+ if (typeof manifest.source === "string" && manifest.source.endsWith(".html")) return true;
46252
+ const browserEntryPath = path.join(packageDirectory, "index.html");
46253
+ recordExistenceProbe(browserEntryPath);
46254
+ return fs.existsSync(browserEntryPath);
46255
+ };
46256
+ const getBuiltInStatus = (manifest) => {
46257
+ if (isVersionAtLeast(getOwnedDependencyVersion(manifest, "next", /(?:^|\s)next\s+dev(?:\s|$)/), MINIMUM_FAST_REFRESH_VERSIONS.next)) return {
46258
+ isActive: true,
46259
+ runtime: "next"
46260
+ };
46261
+ const gatsbyDependency = getOwnedDependencyVersion(manifest, "gatsby", /(?:^|\s)gatsby\s+develop(?:\s|$)/);
46262
+ const gatsbyVersion = parseVersion(gatsbyDependency);
46263
+ const hasGatsbyFastRefresh = gatsbyVersion !== null && (gatsbyVersion.major >= 3 || isVersionAtLeast(gatsbyDependency, MINIMUM_FAST_REFRESH_VERSIONS.gatsby) && isVersionAtLeast(getDependencyVersion(manifest, "react"), MINIMUM_FAST_REFRESH_VERSIONS.reactForGatsbyTwo));
46264
+ 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 {
46265
+ isActive: true,
46266
+ runtime: "generic"
46267
+ };
46268
+ 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 {
46269
+ isActive: true,
46270
+ runtime: "expo"
46271
+ };
46272
+ if (isVersionAtLeast(getOwnedDependencyVersion(manifest, "dumi", /(?:^|\s)dumi\s+dev(?:\s|$)/), MINIMUM_FAST_REFRESH_VERSIONS.dumi)) return {
46273
+ isActive: true,
46274
+ runtime: "generic"
46275
+ };
46276
+ return null;
46277
+ };
46278
+ const getIntegrationLocalNames = (program, scopes) => {
46279
+ const localBindings = /* @__PURE__ */ new Map();
46280
+ const registerLocalName = (source, importedName, localIdentifier) => {
46281
+ const integration = INTEGRATION_IMPORTS.get(source);
46282
+ if (!integration) return;
46283
+ if (integration.importedNames === null ? importedName === null || importedName === "default" : importedName !== null && integration.importedNames.has(importedName)) {
46284
+ const symbol = scopes.symbolFor(localIdentifier);
46285
+ if (symbol) localBindings.set(symbol, integration);
46286
+ }
46287
+ };
46288
+ walkAst(program, (node) => {
46289
+ if (isNodeOfType(node, "ImportDeclaration")) {
46290
+ const source = node.source.value;
46291
+ if (typeof source !== "string") return;
46292
+ for (const specifier of node.specifiers) {
46293
+ if (isNodeOfType(specifier, "ImportDefaultSpecifier") || isNodeOfType(specifier, "ImportNamespaceSpecifier")) {
46294
+ registerLocalName(source, null, specifier.local);
46295
+ continue;
46296
+ }
46297
+ registerLocalName(source, getImportedName(specifier) ?? null, specifier.local);
46298
+ }
46299
+ return;
46300
+ }
46301
+ if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return;
46302
+ const initializer = node.init;
46303
+ if (!isNodeOfType(initializer, "CallExpression") || !isNodeOfType(initializer.callee, "Identifier") || initializer.callee.name !== "require") return;
46304
+ const sourceArgument = initializer.arguments[0];
46305
+ if (!sourceArgument || !isNodeOfType(sourceArgument, "Literal")) return;
46306
+ const source = sourceArgument.value;
46307
+ if (typeof source !== "string") return;
46308
+ if (isNodeOfType(node.id, "Identifier")) {
46309
+ registerLocalName(source, null, node.id);
46310
+ return;
46311
+ }
46312
+ if (!isNodeOfType(node.id, "ObjectPattern")) return;
46313
+ for (const property of node.id.properties) {
46314
+ if (!isNodeOfType(property, "Property") || !isNodeOfType(property.value, "Identifier")) continue;
46315
+ registerLocalName(source, isNodeOfType(property.key, "Identifier") ? property.key.name : isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" ? property.key.value : null, property.value);
46316
+ }
46317
+ });
46318
+ return localBindings;
46319
+ };
46320
+ 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";
46321
+ const getExportedBindings = (program, scopes) => {
46322
+ const exportedBindings = /* @__PURE__ */ new Set();
46323
+ walkAst(program, (node) => {
46324
+ let exportRoot = null;
46325
+ if (isNodeOfType(node, "ExportDefaultDeclaration")) exportRoot = node.declaration;
46326
+ else if (isNodeOfType(node, "AssignmentExpression") && isModuleExportsAssignment(node)) exportRoot = node.right;
46327
+ if (!exportRoot) return;
46328
+ if (isFunctionLike$1(exportRoot) || isNodeOfType(exportRoot, "ObjectExpression")) return;
46329
+ walkAst(exportRoot, (exportNode) => {
46330
+ if (isFunctionLike$1(exportNode) || isNodeOfType(exportNode, "ObjectExpression")) return false;
46331
+ if (!isNodeOfType(exportNode, "Identifier")) return;
46332
+ const symbol = scopes.symbolFor(exportNode);
46333
+ if (symbol) exportedBindings.add(symbol);
46334
+ });
46335
+ });
46336
+ return exportedBindings;
46337
+ };
46338
+ const isExportedConfigProperty = (property, exportedBindings, scopes) => {
46339
+ let ancestor = property.parent;
46340
+ let didFindContainingObject = false;
46341
+ let didCrossNestedProperty = false;
46342
+ let didCrossFunctionBoundary = false;
46343
+ let containingFunction = null;
46344
+ let containingReturn = null;
46345
+ while (ancestor) {
46346
+ if (isNodeOfType(ancestor, "ObjectExpression") && !didFindContainingObject) didFindContainingObject = true;
46347
+ else if (didFindContainingObject && isNodeOfType(ancestor, "Property")) didCrossNestedProperty = true;
46348
+ if (!containingReturn && isNodeOfType(ancestor, "ReturnStatement")) containingReturn = ancestor;
46349
+ if (isFunctionLike$1(ancestor)) if (containingFunction) didCrossFunctionBoundary = true;
46350
+ else containingFunction = ancestor;
46351
+ if (isNodeOfType(ancestor, "ExportDefaultDeclaration") || isModuleExportsAssignment(ancestor)) return !didCrossNestedProperty && !didCrossFunctionBoundary && (!containingFunction || Boolean(containingReturn) || !isNodeOfType(containingFunction.body, "BlockStatement"));
46352
+ if (isNodeOfType(ancestor, "VariableDeclarator") && isNodeOfType(ancestor.id, "Identifier")) {
46353
+ const binding = scopes.symbolFor(ancestor.id);
46354
+ if (binding && exportedBindings.has(binding)) return !didCrossNestedProperty && !containingFunction;
46355
+ }
46356
+ ancestor = ancestor.parent;
46357
+ }
46358
+ return false;
46359
+ };
46360
+ const getExplicitConfigPaths = (packageDirectory, manifest) => {
46361
+ const configPaths = new Set(FAST_REFRESH_CONFIG_FILENAMES.map((configFilename) => path.join(packageDirectory, configFilename)));
46362
+ for (const script of Object.values(manifest.scripts ?? {})) {
46363
+ if (typeof script !== "string") continue;
46364
+ for (const match of script.matchAll(/(?:^|\s)--config(?:=|\s+)(?:"([^"]+)"|'([^']+)'|([^\s;&|]+))/g)) {
46365
+ const relativeConfigPath = match[1] ?? match[2] ?? match[3];
46366
+ if (!relativeConfigPath) continue;
46367
+ const configPath = path.resolve(packageDirectory, relativeConfigPath);
46368
+ const relativePath = path.relative(packageDirectory, configPath);
46369
+ if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) continue;
46370
+ configPaths.add(configPath);
46371
+ }
46372
+ }
46373
+ return [...configPaths];
46374
+ };
46375
+ const getRegisteredIntegration = (packageDirectory, manifest) => {
46376
+ for (const configPath of getExplicitConfigPaths(packageDirectory, manifest)) {
46377
+ const program = parseSourceFile(configPath);
46378
+ if (!program) continue;
46379
+ const scopes = analyzeScopes(program);
46380
+ const integrationLocalBindings = getIntegrationLocalNames(program, scopes);
46381
+ if (integrationLocalBindings.size === 0) continue;
46382
+ const exportedBindings = getExportedBindings(program, scopes);
46383
+ const registeredIntegrations = [];
46384
+ walkAst(program, (node) => {
46385
+ if (!isNodeOfType(node, "Property")) return;
46386
+ if (!(isNodeOfType(node.key, "Identifier") && node.key.name === "plugins" || isNodeOfType(node.key, "Literal") && node.key.value === "plugins")) return;
46387
+ if (!isExportedConfigProperty(node, exportedBindings, scopes)) return;
46388
+ const inspectedBindings = /* @__PURE__ */ new Set();
46389
+ const inspectValue = (value) => {
46390
+ walkAst(value, (valueNode) => {
46391
+ if (isNodeOfType(valueNode, "Identifier")) {
46392
+ const symbol = scopes.symbolFor(valueNode);
46393
+ const initializer = symbol ? getDirectUnreassignedInitializer(symbol) : null;
46394
+ if (symbol && initializer && !inspectedBindings.has(symbol.id)) {
46395
+ inspectedBindings.add(symbol.id);
46396
+ inspectValue(initializer);
46397
+ }
46398
+ }
46399
+ if (!isNodeOfType(valueNode, "CallExpression") && !isNodeOfType(valueNode, "NewExpression")) return;
46400
+ if (!isNodeOfType(valueNode.callee, "Identifier")) return;
46401
+ const symbol = scopes.symbolFor(valueNode.callee);
46402
+ const integration = symbol ? integrationLocalBindings.get(symbol) : null;
46403
+ if (integration) registeredIntegrations.push(integration);
46404
+ });
46405
+ };
46406
+ inspectValue(node.value);
46407
+ return false;
46408
+ });
46409
+ const hasViteDevelopmentRuntime = hasViteDevelopmentCommand(manifest) || hasViteBrowserEntry(manifest, packageDirectory);
46410
+ const registeredIntegration = REGISTERED_INTEGRATION_RUNTIME_PRECEDENCE.flatMap((runtime) => registeredIntegrations.filter((integration) => integration.runtime === runtime && (!integration.requiresViteDevelopmentServer || hasViteDevelopmentRuntime)))[0];
46411
+ if (registeredIntegration) return {
46412
+ isActive: true,
46413
+ runtime: registeredIntegration.runtime
46414
+ };
46415
+ }
46416
+ return null;
46417
+ };
46418
+ const hasStorybookReactViteConfig = (packageDirectory) => {
46419
+ for (const configFilename of [
46420
+ "main.ts",
46421
+ "main.js",
46422
+ "main.mjs",
46423
+ "main.cjs"
46424
+ ]) {
46425
+ const program = parseSourceFile(path.join(packageDirectory, ".storybook", configFilename));
46426
+ if (!program) continue;
46427
+ const scopes = analyzeScopes(program);
46428
+ const exportedBindings = getExportedBindings(program, scopes);
46429
+ let hasReactViteFramework = false;
46430
+ walkAst(program, (node) => {
46431
+ if (!isNodeOfType(node, "Property")) return;
46432
+ if (!(isNodeOfType(node.key, "Identifier") && node.key.name === "framework" || isNodeOfType(node.key, "Literal") && node.key.value === "framework")) return;
46433
+ if (!isExportedConfigProperty(node, exportedBindings, scopes)) return;
46434
+ walkAst(node.value, (valueNode) => {
46435
+ if (isNodeOfType(valueNode, "Literal") && valueNode.value === "@storybook/react-vite") {
46436
+ hasReactViteFramework = true;
46437
+ return false;
46438
+ }
46439
+ });
46440
+ if (hasReactViteFramework) return false;
46441
+ });
46442
+ if (hasReactViteFramework) return true;
46443
+ }
46444
+ return false;
46445
+ };
46446
+ const resolveDirectUnreassignedValue = (value, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
46447
+ const unwrappedValue = stripParenExpression(value);
46448
+ if (!isNodeOfType(unwrappedValue, "Identifier")) return unwrappedValue;
46449
+ const symbol = scopes.symbolFor(unwrappedValue);
46450
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return unwrappedValue;
46451
+ const initializer = getDirectUnreassignedInitializer(symbol);
46452
+ if (!initializer) return unwrappedValue;
46453
+ visitedSymbolIds.add(symbol.id);
46454
+ return resolveDirectUnreassignedValue(initializer, scopes, visitedSymbolIds);
46455
+ };
46456
+ 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);
46457
+ const readLastStaticBooleanProperty = (value, propertyName, scopes) => {
46458
+ const resolvedValue = resolveDirectUnreassignedValue(value, scopes);
46459
+ if (!isNodeOfType(resolvedValue, "ObjectExpression")) return null;
46460
+ let propertyValue = null;
46461
+ for (const property of resolvedValue.properties) {
46462
+ if (isNodeOfType(property, "SpreadElement")) {
46463
+ propertyValue = null;
46464
+ continue;
46465
+ }
46466
+ if (!isStaticPropertyNamed(property, propertyName) || !isNodeOfType(property, "Property")) continue;
46467
+ propertyValue = readStaticBoolean(resolveDirectUnreassignedValue(property.value, scopes));
46468
+ }
46469
+ return propertyValue;
46470
+ };
46471
+ const isLastStaticPropertyInObject = (property, propertyName) => {
46472
+ if (!isNodeOfType(property.parent, "ObjectExpression")) return false;
46473
+ const propertyIndex = property.parent.properties.findIndex((objectProperty) => objectProperty === property);
46474
+ return property.parent.properties.slice(propertyIndex + 1).every((laterProperty) => {
46475
+ if (isNodeOfType(laterProperty, "SpreadElement")) return false;
46476
+ return !isStaticPropertyNamed(laterProperty, propertyName);
46477
+ });
46478
+ };
46479
+ const hasStorybookReactWebpackFastRefreshConfig = (packageDirectory) => {
46480
+ for (const configFilename of [
46481
+ "main.ts",
46482
+ "main.js",
46483
+ "main.mjs",
46484
+ "main.cjs"
46485
+ ]) {
46486
+ const program = parseSourceFile(path.join(packageDirectory, ".storybook", configFilename));
46487
+ if (!program) continue;
46488
+ const scopes = analyzeScopes(program);
46489
+ const exportedBindings = getExportedBindings(program, scopes);
46490
+ let hasFastRefresh = false;
46491
+ walkAst(program, (node) => {
46492
+ if (!isStaticPropertyNamed(node, "reactOptions") || !isNodeOfType(node, "Property")) return;
46493
+ if (!isExportedConfigProperty(node, exportedBindings, scopes)) return;
46494
+ if (!isLastStaticPropertyInObject(node, "reactOptions")) return;
46495
+ if (readLastStaticBooleanProperty(node.value, "fastRefresh", scopes) === true) {
46496
+ hasFastRefresh = true;
46497
+ return false;
46498
+ }
46499
+ });
46500
+ if (hasFastRefresh) return true;
46501
+ }
46502
+ return false;
46503
+ };
46504
+ const hasStorybookReactViteIntegration = (packageDirectory, manifest) => hasOwnedDevelopmentCommand(manifest, /(?:^|\s)(?:storybook\s+dev|start-storybook)(?:\s|$)/) && hasStorybookReactViteConfig(packageDirectory);
46505
+ const hasStorybookReactWebpackFastRefreshIntegration = (packageDirectory, manifest) => {
46506
+ const developmentCommandPattern = /(?:^|\s)(?:storybook\s+dev|start-storybook)(?:\s|$)/;
46507
+ if (!hasOwnedDevelopmentCommand(manifest, developmentCommandPattern)) return false;
46508
+ return isVersionAtLeast(getOwnedDependencyVersion(manifest, "@storybook/react", developmentCommandPattern) ?? getOwnedDependencyVersion(manifest, "@storybook/react-webpack5", developmentCommandPattern), MINIMUM_FAST_REFRESH_VERSIONS.storybookReact) && hasStorybookReactWebpackFastRefreshConfig(packageDirectory);
46509
+ };
46510
+ const hasNxStorybookReactViteIntegration = (packageDirectory) => {
46511
+ if (!hasStorybookReactViteConfig(packageDirectory)) return false;
46512
+ try {
46513
+ const project = JSON.parse(fs.readFileSync(path.join(packageDirectory, "project.json"), "utf8"));
46514
+ if (typeof project !== "object" || project === null) return false;
46515
+ const targets = Reflect.get(project, "targets");
46516
+ if (typeof targets !== "object" || targets === null) return false;
46517
+ return Object.keys(targets).some((targetName) => targetName === "storybook:serve:dev");
46518
+ } catch {
46519
+ return false;
46520
+ }
46521
+ };
46522
+ const getLocalFastRefreshStatus = (packageDirectory, manifest) => {
46523
+ const cachedStatus = cachedLocalStatusByManifest.get(manifest);
46524
+ if (cachedStatus) return cachedStatus;
46525
+ const status = getBuiltInStatus(manifest) ?? (hasStorybookReactViteIntegration(packageDirectory, manifest) || hasNxStorybookReactViteIntegration(packageDirectory) || hasStorybookReactWebpackFastRefreshIntegration(packageDirectory, manifest) ? {
46526
+ isActive: true,
46527
+ runtime: "generic"
46528
+ } : null) ?? getRegisteredIntegration(packageDirectory, manifest) ?? INACTIVE_STATUS;
46529
+ cachedLocalStatusByManifest.set(manifest, status);
46530
+ return status;
46531
+ };
46532
+ const isWorkspaceRoot = (directory, manifest) => {
46533
+ if (manifest?.workspaces !== void 0) return true;
46534
+ return [
46535
+ "pnpm-workspace.yaml",
46536
+ "pnpm-workspace.yml",
46537
+ "nx.json"
46538
+ ].some((filename) => fs.existsSync(path.join(directory, filename)));
46539
+ };
46540
+ const findWorkspaceRoot = (packageDirectory) => {
46541
+ let currentDirectory = packageDirectory;
46542
+ let workspaceRoot = null;
46543
+ while (true) {
46544
+ const manifest = readPackageManifest(currentDirectory);
46545
+ if (isWorkspaceRoot(currentDirectory, manifest)) workspaceRoot = currentDirectory;
46546
+ const parentDirectory = path.dirname(currentDirectory);
46547
+ if (parentDirectory === currentDirectory) return workspaceRoot;
46548
+ currentDirectory = parentDirectory;
46549
+ }
46550
+ };
46551
+ const collectWorkspacePackages = (workspaceRoot) => {
46552
+ const packages = [];
46553
+ const pendingDirectories = [workspaceRoot];
46554
+ while (pendingDirectories.length > 0) {
46555
+ const directory = pendingDirectories.pop();
46556
+ if (!directory) continue;
46557
+ const manifest = readPackageManifest(directory);
46558
+ if (manifest) packages.push({
46559
+ directory,
46560
+ manifest,
46561
+ status: getLocalFastRefreshStatus(directory, manifest)
46562
+ });
46563
+ let entries;
46564
+ try {
46565
+ entries = fs.readdirSync(directory, { withFileTypes: true });
46566
+ } catch {
46567
+ continue;
46568
+ }
46569
+ for (const entry of entries) {
46570
+ if (!entry.isDirectory()) continue;
46571
+ if (entry.name.startsWith(".") || WORKSPACE_IGNORED_DIRECTORY_NAMES.has(entry.name)) continue;
46572
+ pendingDirectories.push(path.join(directory, entry.name));
46573
+ }
46574
+ }
46575
+ return packages;
46576
+ };
46577
+ const isPropertyNamed = (node, name) => isNodeOfType(node, "Property") && (isNodeOfType(node.key, "Identifier") && node.key.name === name || isNodeOfType(node.key, "Literal") && node.key.value === name);
46578
+ const normalizeAliasRoot = (configDirectory, aliasPath) => {
46579
+ if (!aliasPath.startsWith(".") && !path.isAbsolute(aliasPath)) return null;
46580
+ const withoutPlaceholder = aliasPath.replace(/(?:\/)?(?:\*|\$\d+).*$/, "");
46581
+ return path.resolve(configDirectory, withoutPlaceholder);
46582
+ };
46583
+ const collectAliasRootsFromValue = (value, configDirectory, aliasRoots) => {
46584
+ walkAst(value, (node) => {
46585
+ if (!isPropertyNamed(node, "alias")) return;
46586
+ if (!isNodeOfType(node, "Property")) return;
46587
+ walkAst(node.value, (aliasNode) => {
46588
+ if (!isNodeOfType(aliasNode, "Literal") || typeof aliasNode.value !== "string") return;
46589
+ const aliasRoot = normalizeAliasRoot(configDirectory, aliasNode.value);
46590
+ if (aliasRoot) aliasRoots.add(aliasRoot);
46591
+ });
46592
+ return false;
46593
+ });
46594
+ };
46595
+ const getActivePackageAliasRoots = (packageDirectory, manifest) => {
46596
+ const aliasRoots = /* @__PURE__ */ new Set();
46597
+ const configPaths = [...getExplicitConfigPaths(packageDirectory, manifest), ...[
46598
+ "main.ts",
46599
+ "main.js",
46600
+ "main.mjs",
46601
+ "main.cjs"
46602
+ ].map((filename) => path.join(packageDirectory, ".storybook", filename))];
46603
+ for (const configPath of configPaths) {
46604
+ const program = parseSourceFile(configPath);
46605
+ if (!program) continue;
46606
+ const scopes = analyzeScopes(program);
46607
+ const exportedBindings = getExportedBindings(program, scopes);
46608
+ walkAst(program, (node) => {
46609
+ if (!isPropertyNamed(node, "resolve") && !isPropertyNamed(node, "viteFinal")) return;
46610
+ if (!isNodeOfType(node, "Property")) return;
46611
+ if (!isExportedConfigProperty(node, exportedBindings, scopes)) return;
46612
+ collectAliasRootsFromValue(node.value, path.dirname(configPath), aliasRoots);
46613
+ return false;
46614
+ });
46615
+ }
46616
+ return aliasRoots;
46617
+ };
46618
+ const collectStringValues = (value, values) => {
46619
+ if (typeof value === "string") {
46620
+ values.push(value);
46621
+ return;
46622
+ }
46623
+ if (Array.isArray(value)) {
46624
+ for (const entry of value) collectStringValues(entry, values);
46625
+ return;
46626
+ }
46627
+ if (typeof value !== "object" || value === null) return;
46628
+ for (const entry of Object.values(value)) collectStringValues(entry, values);
46629
+ };
46630
+ const hasSourceRuntimeEntry = (manifest) => {
46631
+ const entryValues = [];
46632
+ collectStringValues(manifest.exports, entryValues);
46633
+ collectStringValues(manifest.main, entryValues);
46634
+ collectStringValues(manifest.module, entryValues);
46635
+ return entryValues.some((entry) => /^\.\/?src(?:\/|\.|$)/.test(entry));
46636
+ };
46637
+ const getWorkspaceDependencyVersion = (manifest, dependencyName) => manifest.dependencies?.[dependencyName] ?? manifest.optionalDependencies?.[dependencyName];
46638
+ const buildWorkspaceFastRefreshIndex = (workspaceRoot, rootManifest) => {
46639
+ const cached = cachedWorkspaceIndexByManifest.get(rootManifest);
46640
+ if (cached) return cached;
46641
+ const workspacePackages = collectWorkspacePackages(workspaceRoot);
46642
+ const activePackages = workspacePackages.filter((workspacePackage) => workspacePackage.status.isActive);
46643
+ const aliasOwners = [];
46644
+ for (const activePackage of activePackages) for (const rootDirectory of getActivePackageAliasRoots(activePackage.directory, activePackage.manifest)) aliasOwners.push({
46645
+ rootDirectory,
46646
+ status: activePackage.status
46647
+ });
46648
+ const sourceEntryOwners = /* @__PURE__ */ new Map();
46649
+ for (const producerPackage of workspacePackages) {
46650
+ const packageName = producerPackage.manifest.name;
46651
+ if (typeof packageName !== "string" || !hasSourceRuntimeEntry(producerPackage.manifest)) continue;
46652
+ const owner = activePackages.find((activePackage) => {
46653
+ const dependencyVersion = getWorkspaceDependencyVersion(activePackage.manifest, packageName);
46654
+ return typeof dependencyVersion === "string" && dependencyVersion.startsWith("workspace:");
46655
+ });
46656
+ if (owner) sourceEntryOwners.set(producerPackage.directory, owner.status);
46657
+ }
46658
+ const index = {
46659
+ aliasOwners,
46660
+ sourceEntryOwners
46661
+ };
46662
+ cachedWorkspaceIndexByManifest.set(rootManifest, index);
46663
+ return index;
46664
+ };
46665
+ const isPathInside = (filePath, directory) => {
46666
+ const relativePath = path.relative(directory, filePath);
46667
+ return relativePath === "" || !relativePath.startsWith(`..${path.sep}`) && relativePath !== ".." && !path.isAbsolute(relativePath);
46668
+ };
46669
+ const getWorkspaceOwnedStatus = (filename, packageDirectory) => {
46670
+ const workspaceRoot = findWorkspaceRoot(packageDirectory);
46671
+ if (!workspaceRoot) return null;
46672
+ const rootManifest = readPackageManifest(workspaceRoot);
46673
+ if (!rootManifest) return null;
46674
+ const index = buildWorkspaceFastRefreshIndex(workspaceRoot, rootManifest);
46675
+ const aliasOwner = index.aliasOwners.find((owner) => isPathInside(filename, owner.rootDirectory));
46676
+ if (aliasOwner) return aliasOwner.status;
46677
+ for (const [producerDirectory, status] of index.sourceEntryOwners) if (isPathInside(filename, producerDirectory)) return status;
46678
+ return null;
46679
+ };
46680
+ const resolveFastRefreshFileStatus = (filename) => {
46681
+ const manifest = readNearestPackageManifest(filename);
46682
+ if (!manifest) return INACTIVE_STATUS;
46683
+ const packageDirectory = findNearestPackageDirectory(filename);
46684
+ if (!packageDirectory) return INACTIVE_STATUS;
46685
+ const localStatus = getLocalFastRefreshStatus(packageDirectory, manifest);
46686
+ if (localStatus.isActive) return localStatus;
46687
+ return getWorkspaceOwnedStatus(filename, packageDirectory) ?? INACTIVE_STATUS;
46688
+ };
46689
+ const getConfiguredFallbackStatus = (context) => {
46690
+ const framework = getReactDoctorStringSetting(context.settings, "framework");
46691
+ return {
46692
+ isActive: true,
46693
+ runtime: framework === "nextjs" ? "next" : framework === "expo" || framework === "react-native" ? "expo" : framework === "remix" ? "remix" : framework === "tanstack-start" ? "tanstack" : "generic"
46694
+ };
46695
+ };
46696
+ const getFastRefreshFileStatus = (context) => {
46697
+ let filename = context.filename;
46698
+ if (!filename) return getConfiguredFallbackStatus(context);
46699
+ if (!path.isAbsolute(filename)) {
46700
+ const absoluteFilename = path.resolve(filename);
46701
+ if (!fs.existsSync(absoluteFilename)) return getConfiguredFallbackStatus(context);
46702
+ filename = absoluteFilename;
46703
+ }
46704
+ if (!readNearestPackageManifest(filename)) return INACTIVE_STATUS;
46705
+ return resolveFastRefreshFileStatus(filename);
45760
46706
  };
45761
46707
  //#endregion
45762
46708
  //#region src/plugin/rules/react-builtins/only-export-components-tables.ts
@@ -45768,6 +46714,7 @@ const NOT_REACT_COMPONENT_EXPRESSION_TYPES = new Set([
45768
46714
  "ConditionalExpression",
45769
46715
  "Literal",
45770
46716
  "LogicalExpression",
46717
+ "NewExpression",
45771
46718
  "ObjectExpression",
45772
46719
  "TemplateLiteral",
45773
46720
  "ThisExpression",
@@ -45786,124 +46733,25 @@ const NON_FAST_REFRESH_PATH_SEGMENTS = [
45786
46733
  "/cypress/",
45787
46734
  "/.storybook/",
45788
46735
  "/stories/",
45789
- "/__stories__/",
45790
- "/playground/",
45791
- "/playgrounds/",
45792
- "/examples/",
45793
- "/example/",
45794
- "/demo/",
45795
- "/demos/",
45796
- "/sandbox/",
45797
- "/sandboxes/"
46736
+ "/__stories__/"
45798
46737
  ];
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([
46738
+ const TANSTACK_ROUTE_FACTORY_CALLEE_NAMES = new Set([
45893
46739
  ...TANSTACK_ROUTE_CREATION_FUNCTIONS,
45894
46740
  "createLazyFileRoute",
45895
46741
  "createLazyRoute",
45896
46742
  "createAPIFileRoute",
45897
46743
  "createServerFileRoute",
45898
46744
  "createServerRootRoute",
45899
- "createServerRoute",
46745
+ "createServerRoute"
46746
+ ]);
46747
+ const REACT_ROUTER_FACTORY_CALLEE_NAMES = new Set([
45900
46748
  "createBrowserRouter",
45901
46749
  "createHashRouter",
45902
46750
  "createMemoryRouter",
45903
46751
  "createStaticRouter",
45904
46752
  "createRouter"
45905
46753
  ]);
45906
- const ROUTE_MODULE_ALLOWED_EXPORT_NAMES = new Set([
46754
+ const REACT_ROUTER_ALLOWED_EXPORT_NAMES = new Set([
45907
46755
  "loader",
45908
46756
  "clientLoader",
45909
46757
  "action",
@@ -45914,7 +46762,9 @@ const ROUTE_MODULE_ALLOWED_EXPORT_NAMES = new Set([
45914
46762
  "handle",
45915
46763
  "shouldRevalidate",
45916
46764
  "middleware",
45917
- "unstable_middleware",
46765
+ "unstable_middleware"
46766
+ ]);
46767
+ const NEXT_ALLOWED_EXPORT_NAMES = new Set([
45918
46768
  "getServerSideProps",
45919
46769
  "getStaticProps",
45920
46770
  "getStaticPaths",
@@ -45934,9 +46784,9 @@ const ROUTE_MODULE_ALLOWED_EXPORT_NAMES = new Set([
45934
46784
  "runtime",
45935
46785
  "preferredRegion",
45936
46786
  "maxDuration",
45937
- "experimental_ppr",
45938
- "unstable_settings"
46787
+ "experimental_ppr"
45939
46788
  ]);
46789
+ const EXPO_ALLOWED_EXPORT_NAMES = new Set(["unstable_settings"]);
45940
46790
  //#endregion
45941
46791
  //#region src/plugin/rules/react-builtins/only-export-components.ts
45942
46792
  const NAMED_EXPORT_MESSAGE = "This file exports non-components, so Fast Refresh can't safely preserve component state.";
@@ -45949,6 +46799,8 @@ const DEFAULT_REACT_HOCS = [
45949
46799
  "forwardRef",
45950
46800
  "lazy"
45951
46801
  ];
46802
+ const EMPTY_NAME_SET = /* @__PURE__ */ new Set();
46803
+ const TEST_SUPPORT_FILE_PATTERN = /(?:^|\/)(?:test|spec)(?:[-_.]?(?:utils?|helpers?|setup|fixtures?))?\.(?:jsx?|tsx?)$/i;
45952
46804
  const resolveSettings$6 = (settings) => {
45953
46805
  const reactDoctor = settings?.["react-doctor"];
45954
46806
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.onlyExportComponents ?? {} : {};
@@ -45972,13 +46824,12 @@ const isReactCreateContext = (initializer) => {
45972
46824
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "createContext") return true;
45973
46825
  return false;
45974
46826
  };
45975
- const isRouteFactoryName = (name) => ROUTE_FACTORY_CALLEE_NAMES.has(name);
45976
- const isRouteFactoryCall = (expression) => {
46827
+ const isRouteFactoryCall = (expression, bindings) => {
45977
46828
  let currentCall = expression;
45978
46829
  while (isNodeOfType(currentCall, "CallExpression")) {
45979
46830
  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;
46831
+ if (isNodeOfType(callee, "Identifier") && bindings.localNames.has(callee.name)) return true;
46832
+ 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
46833
  if (!isNodeOfType(callee, "CallExpression")) return false;
45983
46834
  currentCall = callee;
45984
46835
  }
@@ -46006,10 +46857,11 @@ const isHocCallee = (callee, state) => {
46006
46857
  const canBeReactFunctionComponent = (initializer, state) => {
46007
46858
  if (!initializer) return false;
46008
46859
  const expression = skipTsExpression(initializer);
46009
- if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression")) return true;
46860
+ if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression")) return functionHasReactRenderSemantics(expression, state);
46010
46861
  if (isNodeOfType(expression, "CallExpression")) return isHocCallee(expression.callee, state);
46011
46862
  return false;
46012
46863
  };
46864
+ const functionHasReactRenderSemantics = (functionNode, state) => functionContainsReactRenderOutput(functionNode, state.scopes, state.controlFlow) || functionHasReactElementReturnType(functionNode) || functionReturnsOnlyNull(functionNode);
46013
46865
  const isReactComponentInitializer = (expression, state) => {
46014
46866
  const stripped = skipTsExpression(expression);
46015
46867
  if (isNodeOfType(stripped, "ArrowFunctionExpression")) return true;
@@ -46018,6 +46870,60 @@ const isReactComponentInitializer = (expression, state) => {
46018
46870
  if (isNodeOfType(stripped, "CallExpression") && isHocCallee(stripped.callee, state) && stripped.arguments.length > 0) return true;
46019
46871
  return false;
46020
46872
  };
46873
+ const isNextDynamicCall = (expression, nextDynamicImportSymbolIds, scopes) => {
46874
+ const stripped = skipTsExpression(expression);
46875
+ if (!isNodeOfType(stripped, "CallExpression")) return false;
46876
+ const callee = skipTsExpression(stripped.callee);
46877
+ if (!isNodeOfType(callee, "Identifier")) return false;
46878
+ const symbol = scopes.symbolFor(callee);
46879
+ return symbol !== null && nextDynamicImportSymbolIds.has(symbol.id);
46880
+ };
46881
+ const functionReturnsNextDynamicComponent = (expression, nextDynamicImportSymbolIds, scopes) => {
46882
+ const stripped = skipTsExpression(expression);
46883
+ if (!isNodeOfType(stripped, "ArrowFunctionExpression") && !isNodeOfType(stripped, "FunctionExpression") && !isNodeOfType(stripped, "FunctionDeclaration")) return false;
46884
+ const body = stripped.body;
46885
+ if (!isNodeOfType(body, "BlockStatement")) return isNextDynamicCall(body, nextDynamicImportSymbolIds, scopes);
46886
+ if (body.body.length !== 1) return false;
46887
+ const statement = body.body[0];
46888
+ return Boolean(statement) && isNodeOfType(statement, "ReturnStatement") && Boolean(statement.argument) && isNextDynamicCall(statement.argument, nextDynamicImportSymbolIds, scopes);
46889
+ };
46890
+ const isComponentFactoryCall = (expression, state) => {
46891
+ const stripped = skipTsExpression(expression);
46892
+ if (!isNodeOfType(stripped, "CallExpression")) return false;
46893
+ const callee = skipTsExpression(stripped.callee);
46894
+ if (!isNodeOfType(callee, "Identifier")) return false;
46895
+ const symbol = state.scopes.symbolFor(callee);
46896
+ return symbol !== null && state.componentFactorySymbolIds.has(symbol.id);
46897
+ };
46898
+ const isProvenComponentValue = (expression, state, inspectedSymbolIds = /* @__PURE__ */ new Set()) => {
46899
+ const stripped = skipTsExpression(expression);
46900
+ if (isNodeOfType(stripped, "Identifier")) {
46901
+ const symbol = state.scopes.symbolFor(stripped);
46902
+ if (!symbol) return false;
46903
+ if (state.localComponentNames.has(stripped.name)) return symbol.references.every((reference) => reference.flag === "read");
46904
+ if (isReactComponentName(stripped.name) && symbol.kind === "import") return true;
46905
+ if (inspectedSymbolIds.has(symbol.id)) return false;
46906
+ const initializer = getDirectUnreassignedInitializer(symbol);
46907
+ if (!initializer) return false;
46908
+ const strippedInitializer = skipTsExpression(initializer);
46909
+ if (isNodeOfType(strippedInitializer, "ArrowFunctionExpression") || isNodeOfType(strippedInitializer, "FunctionExpression")) return false;
46910
+ return isProvenComponentValue(initializer, state, new Set([...inspectedSymbolIds, symbol.id]));
46911
+ }
46912
+ if (isNodeOfType(stripped, "MemberExpression") && !stripped.computed && isNodeOfType(stripped.object, "Identifier") && isNodeOfType(stripped.property, "Identifier") && isReactComponentName(stripped.property.name)) {
46913
+ const objectSymbol = state.scopes.symbolFor(stripped.object);
46914
+ return objectSymbol !== null && state.importSymbolIds.has(objectSymbol.id);
46915
+ }
46916
+ if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "FunctionExpression")) return functionHasReactRenderSemantics(stripped, state);
46917
+ if (!isNodeOfType(stripped, "CallExpression")) return false;
46918
+ if (!isHocCallee(stripped.callee, state)) return false;
46919
+ return stripped.arguments.some((argument) => isProvenComponentValue(argument, state));
46920
+ };
46921
+ const isDirectRefreshWrapperCall = (call, state) => {
46922
+ const callee = skipTsExpression(call.callee);
46923
+ if (isHocCallee(callee, state)) return call.arguments.length > 0;
46924
+ if (!isNodeOfType(callee, "Identifier") && !isNodeOfType(callee, "MemberExpression")) return false;
46925
+ return call.arguments.some((argument) => isProvenComponentValue(argument, state));
46926
+ };
46021
46927
  const objectExpressionBundlesComponents = (objectExpression, state) => {
46022
46928
  for (const property of objectExpression.properties ?? []) {
46023
46929
  if (!isNodeOfType(property, "Property")) continue;
@@ -46027,20 +46933,22 @@ const objectExpressionBundlesComponents = (objectExpression, state) => {
46027
46933
  continue;
46028
46934
  }
46029
46935
  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;
46936
+ if ((isNodeOfType(value, "ArrowFunctionExpression") || isNodeOfType(value, "FunctionExpression")) && functionHasReactRenderSemantics(value, state)) return true;
46031
46937
  if (isNodeOfType(value, "CallExpression") && isHocCallee(value.callee, state) && value.arguments.length > 0) return true;
46032
46938
  }
46033
46939
  return false;
46034
46940
  };
46035
46941
  const classifyExport = (name, reportNode, isFunction, initializer, state) => {
46942
+ if (isNodeOfType(reportNode, "Identifier") && isProvenComponentValue(reportNode, state)) return { kind: "react-component" };
46036
46943
  if (initializer) {
46037
46944
  const expression = skipTsExpression(initializer);
46038
- if (isRouteFactoryCall(expression)) return { kind: "react-component" };
46945
+ if (isNodeOfType(expression, "CallExpression") && isReactComponentName(name) && isComponentFactoryCall(expression, state)) return { kind: "react-component" };
46946
+ if (isRouteFactoryCall(expression, state.routeFactoryBindings)) return { kind: "react-component" };
46039
46947
  if (isNodeOfType(expression, "CallExpression") && isHocCallee(expression.callee, state) && expression.arguments.length > 0 && isReactComponentName(name)) return { kind: "react-component" };
46040
46948
  if (isNodeOfType(expression, "ConditionalExpression") && isReactComponentName(name) && isReactComponentInitializer(expression.consequent, state) && isReactComponentInitializer(expression.alternate, state)) return { kind: "react-component" };
46041
46949
  }
46042
46950
  if (state.allowExportNames.has(name)) return { kind: "allowed" };
46043
- if (ROUTE_MODULE_ALLOWED_EXPORT_NAMES.has(name)) return { kind: "allowed" };
46951
+ if (state.allowedRouteExportNames.has(name)) return { kind: "allowed" };
46044
46952
  if (/^use[A-Z]/.test(name)) return { kind: "allowed" };
46045
46953
  if (state.allowConstantExport && initializer) {
46046
46954
  const expression = skipTsExpression(initializer);
@@ -46066,42 +46974,33 @@ const classifyExport = (name, reportNode, isFunction, initializer, state) => {
46066
46974
  kind: "namespace-object",
46067
46975
  reportNode
46068
46976
  };
46977
+ if (isNodeOfType(stripped, "MemberExpression")) return isProvenComponentValue(stripped, state) ? { kind: "react-component" } : {
46978
+ kind: "non-component",
46979
+ reportNode
46980
+ };
46981
+ if (isNodeOfType(stripped, "Identifier")) return isProvenComponentValue(stripped, state) ? { kind: "react-component" } : {
46982
+ kind: "non-component",
46983
+ reportNode
46984
+ };
46069
46985
  if (NOT_REACT_COMPONENT_EXPRESSION_TYPES.has(stripped.type)) return {
46070
46986
  kind: "non-component",
46071
46987
  reportNode
46072
46988
  };
46989
+ if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "FunctionExpression")) return {
46990
+ kind: "non-component",
46991
+ reportNode
46992
+ };
46073
46993
  }
46074
46994
  return isReactComponentName(name) ? { kind: "react-component" } : {
46075
46995
  kind: "non-component",
46076
46996
  reportNode
46077
46997
  };
46078
46998
  };
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
46999
  const isFileNameAllowed = (filename, checkJS) => {
46099
47000
  if (!filename) return true;
47001
+ if (TEST_SUPPORT_FILE_PATTERN.test(filename)) return false;
46100
47002
  if (filename.includes(".test.") || filename.includes(".spec.") || filename.includes(".cy.") || filename.includes(".stories.")) return false;
46101
47003
  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
47004
  if (filename.endsWith(".tsx") || filename.endsWith(".jsx")) return true;
46106
47005
  if (checkJS && filename.endsWith(".js")) return true;
46107
47006
  return false;
@@ -46114,16 +47013,100 @@ const onlyExportComponents = defineRule({
46114
47013
  category: "Architecture",
46115
47014
  create: (context) => {
46116
47015
  const settings = resolveSettings$6(context.settings);
46117
- if (!isFileNameAllowed(normalizeFilename(context.filename ?? ""), settings.checkJS)) return {};
47016
+ const filename = normalizeFilename(context.filename ?? "");
47017
+ const fastRefreshStatus = getFastRefreshFileStatus(context);
47018
+ if (!fastRefreshStatus.isActive) return {};
47019
+ if (isFrameworkRouteOrSpecialFilename(context, fastRefreshStatus.runtime)) return {};
47020
+ if (!isFileNameAllowed(filename, settings.checkJS)) return {};
47021
+ 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;
47022
+ 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;
47023
+ const routeFactoryLocalNames = /* @__PURE__ */ new Set();
47024
+ const routeFactoryNamespaceNames = /* @__PURE__ */ new Set();
47025
+ const nextDynamicImportSymbolIds = /* @__PURE__ */ new Set();
47026
+ const importSymbolIds = /* @__PURE__ */ new Set();
47027
+ const routeFactoryBindings = {
47028
+ localNames: routeFactoryLocalNames,
47029
+ memberNames: routeFactoryMemberNames,
47030
+ namespaceNames: routeFactoryNamespaceNames
47031
+ };
46118
47032
  const exportNodes = [];
46119
47033
  const componentCandidates = [];
47034
+ const createRootNames = /* @__PURE__ */ new Set();
47035
+ const hydrateRootNames = /* @__PURE__ */ new Set();
47036
+ const legacyRenderNames = /* @__PURE__ */ new Set();
47037
+ const reactDomNamespaceNames = /* @__PURE__ */ new Set();
47038
+ const rootNames = /* @__PURE__ */ new Set();
47039
+ let hasRootMount = false;
47040
+ const isCreateRootCall = (node) => {
47041
+ if (!isNodeOfType(node, "CallExpression")) return false;
47042
+ if (isNodeOfType(node.callee, "Identifier")) return createRootNames.has(node.callee.name);
47043
+ 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";
47044
+ };
47045
+ const visitImportDeclaration = (node) => {
47046
+ if (!isNodeOfType(node, "ImportDeclaration")) return;
47047
+ const source = node.source.value;
47048
+ for (const specifier of node.specifiers) {
47049
+ const symbol = context.scopes.symbolFor(specifier.local);
47050
+ if (symbol) importSymbolIds.add(symbol.id);
47051
+ }
47052
+ 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) {
47053
+ if (isNodeOfType(specifier, "ImportNamespaceSpecifier")) {
47054
+ routeFactoryNamespaceNames.add(specifier.local.name);
47055
+ continue;
47056
+ }
47057
+ const importedName = getImportedName(specifier);
47058
+ if (importedName && routeFactoryMemberNames.has(importedName)) routeFactoryLocalNames.add(specifier.local.name);
47059
+ }
47060
+ if (source === "next/dynamic") for (const specifier of node.specifiers) {
47061
+ if (!isNodeOfType(specifier, "ImportDefaultSpecifier")) continue;
47062
+ const symbol = context.scopes.symbolFor(specifier.local);
47063
+ if (symbol) nextDynamicImportSymbolIds.add(symbol.id);
47064
+ }
47065
+ if (source !== "react-dom" && source !== "react-dom/client") return;
47066
+ for (const specifier of node.specifiers) {
47067
+ if (isNodeOfType(specifier, "ImportDefaultSpecifier") || isNodeOfType(specifier, "ImportNamespaceSpecifier")) {
47068
+ reactDomNamespaceNames.add(specifier.local.name);
47069
+ continue;
47070
+ }
47071
+ const importedName = getImportedName(specifier);
47072
+ if (importedName === "createRoot") createRootNames.add(specifier.local.name);
47073
+ if (importedName === "hydrateRoot") hydrateRootNames.add(specifier.local.name);
47074
+ if (source === "react-dom" && (importedName === "render" || importedName === "hydrate")) legacyRenderNames.add(specifier.local.name);
47075
+ }
47076
+ };
47077
+ const visitCallExpression = (node) => {
47078
+ if (!isNodeOfType(node, "CallExpression")) return;
47079
+ if (isInsideFunctionScope(node)) return;
47080
+ if (isNodeOfType(node.callee, "Identifier")) {
47081
+ if (hydrateRootNames.has(node.callee.name) || legacyRenderNames.has(node.callee.name)) hasRootMount = true;
47082
+ return;
47083
+ }
47084
+ if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier")) return;
47085
+ const methodName = node.callee.property.name;
47086
+ if (isNodeOfType(node.callee.object, "Identifier") && reactDomNamespaceNames.has(node.callee.object.name) && (methodName === "render" || methodName === "hydrate" || methodName === "hydrateRoot")) {
47087
+ hasRootMount = true;
47088
+ return;
47089
+ }
47090
+ if (methodName !== "render") return;
47091
+ if (isCreateRootCall(node.callee.object)) {
47092
+ hasRootMount = true;
47093
+ return;
47094
+ }
47095
+ if (isNodeOfType(node.callee.object, "Identifier") && rootNames.has(node.callee.object.name)) hasRootMount = true;
47096
+ };
46120
47097
  const pushExportNode = (node) => {
46121
47098
  exportNodes.push(node);
46122
47099
  };
46123
47100
  const pushComponentCandidate = (node) => {
46124
47101
  componentCandidates.push(node);
47102
+ if (isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && node.init && isCreateRootCall(node.init) && !isInsideFunctionScope(node)) rootNames.add(node.id.name);
46125
47103
  };
46126
47104
  return {
47105
+ ImportDeclaration: visitImportDeclaration,
47106
+ CallExpression: visitCallExpression,
47107
+ AssignmentExpression(node) {
47108
+ if (isNodeOfType(node.left, "Identifier") && rootNames.has(node.left.name)) rootNames.delete(node.left.name);
47109
+ },
46127
47110
  ExportAllDeclaration: pushExportNode,
46128
47111
  ExportDefaultDeclaration: pushExportNode,
46129
47112
  ExportNamedDeclaration: pushExportNode,
@@ -46131,31 +47114,53 @@ const onlyExportComponents = defineRule({
46131
47114
  VariableDeclarator: pushComponentCandidate,
46132
47115
  ClassDeclaration: pushComponentCandidate,
46133
47116
  "Program:exit"() {
47117
+ if (hasRootMount) return;
46134
47118
  const localComponentNames = /* @__PURE__ */ new Set();
47119
+ const componentFactorySymbolIds = /* @__PURE__ */ new Set();
47120
+ for (const child of componentCandidates) {
47121
+ if (isInsideFunctionScope(child)) continue;
47122
+ if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
47123
+ if (functionReturnsNextDynamicComponent(child, nextDynamicImportSymbolIds, context.scopes)) {
47124
+ const symbol = context.scopes.symbolFor(child.id);
47125
+ if (symbol?.references.every((reference) => reference.flag === "read")) componentFactorySymbolIds.add(symbol.id);
47126
+ }
47127
+ continue;
47128
+ }
47129
+ if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier") && child.init && functionReturnsNextDynamicComponent(child.init, nextDynamicImportSymbolIds, context.scopes)) {
47130
+ const symbol = context.scopes.symbolFor(child.id);
47131
+ if (symbol?.references.every((reference) => reference.flag === "read")) componentFactorySymbolIds.add(symbol.id);
47132
+ }
47133
+ }
46135
47134
  const state = {
46136
47135
  customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
46137
47136
  allowExportNames: new Set(settings.allowExportNames),
46138
47137
  allowConstantExport: settings.allowConstantExport,
47138
+ allowedRouteExportNames,
47139
+ routeFactoryBindings,
47140
+ componentFactorySymbolIds,
47141
+ importSymbolIds,
46139
47142
  localComponentNames,
46140
47143
  scopes: context.scopes,
46141
47144
  controlFlow: context.cfg
46142
47145
  };
46143
47146
  for (const child of componentCandidates) {
46144
47147
  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);
47148
+ if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child) && functionHasReactRenderSemantics(child, state)) localComponentNames.add(child.id.name);
46146
47149
  }
46147
47150
  if (isNodeOfType(child, "ClassDeclaration") && child.id) {
46148
47151
  if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
46149
47152
  }
46150
47153
  if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
46151
47154
  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);
47155
+ const expression = initializer ? skipTsExpression(initializer) : null;
47156
+ const isDirectFunction = expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"));
47157
+ if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (expression ? isEs6Component(expression) : false)) && !isInsideFunctionScope(child)) {
47158
+ if (!isDirectFunction || functionHasReactRenderSemantics(expression, state)) localComponentNames.add(child.id.name);
46155
47159
  }
46156
47160
  }
46157
47161
  }
46158
47162
  const exports = [];
47163
+ const exportAllNodes = [];
46159
47164
  let hasReactExport = false;
46160
47165
  let hasAnyExports = false;
46161
47166
  const isExportedNodeIds = /* @__PURE__ */ new WeakSet();
@@ -46163,10 +47168,8 @@ const onlyExportComponents = defineRule({
46163
47168
  if (isNodeOfType(child, "ExportAllDeclaration")) {
46164
47169
  if (child.exportKind === "type") continue;
46165
47170
  hasAnyExports = true;
46166
- context.report({
46167
- node: child,
46168
- message: EXPORT_ALL_MESSAGE
46169
- });
47171
+ const source = child.source.value;
47172
+ if (typeof source !== "string" || exportAllAddsRuntimeValues(filename, source)) exportAllNodes.push(child);
46170
47173
  continue;
46171
47174
  }
46172
47175
  if (isNodeOfType(child, "ExportDefaultDeclaration")) {
@@ -46174,17 +47177,24 @@ const onlyExportComponents = defineRule({
46174
47177
  const declaration = child.declaration;
46175
47178
  const stripped = skipTsExpression(declaration);
46176
47179
  if (isNodeOfType(stripped, "FunctionDeclaration") || isNodeOfType(stripped, "FunctionExpression")) {
47180
+ const hasRenderOutput = functionHasReactRenderSemantics(stripped, state);
46177
47181
  if (stripped.id) {
46178
47182
  const idNode = stripped.id;
46179
47183
  isExportedNodeIds.add(stripped);
46180
- exports.push(classifyExport(idNode.name, idNode, true, null, state));
46181
- } else {
47184
+ exports.push(hasRenderOutput ? classifyExport(idNode.name, idNode, true, null, state) : {
47185
+ kind: "non-component",
47186
+ reportNode: idNode
47187
+ });
47188
+ } else if (hasRenderOutput) {
46182
47189
  context.report({
46183
47190
  node: stripped,
46184
47191
  message: ANONYMOUS_MESSAGE
46185
47192
  });
46186
47193
  hasReactExport = true;
46187
- }
47194
+ } else exports.push({
47195
+ kind: "non-component",
47196
+ reportNode: stripped
47197
+ });
46188
47198
  continue;
46189
47199
  }
46190
47200
  if (isNodeOfType(stripped, "ClassDeclaration") || isNodeOfType(stripped, "ClassExpression")) {
@@ -46203,26 +47213,34 @@ const onlyExportComponents = defineRule({
46203
47213
  continue;
46204
47214
  }
46205
47215
  if (isNodeOfType(stripped, "Identifier")) {
46206
- exports.push(classifyExport(stripped.name, stripped, false, null, state));
47216
+ exports.push(isProvenComponentValue(stripped, state) ? { kind: "react-component" } : {
47217
+ kind: "non-component",
47218
+ reportNode: stripped
47219
+ });
47220
+ continue;
47221
+ }
47222
+ if (isNodeOfType(stripped, "MemberExpression")) {
47223
+ if (isProvenComponentValue(stripped, state)) hasReactExport = true;
47224
+ else exports.push({
47225
+ kind: "non-component",
47226
+ reportNode: stripped
47227
+ });
46207
47228
  continue;
46208
47229
  }
46209
47230
  if (isNodeOfType(stripped, "CallExpression")) {
46210
- if (isRouteFactoryCall(stripped)) {
47231
+ if (isRouteFactoryCall(stripped, state.routeFactoryBindings)) {
46211
47232
  hasReactExport = true;
46212
47233
  continue;
46213
47234
  }
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({
47235
+ if (isReactCreateContext(stripped)) {
47236
+ exports.push({
47237
+ kind: "react-context",
47238
+ reportNode: stripped
47239
+ });
47240
+ continue;
47241
+ }
47242
+ if (isDirectRefreshWrapperCall(stripped, state)) hasReactExport = true;
47243
+ else if (isConfigOnlyFactoryCall(stripped)) exports.push({
46226
47244
  kind: "non-component",
46227
47245
  reportNode: stripped
46228
47246
  });
@@ -46233,16 +47251,32 @@ const onlyExportComponents = defineRule({
46233
47251
  continue;
46234
47252
  }
46235
47253
  if (isNodeOfType(stripped, "ObjectExpression")) {
46236
- context.report({
46237
- node: stripped,
46238
- message: objectExpressionBundlesComponents(stripped, state) ? NAMESPACE_OBJECT_MESSAGE : ANONYMOUS_MESSAGE
47254
+ exports.push(objectExpressionBundlesComponents(stripped, state) ? {
47255
+ kind: "namespace-object",
47256
+ reportNode: stripped
47257
+ } : {
47258
+ kind: "non-component",
47259
+ reportNode: stripped
46239
47260
  });
46240
47261
  continue;
46241
47262
  }
46242
- if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "Literal")) {
46243
- context.report({
46244
- node: stripped,
46245
- message: ANONYMOUS_MESSAGE
47263
+ if (isNodeOfType(stripped, "ArrowFunctionExpression")) {
47264
+ if (functionHasReactRenderSemantics(stripped, state)) {
47265
+ context.report({
47266
+ node: stripped,
47267
+ message: ANONYMOUS_MESSAGE
47268
+ });
47269
+ hasReactExport = true;
47270
+ } else exports.push({
47271
+ kind: "non-component",
47272
+ reportNode: stripped
47273
+ });
47274
+ continue;
47275
+ }
47276
+ if (isNodeOfType(stripped, "Literal") || isNodeOfType(stripped, "NewExpression")) {
47277
+ exports.push({
47278
+ kind: "non-component",
47279
+ reportNode: stripped
46246
47280
  });
46247
47281
  continue;
46248
47282
  }
@@ -46259,7 +47293,10 @@ const onlyExportComponents = defineRule({
46259
47293
  const declaration = child.declaration;
46260
47294
  if (isNodeOfType(declaration, "FunctionDeclaration") && declaration.id) {
46261
47295
  isExportedNodeIds.add(declaration);
46262
- exports.push(classifyExport(declaration.id.name, declaration.id, true, null, state));
47296
+ exports.push(functionHasReactRenderSemantics(declaration, state) || localComponentNames.has(declaration.id.name) ? classifyExport(declaration.id.name, declaration.id, true, null, state) : {
47297
+ kind: "non-component",
47298
+ reportNode: declaration.id
47299
+ });
46263
47300
  } else if (isNodeOfType(declaration, "ClassDeclaration") && declaration.id) {
46264
47301
  isExportedNodeIds.add(declaration);
46265
47302
  if (isReactComponentName(declaration.id.name) && isEs6Component(declaration)) exports.push({ kind: "react-component" });
@@ -46283,6 +47320,7 @@ const onlyExportComponents = defineRule({
46283
47320
  const isReExportFromSource = Boolean(child.source);
46284
47321
  for (const specifier of child.specifiers ?? []) {
46285
47322
  if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
47323
+ if (specifier.exportKind === "type") continue;
46286
47324
  const exported = specifier.exported;
46287
47325
  const local = specifier.local;
46288
47326
  let exportedName = null;
@@ -46290,8 +47328,13 @@ const onlyExportComponents = defineRule({
46290
47328
  const localName = local && isNodeOfType(local, "Identifier") ? local.name : null;
46291
47329
  const reportNode = specifier;
46292
47330
  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);
47331
+ if (localName && localComponentNames.has(localName)) entry = { kind: "react-component" };
47332
+ else if (!isReExportFromSource && localName === exportedName && localName !== null && isReactComponentName(localName)) entry = { kind: "react-component" };
47333
+ else if (exportedName === "default" && localName && local) entry = isProvenComponentValue(local, state) ? { kind: "react-component" } : {
47334
+ kind: "non-component",
47335
+ reportNode
47336
+ };
47337
+ else if (exportedName) entry = classifyExport(exportedName, reportNode, false, isReExportFromSource ? null : local, state);
46295
47338
  else {
46296
47339
  entry = {
46297
47340
  kind: "non-component",
@@ -46309,15 +47352,21 @@ const onlyExportComponents = defineRule({
46309
47352
  node: entry.reportNode,
46310
47353
  message: NAMESPACE_OBJECT_MESSAGE
46311
47354
  });
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
47355
+ if (hasAnyExports && hasReactExport) {
47356
+ for (const exportAllNode of exportAllNodes) context.report({
47357
+ node: exportAllNode,
47358
+ message: EXPORT_ALL_MESSAGE
46320
47359
  });
47360
+ for (const entry of exports) {
47361
+ if (entry.kind === "non-component") context.report({
47362
+ node: entry.reportNode,
47363
+ message: NAMED_EXPORT_MESSAGE
47364
+ });
47365
+ if (entry.kind === "react-context") context.report({
47366
+ node: entry.reportNode,
47367
+ message: REACT_CONTEXT_MESSAGE
47368
+ });
47369
+ }
46321
47370
  }
46322
47371
  }
46323
47372
  };
@@ -50966,6 +52015,357 @@ const rerenderMemoBeforeEarlyReturn = defineRule({
50966
52015
  }
50967
52016
  });
50968
52017
  //#endregion
52018
+ //#region src/plugin/utils/build-same-file-memo-comparator-registry.ts
52019
+ const unwrapTopLevelDeclaration = (statement) => {
52020
+ if (isNodeOfType(statement, "ExportNamedDeclaration")) return statement.declaration;
52021
+ return statement;
52022
+ };
52023
+ const buildSameFileMemoComparatorRegistry = (program) => {
52024
+ const registry = /* @__PURE__ */ new Map();
52025
+ if (!isNodeOfType(program, "Program")) return registry;
52026
+ const memoBindings = /* @__PURE__ */ new Set();
52027
+ const reactNamespaceBindings = /* @__PURE__ */ new Set();
52028
+ for (const statement of program.body) {
52029
+ if (!isNodeOfType(statement, "ImportDeclaration") || statement.source.value !== "react") continue;
52030
+ for (const specifier of statement.specifiers) if (isNodeOfType(specifier, "ImportDefaultSpecifier") || isNodeOfType(specifier, "ImportNamespaceSpecifier")) reactNamespaceBindings.add(specifier.local.name);
52031
+ else if (isNodeOfType(specifier, "ImportSpecifier") && getImportedName(specifier) === "memo") memoBindings.add(specifier.local.name);
52032
+ }
52033
+ for (const statement of program.body) {
52034
+ const declaration = unwrapTopLevelDeclaration(statement);
52035
+ if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) continue;
52036
+ for (const declarator of declaration.declarations ?? []) {
52037
+ if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
52038
+ const initializer = stripParenExpression(declarator.init);
52039
+ if (!isNodeOfType(initializer, "CallExpression")) continue;
52040
+ const callee = stripParenExpression(initializer.callee);
52041
+ const isNamedMemo = isNodeOfType(callee, "Identifier") && memoBindings.has(callee.name);
52042
+ const isNamespaceMemo = isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier") && reactNamespaceBindings.has(callee.object.name) && isNodeOfType(callee.property, "Identifier") && callee.property.name === "memo";
52043
+ if (!isNamedMemo && !isNamespaceMemo) continue;
52044
+ const comparator = initializer.arguments?.[1];
52045
+ if (!comparator || isNodeOfType(comparator, "SpreadElement")) continue;
52046
+ registry.set(declarator.id.name, {
52047
+ bindingIdentifier: declarator.id,
52048
+ comparator
52049
+ });
52050
+ }
52051
+ }
52052
+ return registry;
52053
+ };
52054
+ //#endregion
52055
+ //#region src/plugin/utils/comparator-proves-empty-prop-equality.ts
52056
+ const UNKNOWN_VALUE = { kind: "unknown" };
52057
+ const TRUE_VALUE = {
52058
+ kind: "boolean",
52059
+ value: true
52060
+ };
52061
+ const FALSE_VALUE = {
52062
+ kind: "boolean",
52063
+ value: false
52064
+ };
52065
+ const UNDEFINED_VALUE = { kind: "undefined" };
52066
+ const PREVIOUS_PROPS_VALUE = { kind: "previous-props" };
52067
+ const NEXT_PROPS_VALUE = { kind: "next-props" };
52068
+ const OBJECT_PROTOTYPE_PROPERTY_NAMES = new Set([
52069
+ "__defineGetter__",
52070
+ "__defineSetter__",
52071
+ "__lookupGetter__",
52072
+ "__lookupSetter__",
52073
+ "__proto__",
52074
+ "constructor",
52075
+ "hasOwnProperty",
52076
+ "isPrototypeOf",
52077
+ "propertyIsEnumerable",
52078
+ "toLocaleString",
52079
+ "toString",
52080
+ "valueOf"
52081
+ ]);
52082
+ const PROVABLY_CALLABLE_GLOBAL_NAMES = new Set([
52083
+ "Boolean",
52084
+ "Number",
52085
+ "String"
52086
+ ]);
52087
+ const booleanValue = (value) => value ? TRUE_VALUE : FALSE_VALUE;
52088
+ const constantBooleanFormula = (value) => ({
52089
+ kind: "constant",
52090
+ value
52091
+ });
52092
+ const getBooleanFormula = (value) => {
52093
+ if (value.kind === "boolean") return constantBooleanFormula(value.value === true);
52094
+ return value.kind === "boolean-formula" && value.formula ? value.formula : null;
52095
+ };
52096
+ const booleanFormulaValue = (formula) => formula.kind === "constant" ? booleanValue(formula.value === true) : {
52097
+ kind: "boolean-formula",
52098
+ formula
52099
+ };
52100
+ const negateBooleanFormula = (formula) => {
52101
+ if (formula.kind === "constant") return constantBooleanFormula(formula.value !== true);
52102
+ if (formula.kind === "not" && formula.operand) return formula.operand;
52103
+ return {
52104
+ kind: "not",
52105
+ operand: formula
52106
+ };
52107
+ };
52108
+ const combineBooleanFormulas = (operator, left, right) => {
52109
+ if (left.kind === "constant") {
52110
+ if (operator === "and") return left.value === true ? right : left;
52111
+ return left.value === true ? left : right;
52112
+ }
52113
+ if (right.kind === "constant") {
52114
+ if (operator === "and") return right.value === true ? left : right;
52115
+ return right.value === true ? right : left;
52116
+ }
52117
+ return {
52118
+ kind: operator,
52119
+ left,
52120
+ right
52121
+ };
52122
+ };
52123
+ const conditionalBooleanFormula = (test, consequent, alternate) => {
52124
+ if (test.kind === "constant") return test.value === true ? consequent : alternate;
52125
+ return {
52126
+ kind: "conditional",
52127
+ test,
52128
+ consequent,
52129
+ alternate
52130
+ };
52131
+ };
52132
+ const collectBooleanFormulaAtomKeys = (formula, atomKeys) => {
52133
+ if (formula.kind === "atom") {
52134
+ if (formula.atomKey) atomKeys.add(formula.atomKey);
52135
+ return;
52136
+ }
52137
+ if (formula.operand) collectBooleanFormulaAtomKeys(formula.operand, atomKeys);
52138
+ if (formula.left) collectBooleanFormulaAtomKeys(formula.left, atomKeys);
52139
+ if (formula.right) collectBooleanFormulaAtomKeys(formula.right, atomKeys);
52140
+ if (formula.test) collectBooleanFormulaAtomKeys(formula.test, atomKeys);
52141
+ if (formula.consequent) collectBooleanFormulaAtomKeys(formula.consequent, atomKeys);
52142
+ if (formula.alternate) collectBooleanFormulaAtomKeys(formula.alternate, atomKeys);
52143
+ };
52144
+ const evaluateBooleanFormula = (formula, atomValues) => {
52145
+ if (formula.kind === "constant") return formula.value ?? null;
52146
+ if (formula.kind === "atom") return formula.atomKey ? atomValues.get(formula.atomKey) ?? null : null;
52147
+ if (formula.kind === "not") {
52148
+ const operand = formula.operand ? evaluateBooleanFormula(formula.operand, atomValues) : null;
52149
+ return operand === null ? null : !operand;
52150
+ }
52151
+ if (formula.kind === "and" || formula.kind === "or") {
52152
+ const left = formula.left ? evaluateBooleanFormula(formula.left, atomValues) : null;
52153
+ const right = formula.right ? evaluateBooleanFormula(formula.right, atomValues) : null;
52154
+ if (left === null || right === null) return null;
52155
+ return formula.kind === "and" ? left && right : left || right;
52156
+ }
52157
+ const test = formula.test ? evaluateBooleanFormula(formula.test, atomValues) : null;
52158
+ if (test === null) return null;
52159
+ const branch = test ? formula.consequent : formula.alternate;
52160
+ return branch ? evaluateBooleanFormula(branch, atomValues) : null;
52161
+ };
52162
+ const couldStableTargetReferencePreventRender = (distinctReferenceFormula, sharedReferenceFormula) => {
52163
+ const atomKeys = /* @__PURE__ */ new Set();
52164
+ collectBooleanFormulaAtomKeys(distinctReferenceFormula, atomKeys);
52165
+ collectBooleanFormulaAtomKeys(sharedReferenceFormula, atomKeys);
52166
+ if (atomKeys.size > 8) return true;
52167
+ const orderedAtomKeys = [...atomKeys];
52168
+ const assignmentCount = 2 ** orderedAtomKeys.length;
52169
+ for (let assignmentIndex = 0; assignmentIndex < assignmentCount; assignmentIndex += 1) {
52170
+ const atomValues = /* @__PURE__ */ new Map();
52171
+ for (const [atomIndex, atomKey] of orderedAtomKeys.entries()) atomValues.set(atomKey, Boolean(assignmentIndex & 1 << atomIndex));
52172
+ const distinctReferenceResult = evaluateBooleanFormula(distinctReferenceFormula, atomValues);
52173
+ const sharedReferenceResult = evaluateBooleanFormula(sharedReferenceFormula, atomValues);
52174
+ if (distinctReferenceResult === null || sharedReferenceResult === null) return true;
52175
+ if (!distinctReferenceResult && sharedReferenceResult) return true;
52176
+ }
52177
+ return false;
52178
+ };
52179
+ const emptyReferenceValue = (kind, referenceOrigin) => ({
52180
+ kind,
52181
+ referenceOrigin
52182
+ });
52183
+ const evaluateEquality = (left, right, emptyReferencesAreEqual, equalityKind) => {
52184
+ if (left.kind === "unknown" || right.kind === "unknown") return UNKNOWN_VALUE;
52185
+ const leftBooleanFormula = getBooleanFormula(left);
52186
+ const rightBooleanFormula = getBooleanFormula(right);
52187
+ if (leftBooleanFormula || rightBooleanFormula) {
52188
+ if (!leftBooleanFormula || !rightBooleanFormula) return UNKNOWN_VALUE;
52189
+ return booleanFormulaValue(combineBooleanFormulas("or", combineBooleanFormulas("and", leftBooleanFormula, rightBooleanFormula), combineBooleanFormulas("and", negateBooleanFormula(leftBooleanFormula), negateBooleanFormula(rightBooleanFormula))));
52190
+ }
52191
+ if (left.kind === "prop-symbol" || right.kind === "prop-symbol") {
52192
+ if (left.kind !== "prop-symbol" || right.kind !== "prop-symbol") return UNKNOWN_VALUE;
52193
+ if (left.propOwner === right.propOwner && left.value === right.value) return TRUE_VALUE;
52194
+ if (left.propOwner === right.propOwner || left.value !== right.value) return UNKNOWN_VALUE;
52195
+ return booleanFormulaValue({
52196
+ kind: "atom",
52197
+ atomKey: `${equalityKind}:${String(left.value)}`
52198
+ });
52199
+ }
52200
+ if (left.kind === "empty-array" || left.kind === "empty-object") {
52201
+ if (left.kind !== right.kind) return FALSE_VALUE;
52202
+ if (left.referenceOrigin === right.referenceOrigin) return TRUE_VALUE;
52203
+ return booleanValue((left.referenceOrigin === "previous-target" && right.referenceOrigin === "next-target" || left.referenceOrigin === "next-target" && right.referenceOrigin === "previous-target") && emptyReferencesAreEqual);
52204
+ }
52205
+ if (right.kind === "empty-array" || right.kind === "empty-object") return FALSE_VALUE;
52206
+ if (left.kind !== right.kind) return FALSE_VALUE;
52207
+ if (left.kind === "previous-props" || left.kind === "next-props") return UNKNOWN_VALUE;
52208
+ if (left.kind === "symbol" && left.value !== right.value) return UNKNOWN_VALUE;
52209
+ return booleanValue(left.value === right.value);
52210
+ };
52211
+ const getReturnedExpression = (functionNode) => {
52212
+ if (!isFunctionLike$1(functionNode)) return null;
52213
+ if (isNodeOfType(functionNode, "ArrowFunctionExpression")) {
52214
+ const body = functionNode.body;
52215
+ if (!isNodeOfType(body, "BlockStatement")) return body;
52216
+ }
52217
+ const body = functionNode.body;
52218
+ if (!isNodeOfType(body, "BlockStatement") || body.body.length !== 1) return null;
52219
+ const returnStatement = body.body[0];
52220
+ return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument ? returnStatement.argument : null;
52221
+ };
52222
+ const isProvablyCallable = (expression, state) => {
52223
+ const node = stripParenExpression(expression);
52224
+ if (isFunctionLike$1(node)) return true;
52225
+ if (isNodeOfType(node, "Identifier") && PROVABLY_CALLABLE_GLOBAL_NAMES.has(node.name) && state.scopes.isGlobalReference(node)) return true;
52226
+ return isFunctionLike$1(resolveExactLocalFunction(node, state.scopes));
52227
+ };
52228
+ const evaluateExpression = (expression, state) => {
52229
+ const node = stripParenExpression(expression);
52230
+ if (isNodeOfType(node, "Literal")) {
52231
+ if (typeof node.value === "boolean") return booleanValue(node.value);
52232
+ if (typeof node.value === "number") return {
52233
+ kind: "number",
52234
+ value: node.value
52235
+ };
52236
+ if (typeof node.value === "string") return {
52237
+ kind: "string",
52238
+ value: node.value
52239
+ };
52240
+ return node.value === void 0 ? UNDEFINED_VALUE : UNKNOWN_VALUE;
52241
+ }
52242
+ if (isNodeOfType(node, "Identifier")) {
52243
+ if (node.name === "undefined" && state.scopes.isGlobalReference(node)) return UNDEFINED_VALUE;
52244
+ return state.bindings.get(node.name) ?? UNKNOWN_VALUE;
52245
+ }
52246
+ if (isNodeOfType(node, "MemberExpression")) {
52247
+ const objectValue = evaluateExpression(node.object, state);
52248
+ if ((objectValue.kind === "empty-array" || objectValue.kind === "empty-object") && node.computed && isNodeOfType(node.property, "Literal") && (typeof node.property.value === "number" || typeof node.property.value === "string" && /^\d+$/.test(node.property.value))) return UNDEFINED_VALUE;
52249
+ const propertyName = getStaticPropertyName(node);
52250
+ if (propertyName === null) return UNKNOWN_VALUE;
52251
+ if (objectValue.kind === "previous-props" || objectValue.kind === "next-props") {
52252
+ if (propertyName === state.propName) {
52253
+ const referenceOrigin = objectValue.kind === "previous-props" ? "previous-target" : "next-target";
52254
+ return emptyReferenceValue(state.emptyLiteralKind === "array" ? "empty-array" : "empty-object", referenceOrigin);
52255
+ }
52256
+ return {
52257
+ kind: "prop-symbol",
52258
+ propOwner: objectValue.kind === "previous-props" ? "previous" : "next",
52259
+ value: propertyName
52260
+ };
52261
+ }
52262
+ if (objectValue.kind === "empty-array" && propertyName === "length") return {
52263
+ kind: "number",
52264
+ value: 0
52265
+ };
52266
+ if (objectValue.kind === "empty-object" && !OBJECT_PROTOTYPE_PROPERTY_NAMES.has(propertyName)) return UNDEFINED_VALUE;
52267
+ return UNKNOWN_VALUE;
52268
+ }
52269
+ if (isNodeOfType(node, "UnaryExpression") && node.operator === "!") {
52270
+ const argumentFormula = getBooleanFormula(evaluateExpression(node.argument, state));
52271
+ return argumentFormula ? booleanFormulaValue(negateBooleanFormula(argumentFormula)) : UNKNOWN_VALUE;
52272
+ }
52273
+ if (isNodeOfType(node, "LogicalExpression")) {
52274
+ const leftFormula = getBooleanFormula(evaluateExpression(node.left, state));
52275
+ if (!leftFormula) return UNKNOWN_VALUE;
52276
+ if (node.operator === "&&") {
52277
+ if (leftFormula.kind === "constant" && leftFormula.value === false) return FALSE_VALUE;
52278
+ const rightFormula = getBooleanFormula(evaluateExpression(node.right, state));
52279
+ return rightFormula ? booleanFormulaValue(combineBooleanFormulas("and", leftFormula, rightFormula)) : UNKNOWN_VALUE;
52280
+ }
52281
+ if (node.operator === "||") {
52282
+ if (leftFormula.kind === "constant" && leftFormula.value === true) return TRUE_VALUE;
52283
+ const rightFormula = getBooleanFormula(evaluateExpression(node.right, state));
52284
+ return rightFormula ? booleanFormulaValue(combineBooleanFormulas("or", leftFormula, rightFormula)) : UNKNOWN_VALUE;
52285
+ }
52286
+ return UNKNOWN_VALUE;
52287
+ }
52288
+ if (isNodeOfType(node, "BinaryExpression")) {
52289
+ const left = evaluateExpression(node.left, state);
52290
+ const right = evaluateExpression(node.right, state);
52291
+ if ([
52292
+ "===",
52293
+ "==",
52294
+ "!==",
52295
+ "!="
52296
+ ].includes(node.operator)) {
52297
+ const equality = evaluateEquality(left, right, state.emptyReferencesAreEqual, node.operator === "===" || node.operator === "!==" ? "strict" : "loose");
52298
+ if (node.operator === "===" || node.operator === "==") return equality;
52299
+ const equalityFormula = getBooleanFormula(equality);
52300
+ return equalityFormula ? booleanFormulaValue(negateBooleanFormula(equalityFormula)) : UNKNOWN_VALUE;
52301
+ }
52302
+ if (left.kind !== "number" || right.kind !== "number") return UNKNOWN_VALUE;
52303
+ if (node.operator === "<") return booleanValue(Number(left.value) < Number(right.value));
52304
+ if (node.operator === "<=") return booleanValue(Number(left.value) <= Number(right.value));
52305
+ if (node.operator === ">") return booleanValue(Number(left.value) > Number(right.value));
52306
+ if (node.operator === ">=") return booleanValue(Number(left.value) >= Number(right.value));
52307
+ return UNKNOWN_VALUE;
52308
+ }
52309
+ if (isNodeOfType(node, "ConditionalExpression")) {
52310
+ const testFormula = getBooleanFormula(evaluateExpression(node.test, state));
52311
+ if (!testFormula) return UNKNOWN_VALUE;
52312
+ if (testFormula.kind === "constant") return evaluateExpression(testFormula.value === true ? node.consequent : node.alternate, state);
52313
+ const consequentFormula = getBooleanFormula(evaluateExpression(node.consequent, state));
52314
+ const alternateFormula = getBooleanFormula(evaluateExpression(node.alternate, state));
52315
+ return consequentFormula && alternateFormula ? booleanFormulaValue(conditionalBooleanFormula(testFormula, consequentFormula, alternateFormula)) : UNKNOWN_VALUE;
52316
+ }
52317
+ if (!isNodeOfType(node, "CallExpression")) return UNKNOWN_VALUE;
52318
+ const callee = stripParenExpression(node.callee);
52319
+ if (isNodeOfType(callee, "MemberExpression")) {
52320
+ const receiver = evaluateExpression(callee.object, state);
52321
+ const methodName = getStaticPropertyName(callee);
52322
+ const callback = node.arguments[0];
52323
+ if (receiver.kind === "empty-array" && (methodName === "every" || methodName === "some") && callback && !isNodeOfType(callback, "SpreadElement") && isProvablyCallable(callback, state)) return methodName === "every" ? TRUE_VALUE : FALSE_VALUE;
52324
+ if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Object" && state.scopes.isGlobalReference(callee.object) && (methodName === "keys" || methodName === "values") && node.arguments.length === 1 && !isNodeOfType(node.arguments[0], "SpreadElement")) {
52325
+ if (evaluateExpression(node.arguments[0], state).kind === "empty-object") return emptyReferenceValue("empty-array", node);
52326
+ }
52327
+ return UNKNOWN_VALUE;
52328
+ }
52329
+ if (!isNodeOfType(callee, "Identifier")) return UNKNOWN_VALUE;
52330
+ const localFunction = resolveExactLocalFunction(callee, state.scopes);
52331
+ if (!isFunctionLike$1(localFunction) || localFunction.async || localFunction.generator || state.activeFunctions.has(localFunction)) return UNKNOWN_VALUE;
52332
+ const returnedExpression = getReturnedExpression(localFunction);
52333
+ if (!returnedExpression) return UNKNOWN_VALUE;
52334
+ const parameters = localFunction.params ?? [];
52335
+ if (parameters.length !== node.arguments.length) return UNKNOWN_VALUE;
52336
+ const bindings = new Map(state.bindings);
52337
+ for (const [parameterIndex, parameter] of parameters.entries()) {
52338
+ const argument = node.arguments[parameterIndex];
52339
+ if (!isNodeOfType(parameter, "Identifier") || !argument || isNodeOfType(argument, "SpreadElement")) return UNKNOWN_VALUE;
52340
+ bindings.set(parameter.name, evaluateExpression(argument, state));
52341
+ }
52342
+ return evaluateExpression(returnedExpression, {
52343
+ ...state,
52344
+ activeFunctions: new Set([...state.activeFunctions, localFunction]),
52345
+ bindings
52346
+ });
52347
+ };
52348
+ const comparatorProvesEmptyPropDoesNotBreakMemo = (comparatorExpression, propName, emptyLiteralKind, scopes) => {
52349
+ const comparator = resolveExactLocalFunction(comparatorExpression, scopes);
52350
+ if (!isFunctionLike$1(comparator) || comparator.async || comparator.generator) return false;
52351
+ const returnedExpression = getReturnedExpression(comparator);
52352
+ if (!returnedExpression) return false;
52353
+ const parameters = comparator.params ?? [];
52354
+ if (parameters.length !== 2 || !isNodeOfType(parameters[0], "Identifier") || !isNodeOfType(parameters[1], "Identifier")) return false;
52355
+ const bindings = new Map([[parameters[0].name, PREVIOUS_PROPS_VALUE], [parameters[1].name, NEXT_PROPS_VALUE]]);
52356
+ const evaluateComparator = (emptyReferencesAreEqual) => evaluateExpression(returnedExpression, {
52357
+ activeFunctions: new Set([comparator]),
52358
+ bindings,
52359
+ emptyLiteralKind,
52360
+ emptyReferencesAreEqual,
52361
+ propName,
52362
+ scopes
52363
+ });
52364
+ const distinctReferenceFormula = getBooleanFormula(evaluateComparator(false));
52365
+ const sharedReferenceFormula = getBooleanFormula(evaluateComparator(true));
52366
+ return Boolean(distinctReferenceFormula && sharedReferenceFormula && !couldStableTargetReferencePreventRender(distinctReferenceFormula, sharedReferenceFormula));
52367
+ };
52368
+ //#endregion
50969
52369
  //#region src/plugin/rules/performance/rerender-memo-with-default-value.ts
50970
52370
  const DEP_TAKING_HOOK_NAMES = new Set([
50971
52371
  ...HOOKS_WITH_DEPS,
@@ -51043,7 +52443,7 @@ const markCandidateIdentifier = (expression, candidateNames, shadowedNames, use,
51043
52443
  if (!candidateNames.has(stripped.name)) return;
51044
52444
  if (!into.has(stripped.name)) into.set(stripped.name, use);
51045
52445
  };
51046
- const collectIdentitySensitiveUses = (node, candidateNames, shadowedNames, memoRegistry, into) => {
52446
+ const collectIdentitySensitiveUses = (node, candidateNames, shadowedNames, memoRegistry, memoComparatorRegistry, defaultedBindings, context, into) => {
51047
52447
  let innerShadowedNames = shadowedNames;
51048
52448
  if (isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression")) {
51049
52449
  const parameterNames = /* @__PURE__ */ new Set();
@@ -51058,12 +52458,21 @@ const collectIdentitySensitiveUses = (node, candidateNames, shadowedNames, memoR
51058
52458
  if (isNodeOfType(node, "JSXOpeningElement")) {
51059
52459
  const openingName = node.name;
51060
52460
  const memoStatus = memoStatusForJsxOpeningName(memoRegistry, openingName);
51061
- if (!isIntrinsicJsxElementName(openingName) && memoStatus !== "not-memoised") for (const attribute of node.attributes ?? []) {
51062
- if (!isNodeOfType(attribute, "JSXAttribute")) continue;
51063
- if (!attribute.value || !isNodeOfType(attribute.value, "JSXExpressionContainer")) continue;
51064
- const attributeExpression = attribute.value.expression;
51065
- if (!attributeExpression || attributeExpression.type === "JSXEmptyExpression") continue;
51066
- markCandidateIdentifier(attributeExpression, candidateNames, innerShadowedNames, "memoized-prop", into);
52461
+ if (!isIntrinsicJsxElementName(openingName) && memoStatus !== "not-memoised") {
52462
+ const comparatorDescriptor = isNodeOfType(openingName, "JSXIdentifier") ? memoComparatorRegistry.get(openingName.name) : void 0;
52463
+ const comparator = comparatorDescriptor && context.scopes.symbolFor(openingName)?.bindingIdentifier === comparatorDescriptor.bindingIdentifier ? comparatorDescriptor.comparator : void 0;
52464
+ for (const attribute of node.attributes ?? []) {
52465
+ if (!isNodeOfType(attribute, "JSXAttribute")) continue;
52466
+ if (!attribute.value || !isNodeOfType(attribute.value, "JSXExpressionContainer")) continue;
52467
+ const attributeExpression = attribute.value.expression;
52468
+ if (!attributeExpression || attributeExpression.type === "JSXEmptyExpression") continue;
52469
+ const strippedAttributeExpression = stripParenExpression(attributeExpression);
52470
+ if (comparator && isNodeOfType(attribute.name, "JSXIdentifier") && isNodeOfType(strippedAttributeExpression, "Identifier") && !innerShadowedNames.has(strippedAttributeExpression.name)) {
52471
+ const binding = defaultedBindings.get(strippedAttributeExpression.name);
52472
+ if (binding && comparatorProvesEmptyPropDoesNotBreakMemo(comparator, attribute.name.name, binding.literalKind, context.scopes)) continue;
52473
+ }
52474
+ markCandidateIdentifier(attributeExpression, candidateNames, innerShadowedNames, "memoized-prop", into);
52475
+ }
51067
52476
  }
51068
52477
  }
51069
52478
  const nodeRecord = node;
@@ -51071,8 +52480,8 @@ const collectIdentitySensitiveUses = (node, candidateNames, shadowedNames, memoR
51071
52480
  if (key === "parent") continue;
51072
52481
  const child = nodeRecord[key];
51073
52482
  if (Array.isArray(child)) {
51074
- for (const item of child) if (isAstNode(item)) collectIdentitySensitiveUses(item, candidateNames, innerShadowedNames, memoRegistry, into);
51075
- } else if (isAstNode(child)) collectIdentitySensitiveUses(child, candidateNames, innerShadowedNames, memoRegistry, into);
52483
+ for (const item of child) if (isAstNode(item)) collectIdentitySensitiveUses(item, candidateNames, innerShadowedNames, memoRegistry, memoComparatorRegistry, defaultedBindings, context, into);
52484
+ } else if (isAstNode(child)) collectIdentitySensitiveUses(child, candidateNames, innerShadowedNames, memoRegistry, memoComparatorRegistry, defaultedBindings, context, into);
51076
52485
  }
51077
52486
  };
51078
52487
  const buildMessage$2 = (literalKind, use) => {
@@ -51089,13 +52498,14 @@ const rerenderMemoWithDefaultValue = defineRule({
51089
52498
  recommendation: "Move it to the top of the file: `const EMPTY_ITEMS: Item[] = []`, then use that as the default value",
51090
52499
  create: (context) => {
51091
52500
  let memoRegistry = /* @__PURE__ */ new Map();
52501
+ let memoComparatorRegistry = /* @__PURE__ */ new Map();
51092
52502
  const checkComponentFunction = (functionNode) => {
51093
52503
  if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return;
51094
52504
  const defaultedBindings = collectDefaultedEmptyBindings(functionNode);
51095
52505
  if (defaultedBindings.size === 0) return;
51096
52506
  if (!functionNode.body) return;
51097
52507
  const identitySensitiveUses = /* @__PURE__ */ new Map();
51098
- collectIdentitySensitiveUses(functionNode.body, new Set(defaultedBindings.keys()), /* @__PURE__ */ new Set(), memoRegistry, identitySensitiveUses);
52508
+ collectIdentitySensitiveUses(functionNode.body, new Set(defaultedBindings.keys()), /* @__PURE__ */ new Set(), memoRegistry, memoComparatorRegistry, defaultedBindings, context, identitySensitiveUses);
51099
52509
  for (const [bindingName, binding] of defaultedBindings) {
51100
52510
  const use = identitySensitiveUses.get(bindingName);
51101
52511
  if (!use) continue;
@@ -51108,6 +52518,7 @@ const rerenderMemoWithDefaultValue = defineRule({
51108
52518
  return {
51109
52519
  Program(node) {
51110
52520
  memoRegistry = buildSameFileMemoRegistry(node);
52521
+ memoComparatorRegistry = buildSameFileMemoComparatorRegistry(node);
51111
52522
  },
51112
52523
  CallExpression(node) {
51113
52524
  for (const finding of findForwardedFreshHookDependencies(node, context)) {
@@ -66052,6 +67463,7 @@ const CROSS_FILE_RULE_IDS = new Set([
66052
67463
  "no-effect-with-fresh-deps",
66053
67464
  "no-initialize-state",
66054
67465
  "no-mutating-reducer-state",
67466
+ "only-export-components",
66055
67467
  "no-unguarded-browser-global-in-render-or-hook-init",
66056
67468
  "prefer-dynamic-import",
66057
67469
  "rendering-hydration-mismatch-time",
@@ -66259,7 +67671,7 @@ const CROSS_FILE_DEPENDENCY_COLLECTORS = new Map([
66259
67671
  * `CROSS_FILE_DEPENDENCY_COLLECTORS` (the core guard test enforces the
66260
67672
  * partition), forcing a conscious classification.
66261
67673
  */
66262
- const UNBOUNDED_CROSS_FILE_RULE_IDS = /* @__PURE__ */ new Set();
67674
+ const UNBOUNDED_CROSS_FILE_RULE_IDS = new Set(["only-export-components"]);
66263
67675
  /**
66264
67676
  * Runs the collectors for `ruleIds` over one file and returns every
66265
67677
  * filesystem probe they made — the file's cross-file dependency set.