oxlint-plugin-react-doctor 0.7.9-dev.21043e0 → 0.7.9-dev.2450582

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1057 -390
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -836,22 +836,131 @@ 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());
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);
853
954
  };
854
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
+ });
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";
857
966
  //#endregion
@@ -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;
@@ -14954,7 +15184,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
14954
15184
  return true;
14955
15185
  };
14956
15186
  const matchesPairedReleaseVerb = (releaseVerbName, pairedVerbNames) => pairedVerbNames.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName);
14957
- const isReturnedEffectCleanupFunction = (functionNode) => {
15187
+ const isReturnedEffectCleanupFunction = (functionNode, context) => {
14958
15188
  let currentNode = functionNode;
14959
15189
  let parentNode = currentNode.parent;
14960
15190
  while (isNodeOfType(parentNode, "ChainExpression") || isNodeOfType(parentNode, "TSAsExpression") || isNodeOfType(parentNode, "TSNonNullExpression")) {
@@ -14963,16 +15193,147 @@ const isReturnedEffectCleanupFunction = (functionNode) => {
14963
15193
  }
14964
15194
  const effectCallback = isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode ? findEnclosingFunction$1(parentNode) : isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode ? parentNode : null;
14965
15195
  const effectCall = effectCallback?.parent;
14966
- 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));
14967
15197
  };
14968
15198
  const isPotentiallyReachableFunction = (functionNode, context) => {
14969
- if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode)) return true;
15199
+ if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode, context)) return true;
14970
15200
  const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
14971
15201
  if (!bindingIdentifier) return false;
14972
15202
  const symbol = context.scopes.symbolFor(bindingIdentifier);
14973
15203
  if (!symbol) return false;
14974
15204
  return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
14975
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
+ };
14976
15337
  const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
14977
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;
14978
15339
  const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
@@ -14997,6 +15358,7 @@ const isReleaseReachableForUsage = (releaseNode, usage, context) => {
14997
15358
  const releaseFunction = findEnclosingFunction$1(releaseNode);
14998
15359
  if (!releaseFunction) return true;
14999
15360
  if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
15361
+ if (isRetainedDisposerRefRelease(releaseNode, usage, context)) return true;
15000
15362
  const usageFunction = findEnclosingFunction$1(usage.node);
15001
15363
  if (usageFunction && isFunctionLike$1(usageFunction) && getAssignedReactRefSymbol(usageFunction, context) && isCleanupFunctionReferencedByReturn(usageFunction, releaseFunction, context)) return isReactRefCallbackCleanupOwnedByEffect(usageFunction, releaseFunction, usage, context);
15002
15364
  if (isSelfReleasingListenerRelease(releaseNode, releaseFunction, usage, context)) return true;
@@ -15331,7 +15693,7 @@ const findRetainedFunctionLeak = (retainedFunction, context, options) => {
15331
15693
  return false;
15332
15694
  }
15333
15695
  }
15334
- 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))) {
15335
15697
  const registrationDetails = getCallRegistrationDetails(child, context);
15336
15698
  const subscriptionUsage = {
15337
15699
  kind: "subscribe",
@@ -15539,7 +15901,7 @@ const isInlineRetainedHandlerFunction = (functionNode, context) => {
15539
15901
  if (!isFunctionLike$1(functionNode)) return false;
15540
15902
  const functionRoot = findTransparentExpressionRoot(functionNode);
15541
15903
  const callbackCall = functionRoot.parent;
15542
- 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;
15543
15905
  const parentNode = functionNode.parent;
15544
15906
  if (isDirectJsxEventHandlerValue(functionNode)) return true;
15545
15907
  if (!isNodeOfType(parentNode, "Property") || parentNode.value !== functionNode || parentNode.computed) return false;
@@ -15575,12 +15937,12 @@ const effectNeedsCleanup = defineRule({
15575
15937
  };
15576
15938
  return {
15577
15939
  CallExpression(node) {
15578
- if (isHookCall$2(node, "useCallback")) {
15940
+ if (isReactHookCall(node, "useCallback", context.scopes)) {
15579
15941
  const retainedCallback = getEffectCallback(node);
15580
15942
  if (retainedCallback && !isInlineRetainedHandlerFunction(retainedCallback, context)) reportRetainedLeak(retainedCallback);
15581
15943
  return;
15582
15944
  }
15583
- if (!isHookCall$2(node, CLEANUP_EFFECT_HOOK_NAMES)) return;
15945
+ if (!isReactHookCall(node, CLEANUP_EFFECT_HOOK_NAMES, context.scopes)) return;
15584
15946
  const callback = getEffectCallback(node);
15585
15947
  if (!callback) return;
15586
15948
  const usages = removeSynchronouslyReleasedUsages(callback, findSubscribeLikeUsages(callback, context), context);
@@ -15588,7 +15950,7 @@ const effectNeedsCleanup = defineRule({
15588
15950
  const firstUsage = findFirstUsageWithoutCleanup(callback, usages, context);
15589
15951
  if (!firstUsage) return;
15590
15952
  const resourceNoun = RESOURCE_NOUN_BY_KIND[firstUsage.kind];
15591
- const hookName = getCalleeName$2(node) ?? "effect";
15953
+ const hookName = getCalleeName$1(node) ?? "effect";
15592
15954
  context.report({
15593
15955
  node,
15594
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.`
@@ -16931,7 +17293,38 @@ const symbolHasStableImportedAlias = (symbol, scopes) => {
16931
17293
  const resolvedSymbol = resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes);
16932
17294
  return resolvedSymbol !== null && resolvedSymbol !== symbol && resolvedSymbol.kind === "import";
16933
17295
  };
16934
- 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);
16935
17328
  //#endregion
16936
17329
  //#region src/plugin/utils/symbol-has-react-use-effect-event-origin.ts
16937
17330
  const symbolHasReactUseEffectEventOrigin = (symbol, scopes) => {
@@ -18114,7 +18507,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
18114
18507
  if (!isUsed) continue;
18115
18508
  const reportNode = declaredKeyToReportNode.get(declaredKey) ?? depsArgument;
18116
18509
  const rootSymbol = getRootSymbol(reportNode, context.scopes);
18117
- if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) || !isUnstableInitializer(rootSymbol.initializer)) continue;
18510
+ if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) || symbolHasStableValue(rootSymbol, context.scopes) || !isUnstableInitializer(rootSymbol.initializer)) continue;
18118
18511
  context.report({
18119
18512
  node: reportNode,
18120
18513
  message: buildUnstableDepMessage(hookName, declaredKey)
@@ -18430,7 +18823,7 @@ const flattenCalleeName = (callee) => {
18430
18823
  const PRAGMA = "React";
18431
18824
  const isReactFunctionCall = (node, expectedCall) => {
18432
18825
  if (!isNodeOfType(node, "CallExpression")) return false;
18433
- if (getCalleeName$2(node) !== expectedCall) return false;
18826
+ if (getCalleeName$1(node) !== expectedCall) return false;
18434
18827
  if (isNodeOfType(node.callee, "MemberExpression")) {
18435
18828
  const receiver = stripParenExpression(node.callee.object);
18436
18829
  return isNodeOfType(receiver, "Identifier") && receiver.name === PRAGMA;
@@ -18628,10 +19021,14 @@ const hookUseState = defineRule({
18628
19021
  create: (context) => {
18629
19022
  const { allowDestructuredState } = resolveSettings$40(context.settings);
18630
19023
  return { CallExpression(node) {
18631
- if (!isReactFunctionCall(node, "useState")) return;
19024
+ const callExpressionNode = node;
19025
+ if (!isReactFunctionCall(callExpressionNode, "useState")) return;
19026
+ const expressionRoot = findTransparentExpressionRoot(callExpressionNode);
19027
+ const expressionParent = expressionRoot.parent;
19028
+ if (isNodeOfType(expressionParent, "ReturnStatement")) return;
19029
+ if (isNodeOfType(expressionParent, "ArrowFunctionExpression") && expressionParent.body === expressionRoot) return;
18632
19030
  const parent = node.parent;
18633
19031
  if (!parent) return;
18634
- if (isNodeOfType(parent, "ReturnStatement")) return;
18635
19032
  if (!isNodeOfType(parent, "VariableDeclarator")) {
18636
19033
  context.report({
18637
19034
  node,
@@ -18739,8 +19136,8 @@ const hooksNoNanInDeps = defineRule({
18739
19136
  severity: "warn",
18740
19137
  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.",
18741
19138
  create: (context) => ({ CallExpression(node) {
18742
- if (!isHookCall$2(node, HOOKS_WITH_DEP_ARRAY)) return;
18743
- const depsIndex = getCalleeName$2(node) === "useImperativeHandle" ? 2 : 1;
19139
+ if (!isReactHookCall(node, HOOKS_WITH_DEP_ARRAY, context.scopes)) return;
19140
+ const depsIndex = isReactHookCall(node, "useImperativeHandle", context.scopes) ? 2 : 1;
18744
19141
  const depsArgument = node.arguments[depsIndex];
18745
19142
  if (!depsArgument || !isNodeOfType(depsArgument, "ArrayExpression")) return;
18746
19143
  for (const element of depsArgument.elements) {
@@ -19911,7 +20308,7 @@ const isImportedSelectAtom = (callExpression) => {
19911
20308
  const isDeferredCallbackPosition$1 = (functionNode) => {
19912
20309
  const parent = functionNode.parent;
19913
20310
  if (isNodeOfType(parent, "CallExpression") && parent.arguments?.[0] === functionNode) {
19914
- const hookName = getCalleeName$2(parent);
20311
+ const hookName = getCalleeName$1(parent);
19915
20312
  if (hookName && MEMOIZING_HOOK_NAMES$1.has(hookName)) return true;
19916
20313
  if (hookName && EFFECT_HOOK_NAMES$1.has(hookName) && Boolean(parent.arguments?.[1])) return true;
19917
20314
  }
@@ -29982,7 +30379,7 @@ const DEFERRING_CALLEE_NAMES$1 = new Set([
29982
30379
  "on",
29983
30380
  "once"
29984
30381
  ]);
29985
- const getCalleeName$1 = (callee) => {
30382
+ const getCalleeName = (callee) => {
29986
30383
  if (!callee) return null;
29987
30384
  if (isNodeOfType(callee, "Identifier")) return callee.name;
29988
30385
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
@@ -29994,11 +30391,11 @@ const isDeferredCallbackPosition = (expression) => {
29994
30391
  const parent = parentOf(expression);
29995
30392
  if (!parent) return false;
29996
30393
  if (isNodeOfType(parent, "CallExpression") && argumentsInclude(parent.arguments, expression)) {
29997
- const name = getCalleeName$1(parent.callee);
30394
+ const name = getCalleeName(parent.callee);
29998
30395
  if (name && DEFERRING_CALLEE_NAMES$1.has(name)) return true;
29999
30396
  }
30000
30397
  if (isNodeOfType(parent, "NewExpression") && argumentsInclude(parent.arguments, expression)) {
30001
- const name = getCalleeName$1(parent.callee);
30398
+ const name = getCalleeName(parent.callee);
30002
30399
  if (name && (name.endsWith("Observer") || name === "Promise")) return true;
30003
30400
  }
30004
30401
  if (isNodeOfType(parent, "AssignmentExpression") && parent.right === expression && isNodeOfType(parent.left, "MemberExpression") && isNodeOfType(parent.left.property, "Identifier") && parent.left.property.name.startsWith("on")) return true;
@@ -30418,14 +30815,6 @@ const isHookCallee$1 = (analysis, node, hookName) => {
30418
30815
  if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
30419
30816
  return false;
30420
30817
  };
30421
- const isUseEffect = (node) => {
30422
- if (!node || !isNodeOfType(node, "CallExpression")) return false;
30423
- const callee = node.callee;
30424
- if (isNodeOfType(callee, "Identifier") && callee.name === "useEffect") return true;
30425
- if (!isNodeOfType(callee, "MemberExpression")) return false;
30426
- const receiver = stripParenExpression(callee.object);
30427
- return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useEffect";
30428
- };
30429
30818
  const getEffectFn = (analysis, node) => {
30430
30819
  if (!isNodeOfType(node, "CallExpression")) return null;
30431
30820
  const fn = node.arguments?.[0];
@@ -30523,7 +30912,27 @@ const isRefCurrent = (ref) => {
30523
30912
  if (!isNodeOfType(parent.property, "Identifier")) return false;
30524
30913
  return parent.property.name === "current";
30525
30914
  };
30526
- const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isStateSetter(analysis, innerRef));
30915
+ const resolveStateSetterReference = (analysis, ref) => {
30916
+ const visitedReferences = /* @__PURE__ */ new Set();
30917
+ let currentReference = ref;
30918
+ while (currentReference && !visitedReferences.has(currentReference)) {
30919
+ if (isStateSetter(analysis, currentReference)) return currentReference;
30920
+ visitedReferences.add(currentReference);
30921
+ const definitions = currentReference.resolved?.defs ?? [];
30922
+ if (definitions.length !== 1) return null;
30923
+ const definitionNode = definitions[0].node;
30924
+ if (!isNodeOfType(definitionNode, "VariableDeclarator")) return null;
30925
+ if (!isNodeOfType(definitionNode.id, "Identifier")) return null;
30926
+ const declaration = definitionNode.parent;
30927
+ if (!isNodeOfType(declaration, "VariableDeclaration") || declaration.kind !== "const") return null;
30928
+ if (!definitionNode.init) return null;
30929
+ const initializer = stripParenExpression(definitionNode.init);
30930
+ if (!isNodeOfType(initializer, "Identifier")) return null;
30931
+ currentReference = getRef(analysis, initializer);
30932
+ }
30933
+ return null;
30934
+ };
30935
+ const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => resolveStateSetterReference(analysis, innerRef) !== null);
30527
30936
  const isSyncStateSetterCall = (analysis, ref, effectFn) => isStateSetterCall(analysis, ref) && isSynchronous(ref.identifier, effectFn) && !resolvesToAsyncFunction(ref);
30528
30937
  const HANDLER_NAMED_METHOD_PATTERN = /^(on|handle)[A-Z]/;
30529
30938
  const SYNCHRONOUS_CALLBACK_ARGUMENT_INDEX_BY_METHOD = new Map([
@@ -30674,9 +31083,11 @@ const isPropCallbackInvocationRef = (analysis, ref, options = {}) => {
30674
31083
  };
30675
31084
  const isRefCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isRefCurrent(innerRef) || isRef(analysis, innerRef));
30676
31085
  const getUseStateDecl = (analysis, ref) => {
30677
- let node = getUpstreamRefs(analysis, ref).find((upRef) => isHookCallee$1(analysis, upRef.identifier, "useState"))?.identifier;
30678
- while (node && !isNodeOfType(node, "VariableDeclarator")) node = node.parent;
30679
- return node ?? null;
31086
+ const definition = getUpstreamRefs(analysis, ref).find((upstreamReference) => isState(analysis, upstreamReference) || isStateSetter(analysis, upstreamReference))?.resolved?.defs.find((candidateDefinition) => {
31087
+ const definitionNode = candidateDefinition.node;
31088
+ return isNodeOfType(definitionNode, "VariableDeclarator") && isNodeOfType(definitionNode.init, "CallExpression") && isHookCallee$1(analysis, definitionNode.init.callee, "useState");
31089
+ });
31090
+ return definition ? definition.node : null;
30680
31091
  };
30681
31092
  const isCleanupReturnArgument = (analysis, node) => {
30682
31093
  if (isFunctionLike$1(node)) return true;
@@ -31865,6 +32276,7 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
31865
32276
  sourceReferences,
31866
32277
  isDeferred: frame.isDeferred,
31867
32278
  isRenderKnownCopy,
32279
+ isSynchronousRenderValue: !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue,
31868
32280
  matchesStateInitializer: doesMatchStateInitializer,
31869
32281
  resetsSourceState: false
31870
32282
  });
@@ -31880,22 +32292,71 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
31880
32292
  });
31881
32293
  };
31882
32294
  //#endregion
32295
+ //#region src/plugin/rules/state-and-effects/utils/has-deferred-or-external-effect-work.ts
32296
+ const DEFERRED_MEMBER_NAMES = new Set([
32297
+ "catch",
32298
+ "finally",
32299
+ "then"
32300
+ ]);
32301
+ const hasDeferredOrExternalEffectWork = (analysis, effectNode, scopes) => {
32302
+ const effectFunction = getEffectFn(analysis, effectNode);
32303
+ if (!effectFunction) return false;
32304
+ if (containsFetchCall(effectFunction, { stopAtFunctionBoundary: true })) return true;
32305
+ const effectInvokedFunctions = collectEffectInvokedFunctions(effectFunction);
32306
+ let didFindDeferredOrExternalWork = false;
32307
+ walkAst(effectFunction, (child) => {
32308
+ if (didFindDeferredOrExternalWork) return false;
32309
+ if (child !== effectFunction && isFunctionLike$1(child) && !effectInvokedFunctions.has(child)) return false;
32310
+ if (isNodeOfType(child, "AssignmentExpression")) {
32311
+ const assignmentTarget = child.left;
32312
+ if ((isNodeOfType(assignmentTarget, "MemberExpression") ? getStaticPropertyName(assignmentTarget) : null)?.startsWith("on") && isFunctionLike$1(child.right)) {
32313
+ didFindDeferredOrExternalWork = true;
32314
+ return false;
32315
+ }
32316
+ }
32317
+ if (!isNodeOfType(child, "CallExpression")) return;
32318
+ if (isSubscribeOrObserveCallExpression(child)) {
32319
+ didFindDeferredOrExternalWork = true;
32320
+ return false;
32321
+ }
32322
+ const localFunction = resolveExactLocalFunction(child.callee, scopes);
32323
+ if (isFunctionLike$1(localFunction) && localFunction.async) {
32324
+ didFindDeferredOrExternalWork = true;
32325
+ return false;
32326
+ }
32327
+ const callee = child.callee;
32328
+ if (isNodeOfType(callee, "Identifier") && TIMER_AND_SCHEDULER_DIRECT_CALLEE_NAMES.has(callee.name)) {
32329
+ didFindDeferredOrExternalWork = true;
32330
+ return false;
32331
+ }
32332
+ const memberName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
32333
+ if (memberName && DEFERRED_MEMBER_NAMES.has(memberName)) {
32334
+ didFindDeferredOrExternalWork = true;
32335
+ return false;
32336
+ }
32337
+ });
32338
+ return didFindDeferredOrExternalWork;
32339
+ };
32340
+ //#endregion
31883
32341
  //#region src/plugin/rules/state-and-effects/no-adjust-state-on-prop-change.ts
31884
32342
  const noAdjustStateOnPropChange = defineRule({
31885
32343
  id: "no-adjust-state-on-prop-change",
31886
- title: "State synced to a prop inside an effect",
32344
+ title: "State adjusted after a prop changes",
31887
32345
  severity: "warn",
31888
32346
  tags: ["test-noise"],
31889
- 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",
32347
+ 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",
31890
32348
  create: (context) => ({ CallExpression(node) {
31891
- if (!isUseEffect(node)) return;
32349
+ if (!isReactHookCall(node, "useEffect", context.scopes)) return;
31892
32350
  const analysis = getProgramAnalysis(node);
31893
32351
  if (!analysis) return;
31894
32352
  const dependencyReferences = getEffectDepsRefs(analysis, node);
31895
32353
  if (!dependencyReferences) return;
31896
32354
  if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
31897
- for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
31898
- if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
32355
+ const facts = collectEffectStateWriteFacts(analysis, context, node, context.filename);
32356
+ if (hasCleanup(analysis, node) || hasDeferredOrExternalEffectWork(analysis, node, context.scopes) || facts.some((fact) => fact.isDeferred)) return;
32357
+ for (const fact of facts) {
32358
+ if (!fact.isSynchronousRenderValue || fact.resetsSourceState) continue;
32359
+ if (fact.sourceReferences.flatMap((reference) => getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) continue;
31899
32360
  context.report({
31900
32361
  node: fact.callExpression,
31901
32362
  message: "This effect adjusts state after a prop changes, so users briefly see the stale value."
@@ -33908,7 +34369,7 @@ const declarationBodyContainsHookCall = (symbol) => {
33908
34369
  walkAst(componentFunction, (descendant) => {
33909
34370
  if (didFindHookCall) return false;
33910
34371
  if (!isNodeOfType(descendant, "CallExpression")) return;
33911
- const calleeName = getCalleeName$2(descendant);
34372
+ const calleeName = getCalleeName$1(descendant);
33912
34373
  if (calleeName && isReactHookName(calleeName)) {
33913
34374
  didFindHookCall = true;
33914
34375
  return false;
@@ -33933,7 +34394,7 @@ const isReturnedFromUseCallbackAdapter = (callNode) => {
33933
34394
  if (isNodeOfType(parent, "ArrowFunctionExpression")) {
33934
34395
  if (parent.body !== current) return false;
33935
34396
  const grandparent = parent.parent;
33936
- return isNodeOfType(grandparent, "CallExpression") && getCalleeName$2(grandparent) === "useCallback" && grandparent.arguments.some((argumentNode) => argumentNode === parent);
34397
+ return isNodeOfType(grandparent, "CallExpression") && getCalleeName$1(grandparent) === "useCallback" && grandparent.arguments.some((argumentNode) => argumentNode === parent);
33937
34398
  }
33938
34399
  if (!isNodeOfType(parent, "ConditionalExpression") && !isNodeOfType(parent, "LogicalExpression")) return false;
33939
34400
  current = parent;
@@ -34615,7 +35076,7 @@ const noChainStateUpdates = defineRule({
34615
35076
  if (!callExpr) continue;
34616
35077
  if (!isReachableFromStateTrigger(callExpr)) continue;
34617
35078
  if (!readsPostMountValueThroughLocals(callExpr, effectFn, { ignoreBareRefCurrent: true })) continue;
34618
- const declarator = getUseStateDeclarator(ref);
35079
+ const declarator = getUseStateDeclarator(resolveStateSetterReference(analysis, ref) ?? ref);
34619
35080
  if (declarator) domSyncedStateDeclarators.add(declarator);
34620
35081
  }
34621
35082
  for (const ref of effectFnRefs) {
@@ -34624,7 +35085,7 @@ const noChainStateUpdates = defineRule({
34624
35085
  if (!callExpr) continue;
34625
35086
  if (!isReachableFromStateTrigger(callExpr)) continue;
34626
35087
  if (getArgsUpstreamRefs(analysis, ref).some((argRef) => isState(analysis, argRef))) continue;
34627
- const setterDeclarator = getUseStateDeclarator(ref);
35088
+ const setterDeclarator = getUseStateDeclarator(resolveStateSetterReference(analysis, ref) ?? ref);
34628
35089
  if (setterDeclarator && domSyncedStateDeclarators.has(setterDeclarator)) continue;
34629
35090
  const isSelfTargeting = setterDeclarator !== null && stateDepDeclarators.has(setterDeclarator);
34630
35091
  const setterArguments = isNodeOfType(callExpr, "CallExpression") ? callExpr.arguments ?? [] : [];
@@ -36465,7 +36926,7 @@ const noDerivedState = defineRule({
36465
36926
  for (const fact of collectRenderStateWriteFacts(analysis, componentBody, context.filename)) reportStateWrite(fact.callExpression, fact.stateDeclarator);
36466
36927
  } }).visitors,
36467
36928
  CallExpression(node) {
36468
- if (!isUseEffect(node)) return;
36929
+ if (!isReactHookCall(node, "useEffect", context.scopes)) return;
36469
36930
  const analysis = getProgramAnalysis(node);
36470
36931
  if (!analysis) return;
36471
36932
  for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
@@ -36485,7 +36946,7 @@ const noDerivedStateEffect = defineRule({
36485
36946
  tags: ["test-noise"],
36486
36947
  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",
36487
36948
  create: (context) => ({ CallExpression(node) {
36488
- if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
36949
+ if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes)) return;
36489
36950
  const analysis = getProgramAnalysis(node);
36490
36951
  if (!analysis) return;
36491
36952
  if (!collectEffectStateWriteFacts(analysis, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
@@ -36608,7 +37069,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
36608
37069
  if (isFunctionLike$1(cursor)) {
36609
37070
  const parent = cursor.parent ?? null;
36610
37071
  if (parent && isNodeOfType(parent, "CallExpression")) {
36611
- const calleeName = getCalleeName$2(parent);
37072
+ const calleeName = getCalleeName$1(parent);
36612
37073
  if (calleeName !== null && EFFECT_HOOK_NAME_PATTERN.test(calleeName) && (parent.arguments ?? []).some((argument) => argument === cursor)) return cursor;
36613
37074
  }
36614
37075
  }
@@ -36679,7 +37140,7 @@ const isNonHandlerHookCallback = (functionNode) => {
36679
37140
  const parent = functionNode.parent ?? null;
36680
37141
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
36681
37142
  if (!(parent.arguments ?? []).some((argument) => argument === functionNode)) return false;
36682
- const calleeName = getCalleeName$2(parent);
37143
+ const calleeName = getCalleeName$1(parent);
36683
37144
  return calleeName !== null && isReactHookName(calleeName) && calleeName !== "useCallback";
36684
37145
  };
36685
37146
  const isHandlerShapedReseed = (setterCall, componentFunction) => {
@@ -36761,7 +37222,7 @@ const noDerivedUseState = defineRule({
36761
37222
  return {
36762
37223
  ...propStackTracker.visitors,
36763
37224
  CallExpression(node) {
36764
- if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
37225
+ if (!isReactHookCall(node, "useState", context.scopes) || !node.arguments?.length) return;
36765
37226
  const seed = unwrapInitializerSeed(node.arguments[0]);
36766
37227
  const reportStalePropCopy = (propName) => {
36767
37228
  if (isIntentionalSnapshotState(node)) return;
@@ -37437,7 +37898,7 @@ const noDirectMutationState = defineRule({
37437
37898
  const isSetterIdentifier = (name) => SETTER_PATTERN.test(name);
37438
37899
  //#endregion
37439
37900
  //#region src/plugin/rules/state-and-effects/utils/collect-use-state-bindings.ts
37440
- const collectUseStateBindings = (componentBody) => {
37901
+ const collectUseStateBindings = (componentBody, scopes) => {
37441
37902
  const bindings = [];
37442
37903
  if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
37443
37904
  for (const statement of componentBody.body ?? []) {
@@ -37450,7 +37911,7 @@ const collectUseStateBindings = (componentBody) => {
37450
37911
  const setterElement = elements[1];
37451
37912
  if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
37452
37913
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
37453
- if (!isHookCall$2(declarator.init, "useState")) continue;
37914
+ if (!isReactHookCall(declarator.init, "useState", scopes)) continue;
37454
37915
  bindings.push({
37455
37916
  valueName: valueElement.name,
37456
37917
  setterName: setterElement.name,
@@ -37617,7 +38078,7 @@ const noDirectStateMutation = defineRule({
37617
38078
  create: (context) => {
37618
38079
  const checkComponent = (componentBody) => {
37619
38080
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
37620
- const bindings = collectUseStateBindings(componentBody);
38081
+ const bindings = collectUseStateBindings(componentBody, context.scopes);
37621
38082
  if (bindings.length === 0) return;
37622
38083
  const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
37623
38084
  const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
@@ -38177,25 +38638,31 @@ const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSub
38177
38638
  };
38178
38639
  //#endregion
38179
38640
  //#region src/plugin/rules/state-and-effects/no-effect-chain.ts
38180
- const findTopLevelEffectCalls = (componentBody) => {
38641
+ const findTopLevelEffectCalls = (componentBody, scopes) => {
38181
38642
  const effectCalls = [];
38182
38643
  if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
38183
38644
  for (const statement of componentBody.body ?? []) {
38184
38645
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
38185
38646
  const expression = unwrapDiscardedExpression(statement);
38186
38647
  if (!isNodeOfType(expression, "CallExpression")) continue;
38187
- if (!isHookCall$2(expression, EFFECT_HOOK_NAMES$1)) continue;
38648
+ if (!isReactHookCall(expression, EFFECT_HOOK_NAMES$1, scopes)) continue;
38188
38649
  effectCalls.push(expression);
38189
38650
  }
38190
38651
  return effectCalls;
38191
38652
  };
38192
- const collectDepIdentifierNames = (effectNode) => {
38193
- const depNames = /* @__PURE__ */ new Set();
38194
- if (!isNodeOfType(effectNode, "CallExpression")) return depNames;
38653
+ const collectDependencyStateSymbolIds = (effectNode, stateSymbolIds, scopes) => {
38654
+ const dependencyStateSymbolIds = /* @__PURE__ */ new Set();
38655
+ if (!isNodeOfType(effectNode, "CallExpression")) return dependencyStateSymbolIds;
38195
38656
  const depsNode = effectNode.arguments?.[1];
38196
- if (!isNodeOfType(depsNode, "ArrayExpression")) return depNames;
38197
- for (const element of depsNode.elements ?? []) if (isNodeOfType(element, "Identifier")) depNames.add(element.name);
38198
- return depNames;
38657
+ if (!isNodeOfType(depsNode, "ArrayExpression")) return dependencyStateSymbolIds;
38658
+ for (const element of depsNode.elements ?? []) {
38659
+ if (!element || isNodeOfType(element, "SpreadElement")) continue;
38660
+ const rootIdentifier = getRootIdentifier$1(element);
38661
+ if (!isNodeOfType(rootIdentifier, "Identifier")) continue;
38662
+ const symbol = resolveConstIdentifierAlias(rootIdentifier, scopes, true);
38663
+ if (symbol && stateSymbolIds.has(symbol.id)) dependencyStateSymbolIds.add(symbol.id);
38664
+ }
38665
+ return dependencyStateSymbolIds;
38199
38666
  };
38200
38667
  const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
38201
38668
  const analysisFunctions = new Set([effectCallback]);
@@ -38301,12 +38768,13 @@ const readStaticSetterValue = (setterCall, scopes) => {
38301
38768
  if (updater) return readStaticUpdaterReturnValue(updater, scopes);
38302
38769
  return readStaticEffectValue(argument, scopes, null, null);
38303
38770
  };
38304
- const collectStateWritesInEffect = (analysisFunctions, setterToStateName, scopes) => {
38771
+ const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName, scopes) => {
38305
38772
  const stateWrites = /* @__PURE__ */ new Map();
38306
38773
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
38307
38774
  if (!isNodeOfType(child, "CallExpression")) return;
38308
38775
  if (!isNodeOfType(child.callee, "Identifier")) return;
38309
- const stateName = setterToStateName.get(child.callee.name);
38776
+ const setterSymbol = resolveConstIdentifierAlias(child.callee, scopes, true);
38777
+ const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
38310
38778
  if (!stateName) return;
38311
38779
  const writeInfo = stateWrites.get(stateName) ?? {
38312
38780
  values: /* @__PURE__ */ new Set(),
@@ -38392,11 +38860,12 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
38392
38860
  "keys",
38393
38861
  "values"
38394
38862
  ]);
38395
- const isFunctionShapedReturn = (returnedValue, setterToStateName, isExplicitReturnStatement) => {
38863
+ const isFunctionShapedReturn = (returnedValue, setterToStateName, setterSymbolIdToStateName, scopes, isExplicitReturnStatement) => {
38396
38864
  if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
38397
38865
  if (isNodeOfType(returnedValue, "CallExpression")) {
38398
38866
  if (isNodeOfType(returnedValue.callee, "Identifier")) {
38399
- if (setterToStateName.has(returnedValue.callee.name)) return false;
38867
+ const setterSymbol = resolveConstIdentifierAlias(returnedValue.callee, scopes, true);
38868
+ if (setterToStateName.has(returnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
38400
38869
  if (isSetterIdentifier(returnedValue.callee.name)) return true;
38401
38870
  }
38402
38871
  return isCleanupReturn(returnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
@@ -38420,7 +38889,7 @@ const collectStorageHookSetterNames = (componentBody) => {
38420
38889
  for (const declarator of statement.declarations ?? []) {
38421
38890
  if (!isNodeOfType(declarator.id, "ArrayPattern")) continue;
38422
38891
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
38423
- const calleeName = getCalleeName$2(declarator.init);
38892
+ const calleeName = getCalleeName$1(declarator.init);
38424
38893
  if (!calleeName || !STORAGE_HOOK_PATTERN.test(calleeName)) continue;
38425
38894
  for (const element of declarator.id.elements ?? []) if (isNodeOfType(element, "Identifier") && isSetterIdentifier(element.name)) setterNames.add(element.name);
38426
38895
  }
@@ -38563,11 +39032,11 @@ const isExternalSyncNode = (node) => {
38563
39032
  const receiverRootName = getRootIdentifierName(node.callee.object);
38564
39033
  return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
38565
39034
  };
38566
- const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, scopes, allowCommittedDomSync) => {
39035
+ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, scopes, allowCommittedDomSync) => {
38567
39036
  if (!isFunctionLike$1(effectCallback)) return false;
38568
39037
  if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
38569
- if (isFunctionShapedReturn(effectCallback.body, setterToStateName, false)) return true;
38570
- } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, true)) return true;
39038
+ if (isFunctionShapedReturn(effectCallback.body, setterToStateName, setterSymbolIdToStateName, scopes, false)) return true;
39039
+ } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, setterSymbolIdToStateName, scopes, true)) return true;
38571
39040
  let didFindExternalCall = false;
38572
39041
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
38573
39042
  if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
@@ -38583,10 +39052,11 @@ const noEffectChain = defineRule({
38583
39052
  create: (context) => {
38584
39053
  const checkComponent = (componentBody) => {
38585
39054
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
38586
- const useStateBindings = collectUseStateBindings(componentBody);
39055
+ const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
38587
39056
  if (useStateBindings.length === 0) return;
38588
39057
  const setterToStateName = /* @__PURE__ */ new Map();
38589
39058
  const stateSymbolIds = /* @__PURE__ */ new Map();
39059
+ const setterSymbolIdToStateName = /* @__PURE__ */ new Map();
38590
39060
  for (const binding of useStateBindings) {
38591
39061
  setterToStateName.set(binding.setterName, binding.valueName);
38592
39062
  if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
@@ -38595,21 +39065,27 @@ const noEffectChain = defineRule({
38595
39065
  const stateSymbol = context.scopes.symbolFor(stateIdentifier);
38596
39066
  if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
38597
39067
  }
39068
+ const setterIdentifier = binding.declarator.id.elements[1];
39069
+ if (isNodeOfType(setterIdentifier, "Identifier")) {
39070
+ const setterSymbol = context.scopes.symbolFor(setterIdentifier);
39071
+ if (setterSymbol) setterSymbolIdToStateName.set(setterSymbol.id, binding.valueName);
39072
+ }
38598
39073
  }
38599
39074
  const storageSetterNames = collectStorageHookSetterNames(componentBody);
39075
+ const stateSymbolIdSet = new Set(stateSymbolIds.values());
38600
39076
  const effectInfos = [];
38601
- for (const effectCall of findTopLevelEffectCalls(componentBody)) {
39077
+ for (const effectCall of findTopLevelEffectCalls(componentBody, context.scopes)) {
38602
39078
  const callback = getEffectCallback(effectCall, context.scopes);
38603
39079
  if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
38604
39080
  const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
38605
- const stateWrites = collectStateWritesInEffect(analysisFunctions, setterToStateName, context.scopes);
39081
+ const stateWrites = collectStateWritesInEffect(analysisFunctions, setterSymbolIdToStateName, context.scopes);
38606
39082
  const writtenStateNames = new Set(stateWrites.keys());
38607
39083
  effectInfos.push({
38608
39084
  node: effectCall,
38609
- depNames: collectDepIdentifierNames(effectCall),
39085
+ dependencyStateSymbolIds: collectDependencyStateSymbolIds(effectCall, stateSymbolIdSet, context.scopes),
38610
39086
  stateWrites,
38611
39087
  analysisFunctions,
38612
- isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
39088
+ isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
38613
39089
  });
38614
39090
  }
38615
39091
  if (effectInfos.length < 2) return;
@@ -38620,10 +39096,11 @@ const noEffectChain = defineRule({
38620
39096
  for (const readerEffect of effectInfos) {
38621
39097
  if (readerEffect === writerEffect) continue;
38622
39098
  if (readerEffect.isExternalSync) continue;
38623
- if (readerEffect.depNames.size === 0) continue;
39099
+ if (readerEffect.dependencyStateSymbolIds.size === 0) continue;
38624
39100
  let chainedStateName = null;
38625
39101
  for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
38626
- if (!readerEffect.depNames.has(writtenName)) continue;
39102
+ const writtenStateSymbolId = stateSymbolIds.get(writtenName);
39103
+ if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
38627
39104
  if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
38628
39105
  chainedStateName = writtenName;
38629
39106
  break;
@@ -38900,7 +39377,7 @@ const noEffectEventHandler = defineRule({
38900
39377
  return {
38901
39378
  ...propStackTracker.visitors,
38902
39379
  CallExpression(node) {
38903
- if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
39380
+ if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes) || (node.arguments?.length ?? 0) < 2) return;
38904
39381
  const callback = getEffectCallback(node);
38905
39382
  if (!callback) return;
38906
39383
  const analysis = getProgramAnalysis(node);
@@ -39020,14 +39497,14 @@ const noEffectEventInDeps = defineRule({
39020
39497
  if (!isNodeOfType(declaratorNode.id, "Identifier")) return;
39021
39498
  const initializer = declaratorNode.init;
39022
39499
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return;
39023
- if (!isHookCall$2(initializer, "useEffectEvent")) return;
39500
+ if (!isReactHookCall(initializer, "useEffectEvent", context.scopes)) return;
39024
39501
  if (isNonReactEffectEventCallee(initializer.callee, declaratorNode, context.scopes)) return;
39025
39502
  componentBindings.addBindingToCurrentFrame(declaratorNode.id.name);
39026
39503
  } });
39027
39504
  return {
39028
39505
  ...componentBindings.visitors,
39029
39506
  CallExpression(node) {
39030
- if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
39507
+ if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes) || node.arguments.length < 2) return;
39031
39508
  if (!componentBindings.isInsideComponent()) return;
39032
39509
  const depsNode = node.arguments[1];
39033
39510
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -39084,7 +39561,7 @@ const noEffectWithFreshDeps = defineRule({
39084
39561
  node: finding.reportNode,
39085
39562
  message: `A dependency inside this custom Hook changes every render because \`${finding.bindingName}\` is a new ${finding.kind} built fresh each time.`
39086
39563
  });
39087
- if (!isHookCall$2(node, HOOKS_WITH_DEPS)) return;
39564
+ if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes)) return;
39088
39565
  const args = node.arguments ?? [];
39089
39566
  if (args.length < 2) return;
39090
39567
  const depsNode = args[1];
@@ -39345,7 +39822,7 @@ const noEventHandler = defineRule({
39345
39822
  severity: "warn",
39346
39823
  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",
39347
39824
  create: (context) => ({ CallExpression(node) {
39348
- if (!isUseEffect(node)) return;
39825
+ if (!isReactHookCall(node, "useEffect", context.scopes)) return;
39349
39826
  const analysis = getProgramAnalysis(node);
39350
39827
  if (!analysis || hasCleanup(analysis, node)) return;
39351
39828
  const frames = collectBoundedEffectExecutionFrames(analysis, node);
@@ -39807,25 +40284,25 @@ const addDeclarationBindings = (statement, scope) => {
39807
40284
  }
39808
40285
  if (isNodeOfType(statement, "FunctionDeclaration") && statement.id) addPatternBindings(statement.id, scope);
39809
40286
  };
39810
- const collectRenderReachableNamesFromStatements = (statements, names, scope, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
40287
+ const collectRenderReachableNamesFromStatements = (statements, names, scope, scopes, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
39811
40288
  let hasReturn = false;
39812
- for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope, eventHandlerReferenceNames)) hasReturn = true;
40289
+ for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope, scopes, eventHandlerReferenceNames)) hasReturn = true;
39813
40290
  else addDeclarationBindings(statement, scope);
39814
40291
  return hasReturn;
39815
40292
  };
39816
- const collectRenderReachableNamesFromStatement = (statement, names, scope, eventHandlerReferenceNames) => {
40293
+ const collectRenderReachableNamesFromStatement = (statement, names, scope, scopes, eventHandlerReferenceNames) => {
39817
40294
  if (isNodeOfType(statement, "ReturnStatement")) {
39818
40295
  if (statement.argument) addNames(names, collectScopedReferenceNames(statement.argument, scope, eventHandlerReferenceNames));
39819
40296
  return true;
39820
40297
  }
39821
- 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)) {
40298
+ 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)) {
39822
40299
  for (const argument of statement.expression.arguments ?? []) addNames(names, collectScopedReferenceNames(argument, scope, eventHandlerReferenceNames));
39823
40300
  return false;
39824
40301
  }
39825
- if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope), eventHandlerReferenceNames);
40302
+ if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope), scopes, eventHandlerReferenceNames);
39826
40303
  if (isNodeOfType(statement, "IfStatement")) {
39827
- const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope, eventHandlerReferenceNames);
39828
- const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope, eventHandlerReferenceNames) : false;
40304
+ const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope, scopes, eventHandlerReferenceNames);
40305
+ const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope, scopes, eventHandlerReferenceNames) : false;
39829
40306
  if (consequentHasReturn || alternateHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
39830
40307
  return consequentHasReturn || alternateHasReturn;
39831
40308
  }
@@ -39833,7 +40310,7 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, event
39833
40310
  let hasReturn = false;
39834
40311
  for (const switchCase of statement.cases ?? []) {
39835
40312
  const caseScope = createBlockBindingScope(scope);
39836
- if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope, eventHandlerReferenceNames)) continue;
40313
+ if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope, scopes, eventHandlerReferenceNames)) continue;
39837
40314
  hasReturn = true;
39838
40315
  if (switchCase.test) addNames(names, collectScopedReferenceNames(switchCase.test, scope, eventHandlerReferenceNames));
39839
40316
  }
@@ -39841,25 +40318,25 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, event
39841
40318
  return hasReturn;
39842
40319
  }
39843
40320
  if (isNodeOfType(statement, "TryStatement")) {
39844
- const blockHasReturn = collectRenderReachableNamesFromStatement(statement.block, names, scope, eventHandlerReferenceNames);
39845
- const handlerHasReturn = statement.handler ? collectRenderReachableNamesFromStatement(statement.handler, names, scope, eventHandlerReferenceNames) : false;
39846
- const finalizerHasReturn = statement.finalizer ? collectRenderReachableNamesFromStatement(statement.finalizer, names, scope, eventHandlerReferenceNames) : false;
40321
+ const blockHasReturn = collectRenderReachableNamesFromStatement(statement.block, names, scope, scopes, eventHandlerReferenceNames);
40322
+ const handlerHasReturn = statement.handler ? collectRenderReachableNamesFromStatement(statement.handler, names, scope, scopes, eventHandlerReferenceNames) : false;
40323
+ const finalizerHasReturn = statement.finalizer ? collectRenderReachableNamesFromStatement(statement.finalizer, names, scope, scopes, eventHandlerReferenceNames) : false;
39847
40324
  return blockHasReturn || handlerHasReturn || finalizerHasReturn;
39848
40325
  }
39849
40326
  if (isNodeOfType(statement, "CatchClause")) {
39850
40327
  const catchScope = createBlockBindingScope(scope);
39851
40328
  addPatternBindings(statement.param, catchScope);
39852
- return collectRenderReachableNamesFromStatement(statement.body, names, catchScope, eventHandlerReferenceNames);
40329
+ return collectRenderReachableNamesFromStatement(statement.body, names, catchScope, scopes, eventHandlerReferenceNames);
39853
40330
  }
39854
40331
  if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
39855
- const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
40332
+ const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, scopes, eventHandlerReferenceNames);
39856
40333
  if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
39857
40334
  return bodyHasReturn;
39858
40335
  }
39859
40336
  if (isNodeOfType(statement, "ForStatement")) {
39860
40337
  const loopScope = createBlockBindingScope(scope);
39861
40338
  if (statement.init) addDeclarationBindings(statement.init, loopScope);
39862
- if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
40339
+ if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, scopes, eventHandlerReferenceNames)) return false;
39863
40340
  if (statement.init) addNames(names, collectScopedReferenceNames(statement.init, loopScope, eventHandlerReferenceNames));
39864
40341
  if (statement.test) addNames(names, collectScopedReferenceNames(statement.test, loopScope, eventHandlerReferenceNames));
39865
40342
  if (statement.update) addNames(names, collectScopedReferenceNames(statement.update, loopScope, eventHandlerReferenceNames));
@@ -39869,22 +40346,22 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, event
39869
40346
  const rightNames = collectScopedReferenceNames(statement.right, scope, eventHandlerReferenceNames);
39870
40347
  const loopScope = createBlockBindingScope(scope);
39871
40348
  if (isNodeOfType(statement.left, "VariableDeclaration")) addDeclarationBindings(statement.left, loopScope);
39872
- if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
40349
+ if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, scopes, eventHandlerReferenceNames)) return false;
39873
40350
  addNames(names, rightNames);
39874
40351
  return true;
39875
40352
  }
39876
- if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
40353
+ if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope, scopes, eventHandlerReferenceNames);
39877
40354
  if (isNodeOfType(statement, "WithStatement")) {
39878
- const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
40355
+ const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, scopes, eventHandlerReferenceNames);
39879
40356
  if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.object, scope, eventHandlerReferenceNames));
39880
40357
  return bodyHasReturn;
39881
40358
  }
39882
40359
  return false;
39883
40360
  };
39884
- const collectRenderReachableNames = (componentBody, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
40361
+ const collectRenderReachableNames = (componentBody, scopes, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
39885
40362
  const names = /* @__PURE__ */ new Set();
39886
40363
  if (!isNodeOfType(componentBody, "BlockStatement")) return names;
39887
- collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(), eventHandlerReferenceNames);
40364
+ collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(), scopes, eventHandlerReferenceNames);
39888
40365
  return names;
39889
40366
  };
39890
40367
  //#endregion
@@ -39907,42 +40384,36 @@ const expandTransitiveDependencies = (seedNames, dependencyGraph) => {
39907
40384
  };
39908
40385
  //#endregion
39909
40386
  //#region src/plugin/rules/state-and-effects/utils/collect-function-like-local-names.ts
39910
- const isUseCallbackCall = (node) => isNodeOfType(node, "CallExpression") && getCalleeName(node.callee) === "useCallback";
39911
- const getCalleeName = (node) => {
39912
- if (isNodeOfType(node, "Identifier")) return node.name;
39913
- if (isNodeOfType(node, "MemberExpression")) return getStaticMemberPropertyName(node);
39914
- return null;
39915
- };
39916
- const isFunctionLikeReference = (node, functionLikeLocalNames, scope) => {
39917
- if (isInlineFunctionExpression(node) || isUseCallbackCall(node)) return true;
40387
+ const isFunctionLikeReference = (node, functionLikeLocalNames, scope, scopes) => {
40388
+ if (isInlineFunctionExpression(node) || isReactHookCall(node, "useCallback", scopes)) return true;
39918
40389
  if (isNodeOfType(node, "Identifier")) return functionLikeLocalNames.has(resolveBindingName(scope, node.name));
39919
40390
  const memberReferenceName = getStaticMemberReferenceName(node, (name) => resolveBindingName(scope, name));
39920
40391
  return Boolean(memberReferenceName && functionLikeLocalNames.has(memberReferenceName));
39921
40392
  };
39922
- const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope) => {
40393
+ const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope, scopes) => {
39923
40394
  if (!isNodeOfType(node, "ObjectExpression")) return;
39924
40395
  for (const property of node.properties ?? []) {
39925
40396
  if (!isNodeOfType(property, "Property")) continue;
39926
40397
  const propertyName = getStaticPropertyKeyName(property, { stringifyNonStringLiterals: true });
39927
40398
  if (!propertyName) continue;
39928
- if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope)) continue;
40399
+ if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope, scopes)) continue;
39929
40400
  functionLikeLocalNames.add(`${objectBindingName}.${propertyName}`);
39930
40401
  }
39931
40402
  };
39932
- const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope) => {
40403
+ const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope, scopes) => {
39933
40404
  if (!isNodeOfType(statement, "VariableDeclaration")) return;
39934
40405
  const declarationScope = getVariableDeclarationScope(statement, scope);
39935
40406
  for (const declarator of statement.declarations ?? []) {
39936
40407
  const declaredBindingNames = addPatternBindings(declarator.id, declarationScope);
39937
40408
  if (!declarator.init) continue;
39938
- const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope);
40409
+ const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope, scopes);
39939
40410
  for (const declaredBindingName of declaredBindingNames) {
39940
40411
  if (isFunctionReference) functionLikeLocalNames.add(declaredBindingName);
39941
- addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope);
40412
+ addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope, scopes);
39942
40413
  }
39943
40414
  }
39944
40415
  };
39945
- const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope) => {
40416
+ const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope, scopes) => {
39946
40417
  if (isNodeOfType(statement, "FunctionDeclaration")) {
39947
40418
  if (statement.id) {
39948
40419
  const declaredBindingNames = addPatternBindings(statement.id, scope);
@@ -39951,61 +40422,61 @@ const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope)
39951
40422
  return;
39952
40423
  }
39953
40424
  if (isNodeOfType(statement, "VariableDeclaration")) {
39954
- addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope);
40425
+ addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope, scopes);
39955
40426
  return;
39956
40427
  }
39957
40428
  if (isNodeOfType(statement, "BlockStatement")) {
39958
- collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope));
40429
+ collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope), scopes);
39959
40430
  return;
39960
40431
  }
39961
40432
  if (isNodeOfType(statement, "IfStatement")) {
39962
- collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope);
39963
- if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope);
40433
+ collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope, scopes);
40434
+ if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope, scopes);
39964
40435
  return;
39965
40436
  }
39966
40437
  if (isNodeOfType(statement, "SwitchStatement")) {
39967
- for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope));
40438
+ for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope), scopes);
39968
40439
  return;
39969
40440
  }
39970
40441
  if (isNodeOfType(statement, "TryStatement")) {
39971
- collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope);
40442
+ collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope, scopes);
39972
40443
  if (statement.handler) {
39973
40444
  const catchScope = createBlockBindingScope(scope);
39974
40445
  addPatternBindings(statement.handler.param, catchScope);
39975
- collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope);
40446
+ collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope, scopes);
39976
40447
  }
39977
- if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope);
40448
+ if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope, scopes);
39978
40449
  return;
39979
40450
  }
39980
40451
  if (isNodeOfType(statement, "ForStatement")) {
39981
40452
  const loopScope = createBlockBindingScope(scope);
39982
- if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope);
39983
- collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
40453
+ if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope, scopes);
40454
+ collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope, scopes);
39984
40455
  return;
39985
40456
  }
39986
40457
  if (isNodeOfType(statement, "ForInStatement") || isNodeOfType(statement, "ForOfStatement")) {
39987
40458
  const loopScope = createBlockBindingScope(scope);
39988
- if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope);
40459
+ if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope, scopes);
39989
40460
  else addPatternBindings(statement.left, loopScope);
39990
- collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
40461
+ collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope, scopes);
39991
40462
  return;
39992
40463
  }
39993
40464
  if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
39994
- collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
40465
+ collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope, scopes);
39995
40466
  return;
39996
40467
  }
39997
- if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
40468
+ if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope, scopes);
39998
40469
  };
39999
- const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope) => {
40000
- for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope);
40470
+ const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope, scopes) => {
40471
+ for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope, scopes);
40001
40472
  };
40002
- const collectFunctionLikeLocalNames = (componentBody) => {
40473
+ const collectFunctionLikeLocalNames = (componentBody, scopes) => {
40003
40474
  const functionLikeLocalNames = /* @__PURE__ */ new Set();
40004
40475
  if (!isNodeOfType(componentBody, "BlockStatement")) return functionLikeLocalNames;
40005
40476
  let previousSize = -1;
40006
40477
  while (previousSize !== functionLikeLocalNames.size) {
40007
40478
  previousSize = functionLikeLocalNames.size;
40008
- collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope());
40479
+ collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope(), scopes);
40009
40480
  }
40010
40481
  return functionLikeLocalNames;
40011
40482
  };
@@ -40045,17 +40516,17 @@ const noEventTriggerState = defineRule({
40045
40516
  create: (context) => {
40046
40517
  const checkComponent = (componentBody) => {
40047
40518
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
40048
- const useStateBindings = collectUseStateBindings(componentBody);
40519
+ const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
40049
40520
  if (useStateBindings.length === 0) return;
40050
40521
  const analysis = getProgramAnalysis(componentBody);
40051
40522
  if (!analysis) return;
40052
40523
  const localStateNames = new Set(useStateBindings.map((binding) => binding.valueName));
40053
- const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
40524
+ const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
40054
40525
  const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
40055
- const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody, eventHandlerReferenceNames), dependencyGraph);
40526
+ const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames), dependencyGraph);
40056
40527
  walkAst(componentBody, (effectCall) => {
40057
40528
  if (!isNodeOfType(effectCall, "CallExpression")) return;
40058
- if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) return;
40529
+ if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) return;
40059
40530
  if ((effectCall.arguments?.length ?? 0) < 2) return;
40060
40531
  const depsNode = effectCall.arguments[1];
40061
40532
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -40120,10 +40591,17 @@ const isShadowedByLocalBinding = (identifier) => {
40120
40591
  };
40121
40592
  const isRealFetchCall = (node) => {
40122
40593
  if (!isNodeOfType(node, "CallExpression")) return false;
40123
- if (isNodeOfType(node.callee, "Identifier") && FETCH_CALLEE_NAMES.has(node.callee.name)) return !isShadowedByLocalBinding(node.callee);
40124
- return isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && FETCH_MEMBER_OBJECTS.has(node.callee.object.name) && !isShadowedByLocalBinding(node.callee.object);
40594
+ const callee = stripParenExpression(node.callee);
40595
+ if (isNodeOfType(callee, "Identifier") && FETCH_CALLEE_NAMES.has(callee.name)) return !isShadowedByLocalBinding(callee);
40596
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
40597
+ const receiver = stripParenExpression(callee.object);
40598
+ return isNodeOfType(receiver, "Identifier") && FETCH_MEMBER_OBJECTS.has(receiver.name) && !isShadowedByLocalBinding(receiver);
40599
+ };
40600
+ const isXmlHttpRequestConstruction = (node) => {
40601
+ if (!isNodeOfType(node, "NewExpression")) return false;
40602
+ const callee = stripParenExpression(node.callee);
40603
+ return isNodeOfType(callee, "Identifier") && callee.name === "XMLHttpRequest";
40125
40604
  };
40126
- const isXmlHttpRequestConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "XMLHttpRequest";
40127
40605
  const isNetworkRequest = (node) => isRealFetchCall(node) || isXmlHttpRequestConstruction(node);
40128
40606
  const resolveLocalFunction = (expression, context) => {
40129
40607
  if (!expression) return null;
@@ -40439,6 +40917,17 @@ const DOM_MEASUREMENT_NAMES = new Set([
40439
40917
  "scrollHeight"
40440
40918
  ]);
40441
40919
  const MEASUREMENT_HELPER_CALLEE_PATTERN = /^(?:get|measure|read)\w*(?:Width|Height|Rect|Rects|Size|Bounds|Position)$/;
40920
+ const IMPERATIVE_DOM_MUTATION_NAMES = new Set([
40921
+ "blur",
40922
+ "focus",
40923
+ "restoreSelection",
40924
+ "scroll",
40925
+ "scrollBy",
40926
+ "scrollIntoView",
40927
+ "scrollTo",
40928
+ "setRangeText",
40929
+ "setSelectionRange"
40930
+ ]);
40442
40931
  const subtreeReadsDomMeasurement = (root) => {
40443
40932
  if (!root) return false;
40444
40933
  let found = false;
@@ -40457,29 +40946,66 @@ const subtreeReadsDomMeasurement = (root) => {
40457
40946
  });
40458
40947
  return found;
40459
40948
  };
40460
- const collectMeasuringFunctionNames = (program) => {
40949
+ const collectFunctionNamesMatchingBody = (program, matchesBody) => {
40461
40950
  const names = /* @__PURE__ */ new Set();
40462
40951
  walkAst(program, (child) => {
40463
40952
  if (isNodeOfType(child, "FunctionDeclaration")) {
40464
- if (child.id && isNodeOfType(child.id, "Identifier") && subtreeReadsDomMeasurement(child.body)) names.add(child.id.name);
40953
+ if (child.id && isNodeOfType(child.id, "Identifier") && matchesBody(child.body)) names.add(child.id.name);
40465
40954
  return;
40466
40955
  }
40467
40956
  if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier")) return;
40468
40957
  let functionValue = child.init;
40469
40958
  if (functionValue && isNodeOfType(functionValue, "CallExpression") && isNodeOfType(functionValue.callee, "Identifier") && /^use[A-Z]/.test(functionValue.callee.name)) functionValue = functionValue.arguments?.[0];
40470
- if (functionValue && isFunctionLike$1(functionValue) && subtreeReadsDomMeasurement(functionValue.body)) names.add(child.id.name);
40959
+ if (functionValue && isFunctionLike$1(functionValue) && matchesBody(functionValue.body)) names.add(child.id.name);
40471
40960
  });
40472
40961
  return names;
40473
40962
  };
40474
- const callsAnyName = (root, names) => {
40475
- if (!root || names.size === 0) return false;
40963
+ const collectMeasuringFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeReadsDomMeasurement);
40964
+ const subtreeMutatesDomImperatively = (root) => {
40965
+ if (!root || isFunctionLike$1(root)) return false;
40476
40966
  let found = false;
40477
40967
  walkAst(root, (child) => {
40478
40968
  if (found) return false;
40969
+ if (child !== root && isFunctionLike$1(child)) return false;
40970
+ if (!isNodeOfType(child, "CallExpression")) return;
40971
+ const callee = stripParenExpression(child.callee);
40972
+ const propertyName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
40973
+ if (propertyName !== null && IMPERATIVE_DOM_MUTATION_NAMES.has(propertyName)) {
40974
+ found = true;
40975
+ return false;
40976
+ }
40977
+ });
40978
+ return found;
40979
+ };
40980
+ const collectImperativeDomFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeMutatesDomImperatively);
40981
+ const callsAnyName = (root, names, shouldSkipNestedFunctions = false) => {
40982
+ if (!root || names.size === 0 || shouldSkipNestedFunctions && isFunctionLike$1(root)) return false;
40983
+ let found = false;
40984
+ walkAst(root, (child) => {
40985
+ if (found) return false;
40986
+ if (shouldSkipNestedFunctions && child !== root && isFunctionLike$1(child)) return false;
40479
40987
  if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && names.has(child.callee.name)) found = true;
40480
40988
  });
40481
40989
  return found;
40482
40990
  };
40991
+ const isFollowedByImperativeDomMutation = (call, imperativeDomFunctionNames) => {
40992
+ let statement = call;
40993
+ let parent = statement.parent;
40994
+ while (parent) {
40995
+ const statements = isNodeOfType(parent, "BlockStatement") || isNodeOfType(parent, "Program") || isNodeOfType(parent, "StaticBlock") ? parent.body : isNodeOfType(parent, "SwitchCase") ? parent.consequent : null;
40996
+ if (statements) {
40997
+ const statementIndex = statements.findIndex((siblingStatement) => siblingStatement === statement);
40998
+ if (statementIndex >= 0) {
40999
+ const nextStatement = statements[statementIndex + 1];
41000
+ return subtreeMutatesDomImperatively(nextStatement) || callsAnyName(nextStatement, imperativeDomFunctionNames, true);
41001
+ }
41002
+ }
41003
+ if (isFunctionLike$1(parent) || parent.type.endsWith("Statement") && !isNodeOfType(parent, "ExpressionStatement")) return false;
41004
+ statement = parent;
41005
+ parent = parent.parent;
41006
+ }
41007
+ return false;
41008
+ };
40483
41009
  const isInsideStartViewTransition = (node) => {
40484
41010
  let cursor = node.parent;
40485
41011
  while (cursor) {
@@ -40520,11 +41046,12 @@ const importsImperativeDomLibrary = (program) => {
40520
41046
  };
40521
41047
  const hasExemptFlushSyncCall = (program, localName) => {
40522
41048
  const measuringFunctionNames = collectMeasuringFunctionNames(program);
41049
+ const imperativeDomFunctionNames = collectImperativeDomFunctionNames(program);
40523
41050
  let exempt = false;
40524
41051
  walkAst(program, (child) => {
40525
41052
  if (exempt) return false;
40526
41053
  if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "Identifier") || child.callee.name !== localName) return;
40527
- if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames)) {
41054
+ if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames) || isFollowedByImperativeDomMutation(child, imperativeDomFunctionNames)) {
40528
41055
  exempt = true;
40529
41056
  return false;
40530
41057
  }
@@ -41835,7 +42362,7 @@ const noInitializeState = defineRule({
41835
42362
  tags: ["test-noise"],
41836
42363
  recommendation: "Pass the initial value directly to useState() instead of setting it from a mount-only useEffect. For SSR hydration, prefer useSyncExternalStore().",
41837
42364
  create: (context) => ({ CallExpression(node) {
41838
- if (!isUseEffect(node)) return;
42365
+ if (!isReactHookCall(node, "useEffect", context.scopes)) return;
41839
42366
  const dependencies = node.arguments?.[1];
41840
42367
  if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
41841
42368
  const analysis = getProgramAnalysis(node);
@@ -42198,7 +42725,8 @@ const noJsxElementType = defineRule({
42198
42725
  create: (context) => {
42199
42726
  let isJsxImported = false;
42200
42727
  const flaggedAnnotations = [];
42201
- const checkReturnType = (returnType) => {
42728
+ const collectComponentReturnType = (functionNode, returnType) => {
42729
+ if (!(isNodeOfType(functionNode, "TSDeclareFunction") ? Boolean(functionNode.id && isReactComponentName(functionNode.id.name)) : isComponentFunction$1(functionNode))) return;
42202
42730
  const typeAnnotation = extractReturnTypeAnnotation(returnType);
42203
42731
  if (!typeAnnotation) return;
42204
42732
  if (isJsxElementTypeReference(typeAnnotation)) flaggedAnnotations.push(typeAnnotation);
@@ -42208,19 +42736,16 @@ const noJsxElementType = defineRule({
42208
42736
  if (isJsxImportBinding(node)) isJsxImported = true;
42209
42737
  },
42210
42738
  FunctionDeclaration(node) {
42211
- checkReturnType(node.returnType);
42739
+ collectComponentReturnType(node, node.returnType);
42212
42740
  },
42213
42741
  ArrowFunctionExpression(node) {
42214
- checkReturnType(node.returnType);
42742
+ collectComponentReturnType(node, node.returnType);
42215
42743
  },
42216
42744
  FunctionExpression(node) {
42217
- checkReturnType(node.returnType);
42745
+ collectComponentReturnType(node, node.returnType);
42218
42746
  },
42219
42747
  TSDeclareFunction(node) {
42220
- checkReturnType(node.returnType);
42221
- },
42222
- TSMethodSignature(node) {
42223
- checkReturnType(node.returnType);
42748
+ collectComponentReturnType(node, node.returnType);
42224
42749
  },
42225
42750
  "Program:exit"() {
42226
42751
  if (isJsxImported) return;
@@ -43449,7 +43974,7 @@ const noMirrorPropEffect = defineRule({
43449
43974
  const setterElement = elements[1];
43450
43975
  if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
43451
43976
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
43452
- if (!isHookCall$2(declarator.init, "useState")) continue;
43977
+ if (!isReactHookCall(declarator.init, "useState", context.scopes)) continue;
43453
43978
  const initializer = declarator.init.arguments?.[0];
43454
43979
  if (!initializer) continue;
43455
43980
  const propRootName = getPropRootName(initializer, propNames);
@@ -43467,7 +43992,7 @@ const noMirrorPropEffect = defineRule({
43467
43992
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
43468
43993
  const effectCall = unwrapDiscardedExpression(statement);
43469
43994
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
43470
- if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
43995
+ if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
43471
43996
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
43472
43997
  const depsNode = effectCall.arguments[1];
43473
43998
  if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
@@ -43876,7 +44401,7 @@ const noMultiComp = defineRule({
43876
44401
  });
43877
44402
  //#endregion
43878
44403
  //#region src/plugin/rules/state-and-effects/no-mutable-in-deps.ts
43879
- const collectUseRefBindingNames = (componentBody) => {
44404
+ const collectUseRefBindingNames = (componentBody, scopes) => {
43880
44405
  const useRefBindings = /* @__PURE__ */ new Set();
43881
44406
  if (!isNodeOfType(componentBody, "BlockStatement")) return useRefBindings;
43882
44407
  for (const statement of componentBody.body ?? []) {
@@ -43884,7 +44409,7 @@ const collectUseRefBindingNames = (componentBody) => {
43884
44409
  for (const declarator of statement.declarations ?? []) {
43885
44410
  if (!isNodeOfType(declarator.id, "Identifier")) continue;
43886
44411
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
43887
- if (!isHookCall$2(declarator.init, "useRef")) continue;
44412
+ if (!isReactHookCall(declarator.init, "useRef", scopes)) continue;
43888
44413
  useRefBindings.add(declarator.id.name);
43889
44414
  }
43890
44415
  }
@@ -43919,12 +44444,12 @@ const noMutableInDeps = defineRule({
43919
44444
  create: (context) => {
43920
44445
  const checkComponent = (componentBody, componentParams = []) => {
43921
44446
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
43922
- const useRefBindingNames = collectUseRefBindingNames(componentBody);
44447
+ const useRefBindingNames = collectUseRefBindingNames(componentBody, context.scopes);
43923
44448
  const localBindingNames = collectLocalBindingNames(componentBody);
43924
44449
  for (const param of componentParams) collectPatternNames(param, localBindingNames);
43925
44450
  walkAst(componentBody, (child) => {
43926
44451
  if (!isNodeOfType(child, "CallExpression")) return;
43927
- if (!isHookCall$2(child, HOOKS_WITH_DEPS)) return;
44452
+ if (!isReactHookCall(child, HOOKS_WITH_DEPS, context.scopes)) return;
43928
44453
  if ((child.arguments?.length ?? 0) < 2) return;
43929
44454
  const depsNode = child.arguments[1];
43930
44455
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -45754,6 +46279,7 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
45754
46279
  "useMatchMedia",
45755
46280
  "useMediaJobProgress",
45756
46281
  "useMediaQuery",
46282
+ "useMediaQueryState",
45757
46283
  "useResizeObserver",
45758
46284
  "useVisibility",
45759
46285
  "useWindowSize"
@@ -45790,9 +46316,30 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
45790
46316
  if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
45791
46317
  return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
45792
46318
  };
45793
- const isExternalSubscriptionHookRef = (ref) => {
46319
+ const getLocalHookExternalStateProof = (analysis, ref) => {
46320
+ let hookFunction = resolveToFunction(ref);
46321
+ if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
46322
+ const definitionNode = definition.node;
46323
+ if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
46324
+ const initializer = stripParenExpression(definitionNode.init);
46325
+ if (!isNodeOfType(initializer, "CallExpression")) continue;
46326
+ const callee = stripParenExpression(initializer.callee);
46327
+ if (!isNodeOfType(callee, "Identifier")) continue;
46328
+ const calleeReference = getRef(analysis, callee);
46329
+ if (!calleeReference) continue;
46330
+ hookFunction = resolveToFunction(calleeReference);
46331
+ if (hookFunction) break;
46332
+ }
46333
+ if (!hookFunction) return null;
46334
+ const returnedReferences = collectFunctionReturnStatements(hookFunction).flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
46335
+ if (returnedReferences.length === 0) return null;
46336
+ return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
46337
+ };
46338
+ const isExternalSubscriptionHookRef = (analysis, ref) => {
45794
46339
  const identifier = ref.identifier;
45795
46340
  if (!isNodeOfType(identifier, "Identifier")) return false;
46341
+ const localHookProof = getLocalHookExternalStateProof(analysis, ref);
46342
+ if (localHookProof !== null) return localHookProof;
45796
46343
  if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
45797
46344
  return Boolean(ref.resolved?.defs.some((def) => {
45798
46345
  const node = def.node;
@@ -45815,16 +46362,10 @@ const noPassDataToParent = defineRule({
45815
46362
  tags: ["test-noise"],
45816
46363
  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",
45817
46364
  create: (context) => {
45818
- const isReactUseRefCall = (node) => isReactApiCall(node, "useRef", context.scopes, {
45819
- allowGlobalReactNamespace: true,
45820
- allowUnboundBareCalls: true
45821
- });
45822
- const isReactUseEffectCall = (node) => isReactApiCall(node, "useEffect", context.scopes, {
45823
- allowGlobalReactNamespace: true,
45824
- allowUnboundBareCalls: true
45825
- });
46365
+ const isReactUseRefCall = (node) => isReactHookCall(node, "useRef", context.scopes);
46366
+ const isReactUseEffectCall = (node) => isReactHookCall(node, "useEffect", context.scopes);
45826
46367
  return { CallExpression(node) {
45827
- if (!isUseEffect(node)) return;
46368
+ if (!isReactUseEffectCall(node)) return;
45828
46369
  const analysis = getProgramAnalysis(node);
45829
46370
  if (!analysis) return;
45830
46371
  if (hasCleanup(analysis, node)) return;
@@ -45876,11 +46417,11 @@ const noPassDataToParent = defineRule({
45876
46417
  if (argumentRef && resolveToFunction(argumentRef)) return [];
45877
46418
  }
45878
46419
  return getDownstreamRefs(analysis, argument);
45879
- }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
46420
+ }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) || isExternalSubscriptionHookRef(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
45880
46421
  if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
45881
46422
  if (!argsUpstreamRefs.some((argRef) => {
45882
46423
  if (isUseStateIdentifier(argRef.identifier)) return false;
45883
- if (isExternalSubscriptionHookRef(argRef)) return false;
46424
+ if (isExternalSubscriptionHookRef(analysis, argRef)) return false;
45884
46425
  if (isProp(analysis, argRef)) return false;
45885
46426
  if (isUseRefIdentifier(argRef.identifier)) return false;
45886
46427
  if (isRefCurrent(argRef)) return false;
@@ -46111,7 +46652,7 @@ const noPassLiveStateToParent = defineRule({
46111
46652
  tags: ["test-noise"],
46112
46653
  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",
46113
46654
  create: (context) => ({ CallExpression(node) {
46114
- if (!isUseEffect(node)) return;
46655
+ if (!isReactHookCall(node, "useEffect", context.scopes)) return;
46115
46656
  const analysis = getProgramAnalysis(node);
46116
46657
  if (!analysis) return;
46117
46658
  const effectFnRefs = getEffectFnRefs(analysis, node);
@@ -46526,7 +47067,7 @@ const hasPreviousValueDep = (effectNode, depElements) => {
46526
47067
  if (!isNodeOfType(element, "Identifier")) continue;
46527
47068
  const binding = findVariableInitializer(effectNode, element.name);
46528
47069
  if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) continue;
46529
- const calleeName = getCalleeName$2(binding.initializer);
47070
+ const calleeName = getCalleeName$1(binding.initializer);
46530
47071
  if (calleeName && PREVIOUS_VALUE_HOOK_PATTERN.test(calleeName)) return true;
46531
47072
  }
46532
47073
  return false;
@@ -46548,7 +47089,7 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
46548
47089
  if (!isNodeOfType(receiver, "Identifier")) return null;
46549
47090
  const binding = findVariableInitializer(callExpression, receiver.name);
46550
47091
  if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
46551
- if (getCalleeName$2(binding.initializer) !== "useRef") return null;
47092
+ if (getCalleeName$1(binding.initializer) !== "useRef") return null;
46552
47093
  const callbackArgument = binding.initializer.arguments?.[0];
46553
47094
  if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
46554
47095
  return isPropName(callbackArgument.name) ? callbackArgument.name : null;
@@ -46580,7 +47121,7 @@ const noPropCallbackInEffect = defineRule({
46580
47121
  return {
46581
47122
  ...propStackTracker.visitors,
46582
47123
  CallExpression(node) {
46583
- if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
47124
+ if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes) || (node.arguments?.length ?? 0) < 2) return;
46584
47125
  const callback = getEffectCallback(node);
46585
47126
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
46586
47127
  const depsNode = node.arguments[1];
@@ -48473,7 +49014,7 @@ const noResetAllStateOnPropChange = defineRule({
48473
49014
  tags: ["test-noise"],
48474
49015
  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",
48475
49016
  create: (context) => ({ CallExpression(node) {
48476
- if (!isUseEffect(node)) return;
49017
+ if (!isReactHookCall(node, "useEffect", context.scopes)) return;
48477
49018
  const analysis = getProgramAnalysis(node);
48478
49019
  if (!analysis) return;
48479
49020
  const effectFnRefs = getEffectFnRefs(analysis, node);
@@ -48742,7 +49283,7 @@ const isTanStackServerFnHandlerCall = (node) => {
48742
49283
  if (node.callee.property.name !== "handler") return false;
48743
49284
  let currentNode = node.callee.object;
48744
49285
  while (isNodeOfType(currentNode, "CallExpression")) {
48745
- const calleeName = getCalleeName$2(currentNode);
49286
+ const calleeName = getCalleeName$1(currentNode);
48746
49287
  if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) return true;
48747
49288
  if (!isNodeOfType(currentNode.callee, "MemberExpression")) return false;
48748
49289
  currentNode = currentNode.callee.object;
@@ -49243,7 +49784,7 @@ const noSelfUpdatingEffect = defineRule({
49243
49784
  create: (context) => {
49244
49785
  const checkFunctionScope = (functionBody) => {
49245
49786
  if (!functionBody || !isNodeOfType(functionBody, "BlockStatement")) return;
49246
- const useStateBindings = collectUseStateBindings(functionBody);
49787
+ const useStateBindings = collectUseStateBindings(functionBody, context.scopes);
49247
49788
  if (useStateBindings.length === 0) return;
49248
49789
  const setterNameToStateName = /* @__PURE__ */ new Map();
49249
49790
  for (const binding of useStateBindings) setterNameToStateName.set(binding.setterName, binding.valueName);
@@ -49252,7 +49793,7 @@ const noSelfUpdatingEffect = defineRule({
49252
49793
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
49253
49794
  const effectCall = unwrapDiscardedExpression(statement);
49254
49795
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
49255
- if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
49796
+ if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
49256
49797
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
49257
49798
  const dependencyStateNames = collectDependencyStateNames(effectCall.arguments[1]);
49258
49799
  if (dependencyStateNames.size === 0) continue;
@@ -49338,7 +49879,7 @@ const noSetStateInRender = defineRule({
49338
49879
  create: (context) => {
49339
49880
  const checkComponent = (componentBody) => {
49340
49881
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
49341
- const setterNames = new Set(collectUseStateBindings(componentBody).map((binding) => binding.setterName));
49882
+ const setterNames = new Set(collectUseStateBindings(componentBody, context.scopes).map((binding) => binding.setterName));
49342
49883
  if (setterNames.size === 0) return;
49343
49884
  for (const statement of componentBody.body ?? []) {
49344
49885
  const setterCall = isUnconditionalSetterCallStatement(statement, setterNames);
@@ -49587,10 +50128,10 @@ const collectTimerRefUsageFacts = (ownerScope, refName) => {
49587
50128
  });
49588
50129
  return facts;
49589
50130
  };
49590
- const isEffectCallbackFunction = (functionNode) => {
50131
+ const isEffectCallbackFunction = (functionNode, scopes) => {
49591
50132
  const parent = functionNode.parent;
49592
50133
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
49593
- return isHookCall$2(parent, EFFECT_HOOK_NAMES$1) && getEffectCallback(parent) === functionNode;
50134
+ return isReactHookCall(parent, EFFECT_HOOK_NAMES$1, scopes) && getEffectCallback(parent) === functionNode;
49594
50135
  };
49595
50136
  const doesEffectCallbackReturnName = (effectCallback, name) => {
49596
50137
  if (!isFunctionLike$1(effectCallback)) return false;
@@ -49609,24 +50150,24 @@ const isFunctionReturnedFromEffectCallback = (functionNode, effectCallback) => {
49609
50150
  const cleanupBindingName = getFunctionBindingName$1(functionNode);
49610
50151
  return cleanupBindingName !== null && doesEffectCallbackReturnName(effectCallback, cleanupBindingName);
49611
50152
  };
49612
- const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
50153
+ const isReturnedFromAnyEffectInScope = (functionNode, ownerScope, scopes) => {
49613
50154
  const cleanupBindingName = getFunctionBindingName$1(functionNode);
49614
50155
  if (cleanupBindingName === null) return false;
49615
50156
  let isReturnedFromEffect = false;
49616
50157
  walkAst(ownerScope, (child) => {
49617
50158
  if (isReturnedFromEffect) return false;
49618
- if (!isNodeOfType(child, "CallExpression") || !isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
50159
+ if (!isNodeOfType(child, "CallExpression") || !isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) return;
49619
50160
  const effectCallback = getEffectCallback(child);
49620
50161
  if (effectCallback && doesEffectCallbackReturnName(effectCallback, cleanupBindingName)) isReturnedFromEffect = true;
49621
50162
  });
49622
50163
  return isReturnedFromEffect;
49623
50164
  };
49624
- const isInsideEffectCleanupReturn = (node, ownerScope) => {
50165
+ const isInsideEffectCleanupReturn = (node, ownerScope, scopes) => {
49625
50166
  let functionNode = findEnclosingFunction$1(node);
49626
50167
  while (functionNode) {
49627
50168
  const outerFunction = findEnclosingFunction$1(functionNode);
49628
- if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
49629
- if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
50169
+ if (outerFunction && isEffectCallbackFunction(outerFunction, scopes) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
50170
+ if (isReturnedFromAnyEffectInScope(functionNode, ownerScope, scopes)) return true;
49630
50171
  functionNode = outerFunction;
49631
50172
  }
49632
50173
  return false;
@@ -49662,10 +50203,10 @@ const noStaleTimerRef = defineRule({
49662
50203
  if (isShadowedTimerGlobal(node)) return;
49663
50204
  const { clearCalleeName, refName } = clearCall;
49664
50205
  const refBinding = findVariableInitializer(node, refName);
49665
- if (!refBinding?.initializer || !isHookCall$2(refBinding.initializer, "useRef")) return;
50206
+ if (!refBinding?.initializer || !isReactHookCall(refBinding.initializer, "useRef", context.scopes)) return;
49666
50207
  const usageFacts = collectTimerRefUsageFacts(refBinding.scopeOwner, refName);
49667
50208
  if (!usageFacts.holdsScheduledTimerId || !usageFacts.hasPendingSignalRead) return;
49668
- if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner)) return;
50209
+ if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner, context.scopes)) return;
49669
50210
  if (hasRefCurrentReassignmentAfterClear(node, refName)) return;
49670
50211
  context.report({
49671
50212
  node,
@@ -55469,7 +56010,7 @@ const classifyCallableReadsInsideEffect = (callableIdentifier, effectCallback, c
55469
56010
  allReadsAreInSubHandlers = false;
55470
56011
  return;
55471
56012
  }
55472
- if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$2(subHandlerCall);
56013
+ if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$1(subHandlerCall);
55473
56014
  });
55474
56015
  return {
55475
56016
  hasAnyRead,
@@ -55492,7 +56033,7 @@ const preferUseEffectEvent = defineRule({
55492
56033
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
55493
56034
  const effectCall = statement.expression;
55494
56035
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
55495
- if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
56036
+ if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
55496
56037
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
55497
56038
  const depsNode = effectCall.arguments[1];
55498
56039
  if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
@@ -55524,11 +56065,11 @@ const preferUseEffectEvent = defineRule({
55524
56065
  });
55525
56066
  //#endregion
55526
56067
  //#region src/plugin/rules/state-and-effects/prefer-use-sync-external-store.ts
55527
- const findUseEffectsInComponent = (componentBody) => {
56068
+ const findUseEffectsInComponent = (componentBody, scopes) => {
55528
56069
  const effectCalls = [];
55529
56070
  if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
55530
56071
  for (const statement of componentBody.body ?? []) walkAst(statement, (child) => {
55531
- if (isNodeOfType(child, "CallExpression") && isHookCall$2(child, EFFECT_HOOK_NAMES$1)) effectCalls.push(child);
56072
+ if (isNodeOfType(child, "CallExpression") && isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) effectCalls.push(child);
55532
56073
  });
55533
56074
  return effectCalls;
55534
56075
  };
@@ -55731,7 +56272,7 @@ const preferUseSyncExternalStore = defineRule({
55731
56272
  };
55732
56273
  const checkComponent = (componentBody) => {
55733
56274
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
55734
- const useStateBindings = collectUseStateBindings(componentBody);
56275
+ const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
55735
56276
  if (useStateBindings.length === 0) return;
55736
56277
  const useStateInitializerByValueName = /* @__PURE__ */ new Map();
55737
56278
  for (const binding of useStateBindings) {
@@ -55744,7 +56285,7 @@ const preferUseSyncExternalStore = defineRule({
55744
56285
  }
55745
56286
  const setterNameToValueName = /* @__PURE__ */ new Map();
55746
56287
  for (const binding of useStateBindings) setterNameToValueName.set(binding.setterName, binding.valueName);
55747
- for (const effectCall of findUseEffectsInComponent(componentBody)) {
56288
+ for (const effectCall of findUseEffectsInComponent(componentBody, context.scopes)) {
55748
56289
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
55749
56290
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
55750
56291
  const depsNode = effectCall.arguments[1];
@@ -55786,7 +56327,7 @@ const preferUseSyncExternalStore = defineRule({
55786
56327
  })).filter((candidate) => candidate.storeName !== null);
55787
56328
  if (snapshotBindings.length === 0) return;
55788
56329
  const reportedDeclarators = /* @__PURE__ */ new Set();
55789
- for (const effectCall of findUseEffectsInComponent(componentBody)) {
56330
+ for (const effectCall of findUseEffectsInComponent(componentBody, context.scopes)) {
55790
56331
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
55791
56332
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
55792
56333
  const depsNode = effectCall.arguments[1];
@@ -55883,7 +56424,7 @@ const preferUseReducer = defineRule({
55883
56424
  create: (context) => {
55884
56425
  const reportCoUpdatedUseState = (body, componentName) => {
55885
56426
  if (!isNodeOfType(body, "BlockStatement")) return;
55886
- const bindings = collectUseStateBindings(body);
56427
+ const bindings = collectUseStateBindings(body, context.scopes);
55887
56428
  const setterNames = new Set(bindings.map((binding) => binding.setterName));
55888
56429
  if (setterNames.size < 5) return;
55889
56430
  const coUpdatedCount = findLargestCoUpdatedSetterGroup(body, setterNames, new Map(bindings.map((binding) => {
@@ -56099,7 +56640,7 @@ const QUERY_READ_METHOD_NAMES = new Set([
56099
56640
  ]);
56100
56641
  const isQueryCacheSourceCall = (initializer) => {
56101
56642
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
56102
- const hookName = getCalleeName$2(initializer);
56643
+ const hookName = getCalleeName$1(initializer);
56103
56644
  if (!hookName) return false;
56104
56645
  return hookName === "useQueryClient" || TRPC_UTILS_HOOK_PATTERN.test(hookName);
56105
56646
  };
@@ -56231,7 +56772,7 @@ const queryMutationMissingInvalidation = defineRule({
56231
56772
  },
56232
56773
  CallExpression(node) {
56233
56774
  if (!hasQueryReadUsage) {
56234
- const callName = getCalleeName$2(node);
56775
+ const callName = getCalleeName$1(node);
56235
56776
  if (callName && (QUERY_READ_HOOK_NAMES.has(callName) || QUERY_READ_METHOD_NAMES.has(callName) || TRPC_UTILS_HOOK_PATTERN.test(callName))) hasQueryReadUsage = true;
56236
56777
  }
56237
56778
  const calleeName = isNodeOfType(node.callee, "Identifier") ? node.callee.name : null;
@@ -56724,26 +57265,30 @@ const REMOVAL_MESSAGE_BY_REACT_API_NAME = new Map([
56724
57265
  ["useCallback", "This `useCallback` is dead weight, since React Compiler already caches every function here. Delete it."],
56725
57266
  ["memo", "This `memo()` is dead weight, since React Compiler already caches the component's output. Delete it."]
56726
57267
  ]);
56727
- const resolveReactApiNameForIdentifier = (callee) => {
57268
+ const resolveReactApiNameForIdentifier = (callee, context) => {
56728
57269
  if (!isNodeOfType(callee, "Identifier")) return null;
57270
+ if (context.scopes.symbolFor(callee)?.kind !== "import") return null;
56729
57271
  const importedName = getImportedNameFromModule(callee, callee.name, "react");
56730
57272
  if (importedName && REMOVAL_MESSAGE_BY_REACT_API_NAME.has(importedName)) return importedName;
56731
57273
  return null;
56732
57274
  };
56733
- const resolveReactApiNameForMemberExpression = (callee) => {
57275
+ const resolveReactApiNameForMemberExpression = (callee, context) => {
56734
57276
  if (!isNodeOfType(callee, "MemberExpression")) return null;
56735
57277
  if (callee.computed) return null;
56736
- const namespaceIdentifier = callee.object;
57278
+ const namespaceIdentifier = stripParenExpression(callee.object);
56737
57279
  const propertyIdentifier = callee.property;
56738
57280
  if (!isNodeOfType(namespaceIdentifier, "Identifier")) return null;
56739
57281
  if (!isNodeOfType(propertyIdentifier, "Identifier")) return null;
56740
57282
  if (!REMOVAL_MESSAGE_BY_REACT_API_NAME.has(propertyIdentifier.name)) return null;
56741
57283
  const namespaceName = namespaceIdentifier.name;
56742
- if (isCanonicalReactNamespaceName(namespaceName)) return propertyIdentifier.name;
56743
- if (isImportedFromModule(namespaceIdentifier, namespaceName, "react")) return propertyIdentifier.name;
57284
+ if (context.scopes.symbolFor(namespaceIdentifier)?.kind === "import" && isImportedFromModule(namespaceIdentifier, namespaceName, "react")) return propertyIdentifier.name;
57285
+ if (isCanonicalReactNamespaceName(namespaceName) && context.scopes.isGlobalReference(namespaceIdentifier)) return propertyIdentifier.name;
56744
57286
  return null;
56745
57287
  };
56746
- const resolveReactApiNameForCallee = (callee) => resolveReactApiNameForIdentifier(callee) ?? resolveReactApiNameForMemberExpression(callee);
57288
+ const resolveReactApiNameForCallee = (callee, context) => {
57289
+ const unwrappedCallee = stripParenExpression(callee);
57290
+ return resolveReactApiNameForIdentifier(unwrappedCallee, context) ?? resolveReactApiNameForMemberExpression(unwrappedCallee, context);
57291
+ };
56747
57292
  const isNullishComparatorArgument = (argumentNode) => isNodeOfType(argumentNode, "Identifier") && argumentNode.name === "undefined" || isNodeOfType(argumentNode, "Literal") && argumentNode.value === null;
56748
57293
  const COMPILER_INFERABLE_HOC_NAMES = new Set(["memo", "forwardRef"]);
56749
57294
  const calleeTrailingName = (callee) => {
@@ -56769,7 +57314,7 @@ const reactCompilerNoManualMemoization = defineRule({
56769
57314
  requires: ["react-compiler"],
56770
57315
  recommendation: "Delete the `useMemo` / `useCallback` / `memo` call and use the plain value or component. React Compiler caches it for you.",
56771
57316
  create: (context) => ({ CallExpression(node) {
56772
- const apiName = resolveReactApiNameForCallee(node.callee);
57317
+ const apiName = resolveReactApiNameForCallee(node.callee, context);
56773
57318
  if (!apiName) return;
56774
57319
  if (apiName === "memo") {
56775
57320
  const comparatorArgument = node.arguments?.[1];
@@ -57894,6 +58439,125 @@ const ASYNC_DATA_CALLEE_NAMES = new Set([
57894
58439
  const RESOURCE_LOAD_EVENT_ATTRIBUTE_PATTERN = /^on(?:Load|Error|Abort|Progress|CanPlay|Stalled|Suspend|Waiting|Ended)/;
57895
58440
  const JSX_EVENT_HANDLER_ATTRIBUTE_PATTERN = /^on[A-Z]/;
57896
58441
  const REDUX_DISPATCH_HOOK_PATTERN = /^use\w*Dispatch$/;
58442
+ const FILE_READER_READ_METHOD_NAMES = new Set([
58443
+ "readAsArrayBuffer",
58444
+ "readAsBinaryString",
58445
+ "readAsDataURL",
58446
+ "readAsText"
58447
+ ]);
58448
+ const isGlobalFileReaderConstruction = (expression, context) => {
58449
+ if (!expression) return false;
58450
+ const unwrappedExpression = stripParenExpression(expression);
58451
+ if (!isNodeOfType(unwrappedExpression, "NewExpression") || !isNodeOfType(unwrappedExpression.callee, "Identifier")) return false;
58452
+ return unwrappedExpression.callee.name === "FileReader" && context.scopes.isGlobalReference(unwrappedExpression.callee);
58453
+ };
58454
+ const getFileReaderOriginStartBefore = (readerSymbol, readCall, context) => {
58455
+ const readFunction = findEnclosingFunction$1(readCall);
58456
+ let latestValue = null;
58457
+ let latestStart = null;
58458
+ if (readerSymbol.initializer && findEnclosingFunction$1(readerSymbol.declarationNode) === readFunction && readerSymbol.declarationNode.range[0] < readCall.range[0]) {
58459
+ latestValue = readerSymbol.initializer;
58460
+ latestStart = readerSymbol.declarationNode.range[0];
58461
+ }
58462
+ for (const reference of readerSymbol.references) {
58463
+ if (reference.flag === "read" || reference.identifier.range[0] >= readCall.range[0] || latestStart !== null && reference.identifier.range[0] <= latestStart || findEnclosingFunction$1(reference.identifier) !== readFunction) continue;
58464
+ const assignment = reference.identifier.parent;
58465
+ if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== reference.identifier) continue;
58466
+ latestValue = assignment.right;
58467
+ latestStart = reference.identifier.range[0];
58468
+ }
58469
+ return isGlobalFileReaderConstruction(latestValue, context) ? latestStart : null;
58470
+ };
58471
+ const resolveLoadingCompletionFunction = (expression, context) => {
58472
+ const directFunction = resolveExactLocalFunction(expression, context.scopes);
58473
+ if (directFunction) return directFunction;
58474
+ const unwrappedExpression = stripParenExpression(expression);
58475
+ if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
58476
+ const symbol = context.scopes.symbolFor(unwrappedExpression);
58477
+ const initializer = symbol ? getDirectUnreassignedInitializer(symbol) : null;
58478
+ if (!initializer || !isNodeOfType(initializer, "CallExpression") || !isReactApiCall(initializer, "useCallback", context.scopes)) return null;
58479
+ const callback = initializer.arguments?.[0];
58480
+ return callback && isFunctionLike$1(callback) ? callback : null;
58481
+ };
58482
+ const isSetterBooleanCall = (node, setterSymbol, value, context) => {
58483
+ if (!isNodeOfType(node, "CallExpression")) return false;
58484
+ const callee = stripParenExpression(node.callee);
58485
+ const argument = node.arguments?.[0];
58486
+ const unwrappedArgument = argument ? stripParenExpression(argument) : null;
58487
+ return Boolean(isNodeOfType(callee, "Identifier") && context.scopes.symbolFor(callee) === setterSymbol && unwrappedArgument && isNodeOfType(unwrappedArgument, "Literal") && unwrappedArgument.value === value);
58488
+ };
58489
+ const functionClearsLoadingState = (functionNode, setterSymbol, context, visitedFunctions) => {
58490
+ if (visitedFunctions.has(functionNode) || !isFunctionLike$1(functionNode)) return false;
58491
+ visitedFunctions.add(functionNode);
58492
+ let didClearLoadingState = false;
58493
+ walkAst(functionNode.body, (child) => {
58494
+ if (didClearLoadingState) return false;
58495
+ if (child !== functionNode.body && isFunctionLike$1(child)) return false;
58496
+ if (!isNodeOfType(child, "CallExpression")) return;
58497
+ if (isSetterBooleanCall(child, setterSymbol, false, context)) {
58498
+ didClearLoadingState = true;
58499
+ return false;
58500
+ }
58501
+ const helperFunction = resolveLoadingCompletionFunction(child.callee, context);
58502
+ if (helperFunction && functionClearsLoadingState(helperFunction, setterSymbol, context, visitedFunctions)) {
58503
+ didClearLoadingState = true;
58504
+ return false;
58505
+ }
58506
+ });
58507
+ return didClearLoadingState;
58508
+ };
58509
+ const getLatestFileReaderCallbackBefore = (readCall, readerSymbol, propertyName, originStart, context) => {
58510
+ const readFunction = findEnclosingFunction$1(readCall);
58511
+ if (!readFunction || !isFunctionLike$1(readFunction)) return null;
58512
+ let callback = null;
58513
+ let callbackStart = originStart;
58514
+ walkAst(readFunction.body, (child) => {
58515
+ if (child !== readFunction.body && isFunctionLike$1(child)) return false;
58516
+ 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;
58517
+ const receiver = stripParenExpression(child.left.object);
58518
+ if (!isNodeOfType(receiver, "Identifier") || context.scopes.symbolFor(receiver) !== readerSymbol) return;
58519
+ callback = child.right;
58520
+ callbackStart = child.range[0];
58521
+ });
58522
+ return callback;
58523
+ };
58524
+ const setterStartsLoadingBefore = (readCall, setterSymbol, context) => {
58525
+ const readFunction = findEnclosingFunction$1(readCall);
58526
+ if (!readFunction || !isFunctionLike$1(readFunction)) return false;
58527
+ let didStartLoading = false;
58528
+ walkAst(readFunction.body, (child) => {
58529
+ if (didStartLoading) return false;
58530
+ if (child !== readFunction.body && isFunctionLike$1(child)) return false;
58531
+ if (!isNodeOfType(child, "CallExpression") || child.range[0] >= readCall.range[0]) return;
58532
+ if (isSetterBooleanCall(child, setterSymbol, true, context)) {
58533
+ didStartLoading = true;
58534
+ return false;
58535
+ }
58536
+ });
58537
+ return didStartLoading;
58538
+ };
58539
+ const setterTracksFileReader = (functionBody, setterSymbol, context) => {
58540
+ let didFindFileReaderLifecycle = false;
58541
+ walkAst(functionBody, (child) => {
58542
+ if (didFindFileReaderLifecycle) return false;
58543
+ if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || !FILE_READER_READ_METHOD_NAMES.has(getStaticPropertyName(child.callee) ?? "")) return;
58544
+ const receiver = stripParenExpression(child.callee.object);
58545
+ if (!isNodeOfType(receiver, "Identifier")) return;
58546
+ const readerSymbol = context.scopes.symbolFor(receiver);
58547
+ if (!readerSymbol) return;
58548
+ const originStart = getFileReaderOriginStartBefore(readerSymbol, child, context);
58549
+ if (originStart === null || !setterStartsLoadingBefore(child, setterSymbol, context)) return;
58550
+ const loadCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onload", originStart, context);
58551
+ const errorCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onerror", originStart, context);
58552
+ const loadFunction = loadCallback ? resolveLoadingCompletionFunction(loadCallback, context) : null;
58553
+ const errorFunction = errorCallback ? resolveLoadingCompletionFunction(errorCallback, context) : null;
58554
+ if (loadFunction && errorFunction && functionClearsLoadingState(loadFunction, setterSymbol, context, /* @__PURE__ */ new Set()) && functionClearsLoadingState(errorFunction, setterSymbol, context, /* @__PURE__ */ new Set())) {
58555
+ didFindFileReaderLifecycle = true;
58556
+ return false;
58557
+ }
58558
+ });
58559
+ return didFindFileReaderLifecycle;
58560
+ };
57897
58561
  const hasAsyncLoadingWork = (fnBody, setterName) => {
57898
58562
  let found = false;
57899
58563
  walkAst(fnBody, (child) => {
@@ -58067,6 +58731,8 @@ const renderingUsetransitionLoading = defineRule({
58067
58731
  const fnBody = enclosingFunctionBody(node);
58068
58732
  if (fnBody && hasAsyncLoadingWork(fnBody, setterName)) return;
58069
58733
  if (fnBody && setterName) {
58734
+ const setterSymbol = isNodeOfType(secondBinding, "Identifier") ? context.scopes.symbolFor(secondBinding) : null;
58735
+ if (setterSymbol && setterTracksFileReader(fnBody, setterSymbol, context)) return;
58070
58736
  if (setterEscapes(fnBody, setterName, node)) return;
58071
58737
  if (setterCalledAlongsideAsyncSignal(fnBody, setterName)) return;
58072
58738
  if (setterCalledInEventListenerHandler(fnBody, setterName)) return;
@@ -58398,7 +59064,7 @@ const rerenderDependencies = defineRule({
58398
59064
  severity: "error",
58399
59065
  recommendation: "Move it into a useMemo, useRef, or a constant outside the component so it stays the same between renders.",
58400
59066
  create: (context) => ({ CallExpression(node) {
58401
- if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
59067
+ if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes) || node.arguments.length < 2) return;
58402
59068
  const depsNode = node.arguments[1];
58403
59069
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
58404
59070
  for (const element of depsNode.elements ?? []) {
@@ -58653,7 +59319,7 @@ const rerenderLazyRefInit = defineRule({
58653
59319
  category: "Performance",
58654
59320
  recommendation: "Initialize the ref lazily so expensive values are not rebuilt and discarded on every render.",
58655
59321
  create: (context) => ({ CallExpression(node) {
58656
- if (!isHookCall$2(node, "useRef") || !node.arguments?.length) return;
59322
+ if (!isReactHookCall(node, "useRef", context.scopes) || !node.arguments?.length) return;
58657
59323
  const initializer = stripParenExpression(node.arguments[0]);
58658
59324
  const isPlainCall = isNodeOfType(initializer, "CallExpression");
58659
59325
  const isNewCall = isNodeOfType(initializer, "NewExpression");
@@ -58717,7 +59383,7 @@ const rerenderLazyStateInit = defineRule({
58717
59383
  category: "Performance",
58718
59384
  recommendation: "Wrap expensive initial state in an arrow function so the initializer does not rerun and get thrown away on every render.",
58719
59385
  create: (context) => ({ CallExpression(node) {
58720
- if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
59386
+ if (!isReactHookCall(node, "useState", context.scopes) || !node.arguments?.length) return;
58721
59387
  const initializer = findEagerInitializerCall(node.arguments[0]);
58722
59388
  if (!initializer) return;
58723
59389
  const isConstructor = isNodeOfType(initializer, "NewExpression");
@@ -59480,11 +60146,11 @@ const isInsideConditionTest = (identifier, stopAt) => {
59480
60146
  }
59481
60147
  return false;
59482
60148
  };
59483
- const collectEffectDependencyInfos = (componentBody, setterNames) => {
60149
+ const collectEffectDependencyInfos = (componentBody, setterNames, scopes) => {
59484
60150
  const effectInfos = [];
59485
60151
  walkAst(componentBody, (child) => {
59486
60152
  if (!isNodeOfType(child, "CallExpression")) return;
59487
- if (!isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
60153
+ if (!isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) return;
59488
60154
  const dependencyNames = /* @__PURE__ */ new Set();
59489
60155
  for (const argument of child.arguments ?? []) {
59490
60156
  if (!isNodeOfType(argument, "ArrayExpression")) continue;
@@ -59540,13 +60206,14 @@ const collectEffectDependencyInfos = (componentBody, setterNames) => {
59540
60206
  });
59541
60207
  return effectInfos;
59542
60208
  };
59543
- const collectCustomHookArgumentNames = (componentBody) => {
60209
+ const collectCustomHookArgumentNames = (componentBody, scopes) => {
59544
60210
  const argumentNames = /* @__PURE__ */ new Set();
59545
60211
  walkAst(componentBody, (child) => {
59546
60212
  if (!isNodeOfType(child, "CallExpression")) return;
59547
60213
  if (!isNodeOfType(child.callee, "Identifier")) return;
59548
60214
  const calleeName = child.callee.name;
59549
60215
  if (!isReactHookName(calleeName)) return;
60216
+ if (isReactHookCall(child, BUILTIN_HOOK_NAMES, scopes)) return;
59550
60217
  if (BUILTIN_HOOK_NAMES.has(calleeName)) return;
59551
60218
  if (EFFECT_HOOK_NAMES$1.has(calleeName)) return;
59552
60219
  for (const argument of child.arguments ?? []) walkAst(argument, (argumentNode) => {
@@ -59587,21 +60254,21 @@ const rerenderStateOnlyInHandlers = defineRule({
59587
60254
  create: (context) => {
59588
60255
  const checkComponent = (componentBody) => {
59589
60256
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
59590
- const bindings = collectUseStateBindings(componentBody);
60257
+ const bindings = collectUseStateBindings(componentBody, context.scopes);
59591
60258
  if (bindings.length === 0) return;
59592
60259
  if (collectRenderReachableExpressions(componentBody).length === 0) return;
59593
- const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
60260
+ const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
59594
60261
  const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
59595
- const directRenderNames = collectRenderReachableNames(componentBody, eventHandlerReferenceNames);
60262
+ const directRenderNames = collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames);
59596
60263
  if (hasRenderPhaseNonHookCall(componentBody)) for (const voidMarkedName of collectTopLevelVoidMarkedNames(componentBody)) directRenderNames.add(voidMarkedName);
59597
60264
  const renderReachableNames = expandTransitiveDependencies(directRenderNames, dependencyGraph);
59598
60265
  const setterNames = new Set(bindings.map((binding) => binding.setterName));
59599
- const effectInfos = collectEffectDependencyInfos(componentBody, setterNames);
60266
+ const effectInfos = collectEffectDependencyInfos(componentBody, setterNames, context.scopes);
59600
60267
  const selfEchoValueNames = /* @__PURE__ */ new Set();
59601
60268
  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);
59602
60269
  const effectConsumedNames = /* @__PURE__ */ new Set();
59603
60270
  for (const effectInfo of effectInfos) for (const dependencyName of effectInfo.dependencyNames) if (!selfEchoValueNames.has(dependencyName)) effectConsumedNames.add(dependencyName);
59604
- for (const hookArgumentName of collectCustomHookArgumentNames(componentBody)) effectConsumedNames.add(hookArgumentName);
60271
+ for (const hookArgumentName of collectCustomHookArgumentNames(componentBody, context.scopes)) effectConsumedNames.add(hookArgumentName);
59605
60272
  for (const reachableName of expandTransitiveDependencies(effectConsumedNames, dependencyGraph)) renderReachableNames.add(reachableName);
59606
60273
  const calledSetterNames = /* @__PURE__ */ new Set();
59607
60274
  walkAst(componentBody, (child) => {
@@ -67502,7 +68169,7 @@ const declarationAwaitsGate = (declaration, context) => {
67502
68169
  if (!isNodeOfType(argument, "CallExpression")) continue;
67503
68170
  if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
67504
68171
  if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
67505
- const calleeName = getCalleeName$2(argument);
68172
+ const calleeName = getCalleeName$1(argument);
67506
68173
  if (!calleeName) continue;
67507
68174
  if (isAuthGuardName(calleeName)) return true;
67508
68175
  const [leadingToken] = tokenizeIdentifierWords(calleeName);
@@ -68142,7 +68809,7 @@ const walkServerFnChain = (outerNode) => {
68142
68809
  if (!isNodeOfType(outerNode.callee, "MemberExpression")) return result;
68143
68810
  let currentNode = stripParenExpression(outerNode.callee.object);
68144
68811
  while (isNodeOfType(currentNode, "CallExpression")) {
68145
- const calleeName = getCalleeName$2(currentNode);
68812
+ const calleeName = getCalleeName$1(currentNode);
68146
68813
  if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) {
68147
68814
  result.isServerFnChain = true;
68148
68815
  const optionsArgument = currentNode.arguments?.[0];