oxlint-plugin-react-doctor 0.7.4-dev.a31f5e8 → 0.7.4-dev.b686594

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 -93
  2. package/dist/index.js +706 -1944
  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
@@ -7675,21 +7672,6 @@ const readStaticBoolean = (node) => {
7675
7672
  return unwrappedNode.value;
7676
7673
  };
7677
7674
  //#endregion
7678
- //#region src/plugin/utils/resolve-const-identifier-alias.ts
7679
- const resolveConstIdentifierAlias = (identifier, scopes) => {
7680
- if (!isNodeOfType(identifier, "Identifier")) return null;
7681
- const visitedSymbolIds = /* @__PURE__ */ new Set();
7682
- let symbol = scopes.symbolFor(identifier);
7683
- while (symbol?.kind === "const") {
7684
- if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
7685
- visitedSymbolIds.add(symbol.id);
7686
- const initializer = stripParenExpression(symbol.initializer);
7687
- if (!isNodeOfType(initializer, "Identifier")) return symbol;
7688
- symbol = scopes.symbolFor(initializer);
7689
- }
7690
- return symbol;
7691
- };
7692
- //#endregion
7693
7675
  //#region src/plugin/utils/is-react-api-call.ts
7694
7676
  const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? apiNames === apiName : apiNames.has(apiName);
7695
7677
  const isImportedFromReact = (symbol) => {
@@ -7705,7 +7687,8 @@ const isNamedReactApiImport = (identifier, apiNames, scopes) => {
7705
7687
  return Boolean(importedName && includesApiName(apiNames, importedName));
7706
7688
  };
7707
7689
  const isReactNamespaceImport = (identifier, scopes) => {
7708
- const symbol = resolveConstIdentifierAlias(identifier, scopes);
7690
+ if (!isNodeOfType(identifier, "Identifier")) return false;
7691
+ const symbol = scopes.symbolFor(identifier);
7709
7692
  if (!symbol || !isImportedFromReact(symbol)) return false;
7710
7693
  return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier");
7711
7694
  };
@@ -7716,11 +7699,9 @@ const isReactApiCall = (node, apiNames, scopes, options = {}) => {
7716
7699
  if (isNamedReactApiImport(callee, apiNames, scopes)) return true;
7717
7700
  return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
7718
7701
  }
7719
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || !includesApiName(apiNames, callee.property.name)) return false;
7720
- const receiver = stripParenExpression(callee.object);
7721
- if (!isNodeOfType(receiver, "Identifier")) return false;
7722
- if (isReactNamespaceImport(receiver, scopes)) return true;
7723
- return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
7702
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || !includesApiName(apiNames, callee.property.name)) return false;
7703
+ if (isReactNamespaceImport(callee.object, scopes)) return true;
7704
+ return Boolean(options.allowGlobalReactNamespace && callee.object.name === "React" && scopes.isGlobalReference(callee.object));
7724
7705
  };
7725
7706
  //#endregion
7726
7707
  //#region src/plugin/rules/state-and-effects/utils/build-listener-cleanup-mismatch-message.ts
@@ -8406,54 +8387,6 @@ const effectListenerCleanupMismatch = defineRule({
8406
8387
  } })
8407
8388
  });
8408
8389
  //#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
8390
  //#region src/plugin/utils/is-react-hook-name.ts
8458
8391
  const isReactHookName = (name) => {
8459
8392
  if (!name.startsWith("use")) return false;
@@ -8509,90 +8442,46 @@ const enclosingComponentOrHookName = (node) => {
8509
8442
  return functionNode ? componentOrHookDisplayNameForFunction(functionNode) : null;
8510
8443
  };
8511
8444
  //#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
8445
  //#region src/plugin/utils/get-range-start.ts
8589
8446
  const getRangeStart = (node) => {
8590
8447
  const rangeStart = node.range?.[0];
8591
8448
  return typeof rangeStart === "number" ? rangeStart : null;
8592
8449
  };
8593
8450
  //#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);
8451
+ //#region src/plugin/utils/is-result-discarded-call.ts
8452
+ const isResultDiscardedCall = (callExpression) => {
8453
+ let node = callExpression;
8454
+ let parent = node.parent;
8455
+ while (parent) {
8456
+ if (isNodeOfType(parent, "ExpressionStatement")) return true;
8457
+ if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "void") return true;
8458
+ if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === node) return true;
8459
+ if (isNodeOfType(parent, "ChainExpression")) {
8460
+ node = parent;
8461
+ parent = node.parent;
8462
+ continue;
8463
+ }
8464
+ if (isNodeOfType(parent, "LogicalExpression") && parent.right === node) {
8465
+ node = parent;
8466
+ parent = node.parent;
8467
+ continue;
8468
+ }
8469
+ if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === node || parent.alternate === node)) {
8470
+ node = parent;
8471
+ parent = node.parent;
8472
+ continue;
8473
+ }
8474
+ if (isNodeOfType(parent, "SequenceExpression")) {
8475
+ const expressions = parent.expressions ?? [];
8476
+ if (expressions[expressions.length - 1] !== node) return true;
8477
+ node = parent;
8478
+ parent = node.parent;
8479
+ continue;
8480
+ }
8481
+ return false;
8482
+ }
8483
+ return false;
8484
+ };
8596
8485
  //#endregion
8597
8486
  //#region src/plugin/utils/walk-inside-statement-blocks.ts
8598
8487
  const walkInsideStatementBlocks = (node, visitor) => {
@@ -8627,9 +8516,125 @@ const isCleanupReturningSubscribeLikeCallExpression = (node) => {
8627
8516
  return true;
8628
8517
  };
8629
8518
  //#endregion
8519
+ //#region src/plugin/rules/state-and-effects/utils/is-cleanup-return.ts
8520
+ const ITERATOR_CALLBACK_METHOD_NAMES$1 = new Set([
8521
+ "each",
8522
+ "every",
8523
+ "filter",
8524
+ "find",
8525
+ "findIndex",
8526
+ "findLast",
8527
+ "findLastIndex",
8528
+ "flatMap",
8529
+ "forEach",
8530
+ "map",
8531
+ "reduce",
8532
+ "reduceRight",
8533
+ "some",
8534
+ "sort",
8535
+ "toSorted"
8536
+ ]);
8537
+ const STATIC_ITERATOR_CALLBACK_METHOD_NAMES = new Set([
8538
+ "from",
8539
+ "fromAsync",
8540
+ "groupBy"
8541
+ ]);
8542
+ const unwrapChainExpression$3 = (node) => isNodeOfType(node, "ChainExpression") ? node.expression : node;
8543
+ const isNullLiteral = (node) => isNodeOfType(node, "Literal") && node.value === null;
8544
+ const isListenerRemovalViaNullHandler = (callNode) => {
8545
+ if (!isNodeOfType(callNode, "CallExpression")) return false;
8546
+ const callee = unwrapChainExpression$3(callNode.callee);
8547
+ return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "on" && isNullLiteral(callNode.arguments?.[1]);
8548
+ };
8549
+ const REFERENCE_BASED_REMOVAL_METHOD_NAMES = new Set([
8550
+ "off",
8551
+ "removeEventListener",
8552
+ "removeListener"
8553
+ ]);
8554
+ const isNoOpInlineHandlerRemoval = (callNode, methodName) => {
8555
+ if (!REFERENCE_BASED_REMOVAL_METHOD_NAMES.has(methodName)) return false;
8556
+ const handlerArgument = callNode.arguments?.[1];
8557
+ return isNodeOfType(handlerArgument, "ArrowFunctionExpression") || isNodeOfType(handlerArgument, "FunctionExpression");
8558
+ };
8559
+ const isReleaseLikeCall = (node, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
8560
+ const callNode = unwrapChainExpression$3(node);
8561
+ if (!isNodeOfType(callNode, "CallExpression")) return false;
8562
+ if (isListenerRemovalViaNullHandler(callNode)) return true;
8563
+ const callee = unwrapChainExpression$3(callNode.callee);
8564
+ if (isNodeOfType(callee, "Identifier")) {
8565
+ if (TIMER_CLEANUP_CALLEE_NAMES.has(callee.name)) return true;
8566
+ if (CLEANUP_LIKE_RELEASE_CALLEE_NAMES.has(callee.name)) return true;
8567
+ if (knownCleanupFunctionNames.has(callee.name)) return true;
8568
+ return false;
8569
+ }
8570
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) {
8571
+ if (isNoOpInlineHandlerRemoval(callNode, callee.property.name)) return false;
8572
+ if (BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(callee.property.name) && isNodeOfType(callee.object, "Identifier") && knownBoundSubscriptionNames.has(callee.object.name)) return true;
8573
+ return GLOBAL_RELEASE_METHOD_NAMES.has(callee.property.name);
8574
+ }
8575
+ return false;
8576
+ };
8577
+ 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);
8578
+ const isIteratorCallbackArgument = (node) => {
8579
+ const parentNode = node.parent;
8580
+ if (!isNodeOfType(parentNode, "CallExpression")) return false;
8581
+ if (!parentNode.arguments?.some((argument) => argument === node)) return false;
8582
+ const callee = unwrapChainExpression$3(parentNode.callee);
8583
+ if (parentNode.arguments[1] === node && isStaticIteratorCallbackCallee(callee)) return true;
8584
+ return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && ITERATOR_CALLBACK_METHOD_NAMES$1.has(callee.property.name);
8585
+ };
8586
+ const containsReleaseLikeCall = (node, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
8587
+ let didFindRelease = false;
8588
+ walkAst(node, (child) => {
8589
+ if (didFindRelease) return false;
8590
+ if (child !== node && isFunctionLike$1(child) && !isIteratorCallbackArgument(child)) return false;
8591
+ if (isReleaseLikeCall(child, knownCleanupFunctionNames, knownBoundSubscriptionNames)) {
8592
+ didFindRelease = true;
8593
+ return false;
8594
+ }
8595
+ });
8596
+ return didFindRelease;
8597
+ };
8598
+ const isCleanupFunctionLike = (node, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
8599
+ if (!isFunctionLike$1(node)) return false;
8600
+ return containsReleaseLikeCall(node.body, knownCleanupFunctionNames, knownBoundSubscriptionNames);
8601
+ };
8602
+ const isProvablyNoOpCleanupFunction = (node) => {
8603
+ if (!isFunctionLike$1(node)) return false;
8604
+ let sawNoOpRemoval = false;
8605
+ let sawOtherCall = false;
8606
+ walkAst(node.body, (child) => {
8607
+ if (sawOtherCall) return false;
8608
+ if (isFunctionLike$1(child)) return false;
8609
+ const callNode = unwrapChainExpression$3(child);
8610
+ if (!isNodeOfType(callNode, "CallExpression")) return;
8611
+ const callee = unwrapChainExpression$3(callNode.callee);
8612
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && isNoOpInlineHandlerRemoval(callNode, callee.property.name)) {
8613
+ sawNoOpRemoval = true;
8614
+ return;
8615
+ }
8616
+ sawOtherCall = true;
8617
+ });
8618
+ return sawNoOpRemoval && !sawOtherCall;
8619
+ };
8620
+ const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSubscriptionNames, options = {}) => {
8621
+ if (!returnedValue) return false;
8622
+ const unwrappedValue = unwrapChainExpression$3(returnedValue);
8623
+ if (isNodeOfType(unwrappedValue, "Literal") && unwrappedValue.value === null) return false;
8624
+ if (isNodeOfType(unwrappedValue, "Identifier")) {
8625
+ if (unwrappedValue.name === "undefined") return false;
8626
+ if (knownCleanupFunctionNames.has(unwrappedValue.name)) return true;
8627
+ return options.allowOpaqueReturn === true && !knownBoundSubscriptionNames.has(unwrappedValue.name);
8628
+ }
8629
+ if (isCleanupReturningSubscribeLikeCallExpression(unwrappedValue)) return true;
8630
+ if (isProvablyNoOpCleanupFunction(unwrappedValue)) return false;
8631
+ if (options.allowOpaqueReturn === true && !isSubscribeLikeCallExpression(unwrappedValue)) return true;
8632
+ if (isCleanupFunctionLike(unwrappedValue, knownCleanupFunctionNames, knownBoundSubscriptionNames)) return true;
8633
+ return false;
8634
+ };
8635
+ //#endregion
8630
8636
  //#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
8631
8637
  const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
8632
- const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
8633
8638
  const RESOURCE_NOUN_BY_KIND = {
8634
8639
  subscribe: "subscription",
8635
8640
  timer: "timer",
@@ -8640,66 +8645,7 @@ const isSubscribeOrObserveCall = (node) => {
8640
8645
  if (isSubscribeLikeCallExpression(node)) return true;
8641
8646
  return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === OBSERVER_REGISTRATION_METHOD_NAME;
8642
8647
  };
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) => {
8648
+ const findSubscribeLikeUsages = (callback) => {
8703
8649
  const usages = [];
8704
8650
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
8705
8651
  let cleanupArgument = null;
@@ -8708,22 +8654,13 @@ const findSubscribeLikeUsages = (callback, context) => {
8708
8654
  const lastCallbackStatement = callbackStatements[callbackStatements.length - 1];
8709
8655
  if (isNodeOfType(lastCallbackStatement, "ReturnStatement") && lastCallbackStatement.argument) cleanupArgument = lastCallbackStatement.argument;
8710
8656
  }
8711
- const effectInvokedFunctions = collectEffectInvokedFunctions(callback);
8712
8657
  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
- }
8658
+ if (child === cleanupArgument && !isSubscribeLikeCallExpression(child)) return false;
8717
8659
  if (isSocketConstruction(child)) {
8718
8660
  usages.push({
8719
8661
  kind: "socket",
8720
8662
  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
8663
+ resourceName: isNodeOfType(child.callee, "Identifier") ? child.callee.name : "WebSocket"
8727
8664
  });
8728
8665
  return;
8729
8666
  }
@@ -8732,348 +8669,118 @@ const findSubscribeLikeUsages = (callback, context) => {
8732
8669
  usages.push({
8733
8670
  kind: "timer",
8734
8671
  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
8672
+ resourceName: child.callee.name
8741
8673
  });
8742
8674
  return;
8743
8675
  }
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
- });
8676
+ 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({
8677
+ kind: "subscribe",
8678
+ node: child,
8679
+ resourceName: child.callee.property.name
8680
+ });
8681
+ });
8682
+ return usages;
8683
+ };
8684
+ const collectCleanupBindings = (effectCallback) => {
8685
+ const bindings = {
8686
+ cleanupFunctionNames: /* @__PURE__ */ new Set(),
8687
+ subscriptionNames: /* @__PURE__ */ new Set(),
8688
+ effectScopeVariableNames: /* @__PURE__ */ new Set()
8689
+ };
8690
+ if (!isNodeOfType(effectCallback, "ArrowFunctionExpression") && !isNodeOfType(effectCallback, "FunctionExpression")) return bindings;
8691
+ if (!isNodeOfType(effectCallback.body, "BlockStatement")) return bindings;
8692
+ walkAst(effectCallback.body, (child) => {
8693
+ if (!isSubscribeOrObserveCall(child)) return;
8694
+ if (!isNodeOfType(child, "CallExpression")) return;
8695
+ if (!isNodeOfType(child.callee, "MemberExpression")) return;
8696
+ if (!isNodeOfType(child.callee.object, "Identifier")) return;
8697
+ bindings.subscriptionNames.add(child.callee.object.name);
8698
+ });
8699
+ walkInsideStatementBlocks(effectCallback.body, (child) => {
8700
+ if (!isNodeOfType(child, "VariableDeclaration")) return;
8701
+ for (const declarator of child.declarations ?? []) {
8702
+ if (!isNodeOfType(declarator.id, "Identifier")) continue;
8703
+ const bindingName = declarator.id.name;
8704
+ bindings.effectScopeVariableNames.add(bindingName);
8705
+ const init = declarator.init;
8706
+ if (!init) continue;
8707
+ if (isSocketConstruction(init)) {
8708
+ bindings.subscriptionNames.add(bindingName);
8709
+ continue;
8710
+ }
8711
+ if (!isNodeOfType(init, "CallExpression")) continue;
8712
+ if (isSubscribeLikeCallExpression(init)) {
8713
+ bindings.subscriptionNames.add(bindingName);
8714
+ if (isCleanupReturningSubscribeLikeCallExpression(init)) bindings.cleanupFunctionNames.add(bindingName);
8715
+ }
8753
8716
  }
8754
8717
  });
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) => {
8718
+ walkAst(effectCallback.body, (child) => {
8719
+ if (child !== effectCallback.body && (isNodeOfType(child, "ArrowFunctionExpression") || isNodeOfType(child, "FunctionExpression"))) return false;
8720
+ if (isNodeOfType(child, "FunctionDeclaration") && child.id && isCleanupFunctionLike(child, bindings.cleanupFunctionNames, bindings.subscriptionNames)) {
8721
+ bindings.cleanupFunctionNames.add(child.id.name);
8722
+ return false;
8723
+ }
8724
+ });
8725
+ walkInsideStatementBlocks(effectCallback.body, (child) => {
8726
+ if (!isNodeOfType(child, "VariableDeclaration")) return;
8727
+ for (const declarator of child.declarations ?? []) {
8728
+ if (!isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
8729
+ if (isCleanupFunctionLike(declarator.init, bindings.cleanupFunctionNames, bindings.subscriptionNames)) bindings.cleanupFunctionNames.add(declarator.id.name);
8730
+ }
8731
+ });
8732
+ walkAst(effectCallback.body, (child) => {
8733
+ 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);
8734
+ });
8735
+ return bindings;
8736
+ };
8737
+ const removeSynchronouslyReleasedUsages = (callback, usages) => {
8820
8738
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
8821
8739
  if (!isNodeOfType(callback.body, "BlockStatement")) return usages;
8822
- const releaseCalls = [];
8740
+ const releaseStarts = [];
8823
8741
  walkInsideStatementBlocks(callback.body, (child) => {
8824
- if (!isNodeOfType(isNodeOfType(child, "ChainExpression") ? child.expression : child, "CallExpression")) return;
8825
- releaseCalls.push(child);
8742
+ if (!isReleaseLikeCall(child, EMPTY_NAME_SET$1, EMPTY_NAME_SET$1)) return;
8743
+ const releaseStart = getRangeStart(child);
8744
+ if (releaseStart !== null) releaseStarts.push(releaseStart);
8826
8745
  });
8827
- if (releaseCalls.length === 0) return usages;
8746
+ if (releaseStarts.length === 0) return usages;
8828
8747
  return usages.filter((usage) => {
8829
8748
  const usageStart = getRangeStart(usage.node);
8830
8749
  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);
8750
+ return !releaseStarts.some((releaseStart) => releaseStart > usageStart);
8836
8751
  });
8837
8752
  };
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
- }
8753
+ const cleanupReturnRunsAfterUsage = (returnStatement, usages) => {
8754
+ if (returnStatement.argument && isCleanupReturningSubscribeLikeCallExpression(returnStatement.argument)) return true;
8755
+ const returnStart = getRangeStart(returnStatement);
8756
+ if (returnStart === null) return true;
8757
+ return usages.some((usage) => {
8758
+ const usageStart = getRangeStart(usage.node);
8759
+ return usageStart === null || usageStart < returnStart;
9016
8760
  });
9017
- return didCleanupFunctionMatch;
9018
8761
  };
9019
- const effectHasCleanupForUsage = (callback, usage, context) => {
8762
+ const effectHasCleanupReturn = (callback, usages) => {
9020
8763
  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 = [];
8764
+ if (!isNodeOfType(callback.body, "BlockStatement")) return isCleanupReturningSubscribeLikeCallExpression(callback.body);
8765
+ const cleanupBindings = collectCleanupBindings(callback);
8766
+ let didFindCleanupReturn = false;
9024
8767
  walkInsideStatementBlocks(callback.body, (child) => {
8768
+ if (didFindCleanupReturn) return;
9025
8769
  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);
8770
+ if (!cleanupReturnRunsAfterUsage(child, usages)) return;
8771
+ if (isCleanupReturn(child.argument, cleanupBindings.cleanupFunctionNames, cleanupBindings.subscriptionNames, { allowOpaqueReturn: true })) didFindCleanupReturn = true;
9048
8772
  });
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));
8773
+ return didFindCleanupReturn;
9068
8774
  };
9069
- const isSelfReleasingListenerOptionProperty = (property, context) => {
8775
+ const EMPTY_NAME_SET$1 = /* @__PURE__ */ new Set();
8776
+ const isSelfReleasingListenerOptionProperty = (property) => {
9070
8777
  if (!isNodeOfType(property, "Property")) return false;
9071
8778
  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);
8779
+ if (keyName === "signal") return true;
9073
8780
  if (keyName !== "once") return false;
9074
8781
  return isNodeOfType(property.value, "Literal") && property.value.value === true;
9075
8782
  };
9076
- const hasSelfReleasingListenerOptions = (node, context) => isNodeOfType(node, "CallExpression") && (node.arguments ?? []).some((argument) => isNodeOfType(argument, "ObjectExpression") && (argument.properties ?? []).some((property) => isSelfReleasingListenerOptionProperty(property, context)));
8783
+ const hasSelfReleasingListenerOptions = (node) => isNodeOfType(node, "CallExpression") && (node.arguments ?? []).some((argument) => isNodeOfType(argument, "ObjectExpression") && (argument.properties ?? []).some(isSelfReleasingListenerOptionProperty));
9077
8784
  const PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB = new Map([
9078
8785
  ["addEventListener", new Set(["removeEventListener", "abort"])],
9079
8786
  ["addListener", new Set([
@@ -9099,185 +8806,85 @@ const UNIVERSAL_RELEASE_VERB_NAMES = new Set([
9099
8806
  "teardown"
9100
8807
  ]);
9101
8808
  const SOCKET_RELEASE_VERB_NAMES = new Set(["close"]);
8809
+ const INTERVAL_RELEASE_VERB_NAMES = new Set(["clearInterval"]);
9102
8810
  const getReleaseVerbName = (node) => {
8811
+ if (!isReleaseLikeCall(node, EMPTY_NAME_SET$1, EMPTY_NAME_SET$1)) return null;
9103
8812
  const callNode = isNodeOfType(node, "ChainExpression") ? node.expression : node;
9104
8813
  if (!isNodeOfType(callNode, "CallExpression")) return null;
9105
8814
  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
- }
8815
+ if (isNodeOfType(callee, "Identifier")) return callee.name;
8816
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
9111
8817
  return null;
9112
8818
  };
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
8819
  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;
8820
+ const bodyContainsPairedReleaseCall = (body, pairedVerbNames) => {
8821
+ let didFindPairedRelease = false;
8822
+ walkAst(body, (child) => {
8823
+ if (didFindPairedRelease) return false;
8824
+ if (child !== body && isFunctionLike$1(child)) return false;
8825
+ const releaseVerbName = getReleaseVerbName(child);
8826
+ if (releaseVerbName !== null && matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) {
8827
+ didFindPairedRelease = true;
9189
8828
  return false;
9190
8829
  }
9191
8830
  });
9192
- return didFindRelease;
8831
+ return didFindPairedRelease;
9193
8832
  };
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);
8833
+ const fileReleaseVerbNamesCache = /* @__PURE__ */ new WeakMap();
8834
+ const collectFileReleaseVerbNames = (anyNode) => {
8835
+ let programNode = anyNode;
8836
+ while (programNode.parent) programNode = programNode.parent;
8837
+ const cached = fileReleaseVerbNamesCache.get(programNode);
8838
+ if (cached) return cached;
8839
+ const releaseVerbNames = /* @__PURE__ */ new Set();
8840
+ walkAst(programNode, (child) => {
8841
+ const releaseVerbName = getReleaseVerbName(child);
8842
+ if (releaseVerbName !== null) releaseVerbNames.add(releaseVerbName);
8843
+ });
8844
+ fileReleaseVerbNamesCache.set(programNode, releaseVerbNames);
8845
+ return releaseVerbNames;
9211
8846
  };
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
- }
8847
+ const fileContainsPairedReleaseCall = (registrationCall, registrationVerbName) => {
8848
+ const fileReleaseVerbNames = collectFileReleaseVerbNames(registrationCall);
8849
+ const pairedVerbNames = PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(registrationVerbName);
8850
+ if (!pairedVerbNames) return fileReleaseVerbNames.size > 0;
8851
+ for (const releaseVerbName of fileReleaseVerbNames) if (matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return true;
9225
8852
  return false;
9226
8853
  };
9227
- const findRetainedFunctionLeak = (retainedFunction, context) => {
8854
+ const findRetainedFunctionLeak = (retainedFunction) => {
9228
8855
  if (!isFunctionLike$1(retainedFunction)) return null;
9229
8856
  const body = retainedFunction.body;
9230
8857
  if (!body) return null;
8858
+ const conciseReturnValue = isNodeOfType(body, "BlockStatement") ? null : body;
9231
8859
  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
8860
  walkAst(body, (child) => {
9236
8861
  if (leak !== null) return false;
9237
8862
  if (isFunctionLike$1(child)) return false;
9238
- if (isSocketConstruction(child) && !doesResourceResultEscape(child, false)) {
9239
- const socketUsage = {
8863
+ if (child === conciseReturnValue && isNodeOfType(child, "CallExpression")) return;
8864
+ if (isSocketConstruction(child) && isResultDiscardedCall(child) && !bodyContainsPairedReleaseCall(body, SOCKET_RELEASE_VERB_NAMES)) {
8865
+ leak = {
9240
8866
  kind: "socket",
9241
8867
  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
8868
+ resourceName: isNodeOfType(child.callee, "Identifier") ? child.callee.name : "WebSocket"
9248
8869
  };
9249
- if (!hasReleaseForUsage(socketUsage)) {
9250
- leak = socketUsage;
9251
- return false;
9252
- }
8870
+ return false;
9253
8871
  }
9254
8872
  if (!isNodeOfType(child, "CallExpression")) return;
9255
- if (isNodeOfType(child.callee, "Identifier") && child.callee.name === "setInterval" && !doesResourceResultEscape(child, allowConciseReturnEscape)) {
9256
- const timerUsage = {
8873
+ if (isNodeOfType(child.callee, "Identifier") && child.callee.name === "setInterval" && isResultDiscardedCall(child) && !bodyContainsPairedReleaseCall(body, INTERVAL_RELEASE_VERB_NAMES)) {
8874
+ leak = {
9257
8875
  kind: "timer",
9258
8876
  node: child,
9259
- resourceName: "setInterval",
9260
- handleKey: findAssignedResourceKey(child, context),
9261
- receiverKey: null,
9262
- registrationVerbName: "setInterval",
9263
- eventKey: null,
9264
- handlerKey: null
8877
+ resourceName: "setInterval"
9265
8878
  };
9266
- if (!hasReleaseForUsage(timerUsage)) {
9267
- leak = timerUsage;
9268
- return false;
9269
- }
8879
+ return false;
9270
8880
  }
9271
- if (isSubscribeOrObserveCall(child) && !doesResourceResultEscape(child, allowConciseReturnEscape)) {
9272
- const registrationDetails = getCallRegistrationDetails(child, context);
9273
- const subscriptionUsage = {
8881
+ if (isSubscribeOrObserveCall(child) && isResultDiscardedCall(child)) {
8882
+ const registrationVerbName = isNodeOfType(child.callee, "MemberExpression") && isNodeOfType(child.callee.property, "Identifier") ? child.callee.property.name : "subscribe";
8883
+ if (!hasSelfReleasingListenerOptions(child) && !fileContainsPairedReleaseCall(child, registrationVerbName)) leak = {
9274
8884
  kind: "subscribe",
9275
8885
  node: child,
9276
- resourceName: registrationDetails.registrationVerbName ?? "subscribe",
9277
- handleKey: findAssignedResourceKey(child, context),
9278
- ...registrationDetails
8886
+ resourceName: registrationVerbName
9279
8887
  };
9280
- if (!hasSelfReleasingListenerOptions(child, context) && !hasReleaseForUsage(subscriptionUsage)) leak = subscriptionUsage;
9281
8888
  return false;
9282
8889
  }
9283
8890
  });
@@ -9289,26 +8896,6 @@ const isRetainedComponentScopeFunction = (functionNode) => {
9289
8896
  if (!isNodeOfType(functionNode.parent, "VariableDeclarator")) return false;
9290
8897
  return enclosingComponentOrHookName(functionNode) !== null;
9291
8898
  };
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
8899
  const effectNeedsCleanup = defineRule({
9313
8900
  id: "effect-needs-cleanup",
9314
8901
  title: "Effect subscription or timer never cleaned up",
@@ -9317,8 +8904,7 @@ const effectNeedsCleanup = defineRule({
9317
8904
  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
8905
  create: (context) => {
9319
8906
  const reportRetainedLeak = (retainedFunction) => {
9320
- if (!isPotentiallyReachableFunction(retainedFunction, context)) return;
9321
- const leak = findRetainedFunctionLeak(retainedFunction, context);
8907
+ const leak = findRetainedFunctionLeak(retainedFunction);
9322
8908
  if (!leak) return;
9323
8909
  const resourceNoun = RESOURCE_NOUN_BY_KIND[leak.kind];
9324
8910
  context.report({
@@ -9330,31 +8916,30 @@ const effectNeedsCleanup = defineRule({
9330
8916
  CallExpression(node) {
9331
8917
  if (isHookCall$2(node, "useCallback")) {
9332
8918
  const retainedCallback = getEffectCallback(node);
9333
- if (retainedCallback && !isInlineRetainedHandlerFunction(retainedCallback, context)) reportRetainedLeak(retainedCallback);
8919
+ if (retainedCallback) reportRetainedLeak(retainedCallback);
9334
8920
  return;
9335
8921
  }
9336
- if (!isHookCall$2(node, CLEANUP_EFFECT_HOOK_NAMES)) return;
8922
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
9337
8923
  const callback = getEffectCallback(node);
9338
8924
  if (!callback) return;
9339
- const usages = removeSynchronouslyReleasedUsages(callback, findSubscribeLikeUsages(callback, context), context);
8925
+ const usages = removeSynchronouslyReleasedUsages(callback, findSubscribeLikeUsages(callback));
9340
8926
  if (usages.length === 0) return;
9341
- const firstUsage = findFirstUsageWithoutCleanup(callback, usages, context);
9342
- if (!firstUsage) return;
8927
+ if (effectHasCleanupReturn(callback, usages)) return;
8928
+ const firstUsage = usages[0];
9343
8929
  const resourceNoun = RESOURCE_NOUN_BY_KIND[firstUsage.kind];
9344
- const hookName = getCalleeName$2(node) ?? "effect";
9345
8930
  context.report({
9346
8931
  node,
9347
- message: `\`${firstUsage.resourceName}\` creates a ${resourceNoun} in ${hookName} without returning cleanup. Return a cleanup function so it does not leak after unmount.`
8932
+ message: `\`${firstUsage.resourceName}\` creates a ${resourceNoun} in useEffect without returning cleanup. Return a cleanup function so it does not leak after unmount.`
9348
8933
  });
9349
8934
  },
9350
8935
  FunctionDeclaration(node) {
9351
8936
  if (isRetainedComponentScopeFunction(node)) reportRetainedLeak(node);
9352
8937
  },
9353
8938
  ArrowFunctionExpression(node) {
9354
- if (isRetainedComponentScopeFunction(node) || isInlineRetainedHandlerFunction(node, context)) reportRetainedLeak(node);
8939
+ if (isRetainedComponentScopeFunction(node)) reportRetainedLeak(node);
9355
8940
  },
9356
8941
  FunctionExpression(node) {
9357
- if (isRetainedComponentScopeFunction(node) || isInlineRetainedHandlerFunction(node, context)) reportRetainedLeak(node);
8942
+ if (isRetainedComponentScopeFunction(node)) reportRetainedLeak(node);
9358
8943
  }
9359
8944
  };
9360
8945
  }
@@ -10271,14 +9856,35 @@ const symbolHasStableFunctionOrigin = (symbol, scopes, visitedSymbolIds) => {
10271
9856
  };
10272
9857
  const symbolHasStableValue = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => symbolHasStableHookOrigin(symbol) || symbolHasStableFunctionOrigin(symbol, scopes, visitedSymbolIds) || symbolHasStableMemoizedOrigin(symbol, scopes, visitedSymbolIds);
10273
9858
  //#endregion
9859
+ //#region src/plugin/utils/is-imported-from-non-react-module.ts
9860
+ const isImportedFromNonReactModule = (contextNode, localIdentifierName) => {
9861
+ const importSource = getImportSourceForName(contextNode, localIdentifierName);
9862
+ if (importSource === null) return false;
9863
+ return !REACT_RUNTIME_MODULE_SOURCES.has(importSource);
9864
+ };
9865
+ //#endregion
9866
+ //#region src/plugin/utils/is-non-react-effect-event-callee.ts
9867
+ const resolvesToLocalNonImportBinding = (identifier, scopes) => {
9868
+ const symbol = scopes.referenceFor(identifier)?.resolvedSymbol;
9869
+ return Boolean(symbol && symbol.kind !== "import");
9870
+ };
9871
+ const isNonReactEffectEventCallee = (callee, contextNode, scopes) => {
9872
+ if (isNodeOfType(callee, "Identifier")) return isImportedFromNonReactModule(contextNode, callee.name) || resolvesToLocalNonImportBinding(callee, scopes);
9873
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier")) return isImportedFromNonReactModule(contextNode, callee.object.name);
9874
+ return false;
9875
+ };
9876
+ //#endregion
10274
9877
  //#region src/plugin/utils/symbol-has-react-use-effect-event-origin.ts
9878
+ const getUseEffectEventCalleeName = (callee) => {
9879
+ if (isNodeOfType(callee, "Identifier")) return callee.name;
9880
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
9881
+ return null;
9882
+ };
10275
9883
  const symbolHasReactUseEffectEventOrigin = (symbol, scopes) => {
10276
9884
  const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
10277
9885
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
10278
- return isReactApiCall(initializer, "useEffectEvent", scopes, {
10279
- allowGlobalReactNamespace: true,
10280
- allowUnboundBareCalls: true
10281
- });
9886
+ if (getUseEffectEventCalleeName(initializer.callee) !== "useEffectEvent") return false;
9887
+ return !isNonReactEffectEventCallee(initializer.callee, initializer, scopes);
10282
9888
  };
10283
9889
  //#endregion
10284
9890
  //#region src/plugin/rules/react-builtins/exhaustive-deps.ts
@@ -12738,6 +12344,20 @@ const collectHandlerReferencedNames = (root) => {
12738
12344
  return names;
12739
12345
  };
12740
12346
  //#endregion
12347
+ //#region src/plugin/utils/get-function-binding-name.ts
12348
+ const getFunctionBindingIdentifier = (functionNode) => {
12349
+ if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
12350
+ const parent = functionNode.parent;
12351
+ if (isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier")) return parent.id;
12352
+ if (isNodeOfType(parent, "AssignmentExpression") && parent.right === functionNode && isNodeOfType(parent.left, "Identifier")) return parent.left;
12353
+ if (isNodeOfType(parent, "CallExpression")) {
12354
+ const callParent = parent.parent;
12355
+ if (isNodeOfType(callParent, "VariableDeclarator") && isNodeOfType(callParent.id, "Identifier")) return callParent.id;
12356
+ }
12357
+ return null;
12358
+ };
12359
+ const getFunctionBindingName$1 = (functionNode) => getFunctionBindingIdentifier(functionNode)?.name ?? null;
12360
+ //#endregion
12741
12361
  //#region src/plugin/rules/jotai/jotai-select-atom-in-render-body.ts
12742
12362
  const JOTAI_SELECT_ATOM_SOURCES = ["jotai/utils", "jotai"];
12743
12363
  const COMPONENT_NAME_PATTERN = /^[A-Z]/;
@@ -13882,7 +13502,7 @@ const jsHoistIntl = defineRule({
13882
13502
  });
13883
13503
  //#endregion
13884
13504
  //#region src/plugin/utils/create-loop-aware-visitors.ts
13885
- const ITERATOR_CALLBACK_METHOD_NAMES$1 = new Set([
13505
+ const ITERATOR_CALLBACK_METHOD_NAMES = new Set([
13886
13506
  "map",
13887
13507
  "flatMap",
13888
13508
  "forEach",
@@ -13901,7 +13521,7 @@ const isIteratorCallback = (node) => {
13901
13521
  const parent = node.parent;
13902
13522
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
13903
13523
  if (!parent.arguments.includes(node)) return false;
13904
- return isNodeOfType(parent.callee, "MemberExpression") && isNodeOfType(parent.callee.property, "Identifier") && ITERATOR_CALLBACK_METHOD_NAMES$1.has(parent.callee.property.name);
13524
+ return isNodeOfType(parent.callee, "MemberExpression") && isNodeOfType(parent.callee.property, "Identifier") && ITERATOR_CALLBACK_METHOD_NAMES.has(parent.callee.property.name);
13905
13525
  };
13906
13526
  const createLoopAwareVisitors = (innerVisitors, options = {}) => {
13907
13527
  let loopDepth = 0;
@@ -14130,7 +13750,7 @@ const findIndexedArrayObject = (callbackBody, indexParameterName) => {
14130
13750
  });
14131
13751
  return indexedArrayObject;
14132
13752
  };
14133
- const unwrapChainExpression$3 = (node) => isNodeOfType(node, "ChainExpression") ? node.expression : node;
13753
+ const unwrapChainExpression$2 = (node) => isNodeOfType(node, "ChainExpression") ? node.expression : node;
14134
13754
  const LENGTH_EQUALITY_OPERATORS = new Set(["===", "=="]);
14135
13755
  const LENGTH_MISMATCH_OPERATORS = new Set([
14136
13756
  "!==",
@@ -14142,17 +13762,17 @@ const LENGTH_MISMATCH_OPERATORS = new Set([
14142
13762
  ]);
14143
13763
  const LENGTH_ANY_COMPARISON_OPERATORS = new Set([...LENGTH_EQUALITY_OPERATORS, ...LENGTH_MISMATCH_OPERATORS]);
14144
13764
  const isLengthComparison = (candidate, receiverArray, indexedArray, operators) => {
14145
- const binaryGuard = unwrapChainExpression$3(candidate);
13765
+ const binaryGuard = unwrapChainExpression$2(candidate);
14146
13766
  if (!isNodeOfType(binaryGuard, "BinaryExpression")) return false;
14147
13767
  if (!operators.has(binaryGuard.operator)) return false;
14148
- const leftSide = unwrapChainExpression$3(binaryGuard.left);
14149
- const rightSide = unwrapChainExpression$3(binaryGuard.right);
13768
+ const leftSide = unwrapChainExpression$2(binaryGuard.left);
13769
+ const rightSide = unwrapChainExpression$2(binaryGuard.right);
14150
13770
  if (!isMemberProperty(leftSide, "length")) return false;
14151
13771
  if (!isMemberProperty(rightSide, "length")) return false;
14152
- const leftLengthObject = unwrapChainExpression$3(leftSide.object);
14153
- const rightLengthObject = unwrapChainExpression$3(rightSide.object);
14154
- const normalizedReceiver = unwrapChainExpression$3(receiverArray);
14155
- const normalizedIndexed = unwrapChainExpression$3(indexedArray);
13772
+ const leftLengthObject = unwrapChainExpression$2(leftSide.object);
13773
+ const rightLengthObject = unwrapChainExpression$2(rightSide.object);
13774
+ const normalizedReceiver = unwrapChainExpression$2(receiverArray);
13775
+ const normalizedIndexed = unwrapChainExpression$2(indexedArray);
14156
13776
  const matchesReceiverThenIndexed = areExpressionsStructurallyEqual(leftLengthObject, normalizedReceiver) && areExpressionsStructurallyEqual(rightLengthObject, normalizedIndexed);
14157
13777
  const matchesIndexedThenReceiver = areExpressionsStructurallyEqual(leftLengthObject, normalizedIndexed) && areExpressionsStructurallyEqual(rightLengthObject, normalizedReceiver);
14158
13778
  return matchesReceiverThenIndexed || matchesIndexedThenReceiver;
@@ -14239,18 +13859,18 @@ const LENGTH_PRESERVING_METHOD_NAMES = new Set([
14239
13859
  "toReversed"
14240
13860
  ]);
14241
13861
  const peelLengthPreservingDerivation = (expression) => {
14242
- let current = unwrapChainExpression$3(expression);
13862
+ let current = unwrapChainExpression$2(expression);
14243
13863
  for (;;) {
14244
13864
  if (isNodeOfType(current, "ArrayExpression") && current.elements?.length === 1 && isNodeOfType(current.elements[0], "SpreadElement")) {
14245
- current = unwrapChainExpression$3(current.elements[0].argument);
13865
+ current = unwrapChainExpression$2(current.elements[0].argument);
14246
13866
  continue;
14247
13867
  }
14248
13868
  if (isNodeOfType(current, "CallExpression")) {
14249
- const callee = unwrapChainExpression$3(current.callee);
13869
+ const callee = unwrapChainExpression$2(current.callee);
14250
13870
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) {
14251
- const calleeObject = unwrapChainExpression$3(callee.object);
13871
+ const calleeObject = unwrapChainExpression$2(callee.object);
14252
13872
  if (isNodeOfType(calleeObject, "Identifier") && calleeObject.name === "Array" && callee.property.name === "from" && current.arguments?.length === 1) {
14253
- current = unwrapChainExpression$3(current.arguments[0]);
13873
+ current = unwrapChainExpression$2(current.arguments[0]);
14254
13874
  continue;
14255
13875
  }
14256
13876
  if (LENGTH_PRESERVING_METHOD_NAMES.has(callee.property.name)) {
@@ -14295,7 +13915,7 @@ const resolveComparedArraySource = (expression, scopeNode) => {
14295
13915
  const initializer = findConstInitializer(current.name, scopeNode);
14296
13916
  if (!initializer) return current;
14297
13917
  const peeledInitializer = peelLengthPreservingDerivation(initializer);
14298
- if (peeledInitializer === unwrapChainExpression$3(initializer)) return current;
13918
+ if (peeledInitializer === unwrapChainExpression$2(initializer)) return current;
14299
13919
  current = peeledInitializer;
14300
13920
  }
14301
13921
  return current;
@@ -16311,190 +15931,6 @@ const memoStatusForJsxOpeningName = (registry, openingName) => {
16311
15931
  return registry.get(openingName.name) ?? "unknown";
16312
15932
  };
16313
15933
  //#endregion
16314
- //#region src/plugin/utils/unwrap-react-hoc-function.ts
16315
- /**
16316
- * Resolves a `VariableDeclarator.init` (or any expression) to the inline
16317
- * function expression it binds, seeing through chains of `memo` /
16318
- * `forwardRef` / `React.memo` / `React.forwardRef` wrappers:
16319
- *
16320
- * `() => {}` → the arrow
16321
- * `memo(function Foo() {})` → the named function expression
16322
- * `React.memo(forwardRef(() => {}))` → the inner arrow
16323
- * `memo(SomeIdentifier)` → the identifier's same-file function when scopes are provided
16324
- *
16325
- * Component-shaped rules that previously gated on a direct function init
16326
- * (via `isComponentAssignment`) use this so memo-wrapped components are
16327
- * not silently skipped.
16328
- */
16329
- const resolveReactHocFunction = (node, scopes, visitedSymbolIds) => {
16330
- if (!node) return null;
16331
- const current = stripParenExpression(node);
16332
- if (isInlineFunctionExpression(current)) return current;
16333
- if (isNodeOfType(current, "Identifier") && scopes) {
16334
- const symbol = scopes.symbolFor(current);
16335
- if (!symbol || visitedSymbolIds.has(symbol.id) || !symbol.initializer || symbol.kind !== "const" && symbol.kind !== "function") return null;
16336
- if (symbol.kind === "const" && (!isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier)) return null;
16337
- visitedSymbolIds.add(symbol.id);
16338
- return resolveReactHocFunction(symbol.initializer, scopes, visitedSymbolIds);
16339
- }
16340
- if (isNodeOfType(current, "CallExpression")) {
16341
- const calleeName = flattenCalleeName(current.callee);
16342
- if (!calleeName || !REACT_HOC_NAMES.has(calleeName)) return null;
16343
- const firstArgument = current.arguments[0];
16344
- if (!firstArgument || isNodeOfType(firstArgument, "SpreadElement")) return null;
16345
- return resolveReactHocFunction(firstArgument, scopes, visitedSymbolIds);
16346
- }
16347
- return null;
16348
- };
16349
- const unwrapReactHocFunction = (node, scopes) => resolveReactHocFunction(node, scopes, /* @__PURE__ */ new Set());
16350
- //#endregion
16351
- //#region src/plugin/utils/build-same-file-jsx-slot-prop-registry.ts
16352
- const REACT_SLOT_TYPE_NAMES = new Set(["ReactNode", "ReactElement"]);
16353
- const unwrapTopLevelDeclaration = (statement) => {
16354
- if (isNodeOfType(statement, "ExportNamedDeclaration")) return statement.declaration;
16355
- if (isNodeOfType(statement, "ExportDefaultDeclaration")) return statement.declaration;
16356
- return statement;
16357
- };
16358
- const buildReactSlotTypeEnvironment = (program) => {
16359
- const reactSlotTypeBindings = /* @__PURE__ */ new Set();
16360
- const reactNamespaceBindings = /* @__PURE__ */ new Set();
16361
- const reactJsxNamespaceBindings = /* @__PURE__ */ new Set();
16362
- const nonReactJsxNamespaceBindings = /* @__PURE__ */ new Set();
16363
- const sameFileTypeDeclarations = /* @__PURE__ */ new Map();
16364
- let hasLocalJsxNamespace = false;
16365
- if (!isNodeOfType(program, "Program")) return {
16366
- reactSlotTypeBindings,
16367
- reactNamespaceBindings,
16368
- reactJsxNamespaceBindings,
16369
- nonReactJsxNamespaceBindings,
16370
- sameFileTypeDeclarations,
16371
- hasLocalJsxNamespace
16372
- };
16373
- for (const statement of program.body) {
16374
- if (isNodeOfType(statement, "ImportDeclaration")) {
16375
- const isReactImport = statement.source.value === "react";
16376
- for (const specifier of statement.specifiers) {
16377
- if (isReactImport && (isNodeOfType(specifier, "ImportDefaultSpecifier") || isNodeOfType(specifier, "ImportNamespaceSpecifier"))) {
16378
- reactNamespaceBindings.add(specifier.local.name);
16379
- continue;
16380
- }
16381
- if (!isNodeOfType(specifier, "ImportSpecifier")) continue;
16382
- const importedName = getImportedName(specifier);
16383
- if (!importedName) continue;
16384
- if (isReactImport && REACT_SLOT_TYPE_NAMES.has(importedName)) reactSlotTypeBindings.add(specifier.local.name);
16385
- if (importedName === "JSX") if (isReactImport) reactJsxNamespaceBindings.add(specifier.local.name);
16386
- else nonReactJsxNamespaceBindings.add(specifier.local.name);
16387
- }
16388
- continue;
16389
- }
16390
- const declaration = unwrapTopLevelDeclaration(statement);
16391
- if (!declaration) continue;
16392
- if ((isNodeOfType(declaration, "TSInterfaceDeclaration") || isNodeOfType(declaration, "TSTypeAliasDeclaration")) && isNodeOfType(declaration.id, "Identifier")) sameFileTypeDeclarations.set(declaration.id.name, declaration);
16393
- if (isNodeOfType(declaration, "TSModuleDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === "JSX") hasLocalJsxNamespace = true;
16394
- }
16395
- return {
16396
- reactSlotTypeBindings,
16397
- reactNamespaceBindings,
16398
- reactJsxNamespaceBindings,
16399
- nonReactJsxNamespaceBindings,
16400
- sameFileTypeDeclarations,
16401
- hasLocalJsxNamespace
16402
- };
16403
- };
16404
- const isJsxElementTypeName = (typeName, environment) => {
16405
- if (!isNodeOfType(typeName, "TSQualifiedName")) return false;
16406
- if (!isNodeOfType(typeName.right, "Identifier") || typeName.right.name !== "Element") return false;
16407
- if (isNodeOfType(typeName.left, "Identifier")) {
16408
- const namespaceName = typeName.left.name;
16409
- if (environment.reactNamespaceBindings.has(namespaceName)) return true;
16410
- if (environment.reactJsxNamespaceBindings.has(namespaceName)) return true;
16411
- if (environment.nonReactJsxNamespaceBindings.has(namespaceName)) return false;
16412
- return namespaceName === "JSX" && !environment.hasLocalJsxNamespace;
16413
- }
16414
- return isNodeOfType(typeName.left, "TSQualifiedName") && isNodeOfType(typeName.left.left, "Identifier") && environment.reactNamespaceBindings.has(typeName.left.left.name) && isNodeOfType(typeName.left.right, "Identifier") && typeName.left.right.name === "JSX";
16415
- };
16416
- const isReactSlotType = (typeNode, environment, activeDeclarations) => {
16417
- if (isNodeOfType(typeNode, "TSUnionType")) return typeNode.types.some((unionMember) => isReactSlotType(unionMember, environment, activeDeclarations));
16418
- if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
16419
- if (isJsxElementTypeName(typeNode.typeName, environment)) return true;
16420
- if (isNodeOfType(typeNode.typeName, "TSQualifiedName") && isNodeOfType(typeNode.typeName.left, "Identifier") && environment.reactNamespaceBindings.has(typeNode.typeName.left.name) && isNodeOfType(typeNode.typeName.right, "Identifier") && REACT_SLOT_TYPE_NAMES.has(typeNode.typeName.right.name)) return true;
16421
- if (!isNodeOfType(typeNode.typeName, "Identifier")) return false;
16422
- const typeName = typeNode.typeName.name;
16423
- const sameFileDeclaration = environment.sameFileTypeDeclarations.get(typeName);
16424
- if (sameFileDeclaration) {
16425
- if (!isNodeOfType(sameFileDeclaration, "TSTypeAliasDeclaration") || activeDeclarations.has(sameFileDeclaration)) return false;
16426
- activeDeclarations.add(sameFileDeclaration);
16427
- const isSlot = isReactSlotType(sameFileDeclaration.typeAnnotation, environment, activeDeclarations);
16428
- activeDeclarations.delete(sameFileDeclaration);
16429
- return isSlot;
16430
- }
16431
- return environment.reactSlotTypeBindings.has(typeName);
16432
- };
16433
- const getPropertyName = (property) => {
16434
- if (!isNodeOfType(property, "TSPropertySignature") || property.computed) return null;
16435
- if (isNodeOfType(property.key, "Identifier")) return property.key.name;
16436
- if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
16437
- return null;
16438
- };
16439
- const collectSlotPropertyNames = (propsType, environment, slotPropertyNames, activeDeclarations) => {
16440
- let members = null;
16441
- if (isNodeOfType(propsType, "TSTypeLiteral")) members = propsType.members;
16442
- if (isNodeOfType(propsType, "TSInterfaceDeclaration")) members = propsType.body.body;
16443
- if (members) {
16444
- for (const member of members) {
16445
- const propertyName = getPropertyName(member);
16446
- if (!propertyName || !isNodeOfType(member, "TSPropertySignature")) continue;
16447
- const annotation = member.typeAnnotation;
16448
- if (annotation && isNodeOfType(annotation, "TSTypeAnnotation") && isReactSlotType(annotation.typeAnnotation, environment, /* @__PURE__ */ new Set())) slotPropertyNames.add(propertyName);
16449
- }
16450
- return;
16451
- }
16452
- if (isNodeOfType(propsType, "TSIntersectionType")) {
16453
- for (const intersectionMember of propsType.types) collectSlotPropertyNames(intersectionMember, environment, slotPropertyNames, activeDeclarations);
16454
- return;
16455
- }
16456
- if (isNodeOfType(propsType, "TSTypeAliasDeclaration")) {
16457
- collectSlotPropertyNames(propsType.typeAnnotation, environment, slotPropertyNames, activeDeclarations);
16458
- return;
16459
- }
16460
- if (!isNodeOfType(propsType, "TSTypeReference") || !isNodeOfType(propsType.typeName, "Identifier")) return;
16461
- const declaration = environment.sameFileTypeDeclarations.get(propsType.typeName.name);
16462
- if (!declaration || activeDeclarations.has(declaration)) return;
16463
- activeDeclarations.add(declaration);
16464
- collectSlotPropertyNames(declaration, environment, slotPropertyNames, activeDeclarations);
16465
- activeDeclarations.delete(declaration);
16466
- };
16467
- const getMemoizedComponentPropsType = (initializer, scopes) => {
16468
- const componentFunction = unwrapReactHocFunction(initializer, scopes);
16469
- if (componentFunction) {
16470
- const firstParameter = componentFunction.params[0];
16471
- const annotation = firstParameter && "typeAnnotation" in firstParameter ? firstParameter.typeAnnotation : null;
16472
- if (annotation && isNodeOfType(annotation, "TSTypeAnnotation")) return annotation.typeAnnotation;
16473
- }
16474
- if (!isNodeOfType(initializer, "CallExpression")) return null;
16475
- return initializer.typeArguments?.params[0] ?? null;
16476
- };
16477
- const buildSameFileJsxSlotPropRegistry = (program, memoRegistry, scopes) => {
16478
- const registry = /* @__PURE__ */ new Map();
16479
- if (!isNodeOfType(program, "Program")) return registry;
16480
- const environment = buildReactSlotTypeEnvironment(program);
16481
- for (const statement of program.body) {
16482
- const declaration = unwrapTopLevelDeclaration(statement);
16483
- if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) continue;
16484
- for (const declarator of declaration.declarations) {
16485
- if (!isNodeOfType(declarator.id, "Identifier") || memoRegistry.get(declarator.id.name) !== "memoised" || !declarator.init) continue;
16486
- const componentSymbol = scopes.symbolFor(declarator.id);
16487
- if (!componentSymbol) continue;
16488
- const propsType = getMemoizedComponentPropsType(declarator.init, scopes);
16489
- if (!propsType) continue;
16490
- const slotPropertyNames = /* @__PURE__ */ new Set();
16491
- collectSlotPropertyNames(propsType, environment, slotPropertyNames, /* @__PURE__ */ new Set());
16492
- if (slotPropertyNames.size > 0) registry.set(componentSymbol.id, slotPropertyNames);
16493
- }
16494
- }
16495
- return registry;
16496
- };
16497
- //#endregion
16498
15934
  //#region src/plugin/utils/is-on-intrinsic-html-element.ts
16499
15935
  const isJsxAttributeOnIntrinsicHtmlElement = (attribute) => {
16500
15936
  const openingElement = attribute.parent;
@@ -16768,11 +16204,9 @@ const jsxNoJsxAsProp = defineRule({
16768
16204
  create: (context) => {
16769
16205
  const isTestlikeFile = isTestlikeFilename(context.filename);
16770
16206
  let memoRegistry = null;
16771
- let jsxSlotPropRegistry = null;
16772
16207
  return {
16773
16208
  Program(node) {
16774
16209
  memoRegistry = buildSameFileMemoRegistry(node);
16775
- jsxSlotPropRegistry = buildSameFileJsxSlotPropRegistry(node, memoRegistry, context.scopes);
16776
16210
  },
16777
16211
  JSXAttribute(node) {
16778
16212
  if (isTestlikeFile) return;
@@ -16780,8 +16214,6 @@ const jsxNoJsxAsProp = defineRule({
16780
16214
  const parentJsxOpening = node.parent;
16781
16215
  const openingName = parentJsxOpening && isNodeOfType(parentJsxOpening, "JSXOpeningElement") ? parentJsxOpening.name : null;
16782
16216
  if (memoStatusForJsxOpeningName(memoRegistry, openingName) !== "memoised") return;
16783
- const openingSymbol = openingName && isNodeOfType(openingName, "JSXIdentifier") ? context.scopes.symbolFor(openingName) : null;
16784
- if (openingSymbol && isNodeOfType(node.name, "JSXIdentifier") && jsxSlotPropRegistry?.get(openingSymbol.id)?.has(node.name.name)) return;
16785
16217
  if (isNodeOfType(node.name, "JSXIdentifier") && isSlotPropName(node.name.name)) return;
16786
16218
  if (!isInsideFunctionScope(node)) return;
16787
16219
  const value = node.value;
@@ -20025,6 +19457,54 @@ const nextjsNoAElement = defineRule({
20025
19457
  } })
20026
19458
  });
20027
19459
  //#endregion
19460
+ //#region src/plugin/utils/collect-effect-invoked-functions.ts
19461
+ const PROMISE_CHAIN_METHOD_NAMES$1 = new Set([
19462
+ "then",
19463
+ "catch",
19464
+ "finally"
19465
+ ]);
19466
+ const collectEffectInvokedFunctions = (effectCallback) => {
19467
+ const invokedFunctions = new Set([effectCallback]);
19468
+ const localFunctionBindings = /* @__PURE__ */ new Map();
19469
+ const calledBindingNames = /* @__PURE__ */ new Set();
19470
+ const pendingFunctions = [effectCallback];
19471
+ const enqueue = (candidate) => {
19472
+ const strippedCandidate = candidate ? stripParenExpression(candidate) : candidate;
19473
+ if (!isFunctionLike$1(strippedCandidate) || invokedFunctions.has(strippedCandidate)) return;
19474
+ invokedFunctions.add(strippedCandidate);
19475
+ pendingFunctions.push(strippedCandidate);
19476
+ };
19477
+ const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
19478
+ while (pendingFunctions.length > 0) {
19479
+ const currentFunction = pendingFunctions.pop();
19480
+ if (!currentFunction) break;
19481
+ walkAst(currentFunction, (child) => {
19482
+ if (child !== currentFunction && isFunctionLike$1(child)) {
19483
+ if (isNodeOfType(child, "FunctionDeclaration") && isNodeOfType(child.id, "Identifier")) localFunctionBindings.set(child.id.name, child);
19484
+ return false;
19485
+ }
19486
+ if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
19487
+ const initializer = child.init ? stripParenExpression(child.init) : null;
19488
+ if (isFunctionLike$1(initializer)) localFunctionBindings.set(child.id.name, initializer);
19489
+ return;
19490
+ }
19491
+ if (!isNodeOfType(child, "CallExpression")) return;
19492
+ const callee = stripParenExpression(child.callee);
19493
+ if (isFunctionLike$1(callee)) {
19494
+ enqueue(callee);
19495
+ return;
19496
+ }
19497
+ if (isNodeOfType(callee, "Identifier")) {
19498
+ calledBindingNames.add(callee.name);
19499
+ return;
19500
+ }
19501
+ if (isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
19502
+ });
19503
+ for (const calledName of calledBindingNames) enqueue(localFunctionBindings.get(calledName));
19504
+ }
19505
+ return invokedFunctions;
19506
+ };
19507
+ //#endregion
20028
19508
  //#region src/plugin/utils/contains-fetch-call.ts
20029
19509
  const isFetchCall$1 = (node) => {
20030
19510
  if (!isNodeOfType(node, "CallExpression")) return false;
@@ -25151,7 +24631,7 @@ const isAsyncFunctionLike = (node) => {
25151
24631
  if (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) return Boolean(node.async);
25152
24632
  return false;
25153
24633
  };
25154
- const SYNCHRONOUS_ITERATION_METHOD_NAMES = new Set([
24634
+ const SYNCHRONOUS_ITERATION_METHOD_NAMES$1 = new Set([
25155
24635
  "forEach",
25156
24636
  "map",
25157
24637
  "filter",
@@ -25172,7 +24652,7 @@ const runsOnEffectDispatch = (functionNode) => {
25172
24652
  if (parent.callee === functionNode) return true;
25173
24653
  if (!(parent.arguments ?? []).some((argument) => argument === functionNode)) return false;
25174
24654
  const callee = parent.callee;
25175
- return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES.has(callee.property.name);
24655
+ return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES$1.has(callee.property.name);
25176
24656
  };
25177
24657
  const isTerminatingStatement = (statement) => isNodeOfType(statement, "BreakStatement") || isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement") || isNodeOfType(statement, "ContinueStatement");
25178
24658
  const analyzeBranchStatements = (branch, context) => analyzeStatementSequence(isNodeOfType(branch, "BlockStatement") ? branch.body ?? [] : [branch], context);
@@ -27805,14 +27285,14 @@ const hasDocumentClassListMutation = (node) => {
27805
27285
  //#endregion
27806
27286
  //#region src/plugin/rules/state-and-effects/no-effect-event-handler.ts
27807
27287
  const hasEventLikeNode = (node) => findTriggeredSideEffectCalleeName(node) !== null || hasDocumentClassListMutation(node);
27808
- const unwrapChainExpression$2 = (node) => {
27288
+ const unwrapChainExpression$1 = (node) => {
27809
27289
  if (!node) return null;
27810
27290
  if (isNodeOfType(node, "ChainExpression")) return node.expression;
27811
27291
  return node;
27812
27292
  };
27813
27293
  const collectGuardExpressions = (node, into) => {
27814
27294
  if (!node) return;
27815
- const unwrappedNode = unwrapChainExpression$2(node);
27295
+ const unwrappedNode = unwrapChainExpression$1(node);
27816
27296
  if (!unwrappedNode) return;
27817
27297
  const rootIdentifierName = getRootIdentifierName(unwrappedNode);
27818
27298
  if (rootIdentifierName) {
@@ -27843,7 +27323,7 @@ const isReturnOnlyStatement = (node) => {
27843
27323
  };
27844
27324
  const hasEventLikeRemainingStatements = (statements) => statements.some((statement) => !isNodeOfType(statement, "ReturnStatement") && hasEventLikeNode(statement));
27845
27325
  const doesGuardMatchDependency = (guardExpression, dependencyExpression) => {
27846
- const unwrappedDependencyExpression = unwrapChainExpression$2(dependencyExpression);
27326
+ const unwrappedDependencyExpression = unwrapChainExpression$1(dependencyExpression);
27847
27327
  if (!unwrappedDependencyExpression) return false;
27848
27328
  if (areExpressionsStructurallyEqual(guardExpression.expression, unwrappedDependencyExpression)) return true;
27849
27329
  return isNodeOfType(unwrappedDependencyExpression, "Identifier") && unwrappedDependencyExpression.name === guardExpression.rootIdentifierName;
@@ -27934,24 +27414,6 @@ const noEffectEventHandler = defineRule({
27934
27414
  }
27935
27415
  });
27936
27416
  //#endregion
27937
- //#region src/plugin/utils/is-imported-from-non-react-module.ts
27938
- const isImportedFromNonReactModule = (contextNode, localIdentifierName) => {
27939
- const importSource = getImportSourceForName(contextNode, localIdentifierName);
27940
- if (importSource === null) return false;
27941
- return !REACT_RUNTIME_MODULE_SOURCES.has(importSource);
27942
- };
27943
- //#endregion
27944
- //#region src/plugin/utils/is-non-react-effect-event-callee.ts
27945
- const resolvesToLocalNonImportBinding = (identifier, scopes) => {
27946
- const symbol = scopes.referenceFor(identifier)?.resolvedSymbol;
27947
- return Boolean(symbol && symbol.kind !== "import");
27948
- };
27949
- const isNonReactEffectEventCallee = (callee, contextNode, scopes) => {
27950
- if (isNodeOfType(callee, "Identifier")) return isImportedFromNonReactModule(contextNode, callee.name) || resolvesToLocalNonImportBinding(callee, scopes);
27951
- if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier")) return isImportedFromNonReactModule(contextNode, callee.object.name);
27952
- return false;
27953
- };
27954
- //#endregion
27955
27417
  //#region src/plugin/rules/state-and-effects/no-effect-event-in-deps.ts
27956
27418
  const createComponentBindingStackTracker = (callbacks) => {
27957
27419
  const componentBindingStack = [];
@@ -29033,61 +28495,13 @@ const noEventTriggerState = defineRule({
29033
28495
  }
29034
28496
  });
29035
28497
  //#endregion
29036
- //#region src/plugin/utils/resolve-expression-key.ts
29037
- const resolveExpressionKey = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
29038
- if (!expression) return null;
29039
- const unwrappedExpression = stripParenExpression(expression);
29040
- if (isNodeOfType(unwrappedExpression, "Identifier")) {
29041
- const symbol = context.scopes.symbolFor(unwrappedExpression);
29042
- if (!symbol) return context.scopes.isGlobalReference(unwrappedExpression) ? `global:${unwrappedExpression.name}` : null;
29043
- if (visitedSymbolIds.has(symbol.id)) return `symbol:${symbol.id}`;
29044
- visitedSymbolIds.add(symbol.id);
29045
- const bindingProperty = symbol.bindingIdentifier.parent;
29046
- const bindingPattern = bindingProperty?.parent;
29047
- const variableDeclarator = bindingPattern?.parent;
29048
- const bindingPropertyName = isNodeOfType(bindingProperty, "Property") ? getStaticPropertyKeyName(bindingProperty) : null;
29049
- if (bindingPropertyName && isNodeOfType(bindingPattern, "ObjectPattern") && isNodeOfType(variableDeclarator, "VariableDeclarator") && variableDeclarator.id === bindingPattern) {
29050
- const objectKey = resolveExpressionKey(variableDeclarator.init, context, visitedSymbolIds);
29051
- return objectKey ? `${objectKey}.${bindingPropertyName}` : `symbol:${symbol.id}`;
29052
- }
29053
- const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
29054
- if (symbol.kind === "const" && initializer && (isNodeOfType(initializer, "Identifier") || isNodeOfType(initializer, "MemberExpression"))) return resolveExpressionKey(initializer, context, visitedSymbolIds) ?? `symbol:${symbol.id}`;
29055
- return `symbol:${symbol.id}`;
29056
- }
29057
- if (isNodeOfType(unwrappedExpression, "MemberExpression") && !unwrappedExpression.computed) {
29058
- if (!isNodeOfType(unwrappedExpression.property, "Identifier")) return null;
29059
- const objectKey = resolveExpressionKey(unwrappedExpression.object, context, visitedSymbolIds);
29060
- return objectKey ? `${objectKey}.${unwrappedExpression.property.name}` : null;
29061
- }
29062
- if (isNodeOfType(unwrappedExpression, "ThisExpression")) return "this";
29063
- if (isNodeOfType(unwrappedExpression, "Literal") && (typeof unwrappedExpression.value === "string" || typeof unwrappedExpression.value === "number")) return `literal:${String(unwrappedExpression.value)}`;
29064
- if (isFunctionLike$1(unwrappedExpression)) {
29065
- const rangeStart = getRangeStart(unwrappedExpression);
29066
- return rangeStart === null ? null : `function:${rangeStart}`;
29067
- }
29068
- return null;
29069
- };
29070
- //#endregion
29071
- //#region src/plugin/utils/statement-always-exits.ts
29072
- const statementAlwaysExits = (statement) => {
29073
- if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement")) return true;
29074
- if (isNodeOfType(statement, "IfStatement")) return Boolean(statement.alternate && statementAlwaysExits(statement.consequent) && statementAlwaysExits(statement.alternate));
29075
- if (!isNodeOfType(statement, "BlockStatement")) return false;
29076
- return statement.body.some((childStatement) => statementAlwaysExits(childStatement));
29077
- };
29078
- //#endregion
29079
28498
  //#region src/plugin/rules/state-and-effects/no-fetch-in-effect.ts
29080
28499
  const IMPORT_INITIALIZER_TYPES = new Set([
29081
28500
  "ImportSpecifier",
29082
28501
  "ImportDefaultSpecifier",
29083
28502
  "ImportNamespaceSpecifier"
29084
28503
  ]);
29085
- const CANCELLATION_FLAG_NAME_PATTERN = /cancel|ignore/i;
29086
- const PROMISE_CONTINUATION_METHOD_NAMES = new Set([
29087
- "then",
29088
- "catch",
29089
- "finally"
29090
- ]);
28504
+ const CANCELLATION_FLAG_NAME_PATTERN = /cancel/i;
29091
28505
  const isShadowedByLocalBinding = (identifier) => {
29092
28506
  if (!isNodeOfType(identifier, "Identifier")) return false;
29093
28507
  const binding = findVariableInitializer(identifier, identifier.name);
@@ -29103,54 +28517,49 @@ const isRealFetchCall = (node) => {
29103
28517
  };
29104
28518
  const isXmlHttpRequestConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "XMLHttpRequest";
29105
28519
  const isNetworkRequest = (node) => isRealFetchCall(node) || isXmlHttpRequestConstruction(node);
29106
- const resolveLocalFunction = (expression, context) => {
29107
- if (!expression) return null;
29108
- const unwrappedExpression = stripParenExpression(expression);
29109
- if (isFunctionLike$1(unwrappedExpression)) return unwrappedExpression;
29110
- if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
29111
- const initializer = context.scopes.symbolFor(unwrappedExpression)?.initializer;
29112
- if (!initializer) return null;
29113
- const unwrappedInitializer = stripParenExpression(initializer);
29114
- return isFunctionLike$1(unwrappedInitializer) ? unwrappedInitializer : null;
28520
+ const containsNetworkRequest = (root) => {
28521
+ let found = false;
28522
+ walkAst(root, (child) => {
28523
+ if (found) return false;
28524
+ if (isNetworkRequest(child)) {
28525
+ found = true;
28526
+ return false;
28527
+ }
28528
+ });
28529
+ return found;
29115
28530
  };
29116
- const collectEffectAnalysisFunctions = (effectCallback, context) => {
29117
- const analysisFunctions = /* @__PURE__ */ new Set();
29118
- const pendingFunctions = [];
29119
- const enqueueFunction = (functionNode) => {
29120
- for (const invokedFunction of collectEffectInvokedFunctions(functionNode)) {
29121
- if (analysisFunctions.has(invokedFunction)) continue;
29122
- analysisFunctions.add(invokedFunction);
29123
- pendingFunctions.push(invokedFunction);
28531
+ const collectComponentScopeFunctionBindings = (componentScope, effectCallback) => {
28532
+ const bindings = /* @__PURE__ */ new Map();
28533
+ walkAst(componentScope, (child) => {
28534
+ if (child === effectCallback) return false;
28535
+ if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
28536
+ const initializer = child.init ? stripParenExpression(child.init) : null;
28537
+ if (isFunctionLike$1(initializer)) bindings.set(child.id.name, initializer);
28538
+ return;
29124
28539
  }
29125
- };
29126
- enqueueFunction(effectCallback);
29127
- while (pendingFunctions.length > 0) {
29128
- const currentFunction = pendingFunctions.pop();
29129
- if (!currentFunction) break;
29130
- walkAst(currentFunction, (child) => {
29131
- if (child !== currentFunction && isFunctionLike$1(child)) return false;
29132
- if (!isNodeOfType(child, "CallExpression")) return;
29133
- const calledFunction = resolveLocalFunction(child.callee, context);
29134
- if (calledFunction) enqueueFunction(calledFunction);
29135
- for (const callArgument of child.arguments ?? []) {
29136
- const callbackFunction = resolveLocalFunction(callArgument, context);
29137
- if (callbackFunction) enqueueFunction(callbackFunction);
29138
- }
29139
- });
29140
- }
29141
- return analysisFunctions;
28540
+ if (isNodeOfType(child, "FunctionDeclaration") && isNodeOfType(child.id, "Identifier")) bindings.set(child.id.name, child);
28541
+ });
28542
+ return bindings;
29142
28543
  };
29143
- const collectNodesFromAnalysisFunctions = (analysisFunctions, predicate) => {
29144
- const nodes = [];
29145
- const seenNodes = /* @__PURE__ */ new Set();
29146
- for (const analysisFunction of analysisFunctions) walkAst(analysisFunction, (child) => {
29147
- if (child !== analysisFunction && isFunctionLike$1(child)) return false;
29148
- if (!seenNodes.has(child) && predicate(child)) {
29149
- seenNodes.add(child);
29150
- nodes.push(child);
28544
+ const containsNetworkRequestInEffect = (effectCallback) => {
28545
+ const invokedFunctions = collectEffectInvokedFunctions(effectCallback);
28546
+ for (const invokedFunction of invokedFunctions) if (containsNetworkRequest(invokedFunction)) return true;
28547
+ const enclosingComponent = findEnclosingFunction(effectCallback);
28548
+ if (!enclosingComponent) return false;
28549
+ const scopeFunctions = collectComponentScopeFunctionBindings(enclosingComponent, effectCallback);
28550
+ let found = false;
28551
+ walkAst(effectCallback, (child) => {
28552
+ if (found) return false;
28553
+ if (child !== effectCallback && isFunctionLike$1(child) && invokedFunctions.has(child)) return false;
28554
+ if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier")) {
28555
+ const scopeFunction = scopeFunctions.get(child.callee.name);
28556
+ if (scopeFunction && containsNetworkRequest(scopeFunction)) {
28557
+ found = true;
28558
+ return false;
28559
+ }
29151
28560
  }
29152
28561
  });
29153
- return nodes;
28562
+ return found;
29154
28563
  };
29155
28564
  const findEffectCleanupFunction = (callback) => {
29156
28565
  if (!isFunctionLike$1(callback)) return null;
@@ -29163,177 +28572,36 @@ const findEffectCleanupFunction = (callback) => {
29163
28572
  });
29164
28573
  return cleanup;
29165
28574
  };
29166
- const collectEffectCancellationFlagKeys = (effectCallback, context) => {
29167
- const flagKeys = /* @__PURE__ */ new Set();
29168
- if (!isFunctionLike$1(effectCallback)) return flagKeys;
28575
+ const collectEffectCancellationFlagNames = (effectCallback) => {
28576
+ const flagNames = /* @__PURE__ */ new Set();
28577
+ if (!isFunctionLike$1(effectCallback)) return flagNames;
29169
28578
  const body = effectCallback.body;
29170
- if (!isNodeOfType(body, "BlockStatement")) return flagKeys;
28579
+ if (!isNodeOfType(body, "BlockStatement")) return flagNames;
29171
28580
  for (const statement of body.body ?? []) {
29172
28581
  if (!isNodeOfType(statement, "VariableDeclaration") || statement.kind !== "let") continue;
29173
28582
  for (const declarator of statement.declarations ?? []) {
29174
28583
  if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.id, "Identifier")) continue;
29175
28584
  const initializer = declarator.init;
29176
- if (isNodeOfType(initializer, "Literal") && initializer.value === false && CANCELLATION_FLAG_NAME_PATTERN.test(declarator.id.name)) {
29177
- const flagKey = resolveExpressionKey(declarator.id, context);
29178
- if (flagKey) flagKeys.add(flagKey);
29179
- }
29180
- }
29181
- }
29182
- return flagKeys;
29183
- };
29184
- const resolveConstValue = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
29185
- if (!expression) return null;
29186
- const unwrappedExpression = stripParenExpression(expression);
29187
- if (!isNodeOfType(unwrappedExpression, "Identifier")) return unwrappedExpression;
29188
- const symbol = context.scopes.symbolFor(unwrappedExpression);
29189
- if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return unwrappedExpression;
29190
- visitedSymbolIds.add(symbol.id);
29191
- return resolveConstValue(symbol.initializer, context, visitedSymbolIds);
29192
- };
29193
- const resolveRequestAbortControllerKey = (request, context) => {
29194
- if (isNodeOfType(request, "NewExpression")) {
29195
- const declarator = request.parent;
29196
- return isNodeOfType(declarator, "VariableDeclarator") && declarator.init === request ? resolveExpressionKey(declarator.id, context) : null;
29197
- }
29198
- if (!isNodeOfType(request, "CallExpression")) return null;
29199
- for (const requestArgument of request.arguments ?? []) {
29200
- const options = resolveConstValue(requestArgument, context);
29201
- if (!isNodeOfType(options, "ObjectExpression")) continue;
29202
- for (const property of options.properties ?? []) {
29203
- if (!isNodeOfType(property, "Property") || getStaticPropertyKeyName(property, { allowComputedString: true }) !== "signal") continue;
29204
- const signalKey = resolveExpressionKey(property.value, context);
29205
- return signalKey?.endsWith(".signal") ? signalKey.slice(0, -7) : null;
28585
+ if (isNodeOfType(initializer, "Literal") && initializer.value === false && CANCELLATION_FLAG_NAME_PATTERN.test(declarator.id.name)) flagNames.add(declarator.id.name);
29206
28586
  }
29207
28587
  }
29208
- return null;
28588
+ return flagNames;
29209
28589
  };
29210
- const collectCleanupAbortedControllerKeys = (cleanup, context) => {
29211
- const controllerKeys = /* @__PURE__ */ new Set();
29212
- walkAst(cleanup, (child) => {
29213
- if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "MemberExpression") && !child.callee.computed && isNodeOfType(child.callee.property, "Identifier") && child.callee.property.name === "abort") {
29214
- const controllerKey = resolveExpressionKey(child.callee.object, context);
29215
- if (controllerKey) controllerKeys.add(controllerKey);
29216
- }
29217
- });
29218
- return controllerKeys;
29219
- };
29220
- const collectCleanupAssignedCancellationFlagKeys = (cleanup, cancellationFlagKeys, context) => {
29221
- const assignedFlagKeys = /* @__PURE__ */ new Set();
28590
+ const isCancellationCleanup = (cleanup, effectCallback) => {
28591
+ const cancellationFlagNames = collectEffectCancellationFlagNames(effectCallback);
28592
+ let found = false;
29222
28593
  walkAst(cleanup, (child) => {
29223
- if (isNodeOfType(child, "AssignmentExpression") && isNodeOfType(child.right, "Literal") && child.right.value === true) {
29224
- const assignedKey = resolveExpressionKey(child.left, context);
29225
- if (assignedKey && cancellationFlagKeys.has(assignedKey)) assignedFlagKeys.add(assignedKey);
29226
- }
29227
- });
29228
- return assignedFlagKeys;
29229
- };
29230
- const readCancellationCondition = (expression, cancellationFlagKey, context) => {
29231
- const unwrappedExpression = stripParenExpression(expression);
29232
- if (resolveExpressionKey(unwrappedExpression, context) === cancellationFlagKey) return true;
29233
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
29234
- const argumentResult = readCancellationCondition(unwrappedExpression.argument, cancellationFlagKey, context);
29235
- return argumentResult === null ? null : !argumentResult;
29236
- }
29237
- if (!isNodeOfType(unwrappedExpression, "BinaryExpression")) return null;
29238
- const leftResult = readCancellationCondition(unwrappedExpression.left, cancellationFlagKey, context);
29239
- const rightResult = readCancellationCondition(unwrappedExpression.right, cancellationFlagKey, context);
29240
- const leftBoolean = isNodeOfType(unwrappedExpression.left, "Literal") && typeof unwrappedExpression.left.value === "boolean" ? unwrappedExpression.left.value : null;
29241
- const rightBoolean = isNodeOfType(unwrappedExpression.right, "Literal") && typeof unwrappedExpression.right.value === "boolean" ? unwrappedExpression.right.value : null;
29242
- const comparedCancellationValue = leftResult !== null && rightBoolean !== null ? rightBoolean : rightResult !== null && leftBoolean !== null ? leftBoolean : null;
29243
- if (comparedCancellationValue === null) return null;
29244
- if (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==") return comparedCancellationValue;
29245
- if (unwrappedExpression.operator === "!==" || unwrappedExpression.operator === "!=") return !comparedCancellationValue;
29246
- return null;
29247
- };
29248
- const isCompletionSinkInsideCancellationGuard = (completionSink, cancellationFlagKey, context) => {
29249
- let currentNode = completionSink;
29250
- let parentNode = currentNode.parent;
29251
- while (parentNode) {
29252
- if (isFunctionLike$1(parentNode)) return false;
29253
- if (isNodeOfType(parentNode, "IfStatement")) {
29254
- const cancellationResult = readCancellationCondition(parentNode.test, cancellationFlagKey, context);
29255
- if (currentNode === parentNode.consequent && cancellationResult === false) return true;
29256
- if (currentNode === parentNode.alternate && cancellationResult === true) return true;
29257
- }
29258
- if (isNodeOfType(parentNode, "ConditionalExpression")) {
29259
- const cancellationResult = readCancellationCondition(parentNode.test, cancellationFlagKey, context);
29260
- if (currentNode === parentNode.consequent && cancellationResult === false) return true;
29261
- if (currentNode === parentNode.alternate && cancellationResult === true) return true;
29262
- }
29263
- if (isNodeOfType(parentNode, "LogicalExpression") && currentNode === parentNode.right) {
29264
- const cancellationResult = readCancellationCondition(parentNode.left, cancellationFlagKey, context);
29265
- if (parentNode.operator === "&&" && cancellationResult === false) return true;
29266
- if (parentNode.operator === "||" && cancellationResult === true) return true;
28594
+ if (found) return false;
28595
+ if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "MemberExpression") && isNodeOfType(child.callee.property, "Identifier") && child.callee.property.name === "abort") {
28596
+ found = true;
28597
+ return false;
29267
28598
  }
29268
- currentNode = parentNode;
29269
- parentNode = currentNode.parent;
29270
- }
29271
- return false;
29272
- };
29273
- const isCompletionSinkAfterCancellationEarlyExit = (completionSink, cancellationFlagKey, context) => {
29274
- let currentNode = completionSink;
29275
- let parentNode = currentNode.parent;
29276
- while (parentNode) {
29277
- if (isFunctionLike$1(parentNode)) return false;
29278
- if (isNodeOfType(parentNode, "BlockStatement")) {
29279
- const statementIndex = parentNode.body.findIndex((statement) => statement === currentNode);
29280
- if (statementIndex >= 0) for (const statement of parentNode.body.slice(0, statementIndex)) {
29281
- if (!isNodeOfType(statement, "IfStatement")) continue;
29282
- const cancellationResult = readCancellationCondition(statement.test, cancellationFlagKey, context);
29283
- if (cancellationResult === true && statementAlwaysExits(statement.consequent) || cancellationResult === false && statement.alternate && statementAlwaysExits(statement.alternate)) return true;
29284
- }
28599
+ if (isNodeOfType(child, "AssignmentExpression") && isNodeOfType(child.left, "Identifier") && cancellationFlagNames.has(child.left.name) && isNodeOfType(child.right, "Literal") && child.right.value === true) {
28600
+ found = true;
28601
+ return false;
29285
28602
  }
29286
- currentNode = parentNode;
29287
- parentNode = currentNode.parent;
29288
- }
29289
- return false;
29290
- };
29291
- const isCompletionSinkGuardedByCancellationFlag = (completionSink, cancellationFlagKey, context) => isCompletionSinkInsideCancellationGuard(completionSink, cancellationFlagKey, context) || isCompletionSinkAfterCancellationEarlyExit(completionSink, cancellationFlagKey, context);
29292
- const isDescendantOf = (node, ancestor) => {
29293
- let currentNode = node;
29294
- while (currentNode) {
29295
- if (currentNode === ancestor) return true;
29296
- currentNode = currentNode.parent ?? null;
29297
- }
29298
- return false;
29299
- };
29300
- const isPromiseContinuationForRequest = (functionNode, request) => {
29301
- const callNode = functionNode.parent;
29302
- if (!isNodeOfType(callNode, "CallExpression") || !callNode.arguments?.some((callArgument) => callArgument === functionNode) || !isNodeOfType(callNode.callee, "MemberExpression") || callNode.callee.computed || !isNodeOfType(callNode.callee.property, "Identifier") || !PROMISE_CONTINUATION_METHOD_NAMES.has(callNode.callee.property.name)) return false;
29303
- return isDescendantOf(request, callNode.callee.object);
29304
- };
29305
- const isAwaitedInFunction = (request, functionNode) => {
29306
- let currentNode = request.parent;
29307
- while (currentNode && currentNode !== functionNode) {
29308
- if (isNodeOfType(currentNode, "AwaitExpression")) return true;
29309
- currentNode = currentNode.parent ?? null;
29310
- }
29311
- return false;
29312
- };
29313
- const isCompletionSinkForRequest = (completionSink, request) => {
29314
- const requestFunction = findEnclosingFunction(request);
29315
- const completionSinkFunction = findEnclosingFunction(completionSink);
29316
- if (!requestFunction || !completionSinkFunction) return false;
29317
- if (requestFunction === completionSinkFunction) {
29318
- if (!isAwaitedInFunction(request, requestFunction)) return false;
29319
- const requestStart = getRangeStart(request);
29320
- const completionSinkStart = getRangeStart(completionSink);
29321
- return requestStart === null || completionSinkStart === null || completionSinkStart > requestStart;
29322
- }
29323
- return isPromiseContinuationForRequest(completionSinkFunction, request);
29324
- };
29325
- const requestHasGuardedCompletionSinks = (request, completionSinks, assignedCancellationFlagKeys, context) => {
29326
- const followingCompletionSinks = completionSinks.filter((completionSink) => isCompletionSinkForRequest(completionSink, request));
29327
- return followingCompletionSinks.length > 0 && followingCompletionSinks.every((completionSink) => [...assignedCancellationFlagKeys].some((cancellationFlagKey) => isCompletionSinkGuardedByCancellationFlag(completionSink, cancellationFlagKey, context)));
29328
- };
29329
- const allRequestsHaveCorrelatedCancellation = (requests, completionSinks, cleanup, effectCallback, context) => {
29330
- const abortedControllerKeys = collectCleanupAbortedControllerKeys(cleanup, context);
29331
- const assignedCancellationFlagKeys = collectCleanupAssignedCancellationFlagKeys(cleanup, collectEffectCancellationFlagKeys(effectCallback, context), context);
29332
- return requests.every((request) => {
29333
- const requestControllerKey = resolveRequestAbortControllerKey(request, context);
29334
- if (requestControllerKey && abortedControllerKeys.has(requestControllerKey)) return true;
29335
- return requestHasGuardedCompletionSinks(request, completionSinks, assignedCancellationFlagKeys, context);
29336
28603
  });
28604
+ return found;
29337
28605
  };
29338
28606
  const noFetchInEffect = defineRule({
29339
28607
  id: "no-fetch-in-effect",
@@ -29344,13 +28612,9 @@ const noFetchInEffect = defineRule({
29344
28612
  if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
29345
28613
  const callback = getEffectCallback(node);
29346
28614
  if (!callback) return;
29347
- const analysisFunctions = collectEffectAnalysisFunctions(callback, context);
29348
- const requests = collectNodesFromAnalysisFunctions(analysisFunctions, isNetworkRequest);
29349
- if (requests.length === 0) return;
28615
+ if (!containsNetworkRequestInEffect(callback)) return;
29350
28616
  const cleanup = findEffectCleanupFunction(callback);
29351
- if (cleanup) {
29352
- if (allRequestsHaveCorrelatedCancellation(requests, collectNodesFromAnalysisFunctions(analysisFunctions, isSetterCall), cleanup, callback, context)) return;
29353
- }
28617
+ if (cleanup && isCancellationCleanup(cleanup, callback)) return;
29354
28618
  context.report({
29355
28619
  node,
29356
28620
  message: "fetch() inside useEffect can race, double-fire, or leak. Use a data-fetching layer or Server Component instead."
@@ -29650,6 +28914,34 @@ const noGenericHandlerNames = defineRule({
29650
28914
  } })
29651
28915
  });
29652
28916
  //#endregion
28917
+ //#region src/plugin/utils/unwrap-react-hoc-function.ts
28918
+ /**
28919
+ * Resolves a `VariableDeclarator.init` (or any expression) to the inline
28920
+ * function expression it binds, seeing through chains of `memo` /
28921
+ * `forwardRef` / `React.memo` / `React.forwardRef` wrappers:
28922
+ *
28923
+ * `() => {}` → the arrow
28924
+ * `memo(function Foo() {})` → the named function expression
28925
+ * `React.memo(forwardRef(() => {}))` → the inner arrow
28926
+ * `memo(SomeIdentifier)` → `null` (no inline function)
28927
+ *
28928
+ * Component-shaped rules that previously gated on a direct function init
28929
+ * (via `isComponentAssignment`) use this so memo-wrapped components are
28930
+ * not silently skipped.
28931
+ */
28932
+ const unwrapReactHocFunction = (node) => {
28933
+ if (!node) return null;
28934
+ let current = stripParenExpression(node);
28935
+ while (isNodeOfType(current, "CallExpression")) {
28936
+ const calleeName = flattenCalleeName(current.callee);
28937
+ if (!calleeName || !REACT_HOC_NAMES.has(calleeName)) return null;
28938
+ const firstArgument = current.arguments[0];
28939
+ if (!firstArgument) return null;
28940
+ current = stripParenExpression(firstArgument);
28941
+ }
28942
+ return isInlineFunctionExpression(current) ? current : null;
28943
+ };
28944
+ //#endregion
29653
28945
  //#region src/plugin/rules/architecture/no-giant-component.ts
29654
28946
  const noGiantComponent = defineRule({
29655
28947
  id: "no-giant-component",
@@ -29902,405 +29194,6 @@ const noGrayOnColoredBackground = defineRule({
29902
29194
  } })
29903
29195
  });
29904
29196
  //#endregion
29905
- //#region src/plugin/utils/find-enclosing-jsx-opening-element.ts
29906
- const findEnclosingJsxOpeningElement = (node) => {
29907
- let cursor = node.parent;
29908
- while (cursor) {
29909
- if (isNodeOfType(cursor, "JSXElement")) return cursor.openingElement;
29910
- if (isNodeOfType(cursor, "JSXFragment")) return null;
29911
- cursor = cursor.parent ?? null;
29912
- }
29913
- return null;
29914
- };
29915
- //#endregion
29916
- //#region src/plugin/utils/has-client-render-evidence.ts
29917
- const hasClientRenderEvidence = (componentOrHookNode, fileHasUseClientDirective) => {
29918
- if (fileHasUseClientDirective) return true;
29919
- const displayName = componentOrHookDisplayNameForFunction(componentOrHookNode);
29920
- if (displayName && isReactHookName(displayName)) return true;
29921
- let callsHook = false;
29922
- walkAst((isFunctionLike$1(componentOrHookNode) ? componentOrHookNode.body : null) ?? componentOrHookNode, (child) => {
29923
- if (callsHook) return false;
29924
- if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && isReactHookName(child.callee.name)) {
29925
- callsHook = true;
29926
- return false;
29927
- }
29928
- });
29929
- return callsHook;
29930
- };
29931
- //#endregion
29932
- //#region src/plugin/utils/has-suppress-hydration-warning-attribute.ts
29933
- const hasSuppressHydrationWarningAttribute = (openingElement) => {
29934
- if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement")) return false;
29935
- for (const attr of openingElement.attributes ?? []) if (isNodeOfType(attr, "JSXAttribute") && isNodeOfType(attr.name, "JSXIdentifier") && attr.name.name === "suppressHydrationWarning") return true;
29936
- return false;
29937
- };
29938
- //#endregion
29939
- //#region src/plugin/utils/read-initial-state-boolean.ts
29940
- const readLogicalResult = (operator, leftResult, rightResult) => {
29941
- if (operator === "&&") {
29942
- if (leftResult === false || rightResult === false) return false;
29943
- if (leftResult === true && rightResult === true) return true;
29944
- return null;
29945
- }
29946
- if (leftResult === true || rightResult === true) return true;
29947
- if (leftResult === false && rightResult === false) return false;
29948
- return null;
29949
- };
29950
- const readIdentifierInitialStateBoolean = (identifier, scopes, visitedBindings, allowLazyInitializer) => {
29951
- if (!isNodeOfType(identifier, "Identifier")) return null;
29952
- if (identifier.name === "undefined" && scopes.isGlobalReference(identifier)) return false;
29953
- const binding = findVariableInitializer(identifier, identifier.name);
29954
- if (!binding || visitedBindings.has(binding.bindingIdentifier)) return null;
29955
- visitedBindings.add(binding.bindingIdentifier);
29956
- const declarator = findDeclaratorForBinding(binding.bindingIdentifier);
29957
- if (!declarator?.init || scopes.symbolFor(identifier)?.declarationNode !== declarator) return null;
29958
- const initializer = stripParenExpression(declarator.init);
29959
- if (isNodeOfType(declarator.id, "ArrayPattern") && declarator.id.elements?.[0] === binding.bindingIdentifier && isReactApiCall(initializer, "useState", scopes, { allowGlobalReactNamespace: true }) && isNodeOfType(initializer, "CallExpression")) {
29960
- const initialState = initializer.arguments?.[0];
29961
- if (!initialState) return false;
29962
- if (initialState.type === "SpreadElement") return null;
29963
- return readInitialStateBooleanInternal(initialState, scopes, visitedBindings, true);
29964
- }
29965
- const declaration = declarator.parent;
29966
- if (!isNodeOfType(declarator.id, "Identifier") || declarator.id !== binding.bindingIdentifier || !isNodeOfType(declaration, "VariableDeclaration") || declaration.kind !== "const") return null;
29967
- return readInitialStateBooleanInternal(initializer, scopes, visitedBindings, allowLazyInitializer);
29968
- };
29969
- const readInitialStateBooleanInternal = (expression, scopes, visitedBindings, allowLazyInitializer) => {
29970
- const unwrappedExpression = stripParenExpression(expression);
29971
- if (isNodeOfType(unwrappedExpression, "Literal")) return Boolean(unwrappedExpression.value);
29972
- if (isNodeOfType(unwrappedExpression, "Identifier")) return readIdentifierInitialStateBoolean(unwrappedExpression, scopes, visitedBindings, allowLazyInitializer);
29973
- if (allowLazyInitializer && (isNodeOfType(unwrappedExpression, "ArrowFunctionExpression") || isNodeOfType(unwrappedExpression, "FunctionExpression"))) {
29974
- if (unwrappedExpression.async || isNodeOfType(unwrappedExpression, "FunctionExpression") && unwrappedExpression.generator) return null;
29975
- if (!isNodeOfType(unwrappedExpression.body, "BlockStatement")) return readInitialStateBooleanInternal(unwrappedExpression.body, scopes, visitedBindings, false);
29976
- if (unwrappedExpression.body.body.length !== 1) return null;
29977
- const returnStatement = unwrappedExpression.body.body[0];
29978
- if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return null;
29979
- return readInitialStateBooleanInternal(returnStatement.argument, scopes, visitedBindings, false);
29980
- }
29981
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
29982
- const argumentResult = readInitialStateBooleanInternal(unwrappedExpression.argument, scopes, visitedBindings, false);
29983
- return argumentResult === null ? null : !argumentResult;
29984
- }
29985
- 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));
29986
- return null;
29987
- };
29988
- const readInitialStateBoolean = (expression, scopes) => readInitialStateBooleanInternal(expression, scopes, /* @__PURE__ */ new Set(), false);
29989
- //#endregion
29990
- //#region src/plugin/utils/is-after-client-only-early-return.ts
29991
- const isAfterClientOnlyEarlyReturn = (node, componentOrHookNode, scopes) => {
29992
- const body = isFunctionLike$1(componentOrHookNode) ? componentOrHookNode.body : null;
29993
- if (!isNodeOfType(body, "BlockStatement")) return false;
29994
- const ancestors = /* @__PURE__ */ new Set();
29995
- let currentNode = node;
29996
- while (currentNode) {
29997
- ancestors.add(currentNode);
29998
- currentNode = currentNode.parent ?? null;
29999
- }
30000
- for (const statement of body.body ?? []) {
30001
- if (ancestors.has(statement)) return false;
30002
- if (!isNodeOfType(statement, "IfStatement")) continue;
30003
- const initialConditionResult = readInitialStateBoolean(statement.test, scopes);
30004
- if (initialConditionResult === true && statementAlwaysExits(statement.consequent)) return true;
30005
- if (initialConditionResult === false && statement.alternate && statementAlwaysExits(statement.alternate)) return true;
30006
- }
30007
- return false;
30008
- };
30009
- //#endregion
30010
- //#region src/plugin/utils/is-gated-by-falsy-initial-state.ts
30011
- const isGatedByFalsyInitialState = (node, scopes) => {
30012
- let cursor = node;
30013
- let parent = node.parent;
30014
- while (parent) {
30015
- if (isNodeOfType(parent, "LogicalExpression") && parent.right === cursor && (parent.operator === "&&" && readInitialStateBoolean(parent.left, scopes) === false || parent.operator === "||" && readInitialStateBoolean(parent.left, scopes) === true)) return true;
30016
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === cursor && readInitialStateBoolean(parent.test, scopes) === false || parent.alternate === cursor && readInitialStateBoolean(parent.test, scopes) === true)) return true;
30017
- if (isNodeOfType(parent, "IfStatement") && (parent.consequent === cursor && readInitialStateBoolean(parent.test, scopes) === false || parent.alternate === cursor && readInitialStateBoolean(parent.test, scopes) === true)) return true;
30018
- cursor = parent;
30019
- parent = parent.parent ?? null;
30020
- }
30021
- return false;
30022
- };
30023
- //#endregion
30024
- //#region src/plugin/rules/performance/no-hydration-branch-on-browser-global.ts
30025
- const evaluateEquality = (operator, left, right) => {
30026
- if (operator === "===" || operator === "==") return left === right;
30027
- if (operator === "!==" || operator === "!=") return left !== right;
30028
- return null;
30029
- };
30030
- const readTypeofBrowserGlobal = (expression, context) => {
30031
- const unwrappedExpression = stripParenExpression(expression);
30032
- if (!isNodeOfType(unwrappedExpression, "UnaryExpression") || unwrappedExpression.operator !== "typeof") return null;
30033
- const argument = stripParenExpression(unwrappedExpression.argument);
30034
- if (isNodeOfType(argument, "Identifier")) return (argument.name === "window" || argument.name === "document") && context.scopes.isGlobalReference(argument) ? argument.name : null;
30035
- 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;
30036
- return argument.property.name;
30037
- };
30038
- const matchBrowserPredicate = (expression, context) => {
30039
- const unwrappedExpression = stripParenExpression(expression);
30040
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
30041
- const innerMatch = matchBrowserPredicate(unwrappedExpression.argument, context);
30042
- return innerMatch ? {
30043
- browserGlobalName: innerMatch.browserGlobalName,
30044
- clientResult: !innerMatch.clientResult,
30045
- serverResult: !innerMatch.serverResult
30046
- } : null;
30047
- }
30048
- if (!isNodeOfType(unwrappedExpression, "BinaryExpression")) return null;
30049
- const leftGlobalName = readTypeofBrowserGlobal(unwrappedExpression.left, context);
30050
- const rightGlobalName = readTypeofBrowserGlobal(unwrappedExpression.right, context);
30051
- const leftString = isNodeOfType(unwrappedExpression.left, "Literal") ? unwrappedExpression.left.value : null;
30052
- const rightString = isNodeOfType(unwrappedExpression.right, "Literal") ? unwrappedExpression.right.value : null;
30053
- const browserGlobalName = leftGlobalName && typeof rightString === "string" ? leftGlobalName : rightGlobalName && typeof leftString === "string" ? rightGlobalName : null;
30054
- const comparedType = leftGlobalName && typeof rightString === "string" ? rightString : rightGlobalName && typeof leftString === "string" ? leftString : null;
30055
- if (!browserGlobalName || !comparedType) return null;
30056
- const clientResult = evaluateEquality(unwrappedExpression.operator, "object", comparedType);
30057
- const serverResult = evaluateEquality(unwrappedExpression.operator, "undefined", comparedType);
30058
- if (clientResult === null || serverResult === null || clientResult === serverResult) return null;
30059
- return {
30060
- browserGlobalName,
30061
- clientResult,
30062
- serverResult
30063
- };
30064
- };
30065
- const readLogicalConditionResult = (operator, leftResult, rightResult) => {
30066
- if (operator === "&&") {
30067
- if (leftResult === false || rightResult === false) return false;
30068
- if (leftResult === true && rightResult === true) return true;
30069
- return null;
30070
- }
30071
- if (leftResult === true || rightResult === true) return true;
30072
- if (leftResult === false && rightResult === false) return false;
30073
- return null;
30074
- };
30075
- const readHydrationConditionResult = (expression, context, runtime) => {
30076
- const unwrappedExpression = stripParenExpression(expression);
30077
- const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
30078
- if (predicateMatch) return predicateMatch[`${runtime}Result`];
30079
- const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
30080
- if (staticResult !== null) return staticResult;
30081
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
30082
- const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
30083
- return argumentResult === null ? null : !argumentResult;
30084
- }
30085
- if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
30086
- return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
30087
- };
30088
- const matchHydrationCondition = (expression, context) => {
30089
- const unwrappedExpression = stripParenExpression(expression);
30090
- const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
30091
- if (predicateMatch) return {
30092
- predicateMatch,
30093
- predicateNode: unwrappedExpression
30094
- };
30095
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
30096
- if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
30097
- const leftMatch = matchHydrationCondition(unwrappedExpression.left, context);
30098
- const rightMatch = matchHydrationCondition(unwrappedExpression.right, context);
30099
- if (leftMatch && rightMatch) {
30100
- const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client");
30101
- const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server");
30102
- return clientResult !== null && serverResult !== null && clientResult !== serverResult ? leftMatch : null;
30103
- }
30104
- const nestedMatch = leftMatch ?? rightMatch;
30105
- if (!nestedMatch) return null;
30106
- const otherResult = readInitialStateBoolean(leftMatch ? unwrappedExpression.right : unwrappedExpression.left, context.scopes);
30107
- if (unwrappedExpression.operator === "&&" && otherResult === false || unwrappedExpression.operator === "||" && otherResult === true) return null;
30108
- return nestedMatch;
30109
- };
30110
- const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
30111
- const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
30112
- if (!leftNode || !rightNode) return leftNode === rightNode;
30113
- const left = stripParenExpression(leftNode);
30114
- const right = stripParenExpression(rightNode);
30115
- if (areExpressionsStructurallyEqual(left, right)) return true;
30116
- if (left.type !== right.type) return false;
30117
- if (isNodeOfType(left, "JSXText") && isNodeOfType(right, "JSXText")) return left.value === right.value;
30118
- if (isNodeOfType(left, "JSXExpressionContainer") && isNodeOfType(right, "JSXExpressionContainer")) {
30119
- if (!isAstNode(left.expression) || !isAstNode(right.expression)) return left.expression.type === right.expression.type;
30120
- return areRenderedBranchesEquivalent(left.expression, right.expression);
30121
- }
30122
- if (isNodeOfType(left, "JSXElement") && isNodeOfType(right, "JSXElement")) {
30123
- if (flattenJsxName$1(left.openingElement.name) !== flattenJsxName$1(right.openingElement.name)) return false;
30124
- if (!areNodeArraysEquivalent(left.openingElement.attributes, right.openingElement.attributes)) return false;
30125
- return areNodeArraysEquivalent(left.children, right.children);
30126
- }
30127
- if (isNodeOfType(left, "JSXFragment") && isNodeOfType(right, "JSXFragment")) return areNodeArraysEquivalent(left.children, right.children);
30128
- if (isNodeOfType(left, "JSXAttribute") && isNodeOfType(right, "JSXAttribute")) {
30129
- if (flattenJsxName$1(left.name) !== flattenJsxName$1(right.name)) return false;
30130
- return areRenderedBranchesEquivalent(left.value, right.value);
30131
- }
30132
- if (isNodeOfType(left, "JSXSpreadAttribute") && isNodeOfType(right, "JSXSpreadAttribute")) return areRenderedBranchesEquivalent(left.argument, right.argument);
30133
- if (isNodeOfType(left, "TemplateLiteral") && isNodeOfType(right, "TemplateLiteral")) {
30134
- if (left.quasis.length !== right.quasis.length) return false;
30135
- if (!left.quasis.every((quasi, index) => quasi.value.cooked === right.quasis[index]?.value.cooked && quasi.value.raw === right.quasis[index]?.value.raw)) return false;
30136
- return areNodeArraysEquivalent(left.expressions, right.expressions);
30137
- }
30138
- return false;
30139
- };
30140
- const isRenderedValue = (node) => {
30141
- const unwrappedNode = stripParenExpression(node);
30142
- if (isNodeOfType(unwrappedNode, "Literal")) return unwrappedNode.value !== null && unwrappedNode.value !== true && unwrappedNode.value !== false && unwrappedNode.value !== "";
30143
- if (isNodeOfType(unwrappedNode, "TemplateLiteral")) return unwrappedNode.expressions.length > 0 || unwrappedNode.quasis[0]?.value.cooked !== "";
30144
- return isNodeOfType(unwrappedNode, "JSXElement") || isNodeOfType(unwrappedNode, "JSXFragment");
30145
- };
30146
- const findRenderedValueInAndBranch = (node) => {
30147
- const unwrappedNode = stripParenExpression(node);
30148
- if (isRenderedValue(unwrappedNode)) return unwrappedNode;
30149
- if (!isNodeOfType(unwrappedNode, "LogicalExpression") || unwrappedNode.operator !== "&&") return null;
30150
- return findRenderedValueInAndBranch(unwrappedNode.right);
30151
- };
30152
- const findEnclosingJsxAttribute = (node) => {
30153
- let currentNode = node.parent;
30154
- while (currentNode) {
30155
- if (isNodeOfType(currentNode, "JSXAttribute")) return currentNode;
30156
- if (isNodeOfType(currentNode, "JSXElement") || isNodeOfType(currentNode, "JSXFragment") || isFunctionLike$1(currentNode)) return null;
30157
- currentNode = currentNode.parent;
30158
- }
30159
- return null;
30160
- };
30161
- const isInRenderedOutput = (node, componentOrHookNode, scopes) => {
30162
- let currentNode = node;
30163
- let parentNode = currentNode.parent;
30164
- while (parentNode) {
30165
- if (isNodeOfType(parentNode, "JSXExpressionContainer")) {
30166
- const attribute = findEnclosingJsxAttribute(parentNode);
30167
- return attribute ? !isEventHandlerAttribute(attribute) : true;
30168
- }
30169
- if (isNodeOfType(parentNode, "ReturnStatement")) {
30170
- if (findEnclosingFunction(parentNode) === componentOrHookNode) return true;
30171
- }
30172
- if (parentNode === componentOrHookNode) return isFunctionLike$1(componentOrHookNode) && !isNodeOfType(componentOrHookNode.body, "BlockStatement") && componentOrHookNode.body === currentNode;
30173
- if (isFunctionLike$1(parentNode) && !executesDuringRender(parentNode, scopes)) return false;
30174
- currentNode = parentNode;
30175
- parentNode = currentNode.parent;
30176
- }
30177
- return false;
30178
- };
30179
- const getReturnedValues = (statement) => {
30180
- if (!statement) return [];
30181
- if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? [statement.argument] : [];
30182
- if (isNodeOfType(statement, "IfStatement")) return [...getReturnedValues(statement.consequent), ...getReturnedValues(statement.alternate)];
30183
- if (!isNodeOfType(statement, "BlockStatement")) return [];
30184
- const returnedValues = [];
30185
- for (const childStatement of statement.body) {
30186
- returnedValues.push(...getReturnedValues(childStatement));
30187
- if (statementAlwaysExits(childStatement)) break;
30188
- }
30189
- return returnedValues;
30190
- };
30191
- const findFollowingReturnedValues = (ifStatement) => {
30192
- const parentNode = ifStatement.parent;
30193
- if (!isNodeOfType(parentNode, "BlockStatement")) return [];
30194
- const statementIndex = parentNode.body.findIndex((statement) => statement === ifStatement);
30195
- if (statementIndex < 0) return [];
30196
- const returnedValues = [];
30197
- for (const statement of parentNode.body.slice(statementIndex + 1)) {
30198
- returnedValues.push(...getReturnedValues(statement));
30199
- if (statementAlwaysExits(statement)) break;
30200
- }
30201
- return returnedValues;
30202
- };
30203
- const areConditionExpressionsEquivalent = (leftExpression, rightExpression) => {
30204
- const left = stripParenExpression(leftExpression);
30205
- const right = stripParenExpression(rightExpression);
30206
- if (areExpressionsStructurallyEqual(left, right)) return true;
30207
- if (left.type !== right.type) return false;
30208
- if (isNodeOfType(left, "UnaryExpression") && isNodeOfType(right, "UnaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.argument, right.argument);
30209
- if (isNodeOfType(left, "LogicalExpression") && isNodeOfType(right, "LogicalExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left) && areConditionExpressionsEquivalent(left.right, right.right);
30210
- if (isNodeOfType(left, "BinaryExpression") && isNodeOfType(right, "BinaryExpression")) return left.operator === right.operator && areConditionExpressionsEquivalent(left.left, right.left) && areConditionExpressionsEquivalent(left.right, right.right);
30211
- return false;
30212
- };
30213
- const areReturnTreesEquivalent = (leftStatement, rightStatement) => {
30214
- if (!leftStatement || !rightStatement) return leftStatement === rightStatement;
30215
- if (isNodeOfType(leftStatement, "ReturnStatement") && isNodeOfType(rightStatement, "ReturnStatement")) return areRenderedBranchesEquivalent(leftStatement.argument, rightStatement.argument);
30216
- if (isNodeOfType(leftStatement, "IfStatement") && isNodeOfType(rightStatement, "IfStatement")) return areConditionExpressionsEquivalent(leftStatement.test, rightStatement.test) && areReturnTreesEquivalent(leftStatement.consequent, rightStatement.consequent) && areReturnTreesEquivalent(leftStatement.alternate, rightStatement.alternate);
30217
- if (!isNodeOfType(leftStatement, "BlockStatement") || !isNodeOfType(rightStatement, "BlockStatement")) return false;
30218
- const leftReturningStatements = leftStatement.body.filter((statement) => getReturnedValues(statement).length > 0);
30219
- const rightReturningStatements = rightStatement.body.filter((statement) => getReturnedValues(statement).length > 0);
30220
- return leftReturningStatements.length === rightReturningStatements.length && leftReturningStatements.every((statement, index) => areReturnTreesEquivalent(statement, rightReturningStatements[index]));
30221
- };
30222
- const isStructuralRenderedValue = (node) => {
30223
- if (!node) return false;
30224
- const unwrappedNode = stripParenExpression(node);
30225
- return isNodeOfType(unwrappedNode, "JSXElement") || isNodeOfType(unwrappedNode, "JSXFragment");
30226
- };
30227
- const branchRootsSuppressSameElement = (leftBranch, rightBranch) => {
30228
- if (!rightBranch) return false;
30229
- const left = stripParenExpression(leftBranch);
30230
- const right = stripParenExpression(rightBranch);
30231
- return isNodeOfType(left, "JSXElement") && isNodeOfType(right, "JSXElement") && flattenJsxName$1(left.openingElement.name) === flattenJsxName$1(right.openingElement.name) && hasSuppressHydrationWarningAttribute(left.openingElement) && hasSuppressHydrationWarningAttribute(right.openingElement);
30232
- };
30233
- const noHydrationBranchOnBrowserGlobal = defineRule({
30234
- id: "no-hydration-branch-on-browser-global",
30235
- title: "Server and client render different branches",
30236
- severity: "error",
30237
- category: "Correctness",
30238
- requires: ["ssr"],
30239
- recommendation: "Render the same initial output on the server and client, then switch after mount or use useSyncExternalStore with a stable server snapshot.",
30240
- create: (context) => {
30241
- if (isTestlikeFilename(context.filename)) return {};
30242
- if (classifyReactNativeFileTarget(context) === "react-native") return {};
30243
- let fileHasUseClientDirective = false;
30244
- let fileIsEmailTemplate = false;
30245
- const reportedNodes = /* @__PURE__ */ new Set();
30246
- const reportHydrationBranch = (conditionNode, leftBranch, rightBranch, requiresRenderedContext) => {
30247
- const conditionMatch = matchHydrationCondition(conditionNode, context);
30248
- if (!conditionMatch) return;
30249
- const { predicateMatch, predicateNode } = conditionMatch;
30250
- if (reportedNodes.has(predicateNode)) return;
30251
- if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
30252
- const componentOrHookNode = findRenderPhaseComponentOrHook(predicateNode, context.scopes);
30253
- if (!componentOrHookNode) return;
30254
- if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
30255
- if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
30256
- if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
30257
- const attribute = findEnclosingJsxAttribute(predicateNode);
30258
- if (!attribute || isEventHandlerAttribute(attribute)) return;
30259
- }
30260
- if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
30261
- if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
30262
- const openingElement = findEnclosingJsxOpeningElement(predicateNode);
30263
- if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
30264
- if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
30265
- if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
30266
- reportedNodes.add(predicateNode);
30267
- context.report({
30268
- node: predicateNode,
30269
- message: `\`typeof ${predicateMatch.browserGlobalName}\` selects different rendered output on the server and during hydration. Render the same initial output, then switch after mount.`
30270
- });
30271
- };
30272
- return {
30273
- Program(node) {
30274
- fileHasUseClientDirective = hasDirective(node, "use client");
30275
- fileIsEmailTemplate = hasEmailTemplateImport(node);
30276
- },
30277
- ConditionalExpression(node) {
30278
- reportHydrationBranch(node.test, node.consequent, node.alternate, true);
30279
- },
30280
- LogicalExpression(node) {
30281
- if (node.operator !== "&&" && node.operator !== "||") return;
30282
- const renderedValue = node.operator === "&&" ? findRenderedValueInAndBranch(node.right) : isRenderedValue(node.right) ? node.right : null;
30283
- if (!renderedValue) return;
30284
- reportHydrationBranch(node, renderedValue, null, true);
30285
- },
30286
- IfStatement(node) {
30287
- if (node.alternate && areReturnTreesEquivalent(node.consequent, node.alternate)) return;
30288
- const consequentValues = getReturnedValues(node.consequent);
30289
- const alternateValues = node.alternate ? getReturnedValues(node.alternate) : findFollowingReturnedValues(node);
30290
- if (consequentValues.length === 0 || alternateValues.length === 0) return;
30291
- const componentOrHookNode = findRenderPhaseComponentOrHook(node.test, context.scopes);
30292
- if (!componentOrHookNode) return;
30293
- const enclosingFunction = findEnclosingFunction(node);
30294
- if (enclosingFunction !== componentOrHookNode && (!enclosingFunction || !isInRenderedOutput(enclosingFunction, componentOrHookNode, context.scopes))) return;
30295
- for (const consequentValue of consequentValues) for (const alternateValue of alternateValues) {
30296
- if (!isRenderedValue(consequentValue) && !isRenderedValue(alternateValue)) continue;
30297
- reportHydrationBranch(node.test, consequentValue, alternateValue, false);
30298
- }
30299
- }
30300
- };
30301
- }
30302
- });
30303
- //#endregion
30304
29197
  //#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
30305
29198
  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.";
30306
29199
  const noImgLazyWithHighFetchpriority = defineRule({
@@ -31146,6 +30039,111 @@ const noLegacyContextApi = defineRule({
31146
30039
  }
31147
30040
  });
31148
30041
  //#endregion
30042
+ //#region src/plugin/utils/executes-during-render.ts
30043
+ const SYNCHRONOUS_ITERATION_METHOD_NAMES = new Set([
30044
+ "map",
30045
+ "filter",
30046
+ "forEach",
30047
+ "flatMap",
30048
+ "reduce",
30049
+ "reduceRight",
30050
+ "some",
30051
+ "every",
30052
+ "find",
30053
+ "findIndex",
30054
+ "findLast",
30055
+ "findLastIndex",
30056
+ "sort",
30057
+ "toSorted"
30058
+ ]);
30059
+ const executesDuringRender = (functionNode) => {
30060
+ const parent = functionNode.parent;
30061
+ if (!isNodeOfType(parent, "CallExpression")) return false;
30062
+ if (parent.callee === functionNode) return true;
30063
+ if (isHookCall$2(parent, "useMemo") && parent.arguments?.[0] === functionNode) return true;
30064
+ 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;
30065
+ };
30066
+ //#endregion
30067
+ //#region src/plugin/utils/has-suppress-hydration-warning-attribute.ts
30068
+ const hasSuppressHydrationWarningAttribute = (openingElement) => {
30069
+ if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement")) return false;
30070
+ for (const attr of openingElement.attributes ?? []) if (isNodeOfType(attr, "JSXAttribute") && isNodeOfType(attr.name, "JSXIdentifier") && attr.name.name === "suppressHydrationWarning") return true;
30071
+ return false;
30072
+ };
30073
+ //#endregion
30074
+ //#region src/plugin/utils/find-declarator-for-binding.ts
30075
+ const findDeclaratorForBinding = (bindingIdentifier) => {
30076
+ let ancestor = bindingIdentifier.parent;
30077
+ while (ancestor) {
30078
+ if (isNodeOfType(ancestor, "VariableDeclarator")) return ancestor;
30079
+ if (isNodeOfType(ancestor, "FunctionDeclaration") || isNodeOfType(ancestor, "FunctionExpression") || isNodeOfType(ancestor, "ArrowFunctionExpression") || isNodeOfType(ancestor, "Program")) return null;
30080
+ ancestor = ancestor.parent ?? null;
30081
+ }
30082
+ return null;
30083
+ };
30084
+ //#endregion
30085
+ //#region src/plugin/utils/references-falsy-initial-state.ts
30086
+ const isFalsyLiteral = (node) => {
30087
+ if (!node) return true;
30088
+ if (isNodeOfType(node, "Literal")) return !node.value;
30089
+ return isNodeOfType(node, "Identifier") && node.name === "undefined";
30090
+ };
30091
+ const isFalsyInitialStateBinding = (identifier) => {
30092
+ if (!isNodeOfType(identifier, "Identifier")) return false;
30093
+ const binding = findVariableInitializer(identifier, identifier.name);
30094
+ if (!binding) return false;
30095
+ const declarator = findDeclaratorForBinding(binding.bindingIdentifier);
30096
+ if (!declarator?.init) return false;
30097
+ const init = stripParenExpression(declarator.init);
30098
+ if (!isHookCall$2(init, "useState") || !isNodeOfType(init, "CallExpression")) return false;
30099
+ if (!isNodeOfType(declarator.id, "ArrayPattern")) return false;
30100
+ if (declarator.id.elements?.[0] !== binding.bindingIdentifier) return false;
30101
+ return isFalsyLiteral(init.arguments?.[0]);
30102
+ };
30103
+ const referencesFalsyInitialState = (expression) => flattenLogicalAndChain(stripParenExpression(expression)).some((operand) => isFalsyInitialStateBinding(stripParenExpression(operand)));
30104
+ //#endregion
30105
+ //#region src/plugin/utils/is-gated-by-falsy-initial-state.ts
30106
+ const isGatedByFalsyInitialState = (node) => {
30107
+ let cursor = node;
30108
+ let parent = node.parent;
30109
+ while (parent) {
30110
+ if (isNodeOfType(parent, "LogicalExpression") && parent.operator === "&&" && parent.right === cursor && referencesFalsyInitialState(parent.left)) return true;
30111
+ if (isNodeOfType(parent, "ConditionalExpression") && parent.consequent === cursor && referencesFalsyInitialState(parent.test)) return true;
30112
+ if (isNodeOfType(parent, "IfStatement") && parent.consequent === cursor && referencesFalsyInitialState(parent.test)) return true;
30113
+ cursor = parent;
30114
+ parent = parent.parent ?? null;
30115
+ }
30116
+ return false;
30117
+ };
30118
+ //#endregion
30119
+ //#region src/plugin/utils/references-client-only-flag.ts
30120
+ const CLIENT_ONLY_FLAG_NAME_PATTERN = /^(?:is|has|did)?_?(?:client|mounted|hydrated|browser)(?:_?(?:side|ready|only))?$/i;
30121
+ const referencesClientOnlyFlag = (expression) => {
30122
+ const unwrapped = stripParenExpression(expression);
30123
+ if (isNodeOfType(unwrapped, "Identifier")) return CLIENT_ONLY_FLAG_NAME_PATTERN.test(unwrapped.name);
30124
+ if (isNodeOfType(unwrapped, "MemberExpression")) {
30125
+ const property = unwrapped.property;
30126
+ return isNodeOfType(property, "Identifier") && CLIENT_ONLY_FLAG_NAME_PATTERN.test(property.name);
30127
+ }
30128
+ if (isNodeOfType(unwrapped, "UnaryExpression") && unwrapped.operator === "!") return referencesClientOnlyFlag(unwrapped.argument);
30129
+ if (isNodeOfType(unwrapped, "LogicalExpression")) return referencesClientOnlyFlag(unwrapped.left) || referencesClientOnlyFlag(unwrapped.right);
30130
+ return false;
30131
+ };
30132
+ //#endregion
30133
+ //#region src/plugin/utils/is-inside-client-only-guard.ts
30134
+ const isInsideClientOnlyGuard = (node) => {
30135
+ let cursor = node;
30136
+ let parent = node.parent;
30137
+ while (parent) {
30138
+ if (isNodeOfType(parent, "LogicalExpression") && parent.operator === "&&" && parent.right === cursor && referencesClientOnlyFlag(parent.left)) return true;
30139
+ if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === cursor || parent.alternate === cursor) && referencesClientOnlyFlag(parent.test)) return true;
30140
+ if (isNodeOfType(parent, "IfStatement") && parent.consequent === cursor && referencesClientOnlyFlag(parent.test)) return true;
30141
+ cursor = parent;
30142
+ parent = parent.parent ?? null;
30143
+ }
30144
+ return false;
30145
+ };
30146
+ //#endregion
31149
30147
  //#region src/plugin/rules/performance/no-locale-format-in-render.ts
31150
30148
  const LOCALE_FORMAT_METHOD_NAMES = new Set([
31151
30149
  "toLocaleString",
@@ -31275,12 +30273,75 @@ const matchDateDefaultStringification = (node) => {
31275
30273
  }
31276
30274
  return null;
31277
30275
  };
30276
+ const findRenderPhaseComponentOrHook = (node) => {
30277
+ let functionNode = findEnclosingFunction(node);
30278
+ while (functionNode) {
30279
+ if (componentOrHookDisplayNameForFunction(functionNode)) return functionNode;
30280
+ if (!executesDuringRender(functionNode)) return null;
30281
+ functionNode = findEnclosingFunction(functionNode);
30282
+ }
30283
+ return null;
30284
+ };
30285
+ const hasClientRenderEvidence = (componentOrHookNode, fileHasUseClientDirective) => {
30286
+ if (fileHasUseClientDirective) return true;
30287
+ const displayName = componentOrHookDisplayNameForFunction(componentOrHookNode);
30288
+ if (displayName && isReactHookName(displayName)) return true;
30289
+ let callsHook = false;
30290
+ walkAst((isFunctionLike$1(componentOrHookNode) ? componentOrHookNode.body : null) ?? componentOrHookNode, (child) => {
30291
+ if (callsHook) return false;
30292
+ if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && isReactHookName(child.callee.name)) {
30293
+ callsHook = true;
30294
+ return false;
30295
+ }
30296
+ });
30297
+ return callsHook;
30298
+ };
30299
+ const isAfterClientOnlyEarlyReturn = (node, componentOrHookNode) => {
30300
+ const body = isFunctionLike$1(componentOrHookNode) ? componentOrHookNode.body : null;
30301
+ if (!isNodeOfType(body, "BlockStatement")) return false;
30302
+ const ancestors = /* @__PURE__ */ new Set();
30303
+ let cursor = node;
30304
+ while (cursor) {
30305
+ ancestors.add(cursor);
30306
+ cursor = cursor.parent ?? null;
30307
+ }
30308
+ for (const statement of body.body ?? []) {
30309
+ if (ancestors.has(statement)) return false;
30310
+ if (!isNodeOfType(statement, "IfStatement")) continue;
30311
+ if (!referencesClientOnlyFlag(statement.test) && !referencesFalsyInitialState(statement.test)) continue;
30312
+ let returnsEarly = false;
30313
+ walkAst(statement.consequent, (child) => {
30314
+ if (isFunctionLike$1(child)) return false;
30315
+ if (isNodeOfType(child, "ReturnStatement")) {
30316
+ returnsEarly = true;
30317
+ return false;
30318
+ }
30319
+ });
30320
+ if (returnsEarly) return true;
30321
+ }
30322
+ return false;
30323
+ };
30324
+ const findEnclosingJsxOpeningElement = (node) => {
30325
+ let cursor = node.parent;
30326
+ while (cursor) {
30327
+ if (isNodeOfType(cursor, "JSXElement")) return cursor.openingElement;
30328
+ if (isNodeOfType(cursor, "JSXFragment")) return null;
30329
+ cursor = cursor.parent ?? null;
30330
+ }
30331
+ return null;
30332
+ };
31278
30333
  const noLocaleFormatInRender = defineRule({
31279
30334
  id: "no-locale-format-in-render",
31280
30335
  title: "Locale/timezone formatting during render",
31281
30336
  severity: "warn",
31282
30337
  category: "Correctness",
31283
- requires: ["ssr"],
30338
+ disabledWhen: [
30339
+ "vite",
30340
+ "cra",
30341
+ "expo",
30342
+ "react-native",
30343
+ "unknown"
30344
+ ],
31284
30345
  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.",
31285
30346
  create: (context) => {
31286
30347
  if (isTestlikeFilename(context.filename)) return {};
@@ -31290,12 +30351,13 @@ const noLocaleFormatInRender = defineRule({
31290
30351
  const reportedNodes = /* @__PURE__ */ new Set();
31291
30352
  const reportIfRenderPhase = (match) => {
31292
30353
  if (reportedNodes.has(match.node)) return;
31293
- const componentOrHookNode = findRenderPhaseComponentOrHook(match.node, context.scopes);
30354
+ const componentOrHookNode = findRenderPhaseComponentOrHook(match.node);
31294
30355
  if (!componentOrHookNode) return;
31295
30356
  if (fileIsEmailTemplate) return;
31296
30357
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
31297
- if (isGatedByFalsyInitialState(match.node, context.scopes)) return;
31298
- if (isAfterClientOnlyEarlyReturn(match.node, componentOrHookNode, context.scopes)) return;
30358
+ if (isInsideClientOnlyGuard(match.node)) return;
30359
+ if (isGatedByFalsyInitialState(match.node)) return;
30360
+ if (isAfterClientOnlyEarlyReturn(match.node, componentOrHookNode)) return;
31299
30361
  if (hasSuppressHydrationWarningAttribute(findEnclosingJsxOpeningElement(match.node))) return;
31300
30362
  if (isGeneratedImageRenderContext(context, findEnclosingJsxOpeningElement(match.node)?.parent ?? match.node)) return;
31301
30363
  reportedNodes.add(match.node);
@@ -31322,7 +30384,7 @@ const noLocaleFormatInRender = defineRule({
31322
30384
  if (!isNodeOfType(expression, "CallExpression")) return;
31323
30385
  if (!isNodeOfType(expression.callee, "Identifier")) return;
31324
30386
  const helperName = expression.callee.name;
31325
- const componentOrHookNode = findRenderPhaseComponentOrHook(node, context.scopes);
30387
+ const componentOrHookNode = findRenderPhaseComponentOrHook(node);
31326
30388
  if (!componentOrHookNode) return;
31327
30389
  const helperNode = findVariableInitializer(expression.callee, helperName)?.initializer;
31328
30390
  if (!helperNode || !isFunctionLike$1(helperNode)) return;
@@ -31334,8 +30396,9 @@ const noLocaleFormatInRender = defineRule({
31334
30396
  if (!match || reportedNodes.has(match.node)) return;
31335
30397
  if (fileIsEmailTemplate) return;
31336
30398
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
31337
- if (isGatedByFalsyInitialState(node, context.scopes)) return;
31338
- if (isAfterClientOnlyEarlyReturn(node, componentOrHookNode, context.scopes)) return;
30399
+ if (isInsideClientOnlyGuard(node)) return;
30400
+ if (isGatedByFalsyInitialState(node)) return;
30401
+ if (isAfterClientOnlyEarlyReturn(node, componentOrHookNode)) return;
31339
30402
  if (hasSuppressHydrationWarningAttribute(findEnclosingJsxOpeningElement(node))) return;
31340
30403
  reportedNodes.add(match.node);
31341
30404
  context.report({
@@ -31437,6 +30500,9 @@ const noLongTransitionDuration = defineRule({
31437
30500
  const BOOLEAN_PROP_PREFIX_PATTERN = /^(?:is|has|should|can|show|hide|enable|disable|with)[A-Z]/;
31438
30501
  const isBooleanPrefixedPropName = (propName) => BOOLEAN_PROP_PREFIX_PATTERN.test(propName);
31439
30502
  //#endregion
30503
+ //#region src/plugin/utils/is-event-handler-attribute.ts
30504
+ const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && /^on[A-Z]/.test(node.name.name);
30505
+ //#endregion
31440
30506
  //#region src/plugin/rules/architecture/no-many-boolean-props.ts
31441
30507
  const IMPERATIVE_CALLBACK_PREFIX_PATTERN = /^(?:show|hide|enable|disable)[A-Z]/;
31442
30508
  const collectCallbackUsedNames = (componentBody, propsParam, scopes) => {
@@ -31568,7 +30634,7 @@ const noMatchMediaInStateInitializer = defineRule({
31568
30634
  title: "matchMedia in state initializer",
31569
30635
  severity: "warn",
31570
30636
  category: "Correctness",
31571
- requires: ["ssr"],
30637
+ disabledWhen: ["vite", "cra"],
31572
30638
  recommendation: "Prefer CSS media queries for layout, or subscribe with `useSyncExternalStore` and provide a stable server snapshot.",
31573
30639
  create: (context) => {
31574
30640
  if (isTestlikeFilename(context.filename)) return {};
@@ -32119,41 +31185,6 @@ const noMutableInDeps = defineRule({
32119
31185
  }
32120
31186
  });
32121
31187
  //#endregion
32122
- //#region src/plugin/utils/is-result-discarded-call.ts
32123
- const isResultDiscardedCall = (callExpression) => {
32124
- let node = callExpression;
32125
- let parent = node.parent;
32126
- while (parent) {
32127
- if (isNodeOfType(parent, "ExpressionStatement")) return true;
32128
- if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "void") return true;
32129
- if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === node) return true;
32130
- if (isNodeOfType(parent, "ChainExpression")) {
32131
- node = parent;
32132
- parent = node.parent;
32133
- continue;
32134
- }
32135
- if (isNodeOfType(parent, "LogicalExpression") && parent.right === node) {
32136
- node = parent;
32137
- parent = node.parent;
32138
- continue;
32139
- }
32140
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === node || parent.alternate === node)) {
32141
- node = parent;
32142
- parent = node.parent;
32143
- continue;
32144
- }
32145
- if (isNodeOfType(parent, "SequenceExpression")) {
32146
- const expressions = parent.expressions ?? [];
32147
- if (expressions[expressions.length - 1] !== node) return true;
32148
- node = parent;
32149
- parent = node.parent;
32150
- continue;
32151
- }
32152
- return false;
32153
- }
32154
- return false;
32155
- };
32156
- //#endregion
32157
31188
  //#region src/plugin/rules/state-and-effects/utils/lodash-mutator-call.ts
32158
31189
  const LODASH_MUTATOR_NAMES = new Set([
32159
31190
  "set",
@@ -33444,7 +32475,7 @@ const isUseRefIdentifier = (identifier) => {
33444
32475
  };
33445
32476
  const COMMAND_PROP_NAME_PATTERN = /^(fetch|load|refetch|dispatch|register|render)([A-Z_]|$)/;
33446
32477
  const SETTER_NAMED_PROP_PATTERN = /^set[A-Z]/;
33447
- const unwrapChainExpression$1 = (node) => isNodeOfType(node, "ChainExpression") ? node.expression : node;
32478
+ const unwrapChainExpression = (node) => isNodeOfType(node, "ChainExpression") ? node.expression : node;
33448
32479
  const FUNCTION_WRAPPER_HOOK_NAMES$1 = new Set([
33449
32480
  "useCallback",
33450
32481
  "useMemo",
@@ -33479,7 +32510,7 @@ const isDirectParentCallbackRef = (analysis, ref) => {
33479
32510
  return Boolean(ref.resolved?.defs.some((def) => {
33480
32511
  const node = def.node;
33481
32512
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
33482
- const initializer = unwrapChainExpression$1(node.init);
32513
+ const initializer = unwrapChainExpression(node.init);
33483
32514
  const wrappedFunction = getWrapperHookWrappedFunction(initializer);
33484
32515
  if (wrappedFunction) {
33485
32516
  if (wrappedFunction.async) return false;
@@ -33492,7 +32523,7 @@ const isDirectParentCallbackRef = (analysis, ref) => {
33492
32523
  const isWrapperHookCallbackRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
33493
32524
  const node = def.node;
33494
32525
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
33495
- return getWrapperHookWrappedFunction(unwrapChainExpression$1(node.init)) !== null;
32526
+ return getWrapperHookWrappedFunction(unwrapChainExpression(node.init)) !== null;
33496
32527
  }));
33497
32528
  const isHandlerBagArgument = (analysis, argument) => {
33498
32529
  if (!isNodeOfType(argument, "ObjectExpression")) return false;
@@ -33514,7 +32545,7 @@ const HOOK_NAME_PATTERN$1 = /^use[A-Z0-9]/;
33514
32545
  const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
33515
32546
  const node = def.node;
33516
32547
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
33517
- const init = unwrapChainExpression$1(node.init);
32548
+ const init = unwrapChainExpression(node.init);
33518
32549
  if (!isNodeOfType(init, "CallExpression")) return false;
33519
32550
  const callee = init.callee;
33520
32551
  if (!isNodeOfType(callee, "Identifier") || !HOOK_NAME_PATTERN$1.test(callee.name)) return false;
@@ -33558,7 +32589,7 @@ const noPassDataToParent = defineRule({
33558
32589
  if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
33559
32590
  if (isRefCall(analysis, ref)) continue;
33560
32591
  if (!isSynchronous(ref.identifier, effectFn)) continue;
33561
- const calleeNode = unwrapChainExpression$1(callExpr.callee);
32592
+ const calleeNode = unwrapChainExpression(callExpr.callee);
33562
32593
  const identifier = ref.identifier;
33563
32594
  if (calleeNode === identifier) {
33564
32595
  if (!isDirectParentCallbackRef(analysis, ref)) continue;
@@ -34407,11 +33438,7 @@ const noReactChildren = defineRule({
34407
33438
  //#endregion
34408
33439
  //#region src/plugin/rules/architecture/utils/create-deprecated-react-import-rule.ts
34409
33440
  const createDeprecatedReactImportRule = ({ source, messages, handleExtraSource }) => ({ create: (context) => {
34410
- const namespaceImportSpecifiers = /* @__PURE__ */ new Set();
34411
- const resolvesToNamespaceImport = (identifier) => {
34412
- const symbol = resolveConstIdentifierAlias(identifier, context.scopes);
34413
- return Boolean(symbol && namespaceImportSpecifiers.has(symbol.declarationNode));
34414
- };
33441
+ const namespaceBindings = /* @__PURE__ */ new Set();
34415
33442
  return {
34416
33443
  ImportDeclaration(node) {
34417
33444
  const sourceValue = node.source?.value;
@@ -34431,15 +33458,18 @@ const createDeprecatedReactImportRule = ({ source, messages, handleExtraSource }
34431
33458
  });
34432
33459
  continue;
34433
33460
  }
34434
- if (isNodeOfType(specifier, "ImportDefaultSpecifier") || isNodeOfType(specifier, "ImportNamespaceSpecifier")) namespaceImportSpecifiers.add(specifier);
33461
+ if (isNodeOfType(specifier, "ImportDefaultSpecifier") || isNodeOfType(specifier, "ImportNamespaceSpecifier")) {
33462
+ const localName = specifier.local?.name;
33463
+ if (localName) namespaceBindings.add(localName);
33464
+ }
34435
33465
  }
34436
33466
  },
34437
33467
  MemberExpression(node) {
34438
- if (namespaceImportSpecifiers.size === 0) return;
33468
+ if (namespaceBindings.size === 0) return;
34439
33469
  if (node.computed) return;
34440
33470
  const receiver = stripParenExpression(node.object);
34441
33471
  if (!isNodeOfType(receiver, "Identifier")) return;
34442
- if (!resolvesToNamespaceImport(receiver)) return;
33472
+ if (!namespaceBindings.has(receiver.name)) return;
34443
33473
  if (!isNodeOfType(node.property, "Identifier")) return;
34444
33474
  const message = messages.get(node.property.name);
34445
33475
  if (message) context.report({
@@ -34504,9 +33534,15 @@ const noReactDomDeprecatedApis = defineRule({
34504
33534
  });
34505
33535
  //#endregion
34506
33536
  //#region src/plugin/rules/architecture/no-react19-deprecated-apis.ts
33537
+ const REACT_19_DEPRECATED_MESSAGES = new Map([["forwardRef", "forwardRef is dead weight in React 19, since ref is a normal prop now, so drop it & pass ref straight through."]]);
33538
+ const isVendoredShadcnUiFilename = (rawFilename) => {
33539
+ if (!rawFilename) return false;
33540
+ const filename = rawFilename.replaceAll("\\", "/");
33541
+ return (filename.startsWith("/") ? filename : `/${filename}`).includes("/components/ui/");
33542
+ };
34507
33543
  const deprecatedReactImportRule = createDeprecatedReactImportRule({
34508
33544
  source: "react",
34509
- messages: new Map([["createFactory", "React 19 removed createFactory. Use JSX or createElement instead."]])
33545
+ messages: REACT_19_DEPRECATED_MESSAGES
34510
33546
  });
34511
33547
  const buildOncePerApiContext = (context) => {
34512
33548
  const reportedMessages = /* @__PURE__ */ new Set();
@@ -34536,8 +33572,11 @@ const noReact19DeprecatedApis = defineRule({
34536
33572
  requires: ["react:19"],
34537
33573
  tags: ["test-noise", "migration-hint"],
34538
33574
  severity: "warn",
34539
- recommendation: "Replace removed React APIs with their supported React 19 alternatives. Only runs on React 19+ projects.",
34540
- create: (context) => deprecatedReactImportRule.create(buildOncePerApiContext(context))
33575
+ recommendation: "Pass `ref` as a normal prop on function components, since `forwardRef` isn't needed in React 19. Only runs on React 19+ projects.",
33576
+ create: (context) => {
33577
+ if (isVendoredShadcnUiFilename(context.filename)) return {};
33578
+ return deprecatedReactImportRule.create(buildOncePerApiContext(context));
33579
+ }
34541
33580
  });
34542
33581
  //#endregion
34543
33582
  //#region src/plugin/constants/aria-element-roles.ts
@@ -36870,140 +35909,6 @@ const noUnescapedEntities = defineRule({
36870
35909
  } })
36871
35910
  });
36872
35911
  //#endregion
36873
- //#region src/plugin/rules/performance/no-unguarded-browser-global-in-render-or-hook-init.ts
36874
- const BROWSER_GLOBAL_NAMES = new Set([
36875
- "window",
36876
- "document",
36877
- "localStorage",
36878
- "sessionStorage",
36879
- "navigator",
36880
- "matchMedia"
36881
- ]);
36882
- const getTypeofBrowserGlobalName = (expression, context) => {
36883
- const unwrappedExpression = stripParenExpression(expression);
36884
- if (!isNodeOfType(unwrappedExpression, "UnaryExpression") || unwrappedExpression.operator !== "typeof") return null;
36885
- const argument = stripParenExpression(unwrappedExpression.argument);
36886
- if (isNodeOfType(argument, "Identifier")) return BROWSER_GLOBAL_NAMES.has(argument.name) && context.scopes.isGlobalReference(argument) ? argument.name : null;
36887
- 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;
36888
- return argument.property.name;
36889
- };
36890
- const browserGuardCoversGlobal = (guardName, browserGlobalName) => guardName === browserGlobalName || guardName === "window" || guardName === "document";
36891
- const mergeAvailability = (leftAvailability, rightAvailability) => {
36892
- if (leftAvailability === null) return rightAvailability;
36893
- if (rightAvailability === null) return leftAvailability;
36894
- return leftAvailability === rightAvailability ? leftAvailability : null;
36895
- };
36896
- const readAvailabilityWhenPredicate = (expression, browserGlobalName, context, predicateResult) => {
36897
- const unwrappedExpression = stripParenExpression(expression);
36898
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return readAvailabilityWhenPredicate(unwrappedExpression.argument, browserGlobalName, context, !predicateResult);
36899
- if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
36900
- if (unwrappedExpression.operator === "&&" && predicateResult) return mergeAvailability(readAvailabilityWhenPredicate(unwrappedExpression.left, browserGlobalName, context, true), readAvailabilityWhenPredicate(unwrappedExpression.right, browserGlobalName, context, true));
36901
- if (unwrappedExpression.operator === "||" && !predicateResult) return mergeAvailability(readAvailabilityWhenPredicate(unwrappedExpression.left, browserGlobalName, context, false), readAvailabilityWhenPredicate(unwrappedExpression.right, browserGlobalName, context, false));
36902
- return null;
36903
- }
36904
- if (!isNodeOfType(unwrappedExpression, "BinaryExpression")) return null;
36905
- const leftTypeofName = getTypeofBrowserGlobalName(unwrappedExpression.left, context);
36906
- const rightTypeofName = getTypeofBrowserGlobalName(unwrappedExpression.right, context);
36907
- const leftComparedType = isNodeOfType(unwrappedExpression.left, "Literal") && typeof unwrappedExpression.left.value === "string" ? unwrappedExpression.left.value : null;
36908
- const rightComparedType = isNodeOfType(unwrappedExpression.right, "Literal") && typeof unwrappedExpression.right.value === "string" ? unwrappedExpression.right.value : null;
36909
- const guardName = leftTypeofName && rightComparedType ? leftTypeofName : rightTypeofName && leftComparedType ? rightTypeofName : null;
36910
- const comparedType = leftTypeofName && rightComparedType ? rightComparedType : rightTypeofName && leftComparedType ? leftComparedType : null;
36911
- if (!guardName || !browserGuardCoversGlobal(guardName, browserGlobalName)) return null;
36912
- if (!comparedType) return null;
36913
- const isEquality = unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==";
36914
- const isInequality = unwrappedExpression.operator === "!==" || unwrappedExpression.operator === "!=";
36915
- if (!isEquality && !isInequality) return null;
36916
- const browserType = guardName === "matchMedia" ? "function" : "object";
36917
- const browserResult = isEquality ? browserType === comparedType : browserType !== comparedType;
36918
- if (browserResult === (isEquality ? comparedType === "undefined" : comparedType !== "undefined")) return null;
36919
- return predicateResult === browserResult;
36920
- };
36921
- const isInsideAvailabilityGuard = (node, browserGlobalName, context) => {
36922
- let currentNode = node;
36923
- let parentNode = currentNode.parent;
36924
- while (parentNode) {
36925
- if (isFunctionLike$1(parentNode) && !executesDuringRender(parentNode, context.scopes)) break;
36926
- if (isNodeOfType(parentNode, "LogicalExpression") && (parentNode.operator === "&&" || parentNode.operator === "||") && parentNode.right === currentNode && readAvailabilityWhenPredicate(parentNode.left, browserGlobalName, context, parentNode.operator === "&&") === true) return true;
36927
- if (isNodeOfType(parentNode, "ConditionalExpression")) {
36928
- if (parentNode.consequent === currentNode && readAvailabilityWhenPredicate(parentNode.test, browserGlobalName, context, true) === true || parentNode.alternate === currentNode && readAvailabilityWhenPredicate(parentNode.test, browserGlobalName, context, false) === true) return true;
36929
- }
36930
- if (isNodeOfType(parentNode, "IfStatement")) {
36931
- if (parentNode.consequent === currentNode && readAvailabilityWhenPredicate(parentNode.test, browserGlobalName, context, true) === true || parentNode.alternate === currentNode && readAvailabilityWhenPredicate(parentNode.test, browserGlobalName, context, false) === true) return true;
36932
- }
36933
- currentNode = parentNode;
36934
- parentNode = currentNode.parent;
36935
- }
36936
- return false;
36937
- };
36938
- const isAfterAvailabilityEarlyExit = (node, componentOrHookNode, browserGlobalName, context) => {
36939
- const enclosingFunction = findEnclosingFunction(node);
36940
- if (!enclosingFunction || enclosingFunction !== componentOrHookNode && !executesDuringRender(enclosingFunction, context.scopes) || !isFunctionLike$1(enclosingFunction) || !isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
36941
- let currentNode = node;
36942
- while (currentNode !== enclosingFunction) {
36943
- const parentNode = currentNode.parent;
36944
- if (!parentNode) return false;
36945
- if (isNodeOfType(parentNode, "BlockStatement")) for (const statement of parentNode.body) {
36946
- if (statement === currentNode) break;
36947
- if (!isNodeOfType(statement, "IfStatement")) continue;
36948
- if (readAvailabilityWhenPredicate(statement.test, browserGlobalName, context, false) === true && statementAlwaysExits(statement.consequent)) return true;
36949
- if (readAvailabilityWhenPredicate(statement.test, browserGlobalName, context, true) === true && statement.alternate && statementAlwaysExits(statement.alternate)) return true;
36950
- }
36951
- currentNode = parentNode;
36952
- }
36953
- return false;
36954
- };
36955
- const isTypeofProbe = (node) => {
36956
- const expressionRoot = findTransparentExpressionRoot(node);
36957
- const parentNode = expressionRoot.parent;
36958
- return isNodeOfType(parentNode, "UnaryExpression") && parentNode.operator === "typeof" && parentNode.argument === expressionRoot;
36959
- };
36960
- const noUnguardedBrowserGlobalInRenderOrHookInit = defineRule({
36961
- id: "no-unguarded-browser-global-in-render-or-hook-init",
36962
- title: "Browser global read during server render",
36963
- severity: "error",
36964
- category: "Correctness",
36965
- requires: ["ssr"],
36966
- 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.",
36967
- create: (context) => {
36968
- if (isTestlikeFilename(context.filename)) return {};
36969
- if (classifyReactNativeFileTarget(context) === "react-native") return {};
36970
- let fileIsEmailTemplate = false;
36971
- const reportedNodes = /* @__PURE__ */ new Set();
36972
- const reportBrowserRead = (node, browserGlobalName) => {
36973
- if (reportedNodes.has(node) || isTypeofProbe(node)) return;
36974
- const componentOrHookNode = findRenderPhaseComponentOrHook(node, context.scopes);
36975
- if (!componentOrHookNode) return;
36976
- if (fileIsEmailTemplate) return;
36977
- if (isGeneratedImageRenderContext(context, findEnclosingJsxOpeningElement(node) ?? node)) return;
36978
- if (isGatedByFalsyInitialState(node, context.scopes)) return;
36979
- if (isAfterClientOnlyEarlyReturn(node, componentOrHookNode, context.scopes)) return;
36980
- if (isInsideAvailabilityGuard(node, browserGlobalName, context)) return;
36981
- if (isAfterAvailabilityEarlyExit(node, componentOrHookNode, browserGlobalName, context)) return;
36982
- reportedNodes.add(node);
36983
- context.report({
36984
- node,
36985
- 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.`
36986
- });
36987
- };
36988
- return {
36989
- Program(node) {
36990
- fileIsEmailTemplate = hasEmailTemplateImport(node);
36991
- },
36992
- Identifier(node) {
36993
- if (!BROWSER_GLOBAL_NAMES.has(node.name)) return;
36994
- if (!context.scopes.isGlobalReference(node)) return;
36995
- reportBrowserRead(node, node.name);
36996
- },
36997
- MemberExpression(node) {
36998
- if (node.computed) return;
36999
- const objectNode = stripParenExpression(node.object);
37000
- if (!isNodeOfType(objectNode, "Identifier") || objectNode.name !== "globalThis" || !context.scopes.isGlobalReference(objectNode) || !isNodeOfType(node.property, "Identifier") || !BROWSER_GLOBAL_NAMES.has(node.property.name)) return;
37001
- reportBrowserRead(node, node.property.name);
37002
- }
37003
- };
37004
- }
37005
- });
37006
- //#endregion
37007
35912
  //#region src/plugin/constants/dom-aria-properties.ts
37008
35913
  const ARIA_PROPERTY_NAMES = new Set([
37009
35914
  "activedescendant",
@@ -40846,123 +39751,6 @@ const preferUseEffectEvent = defineRule({
40846
39751
  }
40847
39752
  });
40848
39753
  //#endregion
40849
- //#region src/plugin/rules/state-and-effects/utils/is-cleanup-return.ts
40850
- const ITERATOR_CALLBACK_METHOD_NAMES = new Set([
40851
- "each",
40852
- "every",
40853
- "filter",
40854
- "find",
40855
- "findIndex",
40856
- "findLast",
40857
- "findLastIndex",
40858
- "flatMap",
40859
- "forEach",
40860
- "map",
40861
- "reduce",
40862
- "reduceRight",
40863
- "some",
40864
- "sort",
40865
- "toSorted"
40866
- ]);
40867
- const STATIC_ITERATOR_CALLBACK_METHOD_NAMES = new Set([
40868
- "from",
40869
- "fromAsync",
40870
- "groupBy"
40871
- ]);
40872
- const unwrapChainExpression = (node) => isNodeOfType(node, "ChainExpression") ? node.expression : node;
40873
- const isNullLiteral = (node) => isNodeOfType(node, "Literal") && node.value === null;
40874
- const isListenerRemovalViaNullHandler = (callNode) => {
40875
- if (!isNodeOfType(callNode, "CallExpression")) return false;
40876
- const callee = unwrapChainExpression(callNode.callee);
40877
- return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "on" && isNullLiteral(callNode.arguments?.[1]);
40878
- };
40879
- const REFERENCE_BASED_REMOVAL_METHOD_NAMES = new Set([
40880
- "off",
40881
- "removeEventListener",
40882
- "removeListener"
40883
- ]);
40884
- const isNoOpInlineHandlerRemoval = (callNode, methodName) => {
40885
- if (!REFERENCE_BASED_REMOVAL_METHOD_NAMES.has(methodName)) return false;
40886
- const handlerArgument = callNode.arguments?.[1];
40887
- return isNodeOfType(handlerArgument, "ArrowFunctionExpression") || isNodeOfType(handlerArgument, "FunctionExpression");
40888
- };
40889
- const isReleaseLikeCall = (node, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
40890
- const callNode = unwrapChainExpression(node);
40891
- if (!isNodeOfType(callNode, "CallExpression")) return false;
40892
- if (isListenerRemovalViaNullHandler(callNode)) return true;
40893
- const callee = unwrapChainExpression(callNode.callee);
40894
- if (isNodeOfType(callee, "Identifier")) {
40895
- if (TIMER_CLEANUP_CALLEE_NAMES.has(callee.name)) return true;
40896
- if (CLEANUP_LIKE_RELEASE_CALLEE_NAMES.has(callee.name)) return true;
40897
- if (knownCleanupFunctionNames.has(callee.name)) return true;
40898
- return false;
40899
- }
40900
- if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) {
40901
- if (isNoOpInlineHandlerRemoval(callNode, callee.property.name)) return false;
40902
- if (BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(callee.property.name) && isNodeOfType(callee.object, "Identifier") && knownBoundSubscriptionNames.has(callee.object.name)) return true;
40903
- return GLOBAL_RELEASE_METHOD_NAMES.has(callee.property.name);
40904
- }
40905
- return false;
40906
- };
40907
- 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);
40908
- const isIteratorCallbackArgument = (node) => {
40909
- const parentNode = node.parent;
40910
- if (!isNodeOfType(parentNode, "CallExpression")) return false;
40911
- if (!parentNode.arguments?.some((argument) => argument === node)) return false;
40912
- const callee = unwrapChainExpression(parentNode.callee);
40913
- if (parentNode.arguments[1] === node && isStaticIteratorCallbackCallee(callee)) return true;
40914
- return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && ITERATOR_CALLBACK_METHOD_NAMES.has(callee.property.name);
40915
- };
40916
- const containsReleaseLikeCall = (node, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
40917
- let didFindRelease = false;
40918
- walkAst(node, (child) => {
40919
- if (didFindRelease) return false;
40920
- if (child !== node && isFunctionLike$1(child) && !isIteratorCallbackArgument(child)) return false;
40921
- if (isReleaseLikeCall(child, knownCleanupFunctionNames, knownBoundSubscriptionNames)) {
40922
- didFindRelease = true;
40923
- return false;
40924
- }
40925
- });
40926
- return didFindRelease;
40927
- };
40928
- const isCleanupFunctionLike = (node, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
40929
- if (!isFunctionLike$1(node)) return false;
40930
- return containsReleaseLikeCall(node.body, knownCleanupFunctionNames, knownBoundSubscriptionNames);
40931
- };
40932
- const isProvablyNoOpCleanupFunction = (node) => {
40933
- if (!isFunctionLike$1(node)) return false;
40934
- let sawNoOpRemoval = false;
40935
- let sawOtherCall = false;
40936
- walkAst(node.body, (child) => {
40937
- if (sawOtherCall) return false;
40938
- if (isFunctionLike$1(child)) return false;
40939
- const callNode = unwrapChainExpression(child);
40940
- if (!isNodeOfType(callNode, "CallExpression")) return;
40941
- const callee = unwrapChainExpression(callNode.callee);
40942
- if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && isNoOpInlineHandlerRemoval(callNode, callee.property.name)) {
40943
- sawNoOpRemoval = true;
40944
- return;
40945
- }
40946
- sawOtherCall = true;
40947
- });
40948
- return sawNoOpRemoval && !sawOtherCall;
40949
- };
40950
- const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSubscriptionNames, options = {}) => {
40951
- if (!returnedValue) return false;
40952
- const unwrappedValue = unwrapChainExpression(returnedValue);
40953
- if (isNodeOfType(unwrappedValue, "Literal") && unwrappedValue.value === null) return false;
40954
- if (isNodeOfType(unwrappedValue, "Identifier")) {
40955
- if (unwrappedValue.name === "undefined") return false;
40956
- if (knownCleanupFunctionNames.has(unwrappedValue.name)) return true;
40957
- return options.allowOpaqueReturn === true && !knownBoundSubscriptionNames.has(unwrappedValue.name);
40958
- }
40959
- if (isCleanupReturningSubscribeLikeCallExpression(unwrappedValue)) return true;
40960
- if (isProvablyNoOpCleanupFunction(unwrappedValue)) return false;
40961
- if (options.allowOpaqueReturn === true && !isSubscribeLikeCallExpression(unwrappedValue)) return true;
40962
- if (isCleanupFunctionLike(unwrappedValue, knownCleanupFunctionNames, knownBoundSubscriptionNames)) return true;
40963
- return false;
40964
- };
40965
- //#endregion
40966
39754
  //#region src/plugin/rules/state-and-effects/prefer-use-sync-external-store.ts
40967
39755
  const findUseEffectsInComponent = (componentBody) => {
40968
39756
  const effectCalls = [];
@@ -42431,7 +41219,7 @@ const renderingHydrationMismatchTime = defineRule({
42431
41219
  title: "Time or random value in JSX",
42432
41220
  severity: "warn",
42433
41221
  category: "Correctness",
42434
- requires: ["ssr"],
41222
+ disabledWhen: ["vite", "cra"],
42435
41223
  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",
42436
41224
  create: (context) => {
42437
41225
  const isTestlikeFile = isTestlikeFilename(context.filename);
@@ -42445,7 +41233,8 @@ const renderingHydrationMismatchTime = defineRule({
42445
41233
  const matched = NONDETERMINISTIC_RENDER_PATTERNS.find((pattern) => pattern.matches(node.expression));
42446
41234
  if (matched) {
42447
41235
  if (hasSuppressHydrationWarningAttribute(findOpeningElementOfChild(node))) return;
42448
- if (isGatedByFalsyInitialState(node, context.scopes)) return;
41236
+ if (isInsideClientOnlyGuard(node)) return;
41237
+ if (isGatedByFalsyInitialState(node)) return;
42449
41238
  if (isInsideMotionTransitionAttribute(node)) return;
42450
41239
  context.report({
42451
41240
  node,
@@ -42454,10 +41243,11 @@ const renderingHydrationMismatchTime = defineRule({
42454
41243
  return;
42455
41244
  }
42456
41245
  walkAst(node.expression, (child) => {
42457
- if (isFunctionLike$1(child) && !executesDuringRender(child, context.scopes)) return false;
41246
+ if (isFunctionLike$1(child) && !executesDuringRender(child)) return false;
42458
41247
  for (const pattern of NONDETERMINISTIC_RENDER_PATTERNS) if (pattern.matches(child)) {
42459
41248
  if (hasSuppressHydrationWarningAttribute(findOpeningElementOfChild(node))) return;
42460
- if (isGatedByFalsyInitialState(child, context.scopes)) return;
41249
+ if (isInsideClientOnlyGuard(child)) return;
41250
+ if (isGatedByFalsyInitialState(child)) return;
42461
41251
  if (isInsideMotionTransitionAttribute(child)) return;
42462
41252
  if (pattern.display === "new Date()" && isYearOnlyDateRead(child)) return;
42463
41253
  context.report({
@@ -55794,18 +54584,6 @@ const reactDoctorRules = [
55794
54584
  category: "Accessibility"
55795
54585
  }
55796
54586
  },
55797
- {
55798
- key: "react-doctor/no-hydration-branch-on-browser-global",
55799
- id: "no-hydration-branch-on-browser-global",
55800
- source: "react-doctor",
55801
- originallyExternal: false,
55802
- rule: {
55803
- ...noHydrationBranchOnBrowserGlobal,
55804
- framework: "global",
55805
- category: "Bugs",
55806
- requires: [...new Set(["react", ...noHydrationBranchOnBrowserGlobal.requires ?? []])]
55807
- }
55808
- },
55809
54587
  {
55810
54588
  key: "react-doctor/no-img-lazy-with-high-fetchpriority",
55811
54589
  id: "no-img-lazy-with-high-fetchpriority",
@@ -56569,18 +55347,6 @@ const reactDoctorRules = [
56569
55347
  requires: [...new Set(["react", ...noUnescapedEntities.requires ?? []])]
56570
55348
  }
56571
55349
  },
56572
- {
56573
- key: "react-doctor/no-unguarded-browser-global-in-render-or-hook-init",
56574
- id: "no-unguarded-browser-global-in-render-or-hook-init",
56575
- source: "react-doctor",
56576
- originallyExternal: false,
56577
- rule: {
56578
- ...noUnguardedBrowserGlobalInRenderOrHookInit,
56579
- framework: "global",
56580
- category: "Bugs",
56581
- requires: [...new Set(["react", ...noUnguardedBrowserGlobalInRenderOrHookInit.requires ?? []])]
56582
- }
56583
- },
56584
55350
  {
56585
55351
  key: "react-doctor/no-unknown-property",
56586
55352
  id: "no-unknown-property",
@@ -58913,12 +57679,10 @@ const CROSS_FILE_RULE_IDS = new Set([
58913
57679
  "nextjs-no-use-search-params-without-suspense",
58914
57680
  "no-dynamic-import-path",
58915
57681
  "no-full-lodash-import",
58916
- "no-hydration-branch-on-browser-global",
58917
57682
  "no-indeterminate-attribute",
58918
57683
  "no-locale-format-in-render",
58919
57684
  "no-match-media-in-state-initializer",
58920
57685
  "no-mutating-reducer-state",
58921
- "no-unguarded-browser-global-in-render-or-hook-init",
58922
57686
  "prefer-dynamic-import",
58923
57687
  "rendering-hydration-mismatch-time",
58924
57688
  "rn-no-legacy-shadow-styles",
@@ -59052,12 +57816,10 @@ const CROSS_FILE_DEPENDENCY_COLLECTORS = new Map([
59052
57816
  ["nextjs-no-use-search-params-without-suspense", collectNextjsSearchParamsDependencies],
59053
57817
  ["no-dynamic-import-path", collectNearestManifestDependencies],
59054
57818
  ["no-full-lodash-import", collectNearestManifestDependencies],
59055
- ["no-hydration-branch-on-browser-global", collectNearestManifestDependencies],
59056
57819
  ["no-indeterminate-attribute", collectNearestManifestDependencies],
59057
57820
  ["no-locale-format-in-render", collectNearestManifestDependencies],
59058
57821
  ["no-match-media-in-state-initializer", collectNearestManifestDependencies],
59059
57822
  ["no-mutating-reducer-state", collectMutatingReducerDependencies],
59060
- ["no-unguarded-browser-global-in-render-or-hook-init", collectNearestManifestDependencies],
59061
57823
  ["prefer-dynamic-import", collectNearestManifestDependencies],
59062
57824
  ["rendering-hydration-mismatch-time", collectNearestManifestDependencies],
59063
57825
  ["rn-no-legacy-shadow-styles", collectLegacyArchDependencies],