oxlint-plugin-react-doctor 0.7.4-dev.63fc41f → 0.7.4-dev.70b5d99

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 (3) hide show
  1. package/dist/index.d.ts +1 -231
  2. package/dist/index.js +920 -2827
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1915,10 +1915,7 @@ const isGeneratedImageRenderContext = (context, node) => {
1915
1915
  if (!node) return false;
1916
1916
  const programRoot = findProgramRoot(node);
1917
1917
  if (!programRoot) return false;
1918
- const generatedImageJsxNodes = collectGeneratedImageJsxNodes(programRoot);
1919
- if (generatedImageJsxNodes.has(node)) return true;
1920
- if (isNodeOfType(node, "JSXElement")) return generatedImageJsxNodes.has(node.openingElement);
1921
- return false;
1918
+ return collectGeneratedImageJsxNodes(programRoot).has(node);
1922
1919
  };
1923
1920
  //#endregion
1924
1921
  //#region src/plugin/utils/object-has-accessible-child.ts
@@ -8406,54 +8403,6 @@ const effectListenerCleanupMismatch = defineRule({
8406
8403
  } })
8407
8404
  });
8408
8405
  //#endregion
8409
- //#region src/plugin/utils/collect-effect-invoked-functions.ts
8410
- const PROMISE_CHAIN_METHOD_NAMES$1 = new Set([
8411
- "then",
8412
- "catch",
8413
- "finally"
8414
- ]);
8415
- const collectEffectInvokedFunctions = (effectCallback) => {
8416
- const invokedFunctions = new Set([effectCallback]);
8417
- const localFunctionBindings = /* @__PURE__ */ new Map();
8418
- const calledBindingNames = /* @__PURE__ */ new Set();
8419
- const pendingFunctions = [effectCallback];
8420
- const enqueue = (candidate) => {
8421
- const strippedCandidate = candidate ? stripParenExpression(candidate) : candidate;
8422
- if (!isFunctionLike$1(strippedCandidate) || invokedFunctions.has(strippedCandidate)) return;
8423
- invokedFunctions.add(strippedCandidate);
8424
- pendingFunctions.push(strippedCandidate);
8425
- };
8426
- const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
8427
- while (pendingFunctions.length > 0) {
8428
- const currentFunction = pendingFunctions.pop();
8429
- if (!currentFunction) break;
8430
- walkAst(currentFunction, (child) => {
8431
- if (child !== currentFunction && isFunctionLike$1(child)) {
8432
- if (isNodeOfType(child, "FunctionDeclaration") && isNodeOfType(child.id, "Identifier")) localFunctionBindings.set(child.id.name, child);
8433
- return false;
8434
- }
8435
- if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
8436
- const initializer = child.init ? stripParenExpression(child.init) : null;
8437
- if (isFunctionLike$1(initializer)) localFunctionBindings.set(child.id.name, initializer);
8438
- return;
8439
- }
8440
- if (!isNodeOfType(child, "CallExpression")) return;
8441
- const callee = stripParenExpression(child.callee);
8442
- if (isFunctionLike$1(callee)) {
8443
- enqueue(callee);
8444
- return;
8445
- }
8446
- if (isNodeOfType(callee, "Identifier")) {
8447
- calledBindingNames.add(callee.name);
8448
- return;
8449
- }
8450
- if (isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
8451
- });
8452
- for (const calledName of calledBindingNames) enqueue(localFunctionBindings.get(calledName));
8453
- }
8454
- return invokedFunctions;
8455
- };
8456
- //#endregion
8457
8406
  //#region src/plugin/utils/is-react-hook-name.ts
8458
8407
  const isReactHookName = (name) => {
8459
8408
  if (!name.startsWith("use")) return false;
@@ -8509,90 +8458,46 @@ const enclosingComponentOrHookName = (node) => {
8509
8458
  return functionNode ? componentOrHookDisplayNameForFunction(functionNode) : null;
8510
8459
  };
8511
8460
  //#endregion
8512
- //#region src/plugin/utils/find-declarator-for-binding.ts
8513
- const findDeclaratorForBinding = (bindingIdentifier) => {
8514
- let ancestor = bindingIdentifier.parent;
8515
- while (ancestor) {
8516
- if (isNodeOfType(ancestor, "VariableDeclarator")) return ancestor;
8517
- if (isNodeOfType(ancestor, "FunctionDeclaration") || isNodeOfType(ancestor, "FunctionExpression") || isNodeOfType(ancestor, "ArrowFunctionExpression") || isNodeOfType(ancestor, "Program")) return null;
8518
- ancestor = ancestor.parent ?? null;
8519
- }
8520
- return null;
8521
- };
8522
- //#endregion
8523
- //#region src/plugin/utils/executes-during-render.ts
8524
- const SYNCHRONOUS_ITERATION_METHOD_NAMES$1 = new Set([
8525
- "map",
8526
- "filter",
8527
- "forEach",
8528
- "flatMap",
8529
- "reduce",
8530
- "reduceRight",
8531
- "some",
8532
- "every",
8533
- "find",
8534
- "findIndex",
8535
- "findLast",
8536
- "findLastIndex",
8537
- "sort",
8538
- "toSorted"
8539
- ]);
8540
- const REACT_RENDER_PHASE_HOOK_NAMES = new Set(["useMemo", "useState"]);
8541
- const isGlobalArrayFromMember = (node, scopes) => {
8542
- const unwrappedNode = stripParenExpression(node);
8543
- if (!isNodeOfType(unwrappedNode, "MemberExpression") || unwrappedNode.computed || !isNodeOfType(unwrappedNode.object, "Identifier") || unwrappedNode.object.name !== "Array" || !scopes.isGlobalReference(unwrappedNode.object) || !isNodeOfType(unwrappedNode.property, "Identifier")) return false;
8544
- return unwrappedNode.property.name === "from";
8545
- };
8546
- const isConstAliasOfGlobalArrayFrom = (node, scopes) => {
8547
- const unwrappedNode = stripParenExpression(node);
8548
- if (!isNodeOfType(unwrappedNode, "Identifier")) return false;
8549
- const binding = findVariableInitializer(unwrappedNode, unwrappedNode.name);
8550
- if (!binding?.initializer) return false;
8551
- const declarator = findDeclaratorForBinding(binding.bindingIdentifier);
8552
- return Boolean(declarator && scopes.symbolFor(unwrappedNode)?.declarationNode === declarator && declarator.init && declarator.parent && isNodeOfType(declarator.parent, "VariableDeclaration") && declarator.parent.kind === "const" && isGlobalArrayFromMember(declarator.init, scopes));
8553
- };
8554
- const executesDuringRender = (functionNode, scopes) => {
8555
- const parent = functionNode.parent;
8556
- if (!isNodeOfType(parent, "CallExpression")) return false;
8557
- if (parent.callee === functionNode) return true;
8558
- if ((scopes ? isReactApiCall(parent, REACT_RENDER_PHASE_HOOK_NAMES, scopes, { allowGlobalReactNamespace: true }) : isHookCall$2(parent, REACT_RENDER_PHASE_HOOK_NAMES)) && parent.arguments?.[0] === functionNode) return true;
8559
- if (scopes && parent.arguments?.[1] === functionNode && (isGlobalArrayFromMember(parent.callee, scopes) || isConstAliasOfGlobalArrayFrom(parent.callee, scopes))) return true;
8560
- return isNodeOfType(parent.callee, "MemberExpression") && !parent.callee.computed && isNodeOfType(parent.callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES$1.has(parent.callee.property.name) && parent.arguments?.[0] === functionNode;
8561
- };
8562
- //#endregion
8563
- //#region src/plugin/utils/find-render-phase-component-or-hook.ts
8564
- const findRenderPhaseComponentOrHook = (node, scopes) => {
8565
- let functionNode = findEnclosingFunction(node);
8566
- while (functionNode) {
8567
- if (componentOrHookDisplayNameForFunction(functionNode)) return functionNode;
8568
- if (!executesDuringRender(functionNode, scopes)) return null;
8569
- functionNode = findEnclosingFunction(functionNode);
8570
- }
8571
- return null;
8572
- };
8573
- //#endregion
8574
- //#region src/plugin/utils/get-function-binding-name.ts
8575
- const getFunctionBindingIdentifier = (functionNode) => {
8576
- if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
8577
- const parent = functionNode.parent;
8578
- if (isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier")) return parent.id;
8579
- if (isNodeOfType(parent, "AssignmentExpression") && parent.right === functionNode && isNodeOfType(parent.left, "Identifier")) return parent.left;
8580
- if (isNodeOfType(parent, "CallExpression")) {
8581
- const callParent = parent.parent;
8582
- if (isNodeOfType(callParent, "VariableDeclarator") && isNodeOfType(callParent.id, "Identifier")) return callParent.id;
8583
- }
8584
- return null;
8585
- };
8586
- const getFunctionBindingName$1 = (functionNode) => getFunctionBindingIdentifier(functionNode)?.name ?? null;
8587
- //#endregion
8588
8461
  //#region src/plugin/utils/get-range-start.ts
8589
8462
  const getRangeStart = (node) => {
8590
8463
  const rangeStart = node.range?.[0];
8591
8464
  return typeof rangeStart === "number" ? rangeStart : null;
8592
8465
  };
8593
8466
  //#endregion
8594
- //#region src/plugin/utils/is-event-handler-attribute.ts
8595
- const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && /^on[A-Z]/.test(node.name.name);
8467
+ //#region src/plugin/utils/is-result-discarded-call.ts
8468
+ const isResultDiscardedCall = (callExpression) => {
8469
+ let node = callExpression;
8470
+ let parent = node.parent;
8471
+ while (parent) {
8472
+ if (isNodeOfType(parent, "ExpressionStatement")) return true;
8473
+ if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "void") return true;
8474
+ if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === node) return true;
8475
+ if (isNodeOfType(parent, "ChainExpression")) {
8476
+ node = parent;
8477
+ parent = node.parent;
8478
+ continue;
8479
+ }
8480
+ if (isNodeOfType(parent, "LogicalExpression") && parent.right === node) {
8481
+ node = parent;
8482
+ parent = node.parent;
8483
+ continue;
8484
+ }
8485
+ if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === node || parent.alternate === node)) {
8486
+ node = parent;
8487
+ parent = node.parent;
8488
+ continue;
8489
+ }
8490
+ if (isNodeOfType(parent, "SequenceExpression")) {
8491
+ const expressions = parent.expressions ?? [];
8492
+ if (expressions[expressions.length - 1] !== node) return true;
8493
+ node = parent;
8494
+ parent = node.parent;
8495
+ continue;
8496
+ }
8497
+ return false;
8498
+ }
8499
+ return false;
8500
+ };
8596
8501
  //#endregion
8597
8502
  //#region src/plugin/utils/walk-inside-statement-blocks.ts
8598
8503
  const walkInsideStatementBlocks = (node, visitor) => {
@@ -8627,9 +8532,125 @@ const isCleanupReturningSubscribeLikeCallExpression = (node) => {
8627
8532
  return true;
8628
8533
  };
8629
8534
  //#endregion
8535
+ //#region src/plugin/rules/state-and-effects/utils/is-cleanup-return.ts
8536
+ const ITERATOR_CALLBACK_METHOD_NAMES$1 = new Set([
8537
+ "each",
8538
+ "every",
8539
+ "filter",
8540
+ "find",
8541
+ "findIndex",
8542
+ "findLast",
8543
+ "findLastIndex",
8544
+ "flatMap",
8545
+ "forEach",
8546
+ "map",
8547
+ "reduce",
8548
+ "reduceRight",
8549
+ "some",
8550
+ "sort",
8551
+ "toSorted"
8552
+ ]);
8553
+ const STATIC_ITERATOR_CALLBACK_METHOD_NAMES = new Set([
8554
+ "from",
8555
+ "fromAsync",
8556
+ "groupBy"
8557
+ ]);
8558
+ const unwrapChainExpression$3 = (node) => isNodeOfType(node, "ChainExpression") ? node.expression : node;
8559
+ const isNullLiteral = (node) => isNodeOfType(node, "Literal") && node.value === null;
8560
+ const isListenerRemovalViaNullHandler = (callNode) => {
8561
+ if (!isNodeOfType(callNode, "CallExpression")) return false;
8562
+ const callee = unwrapChainExpression$3(callNode.callee);
8563
+ return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "on" && isNullLiteral(callNode.arguments?.[1]);
8564
+ };
8565
+ const REFERENCE_BASED_REMOVAL_METHOD_NAMES = new Set([
8566
+ "off",
8567
+ "removeEventListener",
8568
+ "removeListener"
8569
+ ]);
8570
+ const isNoOpInlineHandlerRemoval = (callNode, methodName) => {
8571
+ if (!REFERENCE_BASED_REMOVAL_METHOD_NAMES.has(methodName)) return false;
8572
+ const handlerArgument = callNode.arguments?.[1];
8573
+ return isNodeOfType(handlerArgument, "ArrowFunctionExpression") || isNodeOfType(handlerArgument, "FunctionExpression");
8574
+ };
8575
+ const isReleaseLikeCall = (node, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
8576
+ const callNode = unwrapChainExpression$3(node);
8577
+ if (!isNodeOfType(callNode, "CallExpression")) return false;
8578
+ if (isListenerRemovalViaNullHandler(callNode)) return true;
8579
+ const callee = unwrapChainExpression$3(callNode.callee);
8580
+ if (isNodeOfType(callee, "Identifier")) {
8581
+ if (TIMER_CLEANUP_CALLEE_NAMES.has(callee.name)) return true;
8582
+ if (CLEANUP_LIKE_RELEASE_CALLEE_NAMES.has(callee.name)) return true;
8583
+ if (knownCleanupFunctionNames.has(callee.name)) return true;
8584
+ return false;
8585
+ }
8586
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) {
8587
+ if (isNoOpInlineHandlerRemoval(callNode, callee.property.name)) return false;
8588
+ if (BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(callee.property.name) && isNodeOfType(callee.object, "Identifier") && knownBoundSubscriptionNames.has(callee.object.name)) return true;
8589
+ return GLOBAL_RELEASE_METHOD_NAMES.has(callee.property.name);
8590
+ }
8591
+ return false;
8592
+ };
8593
+ const isStaticIteratorCallbackCallee = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && isNodeOfType(callee.property, "Identifier") && (callee.object.name === "Array" || callee.object.name === "Map" || callee.object.name === "Object") && STATIC_ITERATOR_CALLBACK_METHOD_NAMES.has(callee.property.name);
8594
+ const isIteratorCallbackArgument = (node) => {
8595
+ const parentNode = node.parent;
8596
+ if (!isNodeOfType(parentNode, "CallExpression")) return false;
8597
+ if (!parentNode.arguments?.some((argument) => argument === node)) return false;
8598
+ const callee = unwrapChainExpression$3(parentNode.callee);
8599
+ if (parentNode.arguments[1] === node && isStaticIteratorCallbackCallee(callee)) return true;
8600
+ return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && ITERATOR_CALLBACK_METHOD_NAMES$1.has(callee.property.name);
8601
+ };
8602
+ const containsReleaseLikeCall = (node, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
8603
+ let didFindRelease = false;
8604
+ walkAst(node, (child) => {
8605
+ if (didFindRelease) return false;
8606
+ if (child !== node && isFunctionLike$1(child) && !isIteratorCallbackArgument(child)) return false;
8607
+ if (isReleaseLikeCall(child, knownCleanupFunctionNames, knownBoundSubscriptionNames)) {
8608
+ didFindRelease = true;
8609
+ return false;
8610
+ }
8611
+ });
8612
+ return didFindRelease;
8613
+ };
8614
+ const isCleanupFunctionLike = (node, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
8615
+ if (!isFunctionLike$1(node)) return false;
8616
+ return containsReleaseLikeCall(node.body, knownCleanupFunctionNames, knownBoundSubscriptionNames);
8617
+ };
8618
+ const isProvablyNoOpCleanupFunction = (node) => {
8619
+ if (!isFunctionLike$1(node)) return false;
8620
+ let sawNoOpRemoval = false;
8621
+ let sawOtherCall = false;
8622
+ walkAst(node.body, (child) => {
8623
+ if (sawOtherCall) return false;
8624
+ if (isFunctionLike$1(child)) return false;
8625
+ const callNode = unwrapChainExpression$3(child);
8626
+ if (!isNodeOfType(callNode, "CallExpression")) return;
8627
+ const callee = unwrapChainExpression$3(callNode.callee);
8628
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && isNoOpInlineHandlerRemoval(callNode, callee.property.name)) {
8629
+ sawNoOpRemoval = true;
8630
+ return;
8631
+ }
8632
+ sawOtherCall = true;
8633
+ });
8634
+ return sawNoOpRemoval && !sawOtherCall;
8635
+ };
8636
+ const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSubscriptionNames, options = {}) => {
8637
+ if (!returnedValue) return false;
8638
+ const unwrappedValue = unwrapChainExpression$3(returnedValue);
8639
+ if (isNodeOfType(unwrappedValue, "Literal") && unwrappedValue.value === null) return false;
8640
+ if (isNodeOfType(unwrappedValue, "Identifier")) {
8641
+ if (unwrappedValue.name === "undefined") return false;
8642
+ if (knownCleanupFunctionNames.has(unwrappedValue.name)) return true;
8643
+ return options.allowOpaqueReturn === true && !knownBoundSubscriptionNames.has(unwrappedValue.name);
8644
+ }
8645
+ if (isCleanupReturningSubscribeLikeCallExpression(unwrappedValue)) return true;
8646
+ if (isProvablyNoOpCleanupFunction(unwrappedValue)) return false;
8647
+ if (options.allowOpaqueReturn === true && !isSubscribeLikeCallExpression(unwrappedValue)) return true;
8648
+ if (isCleanupFunctionLike(unwrappedValue, knownCleanupFunctionNames, knownBoundSubscriptionNames)) return true;
8649
+ return false;
8650
+ };
8651
+ //#endregion
8630
8652
  //#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
8631
8653
  const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
8632
- const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
8633
8654
  const RESOURCE_NOUN_BY_KIND = {
8634
8655
  subscribe: "subscription",
8635
8656
  timer: "timer",
@@ -8640,66 +8661,7 @@ const isSubscribeOrObserveCall = (node) => {
8640
8661
  if (isSubscribeLikeCallExpression(node)) return true;
8641
8662
  return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === OBSERVER_REGISTRATION_METHOD_NAME;
8642
8663
  };
8643
- const resolveExpressionKey$1 = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
8644
- if (!expression) return null;
8645
- const unwrappedExpression = stripParenExpression(expression);
8646
- if (isNodeOfType(unwrappedExpression, "Identifier")) {
8647
- const symbol = context.scopes.symbolFor(unwrappedExpression);
8648
- if (!symbol) return context.scopes.isGlobalReference(unwrappedExpression) ? `global:${unwrappedExpression.name}` : null;
8649
- if (visitedSymbolIds.has(symbol.id)) return `symbol:${symbol.id}`;
8650
- visitedSymbolIds.add(symbol.id);
8651
- const bindingProperty = symbol.bindingIdentifier.parent;
8652
- const bindingPattern = bindingProperty?.parent;
8653
- const variableDeclarator = bindingPattern?.parent;
8654
- const bindingPropertyName = isNodeOfType(bindingProperty, "Property") ? getStaticPropertyKeyName(bindingProperty) : null;
8655
- if (bindingPropertyName && isNodeOfType(bindingPattern, "ObjectPattern") && isNodeOfType(variableDeclarator, "VariableDeclarator") && variableDeclarator.id === bindingPattern) {
8656
- const objectKey = resolveExpressionKey$1(variableDeclarator.init, context, visitedSymbolIds);
8657
- return objectKey ? `${objectKey}.${bindingPropertyName}` : `symbol:${symbol.id}`;
8658
- }
8659
- const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
8660
- if (symbol.kind === "const" && initializer && (isNodeOfType(initializer, "Identifier") || isNodeOfType(initializer, "MemberExpression"))) return resolveExpressionKey$1(initializer, context, visitedSymbolIds) ?? `symbol:${symbol.id}`;
8661
- return `symbol:${symbol.id}`;
8662
- }
8663
- if (isNodeOfType(unwrappedExpression, "MemberExpression") && !unwrappedExpression.computed) {
8664
- if (!isNodeOfType(unwrappedExpression.property, "Identifier")) return null;
8665
- const objectKey = resolveExpressionKey$1(unwrappedExpression.object, context, visitedSymbolIds);
8666
- return objectKey ? `${objectKey}.${unwrappedExpression.property.name}` : null;
8667
- }
8668
- if (isNodeOfType(unwrappedExpression, "ThisExpression")) return "this";
8669
- if (isNodeOfType(unwrappedExpression, "Literal") && (typeof unwrappedExpression.value === "string" || typeof unwrappedExpression.value === "number")) return `literal:${String(unwrappedExpression.value)}`;
8670
- if (isFunctionLike$1(unwrappedExpression)) {
8671
- const rangeStart = getRangeStart(unwrappedExpression);
8672
- return rangeStart === null ? null : `function:${rangeStart}`;
8673
- }
8674
- return null;
8675
- };
8676
- const findAssignedResourceKey = (resourceNode, context) => {
8677
- let currentNode = resourceNode;
8678
- let parentNode = currentNode.parent;
8679
- while (isNodeOfType(parentNode, "ChainExpression")) {
8680
- currentNode = parentNode;
8681
- parentNode = currentNode.parent;
8682
- }
8683
- if (isNodeOfType(parentNode, "VariableDeclarator") && parentNode.init === currentNode) return resolveExpressionKey$1(parentNode.id, context);
8684
- if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.right === currentNode) return resolveExpressionKey$1(parentNode.left, context);
8685
- return null;
8686
- };
8687
- const getCallRegistrationDetails = (callNode, context) => {
8688
- const callee = stripParenExpression(callNode.callee);
8689
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return {
8690
- receiverKey: null,
8691
- registrationVerbName: null,
8692
- eventKey: null,
8693
- handlerKey: null
8694
- };
8695
- return {
8696
- receiverKey: resolveExpressionKey$1(callee.object, context),
8697
- registrationVerbName: callee.property.name,
8698
- eventKey: resolveExpressionKey$1(callNode.arguments?.[0], context),
8699
- handlerKey: resolveExpressionKey$1(callNode.arguments?.[1], context)
8700
- };
8701
- };
8702
- const findSubscribeLikeUsages = (callback, context) => {
8664
+ const findSubscribeLikeUsages = (callback) => {
8703
8665
  const usages = [];
8704
8666
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
8705
8667
  let cleanupArgument = null;
@@ -8708,22 +8670,13 @@ const findSubscribeLikeUsages = (callback, context) => {
8708
8670
  const lastCallbackStatement = callbackStatements[callbackStatements.length - 1];
8709
8671
  if (isNodeOfType(lastCallbackStatement, "ReturnStatement") && lastCallbackStatement.argument) cleanupArgument = lastCallbackStatement.argument;
8710
8672
  }
8711
- const effectInvokedFunctions = collectEffectInvokedFunctions(callback);
8712
8673
  walkAst(callback, (child) => {
8713
- if (child !== callback && isFunctionLike$1(child)) {
8714
- if (child === cleanupArgument) return false;
8715
- if (!effectInvokedFunctions.has(child) && !isSynchronousIteratorCallback(child)) return false;
8716
- }
8674
+ if (child === cleanupArgument && !isSubscribeLikeCallExpression(child)) return false;
8717
8675
  if (isSocketConstruction(child)) {
8718
8676
  usages.push({
8719
8677
  kind: "socket",
8720
8678
  node: child,
8721
- resourceName: isNodeOfType(child.callee, "Identifier") ? child.callee.name : "WebSocket",
8722
- handleKey: findAssignedResourceKey(child, context),
8723
- receiverKey: null,
8724
- registrationVerbName: null,
8725
- eventKey: null,
8726
- handlerKey: null
8679
+ resourceName: isNodeOfType(child.callee, "Identifier") ? child.callee.name : "WebSocket"
8727
8680
  });
8728
8681
  return;
8729
8682
  }
@@ -8732,348 +8685,118 @@ const findSubscribeLikeUsages = (callback, context) => {
8732
8685
  usages.push({
8733
8686
  kind: "timer",
8734
8687
  node: child,
8735
- resourceName: child.callee.name,
8736
- handleKey: findAssignedResourceKey(child, context),
8737
- receiverKey: null,
8738
- registrationVerbName: child.callee.name,
8739
- eventKey: null,
8740
- handlerKey: null
8688
+ resourceName: child.callee.name
8741
8689
  });
8742
8690
  return;
8743
8691
  }
8744
- if (isNodeOfType(child.callee, "MemberExpression") && isNodeOfType(child.callee.property, "Identifier") && (SUBSCRIPTION_METHOD_NAMES.has(child.callee.property.name) || child.callee.property.name === OBSERVER_REGISTRATION_METHOD_NAME)) {
8745
- const registrationDetails = getCallRegistrationDetails(child, context);
8746
- usages.push({
8747
- kind: "subscribe",
8748
- node: child,
8749
- resourceName: child.callee.property.name,
8750
- handleKey: findAssignedResourceKey(child, context),
8751
- ...registrationDetails
8752
- });
8692
+ if (isNodeOfType(child.callee, "MemberExpression") && isNodeOfType(child.callee.property, "Identifier") && (SUBSCRIPTION_METHOD_NAMES.has(child.callee.property.name) || child.callee.property.name === OBSERVER_REGISTRATION_METHOD_NAME)) usages.push({
8693
+ kind: "subscribe",
8694
+ node: child,
8695
+ resourceName: child.callee.property.name
8696
+ });
8697
+ });
8698
+ return usages;
8699
+ };
8700
+ const collectCleanupBindings = (effectCallback) => {
8701
+ const bindings = {
8702
+ cleanupFunctionNames: /* @__PURE__ */ new Set(),
8703
+ subscriptionNames: /* @__PURE__ */ new Set(),
8704
+ effectScopeVariableNames: /* @__PURE__ */ new Set()
8705
+ };
8706
+ if (!isNodeOfType(effectCallback, "ArrowFunctionExpression") && !isNodeOfType(effectCallback, "FunctionExpression")) return bindings;
8707
+ if (!isNodeOfType(effectCallback.body, "BlockStatement")) return bindings;
8708
+ walkAst(effectCallback.body, (child) => {
8709
+ if (!isSubscribeOrObserveCall(child)) return;
8710
+ if (!isNodeOfType(child, "CallExpression")) return;
8711
+ if (!isNodeOfType(child.callee, "MemberExpression")) return;
8712
+ if (!isNodeOfType(child.callee.object, "Identifier")) return;
8713
+ bindings.subscriptionNames.add(child.callee.object.name);
8714
+ });
8715
+ walkInsideStatementBlocks(effectCallback.body, (child) => {
8716
+ if (!isNodeOfType(child, "VariableDeclaration")) return;
8717
+ for (const declarator of child.declarations ?? []) {
8718
+ if (!isNodeOfType(declarator.id, "Identifier")) continue;
8719
+ const bindingName = declarator.id.name;
8720
+ bindings.effectScopeVariableNames.add(bindingName);
8721
+ const init = declarator.init;
8722
+ if (!init) continue;
8723
+ if (isSocketConstruction(init)) {
8724
+ bindings.subscriptionNames.add(bindingName);
8725
+ continue;
8726
+ }
8727
+ if (!isNodeOfType(init, "CallExpression")) continue;
8728
+ if (isSubscribeLikeCallExpression(init)) {
8729
+ bindings.subscriptionNames.add(bindingName);
8730
+ if (isCleanupReturningSubscribeLikeCallExpression(init)) bindings.cleanupFunctionNames.add(bindingName);
8731
+ }
8753
8732
  }
8754
8733
  });
8755
- return usages.filter((usage) => isNodeReachableWithinFunction(usage.node, context));
8756
- };
8757
- const isNodeReachableWithinFunction = (node, context) => {
8758
- const owner = context.cfg.enclosingFunction(node);
8759
- if (!owner) return true;
8760
- const functionCfg = context.cfg.cfgFor(owner);
8761
- if (!functionCfg) return true;
8762
- const targetBlock = functionCfg.blockOf(node);
8763
- if (!targetBlock) return true;
8764
- const visitedBlocks = new Set([functionCfg.entry]);
8765
- const pendingBlocks = [functionCfg.entry];
8766
- while (pendingBlocks.length > 0) {
8767
- const currentBlock = pendingBlocks.pop();
8768
- if (!currentBlock) break;
8769
- if (currentBlock === targetBlock) return true;
8770
- for (const edge of currentBlock.successors) {
8771
- if (visitedBlocks.has(edge.to)) continue;
8772
- visitedBlocks.add(edge.to);
8773
- pendingBlocks.push(edge.to);
8774
- }
8775
- }
8776
- return false;
8777
- };
8778
- const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, context) => {
8779
- let pathAnchor = usageNode;
8780
- let pathOwner = findEnclosingFunction(pathAnchor);
8781
- while (pathOwner && isSynchronousIteratorCallback(pathOwner)) {
8782
- if (matchingNodes.length > 0 && matchingNodes.every((matchingNode) => context.cfg.enclosingFunction(matchingNode) === pathOwner)) break;
8783
- const iteratorCall = pathOwner.parent;
8784
- if (!isNodeOfType(iteratorCall, "CallExpression")) break;
8785
- pathAnchor = iteratorCall;
8786
- pathOwner = findEnclosingFunction(pathAnchor);
8787
- }
8788
- const owner = context.cfg.enclosingFunction(pathAnchor);
8789
- if (!owner) return false;
8790
- const functionCfg = context.cfg.cfgFor(owner);
8791
- if (!functionCfg) return false;
8792
- const usageBlock = functionCfg.blockOf(pathAnchor);
8793
- if (!usageBlock) return false;
8794
- const usageStart = getRangeStart(usageNode);
8795
- const matchingBlocks = new Set(matchingNodes.flatMap((matchingNode) => {
8796
- if (context.cfg.enclosingFunction(matchingNode) !== owner) return [];
8797
- const matchingBlock = functionCfg.blockOf(matchingNode);
8798
- if (!matchingBlock) return [];
8799
- const matchingStart = getRangeStart(matchingNode);
8800
- if (matchingBlock === usageBlock && usageStart !== null && matchingStart !== null && matchingStart < usageStart) return [];
8801
- return [matchingBlock];
8802
- }));
8803
- if (matchingBlocks.has(usageBlock)) return true;
8804
- const visitedBlocks = new Set([usageBlock]);
8805
- const pendingBlocks = [usageBlock];
8806
- while (pendingBlocks.length > 0) {
8807
- const currentBlock = pendingBlocks.pop();
8808
- if (!currentBlock) break;
8809
- for (const edge of currentBlock.successors) {
8810
- if (matchingBlocks.has(edge.to)) continue;
8811
- if (edge.to === functionCfg.exit) return false;
8812
- if (visitedBlocks.has(edge.to)) continue;
8813
- visitedBlocks.add(edge.to);
8814
- pendingBlocks.push(edge.to);
8815
- }
8816
- }
8817
- return matchingBlocks.size > 0;
8818
- };
8819
- const removeSynchronouslyReleasedUsages = (callback, usages, context) => {
8734
+ walkAst(effectCallback.body, (child) => {
8735
+ if (child !== effectCallback.body && (isNodeOfType(child, "ArrowFunctionExpression") || isNodeOfType(child, "FunctionExpression"))) return false;
8736
+ if (isNodeOfType(child, "FunctionDeclaration") && child.id && isCleanupFunctionLike(child, bindings.cleanupFunctionNames, bindings.subscriptionNames)) {
8737
+ bindings.cleanupFunctionNames.add(child.id.name);
8738
+ return false;
8739
+ }
8740
+ });
8741
+ walkInsideStatementBlocks(effectCallback.body, (child) => {
8742
+ if (!isNodeOfType(child, "VariableDeclaration")) return;
8743
+ for (const declarator of child.declarations ?? []) {
8744
+ if (!isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
8745
+ if (isCleanupFunctionLike(declarator.init, bindings.cleanupFunctionNames, bindings.subscriptionNames)) bindings.cleanupFunctionNames.add(declarator.id.name);
8746
+ }
8747
+ });
8748
+ walkAst(effectCallback.body, (child) => {
8749
+ if (isNodeOfType(child, "AssignmentExpression") && isNodeOfType(child.left, "Identifier") && bindings.effectScopeVariableNames.has(child.left.name) && isCleanupFunctionLike(child.right, bindings.cleanupFunctionNames, bindings.subscriptionNames)) bindings.cleanupFunctionNames.add(child.left.name);
8750
+ });
8751
+ return bindings;
8752
+ };
8753
+ const removeSynchronouslyReleasedUsages = (callback, usages) => {
8820
8754
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
8821
8755
  if (!isNodeOfType(callback.body, "BlockStatement")) return usages;
8822
- const releaseCalls = [];
8756
+ const releaseStarts = [];
8823
8757
  walkInsideStatementBlocks(callback.body, (child) => {
8824
- if (!isNodeOfType(isNodeOfType(child, "ChainExpression") ? child.expression : child, "CallExpression")) return;
8825
- releaseCalls.push(child);
8758
+ if (!isReleaseLikeCall(child, EMPTY_NAME_SET$1, EMPTY_NAME_SET$1)) return;
8759
+ const releaseStart = getRangeStart(child);
8760
+ if (releaseStart !== null) releaseStarts.push(releaseStart);
8826
8761
  });
8827
- if (releaseCalls.length === 0) return usages;
8762
+ if (releaseStarts.length === 0) return usages;
8828
8763
  return usages.filter((usage) => {
8829
8764
  const usageStart = getRangeStart(usage.node);
8830
8765
  if (usageStart === null) return true;
8831
- const matchingReleaseCalls = releaseCalls.filter((releaseCall) => {
8832
- const releaseStart = getRangeStart(releaseCall);
8833
- return releaseStart !== null && releaseStart > usageStart && doesReleaseCallMatchUsage(releaseCall, usage, context);
8834
- });
8835
- return !doMatchingNodesCoverEveryPathAfterUsage(usage.node, matchingReleaseCalls, context);
8766
+ return !releaseStarts.some((releaseStart) => releaseStart > usageStart);
8836
8767
  });
8837
8768
  };
8838
- const resolveIteratorCollectionKey = (expression, context) => {
8839
- if (!expression || !isNodeOfType(expression, "Identifier")) return null;
8840
- const symbol = context.scopes.symbolFor(expression);
8841
- if (!symbol || symbol.kind !== "parameter") return null;
8842
- let callbackNode = symbol.bindingIdentifier.parent;
8843
- while (callbackNode && !isFunctionLike$1(callbackNode)) callbackNode = callbackNode.parent;
8844
- if (!callbackNode || !isFunctionLike$1(callbackNode)) return null;
8845
- const callNode = callbackNode.parent;
8846
- if (!isNodeOfType(callNode, "CallExpression")) return null;
8847
- const callee = stripParenExpression(callNode.callee);
8848
- if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) {
8849
- if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from" && callNode.arguments?.[1] === callbackNode) return resolveExpressionKey$1(callNode.arguments[0], context);
8850
- if (callNode.arguments?.[0] === callbackNode) return resolveExpressionKey$1(callee.object, context);
8851
- }
8852
- return null;
8853
- };
8854
- const findCollectionMappingCall = (callbackNode) => {
8855
- if (!isNodeOfType(callbackNode, "ArrowFunctionExpression") && !isNodeOfType(callbackNode, "FunctionExpression") || callbackNode.async || callbackNode.generator) return null;
8856
- const callNode = callbackNode.parent;
8857
- if (!isNodeOfType(callNode, "CallExpression")) return null;
8858
- const callee = stripParenExpression(callNode.callee);
8859
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return null;
8860
- if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from" && callNode.arguments?.[1] === callbackNode) return callNode;
8861
- return callee.property.name === "map" && callNode.arguments?.[0] === callbackNode ? callNode : null;
8862
- };
8863
- const findMappedResourceCollectionKey = (resourceNode, context) => {
8864
- const callbackNode = findEnclosingFunction(resourceNode);
8865
- if (!callbackNode || !isNodeOfType(callbackNode, "ArrowFunctionExpression") && !isNodeOfType(callbackNode, "FunctionExpression")) return null;
8866
- const mappingCall = findCollectionMappingCall(callbackNode);
8867
- if (!mappingCall) return null;
8868
- if (isNodeOfType(callbackNode.body, "BlockStatement")) {
8869
- const resourceRoot = findTransparentExpressionRoot(resourceNode);
8870
- const resourceDeclarator = resourceRoot.parent;
8871
- const resourceDeclaration = resourceDeclarator?.parent;
8872
- if (!isNodeOfType(resourceDeclarator, "VariableDeclarator") || resourceDeclarator.init !== resourceRoot || !isNodeOfType(resourceDeclarator.id, "Identifier") || !isNodeOfType(resourceDeclaration, "VariableDeclaration") || resourceDeclaration.kind !== "const" || resourceDeclaration.parent !== callbackNode.body) return null;
8873
- const returnStatements = [];
8874
- walkAst(callbackNode.body, (child) => {
8875
- if (child !== callbackNode.body && isFunctionLike$1(child)) return false;
8876
- if (isNodeOfType(child, "ReturnStatement")) returnStatements.push(child);
8877
- });
8878
- const returnStatement = returnStatements[0];
8879
- const callbackStatements = callbackNode.body.body ?? [];
8880
- const returnedIdentifier = isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument ? stripParenExpression(returnStatement.argument) : null;
8881
- const resourceSymbol = context.scopes.symbolFor(resourceDeclarator.id);
8882
- if (returnStatements.length !== 1 || callbackStatements[callbackStatements.length - 1] !== returnStatement || !isNodeOfType(returnedIdentifier, "Identifier") || !resourceSymbol || context.scopes.symbolFor(returnedIdentifier)?.id !== resourceSymbol.id || !doMatchingNodesCoverEveryPathAfterUsage(resourceNode, [returnStatement], context)) return null;
8883
- } else if (findTransparentExpressionRoot(resourceNode) !== callbackNode.body) return null;
8884
- const mappingRoot = findTransparentExpressionRoot(mappingCall);
8885
- const collectionDeclarator = mappingRoot.parent;
8886
- return isNodeOfType(collectionDeclarator, "VariableDeclarator") && collectionDeclarator.init === mappingRoot ? resolveExpressionKey$1(collectionDeclarator.id, context) : null;
8887
- };
8888
- const findContainingCollectionKey = (resourceNode, context) => {
8889
- const mappedCollectionKey = findMappedResourceCollectionKey(resourceNode, context);
8890
- if (mappedCollectionKey !== null) return mappedCollectionKey;
8891
- let currentNode = resourceNode;
8892
- let parentNode = currentNode.parent;
8893
- while (parentNode) {
8894
- if (isFunctionLike$1(parentNode)) return null;
8895
- if (isNodeOfType(parentNode, "VariableDeclarator") && parentNode.init === currentNode) return resolveExpressionKey$1(parentNode.id, context);
8896
- currentNode = parentNode;
8897
- parentNode = currentNode.parent;
8898
- }
8899
- return null;
8900
- };
8901
- const isWithinAssignmentTarget = (identifier) => {
8902
- let currentNode = identifier;
8903
- let parentNode = currentNode.parent;
8904
- while (parentNode) {
8905
- if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
8906
- if (isNodeOfType(parentNode, "UpdateExpression") || isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "delete") return parentNode.argument === currentNode;
8907
- if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
8908
- currentNode = parentNode;
8909
- parentNode = currentNode.parent;
8910
- }
8911
- return false;
8912
- };
8913
- const resolveStableValue = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
8914
- if (!expression) return null;
8915
- const unwrappedExpression = stripParenExpression(expression);
8916
- if (!isNodeOfType(unwrappedExpression, "Identifier")) return unwrappedExpression;
8917
- const symbol = context.scopes.symbolFor(unwrappedExpression);
8918
- const isUnreassignedMutableBinding = (symbol?.kind === "let" || symbol?.kind === "var") && isNodeOfType(symbol.declarationNode, "VariableDeclarator") && symbol.declarationNode.id === symbol.bindingIdentifier && symbol.references.every((reference) => reference.flag === "read" && !isWithinAssignmentTarget(reference.identifier)) && symbol.scope.symbols.filter((candidate) => candidate.name === symbol.name).length === 1;
8919
- const recursiveFunctionSymbol = symbol?.kind === "function" && isFunctionLike$1(symbol.declarationNode) ? context.scopes.ownScopeFor(symbol.declarationNode)?.symbols.find((candidate) => candidate.name === symbol.name && candidate.declarationNode === symbol.declarationNode) : null;
8920
- const isUnreassignedFunctionBinding = symbol?.kind === "function" && symbol.references.every((reference) => reference.flag === "read" && !isWithinAssignmentTarget(reference.identifier)) && (!recursiveFunctionSymbol || recursiveFunctionSymbol.references.every((reference) => reference.flag === "read" && !isWithinAssignmentTarget(reference.identifier))) && symbol.scope.symbols.filter((candidate) => candidate.name === symbol.name).length === 1;
8921
- if (!symbol || symbol.kind !== "const" && !isUnreassignedMutableBinding && !isUnreassignedFunctionBinding || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return unwrappedExpression;
8922
- visitedSymbolIds.add(symbol.id);
8923
- return resolveStableValue(symbol.initializer, context, visitedSymbolIds);
8924
- };
8925
- const resolveObjectExpression = (expression, context) => {
8926
- const resolvedExpression = resolveStableValue(expression, context);
8927
- return isNodeOfType(resolvedExpression, "ObjectExpression") ? resolvedExpression : null;
8928
- };
8929
- const getListenerAbortControllerKey = (usage, context) => {
8930
- if (usage.registrationVerbName !== "addEventListener" || !isNodeOfType(usage.node, "CallExpression")) return null;
8931
- const optionsArgument = usage.node.arguments?.[2];
8932
- const optionsObject = resolveObjectExpression(optionsArgument, context);
8933
- if (!optionsObject) return null;
8934
- for (const property of optionsObject.properties ?? []) {
8935
- if (!isNodeOfType(property, "Property") || getStaticPropertyKeyName(property) !== "signal") continue;
8936
- const signalKey = resolveExpressionKey$1(property.value, context);
8937
- return signalKey?.endsWith(".signal") ? signalKey.slice(0, -7) : null;
8938
- }
8939
- return null;
8940
- };
8941
- const SYNCHRONOUS_ITERATOR_METHOD_NAMES$1 = new Set([
8942
- "every",
8943
- "filter",
8944
- "flatMap",
8945
- "forEach",
8946
- "map",
8947
- "reduce",
8948
- "reduceRight",
8949
- "some"
8950
- ]);
8951
- const isSynchronousIteratorCallback = (functionNode) => {
8952
- const callNode = functionNode.parent;
8953
- if (!isNodeOfType(callNode, "CallExpression")) return false;
8954
- const callee = stripParenExpression(callNode.callee);
8955
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
8956
- if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments?.[1] === functionNode;
8957
- return SYNCHRONOUS_ITERATOR_METHOD_NAMES$1.has(callee.property.name) && callNode.arguments?.[0] === functionNode;
8958
- };
8959
- const findDirectCallForReference = (identifier) => {
8960
- const expressionRoot = findTransparentExpressionRoot(identifier);
8961
- const callNode = expressionRoot.parent;
8962
- return isNodeOfType(callNode, "CallExpression") && callNode.callee === expressionRoot ? callNode : null;
8963
- };
8964
- const findSingleDirectInvocation = (functionNode, caller, context) => {
8965
- const bindingIdentifier = getFunctionBindingIdentifier(functionNode);
8966
- if (!bindingIdentifier || resolveStableValue(bindingIdentifier, context) !== functionNode) return null;
8967
- const symbol = context.scopes.symbolFor(bindingIdentifier);
8968
- if (!symbol) return null;
8969
- const invocationCalls = symbol.references.flatMap((reference) => {
8970
- const callNode = findDirectCallForReference(reference.identifier);
8971
- return callNode ? [callNode] : [];
8972
- });
8973
- if (invocationCalls.length !== 1) return null;
8974
- const invocationCall = invocationCalls[0];
8975
- return findEnclosingFunction(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
8976
- };
8977
- const resolveCleanupPathAnchor = (usageNode, effectCallback, context) => {
8978
- const usageFunction = findEnclosingFunction(usageNode);
8979
- if (!usageFunction || usageFunction === effectCallback) return usageNode;
8980
- return findSingleDirectInvocation(usageFunction, effectCallback, context) ?? usageNode;
8981
- };
8982
- const resolveSingleAssignedCleanupFunction = (expression, usage, context) => {
8983
- const unwrappedExpression = stripParenExpression(expression);
8984
- if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
8985
- const symbol = context.scopes.symbolFor(unwrappedExpression);
8986
- const initializer = symbol?.initializer ? stripParenExpression(symbol.initializer) : null;
8987
- if (!symbol || symbol.kind !== "let" && symbol.kind !== "var" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier || !isNodeOfType(initializer, "Literal") || initializer.value !== null || symbol.scope.symbols.filter((candidate) => candidate.name === symbol.name).length !== 1) return null;
8988
- const assignmentReferences = symbol.references.filter((reference) => isWithinAssignmentTarget(reference.identifier));
8989
- if (assignmentReferences.length !== 1) return null;
8990
- const assignmentReference = assignmentReferences[0];
8991
- const assignmentTarget = findTransparentExpressionRoot(assignmentReference.identifier);
8992
- const assignmentNode = assignmentTarget.parent;
8993
- if (!isNodeOfType(assignmentNode, "AssignmentExpression") || assignmentNode.operator !== "=" || assignmentNode.left !== assignmentTarget || findEnclosingFunction(assignmentNode) !== findEnclosingFunction(usage.node) || !doMatchingNodesCoverEveryPathAfterUsage(usage.node, [assignmentNode], context)) return null;
8994
- const assignedValue = stripParenExpression(assignmentNode.right);
8995
- return isFunctionLike$1(assignedValue) ? assignedValue : null;
8996
- };
8997
- const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visitedFunctions = /* @__PURE__ */ new Set()) => {
8998
- if (!isFunctionLike$1(cleanupFunction) || visitedFunctions.has(cleanupFunction)) return false;
8999
- visitedFunctions.add(cleanupFunction);
9000
- let didCleanupFunctionMatch = false;
9001
- walkAst(cleanupFunction.body, (cleanupChild) => {
9002
- if (didCleanupFunctionMatch) return false;
9003
- if (cleanupChild !== cleanupFunction.body && isFunctionLike$1(cleanupChild) && !isSynchronousIteratorCallback(cleanupChild)) return false;
9004
- if (doesReleaseCallMatchUsage(cleanupChild, usage, context)) {
9005
- didCleanupFunctionMatch = true;
9006
- return false;
9007
- }
9008
- const helperCall = isNodeOfType(cleanupChild, "ChainExpression") ? cleanupChild.expression : cleanupChild;
9009
- if (!isNodeOfType(helperCall, "CallExpression")) return;
9010
- const stableHelperFunction = resolveStableValue(helperCall.callee, context);
9011
- const helperFunction = isNodeOfType(stableHelperFunction, "Identifier") ? resolveSingleAssignedCleanupFunction(stableHelperFunction, usage, context) : stableHelperFunction;
9012
- if (helperFunction && isFunctionLike$1(helperFunction) && doesCleanupFunctionReleaseUsage(helperFunction, usage, context, visitedFunctions)) {
9013
- didCleanupFunctionMatch = true;
9014
- return false;
9015
- }
8769
+ const cleanupReturnRunsAfterUsage = (returnStatement, usages) => {
8770
+ if (returnStatement.argument && isCleanupReturningSubscribeLikeCallExpression(returnStatement.argument)) return true;
8771
+ const returnStart = getRangeStart(returnStatement);
8772
+ if (returnStart === null) return true;
8773
+ return usages.some((usage) => {
8774
+ const usageStart = getRangeStart(usage.node);
8775
+ return usageStart === null || usageStart < returnStart;
9016
8776
  });
9017
- return didCleanupFunctionMatch;
9018
8777
  };
9019
- const effectHasCleanupForUsage = (callback, usage, context) => {
8778
+ const effectHasCleanupReturn = (callback, usages) => {
9020
8779
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
9021
- if (usage.kind === "subscribe" && findEnclosingFunction(usage.node) === callback && doesResourceResultEscape(usage.node, true) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
9022
- if (!isNodeOfType(callback.body, "BlockStatement")) return callback.body === usage.node && isCleanupReturningSubscribeLikeCallExpression(callback.body);
9023
- const matchingCleanupReturns = [];
8780
+ if (!isNodeOfType(callback.body, "BlockStatement")) return isCleanupReturningSubscribeLikeCallExpression(callback.body);
8781
+ const cleanupBindings = collectCleanupBindings(callback);
8782
+ let didFindCleanupReturn = false;
9024
8783
  walkInsideStatementBlocks(callback.body, (child) => {
8784
+ if (didFindCleanupReturn) return;
9025
8785
  if (!isNodeOfType(child, "ReturnStatement")) return;
9026
- const returnStart = getRangeStart(child);
9027
- const usageStart = getRangeStart(usage.node);
9028
- if (returnStart !== null && usageStart !== null && returnStart < usageStart) return;
9029
- const returnedValue = child.argument ? stripParenExpression(child.argument) : null;
9030
- if (!returnedValue) return;
9031
- if (usage.kind === "subscribe" && (returnedValue === usage.node || getRangeStart(returnedValue) !== null && getRangeStart(returnedValue) === getRangeStart(usage.node)) && isCleanupReturningSubscribeLikeCallExpression(returnedValue)) {
9032
- matchingCleanupReturns.push(child);
9033
- return;
9034
- }
9035
- if (usage.kind === "subscribe" && isNodeOfType(returnedValue, "Identifier") && usage.handleKey !== null && resolveExpressionKey$1(returnedValue, context) === usage.handleKey && isCleanupReturningSubscribeLikeCallExpression(usage.node)) {
9036
- matchingCleanupReturns.push(child);
9037
- return;
9038
- }
9039
- if (isNodeOfType(returnedValue, "Identifier")) {
9040
- if (returnedValue.name === "undefined" && context.scopes.isGlobalReference(returnedValue)) return;
9041
- const returnedKey = resolveExpressionKey$1(returnedValue, context);
9042
- if (usage.handleKey !== null && returnedKey === usage.handleKey) return;
9043
- if (!context.scopes.symbolFor(returnedValue)?.initializer) return;
9044
- }
9045
- const cleanupFunction = resolveStableValue(returnedValue, context);
9046
- if (!cleanupFunction || !isFunctionLike$1(cleanupFunction)) return;
9047
- if (doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context)) matchingCleanupReturns.push(child);
8786
+ if (!cleanupReturnRunsAfterUsage(child, usages)) return;
8787
+ if (isCleanupReturn(child.argument, cleanupBindings.cleanupFunctionNames, cleanupBindings.subscriptionNames, { allowOpaqueReturn: true })) didFindCleanupReturn = true;
9048
8788
  });
9049
- return doMatchingNodesCoverEveryPathAfterUsage(resolveCleanupPathAnchor(usage.node, callback, context), matchingCleanupReturns, context);
9050
- };
9051
- const findFirstUsageWithoutCleanup = (callback, usages, context) => {
9052
- for (const usage of usages) if (!effectHasCleanupForUsage(callback, usage, context)) return usage;
9053
- return null;
9054
- };
9055
- const isLocalAbortControllerExpression = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
9056
- const unwrappedExpression = stripParenExpression(expression);
9057
- if (isNodeOfType(unwrappedExpression, "NewExpression") && isNodeOfType(unwrappedExpression.callee, "Identifier") && unwrappedExpression.callee.name === "AbortController") return true;
9058
- if (isNodeOfType(unwrappedExpression, "MemberExpression")) return isLocalAbortControllerExpression(unwrappedExpression.object, context, visitedSymbolIds);
9059
- if (!isNodeOfType(unwrappedExpression, "Identifier")) return false;
9060
- const symbol = context.scopes.symbolFor(unwrappedExpression);
9061
- if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
9062
- visitedSymbolIds.add(symbol.id);
9063
- if (symbol.initializer) return isLocalAbortControllerExpression(symbol.initializer, context, visitedSymbolIds);
9064
- const bindingProperty = symbol.bindingIdentifier.parent;
9065
- const bindingPattern = bindingProperty?.parent;
9066
- const variableDeclarator = bindingPattern?.parent;
9067
- return Boolean(isNodeOfType(bindingProperty, "Property") && isNodeOfType(bindingPattern, "ObjectPattern") && isNodeOfType(variableDeclarator, "VariableDeclarator") && variableDeclarator.init && isLocalAbortControllerExpression(variableDeclarator.init, context, visitedSymbolIds));
8789
+ return didFindCleanupReturn;
9068
8790
  };
9069
- const isSelfReleasingListenerOptionProperty = (property, context) => {
8791
+ const EMPTY_NAME_SET$1 = /* @__PURE__ */ new Set();
8792
+ const isSelfReleasingListenerOptionProperty = (property) => {
9070
8793
  if (!isNodeOfType(property, "Property")) return false;
9071
8794
  const keyName = isNodeOfType(property.key, "Identifier") ? property.key.name : isNodeOfType(property.key, "Literal") ? property.key.value : null;
9072
- if (keyName === "signal") return !isLocalAbortControllerExpression(property.value, context);
8795
+ if (keyName === "signal") return true;
9073
8796
  if (keyName !== "once") return false;
9074
8797
  return isNodeOfType(property.value, "Literal") && property.value.value === true;
9075
8798
  };
9076
- const hasSelfReleasingListenerOptions = (node, context) => isNodeOfType(node, "CallExpression") && (node.arguments ?? []).some((argument) => isNodeOfType(argument, "ObjectExpression") && (argument.properties ?? []).some((property) => isSelfReleasingListenerOptionProperty(property, context)));
8799
+ const hasSelfReleasingListenerOptions = (node) => isNodeOfType(node, "CallExpression") && (node.arguments ?? []).some((argument) => isNodeOfType(argument, "ObjectExpression") && (argument.properties ?? []).some(isSelfReleasingListenerOptionProperty));
9077
8800
  const PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB = new Map([
9078
8801
  ["addEventListener", new Set(["removeEventListener", "abort"])],
9079
8802
  ["addListener", new Set([
@@ -9099,185 +8822,85 @@ const UNIVERSAL_RELEASE_VERB_NAMES = new Set([
9099
8822
  "teardown"
9100
8823
  ]);
9101
8824
  const SOCKET_RELEASE_VERB_NAMES = new Set(["close"]);
8825
+ const INTERVAL_RELEASE_VERB_NAMES = new Set(["clearInterval"]);
9102
8826
  const getReleaseVerbName = (node) => {
8827
+ if (!isReleaseLikeCall(node, EMPTY_NAME_SET$1, EMPTY_NAME_SET$1)) return null;
9103
8828
  const callNode = isNodeOfType(node, "ChainExpression") ? node.expression : node;
9104
8829
  if (!isNodeOfType(callNode, "CallExpression")) return null;
9105
8830
  const callee = isNodeOfType(callNode.callee, "ChainExpression") ? callNode.callee.expression : callNode.callee;
9106
- if (isNodeOfType(callee, "Identifier")) return TIMER_CLEANUP_CALLEE_NAMES.has(callee.name) || GLOBAL_RELEASE_METHOD_NAMES.has(callee.name) || UNIVERSAL_RELEASE_VERB_NAMES.has(callee.name) ? callee.name : null;
9107
- if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) {
9108
- const methodName = callee.property.name;
9109
- return GLOBAL_RELEASE_METHOD_NAMES.has(methodName) || BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(methodName) || methodName === "on" ? methodName : null;
9110
- }
8831
+ if (isNodeOfType(callee, "Identifier")) return callee.name;
8832
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
9111
8833
  return null;
9112
8834
  };
9113
- const doesReleaseCallMatchUsage = (node, usage, context) => {
9114
- const callNode = isNodeOfType(node, "ChainExpression") ? node.expression : node;
9115
- if (!isNodeOfType(callNode, "CallExpression")) return false;
9116
- const callee = isNodeOfType(callNode.callee, "ChainExpression") ? callNode.callee.expression : callNode.callee;
9117
- if (usage.kind === "timer") {
9118
- const expectedCleanupName = usage.registrationVerbName === "setInterval" ? "clearInterval" : "clearTimeout";
9119
- if (!isNodeOfType(callee, "Identifier") || !TIMER_CLEANUP_CALLEE_NAMES.has(callee.name) || callee.name !== expectedCleanupName) return false;
9120
- if (usage.handleKey !== null && resolveExpressionKey$1(callNode.arguments?.[0], context) === usage.handleKey) return true;
9121
- const collectionKey = findContainingCollectionKey(usage.node, context);
9122
- return collectionKey !== null && collectionKey === resolveIteratorCollectionKey(callNode.arguments?.[0], context);
9123
- }
9124
- if (isNodeOfType(callee, "Identifier") && usage.kind === "subscribe" && usage.handleKey !== null && resolveExpressionKey$1(callee, context) === usage.handleKey && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
9125
- const releaseVerbName = getReleaseVerbName(callNode);
9126
- if (!releaseVerbName) return false;
9127
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
9128
- const releaseReceiverKey = resolveExpressionKey$1(callee.object, context);
9129
- if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
9130
- if (usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub" || releaseVerbName === "close" || releaseVerbName === "unwatch" || releaseVerbName === "unlisten" || BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(releaseVerbName))) return true;
9131
- if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
9132
- if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) return false;
9133
- const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
9134
- if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
9135
- const releaseEventKey = resolveExpressionKey$1(callNode.arguments?.[0], context);
9136
- if (usage.eventKey !== null && releaseEventKey !== null && usage.eventKey !== releaseEventKey) {
9137
- const usageIteratorCollectionKey = isNodeOfType(usage.node, "CallExpression") ? resolveIteratorCollectionKey(usage.node.arguments?.[0], context) : null;
9138
- const releaseIteratorCollectionKey = resolveIteratorCollectionKey(callNode.arguments?.[0], context);
9139
- if (usageIteratorCollectionKey === null || usageIteratorCollectionKey !== releaseIteratorCollectionKey) return false;
9140
- }
9141
- if (releaseVerbName === "on") {
9142
- const handlerArgument = callNode.arguments?.[1];
9143
- return isNodeOfType(handlerArgument, "Literal") && handlerArgument.value === null;
9144
- }
9145
- if (releaseVerbName === "removeEventListener" || releaseVerbName === "removeListener" || releaseVerbName === "off") {
9146
- const releaseHandler = callNode.arguments?.[1];
9147
- if (!releaseHandler) return releaseVerbName === "off";
9148
- return usage.handlerKey !== null && resolveExpressionKey$1(releaseHandler, context) === usage.handlerKey;
9149
- }
9150
- if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
9151
- return true;
9152
- };
9153
8835
  const matchesPairedReleaseVerb = (releaseVerbName, pairedVerbNames) => pairedVerbNames.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName);
9154
- const isReturnedEffectCleanupFunction = (functionNode) => {
9155
- let currentNode = functionNode;
9156
- let parentNode = currentNode.parent;
9157
- while (isNodeOfType(parentNode, "ChainExpression") || isNodeOfType(parentNode, "TSAsExpression") || isNodeOfType(parentNode, "TSNonNullExpression")) {
9158
- currentNode = parentNode;
9159
- parentNode = currentNode.parent;
9160
- }
9161
- if (!isNodeOfType(parentNode, "ReturnStatement") || parentNode.argument !== currentNode) return false;
9162
- const effectCallback = findEnclosingFunction(parentNode);
9163
- const effectCall = effectCallback?.parent;
9164
- return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
9165
- };
9166
- const isPotentiallyReachableFunction = (functionNode, context) => {
9167
- if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode)) return true;
9168
- const bindingIdentifier = getFunctionBindingIdentifier(functionNode);
9169
- if (!bindingIdentifier) return false;
9170
- const symbol = context.scopes.symbolFor(bindingIdentifier);
9171
- if (!symbol) return false;
9172
- return symbol.references.some((reference) => findEnclosingFunction(reference.identifier) !== functionNode);
9173
- };
9174
- const isReleaseReachableForUsage = (releaseNode, usage, context) => {
9175
- if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
9176
- const releaseFunction = findEnclosingFunction(releaseNode);
9177
- if (!releaseFunction) return true;
9178
- if (releaseFunction === findEnclosingFunction(usage.node)) return true;
9179
- return isPotentiallyReachableFunction(releaseFunction, context);
9180
- };
9181
- const fileContainsReleaseForUsage = (usage, context) => {
9182
- let programNode = usage.node;
9183
- while (programNode.parent) programNode = programNode.parent;
9184
- let didFindRelease = false;
9185
- walkAst(programNode, (child) => {
9186
- if (didFindRelease) return false;
9187
- if (doesReleaseCallMatchUsage(child, usage, context) && isReleaseReachableForUsage(child, usage, context)) {
9188
- didFindRelease = true;
8836
+ const bodyContainsPairedReleaseCall = (body, pairedVerbNames) => {
8837
+ let didFindPairedRelease = false;
8838
+ walkAst(body, (child) => {
8839
+ if (didFindPairedRelease) return false;
8840
+ if (child !== body && isFunctionLike$1(child)) return false;
8841
+ const releaseVerbName = getReleaseVerbName(child);
8842
+ if (releaseVerbName !== null && matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) {
8843
+ didFindPairedRelease = true;
9189
8844
  return false;
9190
8845
  }
9191
8846
  });
9192
- return didFindRelease;
8847
+ return didFindPairedRelease;
9193
8848
  };
9194
- const isUseSyncExternalStoreSubscribeFunction = (functionNode, context) => {
9195
- const bindingIdentifier = getFunctionBindingIdentifier(functionNode);
9196
- if (!bindingIdentifier) return false;
9197
- const visitedSymbolIds = /* @__PURE__ */ new Set();
9198
- const isSubscribeBinding = (candidateBinding) => {
9199
- const symbol = context.scopes.symbolFor(candidateBinding);
9200
- if (!symbol || visitedSymbolIds.has(symbol.id) || symbol.references.length === 0) return false;
9201
- visitedSymbolIds.add(symbol.id);
9202
- return symbol.references.every((reference) => {
9203
- const referenceRoot = findTransparentExpressionRoot(reference.identifier);
9204
- const referenceParent = referenceRoot.parent;
9205
- if (isNodeOfType(referenceParent, "CallExpression") && referenceParent.arguments?.[0] === referenceRoot) return isReactApiCall(referenceParent, "useSyncExternalStore", context.scopes);
9206
- const aliasDeclaration = referenceParent?.parent;
9207
- return Boolean(isNodeOfType(referenceParent, "VariableDeclarator") && referenceParent.init === referenceRoot && isNodeOfType(referenceParent.id, "Identifier") && isNodeOfType(aliasDeclaration, "VariableDeclaration") && aliasDeclaration.kind === "const" && isSubscribeBinding(referenceParent.id));
9208
- });
9209
- };
9210
- return isSubscribeBinding(bindingIdentifier);
8849
+ const fileReleaseVerbNamesCache = /* @__PURE__ */ new WeakMap();
8850
+ const collectFileReleaseVerbNames = (anyNode) => {
8851
+ let programNode = anyNode;
8852
+ while (programNode.parent) programNode = programNode.parent;
8853
+ const cached = fileReleaseVerbNamesCache.get(programNode);
8854
+ if (cached) return cached;
8855
+ const releaseVerbNames = /* @__PURE__ */ new Set();
8856
+ walkAst(programNode, (child) => {
8857
+ const releaseVerbName = getReleaseVerbName(child);
8858
+ if (releaseVerbName !== null) releaseVerbNames.add(releaseVerbName);
8859
+ });
8860
+ fileReleaseVerbNamesCache.set(programNode, releaseVerbNames);
8861
+ return releaseVerbNames;
9211
8862
  };
9212
- const doesResourceResultEscape = (resourceNode, allowConciseReturnEscape) => {
9213
- let currentNode = resourceNode;
9214
- let parentNode = currentNode.parent;
9215
- while (parentNode) {
9216
- if (isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode) return true;
9217
- if (isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode && allowConciseReturnEscape) return true;
9218
- if (isNodeOfType(parentNode, "ChainExpression") || isNodeOfType(parentNode, "TSAsExpression") || isNodeOfType(parentNode, "TSNonNullExpression")) {
9219
- currentNode = parentNode;
9220
- parentNode = currentNode.parent;
9221
- continue;
9222
- }
9223
- return false;
9224
- }
8863
+ const fileContainsPairedReleaseCall = (registrationCall, registrationVerbName) => {
8864
+ const fileReleaseVerbNames = collectFileReleaseVerbNames(registrationCall);
8865
+ const pairedVerbNames = PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(registrationVerbName);
8866
+ if (!pairedVerbNames) return fileReleaseVerbNames.size > 0;
8867
+ for (const releaseVerbName of fileReleaseVerbNames) if (matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return true;
9225
8868
  return false;
9226
8869
  };
9227
- const findRetainedFunctionLeak = (retainedFunction, context) => {
8870
+ const findRetainedFunctionLeak = (retainedFunction) => {
9228
8871
  if (!isFunctionLike$1(retainedFunction)) return null;
9229
8872
  const body = retainedFunction.body;
9230
8873
  if (!body) return null;
8874
+ const conciseReturnValue = isNodeOfType(body, "BlockStatement") ? null : body;
9231
8875
  let leak = null;
9232
- const allowConciseReturnEscape = !isInlineRetainedHandlerFunction(retainedFunction, context);
9233
- const isExternalStoreSubscribeFunction = isUseSyncExternalStoreSubscribeFunction(retainedFunction, context);
9234
- const hasReleaseForUsage = (usage) => isExternalStoreSubscribeFunction ? effectHasCleanupForUsage(retainedFunction, usage, context) : fileContainsReleaseForUsage(usage, context);
9235
8876
  walkAst(body, (child) => {
9236
8877
  if (leak !== null) return false;
9237
8878
  if (isFunctionLike$1(child)) return false;
9238
- if (isSocketConstruction(child) && !doesResourceResultEscape(child, false)) {
9239
- const socketUsage = {
8879
+ if (child === conciseReturnValue && isNodeOfType(child, "CallExpression")) return;
8880
+ if (isSocketConstruction(child) && isResultDiscardedCall(child) && !bodyContainsPairedReleaseCall(body, SOCKET_RELEASE_VERB_NAMES)) {
8881
+ leak = {
9240
8882
  kind: "socket",
9241
8883
  node: child,
9242
- resourceName: isNodeOfType(child.callee, "Identifier") ? child.callee.name : "WebSocket",
9243
- handleKey: findAssignedResourceKey(child, context),
9244
- receiverKey: null,
9245
- registrationVerbName: null,
9246
- eventKey: null,
9247
- handlerKey: null
8884
+ resourceName: isNodeOfType(child.callee, "Identifier") ? child.callee.name : "WebSocket"
9248
8885
  };
9249
- if (!hasReleaseForUsage(socketUsage)) {
9250
- leak = socketUsage;
9251
- return false;
9252
- }
8886
+ return false;
9253
8887
  }
9254
8888
  if (!isNodeOfType(child, "CallExpression")) return;
9255
- if (isNodeOfType(child.callee, "Identifier") && child.callee.name === "setInterval" && !doesResourceResultEscape(child, allowConciseReturnEscape)) {
9256
- const timerUsage = {
8889
+ if (isNodeOfType(child.callee, "Identifier") && child.callee.name === "setInterval" && isResultDiscardedCall(child) && !bodyContainsPairedReleaseCall(body, INTERVAL_RELEASE_VERB_NAMES)) {
8890
+ leak = {
9257
8891
  kind: "timer",
9258
8892
  node: child,
9259
- resourceName: "setInterval",
9260
- handleKey: findAssignedResourceKey(child, context),
9261
- receiverKey: null,
9262
- registrationVerbName: "setInterval",
9263
- eventKey: null,
9264
- handlerKey: null
8893
+ resourceName: "setInterval"
9265
8894
  };
9266
- if (!hasReleaseForUsage(timerUsage)) {
9267
- leak = timerUsage;
9268
- return false;
9269
- }
8895
+ return false;
9270
8896
  }
9271
- if (isSubscribeOrObserveCall(child) && !doesResourceResultEscape(child, allowConciseReturnEscape)) {
9272
- const registrationDetails = getCallRegistrationDetails(child, context);
9273
- const subscriptionUsage = {
8897
+ if (isSubscribeOrObserveCall(child) && isResultDiscardedCall(child)) {
8898
+ const registrationVerbName = isNodeOfType(child.callee, "MemberExpression") && isNodeOfType(child.callee.property, "Identifier") ? child.callee.property.name : "subscribe";
8899
+ if (!hasSelfReleasingListenerOptions(child) && !fileContainsPairedReleaseCall(child, registrationVerbName)) leak = {
9274
8900
  kind: "subscribe",
9275
8901
  node: child,
9276
- resourceName: registrationDetails.registrationVerbName ?? "subscribe",
9277
- handleKey: findAssignedResourceKey(child, context),
9278
- ...registrationDetails
8902
+ resourceName: registrationVerbName
9279
8903
  };
9280
- if (!hasSelfReleasingListenerOptions(child, context) && !hasReleaseForUsage(subscriptionUsage)) leak = subscriptionUsage;
9281
8904
  return false;
9282
8905
  }
9283
8906
  });
@@ -9289,26 +8912,6 @@ const isRetainedComponentScopeFunction = (functionNode) => {
9289
8912
  if (!isNodeOfType(functionNode.parent, "VariableDeclarator")) return false;
9290
8913
  return enclosingComponentOrHookName(functionNode) !== null;
9291
8914
  };
9292
- const isDirectJsxEventHandlerValue = (expression) => {
9293
- const expressionRoot = findTransparentExpressionRoot(expression);
9294
- const expressionContainer = expressionRoot.parent;
9295
- return isNodeOfType(expressionContainer, "JSXExpressionContainer") && expressionContainer.expression === expressionRoot && isEventHandlerAttribute(expressionContainer.parent);
9296
- };
9297
- const isInlineRetainedHandlerFunction = (functionNode, context) => {
9298
- if (!isFunctionLike$1(functionNode)) return false;
9299
- const functionRoot = findTransparentExpressionRoot(functionNode);
9300
- const callbackCall = functionRoot.parent;
9301
- if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot && isHookCall$2(callbackCall, "useCallback") && isDirectJsxEventHandlerValue(callbackCall)) return true;
9302
- const parentNode = functionNode.parent;
9303
- if (isDirectJsxEventHandlerValue(functionNode)) return true;
9304
- if (!isNodeOfType(parentNode, "Property") || parentNode.value !== functionNode || parentNode.computed) return false;
9305
- const propertyName = getStaticPropertyKeyName(parentNode);
9306
- if (!propertyName || !/^on[A-Z]/.test(propertyName)) return false;
9307
- const objectExpression = parentNode.parent;
9308
- if (!isNodeOfType(objectExpression, "ObjectExpression")) return false;
9309
- const objectParent = objectExpression.parent;
9310
- return (isNodeOfType(objectParent, "CallExpression") && objectParent.arguments.some((argument) => argument === objectExpression) || isNodeOfType(objectParent, "JSXExpressionContainer")) && findRenderPhaseComponentOrHook(parentNode, context.scopes) !== null;
9311
- };
9312
8915
  const effectNeedsCleanup = defineRule({
9313
8916
  id: "effect-needs-cleanup",
9314
8917
  title: "Effect subscription or timer never cleaned up",
@@ -9317,8 +8920,7 @@ const effectNeedsCleanup = defineRule({
9317
8920
  recommendation: "Return a cleanup function that stops the subscription or timer: `return () => target.removeEventListener(name, handler)` for listeners, `return () => clearInterval(id)` or `clearTimeout(id)` for timers, `return () => observer.disconnect()` for observers, `return () => socket.close()` for connections, or `return unsubscribe` if the subscribe call already gave you one.",
9318
8921
  create: (context) => {
9319
8922
  const reportRetainedLeak = (retainedFunction) => {
9320
- if (!isPotentiallyReachableFunction(retainedFunction, context)) return;
9321
- const leak = findRetainedFunctionLeak(retainedFunction, context);
8923
+ const leak = findRetainedFunctionLeak(retainedFunction);
9322
8924
  if (!leak) return;
9323
8925
  const resourceNoun = RESOURCE_NOUN_BY_KIND[leak.kind];
9324
8926
  context.report({
@@ -9330,31 +8932,30 @@ const effectNeedsCleanup = defineRule({
9330
8932
  CallExpression(node) {
9331
8933
  if (isHookCall$2(node, "useCallback")) {
9332
8934
  const retainedCallback = getEffectCallback(node);
9333
- if (retainedCallback && !isInlineRetainedHandlerFunction(retainedCallback, context)) reportRetainedLeak(retainedCallback);
8935
+ if (retainedCallback) reportRetainedLeak(retainedCallback);
9334
8936
  return;
9335
8937
  }
9336
- if (!isHookCall$2(node, CLEANUP_EFFECT_HOOK_NAMES)) return;
8938
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
9337
8939
  const callback = getEffectCallback(node);
9338
8940
  if (!callback) return;
9339
- const usages = removeSynchronouslyReleasedUsages(callback, findSubscribeLikeUsages(callback, context), context);
8941
+ const usages = removeSynchronouslyReleasedUsages(callback, findSubscribeLikeUsages(callback));
9340
8942
  if (usages.length === 0) return;
9341
- const firstUsage = findFirstUsageWithoutCleanup(callback, usages, context);
9342
- if (!firstUsage) return;
8943
+ if (effectHasCleanupReturn(callback, usages)) return;
8944
+ const firstUsage = usages[0];
9343
8945
  const resourceNoun = RESOURCE_NOUN_BY_KIND[firstUsage.kind];
9344
- const hookName = getCalleeName$2(node) ?? "effect";
9345
8946
  context.report({
9346
8947
  node,
9347
- message: `\`${firstUsage.resourceName}\` creates a ${resourceNoun} in ${hookName} without returning cleanup. Return a cleanup function so it does not leak after unmount.`
8948
+ message: `\`${firstUsage.resourceName}\` creates a ${resourceNoun} in useEffect without returning cleanup. Return a cleanup function so it does not leak after unmount.`
9348
8949
  });
9349
8950
  },
9350
8951
  FunctionDeclaration(node) {
9351
8952
  if (isRetainedComponentScopeFunction(node)) reportRetainedLeak(node);
9352
8953
  },
9353
8954
  ArrowFunctionExpression(node) {
9354
- if (isRetainedComponentScopeFunction(node) || isInlineRetainedHandlerFunction(node, context)) reportRetainedLeak(node);
8955
+ if (isRetainedComponentScopeFunction(node)) reportRetainedLeak(node);
9355
8956
  },
9356
8957
  FunctionExpression(node) {
9357
- if (isRetainedComponentScopeFunction(node) || isInlineRetainedHandlerFunction(node, context)) reportRetainedLeak(node);
8958
+ if (isRetainedComponentScopeFunction(node)) reportRetainedLeak(node);
9358
8959
  }
9359
8960
  };
9360
8961
  }
@@ -9968,18 +9569,13 @@ const unwrapExpression$3 = (node) => {
9968
9569
  return current;
9969
9570
  };
9970
9571
  /**
9971
- * Get the hook name from a direct, wrapped, namespaced, or immutable
9972
- * React import alias call.
9572
+ * Get the hook name from a call expression's callee, regardless of
9573
+ * whether the hook is called as `useFoo()` (Identifier) or
9574
+ * `React.useFoo()` (MemberExpression).
9973
9575
  */
9974
- const getHookName = (callee, scopes) => {
9975
- const strippedCallee = unwrapExpression$3(callee);
9976
- if (isNodeOfType(strippedCallee, "Identifier")) {
9977
- const resolvedSymbol = scopes ? resolveConstIdentifierAlias(strippedCallee, scopes) : null;
9978
- const importDeclaration = resolvedSymbol?.declarationNode.parent;
9979
- if (resolvedSymbol?.kind === "import" && importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react") return getImportedName(resolvedSymbol.declarationNode) ?? strippedCallee.name;
9980
- return strippedCallee.name;
9981
- }
9982
- if (isNodeOfType(strippedCallee, "MemberExpression") && !strippedCallee.computed && isNodeOfType(strippedCallee.property, "Identifier")) return strippedCallee.property.name;
9576
+ const getHookName = (callee) => {
9577
+ if (isNodeOfType(callee, "Identifier")) return callee.name;
9578
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
9983
9579
  return null;
9984
9580
  };
9985
9581
  const FUNCTION_SCOPE_KINDS = new Set([
@@ -10164,7 +9760,7 @@ const STABLE_IDENTITY_WRAPPER_HOOK_NAMES = new Set([
10164
9760
  * - primitive-literal local consts (the value never changes
10165
9761
  * between renders unless the literal does)
10166
9762
  */
10167
- const symbolHasStableHookOrigin = (symbol, scopes) => {
9763
+ const symbolHasStableHookOrigin = (symbol) => {
10168
9764
  if (symbol.references.some((reference) => reference.flag !== "read")) return false;
10169
9765
  let declarator = symbol.declarationNode;
10170
9766
  while (declarator && declarator.type !== "VariableDeclarator") declarator = declarator.parent ?? null;
@@ -10177,7 +9773,7 @@ const symbolHasStableHookOrigin = (symbol, scopes) => {
10177
9773
  if (isNodeOfType(initializer, "TemplateLiteral") && getStaticTemplateLiteralValue(initializer) !== null) return true;
10178
9774
  }
10179
9775
  if (!isNodeOfType(initializer, "CallExpression")) return false;
10180
- const initializerHookName = getHookName(initializer.callee, scopes);
9776
+ const initializerHookName = getHookName(initializer.callee);
10181
9777
  if (!initializerHookName) return false;
10182
9778
  if (initializerHookName === "useRef") return true;
10183
9779
  if (initializerHookName === "useEffectEvent") return true;
@@ -10212,7 +9808,7 @@ const symbolHasStableMemoizedOrigin = (symbol, scopes, visitedSymbolIds) => {
10212
9808
  if (!declarator.init) return false;
10213
9809
  const initializer = unwrapExpression$3(declarator.init);
10214
9810
  if (!isNodeOfType(initializer, "CallExpression")) return false;
10215
- const initializerHookName = getHookName(initializer.callee, scopes);
9811
+ const initializerHookName = getHookName(initializer.callee);
10216
9812
  if (!initializerHookName || !MEMOIZING_HOOK_NAMES.has(initializerHookName)) return false;
10217
9813
  const depsArgument = initializer.arguments[1];
10218
9814
  if (!depsArgument || !isAstNode(depsArgument)) return false;
@@ -10242,7 +9838,7 @@ const getObjectPropertyValue = (objectExpression, propertyName) => {
10242
9838
  }
10243
9839
  return null;
10244
9840
  };
10245
- const isStableRefContainerCapture = (symbol, depKey, scopes) => {
9841
+ const isStableRefContainerCapture = (symbol, depKey) => {
10246
9842
  if (symbol.kind !== "const") return false;
10247
9843
  if (!depKey.startsWith(`${symbol.name}.`)) return false;
10248
9844
  if (symbol.references.some((reference) => reference.flag !== "read")) return false;
@@ -10256,7 +9852,7 @@ const isStableRefContainerCapture = (symbol, depKey, scopes) => {
10256
9852
  if (!propertyValue) return false;
10257
9853
  currentValue = propertyValue;
10258
9854
  }
10259
- return isNodeOfType(currentValue, "CallExpression") && getHookName(currentValue.callee, scopes) === "useRef";
9855
+ return isNodeOfType(currentValue, "CallExpression") && getHookName(currentValue.callee) === "useRef";
10260
9856
  };
10261
9857
  const symbolHasStableFunctionOrigin = (symbol, scopes, visitedSymbolIds) => {
10262
9858
  if (visitedSymbolIds.has(symbol.id)) return true;
@@ -10274,13 +9870,7 @@ const symbolHasStableFunctionOrigin = (symbol, scopes, visitedSymbolIds) => {
10274
9870
  }
10275
9871
  return true;
10276
9872
  };
10277
- const symbolHasStableImportedAlias = (symbol, scopes) => {
10278
- if (symbol.kind !== "const") return false;
10279
- if (symbol.references.some((reference) => reference.flag !== "read")) return false;
10280
- const resolvedSymbol = resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes);
10281
- return resolvedSymbol !== null && resolvedSymbol !== symbol && resolvedSymbol.kind === "import";
10282
- };
10283
- const symbolHasStableValue = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => symbolHasStableHookOrigin(symbol, scopes) || symbolHasStableImportedAlias(symbol, scopes) || symbolHasStableFunctionOrigin(symbol, scopes, visitedSymbolIds) || symbolHasStableMemoizedOrigin(symbol, scopes, visitedSymbolIds);
9873
+ const symbolHasStableValue = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => symbolHasStableHookOrigin(symbol) || symbolHasStableFunctionOrigin(symbol, scopes, visitedSymbolIds) || symbolHasStableMemoizedOrigin(symbol, scopes, visitedSymbolIds);
10284
9874
  //#endregion
10285
9875
  //#region src/plugin/utils/symbol-has-react-use-effect-event-origin.ts
10286
9876
  const symbolHasReactUseEffectEventOrigin = (symbol, scopes) => {
@@ -10468,18 +10058,10 @@ const collectCaptureDepKeys = (callback, scopes) => {
10468
10058
  }
10469
10059
  const depKey = computeDepKey(reference);
10470
10060
  if (!depKey) continue;
10471
- if (isStableRefContainerCapture(symbol, depKey, scopes)) {
10061
+ if (isStableRefContainerCapture(symbol, depKey)) {
10472
10062
  stableCapturedNames.add(depKey);
10473
10063
  continue;
10474
10064
  }
10475
- if (depKey === symbol.name) {
10476
- const identitySourceKeys = resolveReactiveIdentitySourceKeys(symbol, scopes);
10477
- if (identitySourceKeys) {
10478
- if (identitySourceKeys.size === 0) stableCapturedNames.add(depKey);
10479
- for (const identitySourceKey of identitySourceKeys) keys.add(identitySourceKey);
10480
- continue;
10481
- }
10482
- }
10483
10065
  keys.add(depKey);
10484
10066
  }
10485
10067
  return {
@@ -10508,60 +10090,12 @@ const hasComputedMemberExpression = (node) => {
10508
10090
  if (stripped.computed) return true;
10509
10091
  return hasComputedMemberExpression(stripped.object);
10510
10092
  };
10511
- const mergeIdentitySourceKeys = (expressions, scopes, visitedSymbolIds) => {
10512
- const identitySourceKeys = /* @__PURE__ */ new Set();
10513
- for (const expression of expressions) {
10514
- const expressionSourceKeys = resolveIdentitySourceKeysFromExpression(expression, scopes, visitedSymbolIds);
10515
- if (!expressionSourceKeys) return null;
10516
- for (const expressionSourceKey of expressionSourceKeys) identitySourceKeys.add(expressionSourceKey);
10517
- }
10518
- return identitySourceKeys;
10519
- };
10520
- const resolveIdentitySourceKeysFromExpression = (expression, scopes, visitedSymbolIds) => {
10521
- const stripped = unwrapExpression$3(expression);
10522
- if (isNodeOfType(stripped, "Literal") && (stripped.value === null || typeof stripped.value === "string" || typeof stripped.value === "number" || typeof stripped.value === "boolean") || isNodeOfType(stripped, "TemplateLiteral") && getStaticTemplateLiteralValue(stripped) !== null) return /* @__PURE__ */ new Set();
10523
- if (isNodeOfType(stripped, "Identifier")) {
10524
- const sourceSymbol = scopes.symbolFor(stripped);
10525
- if (!sourceSymbol) return null;
10526
- if (isOutsideAllFunctions(sourceSymbol) || symbolHasStableValue(sourceSymbol, scopes)) return /* @__PURE__ */ new Set();
10527
- if (sourceSymbol.kind === "const" && sourceSymbol.initializer && isNodeOfType(sourceSymbol.declarationNode, "VariableDeclarator") && sourceSymbol.declarationNode.id === sourceSymbol.bindingIdentifier && sourceSymbol.references.every((reference) => reference.flag === "read")) {
10528
- if (visitedSymbolIds.has(sourceSymbol.id)) return null;
10529
- visitedSymbolIds.add(sourceSymbol.id);
10530
- const sourceKeys = resolveIdentitySourceKeysFromExpression(sourceSymbol.initializer, scopes, visitedSymbolIds);
10531
- visitedSymbolIds.delete(sourceSymbol.id);
10532
- if (sourceKeys) return sourceKeys;
10533
- }
10534
- return new Set([sourceSymbol.name]);
10535
- }
10536
- if (isNodeOfType(stripped, "MemberExpression")) {
10537
- if (hasComputedMemberExpression(stripped)) return null;
10538
- const sourceKey = stringifyMemberChain(stripped);
10539
- const rootIdentifier = getMemberRootIdentifier(stripped);
10540
- const rootSymbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
10541
- if (!sourceKey || !rootSymbol) return null;
10542
- if (isOutsideAllFunctions(rootSymbol)) return /* @__PURE__ */ new Set();
10543
- if (isStableRefContainerCapture(rootSymbol, sourceKey, scopes)) return /* @__PURE__ */ new Set();
10544
- if (symbolHasStableValue(rootSymbol, scopes)) return /* @__PURE__ */ new Set();
10545
- return new Set([sourceKey]);
10546
- }
10547
- if (isNodeOfType(stripped, "LogicalExpression")) return mergeIdentitySourceKeys([stripped.left, stripped.right], scopes, visitedSymbolIds);
10548
- if (isNodeOfType(stripped, "ConditionalExpression")) return mergeIdentitySourceKeys([
10549
- stripped.test,
10550
- stripped.consequent,
10551
- stripped.alternate
10552
- ], scopes, visitedSymbolIds);
10553
- return null;
10554
- };
10555
- const resolveReactiveIdentitySourceKeys = (symbol, scopes) => {
10556
- if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier || symbol.references.some((reference) => reference.flag !== "read")) return null;
10557
- return resolveIdentitySourceKeysFromExpression(symbol.initializer, scopes, new Set([symbol.id]));
10558
- };
10559
10093
  const isUseCallbackResultDep = (node, scopes) => {
10560
10094
  const rootSymbol = getRootSymbol(node, scopes);
10561
10095
  const initializer = rootSymbol?.initializer ? unwrapExpression$3(rootSymbol.initializer) : null;
10562
- return Boolean(initializer && isNodeOfType(initializer, "CallExpression") && getHookName(initializer.callee, scopes) === "useCallback");
10096
+ return Boolean(initializer && isNodeOfType(initializer, "CallExpression") && getHookName(initializer.callee) === "useCallback");
10563
10097
  };
10564
- const isExtraReactiveDepAllowed = (node, scopes) => {
10098
+ const isExtraEffectDepAllowed = (node, scopes) => {
10565
10099
  const rootIdentifier = getMemberRootIdentifier(node);
10566
10100
  if (!rootIdentifier) return false;
10567
10101
  const symbol = scopes.symbolFor(rootIdentifier);
@@ -10589,17 +10123,10 @@ const isUnstableInitializer = (node) => {
10589
10123
  if (isNodeOfType(stripped, "LogicalExpression")) return isUnstableInitializer(stripped.left) || isUnstableInitializer(stripped.right);
10590
10124
  return isNodeOfType(stripped, "ObjectExpression") || isNodeOfType(stripped, "ArrayExpression") || isNodeOfType(stripped, "ClassExpression") || isNodeOfType(stripped, "ClassDeclaration") || isNodeOfType(stripped, "JSXElement") || isNodeOfType(stripped, "JSXFragment") || isNodeOfType(stripped, "AssignmentExpression") || isNodeOfType(stripped, "NewExpression");
10591
10125
  };
10592
- const isExtraDepAllowedForHook = (hookName, node, scopes) => {
10593
- if (!isExtraReactiveDepAllowed(node, scopes)) return false;
10594
- if (EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS.has(hookName)) return true;
10595
- if (hookName !== "useMemo") return false;
10596
- const rootSymbol = getRootSymbol(node, scopes);
10597
- return Boolean(rootSymbol && !symbolHasStableValue(rootSymbol, scopes) && !isUnstableInitializer(rootSymbol.initializer));
10598
- };
10599
10126
  const hasDirectIdentifierDeclarator = (symbol) => isNodeOfType(symbol.declarationNode, "VariableDeclarator") && isNodeOfType(symbol.declarationNode.id, "Identifier") || isNodeOfType(symbol.declarationNode, "ClassDeclaration");
10600
10127
  const isFunctionValueSymbol = (symbol) => getFunctionValueNode(symbol) !== null;
10601
- const isStableSetterLikeSymbol = (symbol, scopes) => {
10602
- if (!symbolHasStableHookOrigin(symbol, scopes)) return false;
10128
+ const isStableSetterLikeSymbol = (symbol) => {
10129
+ if (!symbolHasStableHookOrigin(symbol)) return false;
10603
10130
  return symbol.name.startsWith("set") || symbol.name.startsWith("dispatch") || symbol.name.startsWith("startTransition");
10604
10131
  };
10605
10132
  const findStableSetterReference = (node, scopes) => {
@@ -10609,7 +10136,7 @@ const findStableSetterReference = (node, scopes) => {
10609
10136
  if (current !== node && (isNodeOfType(current, "FunctionDeclaration") || isNodeOfType(current, "FunctionExpression") || isNodeOfType(current, "ArrowFunctionExpression"))) return;
10610
10137
  if (isNodeOfType(current, "Identifier")) {
10611
10138
  const symbol = scopes.referenceFor(current)?.resolvedSymbol;
10612
- if (symbol && isStableSetterLikeSymbol(symbol, scopes)) {
10139
+ if (symbol && isStableSetterLikeSymbol(symbol)) {
10613
10140
  setterName = symbol.name;
10614
10141
  return;
10615
10142
  }
@@ -10725,11 +10252,11 @@ const hasRefCurrentAssignmentInComponent = (refSymbol) => {
10725
10252
  }
10726
10253
  return false;
10727
10254
  };
10728
- const isSeededDataRefSymbol = (refSymbol, scopes) => {
10255
+ const isSeededDataRefSymbol = (refSymbol) => {
10729
10256
  if (!refSymbol) return false;
10730
10257
  const initializer = refSymbol.initializer ? unwrapExpression$3(refSymbol.initializer) : null;
10731
10258
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
10732
- if (getHookName(initializer.callee, scopes) !== "useRef") return false;
10259
+ if (getHookName(initializer.callee) !== "useRef") return false;
10733
10260
  const firstArgument = initializer.arguments[0];
10734
10261
  if (!firstArgument || !isAstNode(firstArgument)) return false;
10735
10262
  const strippedArgument = unwrapExpression$3(firstArgument);
@@ -10769,9 +10296,7 @@ const isOuterFunctionScopeDep = (node, callback, scopes) => {
10769
10296
  const componentOrHookScope = componentOrHookFunction ? scopes.ownScopeFor(componentOrHookFunction) : null;
10770
10297
  return Boolean(componentOrHookScope && !isDescendantScope(symbol.scope, componentOrHookScope));
10771
10298
  };
10772
- const hasMemberCallForRoot = (node, rootName, scopes) => {
10773
- const rootSymbol = closureCaptures(node, scopes).find((reference) => reference.resolvedSymbol?.name === rootName)?.resolvedSymbol ?? null;
10774
- if (!rootSymbol) return false;
10299
+ const hasMemberCallForRoot = (node, rootName) => {
10775
10300
  let didFindMemberCall = false;
10776
10301
  const visit = (current) => {
10777
10302
  if (didFindMemberCall) return;
@@ -10787,7 +10312,7 @@ const hasMemberCallForRoot = (node, rootName, scopes) => {
10787
10312
  }
10788
10313
  chainObject = unwrapExpression$3(chainObject.object);
10789
10314
  }
10790
- if (!doesChainPassThroughCurrent && chainObject && isNodeOfType(chainObject, "Identifier") && chainObject.name === rootName && scopes.symbolFor(chainObject) === rootSymbol) {
10315
+ if (!doesChainPassThroughCurrent && chainObject && isNodeOfType(chainObject, "Identifier") && chainObject.name === rootName) {
10791
10316
  didFindMemberCall = true;
10792
10317
  return;
10793
10318
  }
@@ -10805,11 +10330,9 @@ const hasMemberCallForRoot = (node, rootName, scopes) => {
10805
10330
  visit(node);
10806
10331
  return didFindMemberCall;
10807
10332
  };
10808
- const addAggregatePropsDependency = (captureKeys, declaredKeys, callback, scopes) => {
10809
- const propsCaptureKeys = [...captureKeys].filter((captureKey) => captureKey.startsWith("props."));
10810
- if (propsCaptureKeys.length < 2 || declaredKeys.has("props")) return;
10811
- if (propsCaptureKeys.every((captureKey) => [...declaredKeys].some((declaredKey) => isMatchingDepOrPrefix(declaredKey, captureKey)))) return;
10812
- if (hasMemberCallForRoot(callback, "props", scopes)) captureKeys.add("props");
10333
+ const addAggregatePropsDependency = (captureKeys, declaredKeys, callback) => {
10334
+ if ([...captureKeys].filter((captureKey) => captureKey.startsWith("props.")).length < 2 || declaredKeys.has("props")) return;
10335
+ if (hasMemberCallForRoot(callback, "props")) captureKeys.add("props");
10813
10336
  };
10814
10337
  const exhaustiveDeps = defineRule({
10815
10338
  id: "exhaustive-deps",
@@ -10864,7 +10387,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
10864
10387
  };
10865
10388
  const isAutoDependenciesHook = (hookName) => settings.experimental_autoDependenciesHooks.includes(hookName);
10866
10389
  return { CallExpression(node) {
10867
- const hookName = getHookName(node.callee, context.scopes);
10390
+ const hookName = getHookName(node.callee);
10868
10391
  if (!hookName || !isHookOfInterest(hookName, node.callee)) return;
10869
10392
  const callbackArgumentIndex = getCallbackArgumentIndex(hookName);
10870
10393
  const depsArgumentIndex = getDepsArgumentIndex(hookName);
@@ -10921,7 +10444,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
10921
10444
  if (outerAssignments.length > 0) return;
10922
10445
  const refCurrentInCleanup = findRefCurrentInCleanup(callbackToAnalyze, context.scopes);
10923
10446
  const shouldCheckRefCleanup = EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS.has(hookName) || Boolean(additionalHooksRegex && additionalHooksRegex.test(hookName));
10924
- if (refCurrentInCleanup && shouldCheckRefCleanup && !hasRefCurrentAssignment(callbackToAnalyze, refCurrentInCleanup.refCurrentName) && !hasRefCurrentAssignmentInComponent(refCurrentInCleanup.refSymbol) && !isSeededDataRefSymbol(refCurrentInCleanup.refSymbol, context.scopes)) context.report({
10447
+ if (refCurrentInCleanup && shouldCheckRefCleanup && !hasRefCurrentAssignment(callbackToAnalyze, refCurrentInCleanup.refCurrentName) && !hasRefCurrentAssignmentInComponent(refCurrentInCleanup.refSymbol) && !isSeededDataRefSymbol(refCurrentInCleanup.refSymbol)) context.report({
10925
10448
  node: callbackToAnalyze,
10926
10449
  message: buildRefCleanupMessage(refCurrentInCleanup.refCurrentName)
10927
10450
  });
@@ -11020,7 +10543,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
11020
10543
  const fullChain = stringifyMemberChain(stripped);
11021
10544
  if (fullChain && fullChain.endsWith(".current") && isNodeOfType(stripped, "MemberExpression") && isNodeOfType(stripped.object, "Identifier")) {
11022
10545
  const refSymbol = context.scopes.symbolFor(stripped.object);
11023
- if (refSymbol && symbolHasStableHookOrigin(refSymbol, context.scopes)) {
10546
+ if (refSymbol && symbolHasStableHookOrigin(refSymbol)) {
11024
10547
  if (!didReportRefCurrentDep) {
11025
10548
  context.report({
11026
10549
  node: elementNode,
@@ -11058,7 +10581,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
11058
10581
  declaredKeys.add(key);
11059
10582
  declaredKeyToReportNode.set(key, elementNode);
11060
10583
  }
11061
- addAggregatePropsDependency(captureKeys, declaredKeys, callbackToAnalyze ?? callbackArgument, context.scopes);
10584
+ addAggregatePropsDependency(captureKeys, declaredKeys, callbackToAnalyze ?? callbackArgument);
11062
10585
  const missingCaptureKeys = [];
11063
10586
  for (const captureKey of captureKeys) {
11064
10587
  let isCoveredByDeclared = false;
@@ -11144,7 +10667,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
11144
10667
  if (stableCapturedNames.has(rootName) || stableCapturedNames.has(declaredKey)) continue;
11145
10668
  if (outerFunctionCapturedNames.has(rootName)) continue;
11146
10669
  const reportNode = declaredKeyToReportNode.get(declaredKey) ?? depsArgument;
11147
- if (isExtraDepAllowedForHook(hookName, reportNode, context.scopes)) continue;
10670
+ if (EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS.has(hookName) && isExtraEffectDepAllowed(reportNode, context.scopes)) continue;
11148
10671
  if (missingCaptureKeys.length > 0 && isOuterFunctionScopeDep(reportNode, callbackToAnalyze ?? callbackArgument, context.scopes)) continue;
11149
10672
  const rootSymbol = getRootSymbol(reportNode, context.scopes);
11150
10673
  if (rootSymbol && missingCaptureKeys.length > 0 && isRecursiveInitializerCapture(rootSymbol, callbackToAnalyze ?? callbackArgument)) continue;
@@ -12816,6 +12339,20 @@ const collectHandlerReferencedNames = (root) => {
12816
12339
  return names;
12817
12340
  };
12818
12341
  //#endregion
12342
+ //#region src/plugin/utils/get-function-binding-name.ts
12343
+ const getFunctionBindingIdentifier = (functionNode) => {
12344
+ if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
12345
+ const parent = functionNode.parent;
12346
+ if (isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier")) return parent.id;
12347
+ if (isNodeOfType(parent, "AssignmentExpression") && parent.right === functionNode && isNodeOfType(parent.left, "Identifier")) return parent.left;
12348
+ if (isNodeOfType(parent, "CallExpression")) {
12349
+ const callParent = parent.parent;
12350
+ if (isNodeOfType(callParent, "VariableDeclarator") && isNodeOfType(callParent.id, "Identifier")) return callParent.id;
12351
+ }
12352
+ return null;
12353
+ };
12354
+ const getFunctionBindingName$1 = (functionNode) => getFunctionBindingIdentifier(functionNode)?.name ?? null;
12355
+ //#endregion
12819
12356
  //#region src/plugin/rules/jotai/jotai-select-atom-in-render-body.ts
12820
12357
  const JOTAI_SELECT_ATOM_SOURCES = ["jotai/utils", "jotai"];
12821
12358
  const COMPONENT_NAME_PATTERN = /^[A-Z]/;
@@ -13960,7 +13497,7 @@ const jsHoistIntl = defineRule({
13960
13497
  });
13961
13498
  //#endregion
13962
13499
  //#region src/plugin/utils/create-loop-aware-visitors.ts
13963
- const ITERATOR_CALLBACK_METHOD_NAMES$1 = new Set([
13500
+ const ITERATOR_CALLBACK_METHOD_NAMES = new Set([
13964
13501
  "map",
13965
13502
  "flatMap",
13966
13503
  "forEach",
@@ -13979,7 +13516,7 @@ const isIteratorCallback = (node) => {
13979
13516
  const parent = node.parent;
13980
13517
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
13981
13518
  if (!parent.arguments.includes(node)) return false;
13982
- return isNodeOfType(parent.callee, "MemberExpression") && isNodeOfType(parent.callee.property, "Identifier") && ITERATOR_CALLBACK_METHOD_NAMES$1.has(parent.callee.property.name);
13519
+ return isNodeOfType(parent.callee, "MemberExpression") && isNodeOfType(parent.callee.property, "Identifier") && ITERATOR_CALLBACK_METHOD_NAMES.has(parent.callee.property.name);
13983
13520
  };
13984
13521
  const createLoopAwareVisitors = (innerVisitors, options = {}) => {
13985
13522
  let loopDepth = 0;
@@ -14208,7 +13745,7 @@ const findIndexedArrayObject = (callbackBody, indexParameterName) => {
14208
13745
  });
14209
13746
  return indexedArrayObject;
14210
13747
  };
14211
- const unwrapChainExpression$3 = (node) => isNodeOfType(node, "ChainExpression") ? node.expression : node;
13748
+ const unwrapChainExpression$2 = (node) => isNodeOfType(node, "ChainExpression") ? node.expression : node;
14212
13749
  const LENGTH_EQUALITY_OPERATORS = new Set(["===", "=="]);
14213
13750
  const LENGTH_MISMATCH_OPERATORS = new Set([
14214
13751
  "!==",
@@ -14220,17 +13757,17 @@ const LENGTH_MISMATCH_OPERATORS = new Set([
14220
13757
  ]);
14221
13758
  const LENGTH_ANY_COMPARISON_OPERATORS = new Set([...LENGTH_EQUALITY_OPERATORS, ...LENGTH_MISMATCH_OPERATORS]);
14222
13759
  const isLengthComparison = (candidate, receiverArray, indexedArray, operators) => {
14223
- const binaryGuard = unwrapChainExpression$3(candidate);
13760
+ const binaryGuard = unwrapChainExpression$2(candidate);
14224
13761
  if (!isNodeOfType(binaryGuard, "BinaryExpression")) return false;
14225
13762
  if (!operators.has(binaryGuard.operator)) return false;
14226
- const leftSide = unwrapChainExpression$3(binaryGuard.left);
14227
- const rightSide = unwrapChainExpression$3(binaryGuard.right);
13763
+ const leftSide = unwrapChainExpression$2(binaryGuard.left);
13764
+ const rightSide = unwrapChainExpression$2(binaryGuard.right);
14228
13765
  if (!isMemberProperty(leftSide, "length")) return false;
14229
13766
  if (!isMemberProperty(rightSide, "length")) return false;
14230
- const leftLengthObject = unwrapChainExpression$3(leftSide.object);
14231
- const rightLengthObject = unwrapChainExpression$3(rightSide.object);
14232
- const normalizedReceiver = unwrapChainExpression$3(receiverArray);
14233
- const normalizedIndexed = unwrapChainExpression$3(indexedArray);
13767
+ const leftLengthObject = unwrapChainExpression$2(leftSide.object);
13768
+ const rightLengthObject = unwrapChainExpression$2(rightSide.object);
13769
+ const normalizedReceiver = unwrapChainExpression$2(receiverArray);
13770
+ const normalizedIndexed = unwrapChainExpression$2(indexedArray);
14234
13771
  const matchesReceiverThenIndexed = areExpressionsStructurallyEqual(leftLengthObject, normalizedReceiver) && areExpressionsStructurallyEqual(rightLengthObject, normalizedIndexed);
14235
13772
  const matchesIndexedThenReceiver = areExpressionsStructurallyEqual(leftLengthObject, normalizedIndexed) && areExpressionsStructurallyEqual(rightLengthObject, normalizedReceiver);
14236
13773
  return matchesReceiverThenIndexed || matchesIndexedThenReceiver;
@@ -14317,18 +13854,18 @@ const LENGTH_PRESERVING_METHOD_NAMES = new Set([
14317
13854
  "toReversed"
14318
13855
  ]);
14319
13856
  const peelLengthPreservingDerivation = (expression) => {
14320
- let current = unwrapChainExpression$3(expression);
13857
+ let current = unwrapChainExpression$2(expression);
14321
13858
  for (;;) {
14322
13859
  if (isNodeOfType(current, "ArrayExpression") && current.elements?.length === 1 && isNodeOfType(current.elements[0], "SpreadElement")) {
14323
- current = unwrapChainExpression$3(current.elements[0].argument);
13860
+ current = unwrapChainExpression$2(current.elements[0].argument);
14324
13861
  continue;
14325
13862
  }
14326
13863
  if (isNodeOfType(current, "CallExpression")) {
14327
- const callee = unwrapChainExpression$3(current.callee);
13864
+ const callee = unwrapChainExpression$2(current.callee);
14328
13865
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) {
14329
- const calleeObject = unwrapChainExpression$3(callee.object);
13866
+ const calleeObject = unwrapChainExpression$2(callee.object);
14330
13867
  if (isNodeOfType(calleeObject, "Identifier") && calleeObject.name === "Array" && callee.property.name === "from" && current.arguments?.length === 1) {
14331
- current = unwrapChainExpression$3(current.arguments[0]);
13868
+ current = unwrapChainExpression$2(current.arguments[0]);
14332
13869
  continue;
14333
13870
  }
14334
13871
  if (LENGTH_PRESERVING_METHOD_NAMES.has(callee.property.name)) {
@@ -14373,7 +13910,7 @@ const resolveComparedArraySource = (expression, scopeNode) => {
14373
13910
  const initializer = findConstInitializer(current.name, scopeNode);
14374
13911
  if (!initializer) return current;
14375
13912
  const peeledInitializer = peelLengthPreservingDerivation(initializer);
14376
- if (peeledInitializer === unwrapChainExpression$3(initializer)) return current;
13913
+ if (peeledInitializer === unwrapChainExpression$2(initializer)) return current;
14377
13914
  current = peeledInitializer;
14378
13915
  }
14379
13916
  return current;
@@ -20103,6 +19640,54 @@ const nextjsNoAElement = defineRule({
20103
19640
  } })
20104
19641
  });
20105
19642
  //#endregion
19643
+ //#region src/plugin/utils/collect-effect-invoked-functions.ts
19644
+ const PROMISE_CHAIN_METHOD_NAMES$1 = new Set([
19645
+ "then",
19646
+ "catch",
19647
+ "finally"
19648
+ ]);
19649
+ const collectEffectInvokedFunctions = (effectCallback) => {
19650
+ const invokedFunctions = new Set([effectCallback]);
19651
+ const localFunctionBindings = /* @__PURE__ */ new Map();
19652
+ const calledBindingNames = /* @__PURE__ */ new Set();
19653
+ const pendingFunctions = [effectCallback];
19654
+ const enqueue = (candidate) => {
19655
+ const strippedCandidate = candidate ? stripParenExpression(candidate) : candidate;
19656
+ if (!isFunctionLike$1(strippedCandidate) || invokedFunctions.has(strippedCandidate)) return;
19657
+ invokedFunctions.add(strippedCandidate);
19658
+ pendingFunctions.push(strippedCandidate);
19659
+ };
19660
+ const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
19661
+ while (pendingFunctions.length > 0) {
19662
+ const currentFunction = pendingFunctions.pop();
19663
+ if (!currentFunction) break;
19664
+ walkAst(currentFunction, (child) => {
19665
+ if (child !== currentFunction && isFunctionLike$1(child)) {
19666
+ if (isNodeOfType(child, "FunctionDeclaration") && isNodeOfType(child.id, "Identifier")) localFunctionBindings.set(child.id.name, child);
19667
+ return false;
19668
+ }
19669
+ if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
19670
+ const initializer = child.init ? stripParenExpression(child.init) : null;
19671
+ if (isFunctionLike$1(initializer)) localFunctionBindings.set(child.id.name, initializer);
19672
+ return;
19673
+ }
19674
+ if (!isNodeOfType(child, "CallExpression")) return;
19675
+ const callee = stripParenExpression(child.callee);
19676
+ if (isFunctionLike$1(callee)) {
19677
+ enqueue(callee);
19678
+ return;
19679
+ }
19680
+ if (isNodeOfType(callee, "Identifier")) {
19681
+ calledBindingNames.add(callee.name);
19682
+ return;
19683
+ }
19684
+ if (isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
19685
+ });
19686
+ for (const calledName of calledBindingNames) enqueue(localFunctionBindings.get(calledName));
19687
+ }
19688
+ return invokedFunctions;
19689
+ };
19690
+ //#endregion
20106
19691
  //#region src/plugin/utils/contains-fetch-call.ts
20107
19692
  const isFetchCall$1 = (node) => {
20108
19693
  if (!isNodeOfType(node, "CallExpression")) return false;
@@ -21086,7 +20671,6 @@ const findExportedFunctionBody = (programRoot, exportedName) => {
21086
20671
  continue;
21087
20672
  }
21088
20673
  if (isNodeOfType(statement, "ExportNamedDeclaration")) {
21089
- if (statement.exportKind === "type") continue;
21090
20674
  const declaration = statement.declaration;
21091
20675
  if (declaration && isNodeOfType(declaration, "VariableDeclaration")) {
21092
20676
  recordVariableDeclaration(declaration);
@@ -21101,7 +20685,6 @@ const findExportedFunctionBody = (programRoot, exportedName) => {
21101
20685
  }
21102
20686
  for (const specifier of statement.specifiers ?? []) {
21103
20687
  if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
21104
- if (specifier.exportKind === "type") continue;
21105
20688
  const local = specifier.local;
21106
20689
  const exported = specifier.exported;
21107
20690
  if (!isNodeOfType(local, "Identifier")) continue;
@@ -21150,37 +20733,25 @@ const resolveImportedExportName = (importSpecifier) => {
21150
20733
  if (isNodeOfType(importSpecifier, "ImportDefaultSpecifier")) return "default";
21151
20734
  return null;
21152
20735
  };
21153
- const findReExportTargetsForName = (programRoot, exportedName) => {
20736
+ const findReExportSourcesForName = (programRoot, exportedName) => {
21154
20737
  if (!isNodeOfType(programRoot, "Program")) return [];
21155
- const exportAllTargets = [];
20738
+ const exportAllSources = [];
21156
20739
  for (const statement of programRoot.body ?? []) {
21157
20740
  if (isNodeOfType(statement, "ExportNamedDeclaration") && statement.source) {
21158
- if (statement.exportKind === "type") continue;
21159
20741
  const sourceValue = statement.source.value;
21160
20742
  if (typeof sourceValue !== "string") continue;
21161
20743
  for (const specifier of statement.specifiers ?? []) {
21162
20744
  if (!isNodeOfType(specifier, "ExportSpecifier")) continue;
21163
- if (specifier.exportKind === "type") continue;
21164
20745
  const exported = specifier.exported;
21165
- if ((isNodeOfType(exported, "Identifier") ? exported.name : isNodeOfType(exported, "Literal") && typeof exported.value === "string" ? exported.value : null) !== exportedName) continue;
21166
- const local = specifier.local;
21167
- const importedName = isNodeOfType(local, "Identifier") ? local.name : isNodeOfType(local, "Literal") && typeof local.value === "string" ? local.value : null;
21168
- if (importedName) return [{
21169
- importedName,
21170
- source: sourceValue
21171
- }];
20746
+ if ((isNodeOfType(exported, "Identifier") ? exported.name : isNodeOfType(exported, "Literal") && typeof exported.value === "string" ? exported.value : null) === exportedName) return [sourceValue];
21172
20747
  }
21173
20748
  }
21174
20749
  if (isNodeOfType(statement, "ExportAllDeclaration") && statement.source) {
21175
- if (statement.exportKind === "type" || statement.exported) continue;
21176
20750
  const sourceValue = statement.source.value;
21177
- if (typeof sourceValue === "string") exportAllTargets.push({
21178
- importedName: exportedName,
21179
- source: sourceValue
21180
- });
20751
+ if (typeof sourceValue === "string") exportAllSources.push(sourceValue);
21181
20752
  }
21182
20753
  }
21183
- return exportAllTargets;
20754
+ return exportAllSources;
21184
20755
  };
21185
20756
  //#endregion
21186
20757
  //#region src/plugin/utils/attach-parent-references.ts
@@ -21269,6 +20840,139 @@ const parseSourceFile = (absoluteFilePath) => {
21269
20840
  return parsedProgram;
21270
20841
  };
21271
20842
  //#endregion
20843
+ //#region src/plugin/utils/is-barrel-index-module.ts
20844
+ const INDEX_MODULE_FILE_PATTERN = /^index\.(?:[cm]?[jt]sx?|mjs)$/;
20845
+ const BINDING_IMPORT_DECLARATION_PATTERN = /^\s*import\s+(type\s+)?(?!["'])([^;]*?)\s+from\s+["']([^"']+)["']\s*;?\s*(?:(?:\/\/[^\n]*)?\s*)/gm;
20846
+ const BARREL_REEXPORT_DECLARATION_PATTERN = /^\s*export\s+(type\s+)?(?:\*(?:\s+as\s+([\w$]+))?|\{([\s\S]*?)\})\s+from\s+["']([^"']+)["']\s*;?\s*(?:(?:\/\/[^\n]*)?\s*)/gm;
20847
+ const LOCAL_EXPORT_SPECIFIER_DECLARATION_PATTERN = /^\s*export\s+(type\s+)?\{([\s\S]*?)\}\s*;?\s*(?:(?:\/\/[^\n]*)?\s*)/gm;
20848
+ const barrelIndexModuleInfoCache = /* @__PURE__ */ new Map();
20849
+ const isIndexModuleFilePath = (filePath) => INDEX_MODULE_FILE_PATTERN.test(path.basename(filePath));
20850
+ const createNonBarrelInfo = () => ({
20851
+ isBarrel: false,
20852
+ exportsByName: /* @__PURE__ */ new Map(),
20853
+ starExportSources: []
20854
+ });
20855
+ const addImportedBinding = (importedBindings, binding) => {
20856
+ importedBindings.set(binding.localName, {
20857
+ ...binding,
20858
+ didExport: false
20859
+ });
20860
+ };
20861
+ const collectNamedImportBindings = (namedSpecifiersText, source, declarationIsTypeOnly, importedBindings) => {
20862
+ for (const specifier of parseExportSpecifiers(namedSpecifiersText, declarationIsTypeOnly)) addImportedBinding(importedBindings, {
20863
+ localName: specifier.exportedName,
20864
+ importedName: specifier.localName,
20865
+ source,
20866
+ isTypeOnly: specifier.isTypeOnly
20867
+ });
20868
+ };
20869
+ const collectImportBindings = (importClause, source, declarationIsTypeOnly, importedBindings) => {
20870
+ const trimmedImportClause = importClause.trim();
20871
+ const namespaceMatch = trimmedImportClause.match(/(?:^|,\s*)\*\s+as\s+([\w$]+)/);
20872
+ if (namespaceMatch?.[1]) addImportedBinding(importedBindings, {
20873
+ localName: namespaceMatch[1],
20874
+ importedName: "*",
20875
+ source,
20876
+ isTypeOnly: declarationIsTypeOnly
20877
+ });
20878
+ const namedImportMatch = trimmedImportClause.match(/\{([\s\S]*?)\}/);
20879
+ if (namedImportMatch?.[1]) collectNamedImportBindings(namedImportMatch[1], source, declarationIsTypeOnly, importedBindings);
20880
+ const defaultImportName = trimmedImportClause.split(",")[0]?.trim();
20881
+ if (defaultImportName && !defaultImportName.startsWith("{") && !defaultImportName.startsWith("*")) addImportedBinding(importedBindings, {
20882
+ localName: defaultImportName,
20883
+ importedName: "default",
20884
+ source,
20885
+ isTypeOnly: declarationIsTypeOnly
20886
+ });
20887
+ };
20888
+ const replaceKnownDeclarations = (sourceText, importedBindings, exportsByName, starExportSources) => {
20889
+ let withoutKnownDeclarations = sourceText.replace(BINDING_IMPORT_DECLARATION_PATTERN, (_match, typeKeyword, importClause, source) => {
20890
+ collectImportBindings(importClause, source, Boolean(typeKeyword), importedBindings);
20891
+ return "";
20892
+ });
20893
+ withoutKnownDeclarations = withoutKnownDeclarations.replace(BARREL_REEXPORT_DECLARATION_PATTERN, (_match, typeKeyword, namespaceExportName, specifiersText, source) => {
20894
+ const isTypeOnly = Boolean(typeKeyword);
20895
+ if (namespaceExportName) {
20896
+ exportsByName.set(namespaceExportName, {
20897
+ exportedName: namespaceExportName,
20898
+ importedName: "*",
20899
+ source,
20900
+ isTypeOnly
20901
+ });
20902
+ return "";
20903
+ }
20904
+ if (specifiersText) {
20905
+ for (const specifier of parseExportSpecifiers(specifiersText, isTypeOnly)) exportsByName.set(specifier.exportedName, {
20906
+ exportedName: specifier.exportedName,
20907
+ importedName: specifier.localName,
20908
+ source,
20909
+ isTypeOnly: specifier.isTypeOnly
20910
+ });
20911
+ return "";
20912
+ }
20913
+ starExportSources.push(source);
20914
+ return "";
20915
+ });
20916
+ withoutKnownDeclarations = withoutKnownDeclarations.replace(LOCAL_EXPORT_SPECIFIER_DECLARATION_PATTERN, (_match, typeKeyword, specifiersText) => {
20917
+ for (const specifier of parseExportSpecifiers(specifiersText, Boolean(typeKeyword))) {
20918
+ const importedBinding = importedBindings.get(specifier.localName);
20919
+ if (!importedBinding) return _match;
20920
+ importedBinding.didExport = true;
20921
+ exportsByName.set(specifier.exportedName, {
20922
+ exportedName: specifier.exportedName,
20923
+ importedName: importedBinding.importedName,
20924
+ source: importedBinding.source,
20925
+ isTypeOnly: specifier.isTypeOnly || importedBinding.isTypeOnly
20926
+ });
20927
+ }
20928
+ return "";
20929
+ });
20930
+ return withoutKnownDeclarations;
20931
+ };
20932
+ const hasUnexportedRuntimeImport = (importedBindings) => {
20933
+ for (const binding of importedBindings.values()) if (!binding.isTypeOnly && !binding.didExport) return true;
20934
+ return false;
20935
+ };
20936
+ const classifyBarrelModule = (sourceText) => {
20937
+ const strippedSource = stripJsComments(sourceText).trim();
20938
+ if (!strippedSource) return createNonBarrelInfo();
20939
+ const importedBindings = /* @__PURE__ */ new Map();
20940
+ const exportsByName = /* @__PURE__ */ new Map();
20941
+ const starExportSources = [];
20942
+ if (replaceKnownDeclarations(strippedSource, importedBindings, exportsByName, starExportSources).trim() || hasUnexportedRuntimeImport(importedBindings)) return createNonBarrelInfo();
20943
+ return {
20944
+ isBarrel: exportsByName.size > 0 || starExportSources.length > 0,
20945
+ exportsByName,
20946
+ starExportSources
20947
+ };
20948
+ };
20949
+ const getBarrelIndexModuleInfo = (filePath) => {
20950
+ if (!isIndexModuleFilePath(filePath)) return createNonBarrelInfo();
20951
+ recordContentProbe(filePath);
20952
+ let fileStat;
20953
+ try {
20954
+ fileStat = fs.statSync(filePath);
20955
+ } catch {
20956
+ fileStat = null;
20957
+ }
20958
+ const cachedResult = barrelIndexModuleInfoCache.get(filePath);
20959
+ if (cachedResult !== void 0 && fileStat !== null && cachedResult.mtimeMs === fileStat.mtimeMs && cachedResult.size === fileStat.size) return cachedResult.moduleInfo;
20960
+ if (fileStat === null) return createNonBarrelInfo();
20961
+ let moduleInfo = createNonBarrelInfo();
20962
+ try {
20963
+ moduleInfo = classifyBarrelModule(fs.readFileSync(filePath, "utf8"));
20964
+ } catch {
20965
+ moduleInfo = createNonBarrelInfo();
20966
+ }
20967
+ barrelIndexModuleInfoCache.set(filePath, {
20968
+ mtimeMs: fileStat.mtimeMs,
20969
+ size: fileStat.size,
20970
+ moduleInfo
20971
+ });
20972
+ return moduleInfo;
20973
+ };
20974
+ const isBarrelIndexModule = (filePath) => getBarrelIndexModuleInfo(filePath).isBarrel;
20975
+ //#endregion
21272
20976
  //#region src/plugin/utils/resolve-relative-import-path.ts
21273
20977
  const MODULE_FILE_EXTENSIONS = [
21274
20978
  ".ts",
@@ -21385,6 +21089,35 @@ const resolveModuleFileFromAbsolutePath = (importPath) => {
21385
21089
  };
21386
21090
  const resolveRelativeImportPath = (filename, source) => resolveModuleFileFromAbsolutePath(path.resolve(path.dirname(filename), source));
21387
21091
  //#endregion
21092
+ //#region src/plugin/utils/resolve-barrel-export-file-path.ts
21093
+ const getUniqueFilePath = (filePaths) => {
21094
+ const uniqueFilePaths = new Set(filePaths);
21095
+ if (uniqueFilePaths.size !== 1) return null;
21096
+ const [filePath] = uniqueFilePaths;
21097
+ return filePath ?? null;
21098
+ };
21099
+ const resolveStarExportFilePath = (barrelFilePath, exportedName, source, visitedFilePaths) => {
21100
+ const resolvedTargetPath = resolveRelativeImportPath(barrelFilePath, source);
21101
+ if (!resolvedTargetPath) return null;
21102
+ const nestedTargetPath = resolveBarrelExportFilePath(resolvedTargetPath, exportedName, new Set(visitedFilePaths));
21103
+ if (nestedTargetPath) return nestedTargetPath;
21104
+ return doesModuleExportName(resolvedTargetPath, exportedName) ? resolvedTargetPath : null;
21105
+ };
21106
+ const resolveBarrelExportFilePath = (barrelFilePath, exportedName, visitedFilePaths = /* @__PURE__ */ new Set()) => {
21107
+ if (visitedFilePaths.has(barrelFilePath)) return null;
21108
+ visitedFilePaths.add(barrelFilePath);
21109
+ const moduleInfo = getBarrelIndexModuleInfo(barrelFilePath);
21110
+ if (!moduleInfo.isBarrel) return null;
21111
+ const target = moduleInfo.exportsByName.get(exportedName);
21112
+ if (target) {
21113
+ const resolvedTargetPath = resolveRelativeImportPath(barrelFilePath, target.source);
21114
+ if (!resolvedTargetPath) return null;
21115
+ return resolveBarrelExportFilePath(resolvedTargetPath, target.importedName, visitedFilePaths) ?? resolvedTargetPath;
21116
+ }
21117
+ if (exportedName === "default") return null;
21118
+ return getUniqueFilePath(moduleInfo.starExportSources.map((source) => resolveStarExportFilePath(barrelFilePath, exportedName, source, visitedFilePaths)).filter((filePath) => Boolean(filePath)));
21119
+ };
21120
+ //#endregion
21388
21121
  //#region src/plugin/utils/resolve-tsconfig-alias.ts
21389
21122
  const TSCONFIG_FILE_NAMES = ["tsconfig.json", "jsconfig.json"];
21390
21123
  const isObjectRecord = (value) => typeof value === "object" && value !== null;
@@ -21563,29 +21296,25 @@ const resolveTsconfigAliasPath = (fromFilename, source) => {
21563
21296
  };
21564
21297
  //#endregion
21565
21298
  //#region src/plugin/utils/resolve-module-path.ts
21566
- const resolveModulePath = (fromFilename, source) => {
21567
- if (path.isAbsolute(source)) return null;
21568
- return source.startsWith(".") ? resolveRelativeImportPath(fromFilename, source) : resolveTsconfigAliasPath(fromFilename, source);
21569
- };
21299
+ const resolveModulePath = (fromFilename, source) => resolveRelativeImportPath(fromFilename, source) ?? resolveTsconfigAliasPath(fromFilename, source);
21570
21300
  //#endregion
21571
21301
  //#region src/plugin/utils/resolve-cross-file-function-export.ts
21572
21302
  const resolveFunctionExportInFile = (filePath, exportedName, visitedFilePaths) => {
21573
21303
  if (visitedFilePaths.size >= 4) return null;
21574
21304
  if (visitedFilePaths.has(filePath)) return null;
21575
21305
  visitedFilePaths.add(filePath);
21576
- const programRoot = parseSourceFile(filePath);
21306
+ const actualFilePath = resolveBarrelExportFilePath(filePath, exportedName) ?? filePath;
21307
+ const programRoot = parseSourceFile(actualFilePath);
21577
21308
  if (!programRoot) return null;
21578
21309
  const exported = findExportedFunctionBody(programRoot, exportedName);
21579
21310
  if (exported) return exported;
21580
- const resolvedCandidates = /* @__PURE__ */ new Set();
21581
- for (const target of findReExportTargetsForName(programRoot, exportedName)) {
21582
- const nextFilePath = resolveModulePath(filePath, target.source);
21311
+ for (const reExportSource of findReExportSourcesForName(programRoot, exportedName)) {
21312
+ const nextFilePath = resolveModulePath(actualFilePath, reExportSource);
21583
21313
  if (!nextFilePath) continue;
21584
- const resolved = resolveFunctionExportInFile(nextFilePath, target.importedName, new Set(visitedFilePaths));
21585
- if (resolved) resolvedCandidates.add(resolved);
21314
+ const resolved = resolveFunctionExportInFile(nextFilePath, exportedName, visitedFilePaths);
21315
+ if (resolved) return resolved;
21586
21316
  }
21587
- if (resolvedCandidates.size !== 1) return null;
21588
- return resolvedCandidates.values().next().value ?? null;
21317
+ return null;
21589
21318
  };
21590
21319
  const resolveCrossFileFunctionExport = (fromFilename, source, exportedName) => {
21591
21320
  const resolvedFilePath = resolveModulePath(fromFilename, source);
@@ -22370,26 +22099,6 @@ const isReactNamedImportReference = (ref, importedName) => Boolean(ref?.resolved
22370
22099
  const importDeclaration = declarationNode.parent;
22371
22100
  return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
22372
22101
  }));
22373
- const isReactNamespaceImportReference = (ref) => Boolean(ref?.resolved?.defs.some((def) => {
22374
- if (def.type !== "ImportBinding") return false;
22375
- const declarationNode = def.node;
22376
- if (!isNodeOfType(declarationNode, "ImportNamespaceSpecifier") && !isNodeOfType(declarationNode, "ImportDefaultSpecifier")) return false;
22377
- const importDeclaration = declarationNode.parent;
22378
- return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
22379
- }));
22380
- const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
22381
- if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return false;
22382
- const callee = stripParenExpression(declarator.init.callee);
22383
- if (isNodeOfType(callee, "Identifier")) {
22384
- const reference = getRef(analysis, callee);
22385
- if (!reference?.resolved) return callee.name === hookName;
22386
- return isReactNamedImportReference(reference, hookName);
22387
- }
22388
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
22389
- const namespaceReference = getRef(analysis, callee.object);
22390
- if (!namespaceReference?.resolved) return callee.object.name === "React";
22391
- return isReactNamespaceImportReference(namespaceReference);
22392
- };
22393
22102
  const isHookCallee$1 = (analysis, node, hookName) => {
22394
22103
  if (!node) return false;
22395
22104
  if (isNodeOfType(node, "Identifier")) {
@@ -22788,46 +22497,6 @@ const PURE_GLOBAL_CALLEE_NAMES = new Set([
22788
22497
  "parseInt"
22789
22498
  ]);
22790
22499
  const PURE_GLOBAL_NAMESPACE_NAMES = new Set(["JSON", "Math"]);
22791
- const PURE_HELPER_NAMESPACE_MEMBER_NAMES = new Map([["JSON", new Set([
22792
- "isRawJSON",
22793
- "parse",
22794
- "rawJSON",
22795
- "stringify"
22796
- ])], ["Math", new Set([
22797
- "abs",
22798
- "acos",
22799
- "acosh",
22800
- "asin",
22801
- "asinh",
22802
- "atan",
22803
- "atan2",
22804
- "atanh",
22805
- "cbrt",
22806
- "ceil",
22807
- "clz32",
22808
- "cos",
22809
- "cosh",
22810
- "exp",
22811
- "floor",
22812
- "fround",
22813
- "hypot",
22814
- "imul",
22815
- "log",
22816
- "log10",
22817
- "log1p",
22818
- "log2",
22819
- "max",
22820
- "min",
22821
- "pow",
22822
- "round",
22823
- "sign",
22824
- "sin",
22825
- "sinh",
22826
- "sqrt",
22827
- "tan",
22828
- "tanh",
22829
- "trunc"
22830
- ])]]);
22831
22500
  const PURE_MEMBER_TRANSFORM_NAMES = new Set([
22832
22501
  "concat",
22833
22502
  "filter",
@@ -22874,42 +22543,6 @@ const getParameterBindingIdentity = (analysis, functionNode, parameter) => {
22874
22543
  }
22875
22544
  return parameter;
22876
22545
  };
22877
- const isAsyncOrGeneratorFunction = (functionNode) => Boolean(functionNode.async === true || functionNode.generator === true);
22878
- const isModuleFunction = (functionNode) => {
22879
- let ancestor = functionNode.parent;
22880
- while (ancestor) {
22881
- if (isFunctionLike$1(ancestor)) return false;
22882
- if (isNodeOfType(ancestor, "Program")) return true;
22883
- ancestor = ancestor.parent;
22884
- }
22885
- return false;
22886
- };
22887
- const getFunctionBindingNames = (functionNode) => {
22888
- const names = /* @__PURE__ */ new Set();
22889
- if ((isNodeOfType(functionNode, "FunctionDeclaration") || isNodeOfType(functionNode, "FunctionExpression")) && functionNode.id) names.add(functionNode.id.name);
22890
- const parent = functionNode.parent;
22891
- if (parent && isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier")) names.add(parent.id.name);
22892
- return names;
22893
- };
22894
- const collectModuleBindingNames = (functionNode) => {
22895
- let program = functionNode.parent;
22896
- while (program && !isNodeOfType(program, "Program")) program = program.parent;
22897
- const bindingNames = /* @__PURE__ */ new Set();
22898
- if (!program || !isNodeOfType(program, "Program")) return bindingNames;
22899
- for (const statement of program.body ?? []) {
22900
- if (isNodeOfType(statement, "ImportDeclaration")) {
22901
- for (const specifier of statement.specifiers ?? []) if (isNodeOfType(specifier.local, "Identifier")) bindingNames.add(specifier.local.name);
22902
- continue;
22903
- }
22904
- const declaration = isNodeOfType(statement, "ExportNamedDeclaration") || isNodeOfType(statement, "ExportDefaultDeclaration") ? statement.declaration : statement;
22905
- if (isNodeOfType(declaration, "VariableDeclaration")) {
22906
- for (const declarator of declaration.declarations ?? []) collectPatternNames(declarator.id, bindingNames);
22907
- continue;
22908
- }
22909
- if ((isNodeOfType(declaration, "FunctionDeclaration") || isNodeOfType(declaration, "ClassDeclaration")) && isNodeOfType(declaration.id, "Identifier")) bindingNames.add(declaration.id.name);
22910
- }
22911
- return bindingNames;
22912
- };
22913
22546
  const buildSubstitutions = (analysis, functionNode, argumentExpressions, parentFrame) => {
22914
22547
  const substitutions = /* @__PURE__ */ new Map();
22915
22548
  const parameters = getFunctionParameters(functionNode);
@@ -23040,7 +22673,7 @@ const collectIntroducedBindings = (analysis, functionNode) => {
23040
22673
  for (const parameter of getFunctionParameters(functionNode)) if (isNodeOfType(parameter, "Identifier")) introducedBindings.add(getParameterBindingIdentity(analysis, functionNode, parameter));
23041
22674
  return introducedBindings;
23042
22675
  };
23043
- const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilename) => {
22676
+ const collectBoundedEffectExecutionFrames = (analysis, effectNode) => {
23044
22677
  const effectFunction = getEffectFn(analysis, effectNode);
23045
22678
  if (!effectFunction || !isFunctionLike$1(effectFunction) || effectFunction.async === true) return [];
23046
22679
  const invokedFunctionEvidence = collectEffectInvokedFunctions(effectFunction);
@@ -23049,8 +22682,7 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilena
23049
22682
  invocation: null,
23050
22683
  isDeferred: false,
23051
22684
  introducedBindings: /* @__PURE__ */ new Set(),
23052
- substitutions: /* @__PURE__ */ new Map(),
23053
- currentFilename
22685
+ substitutions: /* @__PURE__ */ new Map()
23054
22686
  };
23055
22687
  const frames = [rootFrame];
23056
22688
  walkAst(effectFunction, (child) => {
@@ -23071,8 +22703,7 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilena
23071
22703
  invocation: child,
23072
22704
  isDeferred,
23073
22705
  introducedBindings,
23074
- substitutions: buildSubstitutions(analysis, callable, argumentsForCallable, rootFrame),
23075
- currentFilename
22706
+ substitutions: buildSubstitutions(analysis, callable, argumentsForCallable, rootFrame)
23076
22707
  });
23077
22708
  };
23078
22709
  if (isFunctionLike$1(callee)) {
@@ -23125,174 +22756,6 @@ const isOpaqueHookCall = (callExpression) => {
23125
22756
  const calleeName = getCallCalleeName$1(callExpression);
23126
22757
  return Boolean(calleeName && /^use[A-Z0-9]/.test(calleeName) && calleeName !== "useMemo" && calleeName !== "useCallback" && calleeName !== "useEffectEvent");
23127
22758
  };
23128
- const analyzeHelperStatements = (statements, environment, usedParameterIndices, analyzeExpression) => {
23129
- let canContinue = true;
23130
- for (const statement of statements) {
23131
- if (!canContinue) {
23132
- if (!isNodeOfType(statement, "EmptyStatement")) return {
23133
- canContinue: false,
23134
- isValid: false
23135
- };
23136
- continue;
23137
- }
23138
- if (isNodeOfType(statement, "EmptyStatement")) continue;
23139
- if (isNodeOfType(statement, "ReturnStatement")) {
23140
- if (!statement.argument || !analyzeExpression(statement.argument, environment, usedParameterIndices)) return {
23141
- canContinue: false,
23142
- isValid: false
23143
- };
23144
- canContinue = false;
23145
- continue;
23146
- }
23147
- if (isNodeOfType(statement, "BlockStatement")) {
23148
- const blockSummary = analyzeHelperStatements(statement.body ?? [], environment, usedParameterIndices, analyzeExpression);
23149
- if (!blockSummary.isValid) return blockSummary;
23150
- canContinue = blockSummary.canContinue;
23151
- continue;
23152
- }
23153
- if (isNodeOfType(statement, "IfStatement")) {
23154
- if (!analyzeExpression(statement.test, environment, usedParameterIndices)) return {
23155
- canContinue: false,
23156
- isValid: false
23157
- };
23158
- const consequentSummary = analyzeHelperStatements([statement.consequent], environment, usedParameterIndices, analyzeExpression);
23159
- if (!consequentSummary.isValid) return consequentSummary;
23160
- const alternateSummary = statement.alternate ? analyzeHelperStatements([statement.alternate], environment, usedParameterIndices, analyzeExpression) : {
23161
- canContinue: true,
23162
- isValid: true
23163
- };
23164
- if (!alternateSummary.isValid) return alternateSummary;
23165
- canContinue = consequentSummary.canContinue || alternateSummary.canContinue;
23166
- continue;
23167
- }
23168
- return {
23169
- canContinue: false,
23170
- isValid: false
23171
- };
23172
- }
23173
- return {
23174
- canContinue,
23175
- isValid: true
23176
- };
23177
- };
23178
- const analyzeHelperExpression = (expression, environment, usedParameterIndices) => {
23179
- const node = stripParenExpression(expression);
23180
- if (isNodeOfType(node, "Literal") || isNodeOfType(node, "TemplateElement")) return true;
23181
- if (isNodeOfType(node, "Identifier")) {
23182
- const parameterIndex = environment.parameterIndices.get(node.name);
23183
- if (parameterIndex !== void 0) {
23184
- if (parameterIndex !== null) usedParameterIndices.add(parameterIndex);
23185
- return true;
23186
- }
23187
- return node.name === "undefined" || node.name === "NaN" || node.name === "Infinity";
23188
- }
23189
- if (isNodeOfType(node, "ArrayExpression")) return (node.elements ?? []).every((element) => !element || analyzeHelperExpression(element, environment, usedParameterIndices));
23190
- if (isNodeOfType(node, "ObjectExpression")) return (node.properties ?? []).every((property) => {
23191
- if (isNodeOfType(property, "SpreadElement")) return analyzeHelperExpression(property.argument, environment, usedParameterIndices);
23192
- if (!isNodeOfType(property, "Property") || property.kind !== "init" || property.method === true) return false;
23193
- if (property.computed && !analyzeHelperExpression(property.key, environment, usedParameterIndices)) return false;
23194
- return analyzeHelperExpression(property.value, environment, usedParameterIndices);
23195
- });
23196
- if (isNodeOfType(node, "TemplateLiteral")) return (node.expressions ?? []).every((templateExpression) => analyzeHelperExpression(templateExpression, environment, usedParameterIndices));
23197
- if (isNodeOfType(node, "UnaryExpression")) return node.operator !== "delete" && analyzeHelperExpression(node.argument, environment, usedParameterIndices);
23198
- if (isNodeOfType(node, "BinaryExpression") || isNodeOfType(node, "LogicalExpression")) return analyzeHelperExpression(node.left, environment, usedParameterIndices) && analyzeHelperExpression(node.right, environment, usedParameterIndices);
23199
- if (isNodeOfType(node, "ConditionalExpression")) return analyzeHelperExpression(node.test, environment, usedParameterIndices) && analyzeHelperExpression(node.consequent, environment, usedParameterIndices) && analyzeHelperExpression(node.alternate, environment, usedParameterIndices);
23200
- if (isNodeOfType(node, "MemberExpression")) {
23201
- if (!analyzeHelperExpression(node.object, environment, usedParameterIndices)) return false;
23202
- return !node.computed || analyzeHelperExpression(node.property, environment, usedParameterIndices);
23203
- }
23204
- if (isFunctionLike$1(node)) {
23205
- if (isAsyncOrGeneratorFunction(node)) return false;
23206
- const callbackParameterIndices = new Map(environment.parameterIndices);
23207
- for (const parameter of getFunctionParameters(node)) {
23208
- if (!isNodeOfType(parameter, "Identifier")) return false;
23209
- callbackParameterIndices.set(parameter.name, null);
23210
- }
23211
- const callbackEnvironment = {
23212
- parameterIndices: callbackParameterIndices,
23213
- recursiveNames: environment.recursiveNames,
23214
- shadowedGlobalNames: environment.shadowedGlobalNames
23215
- };
23216
- if (!isNodeOfType(node.body, "BlockStatement")) return analyzeHelperExpression(node.body, callbackEnvironment, usedParameterIndices);
23217
- const callbackSummary = analyzeHelperStatements(node.body.body ?? [], callbackEnvironment, usedParameterIndices, analyzeHelperExpression);
23218
- return callbackSummary.isValid && !callbackSummary.canContinue;
23219
- }
23220
- if (isNodeOfType(node, "CallExpression")) {
23221
- const callee = stripParenExpression(node.callee);
23222
- const calleeRoot = getMemberRoot(callee);
23223
- const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && !environment.parameterIndices.has(callee.name) && !environment.recursiveNames.has(callee.name) && !environment.shadowedGlobalNames.has(callee.name);
23224
- const namespaceName = isNodeOfType(calleeRoot, "Identifier") && !environment.parameterIndices.has(calleeRoot.name) && !environment.recursiveNames.has(calleeRoot.name) && !environment.shadowedGlobalNames.has(calleeRoot.name) ? calleeRoot.name : null;
23225
- const namespaceMemberName = getStaticMemberName(callee);
23226
- const isPureNamespaceCall = isNodeOfType(callee, "MemberExpression") && namespaceName !== null && namespaceMemberName !== null && PURE_HELPER_NAMESPACE_MEMBER_NAMES.get(namespaceName)?.has(namespaceMemberName) === true;
23227
- const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
23228
- if (!isPureGlobalCall && !isPureNamespaceCall && !isPureMemberTransform) return false;
23229
- if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression") && !analyzeHelperExpression(callee.object, environment, usedParameterIndices)) return false;
23230
- return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
23231
- }
23232
- if (isNodeOfType(node, "SpreadElement")) return analyzeHelperExpression(node.argument, environment, usedParameterIndices);
23233
- return false;
23234
- };
23235
- const helperSummaryCache = /* @__PURE__ */ new WeakMap();
23236
- const summarizeHelperReturn = (functionNode) => {
23237
- if (!isFunctionLike$1(functionNode) || !isModuleFunction(functionNode)) return null;
23238
- if (helperSummaryCache.has(functionNode)) return helperSummaryCache.get(functionNode) ?? null;
23239
- if (isAsyncOrGeneratorFunction(functionNode)) {
23240
- helperSummaryCache.set(functionNode, null);
23241
- return null;
23242
- }
23243
- const parameterIndices = /* @__PURE__ */ new Map();
23244
- const parameters = getFunctionParameters(functionNode);
23245
- for (let parameterIndex = 0; parameterIndex < parameters.length; parameterIndex += 1) {
23246
- const parameter = parameters[parameterIndex];
23247
- if (!parameter || !isNodeOfType(parameter, "Identifier") || parameterIndices.has(parameter.name)) {
23248
- helperSummaryCache.set(functionNode, null);
23249
- return null;
23250
- }
23251
- parameterIndices.set(parameter.name, parameterIndex);
23252
- }
23253
- const environment = {
23254
- parameterIndices,
23255
- recursiveNames: getFunctionBindingNames(functionNode),
23256
- shadowedGlobalNames: collectModuleBindingNames(functionNode)
23257
- };
23258
- const usedParameterIndices = /* @__PURE__ */ new Set();
23259
- if (!isNodeOfType(functionNode.body, "BlockStatement")) {
23260
- if (!analyzeHelperExpression(functionNode.body, environment, usedParameterIndices)) {
23261
- helperSummaryCache.set(functionNode, null);
23262
- return null;
23263
- }
23264
- } else {
23265
- const controlFlowSummary = analyzeHelperStatements(functionNode.body.body ?? [], environment, usedParameterIndices, analyzeHelperExpression);
23266
- if (!controlFlowSummary.isValid || controlFlowSummary.canContinue) {
23267
- helperSummaryCache.set(functionNode, null);
23268
- return null;
23269
- }
23270
- }
23271
- const summary = { usedParameterIndices };
23272
- helperSummaryCache.set(functionNode, summary);
23273
- return summary;
23274
- };
23275
- const resolveValueHelperFunction = (analysis, callee, currentFilename) => {
23276
- if (!isNodeOfType(callee, "Identifier")) return null;
23277
- const reference = getRef(analysis, callee);
23278
- if (!reference?.resolved) return null;
23279
- const importDefinition = reference.resolved.defs.find((definition) => definition.type === "ImportBinding");
23280
- if (importDefinition) {
23281
- if (!currentFilename) return null;
23282
- const specifier = importDefinition.node;
23283
- if (!isNodeOfType(specifier, "ImportSpecifier") && !isNodeOfType(specifier, "ImportDefaultSpecifier")) return null;
23284
- const importDeclaration = specifier.parent;
23285
- if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration")) return null;
23286
- if (importDeclaration.importKind === "type" || isNodeOfType(specifier, "ImportSpecifier") && specifier.importKind === "type") return null;
23287
- const source = importDeclaration.source?.value;
23288
- if (typeof source !== "string") return null;
23289
- const exportedName = resolveImportedExportName(specifier);
23290
- if (!exportedName) return null;
23291
- return resolveCrossFileFunctionExport(currentFilename, source, exportedName);
23292
- }
23293
- const callable = resolveWrappedCallable(analysis, callee);
23294
- return callable && isModuleFunction(callable) ? callable : null;
23295
- };
23296
22759
  const isLocallyConstructedObjectMember = (reference, memberExpression) => isNodeOfType(memberExpression, "MemberExpression") && reference.resolved?.defs.some((definition) => {
23297
22760
  const definitionNode = definition.node;
23298
22761
  return isNodeOfType(definitionNode, "VariableDeclarator") && Boolean(definitionNode.init) && (isNodeOfType(definitionNode.init, "ObjectExpression") || isNodeOfType(definitionNode.init, "ArrayExpression"));
@@ -23378,55 +22841,38 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23378
22841
  const calleeRoot = getMemberRoot(callee);
23379
22842
  const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && getIdentifierBindingIdentity(analysis, callee) === null || isNodeOfType(calleeRoot, "Identifier") && PURE_GLOBAL_NAMESPACE_NAMES.has(calleeRoot.name) && getIdentifierBindingIdentity(analysis, calleeRoot) === null;
23380
22843
  const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName(callee) ?? "");
23381
- if (isPureGlobalCall || isPureMemberTransform) {
23382
- if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression")) mergeEvidence(evidence, collectValueEvidence(analysis, callee.object, frame, remainingCallFrames, new Set(visitedBindings)));
23383
- for (const argument of node.arguments ?? []) {
23384
- if (isFunctionLike$1(argument)) continue;
23385
- mergeEvidence(evidence, collectValueEvidence(analysis, argument, frame, remainingCallFrames, new Set(visitedBindings)));
23386
- }
22844
+ if (isPureMemberTransform) mergeEvidence(evidence, collectValueEvidence(analysis, callee.object, frame, remainingCallFrames, new Set(visitedBindings)));
22845
+ for (const argument of node.arguments ?? []) {
22846
+ if (isFunctionLike$1(argument)) continue;
22847
+ mergeEvidence(evidence, collectValueEvidence(analysis, argument, frame, remainingCallFrames, new Set(visitedBindings)));
22848
+ }
22849
+ if (isPureGlobalCall || isPureMemberTransform) return evidence;
22850
+ if (isNodeOfType(callee, "MemberExpression")) {
22851
+ evidence.hasUnknownSource = true;
23387
22852
  return evidence;
23388
22853
  }
23389
22854
  if (remainingCallFrames <= 0) {
23390
22855
  evidence.hasUnknownSource = true;
23391
22856
  return evidence;
23392
22857
  }
23393
- const localHelperFunction = resolveWrappedCallable(analysis, callee);
23394
- if (localHelperFunction && !isModuleFunction(localHelperFunction)) {
23395
- if (isAsyncOrGeneratorFunction(localHelperFunction) || functionInvokesItself(analysis, localHelperFunction)) {
23396
- evidence.hasUnknownSource = true;
23397
- return evidence;
23398
- }
23399
- const localHelperFrame = {
23400
- functionNode: localHelperFunction,
23401
- invocation: node,
23402
- isDeferred: false,
23403
- introducedBindings: /* @__PURE__ */ new Set(),
23404
- substitutions: buildSubstitutions(analysis, localHelperFunction, node.arguments ?? [], frame),
23405
- currentFilename: frame.currentFilename
23406
- };
23407
- const returnedExpressions = getReturnedExpressions(localHelperFunction);
23408
- if (returnedExpressions.length === 0) {
23409
- evidence.hasUnknownSource = true;
23410
- return evidence;
23411
- }
23412
- for (const returnedExpression of returnedExpressions) mergeEvidence(evidence, collectValueEvidence(analysis, returnedExpression, localHelperFrame, remainingCallFrames - 1, new Set(visitedBindings)));
22858
+ const callable = resolveWrappedCallable(analysis, callee);
22859
+ if (!callable || callable.async === true || functionInvokesItself(analysis, callable)) {
22860
+ evidence.hasUnknownSource = true;
23413
22861
  return evidence;
23414
22862
  }
23415
- const helperFunction = resolveValueHelperFunction(analysis, callee, frame.currentFilename);
23416
- const helperSummary = helperFunction ? summarizeHelperReturn(helperFunction) : null;
23417
- if (!helperSummary) {
22863
+ const valueFrame = {
22864
+ functionNode: callable,
22865
+ invocation: node,
22866
+ isDeferred: false,
22867
+ introducedBindings: /* @__PURE__ */ new Set(),
22868
+ substitutions: buildSubstitutions(analysis, callable, node.arguments ?? [], frame)
22869
+ };
22870
+ const returnedExpressions = getReturnedExpressions(callable);
22871
+ if (returnedExpressions.length === 0) {
23418
22872
  evidence.hasUnknownSource = true;
23419
22873
  return evidence;
23420
22874
  }
23421
- const argumentsForHelper = node.arguments ?? [];
23422
- for (const parameterIndex of helperSummary.usedParameterIndices) {
23423
- const argument = argumentsForHelper[parameterIndex];
23424
- if (!argument) {
23425
- evidence.hasUnknownSource = true;
23426
- return evidence;
23427
- }
23428
- mergeEvidence(evidence, collectValueEvidence(analysis, argument, frame, remainingCallFrames - 1, new Set(visitedBindings)));
23429
- }
22875
+ for (const returnedExpression of returnedExpressions) mergeEvidence(evidence, collectValueEvidence(analysis, returnedExpression, valueFrame, remainingCallFrames - 1, new Set(visitedBindings)));
23430
22876
  return evidence;
23431
22877
  }
23432
22878
  if (isFunctionLike$1(node) || isNodeOfType(node, "AwaitExpression")) {
@@ -23444,20 +22890,6 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
23444
22890
  }
23445
22891
  return evidence;
23446
22892
  };
23447
- const collectRenderValueEvidence = (analysis, expression, componentFunction, currentFilename) => {
23448
- const evidence = collectValueEvidence(analysis, expression, {
23449
- functionNode: componentFunction,
23450
- invocation: null,
23451
- isDeferred: false,
23452
- introducedBindings: /* @__PURE__ */ new Set(),
23453
- substitutions: /* @__PURE__ */ new Map(),
23454
- currentFilename
23455
- }, 1);
23456
- return {
23457
- sourceReferences: evidence.sourceReferences,
23458
- isExclusivelyRenderKnown: evidence.sourceReferences.size > 0 && !evidence.hasUnknownSource && !evidence.hasDeferredIntroducedValue && !evidence.readsExternalValue
23459
- };
23460
- };
23461
22893
  const findStateSetterReference = (analysis, callExpression) => {
23462
22894
  if (!isNodeOfType(callExpression, "CallExpression")) return null;
23463
22895
  const callee = stripParenExpression(callExpression.callee);
@@ -23520,8 +22952,8 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
23520
22952
  }
23521
22953
  return false;
23522
22954
  };
23523
- const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
23524
- const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
22955
+ const collectEffectStateWriteFacts = (analysis, effectNode) => {
22956
+ const frames = collectBoundedEffectExecutionFrames(analysis, effectNode);
23525
22957
  if (frames.length === 0) return [];
23526
22958
  const effectHasCleanup = hasCleanup(analysis, effectNode);
23527
22959
  const cleanupManagedStateDeclarators = /* @__PURE__ */ new Set();
@@ -23541,8 +22973,7 @@ const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) =>
23541
22973
  invocation: callExpression,
23542
22974
  isDeferred: frame.isDeferred,
23543
22975
  introducedBindings: collectIntroducedBindings(analysis, writtenValue),
23544
- substitutions: /* @__PURE__ */ new Map(),
23545
- currentFilename
22976
+ substitutions: /* @__PURE__ */ new Map()
23546
22977
  };
23547
22978
  valueEvidence = emptyEvidence();
23548
22979
  const returnedExpressions = getReturnedExpressions(writtenValue);
@@ -23591,7 +23022,7 @@ const noAdjustStateOnPropChange = defineRule({
23591
23022
  const dependencyReferences = getEffectDepsRefs(analysis, node);
23592
23023
  if (!dependencyReferences) return;
23593
23024
  if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
23594
- for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
23025
+ for (const fact of collectEffectStateWriteFacts(analysis, node)) {
23595
23026
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
23596
23027
  context.report({
23597
23028
  node: fact.callExpression,
@@ -24852,139 +24283,6 @@ const createRelativeImportSource = (filename, targetFilePath) => {
24852
24283
  return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
24853
24284
  };
24854
24285
  //#endregion
24855
- //#region src/plugin/utils/is-barrel-index-module.ts
24856
- const INDEX_MODULE_FILE_PATTERN = /^index\.(?:[cm]?[jt]sx?|mjs)$/;
24857
- const BINDING_IMPORT_DECLARATION_PATTERN = /^\s*import\s+(type\s+)?(?!["'])([^;]*?)\s+from\s+["']([^"']+)["']\s*;?\s*(?:(?:\/\/[^\n]*)?\s*)/gm;
24858
- const BARREL_REEXPORT_DECLARATION_PATTERN = /^\s*export\s+(type\s+)?(?:\*(?:\s+as\s+([\w$]+))?|\{([\s\S]*?)\})\s+from\s+["']([^"']+)["']\s*;?\s*(?:(?:\/\/[^\n]*)?\s*)/gm;
24859
- const LOCAL_EXPORT_SPECIFIER_DECLARATION_PATTERN = /^\s*export\s+(type\s+)?\{([\s\S]*?)\}\s*;?\s*(?:(?:\/\/[^\n]*)?\s*)/gm;
24860
- const barrelIndexModuleInfoCache = /* @__PURE__ */ new Map();
24861
- const isIndexModuleFilePath = (filePath) => INDEX_MODULE_FILE_PATTERN.test(path.basename(filePath));
24862
- const createNonBarrelInfo = () => ({
24863
- isBarrel: false,
24864
- exportsByName: /* @__PURE__ */ new Map(),
24865
- starExportSources: []
24866
- });
24867
- const addImportedBinding = (importedBindings, binding) => {
24868
- importedBindings.set(binding.localName, {
24869
- ...binding,
24870
- didExport: false
24871
- });
24872
- };
24873
- const collectNamedImportBindings = (namedSpecifiersText, source, declarationIsTypeOnly, importedBindings) => {
24874
- for (const specifier of parseExportSpecifiers(namedSpecifiersText, declarationIsTypeOnly)) addImportedBinding(importedBindings, {
24875
- localName: specifier.exportedName,
24876
- importedName: specifier.localName,
24877
- source,
24878
- isTypeOnly: specifier.isTypeOnly
24879
- });
24880
- };
24881
- const collectImportBindings = (importClause, source, declarationIsTypeOnly, importedBindings) => {
24882
- const trimmedImportClause = importClause.trim();
24883
- const namespaceMatch = trimmedImportClause.match(/(?:^|,\s*)\*\s+as\s+([\w$]+)/);
24884
- if (namespaceMatch?.[1]) addImportedBinding(importedBindings, {
24885
- localName: namespaceMatch[1],
24886
- importedName: "*",
24887
- source,
24888
- isTypeOnly: declarationIsTypeOnly
24889
- });
24890
- const namedImportMatch = trimmedImportClause.match(/\{([\s\S]*?)\}/);
24891
- if (namedImportMatch?.[1]) collectNamedImportBindings(namedImportMatch[1], source, declarationIsTypeOnly, importedBindings);
24892
- const defaultImportName = trimmedImportClause.split(",")[0]?.trim();
24893
- if (defaultImportName && !defaultImportName.startsWith("{") && !defaultImportName.startsWith("*")) addImportedBinding(importedBindings, {
24894
- localName: defaultImportName,
24895
- importedName: "default",
24896
- source,
24897
- isTypeOnly: declarationIsTypeOnly
24898
- });
24899
- };
24900
- const replaceKnownDeclarations = (sourceText, importedBindings, exportsByName, starExportSources) => {
24901
- let withoutKnownDeclarations = sourceText.replace(BINDING_IMPORT_DECLARATION_PATTERN, (_match, typeKeyword, importClause, source) => {
24902
- collectImportBindings(importClause, source, Boolean(typeKeyword), importedBindings);
24903
- return "";
24904
- });
24905
- withoutKnownDeclarations = withoutKnownDeclarations.replace(BARREL_REEXPORT_DECLARATION_PATTERN, (_match, typeKeyword, namespaceExportName, specifiersText, source) => {
24906
- const isTypeOnly = Boolean(typeKeyword);
24907
- if (namespaceExportName) {
24908
- exportsByName.set(namespaceExportName, {
24909
- exportedName: namespaceExportName,
24910
- importedName: "*",
24911
- source,
24912
- isTypeOnly
24913
- });
24914
- return "";
24915
- }
24916
- if (specifiersText) {
24917
- for (const specifier of parseExportSpecifiers(specifiersText, isTypeOnly)) exportsByName.set(specifier.exportedName, {
24918
- exportedName: specifier.exportedName,
24919
- importedName: specifier.localName,
24920
- source,
24921
- isTypeOnly: specifier.isTypeOnly
24922
- });
24923
- return "";
24924
- }
24925
- starExportSources.push(source);
24926
- return "";
24927
- });
24928
- withoutKnownDeclarations = withoutKnownDeclarations.replace(LOCAL_EXPORT_SPECIFIER_DECLARATION_PATTERN, (_match, typeKeyword, specifiersText) => {
24929
- for (const specifier of parseExportSpecifiers(specifiersText, Boolean(typeKeyword))) {
24930
- const importedBinding = importedBindings.get(specifier.localName);
24931
- if (!importedBinding) return _match;
24932
- importedBinding.didExport = true;
24933
- exportsByName.set(specifier.exportedName, {
24934
- exportedName: specifier.exportedName,
24935
- importedName: importedBinding.importedName,
24936
- source: importedBinding.source,
24937
- isTypeOnly: specifier.isTypeOnly || importedBinding.isTypeOnly
24938
- });
24939
- }
24940
- return "";
24941
- });
24942
- return withoutKnownDeclarations;
24943
- };
24944
- const hasUnexportedRuntimeImport = (importedBindings) => {
24945
- for (const binding of importedBindings.values()) if (!binding.isTypeOnly && !binding.didExport) return true;
24946
- return false;
24947
- };
24948
- const classifyBarrelModule = (sourceText) => {
24949
- const strippedSource = stripJsComments(sourceText).trim();
24950
- if (!strippedSource) return createNonBarrelInfo();
24951
- const importedBindings = /* @__PURE__ */ new Map();
24952
- const exportsByName = /* @__PURE__ */ new Map();
24953
- const starExportSources = [];
24954
- if (replaceKnownDeclarations(strippedSource, importedBindings, exportsByName, starExportSources).trim() || hasUnexportedRuntimeImport(importedBindings)) return createNonBarrelInfo();
24955
- return {
24956
- isBarrel: exportsByName.size > 0 || starExportSources.length > 0,
24957
- exportsByName,
24958
- starExportSources
24959
- };
24960
- };
24961
- const getBarrelIndexModuleInfo = (filePath) => {
24962
- if (!isIndexModuleFilePath(filePath)) return createNonBarrelInfo();
24963
- recordContentProbe(filePath);
24964
- let fileStat;
24965
- try {
24966
- fileStat = fs.statSync(filePath);
24967
- } catch {
24968
- fileStat = null;
24969
- }
24970
- const cachedResult = barrelIndexModuleInfoCache.get(filePath);
24971
- if (cachedResult !== void 0 && fileStat !== null && cachedResult.mtimeMs === fileStat.mtimeMs && cachedResult.size === fileStat.size) return cachedResult.moduleInfo;
24972
- if (fileStat === null) return createNonBarrelInfo();
24973
- let moduleInfo = createNonBarrelInfo();
24974
- try {
24975
- moduleInfo = classifyBarrelModule(fs.readFileSync(filePath, "utf8"));
24976
- } catch {
24977
- moduleInfo = createNonBarrelInfo();
24978
- }
24979
- barrelIndexModuleInfoCache.set(filePath, {
24980
- mtimeMs: fileStat.mtimeMs,
24981
- size: fileStat.size,
24982
- moduleInfo
24983
- });
24984
- return moduleInfo;
24985
- };
24986
- const isBarrelIndexModule = (filePath) => getBarrelIndexModuleInfo(filePath).isBarrel;
24987
- //#endregion
24988
24286
  //#region src/react-native-dependency-names.ts
24989
24287
  const EXPO_MANAGED_DEPENDENCY_NAMES = new Set([
24990
24288
  "expo",
@@ -25187,35 +24485,6 @@ const classifyReactNativeFileTarget = (context) => {
25187
24485
  };
25188
24486
  const isReactNativeFileActive = (context) => classifyReactNativeFileTarget(context) !== "web";
25189
24487
  //#endregion
25190
- //#region src/plugin/utils/resolve-barrel-export-file-path.ts
25191
- const getUniqueFilePath = (filePaths) => {
25192
- const uniqueFilePaths = new Set(filePaths);
25193
- if (uniqueFilePaths.size !== 1) return null;
25194
- const [filePath] = uniqueFilePaths;
25195
- return filePath ?? null;
25196
- };
25197
- const resolveStarExportFilePath = (barrelFilePath, exportedName, source, visitedFilePaths) => {
25198
- const resolvedTargetPath = resolveRelativeImportPath(barrelFilePath, source);
25199
- if (!resolvedTargetPath) return null;
25200
- const nestedTargetPath = resolveBarrelExportFilePath(resolvedTargetPath, exportedName, new Set(visitedFilePaths));
25201
- if (nestedTargetPath) return nestedTargetPath;
25202
- return doesModuleExportName(resolvedTargetPath, exportedName) ? resolvedTargetPath : null;
25203
- };
25204
- const resolveBarrelExportFilePath = (barrelFilePath, exportedName, visitedFilePaths = /* @__PURE__ */ new Set()) => {
25205
- if (visitedFilePaths.has(barrelFilePath)) return null;
25206
- visitedFilePaths.add(barrelFilePath);
25207
- const moduleInfo = getBarrelIndexModuleInfo(barrelFilePath);
25208
- if (!moduleInfo.isBarrel) return null;
25209
- const target = moduleInfo.exportsByName.get(exportedName);
25210
- if (target) {
25211
- const resolvedTargetPath = resolveRelativeImportPath(barrelFilePath, target.source);
25212
- if (!resolvedTargetPath) return null;
25213
- return resolveBarrelExportFilePath(resolvedTargetPath, target.importedName, visitedFilePaths) ?? resolvedTargetPath;
25214
- }
25215
- if (exportedName === "default") return null;
25216
- return getUniqueFilePath(moduleInfo.starExportSources.map((source) => resolveStarExportFilePath(barrelFilePath, exportedName, source, visitedFilePaths)).filter((filePath) => Boolean(filePath)));
25217
- };
25218
- //#endregion
25219
24488
  //#region src/plugin/rules/bundle-size/no-barrel-import.ts
25220
24489
  const TYPE_DECLARATION_FILE_PATTERN = /\.d\.[cm]?ts$/;
25221
24490
  const SERVER_ONLY_FILE_PATTERN = /\.server\.[cm]?[jt]sx?$/;
@@ -25545,7 +24814,7 @@ const isAsyncFunctionLike = (node) => {
25545
24814
  if (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) return Boolean(node.async);
25546
24815
  return false;
25547
24816
  };
25548
- const SYNCHRONOUS_ITERATION_METHOD_NAMES = new Set([
24817
+ const SYNCHRONOUS_ITERATION_METHOD_NAMES$1 = new Set([
25549
24818
  "forEach",
25550
24819
  "map",
25551
24820
  "filter",
@@ -25566,7 +24835,7 @@ const runsOnEffectDispatch = (functionNode) => {
25566
24835
  if (parent.callee === functionNode) return true;
25567
24836
  if (!(parent.arguments ?? []).some((argument) => argument === functionNode)) return false;
25568
24837
  const callee = parent.callee;
25569
- return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES.has(callee.property.name);
24838
+ return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES$1.has(callee.property.name);
25570
24839
  };
25571
24840
  const isTerminatingStatement = (statement) => isNodeOfType(statement, "BreakStatement") || isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement") || isNodeOfType(statement, "ContinueStatement");
25572
24841
  const analyzeBranchStatements = (branch, context) => analyzeStatementSequence(isNodeOfType(branch, "BlockStatement") ? branch.body ?? [] : [branch], context);
@@ -26510,6 +25779,57 @@ const noDefaultProps = defineRule({
26510
25779
  } })
26511
25780
  });
26512
25781
  //#endregion
25782
+ //#region src/plugin/rules/state-and-effects/no-derived-state.ts
25783
+ const getStateName$1 = (stateDeclarator) => {
25784
+ if (!isNodeOfType(stateDeclarator, "VariableDeclarator")) return "<state>";
25785
+ if (!isNodeOfType(stateDeclarator.id, "ArrayPattern")) return "<state>";
25786
+ const stateBinding = stateDeclarator.id.elements?.[0];
25787
+ if (stateBinding && isNodeOfType(stateBinding, "Identifier")) return stateBinding.name;
25788
+ const setterBinding = stateDeclarator.id.elements?.[1];
25789
+ if (!setterBinding || !isNodeOfType(setterBinding, "Identifier")) return "<state>";
25790
+ if (!setterBinding.name.startsWith("set") || setterBinding.name.length <= 3) return setterBinding.name;
25791
+ return setterBinding.name[3].toLowerCase() + setterBinding.name.slice(4);
25792
+ };
25793
+ const noDerivedState = defineRule({
25794
+ id: "no-derived-state",
25795
+ title: "Derived value copied into state",
25796
+ severity: "warn",
25797
+ tags: ["test-noise"],
25798
+ recommendation: "Work out the value while rendering (or with useMemo if it's expensive) instead of copying it into useState through a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state",
25799
+ create: (context) => ({ CallExpression(node) {
25800
+ if (!isUseEffect(node)) return;
25801
+ const analysis = getProgramAnalysis(node);
25802
+ if (!analysis) return;
25803
+ for (const fact of collectEffectStateWriteFacts(analysis, node)) {
25804
+ if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
25805
+ const stateName = getStateName$1(fact.stateDeclarator);
25806
+ context.report({
25807
+ node: fact.callExpression,
25808
+ message: `Storing "${stateName}" in state when you can derive it from other values costs an extra render.`
25809
+ });
25810
+ }
25811
+ } })
25812
+ });
25813
+ //#endregion
25814
+ //#region src/plugin/rules/state-and-effects/no-derived-state-effect.ts
25815
+ const noDerivedStateEffect = defineRule({
25816
+ id: "no-derived-state-effect",
25817
+ title: "Derived state stored in an effect",
25818
+ severity: "warn",
25819
+ tags: ["test-noise"],
25820
+ recommendation: "Work out derived values while rendering: `const x = fn(dep)`. To reset a component's state when a prop changes, give it a key prop: `<Component key={prop} />`. See https://react.dev/learn/you-might-not-need-an-effect",
25821
+ create: (context) => ({ CallExpression(node) {
25822
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
25823
+ const analysis = getProgramAnalysis(node);
25824
+ if (!analysis) return;
25825
+ if (!collectEffectStateWriteFacts(analysis, node).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
25826
+ context.report({
25827
+ node,
25828
+ message: "You pay an extra render for state you can derive from other values."
25829
+ });
25830
+ } })
25831
+ });
25832
+ //#endregion
26513
25833
  //#region src/plugin/utils/is-component-function.ts
26514
25834
  const isFunctionAssignedToComponent = (functionNode) => {
26515
25835
  let cursor = functionNode.parent ?? null;
@@ -26643,236 +25963,6 @@ const createComponentPropStackTracker = (callbacks) => {
26643
25963
  };
26644
25964
  };
26645
25965
  //#endregion
26646
- //#region src/plugin/rules/state-and-effects/utils/collect-render-state-write-facts.ts
26647
- const findReferenceDeclarator = (reference) => {
26648
- for (const definition of reference.resolved?.defs ?? []) {
26649
- const definitionNode = definition.node;
26650
- if (isNodeOfType(definitionNode, "VariableDeclarator")) return definitionNode;
26651
- }
26652
- return null;
26653
- };
26654
- const resolveSimpleRenderSource = (analysis, expression, visitedBindings = /* @__PURE__ */ new Set()) => {
26655
- const node = stripParenExpression(expression);
26656
- if (!isNodeOfType(node, "Identifier")) return null;
26657
- const reference = getRef(analysis, node);
26658
- if (!reference?.resolved || visitedBindings.has(reference.resolved)) return null;
26659
- if (isProp(analysis, reference)) {
26660
- if (isWholePropsObjectReference(analysis, reference)) return null;
26661
- return { bindingIdentity: reference.resolved };
26662
- }
26663
- if (isState(analysis, reference)) {
26664
- const stateDeclarator = getUseStateDecl(analysis, reference);
26665
- if (!stateDeclarator || !isGenuineReactHookDeclarator(analysis, stateDeclarator, "useState") || isExternallyDrivenState(analysis, reference)) return null;
26666
- return { bindingIdentity: reference.resolved };
26667
- }
26668
- if (reference.resolved.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init)) return null;
26669
- const declarator = findReferenceDeclarator(reference);
26670
- if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return null;
26671
- const nextVisitedBindings = new Set(visitedBindings);
26672
- nextVisitedBindings.add(reference.resolved);
26673
- return resolveSimpleRenderSource(analysis, declarator.init, nextVisitedBindings);
26674
- };
26675
- const doesEvidenceMatchSource = (evidence, source) => evidence.isExclusivelyRenderKnown && evidence.sourceReferences.size > 0 && [...evidence.sourceReferences].every((sourceReference) => sourceReference.resolved === source.bindingIdentity);
26676
- const getDirectBranchStatements = (branch) => {
26677
- if (isNodeOfType(branch, "BlockStatement")) return branch.body ?? [];
26678
- return [branch];
26679
- };
26680
- const getDirectCallExpression = (statement) => {
26681
- if (!isNodeOfType(statement, "ExpressionStatement")) return null;
26682
- const expression = stripParenExpression(statement.expression);
26683
- return isNodeOfType(expression, "CallExpression") ? expression : null;
26684
- };
26685
- const getDirectAssignmentExpression = (statement) => {
26686
- if (!isNodeOfType(statement, "ExpressionStatement")) return null;
26687
- const expression = stripParenExpression(statement.expression);
26688
- return isNodeOfType(expression, "AssignmentExpression") && expression.operator === "=" ? expression : null;
26689
- };
26690
- const findDirectStateSetterReference = (analysis, callExpression) => {
26691
- if (!isNodeOfType(callExpression, "CallExpression")) return null;
26692
- const callee = stripParenExpression(callExpression.callee);
26693
- if (!isNodeOfType(callee, "Identifier")) return null;
26694
- const reference = getRef(analysis, callee);
26695
- return reference && isStateSetter(analysis, reference) ? reference : null;
26696
- };
26697
- const getStaticRefCurrentReference = (analysis, expression) => {
26698
- const node = stripParenExpression(expression);
26699
- if (!isNodeOfType(node, "MemberExpression") || node.computed || !isNodeOfType(node.object, "Identifier") || !isNodeOfType(node.property, "Identifier") || node.property.name !== "current") return null;
26700
- const reference = getRef(analysis, node.object);
26701
- if (!reference) return null;
26702
- const declarator = findReferenceDeclarator(reference);
26703
- return declarator && isGenuineReactHookDeclarator(analysis, declarator, "useRef") ? reference : null;
26704
- };
26705
- const getStateTracker = (analysis, expression) => {
26706
- const node = stripParenExpression(expression);
26707
- if (!isNodeOfType(node, "Identifier")) return null;
26708
- const reference = getRef(analysis, node);
26709
- if (!reference || !isState(analysis, reference)) return null;
26710
- const declarator = getUseStateDecl(analysis, reference);
26711
- if (!declarator || !isGenuineReactHookDeclarator(analysis, declarator, "useState")) return null;
26712
- return {
26713
- kind: "state",
26714
- declarator
26715
- };
26716
- };
26717
- const getRefTracker = (analysis, expression) => {
26718
- const reference = getStaticRefCurrentReference(analysis, expression);
26719
- return reference ? {
26720
- kind: "ref",
26721
- reference
26722
- } : null;
26723
- };
26724
- const getTrackerInitializer = (tracker) => {
26725
- const declarator = tracker.kind === "state" ? tracker.declarator : findReferenceDeclarator(tracker.reference);
26726
- if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return null;
26727
- const initializer = declarator.init.arguments?.[0];
26728
- return initializer ? initializer : null;
26729
- };
26730
- const isTrackerSynchronized = (analysis, guard) => {
26731
- for (const statement of guard.statements) {
26732
- if (guard.tracker.kind === "state") {
26733
- const callExpression = getDirectCallExpression(statement);
26734
- if (!callExpression || !isNodeOfType(callExpression, "CallExpression")) continue;
26735
- const setterReference = findDirectStateSetterReference(analysis, callExpression);
26736
- if (!setterReference || getUseStateDecl(analysis, setterReference) !== guard.tracker.declarator || (callExpression.arguments ?? []).length !== 1) continue;
26737
- const writtenValue = callExpression.arguments?.[0];
26738
- if (writtenValue && resolveSimpleRenderSource(analysis, writtenValue)?.bindingIdentity === guard.source.bindingIdentity) return true;
26739
- continue;
26740
- }
26741
- const assignmentExpression = getDirectAssignmentExpression(statement);
26742
- if (!assignmentExpression || !isNodeOfType(assignmentExpression, "AssignmentExpression")) continue;
26743
- if (getStaticRefCurrentReference(analysis, assignmentExpression.left)?.resolved !== guard.tracker.reference.resolved) continue;
26744
- if (resolveSimpleRenderSource(analysis, assignmentExpression.right)?.bindingIdentity === guard.source.bindingIdentity) return true;
26745
- }
26746
- return false;
26747
- };
26748
- const parseRenderTrackerGuard = (analysis, statement) => {
26749
- if (!isNodeOfType(statement, "IfStatement") || statement.alternate || !isNodeOfType(statement.test, "BinaryExpression") || statement.test.operator !== "!==" || !isNodeOfType(statement.consequent, "BlockStatement")) return null;
26750
- const left = statement.test.left;
26751
- const right = statement.test.right;
26752
- const candidates = [{
26753
- sourceExpression: left,
26754
- trackerExpression: right
26755
- }, {
26756
- sourceExpression: right,
26757
- trackerExpression: left
26758
- }];
26759
- for (const candidate of candidates) {
26760
- const source = resolveSimpleRenderSource(analysis, candidate.sourceExpression);
26761
- if (!source) continue;
26762
- const tracker = getStateTracker(analysis, candidate.trackerExpression) ?? getRefTracker(analysis, candidate.trackerExpression);
26763
- if (!tracker) continue;
26764
- const trackerInitializer = getTrackerInitializer(tracker);
26765
- if (!trackerInitializer || resolveSimpleRenderSource(analysis, trackerInitializer)?.bindingIdentity !== source.bindingIdentity) continue;
26766
- const guard = {
26767
- source,
26768
- tracker,
26769
- statements: getDirectBranchStatements(statement.consequent)
26770
- };
26771
- return isTrackerSynchronized(analysis, guard) ? guard : null;
26772
- }
26773
- return null;
26774
- };
26775
- const isExclusiveDestinationSetterReference = (setterReference, callExpression) => {
26776
- if (!setterReference.resolved || !isNodeOfType(callExpression, "CallExpression")) return false;
26777
- const references = setterReference.resolved.references.filter((reference) => !reference.init);
26778
- if (references.length !== 1) return false;
26779
- return references[0].identifier === callExpression.callee;
26780
- };
26781
- const getStateInitializer = (stateDeclarator) => {
26782
- if (!isNodeOfType(stateDeclarator, "VariableDeclarator") || !isNodeOfType(stateDeclarator.init, "CallExpression")) return null;
26783
- const initializer = stateDeclarator.init.arguments?.[0];
26784
- return initializer ? initializer : null;
26785
- };
26786
- const collectRenderStateWriteFacts = (analysis, componentBody, currentFilename) => {
26787
- if (!isNodeOfType(componentBody, "BlockStatement") || !componentBody.parent || !isFunctionLike$1(componentBody.parent)) return [];
26788
- const componentFunction = componentBody.parent;
26789
- const facts = [];
26790
- for (const statement of componentBody.body ?? []) {
26791
- const guard = parseRenderTrackerGuard(analysis, statement);
26792
- if (!guard) continue;
26793
- for (const branchStatement of guard.statements) {
26794
- const callExpression = getDirectCallExpression(branchStatement);
26795
- if (!callExpression || !isNodeOfType(callExpression, "CallExpression")) continue;
26796
- const setterReference = findDirectStateSetterReference(analysis, callExpression);
26797
- if (!setterReference || (callExpression.arguments ?? []).length !== 1 || !isExclusiveDestinationSetterReference(setterReference, callExpression)) continue;
26798
- const stateDeclarator = getUseStateDecl(analysis, setterReference);
26799
- if (!stateDeclarator || !isGenuineReactHookDeclarator(analysis, stateDeclarator, "useState") || guard.tracker.kind === "state" && stateDeclarator === guard.tracker.declarator) continue;
26800
- const writtenValue = callExpression.arguments?.[0];
26801
- const initializer = getStateInitializer(stateDeclarator);
26802
- if (!writtenValue || !initializer || isFunctionLike$1(writtenValue) || !doesEvidenceMatchSource(collectRenderValueEvidence(analysis, writtenValue, componentFunction, currentFilename), guard.source) || !doesEvidenceMatchSource(collectRenderValueEvidence(analysis, initializer, componentFunction, currentFilename), guard.source)) continue;
26803
- facts.push({
26804
- callExpression,
26805
- stateDeclarator
26806
- });
26807
- }
26808
- }
26809
- return facts;
26810
- };
26811
- //#endregion
26812
- //#region src/plugin/rules/state-and-effects/no-derived-state.ts
26813
- const getStateName$1 = (stateDeclarator) => {
26814
- if (!isNodeOfType(stateDeclarator, "VariableDeclarator")) return "<state>";
26815
- if (!isNodeOfType(stateDeclarator.id, "ArrayPattern")) return "<state>";
26816
- const stateBinding = stateDeclarator.id.elements?.[0];
26817
- if (stateBinding && isNodeOfType(stateBinding, "Identifier")) return stateBinding.name;
26818
- const setterBinding = stateDeclarator.id.elements?.[1];
26819
- if (!setterBinding || !isNodeOfType(setterBinding, "Identifier")) return "<state>";
26820
- if (!setterBinding.name.startsWith("set") || setterBinding.name.length <= 3) return setterBinding.name;
26821
- return setterBinding.name[3].toLowerCase() + setterBinding.name.slice(4);
26822
- };
26823
- const noDerivedState = defineRule({
26824
- id: "no-derived-state",
26825
- title: "Derived value copied into state",
26826
- severity: "warn",
26827
- tags: ["test-noise"],
26828
- recommendation: "Work out the value while rendering (or with useMemo if it's expensive) instead of copying it into state and synchronizing it during render or through an effect. See https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state",
26829
- create: (context) => {
26830
- const reportStateWrite = (callExpression, stateDeclarator) => {
26831
- const stateName = getStateName$1(stateDeclarator);
26832
- context.report({
26833
- node: callExpression,
26834
- message: `Storing "${stateName}" in state when you can derive it from other values costs an extra render.`
26835
- });
26836
- };
26837
- return {
26838
- ...createComponentPropStackTracker({ onComponentEnter: (componentBody) => {
26839
- if (!componentBody) return;
26840
- const analysis = getProgramAnalysis(componentBody);
26841
- if (!analysis) return;
26842
- for (const fact of collectRenderStateWriteFacts(analysis, componentBody, context.filename)) reportStateWrite(fact.callExpression, fact.stateDeclarator);
26843
- } }).visitors,
26844
- CallExpression(node) {
26845
- if (!isUseEffect(node)) return;
26846
- const analysis = getProgramAnalysis(node);
26847
- if (!analysis) return;
26848
- for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
26849
- if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
26850
- reportStateWrite(fact.callExpression, fact.stateDeclarator);
26851
- }
26852
- }
26853
- };
26854
- }
26855
- });
26856
- //#endregion
26857
- //#region src/plugin/rules/state-and-effects/no-derived-state-effect.ts
26858
- const noDerivedStateEffect = defineRule({
26859
- id: "no-derived-state-effect",
26860
- title: "Derived state stored in an effect",
26861
- severity: "warn",
26862
- tags: ["test-noise"],
26863
- recommendation: "Work out derived values while rendering: `const x = fn(dep)`. To reset a component's state when a prop changes, give it a key prop: `<Component key={prop} />`. See https://react.dev/learn/you-might-not-need-an-effect",
26864
- create: (context) => ({ CallExpression(node) {
26865
- if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
26866
- const analysis = getProgramAnalysis(node);
26867
- if (!analysis) return;
26868
- if (!collectEffectStateWriteFacts(analysis, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
26869
- context.report({
26870
- node,
26871
- message: "You pay an extra render for state you can derive from other values."
26872
- });
26873
- } })
26874
- });
26875
- //#endregion
26876
25966
  //#region src/plugin/utils/is-initial-only-prop-name.ts
26877
25967
  const isInitialOnlyPropName = (propName) => {
26878
25968
  if (propName === "initialValue" || propName === "defaultValue" || propName === "seedValue") return true;
@@ -28378,14 +27468,14 @@ const hasDocumentClassListMutation = (node) => {
28378
27468
  //#endregion
28379
27469
  //#region src/plugin/rules/state-and-effects/no-effect-event-handler.ts
28380
27470
  const hasEventLikeNode = (node) => findTriggeredSideEffectCalleeName(node) !== null || hasDocumentClassListMutation(node);
28381
- const unwrapChainExpression$2 = (node) => {
27471
+ const unwrapChainExpression$1 = (node) => {
28382
27472
  if (!node) return null;
28383
27473
  if (isNodeOfType(node, "ChainExpression")) return node.expression;
28384
27474
  return node;
28385
27475
  };
28386
27476
  const collectGuardExpressions = (node, into) => {
28387
27477
  if (!node) return;
28388
- const unwrappedNode = unwrapChainExpression$2(node);
27478
+ const unwrappedNode = unwrapChainExpression$1(node);
28389
27479
  if (!unwrappedNode) return;
28390
27480
  const rootIdentifierName = getRootIdentifierName(unwrappedNode);
28391
27481
  if (rootIdentifierName) {
@@ -28416,7 +27506,7 @@ const isReturnOnlyStatement = (node) => {
28416
27506
  };
28417
27507
  const hasEventLikeRemainingStatements = (statements) => statements.some((statement) => !isNodeOfType(statement, "ReturnStatement") && hasEventLikeNode(statement));
28418
27508
  const doesGuardMatchDependency = (guardExpression, dependencyExpression) => {
28419
- const unwrappedDependencyExpression = unwrapChainExpression$2(dependencyExpression);
27509
+ const unwrappedDependencyExpression = unwrapChainExpression$1(dependencyExpression);
28420
27510
  if (!unwrappedDependencyExpression) return false;
28421
27511
  if (areExpressionsStructurallyEqual(guardExpression.expression, unwrappedDependencyExpression)) return true;
28422
27512
  return isNodeOfType(unwrappedDependencyExpression, "Identifier") && unwrappedDependencyExpression.name === guardExpression.rootIdentifierName;
@@ -30475,405 +29565,6 @@ const noGrayOnColoredBackground = defineRule({
30475
29565
  } })
30476
29566
  });
30477
29567
  //#endregion
30478
- //#region src/plugin/utils/find-enclosing-jsx-opening-element.ts
30479
- const findEnclosingJsxOpeningElement = (node) => {
30480
- let cursor = node.parent;
30481
- while (cursor) {
30482
- if (isNodeOfType(cursor, "JSXElement")) return cursor.openingElement;
30483
- if (isNodeOfType(cursor, "JSXFragment")) return null;
30484
- cursor = cursor.parent ?? null;
30485
- }
30486
- return null;
30487
- };
30488
- //#endregion
30489
- //#region src/plugin/utils/has-client-render-evidence.ts
30490
- const hasClientRenderEvidence = (componentOrHookNode, fileHasUseClientDirective) => {
30491
- if (fileHasUseClientDirective) return true;
30492
- const displayName = componentOrHookDisplayNameForFunction(componentOrHookNode);
30493
- if (displayName && isReactHookName(displayName)) return true;
30494
- let callsHook = false;
30495
- walkAst((isFunctionLike$1(componentOrHookNode) ? componentOrHookNode.body : null) ?? componentOrHookNode, (child) => {
30496
- if (callsHook) return false;
30497
- if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && isReactHookName(child.callee.name)) {
30498
- callsHook = true;
30499
- return false;
30500
- }
30501
- });
30502
- return callsHook;
30503
- };
30504
- //#endregion
30505
- //#region src/plugin/utils/has-suppress-hydration-warning-attribute.ts
30506
- const hasSuppressHydrationWarningAttribute = (openingElement) => {
30507
- if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement")) return false;
30508
- for (const attr of openingElement.attributes ?? []) if (isNodeOfType(attr, "JSXAttribute") && isNodeOfType(attr.name, "JSXIdentifier") && attr.name.name === "suppressHydrationWarning") return true;
30509
- return false;
30510
- };
30511
- //#endregion
30512
- //#region src/plugin/utils/read-initial-state-boolean.ts
30513
- const readLogicalResult = (operator, leftResult, rightResult) => {
30514
- if (operator === "&&") {
30515
- if (leftResult === false || rightResult === false) return false;
30516
- if (leftResult === true && rightResult === true) return true;
30517
- return null;
30518
- }
30519
- if (leftResult === true || rightResult === true) return true;
30520
- if (leftResult === false && rightResult === false) return false;
30521
- return null;
30522
- };
30523
- const readIdentifierInitialStateBoolean = (identifier, scopes, visitedBindings, allowLazyInitializer) => {
30524
- if (!isNodeOfType(identifier, "Identifier")) return null;
30525
- if (identifier.name === "undefined" && scopes.isGlobalReference(identifier)) return false;
30526
- const binding = findVariableInitializer(identifier, identifier.name);
30527
- if (!binding || visitedBindings.has(binding.bindingIdentifier)) return null;
30528
- visitedBindings.add(binding.bindingIdentifier);
30529
- const declarator = findDeclaratorForBinding(binding.bindingIdentifier);
30530
- if (!declarator?.init || scopes.symbolFor(identifier)?.declarationNode !== declarator) return null;
30531
- const initializer = stripParenExpression(declarator.init);
30532
- if (isNodeOfType(declarator.id, "ArrayPattern") && declarator.id.elements?.[0] === binding.bindingIdentifier && isReactApiCall(initializer, "useState", scopes, { allowGlobalReactNamespace: true }) && isNodeOfType(initializer, "CallExpression")) {
30533
- const initialState = initializer.arguments?.[0];
30534
- if (!initialState) return false;
30535
- if (initialState.type === "SpreadElement") return null;
30536
- return readInitialStateBooleanInternal(initialState, scopes, visitedBindings, true);
30537
- }
30538
- const declaration = declarator.parent;
30539
- if (!isNodeOfType(declarator.id, "Identifier") || declarator.id !== binding.bindingIdentifier || !isNodeOfType(declaration, "VariableDeclaration") || declaration.kind !== "const") return null;
30540
- return readInitialStateBooleanInternal(initializer, scopes, visitedBindings, allowLazyInitializer);
30541
- };
30542
- const readInitialStateBooleanInternal = (expression, scopes, visitedBindings, allowLazyInitializer) => {
30543
- const unwrappedExpression = stripParenExpression(expression);
30544
- if (isNodeOfType(unwrappedExpression, "Literal")) return Boolean(unwrappedExpression.value);
30545
- if (isNodeOfType(unwrappedExpression, "Identifier")) return readIdentifierInitialStateBoolean(unwrappedExpression, scopes, visitedBindings, allowLazyInitializer);
30546
- if (allowLazyInitializer && (isNodeOfType(unwrappedExpression, "ArrowFunctionExpression") || isNodeOfType(unwrappedExpression, "FunctionExpression"))) {
30547
- if (unwrappedExpression.async || isNodeOfType(unwrappedExpression, "FunctionExpression") && unwrappedExpression.generator) return null;
30548
- if (!isNodeOfType(unwrappedExpression.body, "BlockStatement")) return readInitialStateBooleanInternal(unwrappedExpression.body, scopes, visitedBindings, false);
30549
- if (unwrappedExpression.body.body.length !== 1) return null;
30550
- const returnStatement = unwrappedExpression.body.body[0];
30551
- if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return null;
30552
- return readInitialStateBooleanInternal(returnStatement.argument, scopes, visitedBindings, false);
30553
- }
30554
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
30555
- const argumentResult = readInitialStateBooleanInternal(unwrappedExpression.argument, scopes, visitedBindings, false);
30556
- return argumentResult === null ? null : !argumentResult;
30557
- }
30558
- if (isNodeOfType(unwrappedExpression, "LogicalExpression") && (unwrappedExpression.operator === "&&" || unwrappedExpression.operator === "||")) return readLogicalResult(unwrappedExpression.operator, readInitialStateBooleanInternal(unwrappedExpression.left, scopes, new Set(visitedBindings), false), readInitialStateBooleanInternal(unwrappedExpression.right, scopes, new Set(visitedBindings), false));
30559
- return null;
30560
- };
30561
- const readInitialStateBoolean = (expression, scopes) => readInitialStateBooleanInternal(expression, scopes, /* @__PURE__ */ new Set(), false);
30562
- //#endregion
30563
- //#region src/plugin/utils/is-after-client-only-early-return.ts
30564
- const isAfterClientOnlyEarlyReturn = (node, componentOrHookNode, scopes) => {
30565
- const body = isFunctionLike$1(componentOrHookNode) ? componentOrHookNode.body : null;
30566
- if (!isNodeOfType(body, "BlockStatement")) return false;
30567
- const ancestors = /* @__PURE__ */ new Set();
30568
- let currentNode = node;
30569
- while (currentNode) {
30570
- ancestors.add(currentNode);
30571
- currentNode = currentNode.parent ?? null;
30572
- }
30573
- for (const statement of body.body ?? []) {
30574
- if (ancestors.has(statement)) return false;
30575
- if (!isNodeOfType(statement, "IfStatement")) continue;
30576
- const initialConditionResult = readInitialStateBoolean(statement.test, scopes);
30577
- if (initialConditionResult === true && statementAlwaysExits(statement.consequent)) return true;
30578
- if (initialConditionResult === false && statement.alternate && statementAlwaysExits(statement.alternate)) return true;
30579
- }
30580
- return false;
30581
- };
30582
- //#endregion
30583
- //#region src/plugin/utils/is-gated-by-falsy-initial-state.ts
30584
- const isGatedByFalsyInitialState = (node, scopes) => {
30585
- let cursor = node;
30586
- let parent = node.parent;
30587
- while (parent) {
30588
- if (isNodeOfType(parent, "LogicalExpression") && parent.right === cursor && (parent.operator === "&&" && readInitialStateBoolean(parent.left, scopes) === false || parent.operator === "||" && readInitialStateBoolean(parent.left, scopes) === true)) return true;
30589
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === cursor && readInitialStateBoolean(parent.test, scopes) === false || parent.alternate === cursor && readInitialStateBoolean(parent.test, scopes) === true)) return true;
30590
- if (isNodeOfType(parent, "IfStatement") && (parent.consequent === cursor && readInitialStateBoolean(parent.test, scopes) === false || parent.alternate === cursor && readInitialStateBoolean(parent.test, scopes) === true)) return true;
30591
- cursor = parent;
30592
- parent = parent.parent ?? null;
30593
- }
30594
- return false;
30595
- };
30596
- //#endregion
30597
- //#region src/plugin/rules/performance/no-hydration-branch-on-browser-global.ts
30598
- const evaluateEquality = (operator, left, right) => {
30599
- if (operator === "===" || operator === "==") return left === right;
30600
- if (operator === "!==" || operator === "!=") return left !== right;
30601
- return null;
30602
- };
30603
- const readTypeofBrowserGlobal = (expression, context) => {
30604
- const unwrappedExpression = stripParenExpression(expression);
30605
- if (!isNodeOfType(unwrappedExpression, "UnaryExpression") || unwrappedExpression.operator !== "typeof") return null;
30606
- const argument = stripParenExpression(unwrappedExpression.argument);
30607
- if (isNodeOfType(argument, "Identifier")) return (argument.name === "window" || argument.name === "document") && context.scopes.isGlobalReference(argument) ? argument.name : null;
30608
- if (!isNodeOfType(argument, "MemberExpression") || argument.computed || !isNodeOfType(argument.object, "Identifier") || argument.object.name !== "globalThis" || !context.scopes.isGlobalReference(argument.object) || !isNodeOfType(argument.property, "Identifier") || argument.property.name !== "window" && argument.property.name !== "document") return null;
30609
- return argument.property.name;
30610
- };
30611
- const matchBrowserPredicate = (expression, context) => {
30612
- const unwrappedExpression = stripParenExpression(expression);
30613
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
30614
- const innerMatch = matchBrowserPredicate(unwrappedExpression.argument, context);
30615
- return innerMatch ? {
30616
- browserGlobalName: innerMatch.browserGlobalName,
30617
- clientResult: !innerMatch.clientResult,
30618
- serverResult: !innerMatch.serverResult
30619
- } : null;
30620
- }
30621
- if (!isNodeOfType(unwrappedExpression, "BinaryExpression")) return null;
30622
- const leftGlobalName = readTypeofBrowserGlobal(unwrappedExpression.left, context);
30623
- const rightGlobalName = readTypeofBrowserGlobal(unwrappedExpression.right, context);
30624
- const leftString = isNodeOfType(unwrappedExpression.left, "Literal") ? unwrappedExpression.left.value : null;
30625
- const rightString = isNodeOfType(unwrappedExpression.right, "Literal") ? unwrappedExpression.right.value : null;
30626
- const browserGlobalName = leftGlobalName && typeof rightString === "string" ? leftGlobalName : rightGlobalName && typeof leftString === "string" ? rightGlobalName : null;
30627
- const comparedType = leftGlobalName && typeof rightString === "string" ? rightString : rightGlobalName && typeof leftString === "string" ? leftString : null;
30628
- if (!browserGlobalName || !comparedType) return null;
30629
- const clientResult = evaluateEquality(unwrappedExpression.operator, "object", comparedType);
30630
- const serverResult = evaluateEquality(unwrappedExpression.operator, "undefined", comparedType);
30631
- if (clientResult === null || serverResult === null || clientResult === serverResult) return null;
30632
- return {
30633
- browserGlobalName,
30634
- clientResult,
30635
- serverResult
30636
- };
30637
- };
30638
- const readLogicalConditionResult = (operator, leftResult, rightResult) => {
30639
- if (operator === "&&") {
30640
- if (leftResult === false || rightResult === false) return false;
30641
- if (leftResult === true && rightResult === true) return true;
30642
- return null;
30643
- }
30644
- if (leftResult === true || rightResult === true) return true;
30645
- if (leftResult === false && rightResult === false) return false;
30646
- return null;
30647
- };
30648
- const readHydrationConditionResult = (expression, context, runtime) => {
30649
- const unwrappedExpression = stripParenExpression(expression);
30650
- const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
30651
- if (predicateMatch) return predicateMatch[`${runtime}Result`];
30652
- const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
30653
- if (staticResult !== null) return staticResult;
30654
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
30655
- const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
30656
- return argumentResult === null ? null : !argumentResult;
30657
- }
30658
- if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
30659
- return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
30660
- };
30661
- const matchHydrationCondition = (expression, context) => {
30662
- const unwrappedExpression = stripParenExpression(expression);
30663
- const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
30664
- if (predicateMatch) return {
30665
- predicateMatch,
30666
- predicateNode: unwrappedExpression
30667
- };
30668
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
30669
- if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
30670
- const leftMatch = matchHydrationCondition(unwrappedExpression.left, context);
30671
- const rightMatch = matchHydrationCondition(unwrappedExpression.right, context);
30672
- if (leftMatch && rightMatch) {
30673
- const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client");
30674
- const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server");
30675
- return clientResult !== null && serverResult !== null && clientResult !== serverResult ? leftMatch : null;
30676
- }
30677
- const nestedMatch = leftMatch ?? rightMatch;
30678
- if (!nestedMatch) return null;
30679
- const otherResult = readInitialStateBoolean(leftMatch ? unwrappedExpression.right : unwrappedExpression.left, context.scopes);
30680
- if (unwrappedExpression.operator === "&&" && otherResult === false || unwrappedExpression.operator === "||" && otherResult === true) return null;
30681
- return nestedMatch;
30682
- };
30683
- const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
30684
- const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
30685
- if (!leftNode || !rightNode) return leftNode === rightNode;
30686
- const left = stripParenExpression(leftNode);
30687
- const right = stripParenExpression(rightNode);
30688
- if (areExpressionsStructurallyEqual(left, right)) return true;
30689
- if (left.type !== right.type) return false;
30690
- if (isNodeOfType(left, "JSXText") && isNodeOfType(right, "JSXText")) return left.value === right.value;
30691
- if (isNodeOfType(left, "JSXExpressionContainer") && isNodeOfType(right, "JSXExpressionContainer")) {
30692
- if (!isAstNode(left.expression) || !isAstNode(right.expression)) return left.expression.type === right.expression.type;
30693
- return areRenderedBranchesEquivalent(left.expression, right.expression);
30694
- }
30695
- if (isNodeOfType(left, "JSXElement") && isNodeOfType(right, "JSXElement")) {
30696
- if (flattenJsxName$1(left.openingElement.name) !== flattenJsxName$1(right.openingElement.name)) return false;
30697
- if (!areNodeArraysEquivalent(left.openingElement.attributes, right.openingElement.attributes)) return false;
30698
- return areNodeArraysEquivalent(left.children, right.children);
30699
- }
30700
- if (isNodeOfType(left, "JSXFragment") && isNodeOfType(right, "JSXFragment")) return areNodeArraysEquivalent(left.children, right.children);
30701
- if (isNodeOfType(left, "JSXAttribute") && isNodeOfType(right, "JSXAttribute")) {
30702
- if (flattenJsxName$1(left.name) !== flattenJsxName$1(right.name)) return false;
30703
- return areRenderedBranchesEquivalent(left.value, right.value);
30704
- }
30705
- if (isNodeOfType(left, "JSXSpreadAttribute") && isNodeOfType(right, "JSXSpreadAttribute")) return areRenderedBranchesEquivalent(left.argument, right.argument);
30706
- if (isNodeOfType(left, "TemplateLiteral") && isNodeOfType(right, "TemplateLiteral")) {
30707
- if (left.quasis.length !== right.quasis.length) return false;
30708
- if (!left.quasis.every((quasi, index) => quasi.value.cooked === right.quasis[index]?.value.cooked && quasi.value.raw === right.quasis[index]?.value.raw)) return false;
30709
- return areNodeArraysEquivalent(left.expressions, right.expressions);
30710
- }
30711
- return false;
30712
- };
30713
- const isRenderedValue = (node) => {
30714
- const unwrappedNode = stripParenExpression(node);
30715
- if (isNodeOfType(unwrappedNode, "Literal")) return unwrappedNode.value !== null && unwrappedNode.value !== true && unwrappedNode.value !== false && unwrappedNode.value !== "";
30716
- if (isNodeOfType(unwrappedNode, "TemplateLiteral")) return unwrappedNode.expressions.length > 0 || unwrappedNode.quasis[0]?.value.cooked !== "";
30717
- return isNodeOfType(unwrappedNode, "JSXElement") || isNodeOfType(unwrappedNode, "JSXFragment");
30718
- };
30719
- const findRenderedValueInAndBranch = (node) => {
30720
- const unwrappedNode = stripParenExpression(node);
30721
- if (isRenderedValue(unwrappedNode)) return unwrappedNode;
30722
- if (!isNodeOfType(unwrappedNode, "LogicalExpression") || unwrappedNode.operator !== "&&") return null;
30723
- return findRenderedValueInAndBranch(unwrappedNode.right);
30724
- };
30725
- const findEnclosingJsxAttribute = (node) => {
30726
- let currentNode = node.parent;
30727
- while (currentNode) {
30728
- if (isNodeOfType(currentNode, "JSXAttribute")) return currentNode;
30729
- if (isNodeOfType(currentNode, "JSXElement") || isNodeOfType(currentNode, "JSXFragment") || isFunctionLike$1(currentNode)) return null;
30730
- currentNode = currentNode.parent;
30731
- }
30732
- return null;
30733
- };
30734
- const isInRenderedOutput = (node, componentOrHookNode, scopes) => {
30735
- let currentNode = node;
30736
- let parentNode = currentNode.parent;
30737
- while (parentNode) {
30738
- if (isNodeOfType(parentNode, "JSXExpressionContainer")) {
30739
- const attribute = findEnclosingJsxAttribute(parentNode);
30740
- return attribute ? !isEventHandlerAttribute(attribute) : true;
30741
- }
30742
- if (isNodeOfType(parentNode, "ReturnStatement")) {
30743
- if (findEnclosingFunction(parentNode) === componentOrHookNode) return true;
30744
- }
30745
- if (parentNode === componentOrHookNode) return isFunctionLike$1(componentOrHookNode) && !isNodeOfType(componentOrHookNode.body, "BlockStatement") && componentOrHookNode.body === currentNode;
30746
- if (isFunctionLike$1(parentNode) && !executesDuringRender(parentNode, scopes)) return false;
30747
- currentNode = parentNode;
30748
- parentNode = currentNode.parent;
30749
- }
30750
- return false;
30751
- };
30752
- const getReturnedValues = (statement) => {
30753
- if (!statement) return [];
30754
- if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? [statement.argument] : [];
30755
- if (isNodeOfType(statement, "IfStatement")) return [...getReturnedValues(statement.consequent), ...getReturnedValues(statement.alternate)];
30756
- if (!isNodeOfType(statement, "BlockStatement")) return [];
30757
- const returnedValues = [];
30758
- for (const childStatement of statement.body) {
30759
- returnedValues.push(...getReturnedValues(childStatement));
30760
- if (statementAlwaysExits(childStatement)) break;
30761
- }
30762
- return returnedValues;
30763
- };
30764
- const findFollowingReturnedValues = (ifStatement) => {
30765
- const parentNode = ifStatement.parent;
30766
- if (!isNodeOfType(parentNode, "BlockStatement")) return [];
30767
- const statementIndex = parentNode.body.findIndex((statement) => statement === ifStatement);
30768
- if (statementIndex < 0) return [];
30769
- const returnedValues = [];
30770
- for (const statement of parentNode.body.slice(statementIndex + 1)) {
30771
- returnedValues.push(...getReturnedValues(statement));
30772
- if (statementAlwaysExits(statement)) break;
30773
- }
30774
- return returnedValues;
30775
- };
30776
- const areConditionExpressionsEquivalent = (leftExpression, rightExpression) => {
30777
- const left = stripParenExpression(leftExpression);
30778
- const right = stripParenExpression(rightExpression);
30779
- if (areExpressionsStructurallyEqual(left, right)) return true;
30780
- if (left.type !== right.type) return false;
30781
- if (isNodeOfType(left, "UnaryExpression") && isNodeOfType(right, "UnaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.argument, right.argument);
30782
- if (isNodeOfType(left, "LogicalExpression") && isNodeOfType(right, "LogicalExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left) && areConditionExpressionsEquivalent(left.right, right.right);
30783
- if (isNodeOfType(left, "BinaryExpression") && isNodeOfType(right, "BinaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left) && areConditionExpressionsEquivalent(left.right, right.right);
30784
- return false;
30785
- };
30786
- const areReturnTreesEquivalent = (leftStatement, rightStatement) => {
30787
- if (!leftStatement || !rightStatement) return leftStatement === rightStatement;
30788
- if (isNodeOfType(leftStatement, "ReturnStatement") && isNodeOfType(rightStatement, "ReturnStatement")) return areRenderedBranchesEquivalent(leftStatement.argument, rightStatement.argument);
30789
- if (isNodeOfType(leftStatement, "IfStatement") && isNodeOfType(rightStatement, "IfStatement")) return areConditionExpressionsEquivalent(leftStatement.test, rightStatement.test) && areReturnTreesEquivalent(leftStatement.consequent, rightStatement.consequent) && areReturnTreesEquivalent(leftStatement.alternate, rightStatement.alternate);
30790
- if (!isNodeOfType(leftStatement, "BlockStatement") || !isNodeOfType(rightStatement, "BlockStatement")) return false;
30791
- const leftReturningStatements = leftStatement.body.filter((statement) => getReturnedValues(statement).length > 0);
30792
- const rightReturningStatements = rightStatement.body.filter((statement) => getReturnedValues(statement).length > 0);
30793
- return leftReturningStatements.length === rightReturningStatements.length && leftReturningStatements.every((statement, index) => areReturnTreesEquivalent(statement, rightReturningStatements[index]));
30794
- };
30795
- const isStructuralRenderedValue = (node) => {
30796
- if (!node) return false;
30797
- const unwrappedNode = stripParenExpression(node);
30798
- return isNodeOfType(unwrappedNode, "JSXElement") || isNodeOfType(unwrappedNode, "JSXFragment");
30799
- };
30800
- const branchRootsSuppressSameElement = (leftBranch, rightBranch) => {
30801
- if (!rightBranch) return false;
30802
- const left = stripParenExpression(leftBranch);
30803
- const right = stripParenExpression(rightBranch);
30804
- return isNodeOfType(left, "JSXElement") && isNodeOfType(right, "JSXElement") && flattenJsxName$1(left.openingElement.name) === flattenJsxName$1(right.openingElement.name) && hasSuppressHydrationWarningAttribute(left.openingElement) && hasSuppressHydrationWarningAttribute(right.openingElement);
30805
- };
30806
- const noHydrationBranchOnBrowserGlobal = defineRule({
30807
- id: "no-hydration-branch-on-browser-global",
30808
- title: "Server and client render different branches",
30809
- severity: "error",
30810
- category: "Correctness",
30811
- requires: ["ssr"],
30812
- recommendation: "Render the same initial output on the server and client, then switch after mount or use useSyncExternalStore with a stable server snapshot.",
30813
- create: (context) => {
30814
- if (isTestlikeFilename(context.filename)) return {};
30815
- if (classifyReactNativeFileTarget(context) === "react-native") return {};
30816
- let fileHasUseClientDirective = false;
30817
- let fileIsEmailTemplate = false;
30818
- const reportedNodes = /* @__PURE__ */ new Set();
30819
- const reportHydrationBranch = (conditionNode, leftBranch, rightBranch, requiresRenderedContext) => {
30820
- const conditionMatch = matchHydrationCondition(conditionNode, context);
30821
- if (!conditionMatch) return;
30822
- const { predicateMatch, predicateNode } = conditionMatch;
30823
- if (reportedNodes.has(predicateNode)) return;
30824
- if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
30825
- const componentOrHookNode = findRenderPhaseComponentOrHook(predicateNode, context.scopes);
30826
- if (!componentOrHookNode) return;
30827
- if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
30828
- if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
30829
- if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
30830
- const attribute = findEnclosingJsxAttribute(predicateNode);
30831
- if (!attribute || isEventHandlerAttribute(attribute)) return;
30832
- }
30833
- if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
30834
- if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
30835
- const openingElement = findEnclosingJsxOpeningElement(predicateNode);
30836
- if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
30837
- if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
30838
- if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
30839
- reportedNodes.add(predicateNode);
30840
- context.report({
30841
- node: predicateNode,
30842
- message: `\`typeof ${predicateMatch.browserGlobalName}\` selects different rendered output on the server and during hydration. Render the same initial output, then switch after mount.`
30843
- });
30844
- };
30845
- return {
30846
- Program(node) {
30847
- fileHasUseClientDirective = hasDirective(node, "use client");
30848
- fileIsEmailTemplate = hasEmailTemplateImport(node);
30849
- },
30850
- ConditionalExpression(node) {
30851
- reportHydrationBranch(node.test, node.consequent, node.alternate, true);
30852
- },
30853
- LogicalExpression(node) {
30854
- if (node.operator !== "&&" && node.operator !== "||") return;
30855
- const renderedValue = node.operator === "&&" ? findRenderedValueInAndBranch(node.right) : isRenderedValue(node.right) ? node.right : null;
30856
- if (!renderedValue) return;
30857
- reportHydrationBranch(node, renderedValue, null, true);
30858
- },
30859
- IfStatement(node) {
30860
- if (node.alternate && areReturnTreesEquivalent(node.consequent, node.alternate)) return;
30861
- const consequentValues = getReturnedValues(node.consequent);
30862
- const alternateValues = node.alternate ? getReturnedValues(node.alternate) : findFollowingReturnedValues(node);
30863
- if (consequentValues.length === 0 || alternateValues.length === 0) return;
30864
- const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes);
30865
- if (!componentOrHookNode) return;
30866
- const enclosingFunction = findEnclosingFunction(node);
30867
- if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes))) return;
30868
- for (const consequentValue of consequentValues) for (const alternateValue of alternateValues) {
30869
- if (!isRenderedValue(consequentValue) && !isRenderedValue(alternateValue)) continue;
30870
- reportHydrationBranch(node.test, consequentValue, alternateValue, false);
30871
- }
30872
- }
30873
- };
30874
- }
30875
- });
30876
- //#endregion
30877
29568
  //#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
30878
29569
  const MESSAGE$22 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
30879
29570
  const noImgLazyWithHighFetchpriority = defineRule({
@@ -30894,131 +29585,6 @@ const noImgLazyWithHighFetchpriority = defineRule({
30894
29585
  } })
30895
29586
  });
30896
29587
  //#endregion
30897
- //#region src/plugin/rules/state-and-effects/no-impure-state-updater.ts
30898
- const TIMER_FUNCTION_NAMES = new Set([
30899
- "cancelAnimationFrame",
30900
- "clearInterval",
30901
- "clearTimeout",
30902
- "queueMicrotask",
30903
- "requestAnimationFrame",
30904
- "setInterval",
30905
- "setTimeout"
30906
- ]);
30907
- const STORAGE_MUTATION_METHOD_NAMES = new Set([
30908
- "clear",
30909
- "removeItem",
30910
- "setItem"
30911
- ]);
30912
- const STORAGE_RECEIVER_NAMES = new Set(["localStorage", "sessionStorage"]);
30913
- const EXTERNAL_READ_METHOD_NAMES = new Set(["getBoundingClientRect", "getClientRects"]);
30914
- const NOTIFICATION_RECEIVER_NAMES = new Set([
30915
- "message",
30916
- "notification",
30917
- "toast"
30918
- ]);
30919
- const NOTIFICATION_METHOD_NAMES = new Set([
30920
- "error",
30921
- "info",
30922
- "loading",
30923
- "open",
30924
- "show",
30925
- "success",
30926
- "warning"
30927
- ]);
30928
- const getMemberCall = (node) => {
30929
- if (!isNodeOfType(node, "CallExpression")) return null;
30930
- if (!isNodeOfType(node.callee, "MemberExpression") || node.callee.computed) return null;
30931
- if (!isNodeOfType(node.callee.property, "Identifier")) return null;
30932
- return {
30933
- methodName: node.callee.property.name,
30934
- receiver: stripParenExpression(node.callee.object)
30935
- };
30936
- };
30937
- const isNotificationReceiver = (receiver, scopes) => {
30938
- if (!isNodeOfType(receiver, "Identifier")) return false;
30939
- if (!NOTIFICATION_RECEIVER_NAMES.has(receiver.name)) return false;
30940
- const symbol = scopes.symbolFor(receiver);
30941
- if (symbol?.kind === "import") return true;
30942
- if (!isNodeOfType(symbol?.initializer, "CallExpression")) return false;
30943
- const callee = symbol.initializer.callee;
30944
- return isNodeOfType(callee, "Identifier") && /^use(?:Message|Notification|Toast)$/.test(callee.name);
30945
- };
30946
- const getKnownImpureCall = (callExpression, scopes) => {
30947
- if (isNodeOfType(callExpression.callee, "Identifier") && TIMER_FUNCTION_NAMES.has(callExpression.callee.name) && scopes.isGlobalReference(callExpression.callee)) return `${callExpression.callee.name}()`;
30948
- const memberCall = getMemberCall(callExpression);
30949
- if (!memberCall) return null;
30950
- const { methodName, receiver } = memberCall;
30951
- if (STORAGE_MUTATION_METHOD_NAMES.has(methodName) && isNodeOfType(receiver, "Identifier") && STORAGE_RECEIVER_NAMES.has(receiver.name) && scopes.isGlobalReference(receiver)) return `${receiver.name}.${methodName}()`;
30952
- if (EXTERNAL_READ_METHOD_NAMES.has(methodName)) return `.${methodName}()`;
30953
- if (NOTIFICATION_METHOD_NAMES.has(methodName) && isNodeOfType(receiver, "Identifier") && isNotificationReceiver(receiver, scopes)) return `${receiver.name}.${methodName}()`;
30954
- return null;
30955
- };
30956
- const getExternalAssignmentDescription = (assignmentTarget, updater, scopes) => {
30957
- let rootIdentifier = null;
30958
- if (isNodeOfType(assignmentTarget, "Identifier")) rootIdentifier = assignmentTarget;
30959
- else if (isNodeOfType(assignmentTarget, "MemberExpression") && isNodeOfType(assignmentTarget.object, "Identifier")) rootIdentifier = assignmentTarget.object;
30960
- if (!rootIdentifier) return null;
30961
- const updaterScope = scopes.ownScopeFor(updater);
30962
- if (!updaterScope) return null;
30963
- const symbol = scopes.symbolFor(rootIdentifier);
30964
- if (!symbol) return `the external value "${rootIdentifier.name}"`;
30965
- if (symbol.kind === "parameter" && symbol.scope === updaterScope) return `the updater argument "${rootIdentifier.name}"`;
30966
- return isDescendantScope(symbol.scope, updaterScope) ? null : `the captured value "${rootIdentifier.name}"`;
30967
- };
30968
- const findImpureUpdaterOperation = (updater, scopes) => {
30969
- const analysis = getProgramAnalysis(updater);
30970
- let operation = null;
30971
- walkAst(updater, (child) => {
30972
- if (operation) return false;
30973
- if (child !== updater && isFunctionLike$1(child) && !executesDuringRender(child, scopes)) return false;
30974
- if (isNodeOfType(child, "CallExpression")) {
30975
- if (isNodeOfType(child.callee, "Identifier") && analysis) {
30976
- const calleeReference = getRef(analysis, child.callee);
30977
- if (calleeReference && isStateSetterCall(analysis, calleeReference)) {
30978
- operation = `the nested state update "${child.callee.name}()"`;
30979
- return false;
30980
- }
30981
- }
30982
- const impureCall = getKnownImpureCall(child, scopes);
30983
- if (impureCall) {
30984
- operation = impureCall;
30985
- return false;
30986
- }
30987
- }
30988
- if (isNodeOfType(child, "AssignmentExpression")) {
30989
- operation = getExternalAssignmentDescription(child.left, updater, scopes);
30990
- if (operation) return false;
30991
- }
30992
- if (isNodeOfType(child, "UpdateExpression")) {
30993
- operation = getExternalAssignmentDescription(child.argument, updater, scopes);
30994
- if (operation) return false;
30995
- }
30996
- });
30997
- return operation;
30998
- };
30999
- const noImpureStateUpdater = defineRule({
31000
- id: "no-impure-state-updater",
31001
- title: "State updater has side effects",
31002
- severity: "error",
31003
- recommendation: "Keep state updater callbacks pure and return only the next state. Move notifications, storage, timers, ref writes, and other external work into the event or effect that queues the update.",
31004
- create: (context) => ({ CallExpression(node) {
31005
- const updater = node.arguments?.[0];
31006
- if (!updater || !isFunctionLike$1(updater)) return;
31007
- const analysis = getProgramAnalysis(node);
31008
- if (!analysis || !isNodeOfType(node.callee, "Identifier")) return;
31009
- const calleeReference = getRef(analysis, node.callee);
31010
- if (!calleeReference || !isStateSetterCall(analysis, calleeReference)) return;
31011
- const stateDeclarator = getUseStateDecl(analysis, calleeReference);
31012
- if (!isNodeOfType(stateDeclarator, "VariableDeclarator") || !isNodeOfType(stateDeclarator.init, "CallExpression") || !isReactApiCall(stateDeclarator.init, "useState", context.scopes, { allowGlobalReactNamespace: true })) return;
31013
- const operation = findImpureUpdaterOperation(updater, context.scopes);
31014
- if (!operation) return;
31015
- context.report({
31016
- node: updater,
31017
- message: `This state updater performs ${operation}. React may run updater functions more than once, so side effects here can repeat or observe inconsistent external state.`
31018
- });
31019
- } })
31020
- });
31021
- //#endregion
31022
29588
  //#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
31023
29589
  const MESSAGE$21 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
31024
29590
  const REACT_USE_REF_OPTIONS = {
@@ -31185,7 +29751,7 @@ const noInitializeState = defineRule({
31185
29751
  if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
31186
29752
  const analysis = getProgramAnalysis(node);
31187
29753
  if (!analysis) return;
31188
- for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
29754
+ for (const fact of collectEffectStateWriteFacts(analysis, node)) {
31189
29755
  if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
31190
29756
  const stateName = getStateName(fact.stateDeclarator);
31191
29757
  context.report({
@@ -31844,6 +30410,111 @@ const noLegacyContextApi = defineRule({
31844
30410
  }
31845
30411
  });
31846
30412
  //#endregion
30413
+ //#region src/plugin/utils/executes-during-render.ts
30414
+ const SYNCHRONOUS_ITERATION_METHOD_NAMES = new Set([
30415
+ "map",
30416
+ "filter",
30417
+ "forEach",
30418
+ "flatMap",
30419
+ "reduce",
30420
+ "reduceRight",
30421
+ "some",
30422
+ "every",
30423
+ "find",
30424
+ "findIndex",
30425
+ "findLast",
30426
+ "findLastIndex",
30427
+ "sort",
30428
+ "toSorted"
30429
+ ]);
30430
+ const executesDuringRender = (functionNode) => {
30431
+ const parent = functionNode.parent;
30432
+ if (!isNodeOfType(parent, "CallExpression")) return false;
30433
+ if (parent.callee === functionNode) return true;
30434
+ if (isHookCall$2(parent, "useMemo") && parent.arguments?.[0] === functionNode) return true;
30435
+ return isNodeOfType(parent.callee, "MemberExpression") && !parent.callee.computed && isNodeOfType(parent.callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES.has(parent.callee.property.name) && parent.arguments?.[0] === functionNode;
30436
+ };
30437
+ //#endregion
30438
+ //#region src/plugin/utils/has-suppress-hydration-warning-attribute.ts
30439
+ const hasSuppressHydrationWarningAttribute = (openingElement) => {
30440
+ if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement")) return false;
30441
+ for (const attr of openingElement.attributes ?? []) if (isNodeOfType(attr, "JSXAttribute") && isNodeOfType(attr.name, "JSXIdentifier") && attr.name.name === "suppressHydrationWarning") return true;
30442
+ return false;
30443
+ };
30444
+ //#endregion
30445
+ //#region src/plugin/utils/find-declarator-for-binding.ts
30446
+ const findDeclaratorForBinding = (bindingIdentifier) => {
30447
+ let ancestor = bindingIdentifier.parent;
30448
+ while (ancestor) {
30449
+ if (isNodeOfType(ancestor, "VariableDeclarator")) return ancestor;
30450
+ if (isNodeOfType(ancestor, "FunctionDeclaration") || isNodeOfType(ancestor, "FunctionExpression") || isNodeOfType(ancestor, "ArrowFunctionExpression") || isNodeOfType(ancestor, "Program")) return null;
30451
+ ancestor = ancestor.parent ?? null;
30452
+ }
30453
+ return null;
30454
+ };
30455
+ //#endregion
30456
+ //#region src/plugin/utils/references-falsy-initial-state.ts
30457
+ const isFalsyLiteral = (node) => {
30458
+ if (!node) return true;
30459
+ if (isNodeOfType(node, "Literal")) return !node.value;
30460
+ return isNodeOfType(node, "Identifier") && node.name === "undefined";
30461
+ };
30462
+ const isFalsyInitialStateBinding = (identifier) => {
30463
+ if (!isNodeOfType(identifier, "Identifier")) return false;
30464
+ const binding = findVariableInitializer(identifier, identifier.name);
30465
+ if (!binding) return false;
30466
+ const declarator = findDeclaratorForBinding(binding.bindingIdentifier);
30467
+ if (!declarator?.init) return false;
30468
+ const init = stripParenExpression(declarator.init);
30469
+ if (!isHookCall$2(init, "useState") || !isNodeOfType(init, "CallExpression")) return false;
30470
+ if (!isNodeOfType(declarator.id, "ArrayPattern")) return false;
30471
+ if (declarator.id.elements?.[0] !== binding.bindingIdentifier) return false;
30472
+ return isFalsyLiteral(init.arguments?.[0]);
30473
+ };
30474
+ const referencesFalsyInitialState = (expression) => flattenLogicalAndChain(stripParenExpression(expression)).some((operand) => isFalsyInitialStateBinding(stripParenExpression(operand)));
30475
+ //#endregion
30476
+ //#region src/plugin/utils/is-gated-by-falsy-initial-state.ts
30477
+ const isGatedByFalsyInitialState = (node) => {
30478
+ let cursor = node;
30479
+ let parent = node.parent;
30480
+ while (parent) {
30481
+ if (isNodeOfType(parent, "LogicalExpression") && parent.operator === "&&" && parent.right === cursor && referencesFalsyInitialState(parent.left)) return true;
30482
+ if (isNodeOfType(parent, "ConditionalExpression") && parent.consequent === cursor && referencesFalsyInitialState(parent.test)) return true;
30483
+ if (isNodeOfType(parent, "IfStatement") && parent.consequent === cursor && referencesFalsyInitialState(parent.test)) return true;
30484
+ cursor = parent;
30485
+ parent = parent.parent ?? null;
30486
+ }
30487
+ return false;
30488
+ };
30489
+ //#endregion
30490
+ //#region src/plugin/utils/references-client-only-flag.ts
30491
+ const CLIENT_ONLY_FLAG_NAME_PATTERN = /^(?:is|has|did)?_?(?:client|mounted|hydrated|browser)(?:_?(?:side|ready|only))?$/i;
30492
+ const referencesClientOnlyFlag = (expression) => {
30493
+ const unwrapped = stripParenExpression(expression);
30494
+ if (isNodeOfType(unwrapped, "Identifier")) return CLIENT_ONLY_FLAG_NAME_PATTERN.test(unwrapped.name);
30495
+ if (isNodeOfType(unwrapped, "MemberExpression")) {
30496
+ const property = unwrapped.property;
30497
+ return isNodeOfType(property, "Identifier") && CLIENT_ONLY_FLAG_NAME_PATTERN.test(property.name);
30498
+ }
30499
+ if (isNodeOfType(unwrapped, "UnaryExpression") && unwrapped.operator === "!") return referencesClientOnlyFlag(unwrapped.argument);
30500
+ if (isNodeOfType(unwrapped, "LogicalExpression")) return referencesClientOnlyFlag(unwrapped.left) || referencesClientOnlyFlag(unwrapped.right);
30501
+ return false;
30502
+ };
30503
+ //#endregion
30504
+ //#region src/plugin/utils/is-inside-client-only-guard.ts
30505
+ const isInsideClientOnlyGuard = (node) => {
30506
+ let cursor = node;
30507
+ let parent = node.parent;
30508
+ while (parent) {
30509
+ if (isNodeOfType(parent, "LogicalExpression") && parent.operator === "&&" && parent.right === cursor && referencesClientOnlyFlag(parent.left)) return true;
30510
+ if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === cursor || parent.alternate === cursor) && referencesClientOnlyFlag(parent.test)) return true;
30511
+ if (isNodeOfType(parent, "IfStatement") && parent.consequent === cursor && referencesClientOnlyFlag(parent.test)) return true;
30512
+ cursor = parent;
30513
+ parent = parent.parent ?? null;
30514
+ }
30515
+ return false;
30516
+ };
30517
+ //#endregion
31847
30518
  //#region src/plugin/rules/performance/no-locale-format-in-render.ts
31848
30519
  const LOCALE_FORMAT_METHOD_NAMES = new Set([
31849
30520
  "toLocaleString",
@@ -31973,12 +30644,75 @@ const matchDateDefaultStringification = (node) => {
31973
30644
  }
31974
30645
  return null;
31975
30646
  };
30647
+ const findRenderPhaseComponentOrHook = (node) => {
30648
+ let functionNode = findEnclosingFunction(node);
30649
+ while (functionNode) {
30650
+ if (componentOrHookDisplayNameForFunction(functionNode)) return functionNode;
30651
+ if (!executesDuringRender(functionNode)) return null;
30652
+ functionNode = findEnclosingFunction(functionNode);
30653
+ }
30654
+ return null;
30655
+ };
30656
+ const hasClientRenderEvidence = (componentOrHookNode, fileHasUseClientDirective) => {
30657
+ if (fileHasUseClientDirective) return true;
30658
+ const displayName = componentOrHookDisplayNameForFunction(componentOrHookNode);
30659
+ if (displayName && isReactHookName(displayName)) return true;
30660
+ let callsHook = false;
30661
+ walkAst((isFunctionLike$1(componentOrHookNode) ? componentOrHookNode.body : null) ?? componentOrHookNode, (child) => {
30662
+ if (callsHook) return false;
30663
+ if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && isReactHookName(child.callee.name)) {
30664
+ callsHook = true;
30665
+ return false;
30666
+ }
30667
+ });
30668
+ return callsHook;
30669
+ };
30670
+ const isAfterClientOnlyEarlyReturn = (node, componentOrHookNode) => {
30671
+ const body = isFunctionLike$1(componentOrHookNode) ? componentOrHookNode.body : null;
30672
+ if (!isNodeOfType(body, "BlockStatement")) return false;
30673
+ const ancestors = /* @__PURE__ */ new Set();
30674
+ let cursor = node;
30675
+ while (cursor) {
30676
+ ancestors.add(cursor);
30677
+ cursor = cursor.parent ?? null;
30678
+ }
30679
+ for (const statement of body.body ?? []) {
30680
+ if (ancestors.has(statement)) return false;
30681
+ if (!isNodeOfType(statement, "IfStatement")) continue;
30682
+ if (!referencesClientOnlyFlag(statement.test) && !referencesFalsyInitialState(statement.test)) continue;
30683
+ let returnsEarly = false;
30684
+ walkAst(statement.consequent, (child) => {
30685
+ if (isFunctionLike$1(child)) return false;
30686
+ if (isNodeOfType(child, "ReturnStatement")) {
30687
+ returnsEarly = true;
30688
+ return false;
30689
+ }
30690
+ });
30691
+ if (returnsEarly) return true;
30692
+ }
30693
+ return false;
30694
+ };
30695
+ const findEnclosingJsxOpeningElement = (node) => {
30696
+ let cursor = node.parent;
30697
+ while (cursor) {
30698
+ if (isNodeOfType(cursor, "JSXElement")) return cursor.openingElement;
30699
+ if (isNodeOfType(cursor, "JSXFragment")) return null;
30700
+ cursor = cursor.parent ?? null;
30701
+ }
30702
+ return null;
30703
+ };
31976
30704
  const noLocaleFormatInRender = defineRule({
31977
30705
  id: "no-locale-format-in-render",
31978
30706
  title: "Locale/timezone formatting during render",
31979
30707
  severity: "warn",
31980
30708
  category: "Correctness",
31981
- requires: ["ssr"],
30709
+ disabledWhen: [
30710
+ "vite",
30711
+ "cra",
30712
+ "expo",
30713
+ "react-native",
30714
+ "unknown"
30715
+ ],
31982
30716
  recommendation: "Format locale/timezone-dependent values in a post-mount useEffect + state, or pass an explicit locale and timeZone so the server and the browser render the same text. Only runs on SSR-capable projects.",
31983
30717
  create: (context) => {
31984
30718
  if (isTestlikeFilename(context.filename)) return {};
@@ -31988,12 +30722,13 @@ const noLocaleFormatInRender = defineRule({
31988
30722
  const reportedNodes = /* @__PURE__ */ new Set();
31989
30723
  const reportIfRenderPhase = (match) => {
31990
30724
  if (reportedNodes.has(match.node)) return;
31991
- const componentOrHookNode = findRenderPhaseComponentOrHook(match.node, context.scopes);
30725
+ const componentOrHookNode = findRenderPhaseComponentOrHook(match.node);
31992
30726
  if (!componentOrHookNode) return;
31993
30727
  if (fileIsEmailTemplate) return;
31994
30728
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
31995
- if (isGatedByFalsyInitialState(match.node, context.scopes)) return;
31996
- if (isAfterClientOnlyEarlyReturn(match.node, componentOrHookNode, context.scopes)) return;
30729
+ if (isInsideClientOnlyGuard(match.node)) return;
30730
+ if (isGatedByFalsyInitialState(match.node)) return;
30731
+ if (isAfterClientOnlyEarlyReturn(match.node, componentOrHookNode)) return;
31997
30732
  if (hasSuppressHydrationWarningAttribute(findEnclosingJsxOpeningElement(match.node))) return;
31998
30733
  if (isGeneratedImageRenderContext(context, findEnclosingJsxOpeningElement(match.node)?.parent ?? match.node)) return;
31999
30734
  reportedNodes.add(match.node);
@@ -32020,7 +30755,7 @@ const noLocaleFormatInRender = defineRule({
32020
30755
  if (!isNodeOfType(expression, "CallExpression")) return;
32021
30756
  if (!isNodeOfType(expression.callee, "Identifier")) return;
32022
30757
  const helperName = expression.callee.name;
32023
- const componentOrHookNode = findRenderPhaseComponentOrHook(node, context.scopes);
30758
+ const componentOrHookNode = findRenderPhaseComponentOrHook(node);
32024
30759
  if (!componentOrHookNode) return;
32025
30760
  const helperNode = findVariableInitializer(expression.callee, helperName)?.initializer;
32026
30761
  if (!helperNode || !isFunctionLike$1(helperNode)) return;
@@ -32032,8 +30767,9 @@ const noLocaleFormatInRender = defineRule({
32032
30767
  if (!match || reportedNodes.has(match.node)) return;
32033
30768
  if (fileIsEmailTemplate) return;
32034
30769
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
32035
- if (isGatedByFalsyInitialState(node, context.scopes)) return;
32036
- if (isAfterClientOnlyEarlyReturn(node, componentOrHookNode, context.scopes)) return;
30770
+ if (isInsideClientOnlyGuard(node)) return;
30771
+ if (isGatedByFalsyInitialState(node)) return;
30772
+ if (isAfterClientOnlyEarlyReturn(node, componentOrHookNode)) return;
32037
30773
  if (hasSuppressHydrationWarningAttribute(findEnclosingJsxOpeningElement(node))) return;
32038
30774
  reportedNodes.add(match.node);
32039
30775
  context.report({
@@ -32135,6 +30871,9 @@ const noLongTransitionDuration = defineRule({
32135
30871
  const BOOLEAN_PROP_PREFIX_PATTERN = /^(?:is|has|should|can|show|hide|enable|disable|with)[A-Z]/;
32136
30872
  const isBooleanPrefixedPropName = (propName) => BOOLEAN_PROP_PREFIX_PATTERN.test(propName);
32137
30873
  //#endregion
30874
+ //#region src/plugin/utils/is-event-handler-attribute.ts
30875
+ const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && /^on[A-Z]/.test(node.name.name);
30876
+ //#endregion
32138
30877
  //#region src/plugin/rules/architecture/no-many-boolean-props.ts
32139
30878
  const IMPERATIVE_CALLBACK_PREFIX_PATTERN = /^(?:show|hide|enable|disable)[A-Z]/;
32140
30879
  const collectCallbackUsedNames = (componentBody, propsParam, scopes) => {
@@ -32266,7 +31005,7 @@ const noMatchMediaInStateInitializer = defineRule({
32266
31005
  title: "matchMedia in state initializer",
32267
31006
  severity: "warn",
32268
31007
  category: "Correctness",
32269
- requires: ["ssr"],
31008
+ disabledWhen: ["vite", "cra"],
32270
31009
  recommendation: "Prefer CSS media queries for layout, or subscribe with `useSyncExternalStore` and provide a stable server snapshot.",
32271
31010
  create: (context) => {
32272
31011
  if (isTestlikeFilename(context.filename)) return {};
@@ -32817,41 +31556,6 @@ const noMutableInDeps = defineRule({
32817
31556
  }
32818
31557
  });
32819
31558
  //#endregion
32820
- //#region src/plugin/utils/is-result-discarded-call.ts
32821
- const isResultDiscardedCall = (callExpression) => {
32822
- let node = callExpression;
32823
- let parent = node.parent;
32824
- while (parent) {
32825
- if (isNodeOfType(parent, "ExpressionStatement")) return true;
32826
- if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "void") return true;
32827
- if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === node) return true;
32828
- if (isNodeOfType(parent, "ChainExpression")) {
32829
- node = parent;
32830
- parent = node.parent;
32831
- continue;
32832
- }
32833
- if (isNodeOfType(parent, "LogicalExpression") && parent.right === node) {
32834
- node = parent;
32835
- parent = node.parent;
32836
- continue;
32837
- }
32838
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === node || parent.alternate === node)) {
32839
- node = parent;
32840
- parent = node.parent;
32841
- continue;
32842
- }
32843
- if (isNodeOfType(parent, "SequenceExpression")) {
32844
- const expressions = parent.expressions ?? [];
32845
- if (expressions[expressions.length - 1] !== node) return true;
32846
- node = parent;
32847
- parent = node.parent;
32848
- continue;
32849
- }
32850
- return false;
32851
- }
32852
- return false;
32853
- };
32854
- //#endregion
32855
31559
  //#region src/plugin/rules/state-and-effects/utils/lodash-mutator-call.ts
32856
31560
  const LODASH_MUTATOR_NAMES = new Set([
32857
31561
  "set",
@@ -34142,7 +32846,7 @@ const isUseRefIdentifier = (identifier) => {
34142
32846
  };
34143
32847
  const COMMAND_PROP_NAME_PATTERN = /^(fetch|load|refetch|dispatch|register|render)([A-Z_]|$)/;
34144
32848
  const SETTER_NAMED_PROP_PATTERN = /^set[A-Z]/;
34145
- const unwrapChainExpression$1 = (node) => isNodeOfType(node, "ChainExpression") ? node.expression : node;
32849
+ const unwrapChainExpression = (node) => isNodeOfType(node, "ChainExpression") ? node.expression : node;
34146
32850
  const FUNCTION_WRAPPER_HOOK_NAMES$1 = new Set([
34147
32851
  "useCallback",
34148
32852
  "useMemo",
@@ -34177,7 +32881,7 @@ const isDirectParentCallbackRef = (analysis, ref) => {
34177
32881
  return Boolean(ref.resolved?.defs.some((def) => {
34178
32882
  const node = def.node;
34179
32883
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
34180
- const initializer = unwrapChainExpression$1(node.init);
32884
+ const initializer = unwrapChainExpression(node.init);
34181
32885
  const wrappedFunction = getWrapperHookWrappedFunction(initializer);
34182
32886
  if (wrappedFunction) {
34183
32887
  if (wrappedFunction.async) return false;
@@ -34187,170 +32891,10 @@ const isDirectParentCallbackRef = (analysis, ref) => {
34187
32891
  return getDownstreamRefs(analysis, initializer).some((initializerRef) => getUpstreamRefs(analysis, initializerRef).some((upstreamRef) => isProp(analysis, upstreamRef)));
34188
32892
  }));
34189
32893
  };
34190
- const getDeclarationKind = (declarator) => {
34191
- const declaration = declarator.parent;
34192
- return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
34193
- };
34194
- const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
34195
- const getDestructuredPropertyName = (bindingIdentifier) => {
34196
- const property = bindingIdentifier.parent;
34197
- if (!property || !isNodeOfType(property, "Property")) return null;
34198
- if (property.computed) return isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" ? String(property.key.value) : null;
34199
- return isNodeOfType(property.key, "Identifier") ? property.key.name : null;
34200
- };
34201
- const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
34202
- const unwrappedExpression = stripParenExpression(expression);
34203
- if (isNodeOfType(unwrappedExpression, "Identifier")) {
34204
- const callbackReference = getRef(analysis, unwrappedExpression);
34205
- const callbackVariable = callbackReference?.resolved;
34206
- if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable)) return null;
34207
- if (isProp(analysis, callbackReference)) {
34208
- if (isWholePropsObjectReference(analysis, callbackReference)) return null;
34209
- const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
34210
- return (bindingIdentifier && getDestructuredPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
34211
- }
34212
- if (hasMutableBindingWrite(callbackReference)) return null;
34213
- const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
34214
- if (definitions.length !== 1) return null;
34215
- const declarator = definitions[0];
34216
- if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || getDeclarationKind(declarator) !== "const" || !declarator.init) return null;
34217
- visitedVariables.add(callbackVariable);
34218
- if (isNodeOfType(declarator.id, "ObjectPattern")) {
34219
- const bindingIdentifier = callbackVariable.defs[0]?.name;
34220
- const propertyName = bindingIdentifier ? getDestructuredPropertyName(bindingIdentifier) : null;
34221
- const propsReference = getDownstreamRefs(analysis, declarator.init).find((candidateReference) => isWholePropsObjectReference(analysis, candidateReference));
34222
- return propertyName && propsReference ? propertyName : null;
34223
- }
34224
- if (!isNodeOfType(declarator.id, "Identifier")) return null;
34225
- return getParentCallbackPropName(analysis, declarator.init, visitedVariables);
34226
- }
34227
- if (!isNodeOfType(unwrappedExpression, "MemberExpression")) return null;
34228
- const callbackName = getStaticMemberPropertyName(unwrappedExpression);
34229
- if (!callbackName) return null;
34230
- const receiver = stripParenExpression(unwrappedExpression.object);
34231
- if (!isNodeOfType(receiver, "Identifier")) return null;
34232
- const receiverReference = getRef(analysis, receiver);
34233
- if (!receiverReference || !isWholePropsObjectReference(analysis, receiverReference)) return null;
34234
- return callbackName;
34235
- };
34236
- const isNullishRefInitializer = (initializer) => {
34237
- if (!initializer) return true;
34238
- const unwrappedInitializer = stripParenExpression(initializer);
34239
- if (isNodeOfType(unwrappedInitializer, "Literal")) return unwrappedInitializer.value === null;
34240
- if (!isNodeOfType(unwrappedInitializer, "Identifier")) return false;
34241
- return unwrappedInitializer.name === "undefined";
34242
- };
34243
- const getDirectComponentBodyStatement = (node, componentBody) => {
34244
- let current = node;
34245
- while (current?.parent && current.parent !== componentBody) current = current.parent;
34246
- return current?.parent === componentBody ? current : null;
34247
- };
34248
- const getVariableForDeclarator = (analysis, declarator) => {
34249
- for (const scope of analysis.scopeManager.scopes) {
34250
- const variable = scope.variables.find((candidateVariable) => candidateVariable.defs.some((definition) => definition.node === declarator));
34251
- if (variable) return variable;
34252
- }
34253
- return null;
34254
- };
34255
- const getRefMember = (identifier) => {
34256
- const receiver = findTransparentExpressionRoot(identifier);
34257
- const member = receiver.parent;
34258
- if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== receiver) return null;
34259
- return member;
34260
- };
34261
- const getRefMemberAssignment = (identifier) => {
34262
- const member = getRefMember(identifier);
34263
- if (!member) return null;
34264
- const memberRoot = findTransparentExpressionRoot(member);
34265
- const assignment = memberRoot.parent;
34266
- if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.left !== memberRoot) return null;
34267
- return assignment;
34268
- };
34269
- const getRefAliasDeclarator = (identifier) => {
34270
- const initializer = findTransparentExpressionRoot(identifier);
34271
- const declarator = initializer.parent;
34272
- if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== initializer || !isNodeOfType(declarator.id, "Identifier") || getDeclarationKind(declarator) !== "const") return null;
34273
- return declarator;
34274
- };
34275
- const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
34276
- if (!isNodeOfType(receiver, "Identifier")) return null;
34277
- const receiverReference = getRef(analysis, receiver);
34278
- if (!receiverReference?.resolved || hasMutableBindingWrite(receiverReference)) return null;
34279
- const variables = /* @__PURE__ */ new Set();
34280
- let currentVariable = receiverReference.resolved;
34281
- let refCall = null;
34282
- while (currentVariable && !variables.has(currentVariable)) {
34283
- variables.add(currentVariable);
34284
- const definitions = currentVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
34285
- if (definitions.length !== 1) return null;
34286
- const declarator = definitions[0];
34287
- if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.id, "Identifier") || !declarator.init) return null;
34288
- if (isNodeOfType(declarator.init, "CallExpression") && isReactUseRefCall(declarator.init)) {
34289
- refCall = declarator.init;
34290
- break;
34291
- }
34292
- if (getDeclarationKind(declarator) !== "const" || !isNodeOfType(stripParenExpression(declarator.init), "Identifier")) return null;
34293
- const upstreamReference = getRef(analysis, stripParenExpression(declarator.init));
34294
- if (!upstreamReference?.resolved || hasMutableBindingWrite(upstreamReference)) return null;
34295
- currentVariable = upstreamReference.resolved;
34296
- }
34297
- if (!refCall) return null;
34298
- const pendingVariables = [...variables];
34299
- while (pendingVariables.length > 0) {
34300
- const variable = pendingVariables.pop();
34301
- if (!variable) continue;
34302
- for (const candidateReference of variable.references) {
34303
- const aliasDeclarator = getRefAliasDeclarator(candidateReference.identifier);
34304
- if (!aliasDeclarator) continue;
34305
- const aliasVariable = getVariableForDeclarator(analysis, aliasDeclarator);
34306
- if (!aliasVariable || variables.has(aliasVariable)) continue;
34307
- if (aliasVariable.references.some((aliasReference) => aliasReference.isWrite() && !aliasReference.init)) return null;
34308
- variables.add(aliasVariable);
34309
- pendingVariables.push(aliasVariable);
34310
- }
34311
- }
34312
- return {
34313
- refCall,
34314
- variables
34315
- };
34316
- };
34317
- const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactUseRefCall) => {
34318
- const callee = stripParenExpression(callExpression.callee);
34319
- if (!isNodeOfType(callee, "MemberExpression") || getStaticMemberPropertyName(callee) !== "current") return null;
34320
- const bindingProvenance = getRefBindingProvenance(analysis, stripParenExpression(callee.object), isReactUseRefCall);
34321
- if (!bindingProvenance) return null;
34322
- const { refCall, variables } = bindingProvenance;
34323
- const componentFunction = findEnclosingFunction(effectCall);
34324
- if (!componentFunction || !isFunctionLike$1(componentFunction) || !isComponentFunction$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
34325
- if (!getDirectComponentBodyStatement(effectCall, componentFunction.body)) return null;
34326
- const callbackPropNames = /* @__PURE__ */ new Set();
34327
- const initializer = refCall.arguments?.[0];
34328
- const initializerCallbackName = initializer ? getParentCallbackPropName(analysis, initializer) : null;
34329
- if (initializerCallbackName) callbackPropNames.add(initializerCallbackName);
34330
- else if (!isNullishRefInitializer(initializer)) return null;
34331
- for (const variable of variables) for (const candidateReference of variable.references) {
34332
- if (candidateReference.init) continue;
34333
- if (candidateReference.isWrite()) return null;
34334
- const identifier = candidateReference.identifier;
34335
- if (getRefAliasDeclarator(identifier)) continue;
34336
- const member = getRefMember(identifier);
34337
- if (!member || getStaticMemberPropertyName(member) !== "current") return null;
34338
- const memberParent = findTransparentExpressionRoot(member).parent;
34339
- if (memberParent && (isNodeOfType(memberParent, "UpdateExpression") || isNodeOfType(memberParent, "UnaryExpression") && memberParent.operator === "delete")) return null;
34340
- const assignment = getRefMemberAssignment(identifier);
34341
- if (!assignment) continue;
34342
- const assignmentStatement = findTransparentExpressionRoot(assignment).parent;
34343
- if (assignment.operator !== "=" || !assignmentStatement || !isNodeOfType(assignmentStatement, "ExpressionStatement") || assignmentStatement.parent !== componentFunction.body) return null;
34344
- const assignedCallbackName = getParentCallbackPropName(analysis, assignment.right);
34345
- if (!assignedCallbackName) return null;
34346
- callbackPropNames.add(assignedCallbackName);
34347
- }
34348
- return callbackPropNames.size > 0 ? { callbackPropNames } : null;
34349
- };
34350
32894
  const isWrapperHookCallbackRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
34351
32895
  const node = def.node;
34352
32896
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
34353
- return getWrapperHookWrappedFunction(unwrapChainExpression$1(node.init)) !== null;
32897
+ return getWrapperHookWrappedFunction(unwrapChainExpression(node.init)) !== null;
34354
32898
  }));
34355
32899
  const isHandlerBagArgument = (analysis, argument) => {
34356
32900
  if (!isNodeOfType(argument, "ObjectExpression")) return false;
@@ -34372,7 +32916,7 @@ const HOOK_NAME_PATTERN$1 = /^use[A-Z0-9]/;
34372
32916
  const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
34373
32917
  const node = def.node;
34374
32918
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
34375
- const init = unwrapChainExpression$1(node.init);
32919
+ const init = unwrapChainExpression(node.init);
34376
32920
  if (!isNodeOfType(init, "CallExpression")) return false;
34377
32921
  const callee = init.callee;
34378
32922
  if (!isNodeOfType(callee, "Identifier") || !HOOK_NAME_PATTERN$1.test(callee.name)) return false;
@@ -34402,79 +32946,71 @@ const noPassDataToParent = defineRule({
34402
32946
  severity: "warn",
34403
32947
  tags: ["test-noise"],
34404
32948
  recommendation: "Fetch the data in the parent and pass it down as a prop (or return it from the hook), instead of handing it back up through a prop callback in a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#passing-data-to-the-parent",
34405
- create: (context) => {
34406
- const isReactUseRefCall = (node) => isReactApiCall(node, "useRef", context.scopes, {
34407
- allowGlobalReactNamespace: true,
34408
- allowUnboundBareCalls: true
34409
- });
34410
- return { CallExpression(node) {
34411
- if (!isUseEffect(node)) return;
34412
- const analysis = getProgramAnalysis(node);
34413
- if (!analysis) return;
34414
- if (hasCleanup(analysis, node)) return;
34415
- const effectFnRefs = getEffectFnRefs(analysis, node);
34416
- if (!effectFnRefs) return;
34417
- const effectFn = getEffectFn(analysis, node);
34418
- if (!effectFn) return;
34419
- for (const ref of effectFnRefs) {
34420
- const callExpr = getCallExpr(ref);
34421
- if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
34422
- const callbackRefProvenance = getCallbackRefProvenance(analysis, node, callExpr, isReactUseRefCall);
34423
- if (isRefCall(analysis, ref) && !callbackRefProvenance) continue;
34424
- if (!isSynchronous(ref.identifier, effectFn)) continue;
34425
- const calleeNode = unwrapChainExpression$1(callExpr.callee);
34426
- const identifier = ref.identifier;
34427
- if (callbackRefProvenance) {
34428
- if ([...callbackRefProvenance.callbackPropNames].some((callbackPropName) => COMMAND_PROP_NAME_PATTERN.test(callbackPropName))) continue;
34429
- } else if (calleeNode === identifier) {
34430
- if (!isDirectParentCallbackRef(analysis, ref)) continue;
34431
- if (isNodeOfType(identifier, "Identifier") && COMMAND_PROP_NAME_PATTERN.test(identifier.name)) continue;
34432
- } else if (isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === identifier) {
34433
- if (!isWholePropsObjectReference(analysis, ref)) continue;
34434
- if (isCustomHookParameter(ref)) continue;
34435
- } else continue;
34436
- const methodName = getCallMethodName(calleeNode);
34437
- const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
34438
- if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
34439
- if (methodName && COMMAND_PROP_NAME_PATTERN.test(methodName)) continue;
34440
- if (!callbackRefProvenance && isNamespacedApiCallee(calleeNode)) continue;
34441
- const isSetterNamedCallee = callbackRefProvenance ? [...callbackRefProvenance.callbackPropNames].every((callbackPropName) => SETTER_NAMED_PROP_PATTERN.test(callbackPropName)) : Boolean((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) && SETTER_NAMED_PROP_PATTERN.test((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) ?? ""));
34442
- const isLeafRef = (argRef) => getUpstreamRefs(analysis, argRef).length === 1;
34443
- const argsUpstreamRefs = (callExpr.arguments ?? []).flatMap((argument) => {
34444
- if (isFunctionLike$1(argument)) {
34445
- if (!isSetterNamedCallee) return [];
34446
- return getFunctionalUpdaterDataRefs(analysis, argument);
34447
- }
34448
- if (isHandlerBagArgument(analysis, argument)) return [];
34449
- if (isParentWiredHookResultArgument(analysis, argument)) return [];
34450
- if (isNodeOfType(argument, "Identifier")) {
34451
- const argumentRef = getRef(analysis, argument);
34452
- if (argumentRef && resolveToFunction(argumentRef)) return [];
34453
- }
34454
- return getDownstreamRefs(analysis, argument);
34455
- }).flatMap((argumentRef) => getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
34456
- if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
34457
- if (!argsUpstreamRefs.some((argRef) => {
34458
- if (isUseStateIdentifier(argRef.identifier)) return false;
34459
- if (isProp(analysis, argRef)) return false;
34460
- if (isUseRefIdentifier(argRef.identifier)) return false;
34461
- if (isRefCurrent(argRef)) return false;
34462
- if (isConstant(argRef)) return false;
34463
- if (isParentWiredHookResultRef(analysis, argRef)) return false;
34464
- if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
34465
- if (resolvesToFunctionBinding(argRef)) return false;
34466
- const argIdentifier = argRef.identifier;
34467
- if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
34468
- if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
34469
- return true;
34470
- })) continue;
34471
- context.report({
34472
- node: callExpr,
34473
- message: "Handing data back to a parent from a useEffect costs your users an extra render."
34474
- });
34475
- }
34476
- } };
34477
- }
32949
+ create: (context) => ({ CallExpression(node) {
32950
+ if (!isUseEffect(node)) return;
32951
+ const analysis = getProgramAnalysis(node);
32952
+ if (!analysis) return;
32953
+ if (hasCleanup(analysis, node)) return;
32954
+ const effectFnRefs = getEffectFnRefs(analysis, node);
32955
+ if (!effectFnRefs) return;
32956
+ const effectFn = getEffectFn(analysis, node);
32957
+ if (!effectFn) return;
32958
+ for (const ref of effectFnRefs) {
32959
+ const callExpr = getCallExpr(ref);
32960
+ if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
32961
+ if (isRefCall(analysis, ref)) continue;
32962
+ if (!isSynchronous(ref.identifier, effectFn)) continue;
32963
+ const calleeNode = unwrapChainExpression(callExpr.callee);
32964
+ const identifier = ref.identifier;
32965
+ if (calleeNode === identifier) {
32966
+ if (!isDirectParentCallbackRef(analysis, ref)) continue;
32967
+ if (isNodeOfType(identifier, "Identifier") && COMMAND_PROP_NAME_PATTERN.test(identifier.name)) continue;
32968
+ } else if (isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === identifier) {
32969
+ if (!isWholePropsObjectReference(analysis, ref)) continue;
32970
+ if (isCustomHookParameter(ref)) continue;
32971
+ } else continue;
32972
+ const methodName = getCallMethodName(calleeNode);
32973
+ const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
32974
+ if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
32975
+ if (methodName && COMMAND_PROP_NAME_PATTERN.test(methodName)) continue;
32976
+ if (isNamespacedApiCallee(calleeNode)) continue;
32977
+ const calleeName = isNodeOfType(identifier, "Identifier") ? identifier.name : methodName;
32978
+ const isSetterNamedCallee = Boolean(calleeName && SETTER_NAMED_PROP_PATTERN.test(calleeName));
32979
+ const isLeafRef = (argRef) => getUpstreamRefs(analysis, argRef).length === 1;
32980
+ const argsUpstreamRefs = (callExpr.arguments ?? []).flatMap((argument) => {
32981
+ if (isFunctionLike$1(argument)) {
32982
+ if (!isSetterNamedCallee) return [];
32983
+ return getFunctionalUpdaterDataRefs(analysis, argument);
32984
+ }
32985
+ if (isHandlerBagArgument(analysis, argument)) return [];
32986
+ if (isParentWiredHookResultArgument(analysis, argument)) return [];
32987
+ if (isNodeOfType(argument, "Identifier")) {
32988
+ const argumentRef = getRef(analysis, argument);
32989
+ if (argumentRef && resolveToFunction(argumentRef)) return [];
32990
+ }
32991
+ return getDownstreamRefs(analysis, argument);
32992
+ }).flatMap((argumentRef) => getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
32993
+ if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
32994
+ if (!argsUpstreamRefs.some((argRef) => {
32995
+ if (isUseStateIdentifier(argRef.identifier)) return false;
32996
+ if (isProp(analysis, argRef)) return false;
32997
+ if (isUseRefIdentifier(argRef.identifier)) return false;
32998
+ if (isRefCurrent(argRef)) return false;
32999
+ if (isConstant(argRef)) return false;
33000
+ if (isParentWiredHookResultRef(analysis, argRef)) return false;
33001
+ if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
33002
+ if (resolvesToFunctionBinding(argRef)) return false;
33003
+ const argIdentifier = argRef.identifier;
33004
+ if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
33005
+ if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
33006
+ return true;
33007
+ })) continue;
33008
+ context.report({
33009
+ node: callExpr,
33010
+ message: "Handing data back to a parent from a useEffect costs your users an extra render."
33011
+ });
33012
+ }
33013
+ } })
34478
33014
  });
34479
33015
  //#endregion
34480
33016
  //#region src/plugin/utils/is-call-result-consumed-as-argument.ts
@@ -35028,66 +33564,6 @@ const noPropCallbackInEffect = defineRule({
35028
33564
  }
35029
33565
  });
35030
33566
  //#endregion
35031
- //#region src/plugin/rules/state-and-effects/no-prop-callback-in-render.ts
35032
- const isPreservedThroughConciseArrow = (callExpression, scopes) => {
35033
- let node = callExpression;
35034
- let parent = node.parent;
35035
- while (parent) {
35036
- if (isNodeOfType(parent, "ChainExpression")) {
35037
- node = parent;
35038
- parent = node.parent;
35039
- continue;
35040
- }
35041
- if (isNodeOfType(parent, "LogicalExpression") && parent.right === node) {
35042
- node = parent;
35043
- parent = node.parent;
35044
- continue;
35045
- }
35046
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === node || parent.alternate === node)) {
35047
- node = parent;
35048
- parent = node.parent;
35049
- continue;
35050
- }
35051
- if (isNodeOfType(parent, "SequenceExpression")) {
35052
- const expressions = parent.expressions ?? [];
35053
- if (expressions[expressions.length - 1] !== node) return false;
35054
- node = parent;
35055
- parent = node.parent;
35056
- continue;
35057
- }
35058
- if (!isNodeOfType(parent, "ArrowFunctionExpression") || parent.body !== node) return !isResultDiscardedCall(node);
35059
- const invocation = parent.parent;
35060
- if (!isNodeOfType(invocation, "CallExpression") || !executesDuringRender(parent, scopes)) return true;
35061
- if (invocation.arguments?.[0] === parent || invocation.arguments?.[1] === parent) {
35062
- const callee = stripParenExpression(invocation.callee);
35063
- return !(isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "forEach" && invocation.arguments[0] === parent);
35064
- }
35065
- node = invocation;
35066
- parent = node.parent;
35067
- }
35068
- return false;
35069
- };
35070
- const noPropCallbackInRender = defineRule({
35071
- id: "no-prop-callback-in-render",
35072
- title: "Prop callback invoked during render",
35073
- severity: "error",
35074
- recommendation: "Invoke the callback from the event or asynchronous operation that produced the value, or from an effect when synchronizing with an external system. Render must stay pure because React can replay or discard it.",
35075
- create: (context) => ({ CallExpression(node) {
35076
- if (!isResultDiscardedCall(node)) return;
35077
- if (isPreservedThroughConciseArrow(node, context.scopes)) return;
35078
- if (!findRenderPhaseComponentOrHook(node, context.scopes)) return;
35079
- const analysis = getProgramAnalysis(node);
35080
- if (!analysis) return;
35081
- const callee = stripParenExpression(node.callee);
35082
- if (isFunctionLike$1(callee)) return;
35083
- if (!getDownstreamRefs(analysis, callee).some((reference) => isPropCallbackInvocationRef(analysis, reference))) return;
35084
- context.report({
35085
- node,
35086
- message: "This prop callback runs during render. React can replay or discard render work, so the callback can fire more than once or for UI that never commits."
35087
- });
35088
- } })
35089
- });
35090
- //#endregion
35091
33567
  //#region src/plugin/rules/architecture/no-prop-types.ts
35092
33568
  const PROP_TYPES_PROPERTY = "propTypes";
35093
33569
  const isPropTypesKey = (key, computed) => {
@@ -35782,63 +34258,6 @@ const noRedundantShouldComponentUpdate = defineRule({
35782
34258
  }
35783
34259
  });
35784
34260
  //#endregion
35785
- //#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
35786
- const resolveReactRefSymbol = (memberExpression, scopes) => {
35787
- const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
35788
- if (!isNodeOfType(memberExpression, "MemberExpression") || memberExpression.computed || !isNodeOfType(memberExpression.property, "Identifier") || memberExpression.property.name !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
35789
- const symbol = resolveConstIdentifierAlias(receiver, scopes);
35790
- if (!symbol?.initializer) return null;
35791
- const initializer = stripParenExpression(symbol.initializer);
35792
- if (!isNodeOfType(initializer, "CallExpression")) return null;
35793
- return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
35794
- };
35795
- const isSameRefCurrentMember = (node, refSymbol, scopes) => {
35796
- if (!isNodeOfType(node, "MemberExpression") || node.computed || !isNodeOfType(node.property, "Identifier") || node.property.name !== "current") return false;
35797
- const receiver = stripParenExpression(node.object);
35798
- return isNodeOfType(receiver, "Identifier") && resolveConstIdentifierAlias(receiver, scopes)?.id === refSymbol.id;
35799
- };
35800
- const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
35801
- if (assignmentExpression.operator !== "=" || !isNodeOfType(assignmentExpression.right, "NewExpression")) return false;
35802
- let descendant = assignmentExpression;
35803
- let ancestor = descendant.parent;
35804
- while (ancestor) {
35805
- if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant && isNodeOfType(ancestor.test, "BinaryExpression") && (ancestor.test.operator === "===" || ancestor.test.operator === "==")) {
35806
- const { left, right } = ancestor.test;
35807
- if (isSameRefCurrentMember(left, refSymbol, scopes) && isNodeOfType(right, "Literal") && right.value === null || isSameRefCurrentMember(right, refSymbol, scopes) && isNodeOfType(left, "Literal") && left.value === null) return true;
35808
- }
35809
- descendant = ancestor;
35810
- ancestor = descendant.parent;
35811
- }
35812
- return false;
35813
- };
35814
- const noRefCurrentInRender = defineRule({
35815
- id: "no-ref-current-in-render",
35816
- title: "Ref mutated during render",
35817
- severity: "error",
35818
- recommendation: "Move ref writes into an event handler or effect. Render must stay pure because React can replay or discard it. The predictable null-guarded lazy initialization pattern remains supported.",
35819
- create: (context) => {
35820
- const report = (memberExpression) => {
35821
- if (!resolveReactRefSymbol(memberExpression, context.scopes)) return;
35822
- if (!findRenderPhaseComponentOrHook(memberExpression, context.scopes)) return;
35823
- context.report({
35824
- node: memberExpression,
35825
- message: "This ref is mutated during render. React can replay or discard render work, so the mutation can leak from UI that never commits."
35826
- });
35827
- };
35828
- return {
35829
- AssignmentExpression(node) {
35830
- const refSymbol = resolveReactRefSymbol(node.left, context.scopes);
35831
- if (!refSymbol) return;
35832
- if (isDocumentedLazyInitialization(node, refSymbol, context.scopes)) return;
35833
- report(node.left);
35834
- },
35835
- UpdateExpression(node) {
35836
- report(node.argument);
35837
- }
35838
- };
35839
- }
35840
- });
35841
- //#endregion
35842
34261
  //#region src/plugin/rules/architecture/no-render-in-render.ts
35843
34262
  const isInsideComponentContext = (node) => {
35844
34263
  let cursor = node.parent;
@@ -37853,140 +36272,6 @@ const noUnescapedEntities = defineRule({
37853
36272
  } })
37854
36273
  });
37855
36274
  //#endregion
37856
- //#region src/plugin/rules/performance/no-unguarded-browser-global-in-render-or-hook-init.ts
37857
- const BROWSER_GLOBAL_NAMES = new Set([
37858
- "window",
37859
- "document",
37860
- "localStorage",
37861
- "sessionStorage",
37862
- "navigator",
37863
- "matchMedia"
37864
- ]);
37865
- const getTypeofBrowserGlobalName = (expression, context) => {
37866
- const unwrappedExpression = stripParenExpression(expression);
37867
- if (!isNodeOfType(unwrappedExpression, "UnaryExpression") || unwrappedExpression.operator !== "typeof") return null;
37868
- const argument = stripParenExpression(unwrappedExpression.argument);
37869
- if (isNodeOfType(argument, "Identifier")) return BROWSER_GLOBAL_NAMES.has(argument.name) && context.scopes.isGlobalReference(argument) ? argument.name : null;
37870
- if (!isNodeOfType(argument, "MemberExpression") || argument.computed || !isNodeOfType(argument.object, "Identifier") || argument.object.name !== "globalThis" || !context.scopes.isGlobalReference(argument.object) || !isNodeOfType(argument.property, "Identifier") || !BROWSER_GLOBAL_NAMES.has(argument.property.name)) return null;
37871
- return argument.property.name;
37872
- };
37873
- const browserGuardCoversGlobal = (guardName, browserGlobalName) => guardName === browserGlobalName || guardName === "window" || guardName === "document";
37874
- const mergeAvailability = (leftAvailability, rightAvailability) => {
37875
- if (leftAvailability === null) return rightAvailability;
37876
- if (rightAvailability === null) return leftAvailability;
37877
- return leftAvailability === rightAvailability ? leftAvailability : null;
37878
- };
37879
- const readAvailabilityWhenPredicate = (expression, browserGlobalName, context, predicateResult) => {
37880
- const unwrappedExpression = stripParenExpression(expression);
37881
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return readAvailabilityWhenPredicate(unwrappedExpression.argument, browserGlobalName, context, !predicateResult);
37882
- if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
37883
- if (unwrappedExpression.operator === "&&" && predicateResult) return mergeAvailability(readAvailabilityWhenPredicate(unwrappedExpression.left, browserGlobalName, context, true), readAvailabilityWhenPredicate(unwrappedExpression.right, browserGlobalName, context, true));
37884
- if (unwrappedExpression.operator === "||" && !predicateResult) return mergeAvailability(readAvailabilityWhenPredicate(unwrappedExpression.left, browserGlobalName, context, false), readAvailabilityWhenPredicate(unwrappedExpression.right, browserGlobalName, context, false));
37885
- return null;
37886
- }
37887
- if (!isNodeOfType(unwrappedExpression, "BinaryExpression")) return null;
37888
- const leftTypeofName = getTypeofBrowserGlobalName(unwrappedExpression.left, context);
37889
- const rightTypeofName = getTypeofBrowserGlobalName(unwrappedExpression.right, context);
37890
- const leftComparedType = isNodeOfType(unwrappedExpression.left, "Literal") && typeof unwrappedExpression.left.value === "string" ? unwrappedExpression.left.value : null;
37891
- const rightComparedType = isNodeOfType(unwrappedExpression.right, "Literal") && typeof unwrappedExpression.right.value === "string" ? unwrappedExpression.right.value : null;
37892
- const guardName = leftTypeofName && rightComparedType ? leftTypeofName : rightTypeofName && leftComparedType ? rightTypeofName : null;
37893
- const comparedType = leftTypeofName && rightComparedType ? rightComparedType : rightTypeofName && leftComparedType ? leftComparedType : null;
37894
- if (!guardName || !browserGuardCoversGlobal(guardName, browserGlobalName)) return null;
37895
- if (!comparedType) return null;
37896
- const isEquality = unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==";
37897
- const isInequality = unwrappedExpression.operator === "!==" || unwrappedExpression.operator === "!=";
37898
- if (!isEquality && !isInequality) return null;
37899
- const browserType = guardName === "matchMedia" ? "function" : "object";
37900
- const browserResult = isEquality ? browserType === comparedType : browserType !== comparedType;
37901
- if (browserResult === (isEquality ? comparedType === "undefined" : comparedType !== "undefined")) return null;
37902
- return predicateResult === browserResult;
37903
- };
37904
- const isInsideAvailabilityGuard = (node, browserGlobalName, context) => {
37905
- let currentNode = node;
37906
- let parentNode = currentNode.parent;
37907
- while (parentNode) {
37908
- if (isFunctionLike$1(parentNode) && !executesDuringRender(parentNode, context.scopes)) break;
37909
- if (isNodeOfType(parentNode, "LogicalExpression") && (parentNode.operator === "&&" || parentNode.operator === "||") && parentNode.right === currentNode && readAvailabilityWhenPredicate(parentNode.left, browserGlobalName, context, parentNode.operator === "&&") === true) return true;
37910
- if (isNodeOfType(parentNode, "ConditionalExpression")) {
37911
- if (parentNode.consequent === currentNode && readAvailabilityWhenPredicate(parentNode.test, browserGlobalName, context, true) === true || parentNode.alternate === currentNode && readAvailabilityWhenPredicate(parentNode.test, browserGlobalName, context, false) === true) return true;
37912
- }
37913
- if (isNodeOfType(parentNode, "IfStatement")) {
37914
- if (parentNode.consequent === currentNode && readAvailabilityWhenPredicate(parentNode.test, browserGlobalName, context, true) === true || parentNode.alternate === currentNode && readAvailabilityWhenPredicate(parentNode.test, browserGlobalName, context, false) === true) return true;
37915
- }
37916
- currentNode = parentNode;
37917
- parentNode = currentNode.parent;
37918
- }
37919
- return false;
37920
- };
37921
- const isAfterAvailabilityEarlyExit = (node, componentOrHookNode, browserGlobalName, context) => {
37922
- const enclosingFunction = findEnclosingFunction(node);
37923
- if (!enclosingFunction || enclosingFunction !== componentOrHookNode && !executesDuringRender(enclosingFunction, context.scopes) || !isFunctionLike$1(enclosingFunction) || !isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
37924
- let currentNode = node;
37925
- while (currentNode !== enclosingFunction) {
37926
- const parentNode = currentNode.parent;
37927
- if (!parentNode) return false;
37928
- if (isNodeOfType(parentNode, "BlockStatement")) for (const statement of parentNode.body) {
37929
- if (statement === currentNode) break;
37930
- if (!isNodeOfType(statement, "IfStatement")) continue;
37931
- if (readAvailabilityWhenPredicate(statement.test, browserGlobalName, context, false) === true && statementAlwaysExits(statement.consequent)) return true;
37932
- if (readAvailabilityWhenPredicate(statement.test, browserGlobalName, context, true) === true && statement.alternate && statementAlwaysExits(statement.alternate)) return true;
37933
- }
37934
- currentNode = parentNode;
37935
- }
37936
- return false;
37937
- };
37938
- const isTypeofProbe = (node) => {
37939
- const expressionRoot = findTransparentExpressionRoot(node);
37940
- const parentNode = expressionRoot.parent;
37941
- return isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "typeof" && parentNode.argument === expressionRoot;
37942
- };
37943
- const noUnguardedBrowserGlobalInRenderOrHookInit = defineRule({
37944
- id: "no-unguarded-browser-global-in-render-or-hook-init",
37945
- title: "Browser global read during server render",
37946
- severity: "error",
37947
- category: "Correctness",
37948
- requires: ["ssr"],
37949
- recommendation: "Move browser-only reads into an effect or event, guard them behind a client-only render path, or use useSyncExternalStore with a stable server snapshot.",
37950
- create: (context) => {
37951
- if (isTestlikeFilename(context.filename)) return {};
37952
- if (classifyReactNativeFileTarget(context) === "react-native") return {};
37953
- let fileIsEmailTemplate = false;
37954
- const reportedNodes = /* @__PURE__ */ new Set();
37955
- const reportBrowserRead = (node, browserGlobalName) => {
37956
- if (reportedNodes.has(node) || isTypeofProbe(node)) return;
37957
- const componentOrHookNode = findRenderPhaseComponentOrHook(node, context.scopes);
37958
- if (!componentOrHookNode) return;
37959
- if (fileIsEmailTemplate) return;
37960
- if (isGeneratedImageRenderContext(context, findEnclosingJsxOpeningElement(node) ?? node)) return;
37961
- if (isGatedByFalsyInitialState(node, context.scopes)) return;
37962
- if (isAfterClientOnlyEarlyReturn(node, componentOrHookNode, context.scopes)) return;
37963
- if (isInsideAvailabilityGuard(node, browserGlobalName, context)) return;
37964
- if (isAfterAvailabilityEarlyExit(node, componentOrHookNode, browserGlobalName, context)) return;
37965
- reportedNodes.add(node);
37966
- context.report({
37967
- node,
37968
- message: `\`${browserGlobalName}\` is read while React is rendering on the server, where browser globals are unavailable. Move the read into an effect or event, or provide a stable server snapshot.`
37969
- });
37970
- };
37971
- return {
37972
- Program(node) {
37973
- fileIsEmailTemplate = hasEmailTemplateImport(node);
37974
- },
37975
- Identifier(node) {
37976
- if (!BROWSER_GLOBAL_NAMES.has(node.name)) return;
37977
- if (!context.scopes.isGlobalReference(node)) return;
37978
- reportBrowserRead(node, node.name);
37979
- },
37980
- MemberExpression(node) {
37981
- if (node.computed) return;
37982
- const objectNode = stripParenExpression(node.object);
37983
- if (!isNodeOfType(objectNode, "Identifier") || objectNode.name !== "globalThis" || !context.scopes.isGlobalReference(objectNode) || !isNodeOfType(node.property, "Identifier") || !BROWSER_GLOBAL_NAMES.has(node.property.name)) return;
37984
- reportBrowserRead(node, node.property.name);
37985
- }
37986
- };
37987
- }
37988
- });
37989
- //#endregion
37990
36275
  //#region src/plugin/constants/dom-aria-properties.ts
37991
36276
  const ARIA_PROPERTY_NAMES = new Set([
37992
36277
  "activedescendant",
@@ -41829,123 +40114,6 @@ const preferUseEffectEvent = defineRule({
41829
40114
  }
41830
40115
  });
41831
40116
  //#endregion
41832
- //#region src/plugin/rules/state-and-effects/utils/is-cleanup-return.ts
41833
- const ITERATOR_CALLBACK_METHOD_NAMES = new Set([
41834
- "each",
41835
- "every",
41836
- "filter",
41837
- "find",
41838
- "findIndex",
41839
- "findLast",
41840
- "findLastIndex",
41841
- "flatMap",
41842
- "forEach",
41843
- "map",
41844
- "reduce",
41845
- "reduceRight",
41846
- "some",
41847
- "sort",
41848
- "toSorted"
41849
- ]);
41850
- const STATIC_ITERATOR_CALLBACK_METHOD_NAMES = new Set([
41851
- "from",
41852
- "fromAsync",
41853
- "groupBy"
41854
- ]);
41855
- const unwrapChainExpression = (node) => isNodeOfType(node, "ChainExpression") ? node.expression : node;
41856
- const isNullLiteral = (node) => isNodeOfType(node, "Literal") && node.value === null;
41857
- const isListenerRemovalViaNullHandler = (callNode) => {
41858
- if (!isNodeOfType(callNode, "CallExpression")) return false;
41859
- const callee = unwrapChainExpression(callNode.callee);
41860
- return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "on" && isNullLiteral(callNode.arguments?.[1]);
41861
- };
41862
- const REFERENCE_BASED_REMOVAL_METHOD_NAMES = new Set([
41863
- "off",
41864
- "removeEventListener",
41865
- "removeListener"
41866
- ]);
41867
- const isNoOpInlineHandlerRemoval = (callNode, methodName) => {
41868
- if (!REFERENCE_BASED_REMOVAL_METHOD_NAMES.has(methodName)) return false;
41869
- const handlerArgument = callNode.arguments?.[1];
41870
- return isNodeOfType(handlerArgument, "ArrowFunctionExpression") || isNodeOfType(handlerArgument, "FunctionExpression");
41871
- };
41872
- const isReleaseLikeCall = (node, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
41873
- const callNode = unwrapChainExpression(node);
41874
- if (!isNodeOfType(callNode, "CallExpression")) return false;
41875
- if (isListenerRemovalViaNullHandler(callNode)) return true;
41876
- const callee = unwrapChainExpression(callNode.callee);
41877
- if (isNodeOfType(callee, "Identifier")) {
41878
- if (TIMER_CLEANUP_CALLEE_NAMES.has(callee.name)) return true;
41879
- if (CLEANUP_LIKE_RELEASE_CALLEE_NAMES.has(callee.name)) return true;
41880
- if (knownCleanupFunctionNames.has(callee.name)) return true;
41881
- return false;
41882
- }
41883
- if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) {
41884
- if (isNoOpInlineHandlerRemoval(callNode, callee.property.name)) return false;
41885
- if (BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(callee.property.name) && isNodeOfType(callee.object, "Identifier") && knownBoundSubscriptionNames.has(callee.object.name)) return true;
41886
- return GLOBAL_RELEASE_METHOD_NAMES.has(callee.property.name);
41887
- }
41888
- return false;
41889
- };
41890
- const isStaticIteratorCallbackCallee = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && isNodeOfType(callee.property, "Identifier") && (callee.object.name === "Array" || callee.object.name === "Map" || callee.object.name === "Object") && STATIC_ITERATOR_CALLBACK_METHOD_NAMES.has(callee.property.name);
41891
- const isIteratorCallbackArgument = (node) => {
41892
- const parentNode = node.parent;
41893
- if (!isNodeOfType(parentNode, "CallExpression")) return false;
41894
- if (!parentNode.arguments?.some((argument) => argument === node)) return false;
41895
- const callee = unwrapChainExpression(parentNode.callee);
41896
- if (parentNode.arguments[1] === node && isStaticIteratorCallbackCallee(callee)) return true;
41897
- return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && ITERATOR_CALLBACK_METHOD_NAMES.has(callee.property.name);
41898
- };
41899
- const containsReleaseLikeCall = (node, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
41900
- let didFindRelease = false;
41901
- walkAst(node, (child) => {
41902
- if (didFindRelease) return false;
41903
- if (child !== node && isFunctionLike$1(child) && !isIteratorCallbackArgument(child)) return false;
41904
- if (isReleaseLikeCall(child, knownCleanupFunctionNames, knownBoundSubscriptionNames)) {
41905
- didFindRelease = true;
41906
- return false;
41907
- }
41908
- });
41909
- return didFindRelease;
41910
- };
41911
- const isCleanupFunctionLike = (node, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
41912
- if (!isFunctionLike$1(node)) return false;
41913
- return containsReleaseLikeCall(node.body, knownCleanupFunctionNames, knownBoundSubscriptionNames);
41914
- };
41915
- const isProvablyNoOpCleanupFunction = (node) => {
41916
- if (!isFunctionLike$1(node)) return false;
41917
- let sawNoOpRemoval = false;
41918
- let sawOtherCall = false;
41919
- walkAst(node.body, (child) => {
41920
- if (sawOtherCall) return false;
41921
- if (isFunctionLike$1(child)) return false;
41922
- const callNode = unwrapChainExpression(child);
41923
- if (!isNodeOfType(callNode, "CallExpression")) return;
41924
- const callee = unwrapChainExpression(callNode.callee);
41925
- if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && isNoOpInlineHandlerRemoval(callNode, callee.property.name)) {
41926
- sawNoOpRemoval = true;
41927
- return;
41928
- }
41929
- sawOtherCall = true;
41930
- });
41931
- return sawNoOpRemoval && !sawOtherCall;
41932
- };
41933
- const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSubscriptionNames, options = {}) => {
41934
- if (!returnedValue) return false;
41935
- const unwrappedValue = unwrapChainExpression(returnedValue);
41936
- if (isNodeOfType(unwrappedValue, "Literal") && unwrappedValue.value === null) return false;
41937
- if (isNodeOfType(unwrappedValue, "Identifier")) {
41938
- if (unwrappedValue.name === "undefined") return false;
41939
- if (knownCleanupFunctionNames.has(unwrappedValue.name)) return true;
41940
- return options.allowOpaqueReturn === true && !knownBoundSubscriptionNames.has(unwrappedValue.name);
41941
- }
41942
- if (isCleanupReturningSubscribeLikeCallExpression(unwrappedValue)) return true;
41943
- if (isProvablyNoOpCleanupFunction(unwrappedValue)) return false;
41944
- if (options.allowOpaqueReturn === true && !isSubscribeLikeCallExpression(unwrappedValue)) return true;
41945
- if (isCleanupFunctionLike(unwrappedValue, knownCleanupFunctionNames, knownBoundSubscriptionNames)) return true;
41946
- return false;
41947
- };
41948
- //#endregion
41949
40117
  //#region src/plugin/rules/state-and-effects/prefer-use-sync-external-store.ts
41950
40118
  const findUseEffectsInComponent = (componentBody) => {
41951
40119
  const effectCalls = [];
@@ -43414,7 +41582,7 @@ const renderingHydrationMismatchTime = defineRule({
43414
41582
  title: "Time or random value in JSX",
43415
41583
  severity: "warn",
43416
41584
  category: "Correctness",
43417
- requires: ["ssr"],
41585
+ disabledWhen: ["vite", "cra"],
43418
41586
  recommendation: "Move time or random values into useEffect+useState so they only run in the browser, or add suppressHydrationWarning to the parent if it's intentional",
43419
41587
  create: (context) => {
43420
41588
  const isTestlikeFile = isTestlikeFilename(context.filename);
@@ -43428,7 +41596,8 @@ const renderingHydrationMismatchTime = defineRule({
43428
41596
  const matched = NONDETERMINISTIC_RENDER_PATTERNS.find((pattern) => pattern.matches(node.expression));
43429
41597
  if (matched) {
43430
41598
  if (hasSuppressHydrationWarningAttribute(findOpeningElementOfChild(node))) return;
43431
- if (isGatedByFalsyInitialState(node, context.scopes)) return;
41599
+ if (isInsideClientOnlyGuard(node)) return;
41600
+ if (isGatedByFalsyInitialState(node)) return;
43432
41601
  if (isInsideMotionTransitionAttribute(node)) return;
43433
41602
  context.report({
43434
41603
  node,
@@ -43437,10 +41606,11 @@ const renderingHydrationMismatchTime = defineRule({
43437
41606
  return;
43438
41607
  }
43439
41608
  walkAst(node.expression, (child) => {
43440
- if (isFunctionLike$1(child) && !executesDuringRender(child, context.scopes)) return false;
41609
+ if (isFunctionLike$1(child) && !executesDuringRender(child)) return false;
43441
41610
  for (const pattern of NONDETERMINISTIC_RENDER_PATTERNS) if (pattern.matches(child)) {
43442
41611
  if (hasSuppressHydrationWarningAttribute(findOpeningElementOfChild(node))) return;
43443
- if (isGatedByFalsyInitialState(child, context.scopes)) return;
41612
+ if (isInsideClientOnlyGuard(child)) return;
41613
+ if (isGatedByFalsyInitialState(child)) return;
43444
41614
  if (isInsideMotionTransitionAttribute(child)) return;
43445
41615
  if (pattern.display === "new Date()" && isYearOnlyDateRead(child)) return;
43446
41616
  context.report({
@@ -56777,18 +54947,6 @@ const reactDoctorRules = [
56777
54947
  category: "Accessibility"
56778
54948
  }
56779
54949
  },
56780
- {
56781
- key: "react-doctor/no-hydration-branch-on-browser-global",
56782
- id: "no-hydration-branch-on-browser-global",
56783
- source: "react-doctor",
56784
- originallyExternal: false,
56785
- rule: {
56786
- ...noHydrationBranchOnBrowserGlobal,
56787
- framework: "global",
56788
- category: "Bugs",
56789
- requires: [...new Set(["react", ...noHydrationBranchOnBrowserGlobal.requires ?? []])]
56790
- }
56791
- },
56792
54950
  {
56793
54951
  key: "react-doctor/no-img-lazy-with-high-fetchpriority",
56794
54952
  id: "no-img-lazy-with-high-fetchpriority",
@@ -56801,18 +54959,6 @@ const reactDoctorRules = [
56801
54959
  requires: [...new Set(["react", ...noImgLazyWithHighFetchpriority.requires ?? []])]
56802
54960
  }
56803
54961
  },
56804
- {
56805
- key: "react-doctor/no-impure-state-updater",
56806
- id: "no-impure-state-updater",
56807
- source: "react-doctor",
56808
- originallyExternal: false,
56809
- rule: {
56810
- ...noImpureStateUpdater,
56811
- framework: "global",
56812
- category: "Bugs",
56813
- requires: [...new Set(["react", ...noImpureStateUpdater.requires ?? []])]
56814
- }
56815
- },
56816
54962
  {
56817
54963
  key: "react-doctor/no-indeterminate-attribute",
56818
54964
  id: "no-indeterminate-attribute",
@@ -57229,18 +55375,6 @@ const reactDoctorRules = [
57229
55375
  requires: [...new Set(["react", ...noPropCallbackInEffect.requires ?? []])]
57230
55376
  }
57231
55377
  },
57232
- {
57233
- key: "react-doctor/no-prop-callback-in-render",
57234
- id: "no-prop-callback-in-render",
57235
- source: "react-doctor",
57236
- originallyExternal: false,
57237
- rule: {
57238
- ...noPropCallbackInRender,
57239
- framework: "global",
57240
- category: "Bugs",
57241
- requires: [...new Set(["react", ...noPropCallbackInRender.requires ?? []])]
57242
- }
57243
- },
57244
55378
  {
57245
55379
  key: "react-doctor/no-prop-types",
57246
55380
  id: "no-prop-types",
@@ -57332,18 +55466,6 @@ const reactDoctorRules = [
57332
55466
  requires: [...new Set(["react", ...noRedundantShouldComponentUpdate.requires ?? []])]
57333
55467
  }
57334
55468
  },
57335
- {
57336
- key: "react-doctor/no-ref-current-in-render",
57337
- id: "no-ref-current-in-render",
57338
- source: "react-doctor",
57339
- originallyExternal: false,
57340
- rule: {
57341
- ...noRefCurrentInRender,
57342
- framework: "global",
57343
- category: "Bugs",
57344
- requires: [...new Set(["react", ...noRefCurrentInRender.requires ?? []])]
57345
- }
57346
- },
57347
55469
  {
57348
55470
  key: "react-doctor/no-render-in-render",
57349
55471
  id: "no-render-in-render",
@@ -57588,18 +55710,6 @@ const reactDoctorRules = [
57588
55710
  requires: [...new Set(["react", ...noUnescapedEntities.requires ?? []])]
57589
55711
  }
57590
55712
  },
57591
- {
57592
- key: "react-doctor/no-unguarded-browser-global-in-render-or-hook-init",
57593
- id: "no-unguarded-browser-global-in-render-or-hook-init",
57594
- source: "react-doctor",
57595
- originallyExternal: false,
57596
- rule: {
57597
- ...noUnguardedBrowserGlobalInRenderOrHookInit,
57598
- framework: "global",
57599
- category: "Bugs",
57600
- requires: [...new Set(["react", ...noUnguardedBrowserGlobalInRenderOrHookInit.requires ?? []])]
57601
- }
57602
- },
57603
55713
  {
57604
55714
  key: "react-doctor/no-unknown-property",
57605
55715
  id: "no-unknown-property",
@@ -59932,17 +58042,10 @@ const CROSS_FILE_RULE_IDS = new Set([
59932
58042
  "nextjs-no-use-search-params-without-suspense",
59933
58043
  "no-dynamic-import-path",
59934
58044
  "no-full-lodash-import",
59935
- "no-hydration-branch-on-browser-global",
59936
58045
  "no-indeterminate-attribute",
59937
58046
  "no-locale-format-in-render",
59938
58047
  "no-match-media-in-state-initializer",
59939
- "no-adjust-state-on-prop-change",
59940
- "no-derived-state",
59941
- "no-derived-state-effect",
59942
- "no-event-handler",
59943
- "no-initialize-state",
59944
58048
  "no-mutating-reducer-state",
59945
- "no-unguarded-browser-global-in-render-or-hook-init",
59946
58049
  "prefer-dynamic-import",
59947
58050
  "rendering-hydration-mismatch-time",
59948
58051
  "rn-no-legacy-shadow-styles",
@@ -59994,9 +58097,6 @@ const collectNextjsSearchParamsDependencies = ({ absoluteFilePath, staticImports
59994
58097
  if (hasAncestorSuspenseLayout(absoluteFilePath)) return;
59995
58098
  for (const entry of flattenImportEntries(staticImports)) resolveCrossFileFunctionExport(absoluteFilePath, entry.source, entry.exportedName);
59996
58099
  };
59997
- const collectEffectValueHelperDependencies = ({ absoluteFilePath, staticImports }) => {
59998
- for (const entry of flattenImportEntries(staticImports)) resolveCrossFileFunctionExport(absoluteFilePath, entry.source, entry.exportedName);
59999
- };
60000
58100
  const collectMutatingReducerDependencies = ({ absoluteFilePath, sourceText, staticImports, program }) => {
60001
58101
  const namedUseReducerLocals = /* @__PURE__ */ new Set();
60002
58102
  const reactObjectLocals = /* @__PURE__ */ new Set();
@@ -60079,17 +58179,10 @@ const CROSS_FILE_DEPENDENCY_COLLECTORS = new Map([
60079
58179
  ["nextjs-no-use-search-params-without-suspense", collectNextjsSearchParamsDependencies],
60080
58180
  ["no-dynamic-import-path", collectNearestManifestDependencies],
60081
58181
  ["no-full-lodash-import", collectNearestManifestDependencies],
60082
- ["no-hydration-branch-on-browser-global", collectNearestManifestDependencies],
60083
58182
  ["no-indeterminate-attribute", collectNearestManifestDependencies],
60084
58183
  ["no-locale-format-in-render", collectNearestManifestDependencies],
60085
58184
  ["no-match-media-in-state-initializer", collectNearestManifestDependencies],
60086
- ["no-adjust-state-on-prop-change", collectEffectValueHelperDependencies],
60087
- ["no-derived-state", collectEffectValueHelperDependencies],
60088
- ["no-derived-state-effect", collectEffectValueHelperDependencies],
60089
- ["no-event-handler", collectEffectValueHelperDependencies],
60090
- ["no-initialize-state", collectEffectValueHelperDependencies],
60091
58185
  ["no-mutating-reducer-state", collectMutatingReducerDependencies],
60092
- ["no-unguarded-browser-global-in-render-or-hook-init", collectNearestManifestDependencies],
60093
58186
  ["prefer-dynamic-import", collectNearestManifestDependencies],
60094
58187
  ["rendering-hydration-mismatch-time", collectNearestManifestDependencies],
60095
58188
  ["rn-no-legacy-shadow-styles", collectLegacyArchDependencies],