oxlint-plugin-react-doctor 0.7.9-dev.2979a9b → 0.7.9-dev.44d3ad8

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 +46 -0
  2. package/dist/index.js +1485 -401
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -836,21 +836,130 @@ const getImportedName = (importSpecifier) => {
836
836
  if (isNodeOfType(imported, "Literal") && typeof imported.value === "string") return imported.value;
837
837
  };
838
838
  //#endregion
839
- //#region src/plugin/utils/get-callee-name.ts
840
- const getCalleeName$2 = (node) => {
841
- if (!isNodeOfType(node, "CallExpression") && !isNodeOfType(node, "NewExpression")) return null;
842
- if (isNodeOfType(node.callee, "Identifier")) return node.callee.name;
843
- if (isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier")) return node.callee.property.name;
839
+ //#region src/plugin/utils/get-static-property-key-name.ts
840
+ const getStaticPropertyKeyName = (node, options = {}) => {
841
+ if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition") && !isNodeOfType(node, "MemberExpression")) return null;
842
+ const key = isNodeOfType(node, "MemberExpression") ? node.property : node.key;
843
+ if (node.computed) {
844
+ if (options.allowComputedString && isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value;
845
+ if (options.allowComputedString && isNodeOfType(key, "TemplateLiteral") && key.expressions.length === 0) return key.quasis[0]?.value.cooked ?? key.quasis[0]?.value.raw ?? null;
846
+ return null;
847
+ }
848
+ if (isNodeOfType(key, "Identifier")) return key.name;
849
+ if (isNodeOfType(key, "Literal")) {
850
+ if (typeof key.value === "string") return key.value;
851
+ if (options.stringifyNonStringLiterals) return String(key.value);
852
+ }
844
853
  return null;
845
854
  };
846
855
  //#endregion
847
- //#region src/plugin/utils/is-hook-call.ts
848
- const isHookCall$2 = (node, hookName) => {
856
+ //#region src/plugin/utils/get-static-property-name.ts
857
+ const getStaticPropertyName = (memberExpression) => {
858
+ const property = memberExpression.property;
859
+ if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
860
+ if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
861
+ if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
862
+ return null;
863
+ };
864
+ //#endregion
865
+ //#region src/plugin/utils/strip-paren-expression.ts
866
+ const TRANSPARENT_EXPRESSION_WRAPPER_TYPES = new Set([
867
+ "ParenthesizedExpression",
868
+ "TSAsExpression",
869
+ "TSSatisfiesExpression",
870
+ "TSTypeAssertion",
871
+ "TSNonNullExpression",
872
+ "TSInstantiationExpression",
873
+ "ChainExpression"
874
+ ]);
875
+ const stripParenExpression = (node) => {
876
+ let current = node;
877
+ while (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(current.type) && "expression" in current && current.expression) current = current.expression;
878
+ return current;
879
+ };
880
+ //#endregion
881
+ //#region src/plugin/utils/resolve-const-identifier-alias.ts
882
+ const resolveConstIdentifierAlias = (identifier, scopes, allowPatternBinding = false) => {
883
+ if (!isNodeOfType(identifier, "Identifier") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
884
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
885
+ let symbol = scopes.symbolFor(identifier);
886
+ while (symbol?.kind === "const") {
887
+ if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return null;
888
+ if (symbol.declarationNode.id !== symbol.bindingIdentifier) return allowPatternBinding ? symbol : null;
889
+ visitedSymbolIds.add(symbol.id);
890
+ const initializer = stripParenExpression(symbol.initializer);
891
+ if (!isNodeOfType(initializer, "Identifier")) return symbol;
892
+ symbol = scopes.symbolFor(initializer);
893
+ }
894
+ return symbol;
895
+ };
896
+ //#endregion
897
+ //#region src/plugin/utils/is-react-api-call.ts
898
+ const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? apiNames === apiName : apiNames.has(apiName);
899
+ const isImportedFromReact = (symbol) => {
900
+ if (symbol.kind !== "import") return false;
901
+ const importDeclaration = symbol.declarationNode.parent;
902
+ return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(importDeclaration.source.value));
903
+ };
904
+ const isNamedReactApiImport = (identifier, apiNames, scopes, resolveAliases) => {
905
+ if (!isNodeOfType(identifier, "Identifier")) return false;
906
+ const symbol = resolveAliases ? resolveConstIdentifierAlias(identifier, scopes) : scopes.symbolFor(identifier);
907
+ if (!symbol || !isImportedFromReact(symbol)) return false;
908
+ const importedName = getImportedName(symbol.declarationNode);
909
+ return Boolean(importedName && includesApiName(apiNames, importedName));
910
+ };
911
+ const isReactNamespaceImport = (identifier, scopes) => {
912
+ const symbol = resolveConstIdentifierAlias(identifier, scopes);
913
+ if (!symbol || !isImportedFromReact(symbol)) return false;
914
+ return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
915
+ };
916
+ const isReactNamespaceReceiver$1 = (receiver, scopes, options) => {
917
+ if (!isNodeOfType(receiver, "Identifier")) return false;
918
+ if (isReactNamespaceImport(receiver, scopes)) return true;
919
+ return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
920
+ };
921
+ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) => {
922
+ const symbol = scopes.symbolFor(identifier);
923
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
924
+ const pattern = symbol.declarationNode.id;
925
+ if (!isNodeOfType(pattern, "ObjectPattern")) return false;
926
+ for (const property of pattern.properties) {
927
+ if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
928
+ const propertyName = getStaticPropertyKeyName(property);
929
+ return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver$1(stripParenExpression(symbol.initializer), scopes, options));
930
+ }
931
+ return false;
932
+ };
933
+ const isReactApiCall = (node, apiNames, scopes, options = {}) => {
849
934
  if (!isNodeOfType(node, "CallExpression")) return false;
850
- const calleeName = getCalleeName$2(node);
851
- if (!calleeName) return false;
852
- return typeof hookName === "string" ? calleeName === hookName : hookName.has(calleeName);
935
+ return isReactApiCallee(node.callee, apiNames, scopes, options, /* @__PURE__ */ new Set());
853
936
  };
937
+ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds) => {
938
+ const callee = stripParenExpression(rawCallee);
939
+ if (options.resolveConditionalAliases && isNodeOfType(callee, "ConditionalExpression")) return isReactApiCallee(callee.consequent, apiNames, scopes, options, new Set(visitedSymbolIds)) && isReactApiCallee(callee.alternate, apiNames, scopes, options, new Set(visitedSymbolIds));
940
+ if (isNodeOfType(callee, "Identifier")) {
941
+ if (isNamedReactApiImport(callee, apiNames, scopes, Boolean(options.resolveNamedAliases))) return true;
942
+ if (options.resolveNamedAliases && isDestructuredReactApiBinding(callee, apiNames, scopes, options)) return true;
943
+ if (options.resolveConditionalAliases) {
944
+ const symbol = scopes.symbolFor(callee);
945
+ if (symbol?.kind === "const" && symbol.initializer && !visitedSymbolIds.has(symbol.id)) {
946
+ visitedSymbolIds.add(symbol.id);
947
+ return isReactApiCallee(symbol.initializer, apiNames, scopes, options, visitedSymbolIds);
948
+ }
949
+ }
950
+ return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
951
+ }
952
+ if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
953
+ return isReactNamespaceReceiver$1(stripParenExpression(callee.object), scopes, options);
954
+ };
955
+ //#endregion
956
+ //#region src/plugin/utils/is-react-hook-call.ts
957
+ const isReactHookCall = (node, hookNames, scopes) => isReactApiCall(node, hookNames, scopes, {
958
+ allowGlobalReactNamespace: true,
959
+ allowUnboundBareCalls: true,
960
+ resolveConditionalAliases: true,
961
+ resolveNamedAliases: true
962
+ });
854
963
  //#endregion
855
964
  //#region src/plugin/utils/is-ast-node.ts
856
965
  const isAstNode = (value) => value !== null && typeof value === "object" && "type" in value && typeof value.type === "string";
@@ -921,12 +1030,12 @@ const collectChildComponentNames = (element, into) => {
921
1030
  into.add(name);
922
1031
  });
923
1032
  };
924
- const countEffectHookCalls = (body) => {
1033
+ const countEffectHookCalls = (body, scopes) => {
925
1034
  if (!body) return 0;
926
1035
  let count = 0;
927
1036
  walkAst(body, (child) => {
928
1037
  if (!isNodeOfType(child, "CallExpression")) return;
929
- if (isHookCall$2(child, EFFECT_HOOK_NAMES$1)) count++;
1038
+ if (isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) count++;
930
1039
  });
931
1040
  return count;
932
1041
  };
@@ -952,11 +1061,11 @@ const getComponentEffectIndex = (programRoot) => {
952
1061
  componentEffectIndexCache.set(programRoot, index);
953
1062
  return index;
954
1063
  };
955
- const getSameFileComponentEffectCount = (programRoot, componentName) => {
1064
+ const getSameFileComponentEffectCount = (programRoot, componentName, scopes) => {
956
1065
  const index = getComponentEffectIndex(programRoot);
957
1066
  const cachedCount = index.effectCountByName.get(componentName);
958
1067
  if (cachedCount !== void 0) return cachedCount;
959
- const count = countEffectHookCalls(index.bodyByName.get(componentName) ?? null);
1068
+ const count = countEffectHookCalls(index.bodyByName.get(componentName) ?? null, scopes);
960
1069
  index.effectCountByName.set(componentName, count);
961
1070
  return count;
962
1071
  };
@@ -1016,7 +1125,7 @@ const activityWrapsEffectHeavySubtree = defineRule({
1016
1125
  let totalEffects = 0;
1017
1126
  const effectfulChildren = [];
1018
1127
  for (const componentName of childComponentNames) {
1019
- const effectCount = getSameFileComponentEffectCount(programRoot, componentName);
1128
+ const effectCount = getSameFileComponentEffectCount(programRoot, componentName, context.scopes);
1020
1129
  if (effectCount === 0) continue;
1021
1130
  totalEffects += effectCount;
1022
1131
  effectfulChildren.push(`<${componentName}>`);
@@ -1207,6 +1316,14 @@ const findVariableInitializer = (referenceNode, bindingName) => {
1207
1316
  return best;
1208
1317
  };
1209
1318
  //#endregion
1319
+ //#region src/plugin/utils/get-callee-name.ts
1320
+ const getCalleeName$1 = (node) => {
1321
+ if (!isNodeOfType(node, "CallExpression") && !isNodeOfType(node, "NewExpression")) return null;
1322
+ if (isNodeOfType(node.callee, "Identifier")) return node.callee.name;
1323
+ if (isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier")) return node.callee.property.name;
1324
+ return null;
1325
+ };
1326
+ //#endregion
1210
1327
  //#region src/plugin/utils/is-function-like.ts
1211
1328
  /**
1212
1329
  * Type-guard for the three "function-like" ESTree node shapes:
@@ -1220,37 +1337,6 @@ const findVariableInitializer = (referenceNode, bindingName) => {
1220
1337
  */
1221
1338
  const isFunctionLike$1 = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")));
1222
1339
  //#endregion
1223
- //#region src/plugin/utils/strip-paren-expression.ts
1224
- const TRANSPARENT_EXPRESSION_WRAPPER_TYPES = new Set([
1225
- "ParenthesizedExpression",
1226
- "TSAsExpression",
1227
- "TSSatisfiesExpression",
1228
- "TSTypeAssertion",
1229
- "TSNonNullExpression",
1230
- "TSInstantiationExpression",
1231
- "ChainExpression"
1232
- ]);
1233
- const stripParenExpression = (node) => {
1234
- let current = node;
1235
- while (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(current.type) && "expression" in current && current.expression) current = current.expression;
1236
- return current;
1237
- };
1238
- //#endregion
1239
- //#region src/plugin/utils/resolve-const-identifier-alias.ts
1240
- const resolveConstIdentifierAlias = (identifier, scopes) => {
1241
- if (!isNodeOfType(identifier, "Identifier") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
1242
- const visitedSymbolIds = /* @__PURE__ */ new Set();
1243
- let symbol = scopes.symbolFor(identifier);
1244
- while (symbol?.kind === "const") {
1245
- if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
1246
- visitedSymbolIds.add(symbol.id);
1247
- const initializer = stripParenExpression(symbol.initializer);
1248
- if (!isNodeOfType(initializer, "Identifier")) return symbol;
1249
- symbol = scopes.symbolFor(initializer);
1250
- }
1251
- return symbol;
1252
- };
1253
- //#endregion
1254
1340
  //#region src/plugin/utils/resolve-exact-local-function.ts
1255
1341
  const resolveExactLocalFunction = (expression, scopes) => {
1256
1342
  const unwrappedExpression = stripParenExpression(expression);
@@ -1296,9 +1382,17 @@ const getRootIdentifier$1 = (node, options) => {
1296
1382
  //#region src/plugin/utils/get-root-identifier-name.ts
1297
1383
  const getRootIdentifierName = (node, options) => getRootIdentifier$1(node, options)?.name ?? null;
1298
1384
  //#endregion
1385
+ //#region src/plugin/utils/is-hook-call.ts
1386
+ const isHookCall$2 = (node, hookName) => {
1387
+ if (!isNodeOfType(node, "CallExpression")) return false;
1388
+ const calleeName = getCalleeName$1(node);
1389
+ if (!calleeName) return false;
1390
+ return typeof hookName === "string" ? calleeName === hookName : hookName.has(calleeName);
1391
+ };
1392
+ //#endregion
1299
1393
  //#region src/plugin/rules/state-and-effects/advanced-event-handler-refs.ts
1300
- const STABLE_HANDLER_HOOK_NAMES = new Set([
1301
- "useCallback",
1394
+ const REACT_STABLE_HANDLER_HOOK_NAMES = new Set(["useCallback", "useEffectEvent"]);
1395
+ const CUSTOM_STABLE_HANDLER_HOOK_NAMES = new Set([
1302
1396
  "useEffectEvent",
1303
1397
  "useEvent",
1304
1398
  "useEventCallback",
@@ -1307,21 +1401,21 @@ const STABLE_HANDLER_HOOK_NAMES = new Set([
1307
1401
  ]);
1308
1402
  const THROTTLED_HANDLER_HOOK_PATTERN = /^use\w*(?:Throttle|Debounce)/i;
1309
1403
  const isThrottledHandlerHookCall = (callNode) => {
1310
- const calleeName = getCalleeName$2(callNode);
1404
+ const calleeName = getCalleeName$1(callNode);
1311
1405
  return calleeName !== null && THROTTLED_HANDLER_HOOK_PATTERN.test(calleeName);
1312
1406
  };
1313
- const isEmptyDepsUseMemoCall = (callNode) => {
1314
- if (!isHookCall$2(callNode, "useMemo")) return false;
1407
+ const isEmptyDepsUseMemoCall = (callNode, scopes) => {
1408
+ if (!isReactHookCall(callNode, "useMemo", scopes)) return false;
1315
1409
  const memoDepsNode = callNode.arguments?.[1];
1316
1410
  return isNodeOfType(memoDepsNode, "ArrayExpression") && (memoDepsNode.elements?.length ?? 0) === 0;
1317
1411
  };
1318
- const isStableHandlerInitializer = (initializer) => {
1319
- if (isNodeOfType(initializer, "CallExpression")) return isHookCall$2(initializer, STABLE_HANDLER_HOOK_NAMES) || isEmptyDepsUseMemoCall(initializer) || isThrottledHandlerHookCall(initializer);
1412
+ const isStableHandlerInitializer = (initializer, scopes) => {
1413
+ if (isNodeOfType(initializer, "CallExpression")) return isReactHookCall(initializer, REACT_STABLE_HANDLER_HOOK_NAMES, scopes) || isHookCall$2(initializer, CUSTOM_STABLE_HANDLER_HOOK_NAMES) || isEmptyDepsUseMemoCall(initializer, scopes) || isThrottledHandlerHookCall(initializer);
1320
1414
  return isNodeOfType(initializer, "MemberExpression") && isNodeOfType(initializer.property, "Identifier") && initializer.property.name === "current";
1321
1415
  };
1322
- const isStableRefReceiverDep = (referenceNode, receiverDepName) => {
1416
+ const isStableRefReceiverDep = (referenceNode, receiverDepName, scopes) => {
1323
1417
  const receiverBinding = findVariableInitializer(referenceNode, receiverDepName);
1324
- return Boolean(receiverBinding?.initializer && isHookCall$2(receiverBinding.initializer, "useRef"));
1418
+ return Boolean(receiverBinding?.initializer && isReactHookCall(receiverBinding.initializer, "useRef", scopes));
1325
1419
  };
1326
1420
  const advancedEventHandlerRefs = defineRule({
1327
1421
  id: "advanced-event-handler-refs",
@@ -1331,7 +1425,7 @@ const advancedEventHandlerRefs = defineRule({
1331
1425
  category: "Performance",
1332
1426
  recommendation: "Store the handler in a ref and have the listener read `handlerRef.current()`. The subscription stays put while the latest handler still runs.",
1333
1427
  create: (context) => ({ CallExpression(node) {
1334
- if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
1428
+ if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes)) return;
1335
1429
  if ((node.arguments?.length ?? 0) < 2) return;
1336
1430
  const callback = getEffectCallback(node);
1337
1431
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
@@ -1355,8 +1449,8 @@ const advancedEventHandlerRefs = defineRule({
1355
1449
  });
1356
1450
  if (!registeredHandlerName) return;
1357
1451
  const handlerBinding = findVariableInitializer(node, registeredHandlerName);
1358
- if (handlerBinding?.initializer && isStableHandlerInitializer(handlerBinding.initializer)) return;
1359
- if ([...depIdentifierNames].some((depName) => depName !== registeredHandlerName && subscriptionReceiverNames.has(depName) && !isStableRefReceiverDep(node, depName))) return;
1452
+ if (handlerBinding?.initializer && isStableHandlerInitializer(handlerBinding.initializer, context.scopes)) return;
1453
+ if ([...depIdentifierNames].some((depName) => depName !== registeredHandlerName && subscriptionReceiverNames.has(depName) && !isStableRefReceiverDep(node, depName, context.scopes))) return;
1360
1454
  context.report({
1361
1455
  node,
1362
1456
  message: `useEffect re-adds the "${registeredHandlerName}" listener every time the handler changes.`
@@ -1843,15 +1937,6 @@ const flattenJsxName$1 = (node) => {
1843
1937
  return null;
1844
1938
  };
1845
1939
  //#endregion
1846
- //#region src/plugin/utils/get-static-property-name.ts
1847
- const getStaticPropertyName = (memberExpression) => {
1848
- const property = memberExpression.property;
1849
- if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
1850
- if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
1851
- if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
1852
- return null;
1853
- };
1854
- //#endregion
1855
1940
  //#region src/plugin/utils/is-generated-image-renderer-call.ts
1856
1941
  const GENERATED_IMAGE_RENDERER_MODULES = [
1857
1942
  "next/og",
@@ -4859,23 +4944,6 @@ const containsDirectAwait = (node) => {
4859
4944
  return foundAwait;
4860
4945
  };
4861
4946
  //#endregion
4862
- //#region src/plugin/utils/get-static-property-key-name.ts
4863
- const getStaticPropertyKeyName = (node, options = {}) => {
4864
- if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition") && !isNodeOfType(node, "MemberExpression")) return null;
4865
- const key = isNodeOfType(node, "MemberExpression") ? node.property : node.key;
4866
- if (node.computed) {
4867
- if (options.allowComputedString && isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value;
4868
- if (options.allowComputedString && isNodeOfType(key, "TemplateLiteral") && key.expressions.length === 0) return key.quasis[0]?.value.cooked ?? key.quasis[0]?.value.raw ?? null;
4869
- return null;
4870
- }
4871
- if (isNodeOfType(key, "Identifier")) return key.name;
4872
- if (isNodeOfType(key, "Literal")) {
4873
- if (typeof key.value === "string") return key.value;
4874
- if (options.stringifyNonStringLiterals) return String(key.value);
4875
- }
4876
- return null;
4877
- };
4878
- //#endregion
4879
4947
  //#region src/plugin/utils/get-destructured-binding-property-name.ts
4880
4948
  const getDestructuredBindingPropertyName = (bindingIdentifier) => {
4881
4949
  let bindingNode = bindingIdentifier;
@@ -8940,65 +9008,6 @@ const hasVisibleBindingNamed = (node, bindingName, scopes) => {
8940
9008
  }
8941
9009
  };
8942
9010
  //#endregion
8943
- //#region src/plugin/utils/is-react-api-call.ts
8944
- const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? apiNames === apiName : apiNames.has(apiName);
8945
- const isImportedFromReact = (symbol) => {
8946
- if (symbol.kind !== "import") return false;
8947
- const importDeclaration = symbol.declarationNode.parent;
8948
- return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(importDeclaration.source.value));
8949
- };
8950
- const isNamedReactApiImport = (identifier, apiNames, scopes, resolveAliases) => {
8951
- if (!isNodeOfType(identifier, "Identifier")) return false;
8952
- const symbol = resolveAliases ? resolveConstIdentifierAlias(identifier, scopes) : scopes.symbolFor(identifier);
8953
- if (!symbol || !isImportedFromReact(symbol)) return false;
8954
- const importedName = getImportedName(symbol.declarationNode);
8955
- return Boolean(importedName && includesApiName(apiNames, importedName));
8956
- };
8957
- const isReactNamespaceImport = (identifier, scopes) => {
8958
- const symbol = resolveConstIdentifierAlias(identifier, scopes);
8959
- if (!symbol || !isImportedFromReact(symbol)) return false;
8960
- return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
8961
- };
8962
- const isReactNamespaceReceiver$1 = (receiver, scopes, options) => {
8963
- if (!isNodeOfType(receiver, "Identifier")) return false;
8964
- if (isReactNamespaceImport(receiver, scopes)) return true;
8965
- return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
8966
- };
8967
- const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) => {
8968
- const symbol = scopes.symbolFor(identifier);
8969
- if (!symbol || symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
8970
- const pattern = symbol.declarationNode.id;
8971
- if (!isNodeOfType(pattern, "ObjectPattern")) return false;
8972
- for (const property of pattern.properties) {
8973
- if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
8974
- const propertyName = getStaticPropertyKeyName(property);
8975
- return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver$1(stripParenExpression(symbol.initializer), scopes, options));
8976
- }
8977
- return false;
8978
- };
8979
- const isReactApiCall = (node, apiNames, scopes, options = {}) => {
8980
- if (!isNodeOfType(node, "CallExpression")) return false;
8981
- return isReactApiCallee(node.callee, apiNames, scopes, options, /* @__PURE__ */ new Set());
8982
- };
8983
- const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds) => {
8984
- const callee = stripParenExpression(rawCallee);
8985
- if (options.resolveConditionalAliases && isNodeOfType(callee, "ConditionalExpression")) return isReactApiCallee(callee.consequent, apiNames, scopes, options, new Set(visitedSymbolIds)) && isReactApiCallee(callee.alternate, apiNames, scopes, options, new Set(visitedSymbolIds));
8986
- if (isNodeOfType(callee, "Identifier")) {
8987
- if (isNamedReactApiImport(callee, apiNames, scopes, Boolean(options.resolveNamedAliases))) return true;
8988
- if (options.resolveNamedAliases && isDestructuredReactApiBinding(callee, apiNames, scopes, options)) return true;
8989
- if (options.resolveConditionalAliases) {
8990
- const symbol = scopes.symbolFor(callee);
8991
- if (symbol?.kind === "const" && symbol.initializer && !visitedSymbolIds.has(symbol.id)) {
8992
- visitedSymbolIds.add(symbol.id);
8993
- return isReactApiCallee(symbol.initializer, apiNames, scopes, options, visitedSymbolIds);
8994
- }
8995
- }
8996
- return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
8997
- }
8998
- if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
8999
- return isReactNamespaceReceiver$1(stripParenExpression(callee.object), scopes, options);
9000
- };
9001
- //#endregion
9002
9011
  //#region src/plugin/utils/is-proven-browser-api-receiver.ts
9003
9012
  const DOM_EVENT_TARGET_TYPE_NAMES = new Set([
9004
9013
  "AbortSignal",
@@ -13652,7 +13661,7 @@ const getPromiseChainCallForCallback = (candidate) => {
13652
13661
  if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
13653
13662
  return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
13654
13663
  };
13655
- const collectEffectInvokedFunctions = (effectCallback) => {
13664
+ const collectInvokedFunctions = (effectCallback, includePromiseCallbacks) => {
13656
13665
  const invokedFunctions = new Set([effectCallback]);
13657
13666
  const localFunctionBindings = /* @__PURE__ */ new Map();
13658
13667
  const calledBindingNames = /* @__PURE__ */ new Set();
@@ -13686,12 +13695,14 @@ const collectEffectInvokedFunctions = (effectCallback) => {
13686
13695
  calledBindingNames.add(callee.name);
13687
13696
  return;
13688
13697
  }
13689
- if (isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
13698
+ if (includePromiseCallbacks && isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
13690
13699
  });
13691
13700
  for (const calledName of calledBindingNames) enqueue(localFunctionBindings.get(calledName));
13692
13701
  }
13693
13702
  return invokedFunctions;
13694
13703
  };
13704
+ const collectEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, true);
13705
+ const collectSynchronouslyEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, false);
13695
13706
  //#endregion
13696
13707
  //#region src/plugin/utils/is-react-hook-name.ts
13697
13708
  const isReactHookName = (name) => {
@@ -13850,15 +13861,19 @@ const resolveReactRefSymbol = (memberExpression, scopes) => {
13850
13861
  if (!isNodeOfType(initializer, "CallExpression")) return null;
13851
13862
  return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
13852
13863
  };
13853
- const hasReactRefCurrentOrigin = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13864
+ const resolveReactRefCurrentOriginSymbol = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13854
13865
  const expression = stripParenExpression(node);
13855
- if (resolveReactRefSymbol(expression, scopes)) return true;
13856
- if (!isNodeOfType(expression, "Identifier")) return false;
13857
- const symbol = resolveConstIdentifierAlias(expression, scopes);
13858
- if (!symbol?.initializer || visitedSymbolIds.has(symbol.id)) return false;
13866
+ const refSymbol = resolveReactRefSymbol(expression, scopes);
13867
+ if (refSymbol) return refSymbol;
13868
+ if (!isNodeOfType(expression, "Identifier")) return null;
13869
+ const symbol = scopes.symbolFor(expression);
13870
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return null;
13871
+ const initializer = getDirectUnreassignedInitializer(symbol);
13872
+ if (!initializer) return null;
13859
13873
  visitedSymbolIds.add(symbol.id);
13860
- return hasReactRefCurrentOrigin(symbol.initializer, scopes, visitedSymbolIds);
13874
+ return resolveReactRefCurrentOriginSymbol(initializer, scopes, visitedSymbolIds);
13861
13875
  };
13876
+ const hasReactRefCurrentOrigin = (node, scopes) => resolveReactRefCurrentOriginSymbol(node, scopes) !== null;
13862
13877
  //#endregion
13863
13878
  //#region src/plugin/utils/walk-inside-statement-blocks.ts
13864
13879
  const walkInsideStatementBlocks = (node, visitor) => {
@@ -13876,6 +13891,7 @@ const walkInsideStatementBlocks = (node, visitor) => {
13876
13891
  };
13877
13892
  //#endregion
13878
13893
  //#region src/plugin/rules/state-and-effects/utils/is-subscribe-like-call-expression.ts
13894
+ const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
13879
13895
  const getSubscribeLikeMethodName = (node) => {
13880
13896
  if (!isNodeOfType(node, "CallExpression")) return null;
13881
13897
  if (!isNodeOfType(node.callee, "MemberExpression")) return null;
@@ -13886,6 +13902,11 @@ const isSubscribeLikeCallExpression = (node) => {
13886
13902
  const methodName = getSubscribeLikeMethodName(node);
13887
13903
  return methodName !== null && SUBSCRIPTION_METHOD_NAMES.has(methodName);
13888
13904
  };
13905
+ const getSubscribeOrObserveMethodName = (node) => {
13906
+ const methodName = getSubscribeLikeMethodName(node);
13907
+ return methodName !== null && (SUBSCRIPTION_METHOD_NAMES.has(methodName) || methodName === "observe") ? methodName : null;
13908
+ };
13909
+ const isSubscribeOrObserveCallExpression = (node) => getSubscribeOrObserveMethodName(node) !== null;
13889
13910
  const isCleanupReturningSubscribeLikeCallExpression = (node) => {
13890
13911
  const methodName = getSubscribeLikeMethodName(node);
13891
13912
  if (methodName === null || !CLEANUP_RETURNING_SUBSCRIPTION_METHOD_NAMES.has(methodName)) return false;
@@ -13938,7 +13959,6 @@ const isNodeReachableWithinFunction = (node, context) => {
13938
13959
  };
13939
13960
  //#endregion
13940
13961
  //#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
13941
- const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
13942
13962
  const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
13943
13963
  const REPLAYABLE_ITERATOR_COLLECTION_CACHE = /* @__PURE__ */ new WeakMap();
13944
13964
  const REACT_REF_EFFECT_ANALYSIS_CACHE = /* @__PURE__ */ new WeakMap();
@@ -13948,10 +13968,6 @@ const RESOURCE_NOUN_BY_KIND = {
13948
13968
  socket: "connection"
13949
13969
  };
13950
13970
  const isSocketConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && SOCKET_CONSTRUCTOR_NAMES_REQUIRING_CLEANUP.has(node.callee.name);
13951
- const isSubscribeOrObserveCall = (node) => {
13952
- if (isSubscribeLikeCallExpression(node)) return true;
13953
- return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === OBSERVER_REGISTRATION_METHOD_NAME;
13954
- };
13955
13971
  const resolveExpressionKey = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13956
13972
  if (!expression) return null;
13957
13973
  const unwrappedExpression = stripParenExpression(expression);
@@ -14053,12 +14069,13 @@ const findSubscribeLikeUsages = (callback, context) => {
14053
14069
  });
14054
14070
  return;
14055
14071
  }
14056
- 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)) {
14072
+ const subscribeOrObserveMethodName = getSubscribeOrObserveMethodName(child);
14073
+ if (subscribeOrObserveMethodName !== null) {
14057
14074
  const registrationDetails = getCallRegistrationDetails(child, context);
14058
14075
  usages.push({
14059
14076
  kind: "subscribe",
14060
14077
  node: child,
14061
- resourceName: child.callee.property.name,
14078
+ resourceName: subscribeOrObserveMethodName,
14062
14079
  handleKey: findAssignedResourceKey(child, context),
14063
14080
  ...registrationDetails
14064
14081
  });
@@ -14340,6 +14357,39 @@ const findContainingCollectionKey = (resourceNode, context) => {
14340
14357
  }
14341
14358
  return null;
14342
14359
  };
14360
+ const findPushedResourceCollectionKey = (usage, context) => {
14361
+ if (!isNodeOfType(usage.node, "CallExpression")) return null;
14362
+ const registrationCallee = stripParenExpression(usage.node.callee);
14363
+ if (!isNodeOfType(registrationCallee, "MemberExpression") || registrationCallee.computed) return null;
14364
+ const resourceIdentifier = stripParenExpression(registrationCallee.object);
14365
+ if (!isPrivatePlainConstIdentifier(resourceIdentifier, context)) return null;
14366
+ const resourceSymbol = context.scopes.symbolFor(resourceIdentifier);
14367
+ if (!resourceSymbol) return null;
14368
+ const pushCalls = resourceSymbol.references.flatMap((reference) => {
14369
+ const referenceRoot = findTransparentExpressionRoot(reference.identifier);
14370
+ const callNode = referenceRoot.parent;
14371
+ if (!isNodeOfType(callNode, "CallExpression") || !callNode.arguments?.some((argument) => argument === referenceRoot)) return [];
14372
+ const pushCallee = stripParenExpression(callNode.callee);
14373
+ return isNodeOfType(pushCallee, "MemberExpression") && !pushCallee.computed && isNodeOfType(pushCallee.object, "Identifier") && isNodeOfType(pushCallee.property, "Identifier") && pushCallee.property.name === "push" ? [callNode] : [];
14374
+ });
14375
+ if (pushCalls.length !== 1) return null;
14376
+ const pushCall = pushCalls[0];
14377
+ if (findEnclosingFunction$1(pushCall) !== findEnclosingFunction$1(usage.node) || !doMatchingNodesCoverEveryPathAfterUsage(usage.node, [pushCall], context)) return null;
14378
+ const pushCallee = stripParenExpression(pushCall.callee);
14379
+ if (!isNodeOfType(pushCallee, "MemberExpression") || !isNodeOfType(pushCallee.object, "Identifier") || !isPrivatePlainConstIdentifier(pushCallee.object, context)) return null;
14380
+ const collectionSymbol = context.scopes.symbolFor(pushCallee.object);
14381
+ const collectionInitializer = collectionSymbol?.initializer ? stripParenExpression(collectionSymbol.initializer) : null;
14382
+ if (!collectionSymbol || !isNodeOfType(collectionInitializer, "ArrayExpression") || (collectionInitializer.elements?.length ?? 0) !== 0 || findEnclosingFunction$1(collectionSymbol.declarationNode) !== findEnclosingFunction$1(usage.node)) return null;
14383
+ return collectionSymbol.references.every((reference) => {
14384
+ const referenceRoot = findTransparentExpressionRoot(reference.identifier);
14385
+ const forOfStatement = referenceRoot.parent;
14386
+ if (isNodeOfType(forOfStatement, "ForOfStatement") && forOfStatement.right === referenceRoot && forOfStatement.await !== true) return true;
14387
+ const memberNode = referenceRoot.parent;
14388
+ const callNode = memberNode?.parent;
14389
+ if (!isNodeOfType(memberNode, "MemberExpression") || memberNode.object !== referenceRoot || memberNode.computed || !isNodeOfType(memberNode.property, "Identifier") || !isNodeOfType(callNode, "CallExpression") || callNode.callee !== memberNode) return false;
14390
+ return memberNode.property.name === "forEach" || memberNode.property.name === "push";
14391
+ }) ? resolveExpressionKey(pushCallee.object, context) : null;
14392
+ };
14343
14393
  const isWithinAssignmentTarget = (identifier) => {
14344
14394
  let currentNode = identifier;
14345
14395
  let parentNode = currentNode.parent;
@@ -14398,6 +14448,14 @@ const isSynchronousIteratorCallback = (functionNode) => {
14398
14448
  if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments?.[1] === functionNode;
14399
14449
  return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments?.[0] === functionNode;
14400
14450
  };
14451
+ const findEnclosingForEachCall = (node) => {
14452
+ const callbackNode = findEnclosingFunction$1(node);
14453
+ if (!callbackNode) return null;
14454
+ const callNode = callbackNode.parent;
14455
+ if (!isNodeOfType(callNode, "CallExpression") || callNode.arguments?.[0] !== callbackNode) return null;
14456
+ const callee = stripParenExpression(callNode.callee);
14457
+ return isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "forEach" ? callNode : null;
14458
+ };
14401
14459
  const findDirectCallForReference = (identifier) => {
14402
14460
  const expressionRoot = findTransparentExpressionRoot(identifier);
14403
14461
  const callNode = expressionRoot.parent;
@@ -14412,7 +14470,7 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
14412
14470
  const callNode = findDirectCallForReference(reference.identifier);
14413
14471
  return callNode ? [callNode] : [];
14414
14472
  });
14415
- if (invocationCalls.length !== 1) return null;
14473
+ if (invocationCalls.length !== 1 || symbol.references.length !== 1) return null;
14416
14474
  const invocationCall = invocationCalls[0];
14417
14475
  return findEnclosingFunction$1(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
14418
14476
  };
@@ -14446,7 +14504,15 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
14446
14504
  if (cleanupChild !== cleanupFunction.body && isFunctionLike$1(cleanupChild) && !isSynchronousIteratorCallback(cleanupChild)) return false;
14447
14505
  const cleanupCall = isNodeOfType(cleanupChild, "ChainExpression") ? cleanupChild.expression : cleanupChild;
14448
14506
  if (doesReleaseCallMatchUsage(cleanupChild, usage, context)) {
14449
- const cleanupForOfStatement = findForOfStatementForIteratorExpression(isNodeOfType(cleanupCall, "CallExpression") ? cleanupCall.arguments?.[0] : null, context);
14507
+ const cleanupForEachCall = findEnclosingForEachCall(cleanupChild);
14508
+ const cleanupCallee = isNodeOfType(cleanupCall, "CallExpression") ? stripParenExpression(cleanupCall.callee) : null;
14509
+ const cleanupReceiverForOfStatement = isNodeOfType(cleanupCallee, "MemberExpression") ? findForOfStatementForIteratorExpression(cleanupCallee.object, context) : null;
14510
+ const cleanupReceiverCollectionKey = cleanupReceiverForOfStatement ? resolveExpressionKey(cleanupReceiverForOfStatement.right, context) : isNodeOfType(cleanupCallee, "MemberExpression") ? resolveIteratorCollectionKey(cleanupCallee.object, context) : null;
14511
+ if (cleanupReceiverCollectionKey !== null && findEnclosingFunction$1(cleanupChild) !== cleanupFunction) {
14512
+ if (cleanupForEachCall && findPushedResourceCollectionKey(usage, context) === cleanupReceiverCollectionKey) matchingLoopOrHelperAnchors.push(cleanupForEachCall);
14513
+ return;
14514
+ }
14515
+ const cleanupForOfStatement = findForOfStatementForIteratorExpression(isNodeOfType(cleanupCall, "CallExpression") ? cleanupCall.arguments?.[0] : null, context) ?? cleanupReceiverForOfStatement;
14450
14516
  if (!cleanupForOfStatement) {
14451
14517
  didCleanupFunctionMatch = true;
14452
14518
  return false;
@@ -14490,28 +14556,28 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
14490
14556
  });
14491
14557
  return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
14492
14558
  };
14493
- const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => {
14494
- if (usage.handleKey === null) return null;
14495
- const doesTestRequireLiveHandle = (test) => {
14496
- if (resolveExpressionKey(test, context) === usage.handleKey) return true;
14497
- const unwrappedTest = stripParenExpression(test);
14498
- if (!isNodeOfType(unwrappedTest, "BinaryExpression") || unwrappedTest.operator !== "!=" && unwrappedTest.operator !== "!==") return false;
14499
- const isNullishOperand = (operand) => {
14500
- const unwrappedOperand = stripParenExpression(operand);
14501
- return isNodeOfType(unwrappedOperand, "Literal") && unwrappedOperand.value === null || isNodeOfType(unwrappedOperand, "Identifier") && unwrappedOperand.name === "undefined" && context.scopes.isGlobalReference(unwrappedOperand);
14502
- };
14503
- return resolveExpressionKey(unwrappedTest.left, context) === usage.handleKey && isNullishOperand(unwrappedTest.right) || resolveExpressionKey(unwrappedTest.right, context) === usage.handleKey && isNullishOperand(unwrappedTest.left);
14559
+ const doesTestRequireLiveExpressionKey = (test, expressionKey, context) => {
14560
+ if (resolveExpressionKey(test, context) === expressionKey) return true;
14561
+ const unwrappedTest = stripParenExpression(test);
14562
+ if (!isNodeOfType(unwrappedTest, "BinaryExpression") || unwrappedTest.operator !== "!=" && unwrappedTest.operator !== "!==") return false;
14563
+ const isNullishOperand = (operand) => {
14564
+ const unwrappedOperand = stripParenExpression(operand);
14565
+ return isNodeOfType(unwrappedOperand, "Literal") && unwrappedOperand.value === null || isNodeOfType(unwrappedOperand, "Identifier") && unwrappedOperand.name === "undefined" && context.scopes.isGlobalReference(unwrappedOperand);
14504
14566
  };
14567
+ return resolveExpressionKey(unwrappedTest.left, context) === expressionKey && isNullishOperand(unwrappedTest.right) || resolveExpressionKey(unwrappedTest.right, context) === expressionKey && isNullishOperand(unwrappedTest.left);
14568
+ };
14569
+ const findLiveExpressionGuardForRelease = (releaseCall, owner, expressionKey, context) => {
14505
14570
  let ancestor = releaseCall.parent;
14506
14571
  while (ancestor && ancestor !== owner) {
14507
14572
  if (isNodeOfType(ancestor, "IfStatement")) {
14508
- if (ancestor.alternate !== null || !doesTestRequireLiveHandle(ancestor.test) || !doMatchingNodesCoverEveryPathAfterUsage(ancestor.consequent, [releaseCall], context)) return null;
14573
+ if (ancestor.alternate !== null || !doesTestRequireLiveExpressionKey(ancestor.test, expressionKey, context) || !doMatchingNodesCoverEveryPathAfterUsage(ancestor.consequent, [releaseCall], context)) return null;
14509
14574
  return ancestor;
14510
14575
  }
14511
14576
  ancestor = ancestor.parent;
14512
14577
  }
14513
14578
  return null;
14514
14579
  };
14580
+ const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => usage.handleKey === null ? null : findLiveExpressionGuardForRelease(releaseCall, owner, usage.handleKey, context);
14515
14581
  const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
14516
14582
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
14517
14583
  const functionCfg = context.cfg.cfgFor(callback);
@@ -14540,7 +14606,7 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
14540
14606
  walkAst(componentFunction.body, (child) => {
14541
14607
  if (didFindUnmountCleanup) return false;
14542
14608
  if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction) return;
14543
- if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
14609
+ if (!isReactHookCall(child, CLEANUP_EFFECT_HOOK_NAMES, context.scopes)) return;
14544
14610
  const dependencyList = child.arguments?.[1];
14545
14611
  if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
14546
14612
  const cleanupCallback = getEffectCallback(child);
@@ -14699,7 +14765,108 @@ const hasPotentialInterruptionAfterGuard = (callback, guardState, usageNode, con
14699
14765
  });
14700
14766
  return hasPotentialInterruption;
14701
14767
  };
14768
+ const getNumericReactRefCurrentKey = (expression, context) => {
14769
+ const refSymbol = resolveReactRefSymbol(stripParenExpression(expression), context.scopes);
14770
+ const initializer = refSymbol?.initializer ? stripParenExpression(refSymbol.initializer) : null;
14771
+ if (!isNodeOfType(initializer, "CallExpression")) return null;
14772
+ const initialValue = initializer.arguments?.[0] ? stripParenExpression(initializer.arguments[0]) : null;
14773
+ if (!isNodeOfType(initialValue, "Literal") || typeof initialValue.value !== "number") return null;
14774
+ return resolveExpressionKey(expression, context);
14775
+ };
14776
+ const getBlockingGenerationKey = (expression, context) => {
14777
+ const test = stripParenExpression(expression);
14778
+ if (isNodeOfType(test, "LogicalExpression") && test.operator === "||") return getBlockingGenerationKey(test.left, context) ?? getBlockingGenerationKey(test.right, context);
14779
+ if (!isNodeOfType(test, "BinaryExpression") || test.operator !== "!==" && test.operator !== "!=") return null;
14780
+ const leftKey = getNumericReactRefCurrentKey(test.left, context);
14781
+ const rightKey = getNumericReactRefCurrentKey(test.right, context);
14782
+ const snapshotExpression = leftKey ? stripParenExpression(test.right) : stripParenExpression(test.left);
14783
+ const key = leftKey ?? rightKey;
14784
+ return key && isNodeOfType(snapshotExpression, "Identifier") ? key : null;
14785
+ };
14786
+ const findGenerationGuardKeyForDeferredUsage = (usageFunction, usageNode, context) => {
14787
+ if (!isFunctionLike$1(usageFunction)) return null;
14788
+ let generationKey = null;
14789
+ walkAst(usageFunction.body, (child) => {
14790
+ if (generationKey) return false;
14791
+ if (child !== usageFunction.body && isFunctionLike$1(child)) return false;
14792
+ if (!isNodeOfType(child, "IfStatement") || child.alternate) return;
14793
+ const key = getBlockingGenerationKey(child.test, context);
14794
+ if (!key || canNodeReachLaterNodeWithinFunction(child.consequent, usageNode, usageFunction, context) || !doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], usageFunction, context)) return;
14795
+ generationKey = key;
14796
+ });
14797
+ return generationKey;
14798
+ };
14799
+ const isGenerationAdvance = (node, generationKey, context) => {
14800
+ if (isNodeOfType(node, "UpdateExpression") && resolveExpressionKey(node.argument, context) === generationKey) return true;
14801
+ if (!isNodeOfType(node, "AssignmentExpression") || resolveExpressionKey(node.left, context) !== generationKey || node.operator !== "+=" && node.operator !== "-=") return false;
14802
+ const amount = stripParenExpression(node.right);
14803
+ return isNodeOfType(amount, "Literal") && typeof amount.value === "number" && amount.value !== 0;
14804
+ };
14805
+ const functionAdvancesGeneration = (owner, generationKey, context) => {
14806
+ if (!isFunctionLike$1(owner)) return false;
14807
+ let didAdvanceGeneration = false;
14808
+ walkAst(owner.body, (child) => {
14809
+ if (didAdvanceGeneration) return false;
14810
+ if (child !== owner.body && isFunctionLike$1(child)) return false;
14811
+ if (isGenerationAdvance(child, generationKey, context)) {
14812
+ didAdvanceGeneration = true;
14813
+ return false;
14814
+ }
14815
+ });
14816
+ return didAdvanceGeneration;
14817
+ };
14818
+ const cleanupReturnsReleaseUsage = (cleanupReturns, usage, context) => cleanupReturns.length > 0 && cleanupReturns.every((cleanupReturn) => {
14819
+ if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
14820
+ const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
14821
+ return Boolean(cleanupFunction && isFunctionLike$1(cleanupFunction) && doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context));
14822
+ });
14823
+ const getOwnedFunctionReference = (reference, usageFunction, usageNode, callback, cleanupReturns, context) => {
14824
+ const directCall = findDirectCallForReference(reference);
14825
+ if (directCall) {
14826
+ const referenceOwner = findEnclosingFunction$1(directCall);
14827
+ if (referenceOwner && referenceOwner !== usageFunction && collectSynchronouslyEffectInvokedFunctions(callback).has(referenceOwner)) return { generationKey: null };
14828
+ const generationKey = referenceOwner ? findGenerationGuardKeyForDeferredUsage(referenceOwner, directCall, context) : null;
14829
+ return generationKey ? { generationKey } : null;
14830
+ }
14831
+ const referenceRoot = findTransparentExpressionRoot(reference);
14832
+ const schedulerCall = referenceRoot.parent;
14833
+ if (!isNodeOfType(schedulerCall, "CallExpression") || !schedulerCall.arguments.some((argument) => argument === referenceRoot) || !isNodeOfType(schedulerCall.callee, "Identifier") || schedulerCall.callee.name !== "setTimeout" || !context.scopes.isGlobalReference(schedulerCall.callee)) return null;
14834
+ const schedulerUsage = {
14835
+ kind: "timer",
14836
+ node: schedulerCall,
14837
+ resourceName: schedulerCall.callee.name,
14838
+ handleKey: findAssignedResourceKey(schedulerCall, context),
14839
+ receiverKey: null,
14840
+ registrationVerbName: schedulerCall.callee.name,
14841
+ eventKey: null,
14842
+ handlerKey: null
14843
+ };
14844
+ const generationKey = findGenerationGuardKeyForDeferredUsage(usageFunction, usageNode, context);
14845
+ return schedulerUsage.handleKey !== null && cleanupReturnsReleaseUsage(cleanupReturns, schedulerUsage, context) && generationKey ? { generationKey } : null;
14846
+ };
14847
+ const hasGuardedRefOwnedNestedCleanup = (callback, usage, cleanupReturns, context) => {
14848
+ const usageFunction = findEnclosingFunction$1(usage.node);
14849
+ const usageExpression = findTransparentExpressionRoot(usage.node);
14850
+ const usageAssignment = usageExpression.parent;
14851
+ if (usage.kind !== "subscribe" && usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usageAssignment, "AssignmentExpression") || usageAssignment.operator !== "=" || usageAssignment.right !== usageExpression || !resolveReactRefSymbol(stripParenExpression(usageAssignment.left), context.scopes) || !collectSynchronouslyEffectInvokedFunctions(callback).has(usageFunction) || !cleanupReturnsReleaseUsage(cleanupReturns, usage, context) || !doMatchingNodesCoverEveryPathFromFunctionEntry(callback, cleanupReturns, context)) return false;
14852
+ const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
14853
+ if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
14854
+ const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
14855
+ return cleanupFunction && isFunctionLike$1(cleanupFunction) ? [cleanupFunction] : [];
14856
+ });
14857
+ const bindingIdentifier = getFunctionBindingIdentifier$1(usageFunction);
14858
+ const functionSymbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
14859
+ if (!functionSymbol || functionSymbol.references.length === 0) return false;
14860
+ const ownedReferences = functionSymbol.references.map((reference) => getOwnedFunctionReference(reference.identifier, usageFunction, usage.node, callback, cleanupReturns, context));
14861
+ if (ownedReferences.some((reference) => reference === null)) return false;
14862
+ const generationKeys = new Set(ownedReferences.flatMap((reference) => reference?.generationKey ? [reference.generationKey] : []));
14863
+ if (generationKeys.size !== 1) return false;
14864
+ const generationKey = generationKeys.values().next().value;
14865
+ if (typeof generationKey !== "string") return false;
14866
+ return [...collectSynchronouslyEffectInvokedFunctions(callback), ...cleanupFunctions].some((owner) => functionAdvancesGeneration(owner, generationKey, context));
14867
+ };
14702
14868
  const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
14869
+ if (hasGuardedRefOwnedNestedCleanup(callback, usage, cleanupReturns, context)) return true;
14703
14870
  const usageFunction = findEnclosingFunction$1(usage.node);
14704
14871
  const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
14705
14872
  if (usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usage.node, "CallExpression") || !isNodeOfType(usage.node.callee, "Identifier") || !context.scopes.isGlobalReference(usage.node.callee) || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
@@ -14870,7 +15037,7 @@ const getReleaseVerbName = (node) => {
14870
15037
  const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) => {
14871
15038
  const releaseFunction = findEnclosingFunction$1(releaseReceiver);
14872
15039
  const usageFunction = findEnclosingFunction$1(usage.node);
14873
- if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction) || !hasReactRefCurrentOrigin(releaseReceiver, context.scopes)) return false;
15040
+ if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction, context) || !resolveReactRefCurrentOriginSymbol(releaseReceiver, context.scopes)) return false;
14874
15041
  const controllerKey = getListenerAbortControllerKey(usage, context);
14875
15042
  const refCurrentKey = resolveExpressionKey(releaseReceiver, context);
14876
15043
  if (controllerKey === null || refCurrentKey === null) return false;
@@ -14890,6 +15057,62 @@ const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) =>
14890
15057
  const safeOwnershipAssignments = ownershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, previousAbortCalls, usageFunction, context));
14891
15058
  return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
14892
15059
  };
15060
+ const isJsxRefAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && node.name.name === "ref";
15061
+ const isFunctionForwardedToReactRef = (functionNode, context) => {
15062
+ const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
15063
+ if (!bindingIdentifier) return false;
15064
+ const symbol = context.scopes.symbolFor(bindingIdentifier);
15065
+ if (!symbol) return false;
15066
+ return symbol.references.some((reference) => {
15067
+ const referenceRoot = findTransparentExpressionRoot(reference.identifier);
15068
+ const expressionContainer = referenceRoot.parent;
15069
+ return Boolean(isNodeOfType(expressionContainer, "JSXExpressionContainer") && expressionContainer.expression === referenceRoot && isJsxRefAttribute(expressionContainer.parent));
15070
+ });
15071
+ };
15072
+ const isFunctionReturnedFromReactHook = (functionNode, context, requireRefPropertyName) => {
15073
+ const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
15074
+ if (!bindingIdentifier) return false;
15075
+ const symbol = context.scopes.symbolFor(bindingIdentifier);
15076
+ if (!symbol) return false;
15077
+ return symbol.references.some((reference) => {
15078
+ const referenceRoot = findTransparentExpressionRoot(reference.identifier);
15079
+ const property = referenceRoot.parent;
15080
+ const propertyName = isNodeOfType(property, "Property") ? getStaticPropertyKeyName(property) : null;
15081
+ if (!isNodeOfType(property, "Property") || property.value !== referenceRoot || !isNodeOfType(property.parent, "ObjectExpression") || requireRefPropertyName && propertyName !== "ref" && !propertyName?.endsWith("Ref")) return false;
15082
+ const returnedObject = findTransparentExpressionRoot(property.parent);
15083
+ const returnStatement = returnedObject.parent;
15084
+ if (!isNodeOfType(returnStatement, "ReturnStatement") || returnStatement.argument !== returnedObject) return false;
15085
+ const ownerFunction = findEnclosingFunction$1(returnStatement);
15086
+ return Boolean(ownerFunction && isReactHookName(getFunctionBindingIdentifier$1(ownerFunction)?.name ?? ""));
15087
+ });
15088
+ };
15089
+ const isFunctionUsedAsReactRef = (functionNode, context) => isFunctionForwardedToReactRef(functionNode, context) || isFunctionReturnedFromReactHook(functionNode, context, true);
15090
+ const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
15091
+ if (!isNodeOfType(usage.node, "CallExpression")) return false;
15092
+ const usageFunction = findEnclosingFunction$1(usage.node);
15093
+ if (!usageFunction || !isFunctionLike$1(usageFunction) || usageFunction !== findEnclosingFunction$1(releaseCall) || !isFunctionUsedAsReactRef(usageFunction, context)) return false;
15094
+ const registrationCallee = stripParenExpression(usage.node.callee);
15095
+ const releaseCallee = stripParenExpression(releaseCall.callee);
15096
+ const releaseRefSymbol = isNodeOfType(releaseCallee, "MemberExpression") ? resolveReactRefCurrentOriginSymbol(releaseCallee.object, context.scopes) : null;
15097
+ if (!isNodeOfType(registrationCallee, "MemberExpression") || registrationCallee.computed || !isNodeOfType(registrationCallee.property, "Identifier") || registrationCallee.property.name !== "addEventListener" || !isNodeOfType(releaseCallee, "MemberExpression") || releaseCallee.computed || !isNodeOfType(releaseCallee.property, "Identifier") || releaseCallee.property.name !== "removeEventListener" || !releaseRefSymbol) return false;
15098
+ const registrationReceiverKey = resolveExpressionKey(stripParenExpression(registrationCallee.object), context);
15099
+ const nodeParameterKey = resolveExpressionKey(usageFunction.params?.[0], context);
15100
+ const releaseReceiverKey = resolveExpressionKey(releaseCallee.object, context);
15101
+ if (registrationReceiverKey === null || registrationReceiverKey !== nodeParameterKey || releaseReceiverKey === null || usage.eventKey === null || usage.eventKey !== resolveExpressionKey(releaseCall.arguments?.[0], context) || usage.handlerKey === null || usage.handlerKey !== resolveExpressionKey(releaseCall.arguments?.[1], context)) return false;
15102
+ const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
15103
+ const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
15104
+ if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
15105
+ const releaseStart = getRangeStart(releaseCall);
15106
+ const matchingOwnershipAssignments = [];
15107
+ const usageFunctionBody = usageFunction.body;
15108
+ walkAst(usageFunctionBody, (child) => {
15109
+ if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
15110
+ if (isNodeOfType(child, "AssignmentExpression") && child.operator === "=" && resolveReactRefSymbol(stripParenExpression(child.left), context.scopes)?.id === releaseRefSymbol.id && resolveExpressionKey(child.right, context) === registrationReceiverKey && releaseStart !== null && (getRangeStart(child) ?? -1) > releaseStart) matchingOwnershipAssignments.push(child);
15111
+ });
15112
+ const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
15113
+ const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
15114
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
15115
+ };
14893
15116
  const doesReleaseCallMatchUsage = (node, usage, context) => {
14894
15117
  const callNode = isNodeOfType(node, "ChainExpression") ? node.expression : node;
14895
15118
  if (!isNodeOfType(callNode, "CallExpression")) return false;
@@ -14906,14 +15129,21 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
14906
15129
  if (!releaseVerbName) return false;
14907
15130
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
14908
15131
  const releaseReceiverKey = resolveExpressionKey(callee.object, context);
15132
+ const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
15133
+ const pairedReleaseVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
15134
+ const pushedResourceCollectionKey = findPushedResourceCollectionKey(usage, context);
15135
+ const releaseReceiverForOfStatement = findForOfStatementForIteratorExpression(callee.object, context);
15136
+ const releaseReceiverCollectionKey = releaseReceiverForOfStatement ? resolveExpressionKey(releaseReceiverForOfStatement.right, context) : resolveIteratorCollectionKey(callee.object, context);
15137
+ if (pairedReleaseVerbNames && matchesPairedReleaseVerb(releaseVerbName, pairedReleaseVerbNames) && pushedResourceCollectionKey !== null && pushedResourceCollectionKey === releaseReceiverCollectionKey && (releaseVerbName !== "unobserve" || usage.eventKey !== null && releaseEventKey === usage.eventKey)) return true;
15138
+ if (isReactRefListenerReplacementRelease(callNode, usage, context)) return true;
14909
15139
  if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
14910
15140
  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;
14911
15141
  if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
14912
15142
  if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
14913
15143
  if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) return false;
15144
+ if (usage.registrationVerbName === "subscribe" && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub") && usage.handleKey !== null && resolveExpressionKey(callNode.arguments?.[0], context) === usage.handleKey) return true;
14914
15145
  const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
14915
15146
  if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
14916
- const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
14917
15147
  const usageEventArgument = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[0] : null;
14918
15148
  const releaseEventArgument = callNode.arguments?.[0];
14919
15149
  if (isAssignmentFormForOfIteratorReference(usageEventArgument, context) || isAssignmentFormForOfIteratorReference(releaseEventArgument, context)) return false;
@@ -14947,13 +15177,14 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
14947
15177
  const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
14948
15178
  if (!releaseHandler) return releaseVerbName === "off";
14949
15179
  const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
14950
- return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey;
15180
+ const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignature ? 0 : 1] : null;
15181
+ return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
14951
15182
  }
14952
15183
  if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
14953
15184
  return true;
14954
15185
  };
14955
15186
  const matchesPairedReleaseVerb = (releaseVerbName, pairedVerbNames) => pairedVerbNames.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName);
14956
- const isReturnedEffectCleanupFunction = (functionNode) => {
15187
+ const isReturnedEffectCleanupFunction = (functionNode, context) => {
14957
15188
  let currentNode = functionNode;
14958
15189
  let parentNode = currentNode.parent;
14959
15190
  while (isNodeOfType(parentNode, "ChainExpression") || isNodeOfType(parentNode, "TSAsExpression") || isNodeOfType(parentNode, "TSNonNullExpression")) {
@@ -14962,23 +15193,175 @@ const isReturnedEffectCleanupFunction = (functionNode) => {
14962
15193
  }
14963
15194
  const effectCallback = isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode ? findEnclosingFunction$1(parentNode) : isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode ? parentNode : null;
14964
15195
  const effectCall = effectCallback?.parent;
14965
- return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
15196
+ return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isReactHookCall(effectCall, CLEANUP_EFFECT_HOOK_NAMES, context.scopes));
14966
15197
  };
14967
15198
  const isPotentiallyReachableFunction = (functionNode, context) => {
14968
- if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode)) return true;
15199
+ if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode, context)) return true;
14969
15200
  const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
14970
15201
  if (!bindingIdentifier) return false;
14971
15202
  const symbol = context.scopes.symbolFor(bindingIdentifier);
14972
15203
  if (!symbol) return false;
14973
15204
  return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
14974
15205
  };
15206
+ const findRetainedDisposerStorages = (disposerFunction, usage, context) => {
15207
+ if (!isFunctionLike$1(disposerFunction) || disposerFunction.async || disposerFunction.generator) return [];
15208
+ const usageFunction = findEnclosingFunction$1(usage.node);
15209
+ if (!usageFunction || !isFunctionLike$1(usageFunction)) return [];
15210
+ const assignments = /* @__PURE__ */ new Map();
15211
+ const collectAssignment = (expression) => {
15212
+ const expressionRoot = findTransparentExpressionRoot(expression);
15213
+ const assignment = expressionRoot.parent;
15214
+ if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.right !== expressionRoot) return;
15215
+ const refSymbol = resolveReactRefSymbol(stripParenExpression(assignment.left), context.scopes);
15216
+ const refCurrentKey = resolveExpressionKey(assignment.left, context);
15217
+ const retainedFunction = findEnclosingFunction$1(assignment);
15218
+ const assignmentStart = getRangeStart(assignment);
15219
+ if (!refSymbol || !refCurrentKey || !retainedFunction || retainedFunction !== usageFunction || assignmentStart === null) return;
15220
+ assignments.set(assignmentStart, {
15221
+ assignmentNode: assignment,
15222
+ refCurrentKey,
15223
+ retainedFunction
15224
+ });
15225
+ };
15226
+ collectAssignment(disposerFunction);
15227
+ const bindingIdentifier = getFunctionBindingIdentifier$1(disposerFunction);
15228
+ const symbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
15229
+ for (const reference of symbol?.references ?? []) collectAssignment(reference.identifier);
15230
+ walkAst(usageFunction.body, (child) => {
15231
+ if (child !== usageFunction.body && isFunctionLike$1(child)) return false;
15232
+ if (isNodeOfType(child, "AssignmentExpression") && resolveStableValue(child.right, context) === disposerFunction) collectAssignment(child.right);
15233
+ });
15234
+ return [...assignments.values()];
15235
+ };
15236
+ const isRetainedDisposerStorageEstablished = (storage, usage, context) => doMatchingNodesCoverEveryPathBeforeUsage(usage.node, [storage.assignmentNode], storage.retainedFunction, context) || doMatchingNodesCoverEveryPathAfterUsage(usage.node, [storage.assignmentNode], context);
15237
+ const hasUnsafeRetainedDisposerOverwrite = (storage, usage, context) => {
15238
+ let hasUnsafeOverwrite = false;
15239
+ walkAst(storage.retainedFunction.body, (child) => {
15240
+ if (hasUnsafeOverwrite) return false;
15241
+ if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
15242
+ if (!isNodeOfType(child, "AssignmentExpression") || child === storage.assignmentNode || resolveExpressionKey(child.left, context) !== storage.refCurrentKey || !canNodeReachLaterNodeWithinFunction(usage.node, child, storage.retainedFunction, context)) return;
15243
+ const storedValue = resolveStableValue(child.right, context);
15244
+ if (!storedValue || !isFunctionLike$1(storedValue) || !doesCleanupFunctionReleaseUsage(storedValue, usage, context)) {
15245
+ hasUnsafeOverwrite = true;
15246
+ return false;
15247
+ }
15248
+ });
15249
+ return hasUnsafeOverwrite;
15250
+ };
15251
+ const hasEffectCleanupInvocation = (storage, usage, context) => {
15252
+ const componentFunction = findEnclosingFunction$1(storage.retainedFunction);
15253
+ if (!componentFunction || !isFunctionLike$1(componentFunction)) return false;
15254
+ const cleanupFunctionInvokesRef = (cleanupFunction) => {
15255
+ if (!isFunctionLike$1(cleanupFunction)) return false;
15256
+ let didFindCleanupCall = false;
15257
+ walkAst(cleanupFunction.body, (child) => {
15258
+ if (didFindCleanupCall) return false;
15259
+ if (child !== cleanupFunction.body && isFunctionLike$1(child)) return false;
15260
+ if (isNodeOfType(child, "CallExpression") && resolveExpressionKey(child.callee, context) === storage.refCurrentKey) {
15261
+ const callRoot = findTransparentExpressionRoot(child);
15262
+ const callStatement = callRoot.parent;
15263
+ const isDirectBlockStatement = isNodeOfType(cleanupFunction.body, "BlockStatement") && isNodeOfType(callStatement, "ExpressionStatement") && callStatement.parent === cleanupFunction.body;
15264
+ const isConciseBody = cleanupFunction.body === callRoot;
15265
+ if ((isDirectBlockStatement || isConciseBody) && !hasUnprovenReturnBeforeRefOwnedRelease(cleanupFunction, child, storage.refCurrentKey, context)) {
15266
+ didFindCleanupCall = true;
15267
+ return false;
15268
+ }
15269
+ }
15270
+ });
15271
+ return didFindCleanupCall;
15272
+ };
15273
+ const effectReturnsCleanup = (effectCallback) => {
15274
+ if (!isFunctionLike$1(effectCallback)) return false;
15275
+ if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
15276
+ const cleanupFunction = resolveRefOwnedCleanupFunction(effectCallback.body, context);
15277
+ return Boolean(cleanupFunction && cleanupFunctionInvokesRef(cleanupFunction));
15278
+ }
15279
+ const matchingReturns = [];
15280
+ walkInsideStatementBlocks(effectCallback.body, (child) => {
15281
+ if (!isNodeOfType(child, "ReturnStatement") || !child.argument) return;
15282
+ const cleanupFunction = resolveRefOwnedCleanupFunction(child.argument, context);
15283
+ if (!cleanupFunction || !cleanupFunctionInvokesRef(cleanupFunction)) return;
15284
+ matchingReturns.push(child);
15285
+ });
15286
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15287
+ };
15288
+ let didFindInvocation = false;
15289
+ walkAst(componentFunction.body, (child) => {
15290
+ if (didFindInvocation) return false;
15291
+ if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
15292
+ const effectCallback = getEffectCallback(child);
15293
+ if (effectCallback && effectReturnsCleanup(effectCallback)) {
15294
+ didFindInvocation = true;
15295
+ return false;
15296
+ }
15297
+ });
15298
+ return didFindInvocation;
15299
+ };
15300
+ const hasCallbackRefReplacementInvocation = (storage, usage, context) => {
15301
+ const isReturnedCallbackRefShape = () => {
15302
+ if (!isFunctionLike$1(storage.retainedFunction)) return false;
15303
+ const callbackCall = findTransparentExpressionRoot(storage.retainedFunction).parent;
15304
+ if (!isNodeOfType(callbackCall, "CallExpression") || !isReactApiCall(callbackCall, "useCallback", context.scopes)) return false;
15305
+ const nodeParameter = storage.retainedFunction.params?.[0];
15306
+ const nodeParameterKey = resolveExpressionKey(nodeParameter, context);
15307
+ if (!nodeParameterKey || usage.receiverKey !== nodeParameterKey) return false;
15308
+ if (!isFunctionReturnedFromReactHook(storage.retainedFunction, context, false)) return false;
15309
+ const usageStart = getRangeStart(usage.node);
15310
+ if (usageStart === null) return false;
15311
+ let hasNullExit = false;
15312
+ walkAst(storage.retainedFunction.body, (child) => {
15313
+ if (hasNullExit) return false;
15314
+ if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
15315
+ if (!isNodeOfType(child, "IfStatement") || (getRangeStart(child) ?? usageStart) >= usageStart) return;
15316
+ const test = stripParenExpression(child.test);
15317
+ if (!isNodeOfType(test, "UnaryExpression") || test.operator !== "!" || resolveExpressionKey(test.argument, context) !== nodeParameterKey) return;
15318
+ const consequent = child.consequent;
15319
+ hasNullExit = isNodeOfType(consequent, "ReturnStatement") || isNodeOfType(consequent, "BlockStatement") && consequent.body.some((statement) => isNodeOfType(statement, "ReturnStatement"));
15320
+ if (hasNullExit) return false;
15321
+ });
15322
+ return hasNullExit;
15323
+ };
15324
+ if (!isFunctionForwardedToReactRef(storage.retainedFunction, context) && !isReturnedCallbackRefShape()) return false;
15325
+ const cleanupCalls = [];
15326
+ walkAst(storage.retainedFunction.body, (child) => {
15327
+ if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
15328
+ if (isNodeOfType(child, "CallExpression") && resolveExpressionKey(child.callee, context) === storage.refCurrentKey) cleanupCalls.push(child);
15329
+ });
15330
+ return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, cleanupCalls, storage.retainedFunction, context);
15331
+ };
15332
+ const isRetainedDisposerRefRelease = (releaseNode, usage, context) => {
15333
+ const disposerFunction = findEnclosingFunction$1(releaseNode);
15334
+ if (!disposerFunction) return false;
15335
+ return findRetainedDisposerStorages(disposerFunction, usage, context).some((storage) => isRetainedDisposerStorageEstablished(storage, usage, context) && !hasUnsafeRetainedDisposerOverwrite(storage, usage, context) && (hasEffectCleanupInvocation(storage, usage, context) || hasCallbackRefReplacementInvocation(storage, usage, context)));
15336
+ };
15337
+ const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
15338
+ if (usage.kind !== "subscribe" || usage.registrationVerbName !== "addEventListener" || usage.receiverKey === null || usage.eventKey === null || !isNodeOfType(usage.node, "CallExpression") || !isFunctionLike$1(releaseFunction) || releaseFunction.async || releaseFunction.generator || !isNodeOfType(releaseFunction.body, "BlockStatement") || !doMatchingNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseNode], context)) return false;
15339
+ const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
15340
+ const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
15341
+ if (!isNodeOfType(releaseCall, "CallExpression")) return false;
15342
+ const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
15343
+ if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
15344
+ const ownerFunction = findEnclosingFunction$1(releaseFunction);
15345
+ if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
15346
+ const triggerRegistrations = [];
15347
+ walkAst(ownerFunction.body, (child) => {
15348
+ if (child !== ownerFunction.body && isFunctionLike$1(child)) return false;
15349
+ if (!isNodeOfType(child, "CallExpression")) return;
15350
+ const registrationDetails = getCallRegistrationDetails(child, context);
15351
+ if (registrationDetails.registrationVerbName === "addEventListener" && registrationDetails.receiverKey === usage.receiverKey && resolveStableValue(child.arguments?.[1], context) === releaseFunction) triggerRegistrations.push(child);
15352
+ });
15353
+ if (triggerRegistrations.some((triggerRegistration) => triggerRegistration === usage.node)) return true;
15354
+ return doMatchingNodesCoverEveryPathAfterUsage(usage.node, triggerRegistrations, context) || doMatchingNodesCoverEveryPathBeforeUsage(usage.node, triggerRegistrations, ownerFunction, context);
15355
+ };
14975
15356
  const isReleaseReachableForUsage = (releaseNode, usage, context) => {
14976
15357
  if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
14977
15358
  const releaseFunction = findEnclosingFunction$1(releaseNode);
14978
15359
  if (!releaseFunction) return true;
14979
15360
  if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
15361
+ if (isRetainedDisposerRefRelease(releaseNode, usage, context)) return true;
14980
15362
  const usageFunction = findEnclosingFunction$1(usage.node);
14981
15363
  if (usageFunction && isFunctionLike$1(usageFunction) && getAssignedReactRefSymbol(usageFunction, context) && isCleanupFunctionReferencedByReturn(usageFunction, releaseFunction, context)) return isReactRefCallbackCleanupOwnedByEffect(usageFunction, releaseFunction, usage, context);
15364
+ if (isSelfReleasingListenerRelease(releaseNode, releaseFunction, usage, context)) return true;
14982
15365
  return isPotentiallyReachableFunction(releaseFunction, context);
14983
15366
  };
14984
15367
  const fileContainsReleaseForUsage = (usage, context) => {
@@ -15310,7 +15693,7 @@ const findRetainedFunctionLeak = (retainedFunction, context, options) => {
15310
15693
  return false;
15311
15694
  }
15312
15695
  }
15313
- if (isSubscribeOrObserveCall(child) && (!doesResourceResultEscape(child, allowReturnedResourceEscape, allowReturnedResourceEscape, context) || options?.requireCallableReturnedResource === true && !isCleanupReturningSubscribeLikeCallExpression(child))) {
15696
+ if (isSubscribeOrObserveCallExpression(child) && (!doesResourceResultEscape(child, allowReturnedResourceEscape, allowReturnedResourceEscape, context) || options?.requireCallableReturnedResource === true && !isCleanupReturningSubscribeLikeCallExpression(child))) {
15314
15697
  const registrationDetails = getCallRegistrationDetails(child, context);
15315
15698
  const subscriptionUsage = {
15316
15699
  kind: "subscribe",
@@ -15518,7 +15901,7 @@ const isInlineRetainedHandlerFunction = (functionNode, context) => {
15518
15901
  if (!isFunctionLike$1(functionNode)) return false;
15519
15902
  const functionRoot = findTransparentExpressionRoot(functionNode);
15520
15903
  const callbackCall = functionRoot.parent;
15521
- if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot && isHookCall$2(callbackCall, "useCallback") && isDirectJsxEventHandlerValue(callbackCall)) return true;
15904
+ if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot && isReactHookCall(callbackCall, "useCallback", context.scopes) && isDirectJsxEventHandlerValue(callbackCall)) return true;
15522
15905
  const parentNode = functionNode.parent;
15523
15906
  if (isDirectJsxEventHandlerValue(functionNode)) return true;
15524
15907
  if (!isNodeOfType(parentNode, "Property") || parentNode.value !== functionNode || parentNode.computed) return false;
@@ -15554,12 +15937,12 @@ const effectNeedsCleanup = defineRule({
15554
15937
  };
15555
15938
  return {
15556
15939
  CallExpression(node) {
15557
- if (isHookCall$2(node, "useCallback")) {
15940
+ if (isReactHookCall(node, "useCallback", context.scopes)) {
15558
15941
  const retainedCallback = getEffectCallback(node);
15559
15942
  if (retainedCallback && !isInlineRetainedHandlerFunction(retainedCallback, context)) reportRetainedLeak(retainedCallback);
15560
15943
  return;
15561
15944
  }
15562
- if (!isHookCall$2(node, CLEANUP_EFFECT_HOOK_NAMES)) return;
15945
+ if (!isReactHookCall(node, CLEANUP_EFFECT_HOOK_NAMES, context.scopes)) return;
15563
15946
  const callback = getEffectCallback(node);
15564
15947
  if (!callback) return;
15565
15948
  const usages = removeSynchronouslyReleasedUsages(callback, findSubscribeLikeUsages(callback, context), context);
@@ -15567,7 +15950,7 @@ const effectNeedsCleanup = defineRule({
15567
15950
  const firstUsage = findFirstUsageWithoutCleanup(callback, usages, context);
15568
15951
  if (!firstUsage) return;
15569
15952
  const resourceNoun = RESOURCE_NOUN_BY_KIND[firstUsage.kind];
15570
- const hookName = getCalleeName$2(node) ?? "effect";
15953
+ const hookName = getCalleeName$1(node) ?? "effect";
15571
15954
  context.report({
15572
15955
  node,
15573
15956
  message: `\`${firstUsage.resourceName}\` creates a ${resourceNoun} in ${hookName} without guaranteed cleanup. Return a cleanup function that owns every allocation so it does not leak after unmount.`
@@ -16910,7 +17293,38 @@ const symbolHasStableImportedAlias = (symbol, scopes) => {
16910
17293
  const resolvedSymbol = resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes);
16911
17294
  return resolvedSymbol !== null && resolvedSymbol !== symbol && resolvedSymbol.kind === "import";
16912
17295
  };
16913
- const symbolHasStableValue = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => symbolHasStableHookOrigin(symbol, scopes) || symbolHasStableImportedAlias(symbol, scopes) || symbolHasStableFunctionOrigin(symbol, scopes, visitedSymbolIds) || symbolHasStableMemoizedOrigin(symbol, scopes, visitedSymbolIds);
17296
+ const isAssignmentTarget = (node) => {
17297
+ let currentNode = findTransparentExpressionRoot(node);
17298
+ while (currentNode.parent) {
17299
+ const parentNode = currentNode.parent;
17300
+ if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
17301
+ if (isNodeOfType(parentNode, "UpdateExpression")) return parentNode.argument === currentNode;
17302
+ if (isNodeOfType(parentNode, "UnaryExpression")) return parentNode.operator === "delete" && parentNode.argument === currentNode;
17303
+ if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
17304
+ if (isNodeOfType(parentNode, "ArrayPattern") && parentNode.elements.some((element) => element === currentNode) || isNodeOfType(parentNode, "ObjectPattern") && parentNode.properties.some((property) => property === currentNode) || isNodeOfType(parentNode, "RestElement") && parentNode.argument === currentNode || isNodeOfType(parentNode, "AssignmentPattern") && parentNode.left === currentNode || isNodeOfType(parentNode, "Property") && parentNode.value === currentNode && isNodeOfType(parentNode.parent, "ObjectPattern")) {
17305
+ currentNode = parentNode;
17306
+ continue;
17307
+ }
17308
+ return false;
17309
+ }
17310
+ return false;
17311
+ };
17312
+ const symbolHasStableRefLazyInitialization = (symbol, scopes) => {
17313
+ if (symbol.kind !== "const" || symbol.references.some((reference) => reference.flag !== "read")) return false;
17314
+ const initializer = symbol.initializer ? unwrapExpression$3(symbol.initializer) : null;
17315
+ if (!isNodeOfType(initializer, "AssignmentExpression") || initializer.operator !== "??=") return false;
17316
+ const refSymbol = resolveReactRefSymbol(unwrapExpression$3(initializer.left), scopes);
17317
+ if (!refSymbol) return false;
17318
+ return refSymbol.references.every((reference) => {
17319
+ const memberExpression = findTransparentExpressionRoot(reference.identifier).parent;
17320
+ if (!isNodeOfType(memberExpression, "MemberExpression") || unwrapExpression$3(memberExpression.object) !== reference.identifier || getStaticPropertyName(memberExpression) !== "current") return false;
17321
+ const referenceRoot = findTransparentExpressionRoot(memberExpression);
17322
+ const parentNode = referenceRoot.parent;
17323
+ if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.left === referenceRoot) return parentNode === initializer;
17324
+ return !isAssignmentTarget(referenceRoot);
17325
+ });
17326
+ };
17327
+ const symbolHasStableValue = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => symbolHasStableHookOrigin(symbol, scopes) || symbolHasStableRefLazyInitialization(symbol, scopes) || symbolHasStableImportedAlias(symbol, scopes) || symbolHasStableFunctionOrigin(symbol, scopes, visitedSymbolIds) || symbolHasStableMemoizedOrigin(symbol, scopes, visitedSymbolIds);
16914
17328
  //#endregion
16915
17329
  //#region src/plugin/utils/symbol-has-react-use-effect-event-origin.ts
16916
17330
  const symbolHasReactUseEffectEventOrigin = (symbol, scopes) => {
@@ -17112,7 +17526,7 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allow
17112
17526
  keys.add(depKey);
17113
17527
  continue;
17114
17528
  }
17115
- const identitySourceKeys = resolveReactiveIdentitySourceKeys(symbol, scopes);
17529
+ const identitySourceKeys = resolvePureCalledFunctionSourceKeys(reference, symbol, scopes) ?? resolveRenderDerivedMutableSourceKeys(reference, symbol, scopes) ?? resolveReactiveIdentitySourceKeys(symbol, scopes);
17116
17530
  if (identitySourceKeys) {
17117
17531
  if (identitySourceKeys.size === 0) stableCapturedNames.add(depKey);
17118
17532
  for (const identitySourceKey of identitySourceKeys) keys.add(identitySourceKey);
@@ -17195,6 +17609,161 @@ const resolveReactiveIdentitySourceKeys = (symbol, scopes) => {
17195
17609
  if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier || symbol.references.some((reference) => reference.flag !== "read")) return null;
17196
17610
  return resolveIdentitySourceKeysFromExpression(symbol.initializer, scopes, new Set([symbol.id]));
17197
17611
  };
17612
+ const isPureDerivedExpression = (expression) => {
17613
+ const candidate = unwrapExpression$3(expression);
17614
+ if (isNodeOfType(candidate, "Literal") || isNodeOfType(candidate, "Identifier")) return true;
17615
+ if (isNodeOfType(candidate, "MemberExpression")) return isPureDerivedExpression(candidate.object) && (!candidate.computed || isPureDerivedExpression(candidate.property));
17616
+ if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return isPureDerivedExpression(candidate.left) && isPureDerivedExpression(candidate.right);
17617
+ if (isNodeOfType(candidate, "UnaryExpression")) return candidate.operator !== "delete" && isPureDerivedExpression(candidate.argument);
17618
+ if (isNodeOfType(candidate, "ConditionalExpression")) return isPureDerivedExpression(candidate.test) && isPureDerivedExpression(candidate.consequent) && isPureDerivedExpression(candidate.alternate);
17619
+ if (isNodeOfType(candidate, "TemplateLiteral")) return candidate.expressions.every((nestedExpression) => isPureDerivedExpression(nestedExpression));
17620
+ return false;
17621
+ };
17622
+ const isPureDerivedStatement = (statement) => {
17623
+ if (isNodeOfType(statement, "BlockStatement")) return statement.body.every((nestedStatement) => isPureDerivedStatement(nestedStatement));
17624
+ if (isNodeOfType(statement, "ReturnStatement")) return !statement.argument || isPureDerivedExpression(statement.argument);
17625
+ if (isNodeOfType(statement, "IfStatement")) return isPureDerivedExpression(statement.test) && isPureDerivedStatement(statement.consequent) && (!statement.alternate || isPureDerivedStatement(statement.alternate));
17626
+ return false;
17627
+ };
17628
+ const isPureDerivedFunction = (functionNode) => {
17629
+ if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return false;
17630
+ if (functionNode.async || functionNode.generator) return false;
17631
+ return isNodeOfType(functionNode.body, "BlockStatement") ? isPureDerivedStatement(functionNode.body) : isPureDerivedExpression(functionNode.body);
17632
+ };
17633
+ const resolvePureCalledFunctionSourceKeys = (reference, symbol, scopes) => {
17634
+ if (symbol.references.some((symbolReference) => symbolReference.flag !== "read")) return null;
17635
+ const referenceRoot = findTransparentExpressionRoot(reference.identifier);
17636
+ const callExpression = referenceRoot.parent;
17637
+ if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== referenceRoot) return null;
17638
+ const functionNode = getFunctionValueNode(symbol);
17639
+ if (!functionNode || !isPureDerivedFunction(functionNode)) return null;
17640
+ const sourceKeys = /* @__PURE__ */ new Set();
17641
+ for (const capturedReference of closureCaptures(functionNode, scopes)) {
17642
+ const capturedSymbol = capturedReference.resolvedSymbol;
17643
+ if (!capturedSymbol || capturedSymbol.id === symbol.id) continue;
17644
+ if (isOutsideAllFunctions(capturedSymbol) || symbolHasStableValue(capturedSymbol, scopes)) continue;
17645
+ const capturedKey = computeDepKey(capturedReference);
17646
+ if (!capturedKey) return null;
17647
+ if (capturedKey === capturedSymbol.name) {
17648
+ const nestedSourceKeys = resolveReactiveIdentitySourceKeys(capturedSymbol, scopes);
17649
+ if (nestedSourceKeys) {
17650
+ for (const nestedSourceKey of nestedSourceKeys) sourceKeys.add(nestedSourceKey);
17651
+ continue;
17652
+ }
17653
+ }
17654
+ sourceKeys.add(capturedKey);
17655
+ }
17656
+ return sourceKeys.size > 0 ? sourceKeys : null;
17657
+ };
17658
+ const mergeDerivedExpressionSourceKeys = (expressions, scopes, visitedSymbolIds) => {
17659
+ const sourceKeys = /* @__PURE__ */ new Set();
17660
+ for (const expression of expressions) {
17661
+ const expressionSourceKeys = resolveDerivedExpressionSourceKeys(expression, scopes, visitedSymbolIds);
17662
+ if (!expressionSourceKeys) return null;
17663
+ for (const expressionSourceKey of expressionSourceKeys) sourceKeys.add(expressionSourceKey);
17664
+ }
17665
+ return sourceKeys;
17666
+ };
17667
+ const resolveDerivedExpressionSourceKeys = (expression, scopes, visitedSymbolIds) => {
17668
+ const candidate = unwrapExpression$3(expression);
17669
+ if (isNodeOfType(candidate, "Literal")) return /* @__PURE__ */ new Set();
17670
+ if (isNodeOfType(candidate, "Identifier")) {
17671
+ if (scopes.isGlobalReference(candidate)) return /* @__PURE__ */ new Set();
17672
+ const sourceSymbol = scopes.symbolFor(candidate);
17673
+ if (!sourceSymbol) return null;
17674
+ if (isOutsideAllFunctions(sourceSymbol) || symbolHasStableValue(sourceSymbol, scopes)) return /* @__PURE__ */ new Set();
17675
+ if (sourceSymbol.kind === "const" && sourceSymbol.initializer && isNodeOfType(sourceSymbol.declarationNode, "VariableDeclarator") && sourceSymbol.declarationNode.id === sourceSymbol.bindingIdentifier && sourceSymbol.references.every((sourceReference) => sourceReference.flag === "read") && !visitedSymbolIds.has(sourceSymbol.id)) {
17676
+ visitedSymbolIds.add(sourceSymbol.id);
17677
+ const sourceKeys = resolveDerivedExpressionSourceKeys(sourceSymbol.initializer, scopes, visitedSymbolIds);
17678
+ visitedSymbolIds.delete(sourceSymbol.id);
17679
+ if (sourceKeys) return sourceKeys;
17680
+ }
17681
+ return new Set([sourceSymbol.name]);
17682
+ }
17683
+ if (isNodeOfType(candidate, "MemberExpression")) {
17684
+ if (hasComputedMemberExpression(candidate)) return null;
17685
+ const sourceKey = stringifyMemberChain(candidate);
17686
+ const rootIdentifier = getMemberRootIdentifier(candidate);
17687
+ const rootSymbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
17688
+ if (!sourceKey || !rootSymbol) return null;
17689
+ if (isOutsideAllFunctions(rootSymbol) || symbolHasStableValue(rootSymbol, scopes)) return /* @__PURE__ */ new Set();
17690
+ return new Set([sourceKey]);
17691
+ }
17692
+ if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return mergeDerivedExpressionSourceKeys([candidate.left, candidate.right], scopes, visitedSymbolIds);
17693
+ if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator !== "delete") return resolveDerivedExpressionSourceKeys(candidate.argument, scopes, visitedSymbolIds);
17694
+ if (isNodeOfType(candidate, "ConditionalExpression")) return mergeDerivedExpressionSourceKeys([
17695
+ candidate.test,
17696
+ candidate.consequent,
17697
+ candidate.alternate
17698
+ ], scopes, visitedSymbolIds);
17699
+ if (isNodeOfType(candidate, "TemplateLiteral")) return mergeDerivedExpressionSourceKeys(candidate.expressions, scopes, visitedSymbolIds);
17700
+ if (isNodeOfType(candidate, "NewExpression")) {
17701
+ const callee = unwrapExpression$3(candidate.callee);
17702
+ if (!isNodeOfType(callee, "Identifier") || callee.name !== "Error" || !scopes.isGlobalReference(callee)) return null;
17703
+ const argumentsToAnalyze = [];
17704
+ for (const argument of candidate.arguments) {
17705
+ if (!isAstNode(argument) || isNodeOfType(argument, "SpreadElement")) return null;
17706
+ argumentsToAnalyze.push(argument);
17707
+ }
17708
+ return mergeDerivedExpressionSourceKeys(argumentsToAnalyze, scopes, visitedSymbolIds);
17709
+ }
17710
+ return null;
17711
+ };
17712
+ const resolveWriteControlSourceKeys = (assignment, boundaryFunction, scopes) => {
17713
+ const sourceKeys = /* @__PURE__ */ new Set();
17714
+ let currentNode = assignment;
17715
+ while (currentNode.parent && currentNode.parent !== boundaryFunction) {
17716
+ const parentNode = currentNode.parent;
17717
+ if (isNodeOfType(parentNode, "IfStatement")) {
17718
+ if (parentNode.test === currentNode) return null;
17719
+ const testSourceKeys = resolveDerivedExpressionSourceKeys(parentNode.test, scopes, /* @__PURE__ */ new Set());
17720
+ if (!testSourceKeys) return null;
17721
+ for (const testSourceKey of testSourceKeys) sourceKeys.add(testSourceKey);
17722
+ } else if (!isNodeOfType(parentNode, "ExpressionStatement") && !isNodeOfType(parentNode, "BlockStatement")) return null;
17723
+ currentNode = parentNode;
17724
+ }
17725
+ return currentNode.parent === boundaryFunction ? sourceKeys : null;
17726
+ };
17727
+ const isReadOnlyInitialStateUse = (referenceNode, scopes) => {
17728
+ const referenceRoot = findTransparentExpressionRoot(referenceNode);
17729
+ const callExpression = referenceRoot.parent;
17730
+ return isNodeOfType(callExpression, "CallExpression") && callExpression.arguments.some((argument) => argument === referenceRoot) && isReactApiCall(callExpression, "useState", scopes, {
17731
+ allowGlobalReactNamespace: true,
17732
+ allowUnboundBareCalls: true,
17733
+ resolveNamedAliases: true
17734
+ });
17735
+ };
17736
+ const resolveRenderDerivedMutableSourceKeys = (capturedReference, symbol, scopes) => {
17737
+ if (symbol.kind !== "let" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
17738
+ const boundaryFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
17739
+ if (!boundaryFunction) return null;
17740
+ const capturingFunction = findEnclosingFunction$1(capturedReference.identifier);
17741
+ if (!capturingFunction || capturingFunction === boundaryFunction) return null;
17742
+ const sourceKeys = /* @__PURE__ */ new Set();
17743
+ if (symbol.initializer) {
17744
+ const initializerSourceKeys = resolveDerivedExpressionSourceKeys(symbol.initializer, scopes, new Set([symbol.id]));
17745
+ if (!initializerSourceKeys) return null;
17746
+ for (const initializerSourceKey of initializerSourceKeys) sourceKeys.add(initializerSourceKey);
17747
+ }
17748
+ let writeCount = 0;
17749
+ for (const symbolReference of symbol.references) {
17750
+ if (symbolReference.flag === "read") {
17751
+ if (findEnclosingFunction$1(symbolReference.identifier) !== capturingFunction && !isReadOnlyInitialStateUse(symbolReference.identifier, scopes)) return null;
17752
+ continue;
17753
+ }
17754
+ if (symbolReference.flag !== "write") return null;
17755
+ const referenceRoot = findTransparentExpressionRoot(symbolReference.identifier);
17756
+ const assignment = referenceRoot.parent;
17757
+ if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== referenceRoot || findEnclosingFunction$1(referenceRoot) !== boundaryFunction) return null;
17758
+ const assignmentSourceKeys = resolveDerivedExpressionSourceKeys(assignment.right, scopes, new Set([symbol.id]));
17759
+ const controlSourceKeys = resolveWriteControlSourceKeys(assignment, boundaryFunction, scopes);
17760
+ if (!assignmentSourceKeys || !controlSourceKeys) return null;
17761
+ for (const assignmentSourceKey of assignmentSourceKeys) sourceKeys.add(assignmentSourceKey);
17762
+ for (const controlSourceKey of controlSourceKeys) sourceKeys.add(controlSourceKey);
17763
+ writeCount += 1;
17764
+ }
17765
+ return writeCount > 0 && sourceKeys.size > 0 ? sourceKeys : null;
17766
+ };
17198
17767
  const isUseCallbackResultDep = (node, scopes) => {
17199
17768
  const rootSymbol = getRootSymbol(node, scopes);
17200
17769
  const initializer = rootSymbol?.initializer ? unwrapExpression$3(rootSymbol.initializer) : null;
@@ -17938,7 +18507,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
17938
18507
  if (!isUsed) continue;
17939
18508
  const reportNode = declaredKeyToReportNode.get(declaredKey) ?? depsArgument;
17940
18509
  const rootSymbol = getRootSymbol(reportNode, context.scopes);
17941
- if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) || !isUnstableInitializer(rootSymbol.initializer)) continue;
18510
+ if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) || symbolHasStableValue(rootSymbol, context.scopes) || !isUnstableInitializer(rootSymbol.initializer)) continue;
17942
18511
  context.report({
17943
18512
  node: reportNode,
17944
18513
  message: buildUnstableDepMessage(hookName, declaredKey)
@@ -18254,7 +18823,7 @@ const flattenCalleeName = (callee) => {
18254
18823
  const PRAGMA = "React";
18255
18824
  const isReactFunctionCall = (node, expectedCall) => {
18256
18825
  if (!isNodeOfType(node, "CallExpression")) return false;
18257
- if (getCalleeName$2(node) !== expectedCall) return false;
18826
+ if (getCalleeName$1(node) !== expectedCall) return false;
18258
18827
  if (isNodeOfType(node.callee, "MemberExpression")) {
18259
18828
  const receiver = stripParenExpression(node.callee.object);
18260
18829
  return isNodeOfType(receiver, "Identifier") && receiver.name === PRAGMA;
@@ -18563,8 +19132,8 @@ const hooksNoNanInDeps = defineRule({
18563
19132
  severity: "warn",
18564
19133
  recommendation: "Remove `NaN` (or `Number.NaN`) from the dependency array. If a value can be NaN at runtime, normalise it (`Number.isNaN(x) ? 0 : x`) before passing it.",
18565
19134
  create: (context) => ({ CallExpression(node) {
18566
- if (!isHookCall$2(node, HOOKS_WITH_DEP_ARRAY)) return;
18567
- const depsIndex = getCalleeName$2(node) === "useImperativeHandle" ? 2 : 1;
19135
+ if (!isReactHookCall(node, HOOKS_WITH_DEP_ARRAY, context.scopes)) return;
19136
+ const depsIndex = isReactHookCall(node, "useImperativeHandle", context.scopes) ? 2 : 1;
18568
19137
  const depsArgument = node.arguments[depsIndex];
18569
19138
  if (!depsArgument || !isNodeOfType(depsArgument, "ArrayExpression")) return;
18570
19139
  for (const element of depsArgument.elements) {
@@ -19735,7 +20304,7 @@ const isImportedSelectAtom = (callExpression) => {
19735
20304
  const isDeferredCallbackPosition$1 = (functionNode) => {
19736
20305
  const parent = functionNode.parent;
19737
20306
  if (isNodeOfType(parent, "CallExpression") && parent.arguments?.[0] === functionNode) {
19738
- const hookName = getCalleeName$2(parent);
20307
+ const hookName = getCalleeName$1(parent);
19739
20308
  if (hookName && MEMOIZING_HOOK_NAMES$1.has(hookName)) return true;
19740
20309
  if (hookName && EFFECT_HOOK_NAMES$1.has(hookName) && Boolean(parent.arguments?.[1])) return true;
19741
20310
  }
@@ -29806,7 +30375,7 @@ const DEFERRING_CALLEE_NAMES$1 = new Set([
29806
30375
  "on",
29807
30376
  "once"
29808
30377
  ]);
29809
- const getCalleeName$1 = (callee) => {
30378
+ const getCalleeName = (callee) => {
29810
30379
  if (!callee) return null;
29811
30380
  if (isNodeOfType(callee, "Identifier")) return callee.name;
29812
30381
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
@@ -29818,11 +30387,11 @@ const isDeferredCallbackPosition = (expression) => {
29818
30387
  const parent = parentOf(expression);
29819
30388
  if (!parent) return false;
29820
30389
  if (isNodeOfType(parent, "CallExpression") && argumentsInclude(parent.arguments, expression)) {
29821
- const name = getCalleeName$1(parent.callee);
30390
+ const name = getCalleeName(parent.callee);
29822
30391
  if (name && DEFERRING_CALLEE_NAMES$1.has(name)) return true;
29823
30392
  }
29824
30393
  if (isNodeOfType(parent, "NewExpression") && argumentsInclude(parent.arguments, expression)) {
29825
- const name = getCalleeName$1(parent.callee);
30394
+ const name = getCalleeName(parent.callee);
29826
30395
  if (name && (name.endsWith("Observer") || name === "Promise")) return true;
29827
30396
  }
29828
30397
  if (isNodeOfType(parent, "AssignmentExpression") && parent.right === expression && isNodeOfType(parent.left, "MemberExpression") && isNodeOfType(parent.left.property, "Identifier") && parent.left.property.name.startsWith("on")) return true;
@@ -30242,14 +30811,6 @@ const isHookCallee$1 = (analysis, node, hookName) => {
30242
30811
  if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
30243
30812
  return false;
30244
30813
  };
30245
- const isUseEffect = (node) => {
30246
- if (!node || !isNodeOfType(node, "CallExpression")) return false;
30247
- const callee = node.callee;
30248
- if (isNodeOfType(callee, "Identifier") && callee.name === "useEffect") return true;
30249
- if (!isNodeOfType(callee, "MemberExpression")) return false;
30250
- const receiver = stripParenExpression(callee.object);
30251
- return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useEffect";
30252
- };
30253
30814
  const getEffectFn = (analysis, node) => {
30254
30815
  if (!isNodeOfType(node, "CallExpression")) return null;
30255
30816
  const fn = node.arguments?.[0];
@@ -30347,7 +30908,27 @@ const isRefCurrent = (ref) => {
30347
30908
  if (!isNodeOfType(parent.property, "Identifier")) return false;
30348
30909
  return parent.property.name === "current";
30349
30910
  };
30350
- const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isStateSetter(analysis, innerRef));
30911
+ const resolveStateSetterReference = (analysis, ref) => {
30912
+ const visitedReferences = /* @__PURE__ */ new Set();
30913
+ let currentReference = ref;
30914
+ while (currentReference && !visitedReferences.has(currentReference)) {
30915
+ if (isStateSetter(analysis, currentReference)) return currentReference;
30916
+ visitedReferences.add(currentReference);
30917
+ const definitions = currentReference.resolved?.defs ?? [];
30918
+ if (definitions.length !== 1) return null;
30919
+ const definitionNode = definitions[0].node;
30920
+ if (!isNodeOfType(definitionNode, "VariableDeclarator")) return null;
30921
+ if (!isNodeOfType(definitionNode.id, "Identifier")) return null;
30922
+ const declaration = definitionNode.parent;
30923
+ if (!isNodeOfType(declaration, "VariableDeclaration") || declaration.kind !== "const") return null;
30924
+ if (!definitionNode.init) return null;
30925
+ const initializer = stripParenExpression(definitionNode.init);
30926
+ if (!isNodeOfType(initializer, "Identifier")) return null;
30927
+ currentReference = getRef(analysis, initializer);
30928
+ }
30929
+ return null;
30930
+ };
30931
+ const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => resolveStateSetterReference(analysis, innerRef) !== null);
30351
30932
  const isSyncStateSetterCall = (analysis, ref, effectFn) => isStateSetterCall(analysis, ref) && isSynchronous(ref.identifier, effectFn) && !resolvesToAsyncFunction(ref);
30352
30933
  const HANDLER_NAMED_METHOD_PATTERN = /^(on|handle)[A-Z]/;
30353
30934
  const SYNCHRONOUS_CALLBACK_ARGUMENT_INDEX_BY_METHOD = new Map([
@@ -30498,9 +31079,11 @@ const isPropCallbackInvocationRef = (analysis, ref, options = {}) => {
30498
31079
  };
30499
31080
  const isRefCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isRefCurrent(innerRef) || isRef(analysis, innerRef));
30500
31081
  const getUseStateDecl = (analysis, ref) => {
30501
- let node = getUpstreamRefs(analysis, ref).find((upRef) => isHookCallee$1(analysis, upRef.identifier, "useState"))?.identifier;
30502
- while (node && !isNodeOfType(node, "VariableDeclarator")) node = node.parent;
30503
- return node ?? null;
31082
+ const definition = getUpstreamRefs(analysis, ref).find((upstreamReference) => isState(analysis, upstreamReference) || isStateSetter(analysis, upstreamReference))?.resolved?.defs.find((candidateDefinition) => {
31083
+ const definitionNode = candidateDefinition.node;
31084
+ return isNodeOfType(definitionNode, "VariableDeclarator") && isNodeOfType(definitionNode.init, "CallExpression") && isHookCallee$1(analysis, definitionNode.init.callee, "useState");
31085
+ });
31086
+ return definition ? definition.node : null;
30504
31087
  };
30505
31088
  const isCleanupReturnArgument = (analysis, node) => {
30506
31089
  if (isFunctionLike$1(node)) return true;
@@ -31689,6 +32272,7 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
31689
32272
  sourceReferences,
31690
32273
  isDeferred: frame.isDeferred,
31691
32274
  isRenderKnownCopy,
32275
+ isSynchronousRenderValue: !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue,
31692
32276
  matchesStateInitializer: doesMatchStateInitializer,
31693
32277
  resetsSourceState: false
31694
32278
  });
@@ -31704,22 +32288,71 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
31704
32288
  });
31705
32289
  };
31706
32290
  //#endregion
32291
+ //#region src/plugin/rules/state-and-effects/utils/has-deferred-or-external-effect-work.ts
32292
+ const DEFERRED_MEMBER_NAMES = new Set([
32293
+ "catch",
32294
+ "finally",
32295
+ "then"
32296
+ ]);
32297
+ const hasDeferredOrExternalEffectWork = (analysis, effectNode, scopes) => {
32298
+ const effectFunction = getEffectFn(analysis, effectNode);
32299
+ if (!effectFunction) return false;
32300
+ if (containsFetchCall(effectFunction, { stopAtFunctionBoundary: true })) return true;
32301
+ const effectInvokedFunctions = collectEffectInvokedFunctions(effectFunction);
32302
+ let didFindDeferredOrExternalWork = false;
32303
+ walkAst(effectFunction, (child) => {
32304
+ if (didFindDeferredOrExternalWork) return false;
32305
+ if (child !== effectFunction && isFunctionLike$1(child) && !effectInvokedFunctions.has(child)) return false;
32306
+ if (isNodeOfType(child, "AssignmentExpression")) {
32307
+ const assignmentTarget = child.left;
32308
+ if ((isNodeOfType(assignmentTarget, "MemberExpression") ? getStaticPropertyName(assignmentTarget) : null)?.startsWith("on") && isFunctionLike$1(child.right)) {
32309
+ didFindDeferredOrExternalWork = true;
32310
+ return false;
32311
+ }
32312
+ }
32313
+ if (!isNodeOfType(child, "CallExpression")) return;
32314
+ if (isSubscribeOrObserveCallExpression(child)) {
32315
+ didFindDeferredOrExternalWork = true;
32316
+ return false;
32317
+ }
32318
+ const localFunction = resolveExactLocalFunction(child.callee, scopes);
32319
+ if (isFunctionLike$1(localFunction) && localFunction.async) {
32320
+ didFindDeferredOrExternalWork = true;
32321
+ return false;
32322
+ }
32323
+ const callee = child.callee;
32324
+ if (isNodeOfType(callee, "Identifier") && TIMER_AND_SCHEDULER_DIRECT_CALLEE_NAMES.has(callee.name)) {
32325
+ didFindDeferredOrExternalWork = true;
32326
+ return false;
32327
+ }
32328
+ const memberName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
32329
+ if (memberName && DEFERRED_MEMBER_NAMES.has(memberName)) {
32330
+ didFindDeferredOrExternalWork = true;
32331
+ return false;
32332
+ }
32333
+ });
32334
+ return didFindDeferredOrExternalWork;
32335
+ };
32336
+ //#endregion
31707
32337
  //#region src/plugin/rules/state-and-effects/no-adjust-state-on-prop-change.ts
31708
32338
  const noAdjustStateOnPropChange = defineRule({
31709
32339
  id: "no-adjust-state-on-prop-change",
31710
- title: "State synced to a prop inside an effect",
32340
+ title: "State adjusted after a prop changes",
31711
32341
  severity: "warn",
31712
32342
  tags: ["test-noise"],
31713
- recommendation: "Adjust the state inline during render with a `prev`-prop comparison (`if (prop !== prevProp) { setPrevProp(prop); setX(...); }`), or refactor to remove the duplicated state. Routing the adjustment through a useEffect forces an extra render with a stale UI between the two commits. See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes",
32343
+ recommendation: "Remove the adjustment effect by deriving values during render, resetting the component with a key, or updating related state in the event that changes the prop. Avoid tracking the previous prop in more state, which preserves the duplication. See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes",
31714
32344
  create: (context) => ({ CallExpression(node) {
31715
- if (!isUseEffect(node)) return;
32345
+ if (!isReactHookCall(node, "useEffect", context.scopes)) return;
31716
32346
  const analysis = getProgramAnalysis(node);
31717
32347
  if (!analysis) return;
31718
32348
  const dependencyReferences = getEffectDepsRefs(analysis, node);
31719
32349
  if (!dependencyReferences) return;
31720
32350
  if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
31721
- for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
31722
- if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
32351
+ const facts = collectEffectStateWriteFacts(analysis, context, node, context.filename);
32352
+ if (hasCleanup(analysis, node) || hasDeferredOrExternalEffectWork(analysis, node, context.scopes) || facts.some((fact) => fact.isDeferred)) return;
32353
+ for (const fact of facts) {
32354
+ if (!fact.isSynchronousRenderValue || fact.resetsSourceState) continue;
32355
+ if (fact.sourceReferences.flatMap((reference) => getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) continue;
31723
32356
  context.report({
31724
32357
  node: fact.callExpression,
31725
32358
  message: "This effect adjusts state after a prop changes, so users briefly see the stale value."
@@ -33732,7 +34365,7 @@ const declarationBodyContainsHookCall = (symbol) => {
33732
34365
  walkAst(componentFunction, (descendant) => {
33733
34366
  if (didFindHookCall) return false;
33734
34367
  if (!isNodeOfType(descendant, "CallExpression")) return;
33735
- const calleeName = getCalleeName$2(descendant);
34368
+ const calleeName = getCalleeName$1(descendant);
33736
34369
  if (calleeName && isReactHookName(calleeName)) {
33737
34370
  didFindHookCall = true;
33738
34371
  return false;
@@ -33757,7 +34390,7 @@ const isReturnedFromUseCallbackAdapter = (callNode) => {
33757
34390
  if (isNodeOfType(parent, "ArrowFunctionExpression")) {
33758
34391
  if (parent.body !== current) return false;
33759
34392
  const grandparent = parent.parent;
33760
- return isNodeOfType(grandparent, "CallExpression") && getCalleeName$2(grandparent) === "useCallback" && grandparent.arguments.some((argumentNode) => argumentNode === parent);
34393
+ return isNodeOfType(grandparent, "CallExpression") && getCalleeName$1(grandparent) === "useCallback" && grandparent.arguments.some((argumentNode) => argumentNode === parent);
33761
34394
  }
33762
34395
  if (!isNodeOfType(parent, "ConditionalExpression") && !isNodeOfType(parent, "LogicalExpression")) return false;
33763
34396
  current = parent;
@@ -34439,7 +35072,7 @@ const noChainStateUpdates = defineRule({
34439
35072
  if (!callExpr) continue;
34440
35073
  if (!isReachableFromStateTrigger(callExpr)) continue;
34441
35074
  if (!readsPostMountValueThroughLocals(callExpr, effectFn, { ignoreBareRefCurrent: true })) continue;
34442
- const declarator = getUseStateDeclarator(ref);
35075
+ const declarator = getUseStateDeclarator(resolveStateSetterReference(analysis, ref) ?? ref);
34443
35076
  if (declarator) domSyncedStateDeclarators.add(declarator);
34444
35077
  }
34445
35078
  for (const ref of effectFnRefs) {
@@ -34448,7 +35081,7 @@ const noChainStateUpdates = defineRule({
34448
35081
  if (!callExpr) continue;
34449
35082
  if (!isReachableFromStateTrigger(callExpr)) continue;
34450
35083
  if (getArgsUpstreamRefs(analysis, ref).some((argRef) => isState(analysis, argRef))) continue;
34451
- const setterDeclarator = getUseStateDeclarator(ref);
35084
+ const setterDeclarator = getUseStateDeclarator(resolveStateSetterReference(analysis, ref) ?? ref);
34452
35085
  if (setterDeclarator && domSyncedStateDeclarators.has(setterDeclarator)) continue;
34453
35086
  const isSelfTargeting = setterDeclarator !== null && stateDepDeclarators.has(setterDeclarator);
34454
35087
  const setterArguments = isNodeOfType(callExpr, "CallExpression") ? callExpr.arguments ?? [] : [];
@@ -36289,7 +36922,7 @@ const noDerivedState = defineRule({
36289
36922
  for (const fact of collectRenderStateWriteFacts(analysis, componentBody, context.filename)) reportStateWrite(fact.callExpression, fact.stateDeclarator);
36290
36923
  } }).visitors,
36291
36924
  CallExpression(node) {
36292
- if (!isUseEffect(node)) return;
36925
+ if (!isReactHookCall(node, "useEffect", context.scopes)) return;
36293
36926
  const analysis = getProgramAnalysis(node);
36294
36927
  if (!analysis) return;
36295
36928
  for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
@@ -36309,7 +36942,7 @@ const noDerivedStateEffect = defineRule({
36309
36942
  tags: ["test-noise"],
36310
36943
  recommendation: "Work out derived values while rendering: `const x = fn(dep)`. To reset a component's state when a prop changes, give it a key prop: `<Component key={prop} />`. See https://react.dev/learn/you-might-not-need-an-effect",
36311
36944
  create: (context) => ({ CallExpression(node) {
36312
- if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
36945
+ if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes)) return;
36313
36946
  const analysis = getProgramAnalysis(node);
36314
36947
  if (!analysis) return;
36315
36948
  if (!collectEffectStateWriteFacts(analysis, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
@@ -36432,7 +37065,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
36432
37065
  if (isFunctionLike$1(cursor)) {
36433
37066
  const parent = cursor.parent ?? null;
36434
37067
  if (parent && isNodeOfType(parent, "CallExpression")) {
36435
- const calleeName = getCalleeName$2(parent);
37068
+ const calleeName = getCalleeName$1(parent);
36436
37069
  if (calleeName !== null && EFFECT_HOOK_NAME_PATTERN.test(calleeName) && (parent.arguments ?? []).some((argument) => argument === cursor)) return cursor;
36437
37070
  }
36438
37071
  }
@@ -36503,7 +37136,7 @@ const isNonHandlerHookCallback = (functionNode) => {
36503
37136
  const parent = functionNode.parent ?? null;
36504
37137
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
36505
37138
  if (!(parent.arguments ?? []).some((argument) => argument === functionNode)) return false;
36506
- const calleeName = getCalleeName$2(parent);
37139
+ const calleeName = getCalleeName$1(parent);
36507
37140
  return calleeName !== null && isReactHookName(calleeName) && calleeName !== "useCallback";
36508
37141
  };
36509
37142
  const isHandlerShapedReseed = (setterCall, componentFunction) => {
@@ -36585,7 +37218,7 @@ const noDerivedUseState = defineRule({
36585
37218
  return {
36586
37219
  ...propStackTracker.visitors,
36587
37220
  CallExpression(node) {
36588
- if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
37221
+ if (!isReactHookCall(node, "useState", context.scopes) || !node.arguments?.length) return;
36589
37222
  const seed = unwrapInitializerSeed(node.arguments[0]);
36590
37223
  const reportStalePropCopy = (propName) => {
36591
37224
  if (isIntentionalSnapshotState(node)) return;
@@ -37261,7 +37894,7 @@ const noDirectMutationState = defineRule({
37261
37894
  const isSetterIdentifier = (name) => SETTER_PATTERN.test(name);
37262
37895
  //#endregion
37263
37896
  //#region src/plugin/rules/state-and-effects/utils/collect-use-state-bindings.ts
37264
- const collectUseStateBindings = (componentBody) => {
37897
+ const collectUseStateBindings = (componentBody, scopes) => {
37265
37898
  const bindings = [];
37266
37899
  if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
37267
37900
  for (const statement of componentBody.body ?? []) {
@@ -37274,7 +37907,7 @@ const collectUseStateBindings = (componentBody) => {
37274
37907
  const setterElement = elements[1];
37275
37908
  if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
37276
37909
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
37277
- if (!isHookCall$2(declarator.init, "useState")) continue;
37910
+ if (!isReactHookCall(declarator.init, "useState", scopes)) continue;
37278
37911
  bindings.push({
37279
37912
  valueName: valueElement.name,
37280
37913
  setterName: setterElement.name,
@@ -37441,7 +38074,7 @@ const noDirectStateMutation = defineRule({
37441
38074
  create: (context) => {
37442
38075
  const checkComponent = (componentBody) => {
37443
38076
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
37444
- const bindings = collectUseStateBindings(componentBody);
38077
+ const bindings = collectUseStateBindings(componentBody, context.scopes);
37445
38078
  if (bindings.length === 0) return;
37446
38079
  const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
37447
38080
  const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
@@ -38001,25 +38634,31 @@ const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSub
38001
38634
  };
38002
38635
  //#endregion
38003
38636
  //#region src/plugin/rules/state-and-effects/no-effect-chain.ts
38004
- const findTopLevelEffectCalls = (componentBody) => {
38637
+ const findTopLevelEffectCalls = (componentBody, scopes) => {
38005
38638
  const effectCalls = [];
38006
38639
  if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
38007
38640
  for (const statement of componentBody.body ?? []) {
38008
38641
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
38009
38642
  const expression = unwrapDiscardedExpression(statement);
38010
38643
  if (!isNodeOfType(expression, "CallExpression")) continue;
38011
- if (!isHookCall$2(expression, EFFECT_HOOK_NAMES$1)) continue;
38644
+ if (!isReactHookCall(expression, EFFECT_HOOK_NAMES$1, scopes)) continue;
38012
38645
  effectCalls.push(expression);
38013
38646
  }
38014
38647
  return effectCalls;
38015
38648
  };
38016
- const collectDepIdentifierNames = (effectNode) => {
38017
- const depNames = /* @__PURE__ */ new Set();
38018
- if (!isNodeOfType(effectNode, "CallExpression")) return depNames;
38649
+ const collectDependencyStateSymbolIds = (effectNode, stateSymbolIds, scopes) => {
38650
+ const dependencyStateSymbolIds = /* @__PURE__ */ new Set();
38651
+ if (!isNodeOfType(effectNode, "CallExpression")) return dependencyStateSymbolIds;
38019
38652
  const depsNode = effectNode.arguments?.[1];
38020
- if (!isNodeOfType(depsNode, "ArrayExpression")) return depNames;
38021
- for (const element of depsNode.elements ?? []) if (isNodeOfType(element, "Identifier")) depNames.add(element.name);
38022
- return depNames;
38653
+ if (!isNodeOfType(depsNode, "ArrayExpression")) return dependencyStateSymbolIds;
38654
+ for (const element of depsNode.elements ?? []) {
38655
+ if (!element || isNodeOfType(element, "SpreadElement")) continue;
38656
+ const rootIdentifier = getRootIdentifier$1(element);
38657
+ if (!isNodeOfType(rootIdentifier, "Identifier")) continue;
38658
+ const symbol = resolveConstIdentifierAlias(rootIdentifier, scopes, true);
38659
+ if (symbol && stateSymbolIds.has(symbol.id)) dependencyStateSymbolIds.add(symbol.id);
38660
+ }
38661
+ return dependencyStateSymbolIds;
38023
38662
  };
38024
38663
  const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
38025
38664
  const analysisFunctions = new Set([effectCallback]);
@@ -38125,12 +38764,13 @@ const readStaticSetterValue = (setterCall, scopes) => {
38125
38764
  if (updater) return readStaticUpdaterReturnValue(updater, scopes);
38126
38765
  return readStaticEffectValue(argument, scopes, null, null);
38127
38766
  };
38128
- const collectStateWritesInEffect = (analysisFunctions, setterToStateName, scopes) => {
38767
+ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName, scopes) => {
38129
38768
  const stateWrites = /* @__PURE__ */ new Map();
38130
38769
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
38131
38770
  if (!isNodeOfType(child, "CallExpression")) return;
38132
38771
  if (!isNodeOfType(child.callee, "Identifier")) return;
38133
- const stateName = setterToStateName.get(child.callee.name);
38772
+ const setterSymbol = resolveConstIdentifierAlias(child.callee, scopes, true);
38773
+ const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
38134
38774
  if (!stateName) return;
38135
38775
  const writeInfo = stateWrites.get(stateName) ?? {
38136
38776
  values: /* @__PURE__ */ new Set(),
@@ -38216,11 +38856,12 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
38216
38856
  "keys",
38217
38857
  "values"
38218
38858
  ]);
38219
- const isFunctionShapedReturn = (returnedValue, setterToStateName, isExplicitReturnStatement) => {
38859
+ const isFunctionShapedReturn = (returnedValue, setterToStateName, setterSymbolIdToStateName, scopes, isExplicitReturnStatement) => {
38220
38860
  if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
38221
38861
  if (isNodeOfType(returnedValue, "CallExpression")) {
38222
38862
  if (isNodeOfType(returnedValue.callee, "Identifier")) {
38223
- if (setterToStateName.has(returnedValue.callee.name)) return false;
38863
+ const setterSymbol = resolveConstIdentifierAlias(returnedValue.callee, scopes, true);
38864
+ if (setterToStateName.has(returnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
38224
38865
  if (isSetterIdentifier(returnedValue.callee.name)) return true;
38225
38866
  }
38226
38867
  return isCleanupReturn(returnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
@@ -38244,7 +38885,7 @@ const collectStorageHookSetterNames = (componentBody) => {
38244
38885
  for (const declarator of statement.declarations ?? []) {
38245
38886
  if (!isNodeOfType(declarator.id, "ArrayPattern")) continue;
38246
38887
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
38247
- const calleeName = getCalleeName$2(declarator.init);
38888
+ const calleeName = getCalleeName$1(declarator.init);
38248
38889
  if (!calleeName || !STORAGE_HOOK_PATTERN.test(calleeName)) continue;
38249
38890
  for (const element of declarator.id.elements ?? []) if (isNodeOfType(element, "Identifier") && isSetterIdentifier(element.name)) setterNames.add(element.name);
38250
38891
  }
@@ -38387,11 +39028,11 @@ const isExternalSyncNode = (node) => {
38387
39028
  const receiverRootName = getRootIdentifierName(node.callee.object);
38388
39029
  return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
38389
39030
  };
38390
- const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, scopes, allowCommittedDomSync) => {
39031
+ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, scopes, allowCommittedDomSync) => {
38391
39032
  if (!isFunctionLike$1(effectCallback)) return false;
38392
39033
  if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
38393
- if (isFunctionShapedReturn(effectCallback.body, setterToStateName, false)) return true;
38394
- } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, true)) return true;
39034
+ if (isFunctionShapedReturn(effectCallback.body, setterToStateName, setterSymbolIdToStateName, scopes, false)) return true;
39035
+ } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, setterSymbolIdToStateName, scopes, true)) return true;
38395
39036
  let didFindExternalCall = false;
38396
39037
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
38397
39038
  if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
@@ -38407,10 +39048,11 @@ const noEffectChain = defineRule({
38407
39048
  create: (context) => {
38408
39049
  const checkComponent = (componentBody) => {
38409
39050
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
38410
- const useStateBindings = collectUseStateBindings(componentBody);
39051
+ const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
38411
39052
  if (useStateBindings.length === 0) return;
38412
39053
  const setterToStateName = /* @__PURE__ */ new Map();
38413
39054
  const stateSymbolIds = /* @__PURE__ */ new Map();
39055
+ const setterSymbolIdToStateName = /* @__PURE__ */ new Map();
38414
39056
  for (const binding of useStateBindings) {
38415
39057
  setterToStateName.set(binding.setterName, binding.valueName);
38416
39058
  if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
@@ -38419,21 +39061,27 @@ const noEffectChain = defineRule({
38419
39061
  const stateSymbol = context.scopes.symbolFor(stateIdentifier);
38420
39062
  if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
38421
39063
  }
39064
+ const setterIdentifier = binding.declarator.id.elements[1];
39065
+ if (isNodeOfType(setterIdentifier, "Identifier")) {
39066
+ const setterSymbol = context.scopes.symbolFor(setterIdentifier);
39067
+ if (setterSymbol) setterSymbolIdToStateName.set(setterSymbol.id, binding.valueName);
39068
+ }
38422
39069
  }
38423
39070
  const storageSetterNames = collectStorageHookSetterNames(componentBody);
39071
+ const stateSymbolIdSet = new Set(stateSymbolIds.values());
38424
39072
  const effectInfos = [];
38425
- for (const effectCall of findTopLevelEffectCalls(componentBody)) {
39073
+ for (const effectCall of findTopLevelEffectCalls(componentBody, context.scopes)) {
38426
39074
  const callback = getEffectCallback(effectCall, context.scopes);
38427
39075
  if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
38428
39076
  const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
38429
- const stateWrites = collectStateWritesInEffect(analysisFunctions, setterToStateName, context.scopes);
39077
+ const stateWrites = collectStateWritesInEffect(analysisFunctions, setterSymbolIdToStateName, context.scopes);
38430
39078
  const writtenStateNames = new Set(stateWrites.keys());
38431
39079
  effectInfos.push({
38432
39080
  node: effectCall,
38433
- depNames: collectDepIdentifierNames(effectCall),
39081
+ dependencyStateSymbolIds: collectDependencyStateSymbolIds(effectCall, stateSymbolIdSet, context.scopes),
38434
39082
  stateWrites,
38435
39083
  analysisFunctions,
38436
- isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
39084
+ isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
38437
39085
  });
38438
39086
  }
38439
39087
  if (effectInfos.length < 2) return;
@@ -38444,10 +39092,11 @@ const noEffectChain = defineRule({
38444
39092
  for (const readerEffect of effectInfos) {
38445
39093
  if (readerEffect === writerEffect) continue;
38446
39094
  if (readerEffect.isExternalSync) continue;
38447
- if (readerEffect.depNames.size === 0) continue;
39095
+ if (readerEffect.dependencyStateSymbolIds.size === 0) continue;
38448
39096
  let chainedStateName = null;
38449
39097
  for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
38450
- if (!readerEffect.depNames.has(writtenName)) continue;
39098
+ const writtenStateSymbolId = stateSymbolIds.get(writtenName);
39099
+ if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
38451
39100
  if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
38452
39101
  chainedStateName = writtenName;
38453
39102
  break;
@@ -38724,7 +39373,7 @@ const noEffectEventHandler = defineRule({
38724
39373
  return {
38725
39374
  ...propStackTracker.visitors,
38726
39375
  CallExpression(node) {
38727
- if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
39376
+ if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes) || (node.arguments?.length ?? 0) < 2) return;
38728
39377
  const callback = getEffectCallback(node);
38729
39378
  if (!callback) return;
38730
39379
  const analysis = getProgramAnalysis(node);
@@ -38844,14 +39493,14 @@ const noEffectEventInDeps = defineRule({
38844
39493
  if (!isNodeOfType(declaratorNode.id, "Identifier")) return;
38845
39494
  const initializer = declaratorNode.init;
38846
39495
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return;
38847
- if (!isHookCall$2(initializer, "useEffectEvent")) return;
39496
+ if (!isReactHookCall(initializer, "useEffectEvent", context.scopes)) return;
38848
39497
  if (isNonReactEffectEventCallee(initializer.callee, declaratorNode, context.scopes)) return;
38849
39498
  componentBindings.addBindingToCurrentFrame(declaratorNode.id.name);
38850
39499
  } });
38851
39500
  return {
38852
39501
  ...componentBindings.visitors,
38853
39502
  CallExpression(node) {
38854
- if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
39503
+ if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes) || node.arguments.length < 2) return;
38855
39504
  if (!componentBindings.isInsideComponent()) return;
38856
39505
  const depsNode = node.arguments[1];
38857
39506
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -38908,7 +39557,7 @@ const noEffectWithFreshDeps = defineRule({
38908
39557
  node: finding.reportNode,
38909
39558
  message: `A dependency inside this custom Hook changes every render because \`${finding.bindingName}\` is a new ${finding.kind} built fresh each time.`
38910
39559
  });
38911
- if (!isHookCall$2(node, HOOKS_WITH_DEPS)) return;
39560
+ if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes)) return;
38912
39561
  const args = node.arguments ?? [];
38913
39562
  if (args.length < 2) return;
38914
39563
  const depsNode = args[1];
@@ -39169,7 +39818,7 @@ const noEventHandler = defineRule({
39169
39818
  severity: "warn",
39170
39819
  recommendation: "Run the side effect in the event handler that triggers it, instead of watching its state from a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#sharing-logic-between-event-handlers",
39171
39820
  create: (context) => ({ CallExpression(node) {
39172
- if (!isUseEffect(node)) return;
39821
+ if (!isReactHookCall(node, "useEffect", context.scopes)) return;
39173
39822
  const analysis = getProgramAnalysis(node);
39174
39823
  if (!analysis || hasCleanup(analysis, node)) return;
39175
39824
  const frames = collectBoundedEffectExecutionFrames(analysis, node);
@@ -39631,25 +40280,25 @@ const addDeclarationBindings = (statement, scope) => {
39631
40280
  }
39632
40281
  if (isNodeOfType(statement, "FunctionDeclaration") && statement.id) addPatternBindings(statement.id, scope);
39633
40282
  };
39634
- const collectRenderReachableNamesFromStatements = (statements, names, scope, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
40283
+ const collectRenderReachableNamesFromStatements = (statements, names, scope, scopes, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
39635
40284
  let hasReturn = false;
39636
- for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope, eventHandlerReferenceNames)) hasReturn = true;
40285
+ for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope, scopes, eventHandlerReferenceNames)) hasReturn = true;
39637
40286
  else addDeclarationBindings(statement, scope);
39638
40287
  return hasReturn;
39639
40288
  };
39640
- const collectRenderReachableNamesFromStatement = (statement, names, scope, eventHandlerReferenceNames) => {
40289
+ const collectRenderReachableNamesFromStatement = (statement, names, scope, scopes, eventHandlerReferenceNames) => {
39641
40290
  if (isNodeOfType(statement, "ReturnStatement")) {
39642
40291
  if (statement.argument) addNames(names, collectScopedReferenceNames(statement.argument, scope, eventHandlerReferenceNames));
39643
40292
  return true;
39644
40293
  }
39645
- if (isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "CallExpression") && isNodeOfType(statement.expression.callee, "Identifier") && isReactHookName(statement.expression.callee.name) && !EFFECT_HOOK_NAMES$1.has(statement.expression.callee.name)) {
40294
+ if (isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "CallExpression") && (isNodeOfType(statement.expression.callee, "Identifier") && isReactHookName(statement.expression.callee.name) || isReactHookCall(statement.expression, BUILTIN_HOOK_NAMES, scopes)) && !isReactHookCall(statement.expression, EFFECT_HOOK_NAMES$1, scopes)) {
39646
40295
  for (const argument of statement.expression.arguments ?? []) addNames(names, collectScopedReferenceNames(argument, scope, eventHandlerReferenceNames));
39647
40296
  return false;
39648
40297
  }
39649
- if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope), eventHandlerReferenceNames);
40298
+ if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope), scopes, eventHandlerReferenceNames);
39650
40299
  if (isNodeOfType(statement, "IfStatement")) {
39651
- const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope, eventHandlerReferenceNames);
39652
- const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope, eventHandlerReferenceNames) : false;
40300
+ const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope, scopes, eventHandlerReferenceNames);
40301
+ const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope, scopes, eventHandlerReferenceNames) : false;
39653
40302
  if (consequentHasReturn || alternateHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
39654
40303
  return consequentHasReturn || alternateHasReturn;
39655
40304
  }
@@ -39657,7 +40306,7 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, event
39657
40306
  let hasReturn = false;
39658
40307
  for (const switchCase of statement.cases ?? []) {
39659
40308
  const caseScope = createBlockBindingScope(scope);
39660
- if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope, eventHandlerReferenceNames)) continue;
40309
+ if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope, scopes, eventHandlerReferenceNames)) continue;
39661
40310
  hasReturn = true;
39662
40311
  if (switchCase.test) addNames(names, collectScopedReferenceNames(switchCase.test, scope, eventHandlerReferenceNames));
39663
40312
  }
@@ -39665,25 +40314,25 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, event
39665
40314
  return hasReturn;
39666
40315
  }
39667
40316
  if (isNodeOfType(statement, "TryStatement")) {
39668
- const blockHasReturn = collectRenderReachableNamesFromStatement(statement.block, names, scope, eventHandlerReferenceNames);
39669
- const handlerHasReturn = statement.handler ? collectRenderReachableNamesFromStatement(statement.handler, names, scope, eventHandlerReferenceNames) : false;
39670
- const finalizerHasReturn = statement.finalizer ? collectRenderReachableNamesFromStatement(statement.finalizer, names, scope, eventHandlerReferenceNames) : false;
40317
+ const blockHasReturn = collectRenderReachableNamesFromStatement(statement.block, names, scope, scopes, eventHandlerReferenceNames);
40318
+ const handlerHasReturn = statement.handler ? collectRenderReachableNamesFromStatement(statement.handler, names, scope, scopes, eventHandlerReferenceNames) : false;
40319
+ const finalizerHasReturn = statement.finalizer ? collectRenderReachableNamesFromStatement(statement.finalizer, names, scope, scopes, eventHandlerReferenceNames) : false;
39671
40320
  return blockHasReturn || handlerHasReturn || finalizerHasReturn;
39672
40321
  }
39673
40322
  if (isNodeOfType(statement, "CatchClause")) {
39674
40323
  const catchScope = createBlockBindingScope(scope);
39675
40324
  addPatternBindings(statement.param, catchScope);
39676
- return collectRenderReachableNamesFromStatement(statement.body, names, catchScope, eventHandlerReferenceNames);
40325
+ return collectRenderReachableNamesFromStatement(statement.body, names, catchScope, scopes, eventHandlerReferenceNames);
39677
40326
  }
39678
40327
  if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
39679
- const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
40328
+ const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, scopes, eventHandlerReferenceNames);
39680
40329
  if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
39681
40330
  return bodyHasReturn;
39682
40331
  }
39683
40332
  if (isNodeOfType(statement, "ForStatement")) {
39684
40333
  const loopScope = createBlockBindingScope(scope);
39685
40334
  if (statement.init) addDeclarationBindings(statement.init, loopScope);
39686
- if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
40335
+ if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, scopes, eventHandlerReferenceNames)) return false;
39687
40336
  if (statement.init) addNames(names, collectScopedReferenceNames(statement.init, loopScope, eventHandlerReferenceNames));
39688
40337
  if (statement.test) addNames(names, collectScopedReferenceNames(statement.test, loopScope, eventHandlerReferenceNames));
39689
40338
  if (statement.update) addNames(names, collectScopedReferenceNames(statement.update, loopScope, eventHandlerReferenceNames));
@@ -39693,22 +40342,22 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, event
39693
40342
  const rightNames = collectScopedReferenceNames(statement.right, scope, eventHandlerReferenceNames);
39694
40343
  const loopScope = createBlockBindingScope(scope);
39695
40344
  if (isNodeOfType(statement.left, "VariableDeclaration")) addDeclarationBindings(statement.left, loopScope);
39696
- if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
40345
+ if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, scopes, eventHandlerReferenceNames)) return false;
39697
40346
  addNames(names, rightNames);
39698
40347
  return true;
39699
40348
  }
39700
- if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
40349
+ if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope, scopes, eventHandlerReferenceNames);
39701
40350
  if (isNodeOfType(statement, "WithStatement")) {
39702
- const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
40351
+ const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, scopes, eventHandlerReferenceNames);
39703
40352
  if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.object, scope, eventHandlerReferenceNames));
39704
40353
  return bodyHasReturn;
39705
40354
  }
39706
40355
  return false;
39707
40356
  };
39708
- const collectRenderReachableNames = (componentBody, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
40357
+ const collectRenderReachableNames = (componentBody, scopes, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
39709
40358
  const names = /* @__PURE__ */ new Set();
39710
40359
  if (!isNodeOfType(componentBody, "BlockStatement")) return names;
39711
- collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(), eventHandlerReferenceNames);
40360
+ collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(), scopes, eventHandlerReferenceNames);
39712
40361
  return names;
39713
40362
  };
39714
40363
  //#endregion
@@ -39731,42 +40380,36 @@ const expandTransitiveDependencies = (seedNames, dependencyGraph) => {
39731
40380
  };
39732
40381
  //#endregion
39733
40382
  //#region src/plugin/rules/state-and-effects/utils/collect-function-like-local-names.ts
39734
- const isUseCallbackCall = (node) => isNodeOfType(node, "CallExpression") && getCalleeName(node.callee) === "useCallback";
39735
- const getCalleeName = (node) => {
39736
- if (isNodeOfType(node, "Identifier")) return node.name;
39737
- if (isNodeOfType(node, "MemberExpression")) return getStaticMemberPropertyName(node);
39738
- return null;
39739
- };
39740
- const isFunctionLikeReference = (node, functionLikeLocalNames, scope) => {
39741
- if (isInlineFunctionExpression(node) || isUseCallbackCall(node)) return true;
40383
+ const isFunctionLikeReference = (node, functionLikeLocalNames, scope, scopes) => {
40384
+ if (isInlineFunctionExpression(node) || isReactHookCall(node, "useCallback", scopes)) return true;
39742
40385
  if (isNodeOfType(node, "Identifier")) return functionLikeLocalNames.has(resolveBindingName(scope, node.name));
39743
40386
  const memberReferenceName = getStaticMemberReferenceName(node, (name) => resolveBindingName(scope, name));
39744
40387
  return Boolean(memberReferenceName && functionLikeLocalNames.has(memberReferenceName));
39745
40388
  };
39746
- const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope) => {
40389
+ const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope, scopes) => {
39747
40390
  if (!isNodeOfType(node, "ObjectExpression")) return;
39748
40391
  for (const property of node.properties ?? []) {
39749
40392
  if (!isNodeOfType(property, "Property")) continue;
39750
40393
  const propertyName = getStaticPropertyKeyName(property, { stringifyNonStringLiterals: true });
39751
40394
  if (!propertyName) continue;
39752
- if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope)) continue;
40395
+ if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope, scopes)) continue;
39753
40396
  functionLikeLocalNames.add(`${objectBindingName}.${propertyName}`);
39754
40397
  }
39755
40398
  };
39756
- const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope) => {
40399
+ const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope, scopes) => {
39757
40400
  if (!isNodeOfType(statement, "VariableDeclaration")) return;
39758
40401
  const declarationScope = getVariableDeclarationScope(statement, scope);
39759
40402
  for (const declarator of statement.declarations ?? []) {
39760
40403
  const declaredBindingNames = addPatternBindings(declarator.id, declarationScope);
39761
40404
  if (!declarator.init) continue;
39762
- const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope);
40405
+ const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope, scopes);
39763
40406
  for (const declaredBindingName of declaredBindingNames) {
39764
40407
  if (isFunctionReference) functionLikeLocalNames.add(declaredBindingName);
39765
- addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope);
40408
+ addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope, scopes);
39766
40409
  }
39767
40410
  }
39768
40411
  };
39769
- const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope) => {
40412
+ const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope, scopes) => {
39770
40413
  if (isNodeOfType(statement, "FunctionDeclaration")) {
39771
40414
  if (statement.id) {
39772
40415
  const declaredBindingNames = addPatternBindings(statement.id, scope);
@@ -39775,61 +40418,61 @@ const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope)
39775
40418
  return;
39776
40419
  }
39777
40420
  if (isNodeOfType(statement, "VariableDeclaration")) {
39778
- addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope);
40421
+ addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope, scopes);
39779
40422
  return;
39780
40423
  }
39781
40424
  if (isNodeOfType(statement, "BlockStatement")) {
39782
- collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope));
40425
+ collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope), scopes);
39783
40426
  return;
39784
40427
  }
39785
40428
  if (isNodeOfType(statement, "IfStatement")) {
39786
- collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope);
39787
- if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope);
40429
+ collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope, scopes);
40430
+ if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope, scopes);
39788
40431
  return;
39789
40432
  }
39790
40433
  if (isNodeOfType(statement, "SwitchStatement")) {
39791
- for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope));
40434
+ for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope), scopes);
39792
40435
  return;
39793
40436
  }
39794
40437
  if (isNodeOfType(statement, "TryStatement")) {
39795
- collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope);
40438
+ collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope, scopes);
39796
40439
  if (statement.handler) {
39797
40440
  const catchScope = createBlockBindingScope(scope);
39798
40441
  addPatternBindings(statement.handler.param, catchScope);
39799
- collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope);
40442
+ collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope, scopes);
39800
40443
  }
39801
- if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope);
40444
+ if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope, scopes);
39802
40445
  return;
39803
40446
  }
39804
40447
  if (isNodeOfType(statement, "ForStatement")) {
39805
40448
  const loopScope = createBlockBindingScope(scope);
39806
- if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope);
39807
- collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
40449
+ if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope, scopes);
40450
+ collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope, scopes);
39808
40451
  return;
39809
40452
  }
39810
40453
  if (isNodeOfType(statement, "ForInStatement") || isNodeOfType(statement, "ForOfStatement")) {
39811
40454
  const loopScope = createBlockBindingScope(scope);
39812
- if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope);
40455
+ if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope, scopes);
39813
40456
  else addPatternBindings(statement.left, loopScope);
39814
- collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
40457
+ collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope, scopes);
39815
40458
  return;
39816
40459
  }
39817
40460
  if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
39818
- collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
40461
+ collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope, scopes);
39819
40462
  return;
39820
40463
  }
39821
- if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
40464
+ if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope, scopes);
39822
40465
  };
39823
- const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope) => {
39824
- for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope);
40466
+ const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope, scopes) => {
40467
+ for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope, scopes);
39825
40468
  };
39826
- const collectFunctionLikeLocalNames = (componentBody) => {
40469
+ const collectFunctionLikeLocalNames = (componentBody, scopes) => {
39827
40470
  const functionLikeLocalNames = /* @__PURE__ */ new Set();
39828
40471
  if (!isNodeOfType(componentBody, "BlockStatement")) return functionLikeLocalNames;
39829
40472
  let previousSize = -1;
39830
40473
  while (previousSize !== functionLikeLocalNames.size) {
39831
40474
  previousSize = functionLikeLocalNames.size;
39832
- collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope());
40475
+ collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope(), scopes);
39833
40476
  }
39834
40477
  return functionLikeLocalNames;
39835
40478
  };
@@ -39869,17 +40512,17 @@ const noEventTriggerState = defineRule({
39869
40512
  create: (context) => {
39870
40513
  const checkComponent = (componentBody) => {
39871
40514
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
39872
- const useStateBindings = collectUseStateBindings(componentBody);
40515
+ const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
39873
40516
  if (useStateBindings.length === 0) return;
39874
40517
  const analysis = getProgramAnalysis(componentBody);
39875
40518
  if (!analysis) return;
39876
40519
  const localStateNames = new Set(useStateBindings.map((binding) => binding.valueName));
39877
- const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
40520
+ const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
39878
40521
  const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
39879
- const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody, eventHandlerReferenceNames), dependencyGraph);
40522
+ const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames), dependencyGraph);
39880
40523
  walkAst(componentBody, (effectCall) => {
39881
40524
  if (!isNodeOfType(effectCall, "CallExpression")) return;
39882
- if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) return;
40525
+ if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) return;
39883
40526
  if ((effectCall.arguments?.length ?? 0) < 2) return;
39884
40527
  const depsNode = effectCall.arguments[1];
39885
40528
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -40263,6 +40906,17 @@ const DOM_MEASUREMENT_NAMES = new Set([
40263
40906
  "scrollHeight"
40264
40907
  ]);
40265
40908
  const MEASUREMENT_HELPER_CALLEE_PATTERN = /^(?:get|measure|read)\w*(?:Width|Height|Rect|Rects|Size|Bounds|Position)$/;
40909
+ const IMPERATIVE_DOM_MUTATION_NAMES = new Set([
40910
+ "blur",
40911
+ "focus",
40912
+ "restoreSelection",
40913
+ "scroll",
40914
+ "scrollBy",
40915
+ "scrollIntoView",
40916
+ "scrollTo",
40917
+ "setRangeText",
40918
+ "setSelectionRange"
40919
+ ]);
40266
40920
  const subtreeReadsDomMeasurement = (root) => {
40267
40921
  if (!root) return false;
40268
40922
  let found = false;
@@ -40281,29 +40935,66 @@ const subtreeReadsDomMeasurement = (root) => {
40281
40935
  });
40282
40936
  return found;
40283
40937
  };
40284
- const collectMeasuringFunctionNames = (program) => {
40938
+ const collectFunctionNamesMatchingBody = (program, matchesBody) => {
40285
40939
  const names = /* @__PURE__ */ new Set();
40286
40940
  walkAst(program, (child) => {
40287
40941
  if (isNodeOfType(child, "FunctionDeclaration")) {
40288
- if (child.id && isNodeOfType(child.id, "Identifier") && subtreeReadsDomMeasurement(child.body)) names.add(child.id.name);
40942
+ if (child.id && isNodeOfType(child.id, "Identifier") && matchesBody(child.body)) names.add(child.id.name);
40289
40943
  return;
40290
40944
  }
40291
40945
  if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier")) return;
40292
40946
  let functionValue = child.init;
40293
40947
  if (functionValue && isNodeOfType(functionValue, "CallExpression") && isNodeOfType(functionValue.callee, "Identifier") && /^use[A-Z]/.test(functionValue.callee.name)) functionValue = functionValue.arguments?.[0];
40294
- if (functionValue && isFunctionLike$1(functionValue) && subtreeReadsDomMeasurement(functionValue.body)) names.add(child.id.name);
40948
+ if (functionValue && isFunctionLike$1(functionValue) && matchesBody(functionValue.body)) names.add(child.id.name);
40295
40949
  });
40296
40950
  return names;
40297
40951
  };
40298
- const callsAnyName = (root, names) => {
40299
- if (!root || names.size === 0) return false;
40952
+ const collectMeasuringFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeReadsDomMeasurement);
40953
+ const subtreeMutatesDomImperatively = (root) => {
40954
+ if (!root || isFunctionLike$1(root)) return false;
40955
+ let found = false;
40956
+ walkAst(root, (child) => {
40957
+ if (found) return false;
40958
+ if (child !== root && isFunctionLike$1(child)) return false;
40959
+ if (!isNodeOfType(child, "CallExpression")) return;
40960
+ const callee = stripParenExpression(child.callee);
40961
+ const propertyName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
40962
+ if (propertyName !== null && IMPERATIVE_DOM_MUTATION_NAMES.has(propertyName)) {
40963
+ found = true;
40964
+ return false;
40965
+ }
40966
+ });
40967
+ return found;
40968
+ };
40969
+ const collectImperativeDomFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeMutatesDomImperatively);
40970
+ const callsAnyName = (root, names, shouldSkipNestedFunctions = false) => {
40971
+ if (!root || names.size === 0 || shouldSkipNestedFunctions && isFunctionLike$1(root)) return false;
40300
40972
  let found = false;
40301
40973
  walkAst(root, (child) => {
40302
40974
  if (found) return false;
40975
+ if (shouldSkipNestedFunctions && child !== root && isFunctionLike$1(child)) return false;
40303
40976
  if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && names.has(child.callee.name)) found = true;
40304
40977
  });
40305
40978
  return found;
40306
40979
  };
40980
+ const isFollowedByImperativeDomMutation = (call, imperativeDomFunctionNames) => {
40981
+ let statement = call;
40982
+ let parent = statement.parent;
40983
+ while (parent) {
40984
+ const statements = isNodeOfType(parent, "BlockStatement") || isNodeOfType(parent, "Program") || isNodeOfType(parent, "StaticBlock") ? parent.body : isNodeOfType(parent, "SwitchCase") ? parent.consequent : null;
40985
+ if (statements) {
40986
+ const statementIndex = statements.findIndex((siblingStatement) => siblingStatement === statement);
40987
+ if (statementIndex >= 0) {
40988
+ const nextStatement = statements[statementIndex + 1];
40989
+ return subtreeMutatesDomImperatively(nextStatement) || callsAnyName(nextStatement, imperativeDomFunctionNames, true);
40990
+ }
40991
+ }
40992
+ if (isFunctionLike$1(parent) || parent.type.endsWith("Statement") && !isNodeOfType(parent, "ExpressionStatement")) return false;
40993
+ statement = parent;
40994
+ parent = parent.parent;
40995
+ }
40996
+ return false;
40997
+ };
40307
40998
  const isInsideStartViewTransition = (node) => {
40308
40999
  let cursor = node.parent;
40309
41000
  while (cursor) {
@@ -40344,11 +41035,12 @@ const importsImperativeDomLibrary = (program) => {
40344
41035
  };
40345
41036
  const hasExemptFlushSyncCall = (program, localName) => {
40346
41037
  const measuringFunctionNames = collectMeasuringFunctionNames(program);
41038
+ const imperativeDomFunctionNames = collectImperativeDomFunctionNames(program);
40347
41039
  let exempt = false;
40348
41040
  walkAst(program, (child) => {
40349
41041
  if (exempt) return false;
40350
41042
  if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "Identifier") || child.callee.name !== localName) return;
40351
- if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames)) {
41043
+ if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames) || isFollowedByImperativeDomMutation(child, imperativeDomFunctionNames)) {
40352
41044
  exempt = true;
40353
41045
  return false;
40354
41046
  }
@@ -40913,41 +41605,223 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
40913
41605
  if (leftResult === false && rightResult === false) return false;
40914
41606
  return null;
40915
41607
  };
40916
- const readHydrationConditionResult = (expression, context, runtime) => {
41608
+ const readHydrationConditionResult = (expression, context, runtime, state) => {
40917
41609
  const unwrappedExpression = stripParenExpression(expression);
40918
41610
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
40919
41611
  if (predicateMatch) return predicateMatch[`${runtime}Result`];
40920
41612
  const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
40921
41613
  if (staticResult !== null) return staticResult;
41614
+ const expressionSymbol = isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression) : null;
41615
+ const parameterValue = expressionSymbol ? state.parameterValuesBySymbolId.get(expressionSymbol.id) : null;
41616
+ if (expressionSymbol && parameterValue && !state.visitedSymbolIds.has(expressionSymbol.id)) {
41617
+ state.visitedSymbolIds.add(expressionSymbol.id);
41618
+ const result = readHydrationConditionResult(parameterValue, context, runtime, state);
41619
+ state.visitedSymbolIds.delete(expressionSymbol.id);
41620
+ return result;
41621
+ }
41622
+ if (expressionSymbol && expressionSymbol.kind === "const" && expressionSymbol.initializer && expressionSymbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(expressionSymbol.id)) {
41623
+ state.visitedSymbolIds.add(expressionSymbol.id);
41624
+ const result = readHydrationConditionResult(expressionSymbol.initializer, context, runtime, state);
41625
+ state.visitedSymbolIds.delete(expressionSymbol.id);
41626
+ return result;
41627
+ }
41628
+ if (isNodeOfType(unwrappedExpression, "CallExpression")) {
41629
+ const callArguments = unwrappedExpression.arguments ?? [];
41630
+ if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
41631
+ allowGlobalReactNamespace: true,
41632
+ resolveNamedAliases: true
41633
+ })) {
41634
+ const callbackArgument = callArguments[0];
41635
+ if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
41636
+ const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
41637
+ return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? readHydrationFunctionResult(callbackFunction, context, runtime, state) : null;
41638
+ }
41639
+ const callee = stripParenExpression(unwrappedExpression.callee);
41640
+ if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return readHydrationConditionResult(callArguments[0], context, runtime, state);
41641
+ const helperFunction = resolveExactLocalFunction(callee, context.scopes);
41642
+ if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
41643
+ const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
41644
+ for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
41645
+ const parameter = helperFunction.params[parameterIndex];
41646
+ const argument = callArguments[parameterIndex];
41647
+ if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
41648
+ const parameterSymbol = context.scopes.symbolFor(parameter);
41649
+ if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
41650
+ }
41651
+ return readHydrationFunctionResult(helperFunction, context, runtime, {
41652
+ ...state,
41653
+ parameterValuesBySymbolId
41654
+ });
41655
+ }
40922
41656
  if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
40923
- const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
41657
+ const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
40924
41658
  return argumentResult === null ? null : !argumentResult;
40925
41659
  }
40926
41660
  if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
40927
- return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
41661
+ return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime, state), readHydrationConditionResult(unwrappedExpression.right, context, runtime, state));
41662
+ };
41663
+ const readHydrationStatementResult = (statement, context, runtime, state) => {
41664
+ if (isNodeOfType(statement, "ReturnStatement")) return {
41665
+ didReturn: true,
41666
+ value: statement.argument ? readHydrationConditionResult(statement.argument, context, runtime, state) : null
41667
+ };
41668
+ if (isNodeOfType(statement, "BlockStatement")) {
41669
+ for (const childStatement of statement.body) {
41670
+ const result = readHydrationStatementResult(childStatement, context, runtime, state);
41671
+ if (result.didReturn) return result;
41672
+ if (statementAlwaysExits(childStatement)) break;
41673
+ }
41674
+ return {
41675
+ didReturn: false,
41676
+ value: null
41677
+ };
41678
+ }
41679
+ if (!isNodeOfType(statement, "IfStatement")) return {
41680
+ didReturn: false,
41681
+ value: null
41682
+ };
41683
+ const conditionResult = readHydrationConditionResult(statement.test, context, runtime, state);
41684
+ if (conditionResult !== null) {
41685
+ const selectedBranch = conditionResult ? statement.consequent : statement.alternate;
41686
+ return selectedBranch ? readHydrationStatementResult(selectedBranch, context, runtime, state) : {
41687
+ didReturn: false,
41688
+ value: null
41689
+ };
41690
+ }
41691
+ const consequentResult = readHydrationStatementResult(statement.consequent, context, runtime, state);
41692
+ const alternateResult = statement.alternate ? readHydrationStatementResult(statement.alternate, context, runtime, state) : {
41693
+ didReturn: false,
41694
+ value: null
41695
+ };
41696
+ return consequentResult.didReturn && alternateResult.didReturn && consequentResult.value !== null && consequentResult.value === alternateResult.value ? consequentResult : {
41697
+ didReturn: consequentResult.didReturn || alternateResult.didReturn,
41698
+ value: null
41699
+ };
41700
+ };
41701
+ const readHydrationFunctionResult = (functionNode, context, runtime, state) => {
41702
+ if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
41703
+ state.visitedFunctionNodes.add(functionNode);
41704
+ const result = isNodeOfType(functionNode.body, "BlockStatement") ? readHydrationStatementResult(functionNode.body, context, runtime, state).value : readHydrationConditionResult(functionNode.body, context, runtime, state);
41705
+ state.visitedFunctionNodes.delete(functionNode);
41706
+ return result;
41707
+ };
41708
+ const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, scopes) => {
41709
+ const left = stripParenExpression(leftExpression);
41710
+ const right = stripParenExpression(rightExpression);
41711
+ if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
41712
+ const leftSymbol = scopes.symbolFor(left);
41713
+ const rightSymbol = scopes.symbolFor(right);
41714
+ return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
41715
+ }
41716
+ if (isNodeOfType(left, "MemberExpression") && isNodeOfType(right, "MemberExpression")) return doEquivalentExpressionBindingsMatch(left.object, right.object, scopes) && (!left.computed || doEquivalentExpressionBindingsMatch(left.property, right.property, scopes));
41717
+ if (isNodeOfType(left, "CallExpression") && isNodeOfType(right, "CallExpression")) {
41718
+ const rightArguments = right.arguments ?? [];
41719
+ return doEquivalentExpressionBindingsMatch(left.callee, right.callee, scopes) && (left.arguments ?? []).every((argument, index) => {
41720
+ const rightArgument = rightArguments[index];
41721
+ return Boolean(rightArgument && doEquivalentExpressionBindingsMatch(argument, rightArgument, scopes));
41722
+ });
41723
+ }
41724
+ return true;
41725
+ };
41726
+ const areHelperReturnValuesEquivalent = (leftValue, rightValue, context) => {
41727
+ if (areExpressionsStructurallyEqual(leftValue, rightValue)) return doEquivalentExpressionBindingsMatch(leftValue, rightValue, context.scopes);
41728
+ const leftBoolean = readInitialStateBoolean(leftValue, context.scopes);
41729
+ const rightBoolean = readInitialStateBoolean(rightValue, context.scopes);
41730
+ return leftBoolean !== null && rightBoolean !== null && leftBoolean === rightBoolean;
40928
41731
  };
40929
- const matchHydrationCondition = (expression, context) => {
41732
+ const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
41733
+ const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
41734
+ return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
41735
+ };
41736
+ const matchHydrationConditionInternal = (expression, context, state) => {
40930
41737
  const unwrappedExpression = stripParenExpression(expression);
40931
41738
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
40932
41739
  if (predicateMatch) return {
40933
41740
  predicateMatch,
40934
41741
  predicateNode: unwrappedExpression
40935
41742
  };
40936
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
40937
- if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
40938
- const leftMatch = matchHydrationCondition(unwrappedExpression.left, context);
40939
- const rightMatch = matchHydrationCondition(unwrappedExpression.right, context);
40940
- if (leftMatch && rightMatch) {
40941
- const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client");
40942
- const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server");
40943
- return clientResult !== null && serverResult !== null && clientResult !== serverResult ? leftMatch : null;
41743
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
41744
+ const symbol = context.scopes.symbolFor(unwrappedExpression);
41745
+ const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
41746
+ if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
41747
+ state.visitedSymbolIds.add(symbol.id);
41748
+ const match = matchHydrationConditionInternal(parameterValue, context, state);
41749
+ state.visitedSymbolIds.delete(symbol.id);
41750
+ return match;
41751
+ }
41752
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
41753
+ state.visitedSymbolIds.add(symbol.id);
41754
+ const match = matchHydrationConditionInternal(symbol.initializer, context, state);
41755
+ state.visitedSymbolIds.delete(symbol.id);
41756
+ return match;
41757
+ }
41758
+ if (isNodeOfType(unwrappedExpression, "CallExpression")) {
41759
+ const callArguments = unwrappedExpression.arguments ?? [];
41760
+ if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
41761
+ allowGlobalReactNamespace: true,
41762
+ resolveNamedAliases: true
41763
+ })) {
41764
+ const callbackArgument = callArguments[0];
41765
+ if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
41766
+ const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
41767
+ return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionResult(callbackFunction, context, state) : null;
41768
+ }
41769
+ const callee = stripParenExpression(unwrappedExpression.callee);
41770
+ if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return matchHydrationConditionInternal(callArguments[0], context, state);
41771
+ const helperFunction = resolveExactLocalFunction(callee, context.scopes);
41772
+ if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
41773
+ const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
41774
+ for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
41775
+ const parameter = helperFunction.params[parameterIndex];
41776
+ const argument = callArguments[parameterIndex];
41777
+ if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
41778
+ const parameterSymbol = context.scopes.symbolFor(parameter);
41779
+ if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
41780
+ }
41781
+ return matchHydrationFunctionResult(helperFunction, context, {
41782
+ ...state,
41783
+ parameterValuesBySymbolId
41784
+ });
40944
41785
  }
41786
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
41787
+ if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
41788
+ const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
41789
+ const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
40945
41790
  const nestedMatch = leftMatch ?? rightMatch;
40946
41791
  if (!nestedMatch) return null;
40947
- const otherResult = readInitialStateBoolean(leftMatch ? unwrappedExpression.right : unwrappedExpression.left, context.scopes);
40948
- if (unwrappedExpression.operator === "&&" && otherResult === false || unwrappedExpression.operator === "||" && otherResult === true) return null;
40949
- return nestedMatch;
41792
+ const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client", state);
41793
+ const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server", state);
41794
+ return clientResult !== null && serverResult !== null && clientResult === serverResult ? null : nestedMatch;
40950
41795
  };
41796
+ const matchHydrationReturningStatement = (statement, context, state) => {
41797
+ if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? matchHydrationConditionInternal(statement.argument, context, state) : null;
41798
+ if (isNodeOfType(statement, "IfStatement")) {
41799
+ const conditionMatch = matchHydrationConditionInternal(statement.test, context, state);
41800
+ const consequentValues = getReturnedValues(statement.consequent);
41801
+ const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
41802
+ if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
41803
+ return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
41804
+ }
41805
+ if (!isNodeOfType(statement, "BlockStatement")) return null;
41806
+ for (const childStatement of statement.body) {
41807
+ const match = matchHydrationReturningStatement(childStatement, context, state);
41808
+ if (match) return match;
41809
+ if (statementAlwaysExits(childStatement)) break;
41810
+ }
41811
+ return null;
41812
+ };
41813
+ const matchHydrationFunctionResult = (functionNode, context, state) => {
41814
+ if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
41815
+ state.visitedFunctionNodes.add(functionNode);
41816
+ const match = isNodeOfType(functionNode.body, "BlockStatement") ? matchHydrationReturningStatement(functionNode.body, context, state) : matchHydrationConditionInternal(functionNode.body, context, state);
41817
+ state.visitedFunctionNodes.delete(functionNode);
41818
+ return match;
41819
+ };
41820
+ const matchHydrationCondition = (expression, context) => matchHydrationConditionInternal(expression, context, {
41821
+ parameterValuesBySymbolId: /* @__PURE__ */ new Map(),
41822
+ visitedFunctionNodes: /* @__PURE__ */ new Set(),
41823
+ visitedSymbolIds: /* @__PURE__ */ new Set()
41824
+ });
40951
41825
  const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
40952
41826
  const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
40953
41827
  if (!leftNode || !rightNode) return leftNode === rightNode;
@@ -41090,17 +41964,17 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
41090
41964
  const { predicateMatch, predicateNode } = conditionMatch;
41091
41965
  if (reportedNodes.has(predicateNode)) return;
41092
41966
  if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
41093
- const componentOrHookNode = findRenderPhaseComponentOrHook(predicateNode, context.scopes);
41967
+ const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes);
41094
41968
  if (!componentOrHookNode) return;
41095
41969
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
41096
- if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
41970
+ if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes)) return;
41097
41971
  if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
41098
- const attribute = findEnclosingJsxAttribute(predicateNode);
41972
+ const attribute = findEnclosingJsxAttribute(conditionNode);
41099
41973
  if (!attribute || isEventHandlerAttribute(attribute)) return;
41100
41974
  }
41101
- if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
41102
- if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
41103
- const openingElement = findEnclosingJsxOpeningElement(predicateNode);
41975
+ if (fileIsEmailTemplate || isGatedByFalsyInitialState(conditionNode, context.scopes)) return;
41976
+ if (isAfterClientOnlyEarlyReturn(conditionNode, componentOrHookNode, context.scopes)) return;
41977
+ const openingElement = findEnclosingJsxOpeningElement(conditionNode);
41104
41978
  if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
41105
41979
  if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
41106
41980
  if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
@@ -41477,7 +42351,7 @@ const noInitializeState = defineRule({
41477
42351
  tags: ["test-noise"],
41478
42352
  recommendation: "Pass the initial value directly to useState() instead of setting it from a mount-only useEffect. For SSR hydration, prefer useSyncExternalStore().",
41479
42353
  create: (context) => ({ CallExpression(node) {
41480
- if (!isUseEffect(node)) return;
42354
+ if (!isReactHookCall(node, "useEffect", context.scopes)) return;
41481
42355
  const dependencies = node.arguments?.[1];
41482
42356
  if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
41483
42357
  const analysis = getProgramAnalysis(node);
@@ -41840,7 +42714,8 @@ const noJsxElementType = defineRule({
41840
42714
  create: (context) => {
41841
42715
  let isJsxImported = false;
41842
42716
  const flaggedAnnotations = [];
41843
- const checkReturnType = (returnType) => {
42717
+ const collectComponentReturnType = (functionNode, returnType) => {
42718
+ if (!(isNodeOfType(functionNode, "TSDeclareFunction") ? Boolean(functionNode.id && isReactComponentName(functionNode.id.name)) : isComponentFunction$1(functionNode))) return;
41844
42719
  const typeAnnotation = extractReturnTypeAnnotation(returnType);
41845
42720
  if (!typeAnnotation) return;
41846
42721
  if (isJsxElementTypeReference(typeAnnotation)) flaggedAnnotations.push(typeAnnotation);
@@ -41850,19 +42725,16 @@ const noJsxElementType = defineRule({
41850
42725
  if (isJsxImportBinding(node)) isJsxImported = true;
41851
42726
  },
41852
42727
  FunctionDeclaration(node) {
41853
- checkReturnType(node.returnType);
42728
+ collectComponentReturnType(node, node.returnType);
41854
42729
  },
41855
42730
  ArrowFunctionExpression(node) {
41856
- checkReturnType(node.returnType);
42731
+ collectComponentReturnType(node, node.returnType);
41857
42732
  },
41858
42733
  FunctionExpression(node) {
41859
- checkReturnType(node.returnType);
42734
+ collectComponentReturnType(node, node.returnType);
41860
42735
  },
41861
42736
  TSDeclareFunction(node) {
41862
- checkReturnType(node.returnType);
41863
- },
41864
- TSMethodSignature(node) {
41865
- checkReturnType(node.returnType);
42737
+ collectComponentReturnType(node, node.returnType);
41866
42738
  },
41867
42739
  "Program:exit"() {
41868
42740
  if (isJsxImported) return;
@@ -43091,7 +43963,7 @@ const noMirrorPropEffect = defineRule({
43091
43963
  const setterElement = elements[1];
43092
43964
  if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
43093
43965
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
43094
- if (!isHookCall$2(declarator.init, "useState")) continue;
43966
+ if (!isReactHookCall(declarator.init, "useState", context.scopes)) continue;
43095
43967
  const initializer = declarator.init.arguments?.[0];
43096
43968
  if (!initializer) continue;
43097
43969
  const propRootName = getPropRootName(initializer, propNames);
@@ -43109,7 +43981,7 @@ const noMirrorPropEffect = defineRule({
43109
43981
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
43110
43982
  const effectCall = unwrapDiscardedExpression(statement);
43111
43983
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
43112
- if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
43984
+ if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
43113
43985
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
43114
43986
  const depsNode = effectCall.arguments[1];
43115
43987
  if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
@@ -43518,7 +44390,7 @@ const noMultiComp = defineRule({
43518
44390
  });
43519
44391
  //#endregion
43520
44392
  //#region src/plugin/rules/state-and-effects/no-mutable-in-deps.ts
43521
- const collectUseRefBindingNames = (componentBody) => {
44393
+ const collectUseRefBindingNames = (componentBody, scopes) => {
43522
44394
  const useRefBindings = /* @__PURE__ */ new Set();
43523
44395
  if (!isNodeOfType(componentBody, "BlockStatement")) return useRefBindings;
43524
44396
  for (const statement of componentBody.body ?? []) {
@@ -43526,7 +44398,7 @@ const collectUseRefBindingNames = (componentBody) => {
43526
44398
  for (const declarator of statement.declarations ?? []) {
43527
44399
  if (!isNodeOfType(declarator.id, "Identifier")) continue;
43528
44400
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
43529
- if (!isHookCall$2(declarator.init, "useRef")) continue;
44401
+ if (!isReactHookCall(declarator.init, "useRef", scopes)) continue;
43530
44402
  useRefBindings.add(declarator.id.name);
43531
44403
  }
43532
44404
  }
@@ -43561,12 +44433,12 @@ const noMutableInDeps = defineRule({
43561
44433
  create: (context) => {
43562
44434
  const checkComponent = (componentBody, componentParams = []) => {
43563
44435
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
43564
- const useRefBindingNames = collectUseRefBindingNames(componentBody);
44436
+ const useRefBindingNames = collectUseRefBindingNames(componentBody, context.scopes);
43565
44437
  const localBindingNames = collectLocalBindingNames(componentBody);
43566
44438
  for (const param of componentParams) collectPatternNames(param, localBindingNames);
43567
44439
  walkAst(componentBody, (child) => {
43568
44440
  if (!isNodeOfType(child, "CallExpression")) return;
43569
- if (!isHookCall$2(child, HOOKS_WITH_DEPS)) return;
44441
+ if (!isReactHookCall(child, HOOKS_WITH_DEPS, context.scopes)) return;
43570
44442
  if ((child.arguments?.length ?? 0) < 2) return;
43571
44443
  const depsNode = child.arguments[1];
43572
44444
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -45396,6 +46268,7 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
45396
46268
  "useMatchMedia",
45397
46269
  "useMediaJobProgress",
45398
46270
  "useMediaQuery",
46271
+ "useMediaQueryState",
45399
46272
  "useResizeObserver",
45400
46273
  "useVisibility",
45401
46274
  "useWindowSize"
@@ -45432,9 +46305,30 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
45432
46305
  if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
45433
46306
  return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
45434
46307
  };
45435
- const isExternalSubscriptionHookRef = (ref) => {
46308
+ const getLocalHookExternalStateProof = (analysis, ref) => {
46309
+ let hookFunction = resolveToFunction(ref);
46310
+ if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
46311
+ const definitionNode = definition.node;
46312
+ if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
46313
+ const initializer = stripParenExpression(definitionNode.init);
46314
+ if (!isNodeOfType(initializer, "CallExpression")) continue;
46315
+ const callee = stripParenExpression(initializer.callee);
46316
+ if (!isNodeOfType(callee, "Identifier")) continue;
46317
+ const calleeReference = getRef(analysis, callee);
46318
+ if (!calleeReference) continue;
46319
+ hookFunction = resolveToFunction(calleeReference);
46320
+ if (hookFunction) break;
46321
+ }
46322
+ if (!hookFunction) return null;
46323
+ const returnedReferences = collectFunctionReturnStatements(hookFunction).flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
46324
+ if (returnedReferences.length === 0) return null;
46325
+ return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
46326
+ };
46327
+ const isExternalSubscriptionHookRef = (analysis, ref) => {
45436
46328
  const identifier = ref.identifier;
45437
46329
  if (!isNodeOfType(identifier, "Identifier")) return false;
46330
+ const localHookProof = getLocalHookExternalStateProof(analysis, ref);
46331
+ if (localHookProof !== null) return localHookProof;
45438
46332
  if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
45439
46333
  return Boolean(ref.resolved?.defs.some((def) => {
45440
46334
  const node = def.node;
@@ -45457,16 +46351,10 @@ const noPassDataToParent = defineRule({
45457
46351
  tags: ["test-noise"],
45458
46352
  recommendation: "Fetch the data in the parent and pass it down as a prop (or return it from the hook), instead of handing it back up through a prop callback in a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#passing-data-to-the-parent",
45459
46353
  create: (context) => {
45460
- const isReactUseRefCall = (node) => isReactApiCall(node, "useRef", context.scopes, {
45461
- allowGlobalReactNamespace: true,
45462
- allowUnboundBareCalls: true
45463
- });
45464
- const isReactUseEffectCall = (node) => isReactApiCall(node, "useEffect", context.scopes, {
45465
- allowGlobalReactNamespace: true,
45466
- allowUnboundBareCalls: true
45467
- });
46354
+ const isReactUseRefCall = (node) => isReactHookCall(node, "useRef", context.scopes);
46355
+ const isReactUseEffectCall = (node) => isReactHookCall(node, "useEffect", context.scopes);
45468
46356
  return { CallExpression(node) {
45469
- if (!isUseEffect(node)) return;
46357
+ if (!isReactUseEffectCall(node)) return;
45470
46358
  const analysis = getProgramAnalysis(node);
45471
46359
  if (!analysis) return;
45472
46360
  if (hasCleanup(analysis, node)) return;
@@ -45518,11 +46406,11 @@ const noPassDataToParent = defineRule({
45518
46406
  if (argumentRef && resolveToFunction(argumentRef)) return [];
45519
46407
  }
45520
46408
  return getDownstreamRefs(analysis, argument);
45521
- }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
46409
+ }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) || isExternalSubscriptionHookRef(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
45522
46410
  if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
45523
46411
  if (!argsUpstreamRefs.some((argRef) => {
45524
46412
  if (isUseStateIdentifier(argRef.identifier)) return false;
45525
- if (isExternalSubscriptionHookRef(argRef)) return false;
46413
+ if (isExternalSubscriptionHookRef(analysis, argRef)) return false;
45526
46414
  if (isProp(analysis, argRef)) return false;
45527
46415
  if (isUseRefIdentifier(argRef.identifier)) return false;
45528
46416
  if (isRefCurrent(argRef)) return false;
@@ -45753,7 +46641,7 @@ const noPassLiveStateToParent = defineRule({
45753
46641
  tags: ["test-noise"],
45754
46642
  recommendation: "Move the state up to the parent (or return it from the hook), instead of handing it back up through a prop callback in a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#notifying-parent-components-about-state-changes",
45755
46643
  create: (context) => ({ CallExpression(node) {
45756
- if (!isUseEffect(node)) return;
46644
+ if (!isReactHookCall(node, "useEffect", context.scopes)) return;
45757
46645
  const analysis = getProgramAnalysis(node);
45758
46646
  if (!analysis) return;
45759
46647
  const effectFnRefs = getEffectFnRefs(analysis, node);
@@ -46168,7 +47056,7 @@ const hasPreviousValueDep = (effectNode, depElements) => {
46168
47056
  if (!isNodeOfType(element, "Identifier")) continue;
46169
47057
  const binding = findVariableInitializer(effectNode, element.name);
46170
47058
  if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) continue;
46171
- const calleeName = getCalleeName$2(binding.initializer);
47059
+ const calleeName = getCalleeName$1(binding.initializer);
46172
47060
  if (calleeName && PREVIOUS_VALUE_HOOK_PATTERN.test(calleeName)) return true;
46173
47061
  }
46174
47062
  return false;
@@ -46190,7 +47078,7 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
46190
47078
  if (!isNodeOfType(receiver, "Identifier")) return null;
46191
47079
  const binding = findVariableInitializer(callExpression, receiver.name);
46192
47080
  if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
46193
- if (getCalleeName$2(binding.initializer) !== "useRef") return null;
47081
+ if (getCalleeName$1(binding.initializer) !== "useRef") return null;
46194
47082
  const callbackArgument = binding.initializer.arguments?.[0];
46195
47083
  if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
46196
47084
  return isPropName(callbackArgument.name) ? callbackArgument.name : null;
@@ -46222,7 +47110,7 @@ const noPropCallbackInEffect = defineRule({
46222
47110
  return {
46223
47111
  ...propStackTracker.visitors,
46224
47112
  CallExpression(node) {
46225
- if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
47113
+ if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes) || (node.arguments?.length ?? 0) < 2) return;
46226
47114
  const callback = getEffectCallback(node);
46227
47115
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
46228
47116
  const depsNode = node.arguments[1];
@@ -47086,6 +47974,69 @@ const noRedundantShouldComponentUpdate = defineRule({
47086
47974
  }
47087
47975
  });
47088
47976
  //#endregion
47977
+ //#region src/plugin/rules/correctness/no-ref-callback-cleanup-before-react-19.ts
47978
+ const resolveFunctionExpressions = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
47979
+ const expression = stripParenExpression(rawExpression);
47980
+ if (isFunctionLike$1(expression)) return expression.async || expression.generator ? [] : [expression];
47981
+ if (isNodeOfType(expression, "ConditionalExpression")) {
47982
+ if (isNodeOfType(expression.test, "Literal")) return resolveFunctionExpressions(expression.test.value ? expression.consequent : expression.alternate, scopes, visitedSymbolIds);
47983
+ return [...resolveFunctionExpressions(expression.consequent, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.alternate, scopes, visitedSymbolIds)];
47984
+ }
47985
+ if (isNodeOfType(expression, "LogicalExpression")) {
47986
+ if (isNodeOfType(expression.left, "Literal")) {
47987
+ const isLeftTruthy = Boolean(expression.left.value);
47988
+ if (expression.operator === "&&" && !isLeftTruthy) return [];
47989
+ if (expression.operator === "||" && isLeftTruthy) return [];
47990
+ if (expression.operator === "??" && expression.left.value !== null) return [];
47991
+ }
47992
+ if (expression.operator === "&&") return resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds);
47993
+ return [...resolveFunctionExpressions(expression.left, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds)];
47994
+ }
47995
+ if (isNodeOfType(expression, "SequenceExpression")) {
47996
+ const finalExpression = expression.expressions.at(-1);
47997
+ return finalExpression ? resolveFunctionExpressions(finalExpression, scopes, visitedSymbolIds) : [];
47998
+ }
47999
+ if (isNodeOfType(expression, "CallExpression")) {
48000
+ if (!isReactApiCall(expression, "useCallback", scopes)) return [];
48001
+ const callback = expression.arguments[0];
48002
+ return callback && !isNodeOfType(callback, "SpreadElement") ? resolveFunctionExpressions(callback, scopes, visitedSymbolIds) : [];
48003
+ }
48004
+ if (!isNodeOfType(expression, "Identifier")) return [];
48005
+ const symbol = scopes.symbolFor(expression);
48006
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return [];
48007
+ if (symbol.kind === "function" && isNodeOfType(symbol.declarationNode, "FunctionDeclaration") && symbol.references.every((reference) => reference.flag === "read")) return resolveFunctionExpressions(symbol.declarationNode, scopes, new Set([...visitedSymbolIds, symbol.id]));
48008
+ const initializer = getDirectConstInitializer(symbol);
48009
+ if (!initializer) return [];
48010
+ return resolveFunctionExpressions(initializer, scopes, new Set([...visitedSymbolIds, symbol.id]));
48011
+ };
48012
+ const functionReturnsCleanupFunction = (functionExpression, scopes) => {
48013
+ if (!isFunctionLike$1(functionExpression)) return false;
48014
+ if (!isNodeOfType(functionExpression.body, "BlockStatement")) return resolveFunctionExpressions(functionExpression.body, scopes).length > 0;
48015
+ return collectFunctionReturnStatements(functionExpression).some((returnStatement) => Boolean(returnStatement.argument && resolveFunctionExpressions(returnStatement.argument, scopes).length > 0));
48016
+ };
48017
+ const callbackReturnsCleanupFunction = (callback, scopes) => {
48018
+ return resolveFunctionExpressions(callback, scopes).some((functionExpression) => functionReturnsCleanupFunction(functionExpression, scopes));
48019
+ };
48020
+ const noRefCallbackCleanupBeforeReact19 = defineRule({
48021
+ id: "no-ref-callback-cleanup-before-react-19",
48022
+ title: "Ref cleanup requires React 19",
48023
+ requires: ["react:18"],
48024
+ disabledWhen: ["react:19"],
48025
+ severity: "warn",
48026
+ recommendation: "React 18 ignores functions returned from ref callbacks. Handle cleanup when React calls the ref with `null`, or require React 19 before returning a cleanup function.",
48027
+ create: (context) => ({ JSXAttribute(node) {
48028
+ if (getJsxAttributeName(node.name) !== "ref") return;
48029
+ if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
48030
+ const callback = node.value.expression;
48031
+ if (!callback || isNodeOfType(callback, "JSXEmptyExpression")) return;
48032
+ if (!callbackReturnsCleanupFunction(callback, context.scopes)) return;
48033
+ context.report({
48034
+ node,
48035
+ message: "This ref callback returns a cleanup function, but React 18 ignores ref cleanup returns, so the cleanup never runs. Handle detachment when React calls the ref with `null`, or require React 19."
48036
+ });
48037
+ } })
48038
+ });
48039
+ //#endregion
47089
48040
  //#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
47090
48041
  const REPEATED_ANCESTOR_TYPES = new Set([
47091
48042
  "DoWhileStatement",
@@ -48052,7 +49003,7 @@ const noResetAllStateOnPropChange = defineRule({
48052
49003
  tags: ["test-noise"],
48053
49004
  recommendation: "Pass the prop as `key` so React resets the component for you when the prop changes, instead of clearing every state value by hand in a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#resetting-all-state-when-a-prop-changes",
48054
49005
  create: (context) => ({ CallExpression(node) {
48055
- if (!isUseEffect(node)) return;
49006
+ if (!isReactHookCall(node, "useEffect", context.scopes)) return;
48056
49007
  const analysis = getProgramAnalysis(node);
48057
49008
  if (!analysis) return;
48058
49009
  const effectFnRefs = getEffectFnRefs(analysis, node);
@@ -48321,7 +49272,7 @@ const isTanStackServerFnHandlerCall = (node) => {
48321
49272
  if (node.callee.property.name !== "handler") return false;
48322
49273
  let currentNode = node.callee.object;
48323
49274
  while (isNodeOfType(currentNode, "CallExpression")) {
48324
- const calleeName = getCalleeName$2(currentNode);
49275
+ const calleeName = getCalleeName$1(currentNode);
48325
49276
  if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) return true;
48326
49277
  if (!isNodeOfType(currentNode.callee, "MemberExpression")) return false;
48327
49278
  currentNode = currentNode.callee.object;
@@ -48822,7 +49773,7 @@ const noSelfUpdatingEffect = defineRule({
48822
49773
  create: (context) => {
48823
49774
  const checkFunctionScope = (functionBody) => {
48824
49775
  if (!functionBody || !isNodeOfType(functionBody, "BlockStatement")) return;
48825
- const useStateBindings = collectUseStateBindings(functionBody);
49776
+ const useStateBindings = collectUseStateBindings(functionBody, context.scopes);
48826
49777
  if (useStateBindings.length === 0) return;
48827
49778
  const setterNameToStateName = /* @__PURE__ */ new Map();
48828
49779
  for (const binding of useStateBindings) setterNameToStateName.set(binding.setterName, binding.valueName);
@@ -48831,7 +49782,7 @@ const noSelfUpdatingEffect = defineRule({
48831
49782
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
48832
49783
  const effectCall = unwrapDiscardedExpression(statement);
48833
49784
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
48834
- if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
49785
+ if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
48835
49786
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
48836
49787
  const dependencyStateNames = collectDependencyStateNames(effectCall.arguments[1]);
48837
49788
  if (dependencyStateNames.size === 0) continue;
@@ -48917,7 +49868,7 @@ const noSetStateInRender = defineRule({
48917
49868
  create: (context) => {
48918
49869
  const checkComponent = (componentBody) => {
48919
49870
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
48920
- const setterNames = new Set(collectUseStateBindings(componentBody).map((binding) => binding.setterName));
49871
+ const setterNames = new Set(collectUseStateBindings(componentBody, context.scopes).map((binding) => binding.setterName));
48921
49872
  if (setterNames.size === 0) return;
48922
49873
  for (const statement of componentBody.body ?? []) {
48923
49874
  const setterCall = isUnconditionalSetterCallStatement(statement, setterNames);
@@ -49166,10 +50117,10 @@ const collectTimerRefUsageFacts = (ownerScope, refName) => {
49166
50117
  });
49167
50118
  return facts;
49168
50119
  };
49169
- const isEffectCallbackFunction = (functionNode) => {
50120
+ const isEffectCallbackFunction = (functionNode, scopes) => {
49170
50121
  const parent = functionNode.parent;
49171
50122
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
49172
- return isHookCall$2(parent, EFFECT_HOOK_NAMES$1) && getEffectCallback(parent) === functionNode;
50123
+ return isReactHookCall(parent, EFFECT_HOOK_NAMES$1, scopes) && getEffectCallback(parent) === functionNode;
49173
50124
  };
49174
50125
  const doesEffectCallbackReturnName = (effectCallback, name) => {
49175
50126
  if (!isFunctionLike$1(effectCallback)) return false;
@@ -49188,24 +50139,24 @@ const isFunctionReturnedFromEffectCallback = (functionNode, effectCallback) => {
49188
50139
  const cleanupBindingName = getFunctionBindingName$1(functionNode);
49189
50140
  return cleanupBindingName !== null && doesEffectCallbackReturnName(effectCallback, cleanupBindingName);
49190
50141
  };
49191
- const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
50142
+ const isReturnedFromAnyEffectInScope = (functionNode, ownerScope, scopes) => {
49192
50143
  const cleanupBindingName = getFunctionBindingName$1(functionNode);
49193
50144
  if (cleanupBindingName === null) return false;
49194
50145
  let isReturnedFromEffect = false;
49195
50146
  walkAst(ownerScope, (child) => {
49196
50147
  if (isReturnedFromEffect) return false;
49197
- if (!isNodeOfType(child, "CallExpression") || !isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
50148
+ if (!isNodeOfType(child, "CallExpression") || !isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) return;
49198
50149
  const effectCallback = getEffectCallback(child);
49199
50150
  if (effectCallback && doesEffectCallbackReturnName(effectCallback, cleanupBindingName)) isReturnedFromEffect = true;
49200
50151
  });
49201
50152
  return isReturnedFromEffect;
49202
50153
  };
49203
- const isInsideEffectCleanupReturn = (node, ownerScope) => {
50154
+ const isInsideEffectCleanupReturn = (node, ownerScope, scopes) => {
49204
50155
  let functionNode = findEnclosingFunction$1(node);
49205
50156
  while (functionNode) {
49206
50157
  const outerFunction = findEnclosingFunction$1(functionNode);
49207
- if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
49208
- if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
50158
+ if (outerFunction && isEffectCallbackFunction(outerFunction, scopes) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
50159
+ if (isReturnedFromAnyEffectInScope(functionNode, ownerScope, scopes)) return true;
49209
50160
  functionNode = outerFunction;
49210
50161
  }
49211
50162
  return false;
@@ -49241,10 +50192,10 @@ const noStaleTimerRef = defineRule({
49241
50192
  if (isShadowedTimerGlobal(node)) return;
49242
50193
  const { clearCalleeName, refName } = clearCall;
49243
50194
  const refBinding = findVariableInitializer(node, refName);
49244
- if (!refBinding?.initializer || !isHookCall$2(refBinding.initializer, "useRef")) return;
50195
+ if (!refBinding?.initializer || !isReactHookCall(refBinding.initializer, "useRef", context.scopes)) return;
49245
50196
  const usageFacts = collectTimerRefUsageFacts(refBinding.scopeOwner, refName);
49246
50197
  if (!usageFacts.holdsScheduledTimerId || !usageFacts.hasPendingSignalRead) return;
49247
- if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner)) return;
50198
+ if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner, context.scopes)) return;
49248
50199
  if (hasRefCurrentReassignmentAfterClear(node, refName)) return;
49249
50200
  context.report({
49250
50201
  node,
@@ -55048,7 +55999,7 @@ const classifyCallableReadsInsideEffect = (callableIdentifier, effectCallback, c
55048
55999
  allReadsAreInSubHandlers = false;
55049
56000
  return;
55050
56001
  }
55051
- if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$2(subHandlerCall);
56002
+ if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$1(subHandlerCall);
55052
56003
  });
55053
56004
  return {
55054
56005
  hasAnyRead,
@@ -55071,7 +56022,7 @@ const preferUseEffectEvent = defineRule({
55071
56022
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
55072
56023
  const effectCall = statement.expression;
55073
56024
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
55074
- if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
56025
+ if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
55075
56026
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
55076
56027
  const depsNode = effectCall.arguments[1];
55077
56028
  if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
@@ -55103,11 +56054,11 @@ const preferUseEffectEvent = defineRule({
55103
56054
  });
55104
56055
  //#endregion
55105
56056
  //#region src/plugin/rules/state-and-effects/prefer-use-sync-external-store.ts
55106
- const findUseEffectsInComponent = (componentBody) => {
56057
+ const findUseEffectsInComponent = (componentBody, scopes) => {
55107
56058
  const effectCalls = [];
55108
56059
  if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
55109
56060
  for (const statement of componentBody.body ?? []) walkAst(statement, (child) => {
55110
- if (isNodeOfType(child, "CallExpression") && isHookCall$2(child, EFFECT_HOOK_NAMES$1)) effectCalls.push(child);
56061
+ if (isNodeOfType(child, "CallExpression") && isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) effectCalls.push(child);
55111
56062
  });
55112
56063
  return effectCalls;
55113
56064
  };
@@ -55310,7 +56261,7 @@ const preferUseSyncExternalStore = defineRule({
55310
56261
  };
55311
56262
  const checkComponent = (componentBody) => {
55312
56263
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
55313
- const useStateBindings = collectUseStateBindings(componentBody);
56264
+ const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
55314
56265
  if (useStateBindings.length === 0) return;
55315
56266
  const useStateInitializerByValueName = /* @__PURE__ */ new Map();
55316
56267
  for (const binding of useStateBindings) {
@@ -55323,7 +56274,7 @@ const preferUseSyncExternalStore = defineRule({
55323
56274
  }
55324
56275
  const setterNameToValueName = /* @__PURE__ */ new Map();
55325
56276
  for (const binding of useStateBindings) setterNameToValueName.set(binding.setterName, binding.valueName);
55326
- for (const effectCall of findUseEffectsInComponent(componentBody)) {
56277
+ for (const effectCall of findUseEffectsInComponent(componentBody, context.scopes)) {
55327
56278
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
55328
56279
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
55329
56280
  const depsNode = effectCall.arguments[1];
@@ -55365,7 +56316,7 @@ const preferUseSyncExternalStore = defineRule({
55365
56316
  })).filter((candidate) => candidate.storeName !== null);
55366
56317
  if (snapshotBindings.length === 0) return;
55367
56318
  const reportedDeclarators = /* @__PURE__ */ new Set();
55368
- for (const effectCall of findUseEffectsInComponent(componentBody)) {
56319
+ for (const effectCall of findUseEffectsInComponent(componentBody, context.scopes)) {
55369
56320
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
55370
56321
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
55371
56322
  const depsNode = effectCall.arguments[1];
@@ -55462,7 +56413,7 @@ const preferUseReducer = defineRule({
55462
56413
  create: (context) => {
55463
56414
  const reportCoUpdatedUseState = (body, componentName) => {
55464
56415
  if (!isNodeOfType(body, "BlockStatement")) return;
55465
- const bindings = collectUseStateBindings(body);
56416
+ const bindings = collectUseStateBindings(body, context.scopes);
55466
56417
  const setterNames = new Set(bindings.map((binding) => binding.setterName));
55467
56418
  if (setterNames.size < 5) return;
55468
56419
  const coUpdatedCount = findLargestCoUpdatedSetterGroup(body, setterNames, new Map(bindings.map((binding) => {
@@ -55678,7 +56629,7 @@ const QUERY_READ_METHOD_NAMES = new Set([
55678
56629
  ]);
55679
56630
  const isQueryCacheSourceCall = (initializer) => {
55680
56631
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
55681
- const hookName = getCalleeName$2(initializer);
56632
+ const hookName = getCalleeName$1(initializer);
55682
56633
  if (!hookName) return false;
55683
56634
  return hookName === "useQueryClient" || TRPC_UTILS_HOOK_PATTERN.test(hookName);
55684
56635
  };
@@ -55810,7 +56761,7 @@ const queryMutationMissingInvalidation = defineRule({
55810
56761
  },
55811
56762
  CallExpression(node) {
55812
56763
  if (!hasQueryReadUsage) {
55813
- const callName = getCalleeName$2(node);
56764
+ const callName = getCalleeName$1(node);
55814
56765
  if (callName && (QUERY_READ_HOOK_NAMES.has(callName) || QUERY_READ_METHOD_NAMES.has(callName) || TRPC_UTILS_HOOK_PATTERN.test(callName))) hasQueryReadUsage = true;
55815
56766
  }
55816
56767
  const calleeName = isNodeOfType(node.callee, "Identifier") ? node.callee.name : null;
@@ -57473,6 +58424,125 @@ const ASYNC_DATA_CALLEE_NAMES = new Set([
57473
58424
  const RESOURCE_LOAD_EVENT_ATTRIBUTE_PATTERN = /^on(?:Load|Error|Abort|Progress|CanPlay|Stalled|Suspend|Waiting|Ended)/;
57474
58425
  const JSX_EVENT_HANDLER_ATTRIBUTE_PATTERN = /^on[A-Z]/;
57475
58426
  const REDUX_DISPATCH_HOOK_PATTERN = /^use\w*Dispatch$/;
58427
+ const FILE_READER_READ_METHOD_NAMES = new Set([
58428
+ "readAsArrayBuffer",
58429
+ "readAsBinaryString",
58430
+ "readAsDataURL",
58431
+ "readAsText"
58432
+ ]);
58433
+ const isGlobalFileReaderConstruction = (expression, context) => {
58434
+ if (!expression) return false;
58435
+ const unwrappedExpression = stripParenExpression(expression);
58436
+ if (!isNodeOfType(unwrappedExpression, "NewExpression") || !isNodeOfType(unwrappedExpression.callee, "Identifier")) return false;
58437
+ return unwrappedExpression.callee.name === "FileReader" && context.scopes.isGlobalReference(unwrappedExpression.callee);
58438
+ };
58439
+ const getFileReaderOriginStartBefore = (readerSymbol, readCall, context) => {
58440
+ const readFunction = findEnclosingFunction$1(readCall);
58441
+ let latestValue = null;
58442
+ let latestStart = null;
58443
+ if (readerSymbol.initializer && findEnclosingFunction$1(readerSymbol.declarationNode) === readFunction && readerSymbol.declarationNode.range[0] < readCall.range[0]) {
58444
+ latestValue = readerSymbol.initializer;
58445
+ latestStart = readerSymbol.declarationNode.range[0];
58446
+ }
58447
+ for (const reference of readerSymbol.references) {
58448
+ if (reference.flag === "read" || reference.identifier.range[0] >= readCall.range[0] || latestStart !== null && reference.identifier.range[0] <= latestStart || findEnclosingFunction$1(reference.identifier) !== readFunction) continue;
58449
+ const assignment = reference.identifier.parent;
58450
+ if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== reference.identifier) continue;
58451
+ latestValue = assignment.right;
58452
+ latestStart = reference.identifier.range[0];
58453
+ }
58454
+ return isGlobalFileReaderConstruction(latestValue, context) ? latestStart : null;
58455
+ };
58456
+ const resolveLoadingCompletionFunction = (expression, context) => {
58457
+ const directFunction = resolveExactLocalFunction(expression, context.scopes);
58458
+ if (directFunction) return directFunction;
58459
+ const unwrappedExpression = stripParenExpression(expression);
58460
+ if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
58461
+ const symbol = context.scopes.symbolFor(unwrappedExpression);
58462
+ const initializer = symbol ? getDirectUnreassignedInitializer(symbol) : null;
58463
+ if (!initializer || !isNodeOfType(initializer, "CallExpression") || !isReactApiCall(initializer, "useCallback", context.scopes)) return null;
58464
+ const callback = initializer.arguments?.[0];
58465
+ return callback && isFunctionLike$1(callback) ? callback : null;
58466
+ };
58467
+ const isSetterBooleanCall = (node, setterSymbol, value, context) => {
58468
+ if (!isNodeOfType(node, "CallExpression")) return false;
58469
+ const callee = stripParenExpression(node.callee);
58470
+ const argument = node.arguments?.[0];
58471
+ const unwrappedArgument = argument ? stripParenExpression(argument) : null;
58472
+ return Boolean(isNodeOfType(callee, "Identifier") && context.scopes.symbolFor(callee) === setterSymbol && unwrappedArgument && isNodeOfType(unwrappedArgument, "Literal") && unwrappedArgument.value === value);
58473
+ };
58474
+ const functionClearsLoadingState = (functionNode, setterSymbol, context, visitedFunctions) => {
58475
+ if (visitedFunctions.has(functionNode) || !isFunctionLike$1(functionNode)) return false;
58476
+ visitedFunctions.add(functionNode);
58477
+ let didClearLoadingState = false;
58478
+ walkAst(functionNode.body, (child) => {
58479
+ if (didClearLoadingState) return false;
58480
+ if (child !== functionNode.body && isFunctionLike$1(child)) return false;
58481
+ if (!isNodeOfType(child, "CallExpression")) return;
58482
+ if (isSetterBooleanCall(child, setterSymbol, false, context)) {
58483
+ didClearLoadingState = true;
58484
+ return false;
58485
+ }
58486
+ const helperFunction = resolveLoadingCompletionFunction(child.callee, context);
58487
+ if (helperFunction && functionClearsLoadingState(helperFunction, setterSymbol, context, visitedFunctions)) {
58488
+ didClearLoadingState = true;
58489
+ return false;
58490
+ }
58491
+ });
58492
+ return didClearLoadingState;
58493
+ };
58494
+ const getLatestFileReaderCallbackBefore = (readCall, readerSymbol, propertyName, originStart, context) => {
58495
+ const readFunction = findEnclosingFunction$1(readCall);
58496
+ if (!readFunction || !isFunctionLike$1(readFunction)) return null;
58497
+ let callback = null;
58498
+ let callbackStart = originStart;
58499
+ walkAst(readFunction.body, (child) => {
58500
+ if (child !== readFunction.body && isFunctionLike$1(child)) return false;
58501
+ if (!isNodeOfType(child, "AssignmentExpression") || child.operator !== "=" || child.range[0] >= readCall.range[0] || child.range[0] <= callbackStart || !isNodeOfType(child.left, "MemberExpression") || getStaticPropertyName(child.left) !== propertyName) return;
58502
+ const receiver = stripParenExpression(child.left.object);
58503
+ if (!isNodeOfType(receiver, "Identifier") || context.scopes.symbolFor(receiver) !== readerSymbol) return;
58504
+ callback = child.right;
58505
+ callbackStart = child.range[0];
58506
+ });
58507
+ return callback;
58508
+ };
58509
+ const setterStartsLoadingBefore = (readCall, setterSymbol, context) => {
58510
+ const readFunction = findEnclosingFunction$1(readCall);
58511
+ if (!readFunction || !isFunctionLike$1(readFunction)) return false;
58512
+ let didStartLoading = false;
58513
+ walkAst(readFunction.body, (child) => {
58514
+ if (didStartLoading) return false;
58515
+ if (child !== readFunction.body && isFunctionLike$1(child)) return false;
58516
+ if (!isNodeOfType(child, "CallExpression") || child.range[0] >= readCall.range[0]) return;
58517
+ if (isSetterBooleanCall(child, setterSymbol, true, context)) {
58518
+ didStartLoading = true;
58519
+ return false;
58520
+ }
58521
+ });
58522
+ return didStartLoading;
58523
+ };
58524
+ const setterTracksFileReader = (functionBody, setterSymbol, context) => {
58525
+ let didFindFileReaderLifecycle = false;
58526
+ walkAst(functionBody, (child) => {
58527
+ if (didFindFileReaderLifecycle) return false;
58528
+ if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || !FILE_READER_READ_METHOD_NAMES.has(getStaticPropertyName(child.callee) ?? "")) return;
58529
+ const receiver = stripParenExpression(child.callee.object);
58530
+ if (!isNodeOfType(receiver, "Identifier")) return;
58531
+ const readerSymbol = context.scopes.symbolFor(receiver);
58532
+ if (!readerSymbol) return;
58533
+ const originStart = getFileReaderOriginStartBefore(readerSymbol, child, context);
58534
+ if (originStart === null || !setterStartsLoadingBefore(child, setterSymbol, context)) return;
58535
+ const loadCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onload", originStart, context);
58536
+ const errorCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onerror", originStart, context);
58537
+ const loadFunction = loadCallback ? resolveLoadingCompletionFunction(loadCallback, context) : null;
58538
+ const errorFunction = errorCallback ? resolveLoadingCompletionFunction(errorCallback, context) : null;
58539
+ if (loadFunction && errorFunction && functionClearsLoadingState(loadFunction, setterSymbol, context, /* @__PURE__ */ new Set()) && functionClearsLoadingState(errorFunction, setterSymbol, context, /* @__PURE__ */ new Set())) {
58540
+ didFindFileReaderLifecycle = true;
58541
+ return false;
58542
+ }
58543
+ });
58544
+ return didFindFileReaderLifecycle;
58545
+ };
57476
58546
  const hasAsyncLoadingWork = (fnBody, setterName) => {
57477
58547
  let found = false;
57478
58548
  walkAst(fnBody, (child) => {
@@ -57646,6 +58716,8 @@ const renderingUsetransitionLoading = defineRule({
57646
58716
  const fnBody = enclosingFunctionBody(node);
57647
58717
  if (fnBody && hasAsyncLoadingWork(fnBody, setterName)) return;
57648
58718
  if (fnBody && setterName) {
58719
+ const setterSymbol = isNodeOfType(secondBinding, "Identifier") ? context.scopes.symbolFor(secondBinding) : null;
58720
+ if (setterSymbol && setterTracksFileReader(fnBody, setterSymbol, context)) return;
57649
58721
  if (setterEscapes(fnBody, setterName, node)) return;
57650
58722
  if (setterCalledAlongsideAsyncSignal(fnBody, setterName)) return;
57651
58723
  if (setterCalledInEventListenerHandler(fnBody, setterName)) return;
@@ -57977,7 +59049,7 @@ const rerenderDependencies = defineRule({
57977
59049
  severity: "error",
57978
59050
  recommendation: "Move it into a useMemo, useRef, or a constant outside the component so it stays the same between renders.",
57979
59051
  create: (context) => ({ CallExpression(node) {
57980
- if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
59052
+ if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes) || node.arguments.length < 2) return;
57981
59053
  const depsNode = node.arguments[1];
57982
59054
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
57983
59055
  for (const element of depsNode.elements ?? []) {
@@ -58232,7 +59304,7 @@ const rerenderLazyRefInit = defineRule({
58232
59304
  category: "Performance",
58233
59305
  recommendation: "Initialize the ref lazily so expensive values are not rebuilt and discarded on every render.",
58234
59306
  create: (context) => ({ CallExpression(node) {
58235
- if (!isHookCall$2(node, "useRef") || !node.arguments?.length) return;
59307
+ if (!isReactHookCall(node, "useRef", context.scopes) || !node.arguments?.length) return;
58236
59308
  const initializer = stripParenExpression(node.arguments[0]);
58237
59309
  const isPlainCall = isNodeOfType(initializer, "CallExpression");
58238
59310
  const isNewCall = isNodeOfType(initializer, "NewExpression");
@@ -58296,7 +59368,7 @@ const rerenderLazyStateInit = defineRule({
58296
59368
  category: "Performance",
58297
59369
  recommendation: "Wrap expensive initial state in an arrow function so the initializer does not rerun and get thrown away on every render.",
58298
59370
  create: (context) => ({ CallExpression(node) {
58299
- if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
59371
+ if (!isReactHookCall(node, "useState", context.scopes) || !node.arguments?.length) return;
58300
59372
  const initializer = findEagerInitializerCall(node.arguments[0]);
58301
59373
  if (!initializer) return;
58302
59374
  const isConstructor = isNodeOfType(initializer, "NewExpression");
@@ -59059,11 +60131,11 @@ const isInsideConditionTest = (identifier, stopAt) => {
59059
60131
  }
59060
60132
  return false;
59061
60133
  };
59062
- const collectEffectDependencyInfos = (componentBody, setterNames) => {
60134
+ const collectEffectDependencyInfos = (componentBody, setterNames, scopes) => {
59063
60135
  const effectInfos = [];
59064
60136
  walkAst(componentBody, (child) => {
59065
60137
  if (!isNodeOfType(child, "CallExpression")) return;
59066
- if (!isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
60138
+ if (!isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) return;
59067
60139
  const dependencyNames = /* @__PURE__ */ new Set();
59068
60140
  for (const argument of child.arguments ?? []) {
59069
60141
  if (!isNodeOfType(argument, "ArrayExpression")) continue;
@@ -59119,13 +60191,14 @@ const collectEffectDependencyInfos = (componentBody, setterNames) => {
59119
60191
  });
59120
60192
  return effectInfos;
59121
60193
  };
59122
- const collectCustomHookArgumentNames = (componentBody) => {
60194
+ const collectCustomHookArgumentNames = (componentBody, scopes) => {
59123
60195
  const argumentNames = /* @__PURE__ */ new Set();
59124
60196
  walkAst(componentBody, (child) => {
59125
60197
  if (!isNodeOfType(child, "CallExpression")) return;
59126
60198
  if (!isNodeOfType(child.callee, "Identifier")) return;
59127
60199
  const calleeName = child.callee.name;
59128
60200
  if (!isReactHookName(calleeName)) return;
60201
+ if (isReactHookCall(child, BUILTIN_HOOK_NAMES, scopes)) return;
59129
60202
  if (BUILTIN_HOOK_NAMES.has(calleeName)) return;
59130
60203
  if (EFFECT_HOOK_NAMES$1.has(calleeName)) return;
59131
60204
  for (const argument of child.arguments ?? []) walkAst(argument, (argumentNode) => {
@@ -59166,21 +60239,21 @@ const rerenderStateOnlyInHandlers = defineRule({
59166
60239
  create: (context) => {
59167
60240
  const checkComponent = (componentBody) => {
59168
60241
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
59169
- const bindings = collectUseStateBindings(componentBody);
60242
+ const bindings = collectUseStateBindings(componentBody, context.scopes);
59170
60243
  if (bindings.length === 0) return;
59171
60244
  if (collectRenderReachableExpressions(componentBody).length === 0) return;
59172
- const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
60245
+ const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
59173
60246
  const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
59174
- const directRenderNames = collectRenderReachableNames(componentBody, eventHandlerReferenceNames);
60247
+ const directRenderNames = collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames);
59175
60248
  if (hasRenderPhaseNonHookCall(componentBody)) for (const voidMarkedName of collectTopLevelVoidMarkedNames(componentBody)) directRenderNames.add(voidMarkedName);
59176
60249
  const renderReachableNames = expandTransitiveDependencies(directRenderNames, dependencyGraph);
59177
60250
  const setterNames = new Set(bindings.map((binding) => binding.setterName));
59178
- const effectInfos = collectEffectDependencyInfos(componentBody, setterNames);
60251
+ const effectInfos = collectEffectDependencyInfos(componentBody, setterNames, context.scopes);
59179
60252
  const selfEchoValueNames = /* @__PURE__ */ new Set();
59180
60253
  for (const binding of bindings) if (effectInfos.some((effectInfo) => effectInfo.dependencyNames.has(binding.valueName) && effectInfo.synchronouslyCalledFunctionNames.has(binding.setterName) && !effectInfo.payloadReadNames.has(binding.valueName) && !effectInfo.nestedCallbackCalledFunctionNames.has(binding.setterName))) selfEchoValueNames.add(binding.valueName);
59181
60254
  const effectConsumedNames = /* @__PURE__ */ new Set();
59182
60255
  for (const effectInfo of effectInfos) for (const dependencyName of effectInfo.dependencyNames) if (!selfEchoValueNames.has(dependencyName)) effectConsumedNames.add(dependencyName);
59183
- for (const hookArgumentName of collectCustomHookArgumentNames(componentBody)) effectConsumedNames.add(hookArgumentName);
60256
+ for (const hookArgumentName of collectCustomHookArgumentNames(componentBody, context.scopes)) effectConsumedNames.add(hookArgumentName);
59184
60257
  for (const reachableName of expandTransitiveDependencies(effectConsumedNames, dependencyGraph)) renderReachableNames.add(reachableName);
59185
60258
  const calledSetterNames = /* @__PURE__ */ new Set();
59186
60259
  walkAst(componentBody, (child) => {
@@ -67081,7 +68154,7 @@ const declarationAwaitsGate = (declaration, context) => {
67081
68154
  if (!isNodeOfType(argument, "CallExpression")) continue;
67082
68155
  if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
67083
68156
  if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
67084
- const calleeName = getCalleeName$2(argument);
68157
+ const calleeName = getCalleeName$1(argument);
67085
68158
  if (!calleeName) continue;
67086
68159
  if (isAuthGuardName(calleeName)) return true;
67087
68160
  const [leadingToken] = tokenizeIdentifierWords(calleeName);
@@ -67721,7 +68794,7 @@ const walkServerFnChain = (outerNode) => {
67721
68794
  if (!isNodeOfType(outerNode.callee, "MemberExpression")) return result;
67722
68795
  let currentNode = stripParenExpression(outerNode.callee.object);
67723
68796
  while (isNodeOfType(currentNode, "CallExpression")) {
67724
- const calleeName = getCalleeName$2(currentNode);
68797
+ const calleeName = getCalleeName$1(currentNode);
67725
68798
  if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) {
67726
68799
  result.isServerFnChain = true;
67727
68800
  const optionsArgument = currentNode.arguments?.[0];
@@ -71853,6 +72926,17 @@ const reactDoctorRules = [
71853
72926
  requires: [...new Set(["react", ...noRedundantShouldComponentUpdate.requires ?? []])]
71854
72927
  }
71855
72928
  },
72929
+ {
72930
+ key: "react-doctor/no-ref-callback-cleanup-before-react-19",
72931
+ id: "no-ref-callback-cleanup-before-react-19",
72932
+ source: "react-doctor",
72933
+ originallyExternal: false,
72934
+ rule: {
72935
+ ...noRefCallbackCleanupBeforeReact19,
72936
+ framework: "global",
72937
+ category: "Bugs"
72938
+ }
72939
+ },
71856
72940
  {
71857
72941
  key: "react-doctor/no-ref-current-in-render",
71858
72942
  id: "no-ref-current-in-render",