oxlint-plugin-react-doctor 0.7.9-dev.a6e4fb5 → 0.7.9-dev.ac392cb

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 +1030 -372
  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;
@@ -40439,6 +40910,17 @@ const DOM_MEASUREMENT_NAMES = new Set([
40439
40910
  "scrollHeight"
40440
40911
  ]);
40441
40912
  const MEASUREMENT_HELPER_CALLEE_PATTERN = /^(?:get|measure|read)\w*(?:Width|Height|Rect|Rects|Size|Bounds|Position)$/;
40913
+ const IMPERATIVE_DOM_MUTATION_NAMES = new Set([
40914
+ "blur",
40915
+ "focus",
40916
+ "restoreSelection",
40917
+ "scroll",
40918
+ "scrollBy",
40919
+ "scrollIntoView",
40920
+ "scrollTo",
40921
+ "setRangeText",
40922
+ "setSelectionRange"
40923
+ ]);
40442
40924
  const subtreeReadsDomMeasurement = (root) => {
40443
40925
  if (!root) return false;
40444
40926
  let found = false;
@@ -40457,29 +40939,66 @@ const subtreeReadsDomMeasurement = (root) => {
40457
40939
  });
40458
40940
  return found;
40459
40941
  };
40460
- const collectMeasuringFunctionNames = (program) => {
40942
+ const collectFunctionNamesMatchingBody = (program, matchesBody) => {
40461
40943
  const names = /* @__PURE__ */ new Set();
40462
40944
  walkAst(program, (child) => {
40463
40945
  if (isNodeOfType(child, "FunctionDeclaration")) {
40464
- if (child.id && isNodeOfType(child.id, "Identifier") && subtreeReadsDomMeasurement(child.body)) names.add(child.id.name);
40946
+ if (child.id && isNodeOfType(child.id, "Identifier") && matchesBody(child.body)) names.add(child.id.name);
40465
40947
  return;
40466
40948
  }
40467
40949
  if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier")) return;
40468
40950
  let functionValue = child.init;
40469
40951
  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);
40952
+ if (functionValue && isFunctionLike$1(functionValue) && matchesBody(functionValue.body)) names.add(child.id.name);
40471
40953
  });
40472
40954
  return names;
40473
40955
  };
40474
- const callsAnyName = (root, names) => {
40475
- if (!root || names.size === 0) return false;
40956
+ const collectMeasuringFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeReadsDomMeasurement);
40957
+ const subtreeMutatesDomImperatively = (root) => {
40958
+ if (!root || isFunctionLike$1(root)) return false;
40476
40959
  let found = false;
40477
40960
  walkAst(root, (child) => {
40478
40961
  if (found) return false;
40962
+ if (child !== root && isFunctionLike$1(child)) return false;
40963
+ if (!isNodeOfType(child, "CallExpression")) return;
40964
+ const callee = stripParenExpression(child.callee);
40965
+ const propertyName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
40966
+ if (propertyName !== null && IMPERATIVE_DOM_MUTATION_NAMES.has(propertyName)) {
40967
+ found = true;
40968
+ return false;
40969
+ }
40970
+ });
40971
+ return found;
40972
+ };
40973
+ const collectImperativeDomFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeMutatesDomImperatively);
40974
+ const callsAnyName = (root, names, shouldSkipNestedFunctions = false) => {
40975
+ if (!root || names.size === 0 || shouldSkipNestedFunctions && isFunctionLike$1(root)) return false;
40976
+ let found = false;
40977
+ walkAst(root, (child) => {
40978
+ if (found) return false;
40979
+ if (shouldSkipNestedFunctions && child !== root && isFunctionLike$1(child)) return false;
40479
40980
  if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && names.has(child.callee.name)) found = true;
40480
40981
  });
40481
40982
  return found;
40482
40983
  };
40984
+ const isFollowedByImperativeDomMutation = (call, imperativeDomFunctionNames) => {
40985
+ let statement = call;
40986
+ let parent = statement.parent;
40987
+ while (parent) {
40988
+ const statements = isNodeOfType(parent, "BlockStatement") || isNodeOfType(parent, "Program") || isNodeOfType(parent, "StaticBlock") ? parent.body : isNodeOfType(parent, "SwitchCase") ? parent.consequent : null;
40989
+ if (statements) {
40990
+ const statementIndex = statements.findIndex((siblingStatement) => siblingStatement === statement);
40991
+ if (statementIndex >= 0) {
40992
+ const nextStatement = statements[statementIndex + 1];
40993
+ return subtreeMutatesDomImperatively(nextStatement) || callsAnyName(nextStatement, imperativeDomFunctionNames, true);
40994
+ }
40995
+ }
40996
+ if (isFunctionLike$1(parent) || parent.type.endsWith("Statement") && !isNodeOfType(parent, "ExpressionStatement")) return false;
40997
+ statement = parent;
40998
+ parent = parent.parent;
40999
+ }
41000
+ return false;
41001
+ };
40483
41002
  const isInsideStartViewTransition = (node) => {
40484
41003
  let cursor = node.parent;
40485
41004
  while (cursor) {
@@ -40520,11 +41039,12 @@ const importsImperativeDomLibrary = (program) => {
40520
41039
  };
40521
41040
  const hasExemptFlushSyncCall = (program, localName) => {
40522
41041
  const measuringFunctionNames = collectMeasuringFunctionNames(program);
41042
+ const imperativeDomFunctionNames = collectImperativeDomFunctionNames(program);
40523
41043
  let exempt = false;
40524
41044
  walkAst(program, (child) => {
40525
41045
  if (exempt) return false;
40526
41046
  if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "Identifier") || child.callee.name !== localName) return;
40527
- if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames)) {
41047
+ if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames) || isFollowedByImperativeDomMutation(child, imperativeDomFunctionNames)) {
40528
41048
  exempt = true;
40529
41049
  return false;
40530
41050
  }
@@ -41835,7 +42355,7 @@ const noInitializeState = defineRule({
41835
42355
  tags: ["test-noise"],
41836
42356
  recommendation: "Pass the initial value directly to useState() instead of setting it from a mount-only useEffect. For SSR hydration, prefer useSyncExternalStore().",
41837
42357
  create: (context) => ({ CallExpression(node) {
41838
- if (!isUseEffect(node)) return;
42358
+ if (!isReactHookCall(node, "useEffect", context.scopes)) return;
41839
42359
  const dependencies = node.arguments?.[1];
41840
42360
  if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
41841
42361
  const analysis = getProgramAnalysis(node);
@@ -43447,7 +43967,7 @@ const noMirrorPropEffect = defineRule({
43447
43967
  const setterElement = elements[1];
43448
43968
  if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
43449
43969
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
43450
- if (!isHookCall$2(declarator.init, "useState")) continue;
43970
+ if (!isReactHookCall(declarator.init, "useState", context.scopes)) continue;
43451
43971
  const initializer = declarator.init.arguments?.[0];
43452
43972
  if (!initializer) continue;
43453
43973
  const propRootName = getPropRootName(initializer, propNames);
@@ -43465,7 +43985,7 @@ const noMirrorPropEffect = defineRule({
43465
43985
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
43466
43986
  const effectCall = unwrapDiscardedExpression(statement);
43467
43987
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
43468
- if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
43988
+ if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
43469
43989
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
43470
43990
  const depsNode = effectCall.arguments[1];
43471
43991
  if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
@@ -43874,7 +44394,7 @@ const noMultiComp = defineRule({
43874
44394
  });
43875
44395
  //#endregion
43876
44396
  //#region src/plugin/rules/state-and-effects/no-mutable-in-deps.ts
43877
- const collectUseRefBindingNames = (componentBody) => {
44397
+ const collectUseRefBindingNames = (componentBody, scopes) => {
43878
44398
  const useRefBindings = /* @__PURE__ */ new Set();
43879
44399
  if (!isNodeOfType(componentBody, "BlockStatement")) return useRefBindings;
43880
44400
  for (const statement of componentBody.body ?? []) {
@@ -43882,7 +44402,7 @@ const collectUseRefBindingNames = (componentBody) => {
43882
44402
  for (const declarator of statement.declarations ?? []) {
43883
44403
  if (!isNodeOfType(declarator.id, "Identifier")) continue;
43884
44404
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
43885
- if (!isHookCall$2(declarator.init, "useRef")) continue;
44405
+ if (!isReactHookCall(declarator.init, "useRef", scopes)) continue;
43886
44406
  useRefBindings.add(declarator.id.name);
43887
44407
  }
43888
44408
  }
@@ -43917,12 +44437,12 @@ const noMutableInDeps = defineRule({
43917
44437
  create: (context) => {
43918
44438
  const checkComponent = (componentBody, componentParams = []) => {
43919
44439
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
43920
- const useRefBindingNames = collectUseRefBindingNames(componentBody);
44440
+ const useRefBindingNames = collectUseRefBindingNames(componentBody, context.scopes);
43921
44441
  const localBindingNames = collectLocalBindingNames(componentBody);
43922
44442
  for (const param of componentParams) collectPatternNames(param, localBindingNames);
43923
44443
  walkAst(componentBody, (child) => {
43924
44444
  if (!isNodeOfType(child, "CallExpression")) return;
43925
- if (!isHookCall$2(child, HOOKS_WITH_DEPS)) return;
44445
+ if (!isReactHookCall(child, HOOKS_WITH_DEPS, context.scopes)) return;
43926
44446
  if ((child.arguments?.length ?? 0) < 2) return;
43927
44447
  const depsNode = child.arguments[1];
43928
44448
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -45752,6 +46272,7 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
45752
46272
  "useMatchMedia",
45753
46273
  "useMediaJobProgress",
45754
46274
  "useMediaQuery",
46275
+ "useMediaQueryState",
45755
46276
  "useResizeObserver",
45756
46277
  "useVisibility",
45757
46278
  "useWindowSize"
@@ -45788,9 +46309,30 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
45788
46309
  if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
45789
46310
  return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
45790
46311
  };
45791
- const isExternalSubscriptionHookRef = (ref) => {
46312
+ const getLocalHookExternalStateProof = (analysis, ref) => {
46313
+ let hookFunction = resolveToFunction(ref);
46314
+ if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
46315
+ const definitionNode = definition.node;
46316
+ if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
46317
+ const initializer = stripParenExpression(definitionNode.init);
46318
+ if (!isNodeOfType(initializer, "CallExpression")) continue;
46319
+ const callee = stripParenExpression(initializer.callee);
46320
+ if (!isNodeOfType(callee, "Identifier")) continue;
46321
+ const calleeReference = getRef(analysis, callee);
46322
+ if (!calleeReference) continue;
46323
+ hookFunction = resolveToFunction(calleeReference);
46324
+ if (hookFunction) break;
46325
+ }
46326
+ if (!hookFunction) return null;
46327
+ const returnedReferences = collectFunctionReturnStatements(hookFunction).flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
46328
+ if (returnedReferences.length === 0) return null;
46329
+ return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
46330
+ };
46331
+ const isExternalSubscriptionHookRef = (analysis, ref) => {
45792
46332
  const identifier = ref.identifier;
45793
46333
  if (!isNodeOfType(identifier, "Identifier")) return false;
46334
+ const localHookProof = getLocalHookExternalStateProof(analysis, ref);
46335
+ if (localHookProof !== null) return localHookProof;
45794
46336
  if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
45795
46337
  return Boolean(ref.resolved?.defs.some((def) => {
45796
46338
  const node = def.node;
@@ -45813,16 +46355,10 @@ const noPassDataToParent = defineRule({
45813
46355
  tags: ["test-noise"],
45814
46356
  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",
45815
46357
  create: (context) => {
45816
- const isReactUseRefCall = (node) => isReactApiCall(node, "useRef", context.scopes, {
45817
- allowGlobalReactNamespace: true,
45818
- allowUnboundBareCalls: true
45819
- });
45820
- const isReactUseEffectCall = (node) => isReactApiCall(node, "useEffect", context.scopes, {
45821
- allowGlobalReactNamespace: true,
45822
- allowUnboundBareCalls: true
45823
- });
46358
+ const isReactUseRefCall = (node) => isReactHookCall(node, "useRef", context.scopes);
46359
+ const isReactUseEffectCall = (node) => isReactHookCall(node, "useEffect", context.scopes);
45824
46360
  return { CallExpression(node) {
45825
- if (!isUseEffect(node)) return;
46361
+ if (!isReactUseEffectCall(node)) return;
45826
46362
  const analysis = getProgramAnalysis(node);
45827
46363
  if (!analysis) return;
45828
46364
  if (hasCleanup(analysis, node)) return;
@@ -45874,11 +46410,11 @@ const noPassDataToParent = defineRule({
45874
46410
  if (argumentRef && resolveToFunction(argumentRef)) return [];
45875
46411
  }
45876
46412
  return getDownstreamRefs(analysis, argument);
45877
- }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
46413
+ }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) || isExternalSubscriptionHookRef(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
45878
46414
  if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
45879
46415
  if (!argsUpstreamRefs.some((argRef) => {
45880
46416
  if (isUseStateIdentifier(argRef.identifier)) return false;
45881
- if (isExternalSubscriptionHookRef(argRef)) return false;
46417
+ if (isExternalSubscriptionHookRef(analysis, argRef)) return false;
45882
46418
  if (isProp(analysis, argRef)) return false;
45883
46419
  if (isUseRefIdentifier(argRef.identifier)) return false;
45884
46420
  if (isRefCurrent(argRef)) return false;
@@ -46109,7 +46645,7 @@ const noPassLiveStateToParent = defineRule({
46109
46645
  tags: ["test-noise"],
46110
46646
  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",
46111
46647
  create: (context) => ({ CallExpression(node) {
46112
- if (!isUseEffect(node)) return;
46648
+ if (!isReactHookCall(node, "useEffect", context.scopes)) return;
46113
46649
  const analysis = getProgramAnalysis(node);
46114
46650
  if (!analysis) return;
46115
46651
  const effectFnRefs = getEffectFnRefs(analysis, node);
@@ -46524,7 +47060,7 @@ const hasPreviousValueDep = (effectNode, depElements) => {
46524
47060
  if (!isNodeOfType(element, "Identifier")) continue;
46525
47061
  const binding = findVariableInitializer(effectNode, element.name);
46526
47062
  if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) continue;
46527
- const calleeName = getCalleeName$2(binding.initializer);
47063
+ const calleeName = getCalleeName$1(binding.initializer);
46528
47064
  if (calleeName && PREVIOUS_VALUE_HOOK_PATTERN.test(calleeName)) return true;
46529
47065
  }
46530
47066
  return false;
@@ -46546,7 +47082,7 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
46546
47082
  if (!isNodeOfType(receiver, "Identifier")) return null;
46547
47083
  const binding = findVariableInitializer(callExpression, receiver.name);
46548
47084
  if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
46549
- if (getCalleeName$2(binding.initializer) !== "useRef") return null;
47085
+ if (getCalleeName$1(binding.initializer) !== "useRef") return null;
46550
47086
  const callbackArgument = binding.initializer.arguments?.[0];
46551
47087
  if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
46552
47088
  return isPropName(callbackArgument.name) ? callbackArgument.name : null;
@@ -46578,7 +47114,7 @@ const noPropCallbackInEffect = defineRule({
46578
47114
  return {
46579
47115
  ...propStackTracker.visitors,
46580
47116
  CallExpression(node) {
46581
- if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
47117
+ if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes) || (node.arguments?.length ?? 0) < 2) return;
46582
47118
  const callback = getEffectCallback(node);
46583
47119
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
46584
47120
  const depsNode = node.arguments[1];
@@ -48471,7 +49007,7 @@ const noResetAllStateOnPropChange = defineRule({
48471
49007
  tags: ["test-noise"],
48472
49008
  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",
48473
49009
  create: (context) => ({ CallExpression(node) {
48474
- if (!isUseEffect(node)) return;
49010
+ if (!isReactHookCall(node, "useEffect", context.scopes)) return;
48475
49011
  const analysis = getProgramAnalysis(node);
48476
49012
  if (!analysis) return;
48477
49013
  const effectFnRefs = getEffectFnRefs(analysis, node);
@@ -48740,7 +49276,7 @@ const isTanStackServerFnHandlerCall = (node) => {
48740
49276
  if (node.callee.property.name !== "handler") return false;
48741
49277
  let currentNode = node.callee.object;
48742
49278
  while (isNodeOfType(currentNode, "CallExpression")) {
48743
- const calleeName = getCalleeName$2(currentNode);
49279
+ const calleeName = getCalleeName$1(currentNode);
48744
49280
  if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) return true;
48745
49281
  if (!isNodeOfType(currentNode.callee, "MemberExpression")) return false;
48746
49282
  currentNode = currentNode.callee.object;
@@ -49241,7 +49777,7 @@ const noSelfUpdatingEffect = defineRule({
49241
49777
  create: (context) => {
49242
49778
  const checkFunctionScope = (functionBody) => {
49243
49779
  if (!functionBody || !isNodeOfType(functionBody, "BlockStatement")) return;
49244
- const useStateBindings = collectUseStateBindings(functionBody);
49780
+ const useStateBindings = collectUseStateBindings(functionBody, context.scopes);
49245
49781
  if (useStateBindings.length === 0) return;
49246
49782
  const setterNameToStateName = /* @__PURE__ */ new Map();
49247
49783
  for (const binding of useStateBindings) setterNameToStateName.set(binding.setterName, binding.valueName);
@@ -49250,7 +49786,7 @@ const noSelfUpdatingEffect = defineRule({
49250
49786
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
49251
49787
  const effectCall = unwrapDiscardedExpression(statement);
49252
49788
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
49253
- if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
49789
+ if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
49254
49790
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
49255
49791
  const dependencyStateNames = collectDependencyStateNames(effectCall.arguments[1]);
49256
49792
  if (dependencyStateNames.size === 0) continue;
@@ -49336,7 +49872,7 @@ const noSetStateInRender = defineRule({
49336
49872
  create: (context) => {
49337
49873
  const checkComponent = (componentBody) => {
49338
49874
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
49339
- const setterNames = new Set(collectUseStateBindings(componentBody).map((binding) => binding.setterName));
49875
+ const setterNames = new Set(collectUseStateBindings(componentBody, context.scopes).map((binding) => binding.setterName));
49340
49876
  if (setterNames.size === 0) return;
49341
49877
  for (const statement of componentBody.body ?? []) {
49342
49878
  const setterCall = isUnconditionalSetterCallStatement(statement, setterNames);
@@ -49585,10 +50121,10 @@ const collectTimerRefUsageFacts = (ownerScope, refName) => {
49585
50121
  });
49586
50122
  return facts;
49587
50123
  };
49588
- const isEffectCallbackFunction = (functionNode) => {
50124
+ const isEffectCallbackFunction = (functionNode, scopes) => {
49589
50125
  const parent = functionNode.parent;
49590
50126
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
49591
- return isHookCall$2(parent, EFFECT_HOOK_NAMES$1) && getEffectCallback(parent) === functionNode;
50127
+ return isReactHookCall(parent, EFFECT_HOOK_NAMES$1, scopes) && getEffectCallback(parent) === functionNode;
49592
50128
  };
49593
50129
  const doesEffectCallbackReturnName = (effectCallback, name) => {
49594
50130
  if (!isFunctionLike$1(effectCallback)) return false;
@@ -49607,24 +50143,24 @@ const isFunctionReturnedFromEffectCallback = (functionNode, effectCallback) => {
49607
50143
  const cleanupBindingName = getFunctionBindingName$1(functionNode);
49608
50144
  return cleanupBindingName !== null && doesEffectCallbackReturnName(effectCallback, cleanupBindingName);
49609
50145
  };
49610
- const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
50146
+ const isReturnedFromAnyEffectInScope = (functionNode, ownerScope, scopes) => {
49611
50147
  const cleanupBindingName = getFunctionBindingName$1(functionNode);
49612
50148
  if (cleanupBindingName === null) return false;
49613
50149
  let isReturnedFromEffect = false;
49614
50150
  walkAst(ownerScope, (child) => {
49615
50151
  if (isReturnedFromEffect) return false;
49616
- if (!isNodeOfType(child, "CallExpression") || !isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
50152
+ if (!isNodeOfType(child, "CallExpression") || !isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) return;
49617
50153
  const effectCallback = getEffectCallback(child);
49618
50154
  if (effectCallback && doesEffectCallbackReturnName(effectCallback, cleanupBindingName)) isReturnedFromEffect = true;
49619
50155
  });
49620
50156
  return isReturnedFromEffect;
49621
50157
  };
49622
- const isInsideEffectCleanupReturn = (node, ownerScope) => {
50158
+ const isInsideEffectCleanupReturn = (node, ownerScope, scopes) => {
49623
50159
  let functionNode = findEnclosingFunction$1(node);
49624
50160
  while (functionNode) {
49625
50161
  const outerFunction = findEnclosingFunction$1(functionNode);
49626
- if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
49627
- if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
50162
+ if (outerFunction && isEffectCallbackFunction(outerFunction, scopes) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
50163
+ if (isReturnedFromAnyEffectInScope(functionNode, ownerScope, scopes)) return true;
49628
50164
  functionNode = outerFunction;
49629
50165
  }
49630
50166
  return false;
@@ -49660,10 +50196,10 @@ const noStaleTimerRef = defineRule({
49660
50196
  if (isShadowedTimerGlobal(node)) return;
49661
50197
  const { clearCalleeName, refName } = clearCall;
49662
50198
  const refBinding = findVariableInitializer(node, refName);
49663
- if (!refBinding?.initializer || !isHookCall$2(refBinding.initializer, "useRef")) return;
50199
+ if (!refBinding?.initializer || !isReactHookCall(refBinding.initializer, "useRef", context.scopes)) return;
49664
50200
  const usageFacts = collectTimerRefUsageFacts(refBinding.scopeOwner, refName);
49665
50201
  if (!usageFacts.holdsScheduledTimerId || !usageFacts.hasPendingSignalRead) return;
49666
- if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner)) return;
50202
+ if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner, context.scopes)) return;
49667
50203
  if (hasRefCurrentReassignmentAfterClear(node, refName)) return;
49668
50204
  context.report({
49669
50205
  node,
@@ -55467,7 +56003,7 @@ const classifyCallableReadsInsideEffect = (callableIdentifier, effectCallback, c
55467
56003
  allReadsAreInSubHandlers = false;
55468
56004
  return;
55469
56005
  }
55470
- if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$2(subHandlerCall);
56006
+ if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$1(subHandlerCall);
55471
56007
  });
55472
56008
  return {
55473
56009
  hasAnyRead,
@@ -55490,7 +56026,7 @@ const preferUseEffectEvent = defineRule({
55490
56026
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
55491
56027
  const effectCall = statement.expression;
55492
56028
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
55493
- if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
56029
+ if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
55494
56030
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
55495
56031
  const depsNode = effectCall.arguments[1];
55496
56032
  if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
@@ -55522,11 +56058,11 @@ const preferUseEffectEvent = defineRule({
55522
56058
  });
55523
56059
  //#endregion
55524
56060
  //#region src/plugin/rules/state-and-effects/prefer-use-sync-external-store.ts
55525
- const findUseEffectsInComponent = (componentBody) => {
56061
+ const findUseEffectsInComponent = (componentBody, scopes) => {
55526
56062
  const effectCalls = [];
55527
56063
  if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
55528
56064
  for (const statement of componentBody.body ?? []) walkAst(statement, (child) => {
55529
- if (isNodeOfType(child, "CallExpression") && isHookCall$2(child, EFFECT_HOOK_NAMES$1)) effectCalls.push(child);
56065
+ if (isNodeOfType(child, "CallExpression") && isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) effectCalls.push(child);
55530
56066
  });
55531
56067
  return effectCalls;
55532
56068
  };
@@ -55729,7 +56265,7 @@ const preferUseSyncExternalStore = defineRule({
55729
56265
  };
55730
56266
  const checkComponent = (componentBody) => {
55731
56267
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
55732
- const useStateBindings = collectUseStateBindings(componentBody);
56268
+ const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
55733
56269
  if (useStateBindings.length === 0) return;
55734
56270
  const useStateInitializerByValueName = /* @__PURE__ */ new Map();
55735
56271
  for (const binding of useStateBindings) {
@@ -55742,7 +56278,7 @@ const preferUseSyncExternalStore = defineRule({
55742
56278
  }
55743
56279
  const setterNameToValueName = /* @__PURE__ */ new Map();
55744
56280
  for (const binding of useStateBindings) setterNameToValueName.set(binding.setterName, binding.valueName);
55745
- for (const effectCall of findUseEffectsInComponent(componentBody)) {
56281
+ for (const effectCall of findUseEffectsInComponent(componentBody, context.scopes)) {
55746
56282
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
55747
56283
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
55748
56284
  const depsNode = effectCall.arguments[1];
@@ -55784,7 +56320,7 @@ const preferUseSyncExternalStore = defineRule({
55784
56320
  })).filter((candidate) => candidate.storeName !== null);
55785
56321
  if (snapshotBindings.length === 0) return;
55786
56322
  const reportedDeclarators = /* @__PURE__ */ new Set();
55787
- for (const effectCall of findUseEffectsInComponent(componentBody)) {
56323
+ for (const effectCall of findUseEffectsInComponent(componentBody, context.scopes)) {
55788
56324
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
55789
56325
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
55790
56326
  const depsNode = effectCall.arguments[1];
@@ -55881,7 +56417,7 @@ const preferUseReducer = defineRule({
55881
56417
  create: (context) => {
55882
56418
  const reportCoUpdatedUseState = (body, componentName) => {
55883
56419
  if (!isNodeOfType(body, "BlockStatement")) return;
55884
- const bindings = collectUseStateBindings(body);
56420
+ const bindings = collectUseStateBindings(body, context.scopes);
55885
56421
  const setterNames = new Set(bindings.map((binding) => binding.setterName));
55886
56422
  if (setterNames.size < 5) return;
55887
56423
  const coUpdatedCount = findLargestCoUpdatedSetterGroup(body, setterNames, new Map(bindings.map((binding) => {
@@ -56097,7 +56633,7 @@ const QUERY_READ_METHOD_NAMES = new Set([
56097
56633
  ]);
56098
56634
  const isQueryCacheSourceCall = (initializer) => {
56099
56635
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
56100
- const hookName = getCalleeName$2(initializer);
56636
+ const hookName = getCalleeName$1(initializer);
56101
56637
  if (!hookName) return false;
56102
56638
  return hookName === "useQueryClient" || TRPC_UTILS_HOOK_PATTERN.test(hookName);
56103
56639
  };
@@ -56229,7 +56765,7 @@ const queryMutationMissingInvalidation = defineRule({
56229
56765
  },
56230
56766
  CallExpression(node) {
56231
56767
  if (!hasQueryReadUsage) {
56232
- const callName = getCalleeName$2(node);
56768
+ const callName = getCalleeName$1(node);
56233
56769
  if (callName && (QUERY_READ_HOOK_NAMES.has(callName) || QUERY_READ_METHOD_NAMES.has(callName) || TRPC_UTILS_HOOK_PATTERN.test(callName))) hasQueryReadUsage = true;
56234
56770
  }
56235
56771
  const calleeName = isNodeOfType(node.callee, "Identifier") ? node.callee.name : null;
@@ -57892,6 +58428,125 @@ const ASYNC_DATA_CALLEE_NAMES = new Set([
57892
58428
  const RESOURCE_LOAD_EVENT_ATTRIBUTE_PATTERN = /^on(?:Load|Error|Abort|Progress|CanPlay|Stalled|Suspend|Waiting|Ended)/;
57893
58429
  const JSX_EVENT_HANDLER_ATTRIBUTE_PATTERN = /^on[A-Z]/;
57894
58430
  const REDUX_DISPATCH_HOOK_PATTERN = /^use\w*Dispatch$/;
58431
+ const FILE_READER_READ_METHOD_NAMES = new Set([
58432
+ "readAsArrayBuffer",
58433
+ "readAsBinaryString",
58434
+ "readAsDataURL",
58435
+ "readAsText"
58436
+ ]);
58437
+ const isGlobalFileReaderConstruction = (expression, context) => {
58438
+ if (!expression) return false;
58439
+ const unwrappedExpression = stripParenExpression(expression);
58440
+ if (!isNodeOfType(unwrappedExpression, "NewExpression") || !isNodeOfType(unwrappedExpression.callee, "Identifier")) return false;
58441
+ return unwrappedExpression.callee.name === "FileReader" && context.scopes.isGlobalReference(unwrappedExpression.callee);
58442
+ };
58443
+ const getFileReaderOriginStartBefore = (readerSymbol, readCall, context) => {
58444
+ const readFunction = findEnclosingFunction$1(readCall);
58445
+ let latestValue = null;
58446
+ let latestStart = null;
58447
+ if (readerSymbol.initializer && findEnclosingFunction$1(readerSymbol.declarationNode) === readFunction && readerSymbol.declarationNode.range[0] < readCall.range[0]) {
58448
+ latestValue = readerSymbol.initializer;
58449
+ latestStart = readerSymbol.declarationNode.range[0];
58450
+ }
58451
+ for (const reference of readerSymbol.references) {
58452
+ if (reference.flag === "read" || reference.identifier.range[0] >= readCall.range[0] || latestStart !== null && reference.identifier.range[0] <= latestStart || findEnclosingFunction$1(reference.identifier) !== readFunction) continue;
58453
+ const assignment = reference.identifier.parent;
58454
+ if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== reference.identifier) continue;
58455
+ latestValue = assignment.right;
58456
+ latestStart = reference.identifier.range[0];
58457
+ }
58458
+ return isGlobalFileReaderConstruction(latestValue, context) ? latestStart : null;
58459
+ };
58460
+ const resolveLoadingCompletionFunction = (expression, context) => {
58461
+ const directFunction = resolveExactLocalFunction(expression, context.scopes);
58462
+ if (directFunction) return directFunction;
58463
+ const unwrappedExpression = stripParenExpression(expression);
58464
+ if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
58465
+ const symbol = context.scopes.symbolFor(unwrappedExpression);
58466
+ const initializer = symbol ? getDirectUnreassignedInitializer(symbol) : null;
58467
+ if (!initializer || !isNodeOfType(initializer, "CallExpression") || !isReactApiCall(initializer, "useCallback", context.scopes)) return null;
58468
+ const callback = initializer.arguments?.[0];
58469
+ return callback && isFunctionLike$1(callback) ? callback : null;
58470
+ };
58471
+ const isSetterBooleanCall = (node, setterSymbol, value, context) => {
58472
+ if (!isNodeOfType(node, "CallExpression")) return false;
58473
+ const callee = stripParenExpression(node.callee);
58474
+ const argument = node.arguments?.[0];
58475
+ const unwrappedArgument = argument ? stripParenExpression(argument) : null;
58476
+ return Boolean(isNodeOfType(callee, "Identifier") && context.scopes.symbolFor(callee) === setterSymbol && unwrappedArgument && isNodeOfType(unwrappedArgument, "Literal") && unwrappedArgument.value === value);
58477
+ };
58478
+ const functionClearsLoadingState = (functionNode, setterSymbol, context, visitedFunctions) => {
58479
+ if (visitedFunctions.has(functionNode) || !isFunctionLike$1(functionNode)) return false;
58480
+ visitedFunctions.add(functionNode);
58481
+ let didClearLoadingState = false;
58482
+ walkAst(functionNode.body, (child) => {
58483
+ if (didClearLoadingState) return false;
58484
+ if (child !== functionNode.body && isFunctionLike$1(child)) return false;
58485
+ if (!isNodeOfType(child, "CallExpression")) return;
58486
+ if (isSetterBooleanCall(child, setterSymbol, false, context)) {
58487
+ didClearLoadingState = true;
58488
+ return false;
58489
+ }
58490
+ const helperFunction = resolveLoadingCompletionFunction(child.callee, context);
58491
+ if (helperFunction && functionClearsLoadingState(helperFunction, setterSymbol, context, visitedFunctions)) {
58492
+ didClearLoadingState = true;
58493
+ return false;
58494
+ }
58495
+ });
58496
+ return didClearLoadingState;
58497
+ };
58498
+ const getLatestFileReaderCallbackBefore = (readCall, readerSymbol, propertyName, originStart, context) => {
58499
+ const readFunction = findEnclosingFunction$1(readCall);
58500
+ if (!readFunction || !isFunctionLike$1(readFunction)) return null;
58501
+ let callback = null;
58502
+ let callbackStart = originStart;
58503
+ walkAst(readFunction.body, (child) => {
58504
+ if (child !== readFunction.body && isFunctionLike$1(child)) return false;
58505
+ 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;
58506
+ const receiver = stripParenExpression(child.left.object);
58507
+ if (!isNodeOfType(receiver, "Identifier") || context.scopes.symbolFor(receiver) !== readerSymbol) return;
58508
+ callback = child.right;
58509
+ callbackStart = child.range[0];
58510
+ });
58511
+ return callback;
58512
+ };
58513
+ const setterStartsLoadingBefore = (readCall, setterSymbol, context) => {
58514
+ const readFunction = findEnclosingFunction$1(readCall);
58515
+ if (!readFunction || !isFunctionLike$1(readFunction)) return false;
58516
+ let didStartLoading = false;
58517
+ walkAst(readFunction.body, (child) => {
58518
+ if (didStartLoading) return false;
58519
+ if (child !== readFunction.body && isFunctionLike$1(child)) return false;
58520
+ if (!isNodeOfType(child, "CallExpression") || child.range[0] >= readCall.range[0]) return;
58521
+ if (isSetterBooleanCall(child, setterSymbol, true, context)) {
58522
+ didStartLoading = true;
58523
+ return false;
58524
+ }
58525
+ });
58526
+ return didStartLoading;
58527
+ };
58528
+ const setterTracksFileReader = (functionBody, setterSymbol, context) => {
58529
+ let didFindFileReaderLifecycle = false;
58530
+ walkAst(functionBody, (child) => {
58531
+ if (didFindFileReaderLifecycle) return false;
58532
+ if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || !FILE_READER_READ_METHOD_NAMES.has(getStaticPropertyName(child.callee) ?? "")) return;
58533
+ const receiver = stripParenExpression(child.callee.object);
58534
+ if (!isNodeOfType(receiver, "Identifier")) return;
58535
+ const readerSymbol = context.scopes.symbolFor(receiver);
58536
+ if (!readerSymbol) return;
58537
+ const originStart = getFileReaderOriginStartBefore(readerSymbol, child, context);
58538
+ if (originStart === null || !setterStartsLoadingBefore(child, setterSymbol, context)) return;
58539
+ const loadCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onload", originStart, context);
58540
+ const errorCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onerror", originStart, context);
58541
+ const loadFunction = loadCallback ? resolveLoadingCompletionFunction(loadCallback, context) : null;
58542
+ const errorFunction = errorCallback ? resolveLoadingCompletionFunction(errorCallback, context) : null;
58543
+ if (loadFunction && errorFunction && functionClearsLoadingState(loadFunction, setterSymbol, context, /* @__PURE__ */ new Set()) && functionClearsLoadingState(errorFunction, setterSymbol, context, /* @__PURE__ */ new Set())) {
58544
+ didFindFileReaderLifecycle = true;
58545
+ return false;
58546
+ }
58547
+ });
58548
+ return didFindFileReaderLifecycle;
58549
+ };
57895
58550
  const hasAsyncLoadingWork = (fnBody, setterName) => {
57896
58551
  let found = false;
57897
58552
  walkAst(fnBody, (child) => {
@@ -58065,6 +58720,8 @@ const renderingUsetransitionLoading = defineRule({
58065
58720
  const fnBody = enclosingFunctionBody(node);
58066
58721
  if (fnBody && hasAsyncLoadingWork(fnBody, setterName)) return;
58067
58722
  if (fnBody && setterName) {
58723
+ const setterSymbol = isNodeOfType(secondBinding, "Identifier") ? context.scopes.symbolFor(secondBinding) : null;
58724
+ if (setterSymbol && setterTracksFileReader(fnBody, setterSymbol, context)) return;
58068
58725
  if (setterEscapes(fnBody, setterName, node)) return;
58069
58726
  if (setterCalledAlongsideAsyncSignal(fnBody, setterName)) return;
58070
58727
  if (setterCalledInEventListenerHandler(fnBody, setterName)) return;
@@ -58396,7 +59053,7 @@ const rerenderDependencies = defineRule({
58396
59053
  severity: "error",
58397
59054
  recommendation: "Move it into a useMemo, useRef, or a constant outside the component so it stays the same between renders.",
58398
59055
  create: (context) => ({ CallExpression(node) {
58399
- if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
59056
+ if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes) || node.arguments.length < 2) return;
58400
59057
  const depsNode = node.arguments[1];
58401
59058
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
58402
59059
  for (const element of depsNode.elements ?? []) {
@@ -58651,7 +59308,7 @@ const rerenderLazyRefInit = defineRule({
58651
59308
  category: "Performance",
58652
59309
  recommendation: "Initialize the ref lazily so expensive values are not rebuilt and discarded on every render.",
58653
59310
  create: (context) => ({ CallExpression(node) {
58654
- if (!isHookCall$2(node, "useRef") || !node.arguments?.length) return;
59311
+ if (!isReactHookCall(node, "useRef", context.scopes) || !node.arguments?.length) return;
58655
59312
  const initializer = stripParenExpression(node.arguments[0]);
58656
59313
  const isPlainCall = isNodeOfType(initializer, "CallExpression");
58657
59314
  const isNewCall = isNodeOfType(initializer, "NewExpression");
@@ -58715,7 +59372,7 @@ const rerenderLazyStateInit = defineRule({
58715
59372
  category: "Performance",
58716
59373
  recommendation: "Wrap expensive initial state in an arrow function so the initializer does not rerun and get thrown away on every render.",
58717
59374
  create: (context) => ({ CallExpression(node) {
58718
- if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
59375
+ if (!isReactHookCall(node, "useState", context.scopes) || !node.arguments?.length) return;
58719
59376
  const initializer = findEagerInitializerCall(node.arguments[0]);
58720
59377
  if (!initializer) return;
58721
59378
  const isConstructor = isNodeOfType(initializer, "NewExpression");
@@ -59478,11 +60135,11 @@ const isInsideConditionTest = (identifier, stopAt) => {
59478
60135
  }
59479
60136
  return false;
59480
60137
  };
59481
- const collectEffectDependencyInfos = (componentBody, setterNames) => {
60138
+ const collectEffectDependencyInfos = (componentBody, setterNames, scopes) => {
59482
60139
  const effectInfos = [];
59483
60140
  walkAst(componentBody, (child) => {
59484
60141
  if (!isNodeOfType(child, "CallExpression")) return;
59485
- if (!isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
60142
+ if (!isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) return;
59486
60143
  const dependencyNames = /* @__PURE__ */ new Set();
59487
60144
  for (const argument of child.arguments ?? []) {
59488
60145
  if (!isNodeOfType(argument, "ArrayExpression")) continue;
@@ -59538,13 +60195,14 @@ const collectEffectDependencyInfos = (componentBody, setterNames) => {
59538
60195
  });
59539
60196
  return effectInfos;
59540
60197
  };
59541
- const collectCustomHookArgumentNames = (componentBody) => {
60198
+ const collectCustomHookArgumentNames = (componentBody, scopes) => {
59542
60199
  const argumentNames = /* @__PURE__ */ new Set();
59543
60200
  walkAst(componentBody, (child) => {
59544
60201
  if (!isNodeOfType(child, "CallExpression")) return;
59545
60202
  if (!isNodeOfType(child.callee, "Identifier")) return;
59546
60203
  const calleeName = child.callee.name;
59547
60204
  if (!isReactHookName(calleeName)) return;
60205
+ if (isReactHookCall(child, BUILTIN_HOOK_NAMES, scopes)) return;
59548
60206
  if (BUILTIN_HOOK_NAMES.has(calleeName)) return;
59549
60207
  if (EFFECT_HOOK_NAMES$1.has(calleeName)) return;
59550
60208
  for (const argument of child.arguments ?? []) walkAst(argument, (argumentNode) => {
@@ -59585,21 +60243,21 @@ const rerenderStateOnlyInHandlers = defineRule({
59585
60243
  create: (context) => {
59586
60244
  const checkComponent = (componentBody) => {
59587
60245
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
59588
- const bindings = collectUseStateBindings(componentBody);
60246
+ const bindings = collectUseStateBindings(componentBody, context.scopes);
59589
60247
  if (bindings.length === 0) return;
59590
60248
  if (collectRenderReachableExpressions(componentBody).length === 0) return;
59591
- const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
60249
+ const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
59592
60250
  const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
59593
- const directRenderNames = collectRenderReachableNames(componentBody, eventHandlerReferenceNames);
60251
+ const directRenderNames = collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames);
59594
60252
  if (hasRenderPhaseNonHookCall(componentBody)) for (const voidMarkedName of collectTopLevelVoidMarkedNames(componentBody)) directRenderNames.add(voidMarkedName);
59595
60253
  const renderReachableNames = expandTransitiveDependencies(directRenderNames, dependencyGraph);
59596
60254
  const setterNames = new Set(bindings.map((binding) => binding.setterName));
59597
- const effectInfos = collectEffectDependencyInfos(componentBody, setterNames);
60255
+ const effectInfos = collectEffectDependencyInfos(componentBody, setterNames, context.scopes);
59598
60256
  const selfEchoValueNames = /* @__PURE__ */ new Set();
59599
60257
  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);
59600
60258
  const effectConsumedNames = /* @__PURE__ */ new Set();
59601
60259
  for (const effectInfo of effectInfos) for (const dependencyName of effectInfo.dependencyNames) if (!selfEchoValueNames.has(dependencyName)) effectConsumedNames.add(dependencyName);
59602
- for (const hookArgumentName of collectCustomHookArgumentNames(componentBody)) effectConsumedNames.add(hookArgumentName);
60260
+ for (const hookArgumentName of collectCustomHookArgumentNames(componentBody, context.scopes)) effectConsumedNames.add(hookArgumentName);
59603
60261
  for (const reachableName of expandTransitiveDependencies(effectConsumedNames, dependencyGraph)) renderReachableNames.add(reachableName);
59604
60262
  const calledSetterNames = /* @__PURE__ */ new Set();
59605
60263
  walkAst(componentBody, (child) => {
@@ -67500,7 +68158,7 @@ const declarationAwaitsGate = (declaration, context) => {
67500
68158
  if (!isNodeOfType(argument, "CallExpression")) continue;
67501
68159
  if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
67502
68160
  if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
67503
- const calleeName = getCalleeName$2(argument);
68161
+ const calleeName = getCalleeName$1(argument);
67504
68162
  if (!calleeName) continue;
67505
68163
  if (isAuthGuardName(calleeName)) return true;
67506
68164
  const [leadingToken] = tokenizeIdentifierWords(calleeName);
@@ -68140,7 +68798,7 @@ const walkServerFnChain = (outerNode) => {
68140
68798
  if (!isNodeOfType(outerNode.callee, "MemberExpression")) return result;
68141
68799
  let currentNode = stripParenExpression(outerNode.callee.object);
68142
68800
  while (isNodeOfType(currentNode, "CallExpression")) {
68143
- const calleeName = getCalleeName$2(currentNode);
68801
+ const calleeName = getCalleeName$1(currentNode);
68144
68802
  if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) {
68145
68803
  result.isServerFnChain = true;
68146
68804
  const optionsArgument = currentNode.arguments?.[0];