oxlint-plugin-react-doctor 0.7.9-dev.2450582 → 0.7.9-dev.2979a9b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +0 -46
  2. package/dist/index.js +413 -1512
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -836,131 +836,22 @@ 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-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
- }
853
- return null;
854
- };
855
- //#endregion
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;
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;
862
844
  return null;
863
845
  };
864
846
  //#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 = {}) => {
847
+ //#region src/plugin/utils/is-hook-call.ts
848
+ const isHookCall$2 = (node, hookName) => {
934
849
  if (!isNodeOfType(node, "CallExpression")) return false;
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);
850
+ const calleeName = getCalleeName$2(node);
851
+ if (!calleeName) return false;
852
+ return typeof hookName === "string" ? calleeName === hookName : hookName.has(calleeName);
954
853
  };
955
854
  //#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
964
855
  //#region src/plugin/utils/is-ast-node.ts
965
856
  const isAstNode = (value) => value !== null && typeof value === "object" && "type" in value && typeof value.type === "string";
966
857
  //#endregion
@@ -1030,12 +921,12 @@ const collectChildComponentNames = (element, into) => {
1030
921
  into.add(name);
1031
922
  });
1032
923
  };
1033
- const countEffectHookCalls = (body, scopes) => {
924
+ const countEffectHookCalls = (body) => {
1034
925
  if (!body) return 0;
1035
926
  let count = 0;
1036
927
  walkAst(body, (child) => {
1037
928
  if (!isNodeOfType(child, "CallExpression")) return;
1038
- if (isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) count++;
929
+ if (isHookCall$2(child, EFFECT_HOOK_NAMES$1)) count++;
1039
930
  });
1040
931
  return count;
1041
932
  };
@@ -1061,11 +952,11 @@ const getComponentEffectIndex = (programRoot) => {
1061
952
  componentEffectIndexCache.set(programRoot, index);
1062
953
  return index;
1063
954
  };
1064
- const getSameFileComponentEffectCount = (programRoot, componentName, scopes) => {
955
+ const getSameFileComponentEffectCount = (programRoot, componentName) => {
1065
956
  const index = getComponentEffectIndex(programRoot);
1066
957
  const cachedCount = index.effectCountByName.get(componentName);
1067
958
  if (cachedCount !== void 0) return cachedCount;
1068
- const count = countEffectHookCalls(index.bodyByName.get(componentName) ?? null, scopes);
959
+ const count = countEffectHookCalls(index.bodyByName.get(componentName) ?? null);
1069
960
  index.effectCountByName.set(componentName, count);
1070
961
  return count;
1071
962
  };
@@ -1125,7 +1016,7 @@ const activityWrapsEffectHeavySubtree = defineRule({
1125
1016
  let totalEffects = 0;
1126
1017
  const effectfulChildren = [];
1127
1018
  for (const componentName of childComponentNames) {
1128
- const effectCount = getSameFileComponentEffectCount(programRoot, componentName, context.scopes);
1019
+ const effectCount = getSameFileComponentEffectCount(programRoot, componentName);
1129
1020
  if (effectCount === 0) continue;
1130
1021
  totalEffects += effectCount;
1131
1022
  effectfulChildren.push(`<${componentName}>`);
@@ -1316,14 +1207,6 @@ const findVariableInitializer = (referenceNode, bindingName) => {
1316
1207
  return best;
1317
1208
  };
1318
1209
  //#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
1327
1210
  //#region src/plugin/utils/is-function-like.ts
1328
1211
  /**
1329
1212
  * Type-guard for the three "function-like" ESTree node shapes:
@@ -1337,6 +1220,37 @@ const getCalleeName$1 = (node) => {
1337
1220
  */
1338
1221
  const isFunctionLike$1 = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")));
1339
1222
  //#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
1340
1254
  //#region src/plugin/utils/resolve-exact-local-function.ts
1341
1255
  const resolveExactLocalFunction = (expression, scopes) => {
1342
1256
  const unwrappedExpression = stripParenExpression(expression);
@@ -1382,17 +1296,9 @@ const getRootIdentifier$1 = (node, options) => {
1382
1296
  //#region src/plugin/utils/get-root-identifier-name.ts
1383
1297
  const getRootIdentifierName = (node, options) => getRootIdentifier$1(node, options)?.name ?? null;
1384
1298
  //#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
1393
1299
  //#region src/plugin/rules/state-and-effects/advanced-event-handler-refs.ts
1394
- const REACT_STABLE_HANDLER_HOOK_NAMES = new Set(["useCallback", "useEffectEvent"]);
1395
- const CUSTOM_STABLE_HANDLER_HOOK_NAMES = new Set([
1300
+ const STABLE_HANDLER_HOOK_NAMES = new Set([
1301
+ "useCallback",
1396
1302
  "useEffectEvent",
1397
1303
  "useEvent",
1398
1304
  "useEventCallback",
@@ -1401,21 +1307,21 @@ const CUSTOM_STABLE_HANDLER_HOOK_NAMES = new Set([
1401
1307
  ]);
1402
1308
  const THROTTLED_HANDLER_HOOK_PATTERN = /^use\w*(?:Throttle|Debounce)/i;
1403
1309
  const isThrottledHandlerHookCall = (callNode) => {
1404
- const calleeName = getCalleeName$1(callNode);
1310
+ const calleeName = getCalleeName$2(callNode);
1405
1311
  return calleeName !== null && THROTTLED_HANDLER_HOOK_PATTERN.test(calleeName);
1406
1312
  };
1407
- const isEmptyDepsUseMemoCall = (callNode, scopes) => {
1408
- if (!isReactHookCall(callNode, "useMemo", scopes)) return false;
1313
+ const isEmptyDepsUseMemoCall = (callNode) => {
1314
+ if (!isHookCall$2(callNode, "useMemo")) return false;
1409
1315
  const memoDepsNode = callNode.arguments?.[1];
1410
1316
  return isNodeOfType(memoDepsNode, "ArrayExpression") && (memoDepsNode.elements?.length ?? 0) === 0;
1411
1317
  };
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);
1318
+ const isStableHandlerInitializer = (initializer) => {
1319
+ if (isNodeOfType(initializer, "CallExpression")) return isHookCall$2(initializer, STABLE_HANDLER_HOOK_NAMES) || isEmptyDepsUseMemoCall(initializer) || isThrottledHandlerHookCall(initializer);
1414
1320
  return isNodeOfType(initializer, "MemberExpression") && isNodeOfType(initializer.property, "Identifier") && initializer.property.name === "current";
1415
1321
  };
1416
- const isStableRefReceiverDep = (referenceNode, receiverDepName, scopes) => {
1322
+ const isStableRefReceiverDep = (referenceNode, receiverDepName) => {
1417
1323
  const receiverBinding = findVariableInitializer(referenceNode, receiverDepName);
1418
- return Boolean(receiverBinding?.initializer && isReactHookCall(receiverBinding.initializer, "useRef", scopes));
1324
+ return Boolean(receiverBinding?.initializer && isHookCall$2(receiverBinding.initializer, "useRef"));
1419
1325
  };
1420
1326
  const advancedEventHandlerRefs = defineRule({
1421
1327
  id: "advanced-event-handler-refs",
@@ -1425,7 +1331,7 @@ const advancedEventHandlerRefs = defineRule({
1425
1331
  category: "Performance",
1426
1332
  recommendation: "Store the handler in a ref and have the listener read `handlerRef.current()`. The subscription stays put while the latest handler still runs.",
1427
1333
  create: (context) => ({ CallExpression(node) {
1428
- if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes)) return;
1334
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
1429
1335
  if ((node.arguments?.length ?? 0) < 2) return;
1430
1336
  const callback = getEffectCallback(node);
1431
1337
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
@@ -1449,8 +1355,8 @@ const advancedEventHandlerRefs = defineRule({
1449
1355
  });
1450
1356
  if (!registeredHandlerName) return;
1451
1357
  const handlerBinding = findVariableInitializer(node, registeredHandlerName);
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;
1358
+ if (handlerBinding?.initializer && isStableHandlerInitializer(handlerBinding.initializer)) return;
1359
+ if ([...depIdentifierNames].some((depName) => depName !== registeredHandlerName && subscriptionReceiverNames.has(depName) && !isStableRefReceiverDep(node, depName))) return;
1454
1360
  context.report({
1455
1361
  node,
1456
1362
  message: `useEffect re-adds the "${registeredHandlerName}" listener every time the handler changes.`
@@ -1937,6 +1843,15 @@ const flattenJsxName$1 = (node) => {
1937
1843
  return null;
1938
1844
  };
1939
1845
  //#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
1940
1855
  //#region src/plugin/utils/is-generated-image-renderer-call.ts
1941
1856
  const GENERATED_IMAGE_RENDERER_MODULES = [
1942
1857
  "next/og",
@@ -4944,6 +4859,23 @@ const containsDirectAwait = (node) => {
4944
4859
  return foundAwait;
4945
4860
  };
4946
4861
  //#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
4947
4879
  //#region src/plugin/utils/get-destructured-binding-property-name.ts
4948
4880
  const getDestructuredBindingPropertyName = (bindingIdentifier) => {
4949
4881
  let bindingNode = bindingIdentifier;
@@ -9008,6 +8940,65 @@ const hasVisibleBindingNamed = (node, bindingName, scopes) => {
9008
8940
  }
9009
8941
  };
9010
8942
  //#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
9011
9002
  //#region src/plugin/utils/is-proven-browser-api-receiver.ts
9012
9003
  const DOM_EVENT_TARGET_TYPE_NAMES = new Set([
9013
9004
  "AbortSignal",
@@ -13661,7 +13652,7 @@ const getPromiseChainCallForCallback = (candidate) => {
13661
13652
  if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
13662
13653
  return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
13663
13654
  };
13664
- const collectInvokedFunctions = (effectCallback, includePromiseCallbacks) => {
13655
+ const collectEffectInvokedFunctions = (effectCallback) => {
13665
13656
  const invokedFunctions = new Set([effectCallback]);
13666
13657
  const localFunctionBindings = /* @__PURE__ */ new Map();
13667
13658
  const calledBindingNames = /* @__PURE__ */ new Set();
@@ -13695,14 +13686,12 @@ const collectInvokedFunctions = (effectCallback, includePromiseCallbacks) => {
13695
13686
  calledBindingNames.add(callee.name);
13696
13687
  return;
13697
13688
  }
13698
- if (includePromiseCallbacks && isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
13689
+ if (isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
13699
13690
  });
13700
13691
  for (const calledName of calledBindingNames) enqueue(localFunctionBindings.get(calledName));
13701
13692
  }
13702
13693
  return invokedFunctions;
13703
13694
  };
13704
- const collectEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, true);
13705
- const collectSynchronouslyEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, false);
13706
13695
  //#endregion
13707
13696
  //#region src/plugin/utils/is-react-hook-name.ts
13708
13697
  const isReactHookName = (name) => {
@@ -13861,19 +13850,15 @@ const resolveReactRefSymbol = (memberExpression, scopes) => {
13861
13850
  if (!isNodeOfType(initializer, "CallExpression")) return null;
13862
13851
  return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
13863
13852
  };
13864
- const resolveReactRefCurrentOriginSymbol = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13853
+ const hasReactRefCurrentOrigin = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13865
13854
  const expression = stripParenExpression(node);
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;
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;
13873
13859
  visitedSymbolIds.add(symbol.id);
13874
- return resolveReactRefCurrentOriginSymbol(initializer, scopes, visitedSymbolIds);
13860
+ return hasReactRefCurrentOrigin(symbol.initializer, scopes, visitedSymbolIds);
13875
13861
  };
13876
- const hasReactRefCurrentOrigin = (node, scopes) => resolveReactRefCurrentOriginSymbol(node, scopes) !== null;
13877
13862
  //#endregion
13878
13863
  //#region src/plugin/utils/walk-inside-statement-blocks.ts
13879
13864
  const walkInsideStatementBlocks = (node, visitor) => {
@@ -13891,7 +13876,6 @@ const walkInsideStatementBlocks = (node, visitor) => {
13891
13876
  };
13892
13877
  //#endregion
13893
13878
  //#region src/plugin/rules/state-and-effects/utils/is-subscribe-like-call-expression.ts
13894
- const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
13895
13879
  const getSubscribeLikeMethodName = (node) => {
13896
13880
  if (!isNodeOfType(node, "CallExpression")) return null;
13897
13881
  if (!isNodeOfType(node.callee, "MemberExpression")) return null;
@@ -13902,11 +13886,6 @@ const isSubscribeLikeCallExpression = (node) => {
13902
13886
  const methodName = getSubscribeLikeMethodName(node);
13903
13887
  return methodName !== null && SUBSCRIPTION_METHOD_NAMES.has(methodName);
13904
13888
  };
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;
13910
13889
  const isCleanupReturningSubscribeLikeCallExpression = (node) => {
13911
13890
  const methodName = getSubscribeLikeMethodName(node);
13912
13891
  if (methodName === null || !CLEANUP_RETURNING_SUBSCRIPTION_METHOD_NAMES.has(methodName)) return false;
@@ -13959,6 +13938,7 @@ const isNodeReachableWithinFunction = (node, context) => {
13959
13938
  };
13960
13939
  //#endregion
13961
13940
  //#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
13941
+ const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
13962
13942
  const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
13963
13943
  const REPLAYABLE_ITERATOR_COLLECTION_CACHE = /* @__PURE__ */ new WeakMap();
13964
13944
  const REACT_REF_EFFECT_ANALYSIS_CACHE = /* @__PURE__ */ new WeakMap();
@@ -13968,6 +13948,10 @@ const RESOURCE_NOUN_BY_KIND = {
13968
13948
  socket: "connection"
13969
13949
  };
13970
13950
  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
+ };
13971
13955
  const resolveExpressionKey = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13972
13956
  if (!expression) return null;
13973
13957
  const unwrappedExpression = stripParenExpression(expression);
@@ -14069,13 +14053,12 @@ const findSubscribeLikeUsages = (callback, context) => {
14069
14053
  });
14070
14054
  return;
14071
14055
  }
14072
- const subscribeOrObserveMethodName = getSubscribeOrObserveMethodName(child);
14073
- if (subscribeOrObserveMethodName !== null) {
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)) {
14074
14057
  const registrationDetails = getCallRegistrationDetails(child, context);
14075
14058
  usages.push({
14076
14059
  kind: "subscribe",
14077
14060
  node: child,
14078
- resourceName: subscribeOrObserveMethodName,
14061
+ resourceName: child.callee.property.name,
14079
14062
  handleKey: findAssignedResourceKey(child, context),
14080
14063
  ...registrationDetails
14081
14064
  });
@@ -14357,39 +14340,6 @@ const findContainingCollectionKey = (resourceNode, context) => {
14357
14340
  }
14358
14341
  return null;
14359
14342
  };
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
- };
14393
14343
  const isWithinAssignmentTarget = (identifier) => {
14394
14344
  let currentNode = identifier;
14395
14345
  let parentNode = currentNode.parent;
@@ -14448,14 +14398,6 @@ const isSynchronousIteratorCallback = (functionNode) => {
14448
14398
  if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments?.[1] === functionNode;
14449
14399
  return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments?.[0] === functionNode;
14450
14400
  };
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
- };
14459
14401
  const findDirectCallForReference = (identifier) => {
14460
14402
  const expressionRoot = findTransparentExpressionRoot(identifier);
14461
14403
  const callNode = expressionRoot.parent;
@@ -14470,7 +14412,7 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
14470
14412
  const callNode = findDirectCallForReference(reference.identifier);
14471
14413
  return callNode ? [callNode] : [];
14472
14414
  });
14473
- if (invocationCalls.length !== 1 || symbol.references.length !== 1) return null;
14415
+ if (invocationCalls.length !== 1) return null;
14474
14416
  const invocationCall = invocationCalls[0];
14475
14417
  return findEnclosingFunction$1(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
14476
14418
  };
@@ -14504,15 +14446,7 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
14504
14446
  if (cleanupChild !== cleanupFunction.body && isFunctionLike$1(cleanupChild) && !isSynchronousIteratorCallback(cleanupChild)) return false;
14505
14447
  const cleanupCall = isNodeOfType(cleanupChild, "ChainExpression") ? cleanupChild.expression : cleanupChild;
14506
14448
  if (doesReleaseCallMatchUsage(cleanupChild, usage, 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;
14449
+ const cleanupForOfStatement = findForOfStatementForIteratorExpression(isNodeOfType(cleanupCall, "CallExpression") ? cleanupCall.arguments?.[0] : null, context);
14516
14450
  if (!cleanupForOfStatement) {
14517
14451
  didCleanupFunctionMatch = true;
14518
14452
  return false;
@@ -14556,28 +14490,28 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
14556
14490
  });
14557
14491
  return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
14558
14492
  };
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);
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);
14566
14504
  };
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) => {
14570
14505
  let ancestor = releaseCall.parent;
14571
14506
  while (ancestor && ancestor !== owner) {
14572
14507
  if (isNodeOfType(ancestor, "IfStatement")) {
14573
- if (ancestor.alternate !== null || !doesTestRequireLiveExpressionKey(ancestor.test, expressionKey, context) || !doMatchingNodesCoverEveryPathAfterUsage(ancestor.consequent, [releaseCall], context)) return null;
14508
+ if (ancestor.alternate !== null || !doesTestRequireLiveHandle(ancestor.test) || !doMatchingNodesCoverEveryPathAfterUsage(ancestor.consequent, [releaseCall], context)) return null;
14574
14509
  return ancestor;
14575
14510
  }
14576
14511
  ancestor = ancestor.parent;
14577
14512
  }
14578
14513
  return null;
14579
14514
  };
14580
- const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => usage.handleKey === null ? null : findLiveExpressionGuardForRelease(releaseCall, owner, usage.handleKey, context);
14581
14515
  const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
14582
14516
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
14583
14517
  const functionCfg = context.cfg.cfgFor(callback);
@@ -14606,7 +14540,7 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
14606
14540
  walkAst(componentFunction.body, (child) => {
14607
14541
  if (didFindUnmountCleanup) return false;
14608
14542
  if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction) return;
14609
- if (!isReactHookCall(child, CLEANUP_EFFECT_HOOK_NAMES, context.scopes)) return;
14543
+ if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
14610
14544
  const dependencyList = child.arguments?.[1];
14611
14545
  if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
14612
14546
  const cleanupCallback = getEffectCallback(child);
@@ -14765,108 +14699,7 @@ const hasPotentialInterruptionAfterGuard = (callback, guardState, usageNode, con
14765
14699
  });
14766
14700
  return hasPotentialInterruption;
14767
14701
  };
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
- };
14868
14702
  const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
14869
- if (hasGuardedRefOwnedNestedCleanup(callback, usage, cleanupReturns, context)) return true;
14870
14703
  const usageFunction = findEnclosingFunction$1(usage.node);
14871
14704
  const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
14872
14705
  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;
@@ -15037,7 +14870,7 @@ const getReleaseVerbName = (node) => {
15037
14870
  const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) => {
15038
14871
  const releaseFunction = findEnclosingFunction$1(releaseReceiver);
15039
14872
  const usageFunction = findEnclosingFunction$1(usage.node);
15040
- if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction, context) || !resolveReactRefCurrentOriginSymbol(releaseReceiver, context.scopes)) return false;
14873
+ if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction) || !hasReactRefCurrentOrigin(releaseReceiver, context.scopes)) return false;
15041
14874
  const controllerKey = getListenerAbortControllerKey(usage, context);
15042
14875
  const refCurrentKey = resolveExpressionKey(releaseReceiver, context);
15043
14876
  if (controllerKey === null || refCurrentKey === null) return false;
@@ -15057,62 +14890,6 @@ const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) =>
15057
14890
  const safeOwnershipAssignments = ownershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, previousAbortCalls, usageFunction, context));
15058
14891
  return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
15059
14892
  };
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
- };
15116
14893
  const doesReleaseCallMatchUsage = (node, usage, context) => {
15117
14894
  const callNode = isNodeOfType(node, "ChainExpression") ? node.expression : node;
15118
14895
  if (!isNodeOfType(callNode, "CallExpression")) return false;
@@ -15129,21 +14906,14 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15129
14906
  if (!releaseVerbName) return false;
15130
14907
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
15131
14908
  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;
15139
14909
  if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
15140
14910
  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;
15141
14911
  if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
15142
14912
  if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
15143
14913
  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;
15145
14914
  const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
15146
14915
  if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
14916
+ const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
15147
14917
  const usageEventArgument = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[0] : null;
15148
14918
  const releaseEventArgument = callNode.arguments?.[0];
15149
14919
  if (isAssignmentFormForOfIteratorReference(usageEventArgument, context) || isAssignmentFormForOfIteratorReference(releaseEventArgument, context)) return false;
@@ -15177,14 +14947,13 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15177
14947
  const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
15178
14948
  if (!releaseHandler) return releaseVerbName === "off";
15179
14949
  const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
15180
- const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignature ? 0 : 1] : null;
15181
- return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
14950
+ return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey;
15182
14951
  }
15183
14952
  if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
15184
14953
  return true;
15185
14954
  };
15186
14955
  const matchesPairedReleaseVerb = (releaseVerbName, pairedVerbNames) => pairedVerbNames.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName);
15187
- const isReturnedEffectCleanupFunction = (functionNode, context) => {
14956
+ const isReturnedEffectCleanupFunction = (functionNode) => {
15188
14957
  let currentNode = functionNode;
15189
14958
  let parentNode = currentNode.parent;
15190
14959
  while (isNodeOfType(parentNode, "ChainExpression") || isNodeOfType(parentNode, "TSAsExpression") || isNodeOfType(parentNode, "TSNonNullExpression")) {
@@ -15193,175 +14962,23 @@ const isReturnedEffectCleanupFunction = (functionNode, context) => {
15193
14962
  }
15194
14963
  const effectCallback = isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode ? findEnclosingFunction$1(parentNode) : isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode ? parentNode : null;
15195
14964
  const effectCall = effectCallback?.parent;
15196
- return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isReactHookCall(effectCall, CLEANUP_EFFECT_HOOK_NAMES, context.scopes));
14965
+ return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
15197
14966
  };
15198
14967
  const isPotentiallyReachableFunction = (functionNode, context) => {
15199
- if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode, context)) return true;
14968
+ if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode)) return true;
15200
14969
  const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
15201
14970
  if (!bindingIdentifier) return false;
15202
14971
  const symbol = context.scopes.symbolFor(bindingIdentifier);
15203
14972
  if (!symbol) return false;
15204
14973
  return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
15205
14974
  };
15206
- const findRetainedDisposerStorages = (disposerFunction, usage, context) => {
15207
- if (!isFunctionLike$1(disposerFunction) || disposerFunction.async || disposerFunction.generator) return [];
15208
- const usageFunction = findEnclosingFunction$1(usage.node);
15209
- if (!usageFunction || !isFunctionLike$1(usageFunction)) return [];
15210
- const assignments = /* @__PURE__ */ new Map();
15211
- const collectAssignment = (expression) => {
15212
- const expressionRoot = findTransparentExpressionRoot(expression);
15213
- const assignment = expressionRoot.parent;
15214
- if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.right !== expressionRoot) return;
15215
- const refSymbol = resolveReactRefSymbol(stripParenExpression(assignment.left), context.scopes);
15216
- const refCurrentKey = resolveExpressionKey(assignment.left, context);
15217
- const retainedFunction = findEnclosingFunction$1(assignment);
15218
- const assignmentStart = getRangeStart(assignment);
15219
- if (!refSymbol || !refCurrentKey || !retainedFunction || retainedFunction !== usageFunction || assignmentStart === null) return;
15220
- assignments.set(assignmentStart, {
15221
- assignmentNode: assignment,
15222
- refCurrentKey,
15223
- retainedFunction
15224
- });
15225
- };
15226
- collectAssignment(disposerFunction);
15227
- const bindingIdentifier = getFunctionBindingIdentifier$1(disposerFunction);
15228
- const symbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
15229
- for (const reference of symbol?.references ?? []) collectAssignment(reference.identifier);
15230
- walkAst(usageFunction.body, (child) => {
15231
- if (child !== usageFunction.body && isFunctionLike$1(child)) return false;
15232
- if (isNodeOfType(child, "AssignmentExpression") && resolveStableValue(child.right, context) === disposerFunction) collectAssignment(child.right);
15233
- });
15234
- return [...assignments.values()];
15235
- };
15236
- const isRetainedDisposerStorageEstablished = (storage, usage, context) => doMatchingNodesCoverEveryPathBeforeUsage(usage.node, [storage.assignmentNode], storage.retainedFunction, context) || doMatchingNodesCoverEveryPathAfterUsage(usage.node, [storage.assignmentNode], context);
15237
- const hasUnsafeRetainedDisposerOverwrite = (storage, usage, context) => {
15238
- let hasUnsafeOverwrite = false;
15239
- walkAst(storage.retainedFunction.body, (child) => {
15240
- if (hasUnsafeOverwrite) return false;
15241
- if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
15242
- if (!isNodeOfType(child, "AssignmentExpression") || child === storage.assignmentNode || resolveExpressionKey(child.left, context) !== storage.refCurrentKey || !canNodeReachLaterNodeWithinFunction(usage.node, child, storage.retainedFunction, context)) return;
15243
- const storedValue = resolveStableValue(child.right, context);
15244
- if (!storedValue || !isFunctionLike$1(storedValue) || !doesCleanupFunctionReleaseUsage(storedValue, usage, context)) {
15245
- hasUnsafeOverwrite = true;
15246
- return false;
15247
- }
15248
- });
15249
- return hasUnsafeOverwrite;
15250
- };
15251
- const hasEffectCleanupInvocation = (storage, usage, context) => {
15252
- const componentFunction = findEnclosingFunction$1(storage.retainedFunction);
15253
- if (!componentFunction || !isFunctionLike$1(componentFunction)) return false;
15254
- const cleanupFunctionInvokesRef = (cleanupFunction) => {
15255
- if (!isFunctionLike$1(cleanupFunction)) return false;
15256
- let didFindCleanupCall = false;
15257
- walkAst(cleanupFunction.body, (child) => {
15258
- if (didFindCleanupCall) return false;
15259
- if (child !== cleanupFunction.body && isFunctionLike$1(child)) return false;
15260
- if (isNodeOfType(child, "CallExpression") && resolveExpressionKey(child.callee, context) === storage.refCurrentKey) {
15261
- const callRoot = findTransparentExpressionRoot(child);
15262
- const callStatement = callRoot.parent;
15263
- const isDirectBlockStatement = isNodeOfType(cleanupFunction.body, "BlockStatement") && isNodeOfType(callStatement, "ExpressionStatement") && callStatement.parent === cleanupFunction.body;
15264
- const isConciseBody = cleanupFunction.body === callRoot;
15265
- if ((isDirectBlockStatement || isConciseBody) && !hasUnprovenReturnBeforeRefOwnedRelease(cleanupFunction, child, storage.refCurrentKey, context)) {
15266
- didFindCleanupCall = true;
15267
- return false;
15268
- }
15269
- }
15270
- });
15271
- return didFindCleanupCall;
15272
- };
15273
- const effectReturnsCleanup = (effectCallback) => {
15274
- if (!isFunctionLike$1(effectCallback)) return false;
15275
- if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
15276
- const cleanupFunction = resolveRefOwnedCleanupFunction(effectCallback.body, context);
15277
- return Boolean(cleanupFunction && cleanupFunctionInvokesRef(cleanupFunction));
15278
- }
15279
- const matchingReturns = [];
15280
- walkInsideStatementBlocks(effectCallback.body, (child) => {
15281
- if (!isNodeOfType(child, "ReturnStatement") || !child.argument) return;
15282
- const cleanupFunction = resolveRefOwnedCleanupFunction(child.argument, context);
15283
- if (!cleanupFunction || !cleanupFunctionInvokesRef(cleanupFunction)) return;
15284
- matchingReturns.push(child);
15285
- });
15286
- return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15287
- };
15288
- let didFindInvocation = false;
15289
- walkAst(componentFunction.body, (child) => {
15290
- if (didFindInvocation) return false;
15291
- if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
15292
- const effectCallback = getEffectCallback(child);
15293
- if (effectCallback && effectReturnsCleanup(effectCallback)) {
15294
- didFindInvocation = true;
15295
- return false;
15296
- }
15297
- });
15298
- return didFindInvocation;
15299
- };
15300
- const hasCallbackRefReplacementInvocation = (storage, usage, context) => {
15301
- const isReturnedCallbackRefShape = () => {
15302
- if (!isFunctionLike$1(storage.retainedFunction)) return false;
15303
- const callbackCall = findTransparentExpressionRoot(storage.retainedFunction).parent;
15304
- if (!isNodeOfType(callbackCall, "CallExpression") || !isReactApiCall(callbackCall, "useCallback", context.scopes)) return false;
15305
- const nodeParameter = storage.retainedFunction.params?.[0];
15306
- const nodeParameterKey = resolveExpressionKey(nodeParameter, context);
15307
- if (!nodeParameterKey || usage.receiverKey !== nodeParameterKey) return false;
15308
- if (!isFunctionReturnedFromReactHook(storage.retainedFunction, context, false)) return false;
15309
- const usageStart = getRangeStart(usage.node);
15310
- if (usageStart === null) return false;
15311
- let hasNullExit = false;
15312
- walkAst(storage.retainedFunction.body, (child) => {
15313
- if (hasNullExit) return false;
15314
- if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
15315
- if (!isNodeOfType(child, "IfStatement") || (getRangeStart(child) ?? usageStart) >= usageStart) return;
15316
- const test = stripParenExpression(child.test);
15317
- if (!isNodeOfType(test, "UnaryExpression") || test.operator !== "!" || resolveExpressionKey(test.argument, context) !== nodeParameterKey) return;
15318
- const consequent = child.consequent;
15319
- hasNullExit = isNodeOfType(consequent, "ReturnStatement") || isNodeOfType(consequent, "BlockStatement") && consequent.body.some((statement) => isNodeOfType(statement, "ReturnStatement"));
15320
- if (hasNullExit) return false;
15321
- });
15322
- return hasNullExit;
15323
- };
15324
- if (!isFunctionForwardedToReactRef(storage.retainedFunction, context) && !isReturnedCallbackRefShape()) return false;
15325
- const cleanupCalls = [];
15326
- walkAst(storage.retainedFunction.body, (child) => {
15327
- if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
15328
- if (isNodeOfType(child, "CallExpression") && resolveExpressionKey(child.callee, context) === storage.refCurrentKey) cleanupCalls.push(child);
15329
- });
15330
- return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, cleanupCalls, storage.retainedFunction, context);
15331
- };
15332
- const isRetainedDisposerRefRelease = (releaseNode, usage, context) => {
15333
- const disposerFunction = findEnclosingFunction$1(releaseNode);
15334
- if (!disposerFunction) return false;
15335
- return findRetainedDisposerStorages(disposerFunction, usage, context).some((storage) => isRetainedDisposerStorageEstablished(storage, usage, context) && !hasUnsafeRetainedDisposerOverwrite(storage, usage, context) && (hasEffectCleanupInvocation(storage, usage, context) || hasCallbackRefReplacementInvocation(storage, usage, context)));
15336
- };
15337
- const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
15338
- if (usage.kind !== "subscribe" || usage.registrationVerbName !== "addEventListener" || usage.receiverKey === null || usage.eventKey === null || !isNodeOfType(usage.node, "CallExpression") || !isFunctionLike$1(releaseFunction) || releaseFunction.async || releaseFunction.generator || !isNodeOfType(releaseFunction.body, "BlockStatement") || !doMatchingNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseNode], context)) return false;
15339
- const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
15340
- const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
15341
- if (!isNodeOfType(releaseCall, "CallExpression")) return false;
15342
- const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
15343
- if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
15344
- const ownerFunction = findEnclosingFunction$1(releaseFunction);
15345
- if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
15346
- const triggerRegistrations = [];
15347
- walkAst(ownerFunction.body, (child) => {
15348
- if (child !== ownerFunction.body && isFunctionLike$1(child)) return false;
15349
- if (!isNodeOfType(child, "CallExpression")) return;
15350
- const registrationDetails = getCallRegistrationDetails(child, context);
15351
- if (registrationDetails.registrationVerbName === "addEventListener" && registrationDetails.receiverKey === usage.receiverKey && resolveStableValue(child.arguments?.[1], context) === releaseFunction) triggerRegistrations.push(child);
15352
- });
15353
- if (triggerRegistrations.some((triggerRegistration) => triggerRegistration === usage.node)) return true;
15354
- return doMatchingNodesCoverEveryPathAfterUsage(usage.node, triggerRegistrations, context) || doMatchingNodesCoverEveryPathBeforeUsage(usage.node, triggerRegistrations, ownerFunction, context);
15355
- };
15356
14975
  const isReleaseReachableForUsage = (releaseNode, usage, context) => {
15357
14976
  if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
15358
14977
  const releaseFunction = findEnclosingFunction$1(releaseNode);
15359
14978
  if (!releaseFunction) return true;
15360
14979
  if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
15361
- if (isRetainedDisposerRefRelease(releaseNode, usage, context)) return true;
15362
14980
  const usageFunction = findEnclosingFunction$1(usage.node);
15363
14981
  if (usageFunction && isFunctionLike$1(usageFunction) && getAssignedReactRefSymbol(usageFunction, context) && isCleanupFunctionReferencedByReturn(usageFunction, releaseFunction, context)) return isReactRefCallbackCleanupOwnedByEffect(usageFunction, releaseFunction, usage, context);
15364
- if (isSelfReleasingListenerRelease(releaseNode, releaseFunction, usage, context)) return true;
15365
14982
  return isPotentiallyReachableFunction(releaseFunction, context);
15366
14983
  };
15367
14984
  const fileContainsReleaseForUsage = (usage, context) => {
@@ -15693,7 +15310,7 @@ const findRetainedFunctionLeak = (retainedFunction, context, options) => {
15693
15310
  return false;
15694
15311
  }
15695
15312
  }
15696
- if (isSubscribeOrObserveCallExpression(child) && (!doesResourceResultEscape(child, allowReturnedResourceEscape, allowReturnedResourceEscape, context) || options?.requireCallableReturnedResource === true && !isCleanupReturningSubscribeLikeCallExpression(child))) {
15313
+ if (isSubscribeOrObserveCall(child) && (!doesResourceResultEscape(child, allowReturnedResourceEscape, allowReturnedResourceEscape, context) || options?.requireCallableReturnedResource === true && !isCleanupReturningSubscribeLikeCallExpression(child))) {
15697
15314
  const registrationDetails = getCallRegistrationDetails(child, context);
15698
15315
  const subscriptionUsage = {
15699
15316
  kind: "subscribe",
@@ -15901,7 +15518,7 @@ const isInlineRetainedHandlerFunction = (functionNode, context) => {
15901
15518
  if (!isFunctionLike$1(functionNode)) return false;
15902
15519
  const functionRoot = findTransparentExpressionRoot(functionNode);
15903
15520
  const callbackCall = functionRoot.parent;
15904
- if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot && isReactHookCall(callbackCall, "useCallback", context.scopes) && isDirectJsxEventHandlerValue(callbackCall)) return true;
15521
+ if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot && isHookCall$2(callbackCall, "useCallback") && isDirectJsxEventHandlerValue(callbackCall)) return true;
15905
15522
  const parentNode = functionNode.parent;
15906
15523
  if (isDirectJsxEventHandlerValue(functionNode)) return true;
15907
15524
  if (!isNodeOfType(parentNode, "Property") || parentNode.value !== functionNode || parentNode.computed) return false;
@@ -15937,12 +15554,12 @@ const effectNeedsCleanup = defineRule({
15937
15554
  };
15938
15555
  return {
15939
15556
  CallExpression(node) {
15940
- if (isReactHookCall(node, "useCallback", context.scopes)) {
15557
+ if (isHookCall$2(node, "useCallback")) {
15941
15558
  const retainedCallback = getEffectCallback(node);
15942
15559
  if (retainedCallback && !isInlineRetainedHandlerFunction(retainedCallback, context)) reportRetainedLeak(retainedCallback);
15943
15560
  return;
15944
15561
  }
15945
- if (!isReactHookCall(node, CLEANUP_EFFECT_HOOK_NAMES, context.scopes)) return;
15562
+ if (!isHookCall$2(node, CLEANUP_EFFECT_HOOK_NAMES)) return;
15946
15563
  const callback = getEffectCallback(node);
15947
15564
  if (!callback) return;
15948
15565
  const usages = removeSynchronouslyReleasedUsages(callback, findSubscribeLikeUsages(callback, context), context);
@@ -15950,7 +15567,7 @@ const effectNeedsCleanup = defineRule({
15950
15567
  const firstUsage = findFirstUsageWithoutCleanup(callback, usages, context);
15951
15568
  if (!firstUsage) return;
15952
15569
  const resourceNoun = RESOURCE_NOUN_BY_KIND[firstUsage.kind];
15953
- const hookName = getCalleeName$1(node) ?? "effect";
15570
+ const hookName = getCalleeName$2(node) ?? "effect";
15954
15571
  context.report({
15955
15572
  node,
15956
15573
  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.`
@@ -17293,38 +16910,7 @@ const symbolHasStableImportedAlias = (symbol, scopes) => {
17293
16910
  const resolvedSymbol = resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes);
17294
16911
  return resolvedSymbol !== null && resolvedSymbol !== symbol && resolvedSymbol.kind === "import";
17295
16912
  };
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);
16913
+ const symbolHasStableValue = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => symbolHasStableHookOrigin(symbol, scopes) || symbolHasStableImportedAlias(symbol, scopes) || symbolHasStableFunctionOrigin(symbol, scopes, visitedSymbolIds) || symbolHasStableMemoizedOrigin(symbol, scopes, visitedSymbolIds);
17328
16914
  //#endregion
17329
16915
  //#region src/plugin/utils/symbol-has-react-use-effect-event-origin.ts
17330
16916
  const symbolHasReactUseEffectEventOrigin = (symbol, scopes) => {
@@ -17526,7 +17112,7 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allow
17526
17112
  keys.add(depKey);
17527
17113
  continue;
17528
17114
  }
17529
- const identitySourceKeys = resolvePureCalledFunctionSourceKeys(reference, symbol, scopes) ?? resolveRenderDerivedMutableSourceKeys(reference, symbol, scopes) ?? resolveReactiveIdentitySourceKeys(symbol, scopes);
17115
+ const identitySourceKeys = resolveReactiveIdentitySourceKeys(symbol, scopes);
17530
17116
  if (identitySourceKeys) {
17531
17117
  if (identitySourceKeys.size === 0) stableCapturedNames.add(depKey);
17532
17118
  for (const identitySourceKey of identitySourceKeys) keys.add(identitySourceKey);
@@ -17609,161 +17195,6 @@ const resolveReactiveIdentitySourceKeys = (symbol, scopes) => {
17609
17195
  if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier || symbol.references.some((reference) => reference.flag !== "read")) return null;
17610
17196
  return resolveIdentitySourceKeysFromExpression(symbol.initializer, scopes, new Set([symbol.id]));
17611
17197
  };
17612
- const isPureDerivedExpression = (expression) => {
17613
- const candidate = unwrapExpression$3(expression);
17614
- if (isNodeOfType(candidate, "Literal") || isNodeOfType(candidate, "Identifier")) return true;
17615
- if (isNodeOfType(candidate, "MemberExpression")) return isPureDerivedExpression(candidate.object) && (!candidate.computed || isPureDerivedExpression(candidate.property));
17616
- if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return isPureDerivedExpression(candidate.left) && isPureDerivedExpression(candidate.right);
17617
- if (isNodeOfType(candidate, "UnaryExpression")) return candidate.operator !== "delete" && isPureDerivedExpression(candidate.argument);
17618
- if (isNodeOfType(candidate, "ConditionalExpression")) return isPureDerivedExpression(candidate.test) && isPureDerivedExpression(candidate.consequent) && isPureDerivedExpression(candidate.alternate);
17619
- if (isNodeOfType(candidate, "TemplateLiteral")) return candidate.expressions.every((nestedExpression) => isPureDerivedExpression(nestedExpression));
17620
- return false;
17621
- };
17622
- const isPureDerivedStatement = (statement) => {
17623
- if (isNodeOfType(statement, "BlockStatement")) return statement.body.every((nestedStatement) => isPureDerivedStatement(nestedStatement));
17624
- if (isNodeOfType(statement, "ReturnStatement")) return !statement.argument || isPureDerivedExpression(statement.argument);
17625
- if (isNodeOfType(statement, "IfStatement")) return isPureDerivedExpression(statement.test) && isPureDerivedStatement(statement.consequent) && (!statement.alternate || isPureDerivedStatement(statement.alternate));
17626
- return false;
17627
- };
17628
- const isPureDerivedFunction = (functionNode) => {
17629
- if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return false;
17630
- if (functionNode.async || functionNode.generator) return false;
17631
- return isNodeOfType(functionNode.body, "BlockStatement") ? isPureDerivedStatement(functionNode.body) : isPureDerivedExpression(functionNode.body);
17632
- };
17633
- const resolvePureCalledFunctionSourceKeys = (reference, symbol, scopes) => {
17634
- if (symbol.references.some((symbolReference) => symbolReference.flag !== "read")) return null;
17635
- const referenceRoot = findTransparentExpressionRoot(reference.identifier);
17636
- const callExpression = referenceRoot.parent;
17637
- if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== referenceRoot) return null;
17638
- const functionNode = getFunctionValueNode(symbol);
17639
- if (!functionNode || !isPureDerivedFunction(functionNode)) return null;
17640
- const sourceKeys = /* @__PURE__ */ new Set();
17641
- for (const capturedReference of closureCaptures(functionNode, scopes)) {
17642
- const capturedSymbol = capturedReference.resolvedSymbol;
17643
- if (!capturedSymbol || capturedSymbol.id === symbol.id) continue;
17644
- if (isOutsideAllFunctions(capturedSymbol) || symbolHasStableValue(capturedSymbol, scopes)) continue;
17645
- const capturedKey = computeDepKey(capturedReference);
17646
- if (!capturedKey) return null;
17647
- if (capturedKey === capturedSymbol.name) {
17648
- const nestedSourceKeys = resolveReactiveIdentitySourceKeys(capturedSymbol, scopes);
17649
- if (nestedSourceKeys) {
17650
- for (const nestedSourceKey of nestedSourceKeys) sourceKeys.add(nestedSourceKey);
17651
- continue;
17652
- }
17653
- }
17654
- sourceKeys.add(capturedKey);
17655
- }
17656
- return sourceKeys.size > 0 ? sourceKeys : null;
17657
- };
17658
- const mergeDerivedExpressionSourceKeys = (expressions, scopes, visitedSymbolIds) => {
17659
- const sourceKeys = /* @__PURE__ */ new Set();
17660
- for (const expression of expressions) {
17661
- const expressionSourceKeys = resolveDerivedExpressionSourceKeys(expression, scopes, visitedSymbolIds);
17662
- if (!expressionSourceKeys) return null;
17663
- for (const expressionSourceKey of expressionSourceKeys) sourceKeys.add(expressionSourceKey);
17664
- }
17665
- return sourceKeys;
17666
- };
17667
- const resolveDerivedExpressionSourceKeys = (expression, scopes, visitedSymbolIds) => {
17668
- const candidate = unwrapExpression$3(expression);
17669
- if (isNodeOfType(candidate, "Literal")) return /* @__PURE__ */ new Set();
17670
- if (isNodeOfType(candidate, "Identifier")) {
17671
- if (scopes.isGlobalReference(candidate)) return /* @__PURE__ */ new Set();
17672
- const sourceSymbol = scopes.symbolFor(candidate);
17673
- if (!sourceSymbol) return null;
17674
- if (isOutsideAllFunctions(sourceSymbol) || symbolHasStableValue(sourceSymbol, scopes)) return /* @__PURE__ */ new Set();
17675
- if (sourceSymbol.kind === "const" && sourceSymbol.initializer && isNodeOfType(sourceSymbol.declarationNode, "VariableDeclarator") && sourceSymbol.declarationNode.id === sourceSymbol.bindingIdentifier && sourceSymbol.references.every((sourceReference) => sourceReference.flag === "read") && !visitedSymbolIds.has(sourceSymbol.id)) {
17676
- visitedSymbolIds.add(sourceSymbol.id);
17677
- const sourceKeys = resolveDerivedExpressionSourceKeys(sourceSymbol.initializer, scopes, visitedSymbolIds);
17678
- visitedSymbolIds.delete(sourceSymbol.id);
17679
- if (sourceKeys) return sourceKeys;
17680
- }
17681
- return new Set([sourceSymbol.name]);
17682
- }
17683
- if (isNodeOfType(candidate, "MemberExpression")) {
17684
- if (hasComputedMemberExpression(candidate)) return null;
17685
- const sourceKey = stringifyMemberChain(candidate);
17686
- const rootIdentifier = getMemberRootIdentifier(candidate);
17687
- const rootSymbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
17688
- if (!sourceKey || !rootSymbol) return null;
17689
- if (isOutsideAllFunctions(rootSymbol) || symbolHasStableValue(rootSymbol, scopes)) return /* @__PURE__ */ new Set();
17690
- return new Set([sourceKey]);
17691
- }
17692
- if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return mergeDerivedExpressionSourceKeys([candidate.left, candidate.right], scopes, visitedSymbolIds);
17693
- if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator !== "delete") return resolveDerivedExpressionSourceKeys(candidate.argument, scopes, visitedSymbolIds);
17694
- if (isNodeOfType(candidate, "ConditionalExpression")) return mergeDerivedExpressionSourceKeys([
17695
- candidate.test,
17696
- candidate.consequent,
17697
- candidate.alternate
17698
- ], scopes, visitedSymbolIds);
17699
- if (isNodeOfType(candidate, "TemplateLiteral")) return mergeDerivedExpressionSourceKeys(candidate.expressions, scopes, visitedSymbolIds);
17700
- if (isNodeOfType(candidate, "NewExpression")) {
17701
- const callee = unwrapExpression$3(candidate.callee);
17702
- if (!isNodeOfType(callee, "Identifier") || callee.name !== "Error" || !scopes.isGlobalReference(callee)) return null;
17703
- const argumentsToAnalyze = [];
17704
- for (const argument of candidate.arguments) {
17705
- if (!isAstNode(argument) || isNodeOfType(argument, "SpreadElement")) return null;
17706
- argumentsToAnalyze.push(argument);
17707
- }
17708
- return mergeDerivedExpressionSourceKeys(argumentsToAnalyze, scopes, visitedSymbolIds);
17709
- }
17710
- return null;
17711
- };
17712
- const resolveWriteControlSourceKeys = (assignment, boundaryFunction, scopes) => {
17713
- const sourceKeys = /* @__PURE__ */ new Set();
17714
- let currentNode = assignment;
17715
- while (currentNode.parent && currentNode.parent !== boundaryFunction) {
17716
- const parentNode = currentNode.parent;
17717
- if (isNodeOfType(parentNode, "IfStatement")) {
17718
- if (parentNode.test === currentNode) return null;
17719
- const testSourceKeys = resolveDerivedExpressionSourceKeys(parentNode.test, scopes, /* @__PURE__ */ new Set());
17720
- if (!testSourceKeys) return null;
17721
- for (const testSourceKey of testSourceKeys) sourceKeys.add(testSourceKey);
17722
- } else if (!isNodeOfType(parentNode, "ExpressionStatement") && !isNodeOfType(parentNode, "BlockStatement")) return null;
17723
- currentNode = parentNode;
17724
- }
17725
- return currentNode.parent === boundaryFunction ? sourceKeys : null;
17726
- };
17727
- const isReadOnlyInitialStateUse = (referenceNode, scopes) => {
17728
- const referenceRoot = findTransparentExpressionRoot(referenceNode);
17729
- const callExpression = referenceRoot.parent;
17730
- return isNodeOfType(callExpression, "CallExpression") && callExpression.arguments.some((argument) => argument === referenceRoot) && isReactApiCall(callExpression, "useState", scopes, {
17731
- allowGlobalReactNamespace: true,
17732
- allowUnboundBareCalls: true,
17733
- resolveNamedAliases: true
17734
- });
17735
- };
17736
- const resolveRenderDerivedMutableSourceKeys = (capturedReference, symbol, scopes) => {
17737
- if (symbol.kind !== "let" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
17738
- const boundaryFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
17739
- if (!boundaryFunction) return null;
17740
- const capturingFunction = findEnclosingFunction$1(capturedReference.identifier);
17741
- if (!capturingFunction || capturingFunction === boundaryFunction) return null;
17742
- const sourceKeys = /* @__PURE__ */ new Set();
17743
- if (symbol.initializer) {
17744
- const initializerSourceKeys = resolveDerivedExpressionSourceKeys(symbol.initializer, scopes, new Set([symbol.id]));
17745
- if (!initializerSourceKeys) return null;
17746
- for (const initializerSourceKey of initializerSourceKeys) sourceKeys.add(initializerSourceKey);
17747
- }
17748
- let writeCount = 0;
17749
- for (const symbolReference of symbol.references) {
17750
- if (symbolReference.flag === "read") {
17751
- if (findEnclosingFunction$1(symbolReference.identifier) !== capturingFunction && !isReadOnlyInitialStateUse(symbolReference.identifier, scopes)) return null;
17752
- continue;
17753
- }
17754
- if (symbolReference.flag !== "write") return null;
17755
- const referenceRoot = findTransparentExpressionRoot(symbolReference.identifier);
17756
- const assignment = referenceRoot.parent;
17757
- if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== referenceRoot || findEnclosingFunction$1(referenceRoot) !== boundaryFunction) return null;
17758
- const assignmentSourceKeys = resolveDerivedExpressionSourceKeys(assignment.right, scopes, new Set([symbol.id]));
17759
- const controlSourceKeys = resolveWriteControlSourceKeys(assignment, boundaryFunction, scopes);
17760
- if (!assignmentSourceKeys || !controlSourceKeys) return null;
17761
- for (const assignmentSourceKey of assignmentSourceKeys) sourceKeys.add(assignmentSourceKey);
17762
- for (const controlSourceKey of controlSourceKeys) sourceKeys.add(controlSourceKey);
17763
- writeCount += 1;
17764
- }
17765
- return writeCount > 0 && sourceKeys.size > 0 ? sourceKeys : null;
17766
- };
17767
17198
  const isUseCallbackResultDep = (node, scopes) => {
17768
17199
  const rootSymbol = getRootSymbol(node, scopes);
17769
17200
  const initializer = rootSymbol?.initializer ? unwrapExpression$3(rootSymbol.initializer) : null;
@@ -18507,7 +17938,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
18507
17938
  if (!isUsed) continue;
18508
17939
  const reportNode = declaredKeyToReportNode.get(declaredKey) ?? depsArgument;
18509
17940
  const rootSymbol = getRootSymbol(reportNode, context.scopes);
18510
- if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) || symbolHasStableValue(rootSymbol, context.scopes) || !isUnstableInitializer(rootSymbol.initializer)) continue;
17941
+ if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) || !isUnstableInitializer(rootSymbol.initializer)) continue;
18511
17942
  context.report({
18512
17943
  node: reportNode,
18513
17944
  message: buildUnstableDepMessage(hookName, declaredKey)
@@ -18823,7 +18254,7 @@ const flattenCalleeName = (callee) => {
18823
18254
  const PRAGMA = "React";
18824
18255
  const isReactFunctionCall = (node, expectedCall) => {
18825
18256
  if (!isNodeOfType(node, "CallExpression")) return false;
18826
- if (getCalleeName$1(node) !== expectedCall) return false;
18257
+ if (getCalleeName$2(node) !== expectedCall) return false;
18827
18258
  if (isNodeOfType(node.callee, "MemberExpression")) {
18828
18259
  const receiver = stripParenExpression(node.callee.object);
18829
18260
  return isNodeOfType(receiver, "Identifier") && receiver.name === PRAGMA;
@@ -19021,14 +18452,10 @@ const hookUseState = defineRule({
19021
18452
  create: (context) => {
19022
18453
  const { allowDestructuredState } = resolveSettings$40(context.settings);
19023
18454
  return { CallExpression(node) {
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;
18455
+ if (!isReactFunctionCall(node, "useState")) return;
19030
18456
  const parent = node.parent;
19031
18457
  if (!parent) return;
18458
+ if (isNodeOfType(parent, "ReturnStatement")) return;
19032
18459
  if (!isNodeOfType(parent, "VariableDeclarator")) {
19033
18460
  context.report({
19034
18461
  node,
@@ -19136,8 +18563,8 @@ const hooksNoNanInDeps = defineRule({
19136
18563
  severity: "warn",
19137
18564
  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.",
19138
18565
  create: (context) => ({ CallExpression(node) {
19139
- if (!isReactHookCall(node, HOOKS_WITH_DEP_ARRAY, context.scopes)) return;
19140
- const depsIndex = isReactHookCall(node, "useImperativeHandle", context.scopes) ? 2 : 1;
18566
+ if (!isHookCall$2(node, HOOKS_WITH_DEP_ARRAY)) return;
18567
+ const depsIndex = getCalleeName$2(node) === "useImperativeHandle" ? 2 : 1;
19141
18568
  const depsArgument = node.arguments[depsIndex];
19142
18569
  if (!depsArgument || !isNodeOfType(depsArgument, "ArrayExpression")) return;
19143
18570
  for (const element of depsArgument.elements) {
@@ -20308,7 +19735,7 @@ const isImportedSelectAtom = (callExpression) => {
20308
19735
  const isDeferredCallbackPosition$1 = (functionNode) => {
20309
19736
  const parent = functionNode.parent;
20310
19737
  if (isNodeOfType(parent, "CallExpression") && parent.arguments?.[0] === functionNode) {
20311
- const hookName = getCalleeName$1(parent);
19738
+ const hookName = getCalleeName$2(parent);
20312
19739
  if (hookName && MEMOIZING_HOOK_NAMES$1.has(hookName)) return true;
20313
19740
  if (hookName && EFFECT_HOOK_NAMES$1.has(hookName) && Boolean(parent.arguments?.[1])) return true;
20314
19741
  }
@@ -30379,7 +29806,7 @@ const DEFERRING_CALLEE_NAMES$1 = new Set([
30379
29806
  "on",
30380
29807
  "once"
30381
29808
  ]);
30382
- const getCalleeName = (callee) => {
29809
+ const getCalleeName$1 = (callee) => {
30383
29810
  if (!callee) return null;
30384
29811
  if (isNodeOfType(callee, "Identifier")) return callee.name;
30385
29812
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
@@ -30391,11 +29818,11 @@ const isDeferredCallbackPosition = (expression) => {
30391
29818
  const parent = parentOf(expression);
30392
29819
  if (!parent) return false;
30393
29820
  if (isNodeOfType(parent, "CallExpression") && argumentsInclude(parent.arguments, expression)) {
30394
- const name = getCalleeName(parent.callee);
29821
+ const name = getCalleeName$1(parent.callee);
30395
29822
  if (name && DEFERRING_CALLEE_NAMES$1.has(name)) return true;
30396
29823
  }
30397
29824
  if (isNodeOfType(parent, "NewExpression") && argumentsInclude(parent.arguments, expression)) {
30398
- const name = getCalleeName(parent.callee);
29825
+ const name = getCalleeName$1(parent.callee);
30399
29826
  if (name && (name.endsWith("Observer") || name === "Promise")) return true;
30400
29827
  }
30401
29828
  if (isNodeOfType(parent, "AssignmentExpression") && parent.right === expression && isNodeOfType(parent.left, "MemberExpression") && isNodeOfType(parent.left.property, "Identifier") && parent.left.property.name.startsWith("on")) return true;
@@ -30815,6 +30242,14 @@ const isHookCallee$1 = (analysis, node, hookName) => {
30815
30242
  if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
30816
30243
  return false;
30817
30244
  };
30245
+ const isUseEffect = (node) => {
30246
+ if (!node || !isNodeOfType(node, "CallExpression")) return false;
30247
+ const callee = node.callee;
30248
+ if (isNodeOfType(callee, "Identifier") && callee.name === "useEffect") return true;
30249
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
30250
+ const receiver = stripParenExpression(callee.object);
30251
+ return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useEffect";
30252
+ };
30818
30253
  const getEffectFn = (analysis, node) => {
30819
30254
  if (!isNodeOfType(node, "CallExpression")) return null;
30820
30255
  const fn = node.arguments?.[0];
@@ -30912,27 +30347,7 @@ const isRefCurrent = (ref) => {
30912
30347
  if (!isNodeOfType(parent.property, "Identifier")) return false;
30913
30348
  return parent.property.name === "current";
30914
30349
  };
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);
30350
+ const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isStateSetter(analysis, innerRef));
30936
30351
  const isSyncStateSetterCall = (analysis, ref, effectFn) => isStateSetterCall(analysis, ref) && isSynchronous(ref.identifier, effectFn) && !resolvesToAsyncFunction(ref);
30937
30352
  const HANDLER_NAMED_METHOD_PATTERN = /^(on|handle)[A-Z]/;
30938
30353
  const SYNCHRONOUS_CALLBACK_ARGUMENT_INDEX_BY_METHOD = new Map([
@@ -31083,11 +30498,9 @@ const isPropCallbackInvocationRef = (analysis, ref, options = {}) => {
31083
30498
  };
31084
30499
  const isRefCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isRefCurrent(innerRef) || isRef(analysis, innerRef));
31085
30500
  const getUseStateDecl = (analysis, ref) => {
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;
30501
+ let node = getUpstreamRefs(analysis, ref).find((upRef) => isHookCallee$1(analysis, upRef.identifier, "useState"))?.identifier;
30502
+ while (node && !isNodeOfType(node, "VariableDeclarator")) node = node.parent;
30503
+ return node ?? null;
31091
30504
  };
31092
30505
  const isCleanupReturnArgument = (analysis, node) => {
31093
30506
  if (isFunctionLike$1(node)) return true;
@@ -32276,7 +31689,6 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
32276
31689
  sourceReferences,
32277
31690
  isDeferred: frame.isDeferred,
32278
31691
  isRenderKnownCopy,
32279
- isSynchronousRenderValue: !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue,
32280
31692
  matchesStateInitializer: doesMatchStateInitializer,
32281
31693
  resetsSourceState: false
32282
31694
  });
@@ -32292,71 +31704,22 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
32292
31704
  });
32293
31705
  };
32294
31706
  //#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
32341
31707
  //#region src/plugin/rules/state-and-effects/no-adjust-state-on-prop-change.ts
32342
31708
  const noAdjustStateOnPropChange = defineRule({
32343
31709
  id: "no-adjust-state-on-prop-change",
32344
- title: "State adjusted after a prop changes",
31710
+ title: "State synced to a prop inside an effect",
32345
31711
  severity: "warn",
32346
31712
  tags: ["test-noise"],
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",
31713
+ recommendation: "Adjust the state inline during render with a `prev`-prop comparison (`if (prop !== prevProp) { setPrevProp(prop); setX(...); }`), or refactor to remove the duplicated state. Routing the adjustment through a useEffect forces an extra render with a stale UI between the two commits. See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes",
32348
31714
  create: (context) => ({ CallExpression(node) {
32349
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
31715
+ if (!isUseEffect(node)) return;
32350
31716
  const analysis = getProgramAnalysis(node);
32351
31717
  if (!analysis) return;
32352
31718
  const dependencyReferences = getEffectDepsRefs(analysis, node);
32353
31719
  if (!dependencyReferences) return;
32354
31720
  if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
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;
31721
+ for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
31722
+ if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
32360
31723
  context.report({
32361
31724
  node: fact.callExpression,
32362
31725
  message: "This effect adjusts state after a prop changes, so users briefly see the stale value."
@@ -34369,7 +33732,7 @@ const declarationBodyContainsHookCall = (symbol) => {
34369
33732
  walkAst(componentFunction, (descendant) => {
34370
33733
  if (didFindHookCall) return false;
34371
33734
  if (!isNodeOfType(descendant, "CallExpression")) return;
34372
- const calleeName = getCalleeName$1(descendant);
33735
+ const calleeName = getCalleeName$2(descendant);
34373
33736
  if (calleeName && isReactHookName(calleeName)) {
34374
33737
  didFindHookCall = true;
34375
33738
  return false;
@@ -34394,7 +33757,7 @@ const isReturnedFromUseCallbackAdapter = (callNode) => {
34394
33757
  if (isNodeOfType(parent, "ArrowFunctionExpression")) {
34395
33758
  if (parent.body !== current) return false;
34396
33759
  const grandparent = parent.parent;
34397
- return isNodeOfType(grandparent, "CallExpression") && getCalleeName$1(grandparent) === "useCallback" && grandparent.arguments.some((argumentNode) => argumentNode === parent);
33760
+ return isNodeOfType(grandparent, "CallExpression") && getCalleeName$2(grandparent) === "useCallback" && grandparent.arguments.some((argumentNode) => argumentNode === parent);
34398
33761
  }
34399
33762
  if (!isNodeOfType(parent, "ConditionalExpression") && !isNodeOfType(parent, "LogicalExpression")) return false;
34400
33763
  current = parent;
@@ -35076,7 +34439,7 @@ const noChainStateUpdates = defineRule({
35076
34439
  if (!callExpr) continue;
35077
34440
  if (!isReachableFromStateTrigger(callExpr)) continue;
35078
34441
  if (!readsPostMountValueThroughLocals(callExpr, effectFn, { ignoreBareRefCurrent: true })) continue;
35079
- const declarator = getUseStateDeclarator(resolveStateSetterReference(analysis, ref) ?? ref);
34442
+ const declarator = getUseStateDeclarator(ref);
35080
34443
  if (declarator) domSyncedStateDeclarators.add(declarator);
35081
34444
  }
35082
34445
  for (const ref of effectFnRefs) {
@@ -35085,7 +34448,7 @@ const noChainStateUpdates = defineRule({
35085
34448
  if (!callExpr) continue;
35086
34449
  if (!isReachableFromStateTrigger(callExpr)) continue;
35087
34450
  if (getArgsUpstreamRefs(analysis, ref).some((argRef) => isState(analysis, argRef))) continue;
35088
- const setterDeclarator = getUseStateDeclarator(resolveStateSetterReference(analysis, ref) ?? ref);
34451
+ const setterDeclarator = getUseStateDeclarator(ref);
35089
34452
  if (setterDeclarator && domSyncedStateDeclarators.has(setterDeclarator)) continue;
35090
34453
  const isSelfTargeting = setterDeclarator !== null && stateDepDeclarators.has(setterDeclarator);
35091
34454
  const setterArguments = isNodeOfType(callExpr, "CallExpression") ? callExpr.arguments ?? [] : [];
@@ -36926,7 +36289,7 @@ const noDerivedState = defineRule({
36926
36289
  for (const fact of collectRenderStateWriteFacts(analysis, componentBody, context.filename)) reportStateWrite(fact.callExpression, fact.stateDeclarator);
36927
36290
  } }).visitors,
36928
36291
  CallExpression(node) {
36929
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
36292
+ if (!isUseEffect(node)) return;
36930
36293
  const analysis = getProgramAnalysis(node);
36931
36294
  if (!analysis) return;
36932
36295
  for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
@@ -36946,7 +36309,7 @@ const noDerivedStateEffect = defineRule({
36946
36309
  tags: ["test-noise"],
36947
36310
  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",
36948
36311
  create: (context) => ({ CallExpression(node) {
36949
- if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes)) return;
36312
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
36950
36313
  const analysis = getProgramAnalysis(node);
36951
36314
  if (!analysis) return;
36952
36315
  if (!collectEffectStateWriteFacts(analysis, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
@@ -37069,7 +36432,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
37069
36432
  if (isFunctionLike$1(cursor)) {
37070
36433
  const parent = cursor.parent ?? null;
37071
36434
  if (parent && isNodeOfType(parent, "CallExpression")) {
37072
- const calleeName = getCalleeName$1(parent);
36435
+ const calleeName = getCalleeName$2(parent);
37073
36436
  if (calleeName !== null && EFFECT_HOOK_NAME_PATTERN.test(calleeName) && (parent.arguments ?? []).some((argument) => argument === cursor)) return cursor;
37074
36437
  }
37075
36438
  }
@@ -37140,7 +36503,7 @@ const isNonHandlerHookCallback = (functionNode) => {
37140
36503
  const parent = functionNode.parent ?? null;
37141
36504
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
37142
36505
  if (!(parent.arguments ?? []).some((argument) => argument === functionNode)) return false;
37143
- const calleeName = getCalleeName$1(parent);
36506
+ const calleeName = getCalleeName$2(parent);
37144
36507
  return calleeName !== null && isReactHookName(calleeName) && calleeName !== "useCallback";
37145
36508
  };
37146
36509
  const isHandlerShapedReseed = (setterCall, componentFunction) => {
@@ -37222,7 +36585,7 @@ const noDerivedUseState = defineRule({
37222
36585
  return {
37223
36586
  ...propStackTracker.visitors,
37224
36587
  CallExpression(node) {
37225
- if (!isReactHookCall(node, "useState", context.scopes) || !node.arguments?.length) return;
36588
+ if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
37226
36589
  const seed = unwrapInitializerSeed(node.arguments[0]);
37227
36590
  const reportStalePropCopy = (propName) => {
37228
36591
  if (isIntentionalSnapshotState(node)) return;
@@ -37898,7 +37261,7 @@ const noDirectMutationState = defineRule({
37898
37261
  const isSetterIdentifier = (name) => SETTER_PATTERN.test(name);
37899
37262
  //#endregion
37900
37263
  //#region src/plugin/rules/state-and-effects/utils/collect-use-state-bindings.ts
37901
- const collectUseStateBindings = (componentBody, scopes) => {
37264
+ const collectUseStateBindings = (componentBody) => {
37902
37265
  const bindings = [];
37903
37266
  if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
37904
37267
  for (const statement of componentBody.body ?? []) {
@@ -37911,7 +37274,7 @@ const collectUseStateBindings = (componentBody, scopes) => {
37911
37274
  const setterElement = elements[1];
37912
37275
  if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
37913
37276
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
37914
- if (!isReactHookCall(declarator.init, "useState", scopes)) continue;
37277
+ if (!isHookCall$2(declarator.init, "useState")) continue;
37915
37278
  bindings.push({
37916
37279
  valueName: valueElement.name,
37917
37280
  setterName: setterElement.name,
@@ -38078,7 +37441,7 @@ const noDirectStateMutation = defineRule({
38078
37441
  create: (context) => {
38079
37442
  const checkComponent = (componentBody) => {
38080
37443
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
38081
- const bindings = collectUseStateBindings(componentBody, context.scopes);
37444
+ const bindings = collectUseStateBindings(componentBody);
38082
37445
  if (bindings.length === 0) return;
38083
37446
  const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
38084
37447
  const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
@@ -38638,31 +38001,25 @@ const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSub
38638
38001
  };
38639
38002
  //#endregion
38640
38003
  //#region src/plugin/rules/state-and-effects/no-effect-chain.ts
38641
- const findTopLevelEffectCalls = (componentBody, scopes) => {
38004
+ const findTopLevelEffectCalls = (componentBody) => {
38642
38005
  const effectCalls = [];
38643
38006
  if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
38644
38007
  for (const statement of componentBody.body ?? []) {
38645
38008
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
38646
38009
  const expression = unwrapDiscardedExpression(statement);
38647
38010
  if (!isNodeOfType(expression, "CallExpression")) continue;
38648
- if (!isReactHookCall(expression, EFFECT_HOOK_NAMES$1, scopes)) continue;
38011
+ if (!isHookCall$2(expression, EFFECT_HOOK_NAMES$1)) continue;
38649
38012
  effectCalls.push(expression);
38650
38013
  }
38651
38014
  return effectCalls;
38652
38015
  };
38653
- const collectDependencyStateSymbolIds = (effectNode, stateSymbolIds, scopes) => {
38654
- const dependencyStateSymbolIds = /* @__PURE__ */ new Set();
38655
- if (!isNodeOfType(effectNode, "CallExpression")) return dependencyStateSymbolIds;
38016
+ const collectDepIdentifierNames = (effectNode) => {
38017
+ const depNames = /* @__PURE__ */ new Set();
38018
+ if (!isNodeOfType(effectNode, "CallExpression")) return depNames;
38656
38019
  const depsNode = effectNode.arguments?.[1];
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;
38020
+ if (!isNodeOfType(depsNode, "ArrayExpression")) return depNames;
38021
+ for (const element of depsNode.elements ?? []) if (isNodeOfType(element, "Identifier")) depNames.add(element.name);
38022
+ return depNames;
38666
38023
  };
38667
38024
  const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
38668
38025
  const analysisFunctions = new Set([effectCallback]);
@@ -38768,13 +38125,12 @@ const readStaticSetterValue = (setterCall, scopes) => {
38768
38125
  if (updater) return readStaticUpdaterReturnValue(updater, scopes);
38769
38126
  return readStaticEffectValue(argument, scopes, null, null);
38770
38127
  };
38771
- const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName, scopes) => {
38128
+ const collectStateWritesInEffect = (analysisFunctions, setterToStateName, scopes) => {
38772
38129
  const stateWrites = /* @__PURE__ */ new Map();
38773
38130
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
38774
38131
  if (!isNodeOfType(child, "CallExpression")) return;
38775
38132
  if (!isNodeOfType(child.callee, "Identifier")) return;
38776
- const setterSymbol = resolveConstIdentifierAlias(child.callee, scopes, true);
38777
- const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
38133
+ const stateName = setterToStateName.get(child.callee.name);
38778
38134
  if (!stateName) return;
38779
38135
  const writeInfo = stateWrites.get(stateName) ?? {
38780
38136
  values: /* @__PURE__ */ new Set(),
@@ -38860,12 +38216,11 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
38860
38216
  "keys",
38861
38217
  "values"
38862
38218
  ]);
38863
- const isFunctionShapedReturn = (returnedValue, setterToStateName, setterSymbolIdToStateName, scopes, isExplicitReturnStatement) => {
38219
+ const isFunctionShapedReturn = (returnedValue, setterToStateName, isExplicitReturnStatement) => {
38864
38220
  if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
38865
38221
  if (isNodeOfType(returnedValue, "CallExpression")) {
38866
38222
  if (isNodeOfType(returnedValue.callee, "Identifier")) {
38867
- const setterSymbol = resolveConstIdentifierAlias(returnedValue.callee, scopes, true);
38868
- if (setterToStateName.has(returnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
38223
+ if (setterToStateName.has(returnedValue.callee.name)) return false;
38869
38224
  if (isSetterIdentifier(returnedValue.callee.name)) return true;
38870
38225
  }
38871
38226
  return isCleanupReturn(returnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
@@ -38889,7 +38244,7 @@ const collectStorageHookSetterNames = (componentBody) => {
38889
38244
  for (const declarator of statement.declarations ?? []) {
38890
38245
  if (!isNodeOfType(declarator.id, "ArrayPattern")) continue;
38891
38246
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
38892
- const calleeName = getCalleeName$1(declarator.init);
38247
+ const calleeName = getCalleeName$2(declarator.init);
38893
38248
  if (!calleeName || !STORAGE_HOOK_PATTERN.test(calleeName)) continue;
38894
38249
  for (const element of declarator.id.elements ?? []) if (isNodeOfType(element, "Identifier") && isSetterIdentifier(element.name)) setterNames.add(element.name);
38895
38250
  }
@@ -39032,11 +38387,11 @@ const isExternalSyncNode = (node) => {
39032
38387
  const receiverRootName = getRootIdentifierName(node.callee.object);
39033
38388
  return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
39034
38389
  };
39035
- const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, scopes, allowCommittedDomSync) => {
38390
+ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, scopes, allowCommittedDomSync) => {
39036
38391
  if (!isFunctionLike$1(effectCallback)) return false;
39037
38392
  if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
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;
38393
+ if (isFunctionShapedReturn(effectCallback.body, setterToStateName, false)) return true;
38394
+ } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, true)) return true;
39040
38395
  let didFindExternalCall = false;
39041
38396
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
39042
38397
  if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
@@ -39052,11 +38407,10 @@ const noEffectChain = defineRule({
39052
38407
  create: (context) => {
39053
38408
  const checkComponent = (componentBody) => {
39054
38409
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
39055
- const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
38410
+ const useStateBindings = collectUseStateBindings(componentBody);
39056
38411
  if (useStateBindings.length === 0) return;
39057
38412
  const setterToStateName = /* @__PURE__ */ new Map();
39058
38413
  const stateSymbolIds = /* @__PURE__ */ new Map();
39059
- const setterSymbolIdToStateName = /* @__PURE__ */ new Map();
39060
38414
  for (const binding of useStateBindings) {
39061
38415
  setterToStateName.set(binding.setterName, binding.valueName);
39062
38416
  if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
@@ -39065,27 +38419,21 @@ const noEffectChain = defineRule({
39065
38419
  const stateSymbol = context.scopes.symbolFor(stateIdentifier);
39066
38420
  if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
39067
38421
  }
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
- }
39073
38422
  }
39074
38423
  const storageSetterNames = collectStorageHookSetterNames(componentBody);
39075
- const stateSymbolIdSet = new Set(stateSymbolIds.values());
39076
38424
  const effectInfos = [];
39077
- for (const effectCall of findTopLevelEffectCalls(componentBody, context.scopes)) {
38425
+ for (const effectCall of findTopLevelEffectCalls(componentBody)) {
39078
38426
  const callback = getEffectCallback(effectCall, context.scopes);
39079
38427
  if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
39080
38428
  const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
39081
- const stateWrites = collectStateWritesInEffect(analysisFunctions, setterSymbolIdToStateName, context.scopes);
38429
+ const stateWrites = collectStateWritesInEffect(analysisFunctions, setterToStateName, context.scopes);
39082
38430
  const writtenStateNames = new Set(stateWrites.keys());
39083
38431
  effectInfos.push({
39084
38432
  node: effectCall,
39085
- dependencyStateSymbolIds: collectDependencyStateSymbolIds(effectCall, stateSymbolIdSet, context.scopes),
38433
+ depNames: collectDepIdentifierNames(effectCall),
39086
38434
  stateWrites,
39087
38435
  analysisFunctions,
39088
- isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
38436
+ isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
39089
38437
  });
39090
38438
  }
39091
38439
  if (effectInfos.length < 2) return;
@@ -39096,11 +38444,10 @@ const noEffectChain = defineRule({
39096
38444
  for (const readerEffect of effectInfos) {
39097
38445
  if (readerEffect === writerEffect) continue;
39098
38446
  if (readerEffect.isExternalSync) continue;
39099
- if (readerEffect.dependencyStateSymbolIds.size === 0) continue;
38447
+ if (readerEffect.depNames.size === 0) continue;
39100
38448
  let chainedStateName = null;
39101
38449
  for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
39102
- const writtenStateSymbolId = stateSymbolIds.get(writtenName);
39103
- if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
38450
+ if (!readerEffect.depNames.has(writtenName)) continue;
39104
38451
  if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
39105
38452
  chainedStateName = writtenName;
39106
38453
  break;
@@ -39377,7 +38724,7 @@ const noEffectEventHandler = defineRule({
39377
38724
  return {
39378
38725
  ...propStackTracker.visitors,
39379
38726
  CallExpression(node) {
39380
- if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes) || (node.arguments?.length ?? 0) < 2) return;
38727
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
39381
38728
  const callback = getEffectCallback(node);
39382
38729
  if (!callback) return;
39383
38730
  const analysis = getProgramAnalysis(node);
@@ -39497,14 +38844,14 @@ const noEffectEventInDeps = defineRule({
39497
38844
  if (!isNodeOfType(declaratorNode.id, "Identifier")) return;
39498
38845
  const initializer = declaratorNode.init;
39499
38846
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return;
39500
- if (!isReactHookCall(initializer, "useEffectEvent", context.scopes)) return;
38847
+ if (!isHookCall$2(initializer, "useEffectEvent")) return;
39501
38848
  if (isNonReactEffectEventCallee(initializer.callee, declaratorNode, context.scopes)) return;
39502
38849
  componentBindings.addBindingToCurrentFrame(declaratorNode.id.name);
39503
38850
  } });
39504
38851
  return {
39505
38852
  ...componentBindings.visitors,
39506
38853
  CallExpression(node) {
39507
- if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes) || node.arguments.length < 2) return;
38854
+ if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
39508
38855
  if (!componentBindings.isInsideComponent()) return;
39509
38856
  const depsNode = node.arguments[1];
39510
38857
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -39561,7 +38908,7 @@ const noEffectWithFreshDeps = defineRule({
39561
38908
  node: finding.reportNode,
39562
38909
  message: `A dependency inside this custom Hook changes every render because \`${finding.bindingName}\` is a new ${finding.kind} built fresh each time.`
39563
38910
  });
39564
- if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes)) return;
38911
+ if (!isHookCall$2(node, HOOKS_WITH_DEPS)) return;
39565
38912
  const args = node.arguments ?? [];
39566
38913
  if (args.length < 2) return;
39567
38914
  const depsNode = args[1];
@@ -39822,7 +39169,7 @@ const noEventHandler = defineRule({
39822
39169
  severity: "warn",
39823
39170
  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",
39824
39171
  create: (context) => ({ CallExpression(node) {
39825
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
39172
+ if (!isUseEffect(node)) return;
39826
39173
  const analysis = getProgramAnalysis(node);
39827
39174
  if (!analysis || hasCleanup(analysis, node)) return;
39828
39175
  const frames = collectBoundedEffectExecutionFrames(analysis, node);
@@ -40284,25 +39631,25 @@ const addDeclarationBindings = (statement, scope) => {
40284
39631
  }
40285
39632
  if (isNodeOfType(statement, "FunctionDeclaration") && statement.id) addPatternBindings(statement.id, scope);
40286
39633
  };
40287
- const collectRenderReachableNamesFromStatements = (statements, names, scope, scopes, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
39634
+ const collectRenderReachableNamesFromStatements = (statements, names, scope, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
40288
39635
  let hasReturn = false;
40289
- for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope, scopes, eventHandlerReferenceNames)) hasReturn = true;
39636
+ for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope, eventHandlerReferenceNames)) hasReturn = true;
40290
39637
  else addDeclarationBindings(statement, scope);
40291
39638
  return hasReturn;
40292
39639
  };
40293
- const collectRenderReachableNamesFromStatement = (statement, names, scope, scopes, eventHandlerReferenceNames) => {
39640
+ const collectRenderReachableNamesFromStatement = (statement, names, scope, eventHandlerReferenceNames) => {
40294
39641
  if (isNodeOfType(statement, "ReturnStatement")) {
40295
39642
  if (statement.argument) addNames(names, collectScopedReferenceNames(statement.argument, scope, eventHandlerReferenceNames));
40296
39643
  return true;
40297
39644
  }
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)) {
39645
+ if (isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "CallExpression") && isNodeOfType(statement.expression.callee, "Identifier") && isReactHookName(statement.expression.callee.name) && !EFFECT_HOOK_NAMES$1.has(statement.expression.callee.name)) {
40299
39646
  for (const argument of statement.expression.arguments ?? []) addNames(names, collectScopedReferenceNames(argument, scope, eventHandlerReferenceNames));
40300
39647
  return false;
40301
39648
  }
40302
- if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope), scopes, eventHandlerReferenceNames);
39649
+ if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope), eventHandlerReferenceNames);
40303
39650
  if (isNodeOfType(statement, "IfStatement")) {
40304
- const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope, scopes, eventHandlerReferenceNames);
40305
- const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope, scopes, eventHandlerReferenceNames) : false;
39651
+ const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope, eventHandlerReferenceNames);
39652
+ const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope, eventHandlerReferenceNames) : false;
40306
39653
  if (consequentHasReturn || alternateHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
40307
39654
  return consequentHasReturn || alternateHasReturn;
40308
39655
  }
@@ -40310,7 +39657,7 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
40310
39657
  let hasReturn = false;
40311
39658
  for (const switchCase of statement.cases ?? []) {
40312
39659
  const caseScope = createBlockBindingScope(scope);
40313
- if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope, scopes, eventHandlerReferenceNames)) continue;
39660
+ if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope, eventHandlerReferenceNames)) continue;
40314
39661
  hasReturn = true;
40315
39662
  if (switchCase.test) addNames(names, collectScopedReferenceNames(switchCase.test, scope, eventHandlerReferenceNames));
40316
39663
  }
@@ -40318,25 +39665,25 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
40318
39665
  return hasReturn;
40319
39666
  }
40320
39667
  if (isNodeOfType(statement, "TryStatement")) {
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;
39668
+ const blockHasReturn = collectRenderReachableNamesFromStatement(statement.block, names, scope, eventHandlerReferenceNames);
39669
+ const handlerHasReturn = statement.handler ? collectRenderReachableNamesFromStatement(statement.handler, names, scope, eventHandlerReferenceNames) : false;
39670
+ const finalizerHasReturn = statement.finalizer ? collectRenderReachableNamesFromStatement(statement.finalizer, names, scope, eventHandlerReferenceNames) : false;
40324
39671
  return blockHasReturn || handlerHasReturn || finalizerHasReturn;
40325
39672
  }
40326
39673
  if (isNodeOfType(statement, "CatchClause")) {
40327
39674
  const catchScope = createBlockBindingScope(scope);
40328
39675
  addPatternBindings(statement.param, catchScope);
40329
- return collectRenderReachableNamesFromStatement(statement.body, names, catchScope, scopes, eventHandlerReferenceNames);
39676
+ return collectRenderReachableNamesFromStatement(statement.body, names, catchScope, eventHandlerReferenceNames);
40330
39677
  }
40331
39678
  if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
40332
- const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, scopes, eventHandlerReferenceNames);
39679
+ const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
40333
39680
  if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
40334
39681
  return bodyHasReturn;
40335
39682
  }
40336
39683
  if (isNodeOfType(statement, "ForStatement")) {
40337
39684
  const loopScope = createBlockBindingScope(scope);
40338
39685
  if (statement.init) addDeclarationBindings(statement.init, loopScope);
40339
- if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, scopes, eventHandlerReferenceNames)) return false;
39686
+ if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
40340
39687
  if (statement.init) addNames(names, collectScopedReferenceNames(statement.init, loopScope, eventHandlerReferenceNames));
40341
39688
  if (statement.test) addNames(names, collectScopedReferenceNames(statement.test, loopScope, eventHandlerReferenceNames));
40342
39689
  if (statement.update) addNames(names, collectScopedReferenceNames(statement.update, loopScope, eventHandlerReferenceNames));
@@ -40346,22 +39693,22 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
40346
39693
  const rightNames = collectScopedReferenceNames(statement.right, scope, eventHandlerReferenceNames);
40347
39694
  const loopScope = createBlockBindingScope(scope);
40348
39695
  if (isNodeOfType(statement.left, "VariableDeclaration")) addDeclarationBindings(statement.left, loopScope);
40349
- if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, scopes, eventHandlerReferenceNames)) return false;
39696
+ if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
40350
39697
  addNames(names, rightNames);
40351
39698
  return true;
40352
39699
  }
40353
- if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope, scopes, eventHandlerReferenceNames);
39700
+ if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
40354
39701
  if (isNodeOfType(statement, "WithStatement")) {
40355
- const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, scopes, eventHandlerReferenceNames);
39702
+ const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
40356
39703
  if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.object, scope, eventHandlerReferenceNames));
40357
39704
  return bodyHasReturn;
40358
39705
  }
40359
39706
  return false;
40360
39707
  };
40361
- const collectRenderReachableNames = (componentBody, scopes, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
39708
+ const collectRenderReachableNames = (componentBody, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
40362
39709
  const names = /* @__PURE__ */ new Set();
40363
39710
  if (!isNodeOfType(componentBody, "BlockStatement")) return names;
40364
- collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(), scopes, eventHandlerReferenceNames);
39711
+ collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(), eventHandlerReferenceNames);
40365
39712
  return names;
40366
39713
  };
40367
39714
  //#endregion
@@ -40384,36 +39731,42 @@ const expandTransitiveDependencies = (seedNames, dependencyGraph) => {
40384
39731
  };
40385
39732
  //#endregion
40386
39733
  //#region src/plugin/rules/state-and-effects/utils/collect-function-like-local-names.ts
40387
- const isFunctionLikeReference = (node, functionLikeLocalNames, scope, scopes) => {
40388
- if (isInlineFunctionExpression(node) || isReactHookCall(node, "useCallback", scopes)) return true;
39734
+ const isUseCallbackCall = (node) => isNodeOfType(node, "CallExpression") && getCalleeName(node.callee) === "useCallback";
39735
+ const getCalleeName = (node) => {
39736
+ if (isNodeOfType(node, "Identifier")) return node.name;
39737
+ if (isNodeOfType(node, "MemberExpression")) return getStaticMemberPropertyName(node);
39738
+ return null;
39739
+ };
39740
+ const isFunctionLikeReference = (node, functionLikeLocalNames, scope) => {
39741
+ if (isInlineFunctionExpression(node) || isUseCallbackCall(node)) return true;
40389
39742
  if (isNodeOfType(node, "Identifier")) return functionLikeLocalNames.has(resolveBindingName(scope, node.name));
40390
39743
  const memberReferenceName = getStaticMemberReferenceName(node, (name) => resolveBindingName(scope, name));
40391
39744
  return Boolean(memberReferenceName && functionLikeLocalNames.has(memberReferenceName));
40392
39745
  };
40393
- const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope, scopes) => {
39746
+ const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope) => {
40394
39747
  if (!isNodeOfType(node, "ObjectExpression")) return;
40395
39748
  for (const property of node.properties ?? []) {
40396
39749
  if (!isNodeOfType(property, "Property")) continue;
40397
39750
  const propertyName = getStaticPropertyKeyName(property, { stringifyNonStringLiterals: true });
40398
39751
  if (!propertyName) continue;
40399
- if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope, scopes)) continue;
39752
+ if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope)) continue;
40400
39753
  functionLikeLocalNames.add(`${objectBindingName}.${propertyName}`);
40401
39754
  }
40402
39755
  };
40403
- const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope, scopes) => {
39756
+ const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope) => {
40404
39757
  if (!isNodeOfType(statement, "VariableDeclaration")) return;
40405
39758
  const declarationScope = getVariableDeclarationScope(statement, scope);
40406
39759
  for (const declarator of statement.declarations ?? []) {
40407
39760
  const declaredBindingNames = addPatternBindings(declarator.id, declarationScope);
40408
39761
  if (!declarator.init) continue;
40409
- const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope, scopes);
39762
+ const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope);
40410
39763
  for (const declaredBindingName of declaredBindingNames) {
40411
39764
  if (isFunctionReference) functionLikeLocalNames.add(declaredBindingName);
40412
- addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope, scopes);
39765
+ addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope);
40413
39766
  }
40414
39767
  }
40415
39768
  };
40416
- const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope, scopes) => {
39769
+ const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope) => {
40417
39770
  if (isNodeOfType(statement, "FunctionDeclaration")) {
40418
39771
  if (statement.id) {
40419
39772
  const declaredBindingNames = addPatternBindings(statement.id, scope);
@@ -40422,61 +39775,61 @@ const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope,
40422
39775
  return;
40423
39776
  }
40424
39777
  if (isNodeOfType(statement, "VariableDeclaration")) {
40425
- addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope, scopes);
39778
+ addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope);
40426
39779
  return;
40427
39780
  }
40428
39781
  if (isNodeOfType(statement, "BlockStatement")) {
40429
- collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope), scopes);
39782
+ collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope));
40430
39783
  return;
40431
39784
  }
40432
39785
  if (isNodeOfType(statement, "IfStatement")) {
40433
- collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope, scopes);
40434
- if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope, scopes);
39786
+ collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope);
39787
+ if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope);
40435
39788
  return;
40436
39789
  }
40437
39790
  if (isNodeOfType(statement, "SwitchStatement")) {
40438
- for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope), scopes);
39791
+ for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope));
40439
39792
  return;
40440
39793
  }
40441
39794
  if (isNodeOfType(statement, "TryStatement")) {
40442
- collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope, scopes);
39795
+ collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope);
40443
39796
  if (statement.handler) {
40444
39797
  const catchScope = createBlockBindingScope(scope);
40445
39798
  addPatternBindings(statement.handler.param, catchScope);
40446
- collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope, scopes);
39799
+ collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope);
40447
39800
  }
40448
- if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope, scopes);
39801
+ if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope);
40449
39802
  return;
40450
39803
  }
40451
39804
  if (isNodeOfType(statement, "ForStatement")) {
40452
39805
  const loopScope = createBlockBindingScope(scope);
40453
- if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope, scopes);
40454
- collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope, scopes);
39806
+ if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope);
39807
+ collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
40455
39808
  return;
40456
39809
  }
40457
39810
  if (isNodeOfType(statement, "ForInStatement") || isNodeOfType(statement, "ForOfStatement")) {
40458
39811
  const loopScope = createBlockBindingScope(scope);
40459
- if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope, scopes);
39812
+ if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope);
40460
39813
  else addPatternBindings(statement.left, loopScope);
40461
- collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope, scopes);
39814
+ collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
40462
39815
  return;
40463
39816
  }
40464
39817
  if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
40465
- collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope, scopes);
39818
+ collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
40466
39819
  return;
40467
39820
  }
40468
- if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope, scopes);
39821
+ if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
40469
39822
  };
40470
- const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope, scopes) => {
40471
- for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope, scopes);
39823
+ const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope) => {
39824
+ for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope);
40472
39825
  };
40473
- const collectFunctionLikeLocalNames = (componentBody, scopes) => {
39826
+ const collectFunctionLikeLocalNames = (componentBody) => {
40474
39827
  const functionLikeLocalNames = /* @__PURE__ */ new Set();
40475
39828
  if (!isNodeOfType(componentBody, "BlockStatement")) return functionLikeLocalNames;
40476
39829
  let previousSize = -1;
40477
39830
  while (previousSize !== functionLikeLocalNames.size) {
40478
39831
  previousSize = functionLikeLocalNames.size;
40479
- collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope(), scopes);
39832
+ collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope());
40480
39833
  }
40481
39834
  return functionLikeLocalNames;
40482
39835
  };
@@ -40516,17 +39869,17 @@ const noEventTriggerState = defineRule({
40516
39869
  create: (context) => {
40517
39870
  const checkComponent = (componentBody) => {
40518
39871
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
40519
- const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
39872
+ const useStateBindings = collectUseStateBindings(componentBody);
40520
39873
  if (useStateBindings.length === 0) return;
40521
39874
  const analysis = getProgramAnalysis(componentBody);
40522
39875
  if (!analysis) return;
40523
39876
  const localStateNames = new Set(useStateBindings.map((binding) => binding.valueName));
40524
- const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
39877
+ const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
40525
39878
  const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
40526
- const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames), dependencyGraph);
39879
+ const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody, eventHandlerReferenceNames), dependencyGraph);
40527
39880
  walkAst(componentBody, (effectCall) => {
40528
39881
  if (!isNodeOfType(effectCall, "CallExpression")) return;
40529
- if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) return;
39882
+ if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) return;
40530
39883
  if ((effectCall.arguments?.length ?? 0) < 2) return;
40531
39884
  const depsNode = effectCall.arguments[1];
40532
39885
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -40591,17 +39944,10 @@ const isShadowedByLocalBinding = (identifier) => {
40591
39944
  };
40592
39945
  const isRealFetchCall = (node) => {
40593
39946
  if (!isNodeOfType(node, "CallExpression")) return false;
40594
- const callee = stripParenExpression(node.callee);
40595
- if (isNodeOfType(callee, "Identifier") && FETCH_CALLEE_NAMES.has(callee.name)) return !isShadowedByLocalBinding(callee);
40596
- if (!isNodeOfType(callee, "MemberExpression")) return false;
40597
- const receiver = stripParenExpression(callee.object);
40598
- return isNodeOfType(receiver, "Identifier") && FETCH_MEMBER_OBJECTS.has(receiver.name) && !isShadowedByLocalBinding(receiver);
40599
- };
40600
- const isXmlHttpRequestConstruction = (node) => {
40601
- if (!isNodeOfType(node, "NewExpression")) return false;
40602
- const callee = stripParenExpression(node.callee);
40603
- return isNodeOfType(callee, "Identifier") && callee.name === "XMLHttpRequest";
39947
+ if (isNodeOfType(node.callee, "Identifier") && FETCH_CALLEE_NAMES.has(node.callee.name)) return !isShadowedByLocalBinding(node.callee);
39948
+ return isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && FETCH_MEMBER_OBJECTS.has(node.callee.object.name) && !isShadowedByLocalBinding(node.callee.object);
40604
39949
  };
39950
+ const isXmlHttpRequestConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "XMLHttpRequest";
40605
39951
  const isNetworkRequest = (node) => isRealFetchCall(node) || isXmlHttpRequestConstruction(node);
40606
39952
  const resolveLocalFunction = (expression, context) => {
40607
39953
  if (!expression) return null;
@@ -40917,17 +40263,6 @@ const DOM_MEASUREMENT_NAMES = new Set([
40917
40263
  "scrollHeight"
40918
40264
  ]);
40919
40265
  const MEASUREMENT_HELPER_CALLEE_PATTERN = /^(?:get|measure|read)\w*(?:Width|Height|Rect|Rects|Size|Bounds|Position)$/;
40920
- const IMPERATIVE_DOM_MUTATION_NAMES = new Set([
40921
- "blur",
40922
- "focus",
40923
- "restoreSelection",
40924
- "scroll",
40925
- "scrollBy",
40926
- "scrollIntoView",
40927
- "scrollTo",
40928
- "setRangeText",
40929
- "setSelectionRange"
40930
- ]);
40931
40266
  const subtreeReadsDomMeasurement = (root) => {
40932
40267
  if (!root) return false;
40933
40268
  let found = false;
@@ -40946,66 +40281,29 @@ const subtreeReadsDomMeasurement = (root) => {
40946
40281
  });
40947
40282
  return found;
40948
40283
  };
40949
- const collectFunctionNamesMatchingBody = (program, matchesBody) => {
40284
+ const collectMeasuringFunctionNames = (program) => {
40950
40285
  const names = /* @__PURE__ */ new Set();
40951
40286
  walkAst(program, (child) => {
40952
40287
  if (isNodeOfType(child, "FunctionDeclaration")) {
40953
- if (child.id && isNodeOfType(child.id, "Identifier") && matchesBody(child.body)) names.add(child.id.name);
40288
+ if (child.id && isNodeOfType(child.id, "Identifier") && subtreeReadsDomMeasurement(child.body)) names.add(child.id.name);
40954
40289
  return;
40955
40290
  }
40956
40291
  if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier")) return;
40957
40292
  let functionValue = child.init;
40958
40293
  if (functionValue && isNodeOfType(functionValue, "CallExpression") && isNodeOfType(functionValue.callee, "Identifier") && /^use[A-Z]/.test(functionValue.callee.name)) functionValue = functionValue.arguments?.[0];
40959
- if (functionValue && isFunctionLike$1(functionValue) && matchesBody(functionValue.body)) names.add(child.id.name);
40294
+ if (functionValue && isFunctionLike$1(functionValue) && subtreeReadsDomMeasurement(functionValue.body)) names.add(child.id.name);
40960
40295
  });
40961
40296
  return names;
40962
40297
  };
40963
- const collectMeasuringFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeReadsDomMeasurement);
40964
- const subtreeMutatesDomImperatively = (root) => {
40965
- if (!root || isFunctionLike$1(root)) return false;
40966
- let found = false;
40967
- walkAst(root, (child) => {
40968
- if (found) return false;
40969
- if (child !== root && isFunctionLike$1(child)) return false;
40970
- if (!isNodeOfType(child, "CallExpression")) return;
40971
- const callee = stripParenExpression(child.callee);
40972
- const propertyName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
40973
- if (propertyName !== null && IMPERATIVE_DOM_MUTATION_NAMES.has(propertyName)) {
40974
- found = true;
40975
- return false;
40976
- }
40977
- });
40978
- return found;
40979
- };
40980
- const collectImperativeDomFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeMutatesDomImperatively);
40981
- const callsAnyName = (root, names, shouldSkipNestedFunctions = false) => {
40982
- if (!root || names.size === 0 || shouldSkipNestedFunctions && isFunctionLike$1(root)) return false;
40298
+ const callsAnyName = (root, names) => {
40299
+ if (!root || names.size === 0) return false;
40983
40300
  let found = false;
40984
40301
  walkAst(root, (child) => {
40985
40302
  if (found) return false;
40986
- if (shouldSkipNestedFunctions && child !== root && isFunctionLike$1(child)) return false;
40987
40303
  if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && names.has(child.callee.name)) found = true;
40988
40304
  });
40989
40305
  return found;
40990
40306
  };
40991
- const isFollowedByImperativeDomMutation = (call, imperativeDomFunctionNames) => {
40992
- let statement = call;
40993
- let parent = statement.parent;
40994
- while (parent) {
40995
- const statements = isNodeOfType(parent, "BlockStatement") || isNodeOfType(parent, "Program") || isNodeOfType(parent, "StaticBlock") ? parent.body : isNodeOfType(parent, "SwitchCase") ? parent.consequent : null;
40996
- if (statements) {
40997
- const statementIndex = statements.findIndex((siblingStatement) => siblingStatement === statement);
40998
- if (statementIndex >= 0) {
40999
- const nextStatement = statements[statementIndex + 1];
41000
- return subtreeMutatesDomImperatively(nextStatement) || callsAnyName(nextStatement, imperativeDomFunctionNames, true);
41001
- }
41002
- }
41003
- if (isFunctionLike$1(parent) || parent.type.endsWith("Statement") && !isNodeOfType(parent, "ExpressionStatement")) return false;
41004
- statement = parent;
41005
- parent = parent.parent;
41006
- }
41007
- return false;
41008
- };
41009
40307
  const isInsideStartViewTransition = (node) => {
41010
40308
  let cursor = node.parent;
41011
40309
  while (cursor) {
@@ -41046,12 +40344,11 @@ const importsImperativeDomLibrary = (program) => {
41046
40344
  };
41047
40345
  const hasExemptFlushSyncCall = (program, localName) => {
41048
40346
  const measuringFunctionNames = collectMeasuringFunctionNames(program);
41049
- const imperativeDomFunctionNames = collectImperativeDomFunctionNames(program);
41050
40347
  let exempt = false;
41051
40348
  walkAst(program, (child) => {
41052
40349
  if (exempt) return false;
41053
40350
  if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "Identifier") || child.callee.name !== localName) return;
41054
- if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames) || isFollowedByImperativeDomMutation(child, imperativeDomFunctionNames)) {
40351
+ if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames)) {
41055
40352
  exempt = true;
41056
40353
  return false;
41057
40354
  }
@@ -41616,223 +40913,41 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
41616
40913
  if (leftResult === false && rightResult === false) return false;
41617
40914
  return null;
41618
40915
  };
41619
- const readHydrationConditionResult = (expression, context, runtime, state) => {
40916
+ const readHydrationConditionResult = (expression, context, runtime) => {
41620
40917
  const unwrappedExpression = stripParenExpression(expression);
41621
40918
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
41622
40919
  if (predicateMatch) return predicateMatch[`${runtime}Result`];
41623
40920
  const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
41624
40921
  if (staticResult !== null) return staticResult;
41625
- const expressionSymbol = isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression) : null;
41626
- const parameterValue = expressionSymbol ? state.parameterValuesBySymbolId.get(expressionSymbol.id) : null;
41627
- if (expressionSymbol && parameterValue && !state.visitedSymbolIds.has(expressionSymbol.id)) {
41628
- state.visitedSymbolIds.add(expressionSymbol.id);
41629
- const result = readHydrationConditionResult(parameterValue, context, runtime, state);
41630
- state.visitedSymbolIds.delete(expressionSymbol.id);
41631
- return result;
41632
- }
41633
- if (expressionSymbol && expressionSymbol.kind === "const" && expressionSymbol.initializer && expressionSymbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(expressionSymbol.id)) {
41634
- state.visitedSymbolIds.add(expressionSymbol.id);
41635
- const result = readHydrationConditionResult(expressionSymbol.initializer, context, runtime, state);
41636
- state.visitedSymbolIds.delete(expressionSymbol.id);
41637
- return result;
41638
- }
41639
- if (isNodeOfType(unwrappedExpression, "CallExpression")) {
41640
- const callArguments = unwrappedExpression.arguments ?? [];
41641
- if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
41642
- allowGlobalReactNamespace: true,
41643
- resolveNamedAliases: true
41644
- })) {
41645
- const callbackArgument = callArguments[0];
41646
- if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
41647
- const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
41648
- return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? readHydrationFunctionResult(callbackFunction, context, runtime, state) : null;
41649
- }
41650
- const callee = stripParenExpression(unwrappedExpression.callee);
41651
- if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return readHydrationConditionResult(callArguments[0], context, runtime, state);
41652
- const helperFunction = resolveExactLocalFunction(callee, context.scopes);
41653
- if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
41654
- const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
41655
- for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
41656
- const parameter = helperFunction.params[parameterIndex];
41657
- const argument = callArguments[parameterIndex];
41658
- if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
41659
- const parameterSymbol = context.scopes.symbolFor(parameter);
41660
- if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
41661
- }
41662
- return readHydrationFunctionResult(helperFunction, context, runtime, {
41663
- ...state,
41664
- parameterValuesBySymbolId
41665
- });
41666
- }
41667
40922
  if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
41668
- const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
40923
+ const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
41669
40924
  return argumentResult === null ? null : !argumentResult;
41670
40925
  }
41671
40926
  if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
41672
- return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime, state), readHydrationConditionResult(unwrappedExpression.right, context, runtime, state));
40927
+ return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
41673
40928
  };
41674
- const readHydrationStatementResult = (statement, context, runtime, state) => {
41675
- if (isNodeOfType(statement, "ReturnStatement")) return {
41676
- didReturn: true,
41677
- value: statement.argument ? readHydrationConditionResult(statement.argument, context, runtime, state) : null
41678
- };
41679
- if (isNodeOfType(statement, "BlockStatement")) {
41680
- for (const childStatement of statement.body) {
41681
- const result = readHydrationStatementResult(childStatement, context, runtime, state);
41682
- if (result.didReturn) return result;
41683
- if (statementAlwaysExits(childStatement)) break;
41684
- }
41685
- return {
41686
- didReturn: false,
41687
- value: null
41688
- };
41689
- }
41690
- if (!isNodeOfType(statement, "IfStatement")) return {
41691
- didReturn: false,
41692
- value: null
41693
- };
41694
- const conditionResult = readHydrationConditionResult(statement.test, context, runtime, state);
41695
- if (conditionResult !== null) {
41696
- const selectedBranch = conditionResult ? statement.consequent : statement.alternate;
41697
- return selectedBranch ? readHydrationStatementResult(selectedBranch, context, runtime, state) : {
41698
- didReturn: false,
41699
- value: null
41700
- };
41701
- }
41702
- const consequentResult = readHydrationStatementResult(statement.consequent, context, runtime, state);
41703
- const alternateResult = statement.alternate ? readHydrationStatementResult(statement.alternate, context, runtime, state) : {
41704
- didReturn: false,
41705
- value: null
41706
- };
41707
- return consequentResult.didReturn && alternateResult.didReturn && consequentResult.value !== null && consequentResult.value === alternateResult.value ? consequentResult : {
41708
- didReturn: consequentResult.didReturn || alternateResult.didReturn,
41709
- value: null
41710
- };
41711
- };
41712
- const readHydrationFunctionResult = (functionNode, context, runtime, state) => {
41713
- if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
41714
- state.visitedFunctionNodes.add(functionNode);
41715
- const result = isNodeOfType(functionNode.body, "BlockStatement") ? readHydrationStatementResult(functionNode.body, context, runtime, state).value : readHydrationConditionResult(functionNode.body, context, runtime, state);
41716
- state.visitedFunctionNodes.delete(functionNode);
41717
- return result;
41718
- };
41719
- const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, scopes) => {
41720
- const left = stripParenExpression(leftExpression);
41721
- const right = stripParenExpression(rightExpression);
41722
- if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
41723
- const leftSymbol = scopes.symbolFor(left);
41724
- const rightSymbol = scopes.symbolFor(right);
41725
- return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
41726
- }
41727
- if (isNodeOfType(left, "MemberExpression") && isNodeOfType(right, "MemberExpression")) return doEquivalentExpressionBindingsMatch(left.object, right.object, scopes) && (!left.computed || doEquivalentExpressionBindingsMatch(left.property, right.property, scopes));
41728
- if (isNodeOfType(left, "CallExpression") && isNodeOfType(right, "CallExpression")) {
41729
- const rightArguments = right.arguments ?? [];
41730
- return doEquivalentExpressionBindingsMatch(left.callee, right.callee, scopes) && (left.arguments ?? []).every((argument, index) => {
41731
- const rightArgument = rightArguments[index];
41732
- return Boolean(rightArgument && doEquivalentExpressionBindingsMatch(argument, rightArgument, scopes));
41733
- });
41734
- }
41735
- return true;
41736
- };
41737
- const areHelperReturnValuesEquivalent = (leftValue, rightValue, context) => {
41738
- if (areExpressionsStructurallyEqual(leftValue, rightValue)) return doEquivalentExpressionBindingsMatch(leftValue, rightValue, context.scopes);
41739
- const leftBoolean = readInitialStateBoolean(leftValue, context.scopes);
41740
- const rightBoolean = readInitialStateBoolean(rightValue, context.scopes);
41741
- return leftBoolean !== null && rightBoolean !== null && leftBoolean === rightBoolean;
41742
- };
41743
- const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
41744
- const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
41745
- return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
41746
- };
41747
- const matchHydrationConditionInternal = (expression, context, state) => {
40929
+ const matchHydrationCondition = (expression, context) => {
41748
40930
  const unwrappedExpression = stripParenExpression(expression);
41749
40931
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
41750
40932
  if (predicateMatch) return {
41751
40933
  predicateMatch,
41752
40934
  predicateNode: unwrappedExpression
41753
40935
  };
41754
- if (isNodeOfType(unwrappedExpression, "Identifier")) {
41755
- const symbol = context.scopes.symbolFor(unwrappedExpression);
41756
- const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
41757
- if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
41758
- state.visitedSymbolIds.add(symbol.id);
41759
- const match = matchHydrationConditionInternal(parameterValue, context, state);
41760
- state.visitedSymbolIds.delete(symbol.id);
41761
- return match;
41762
- }
41763
- if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
41764
- state.visitedSymbolIds.add(symbol.id);
41765
- const match = matchHydrationConditionInternal(symbol.initializer, context, state);
41766
- state.visitedSymbolIds.delete(symbol.id);
41767
- return match;
41768
- }
41769
- if (isNodeOfType(unwrappedExpression, "CallExpression")) {
41770
- const callArguments = unwrappedExpression.arguments ?? [];
41771
- if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
41772
- allowGlobalReactNamespace: true,
41773
- resolveNamedAliases: true
41774
- })) {
41775
- const callbackArgument = callArguments[0];
41776
- if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
41777
- const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
41778
- return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionResult(callbackFunction, context, state) : null;
41779
- }
41780
- const callee = stripParenExpression(unwrappedExpression.callee);
41781
- if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return matchHydrationConditionInternal(callArguments[0], context, state);
41782
- const helperFunction = resolveExactLocalFunction(callee, context.scopes);
41783
- if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
41784
- const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
41785
- for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
41786
- const parameter = helperFunction.params[parameterIndex];
41787
- const argument = callArguments[parameterIndex];
41788
- if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
41789
- const parameterSymbol = context.scopes.symbolFor(parameter);
41790
- if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
41791
- }
41792
- return matchHydrationFunctionResult(helperFunction, context, {
41793
- ...state,
41794
- parameterValuesBySymbolId
41795
- });
41796
- }
41797
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
40936
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
41798
40937
  if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
41799
- const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
41800
- const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
40938
+ const leftMatch = matchHydrationCondition(unwrappedExpression.left, context);
40939
+ const rightMatch = matchHydrationCondition(unwrappedExpression.right, context);
40940
+ if (leftMatch && rightMatch) {
40941
+ const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client");
40942
+ const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server");
40943
+ return clientResult !== null && serverResult !== null && clientResult !== serverResult ? leftMatch : null;
40944
+ }
41801
40945
  const nestedMatch = leftMatch ?? rightMatch;
41802
40946
  if (!nestedMatch) return null;
41803
- const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client", state);
41804
- const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server", state);
41805
- return clientResult !== null && serverResult !== null && clientResult === serverResult ? null : nestedMatch;
41806
- };
41807
- const matchHydrationReturningStatement = (statement, context, state) => {
41808
- if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? matchHydrationConditionInternal(statement.argument, context, state) : null;
41809
- if (isNodeOfType(statement, "IfStatement")) {
41810
- const conditionMatch = matchHydrationConditionInternal(statement.test, context, state);
41811
- const consequentValues = getReturnedValues(statement.consequent);
41812
- const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
41813
- if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
41814
- return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
41815
- }
41816
- if (!isNodeOfType(statement, "BlockStatement")) return null;
41817
- for (const childStatement of statement.body) {
41818
- const match = matchHydrationReturningStatement(childStatement, context, state);
41819
- if (match) return match;
41820
- if (statementAlwaysExits(childStatement)) break;
41821
- }
41822
- return null;
40947
+ const otherResult = readInitialStateBoolean(leftMatch ? unwrappedExpression.right : unwrappedExpression.left, context.scopes);
40948
+ if (unwrappedExpression.operator === "&&" && otherResult === false || unwrappedExpression.operator === "||" && otherResult === true) return null;
40949
+ return nestedMatch;
41823
40950
  };
41824
- const matchHydrationFunctionResult = (functionNode, context, state) => {
41825
- if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
41826
- state.visitedFunctionNodes.add(functionNode);
41827
- const match = isNodeOfType(functionNode.body, "BlockStatement") ? matchHydrationReturningStatement(functionNode.body, context, state) : matchHydrationConditionInternal(functionNode.body, context, state);
41828
- state.visitedFunctionNodes.delete(functionNode);
41829
- return match;
41830
- };
41831
- const matchHydrationCondition = (expression, context) => matchHydrationConditionInternal(expression, context, {
41832
- parameterValuesBySymbolId: /* @__PURE__ */ new Map(),
41833
- visitedFunctionNodes: /* @__PURE__ */ new Set(),
41834
- visitedSymbolIds: /* @__PURE__ */ new Set()
41835
- });
41836
40951
  const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
41837
40952
  const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
41838
40953
  if (!leftNode || !rightNode) return leftNode === rightNode;
@@ -41975,17 +41090,17 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
41975
41090
  const { predicateMatch, predicateNode } = conditionMatch;
41976
41091
  if (reportedNodes.has(predicateNode)) return;
41977
41092
  if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
41978
- const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes);
41093
+ const componentOrHookNode = findRenderPhaseComponentOrHook(predicateNode, context.scopes);
41979
41094
  if (!componentOrHookNode) return;
41980
41095
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
41981
- if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes)) return;
41096
+ if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
41982
41097
  if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
41983
- const attribute = findEnclosingJsxAttribute(conditionNode);
41098
+ const attribute = findEnclosingJsxAttribute(predicateNode);
41984
41099
  if (!attribute || isEventHandlerAttribute(attribute)) return;
41985
41100
  }
41986
- if (fileIsEmailTemplate || isGatedByFalsyInitialState(conditionNode, context.scopes)) return;
41987
- if (isAfterClientOnlyEarlyReturn(conditionNode, componentOrHookNode, context.scopes)) return;
41988
- const openingElement = findEnclosingJsxOpeningElement(conditionNode);
41101
+ if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
41102
+ if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
41103
+ const openingElement = findEnclosingJsxOpeningElement(predicateNode);
41989
41104
  if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
41990
41105
  if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
41991
41106
  if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
@@ -42362,7 +41477,7 @@ const noInitializeState = defineRule({
42362
41477
  tags: ["test-noise"],
42363
41478
  recommendation: "Pass the initial value directly to useState() instead of setting it from a mount-only useEffect. For SSR hydration, prefer useSyncExternalStore().",
42364
41479
  create: (context) => ({ CallExpression(node) {
42365
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
41480
+ if (!isUseEffect(node)) return;
42366
41481
  const dependencies = node.arguments?.[1];
42367
41482
  if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
42368
41483
  const analysis = getProgramAnalysis(node);
@@ -42725,8 +41840,7 @@ const noJsxElementType = defineRule({
42725
41840
  create: (context) => {
42726
41841
  let isJsxImported = false;
42727
41842
  const flaggedAnnotations = [];
42728
- const collectComponentReturnType = (functionNode, returnType) => {
42729
- if (!(isNodeOfType(functionNode, "TSDeclareFunction") ? Boolean(functionNode.id && isReactComponentName(functionNode.id.name)) : isComponentFunction$1(functionNode))) return;
41843
+ const checkReturnType = (returnType) => {
42730
41844
  const typeAnnotation = extractReturnTypeAnnotation(returnType);
42731
41845
  if (!typeAnnotation) return;
42732
41846
  if (isJsxElementTypeReference(typeAnnotation)) flaggedAnnotations.push(typeAnnotation);
@@ -42736,16 +41850,19 @@ const noJsxElementType = defineRule({
42736
41850
  if (isJsxImportBinding(node)) isJsxImported = true;
42737
41851
  },
42738
41852
  FunctionDeclaration(node) {
42739
- collectComponentReturnType(node, node.returnType);
41853
+ checkReturnType(node.returnType);
42740
41854
  },
42741
41855
  ArrowFunctionExpression(node) {
42742
- collectComponentReturnType(node, node.returnType);
41856
+ checkReturnType(node.returnType);
42743
41857
  },
42744
41858
  FunctionExpression(node) {
42745
- collectComponentReturnType(node, node.returnType);
41859
+ checkReturnType(node.returnType);
42746
41860
  },
42747
41861
  TSDeclareFunction(node) {
42748
- collectComponentReturnType(node, node.returnType);
41862
+ checkReturnType(node.returnType);
41863
+ },
41864
+ TSMethodSignature(node) {
41865
+ checkReturnType(node.returnType);
42749
41866
  },
42750
41867
  "Program:exit"() {
42751
41868
  if (isJsxImported) return;
@@ -43974,7 +43091,7 @@ const noMirrorPropEffect = defineRule({
43974
43091
  const setterElement = elements[1];
43975
43092
  if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
43976
43093
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
43977
- if (!isReactHookCall(declarator.init, "useState", context.scopes)) continue;
43094
+ if (!isHookCall$2(declarator.init, "useState")) continue;
43978
43095
  const initializer = declarator.init.arguments?.[0];
43979
43096
  if (!initializer) continue;
43980
43097
  const propRootName = getPropRootName(initializer, propNames);
@@ -43992,7 +43109,7 @@ const noMirrorPropEffect = defineRule({
43992
43109
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
43993
43110
  const effectCall = unwrapDiscardedExpression(statement);
43994
43111
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
43995
- if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
43112
+ if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
43996
43113
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
43997
43114
  const depsNode = effectCall.arguments[1];
43998
43115
  if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
@@ -44401,7 +43518,7 @@ const noMultiComp = defineRule({
44401
43518
  });
44402
43519
  //#endregion
44403
43520
  //#region src/plugin/rules/state-and-effects/no-mutable-in-deps.ts
44404
- const collectUseRefBindingNames = (componentBody, scopes) => {
43521
+ const collectUseRefBindingNames = (componentBody) => {
44405
43522
  const useRefBindings = /* @__PURE__ */ new Set();
44406
43523
  if (!isNodeOfType(componentBody, "BlockStatement")) return useRefBindings;
44407
43524
  for (const statement of componentBody.body ?? []) {
@@ -44409,7 +43526,7 @@ const collectUseRefBindingNames = (componentBody, scopes) => {
44409
43526
  for (const declarator of statement.declarations ?? []) {
44410
43527
  if (!isNodeOfType(declarator.id, "Identifier")) continue;
44411
43528
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
44412
- if (!isReactHookCall(declarator.init, "useRef", scopes)) continue;
43529
+ if (!isHookCall$2(declarator.init, "useRef")) continue;
44413
43530
  useRefBindings.add(declarator.id.name);
44414
43531
  }
44415
43532
  }
@@ -44444,12 +43561,12 @@ const noMutableInDeps = defineRule({
44444
43561
  create: (context) => {
44445
43562
  const checkComponent = (componentBody, componentParams = []) => {
44446
43563
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
44447
- const useRefBindingNames = collectUseRefBindingNames(componentBody, context.scopes);
43564
+ const useRefBindingNames = collectUseRefBindingNames(componentBody);
44448
43565
  const localBindingNames = collectLocalBindingNames(componentBody);
44449
43566
  for (const param of componentParams) collectPatternNames(param, localBindingNames);
44450
43567
  walkAst(componentBody, (child) => {
44451
43568
  if (!isNodeOfType(child, "CallExpression")) return;
44452
- if (!isReactHookCall(child, HOOKS_WITH_DEPS, context.scopes)) return;
43569
+ if (!isHookCall$2(child, HOOKS_WITH_DEPS)) return;
44453
43570
  if ((child.arguments?.length ?? 0) < 2) return;
44454
43571
  const depsNode = child.arguments[1];
44455
43572
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -46279,7 +45396,6 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
46279
45396
  "useMatchMedia",
46280
45397
  "useMediaJobProgress",
46281
45398
  "useMediaQuery",
46282
- "useMediaQueryState",
46283
45399
  "useResizeObserver",
46284
45400
  "useVisibility",
46285
45401
  "useWindowSize"
@@ -46316,30 +45432,9 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
46316
45432
  if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
46317
45433
  return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
46318
45434
  };
46319
- const getLocalHookExternalStateProof = (analysis, ref) => {
46320
- let hookFunction = resolveToFunction(ref);
46321
- if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
46322
- const definitionNode = definition.node;
46323
- if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
46324
- const initializer = stripParenExpression(definitionNode.init);
46325
- if (!isNodeOfType(initializer, "CallExpression")) continue;
46326
- const callee = stripParenExpression(initializer.callee);
46327
- if (!isNodeOfType(callee, "Identifier")) continue;
46328
- const calleeReference = getRef(analysis, callee);
46329
- if (!calleeReference) continue;
46330
- hookFunction = resolveToFunction(calleeReference);
46331
- if (hookFunction) break;
46332
- }
46333
- if (!hookFunction) return null;
46334
- const returnedReferences = collectFunctionReturnStatements(hookFunction).flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
46335
- if (returnedReferences.length === 0) return null;
46336
- return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
46337
- };
46338
- const isExternalSubscriptionHookRef = (analysis, ref) => {
45435
+ const isExternalSubscriptionHookRef = (ref) => {
46339
45436
  const identifier = ref.identifier;
46340
45437
  if (!isNodeOfType(identifier, "Identifier")) return false;
46341
- const localHookProof = getLocalHookExternalStateProof(analysis, ref);
46342
- if (localHookProof !== null) return localHookProof;
46343
45438
  if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
46344
45439
  return Boolean(ref.resolved?.defs.some((def) => {
46345
45440
  const node = def.node;
@@ -46362,10 +45457,16 @@ const noPassDataToParent = defineRule({
46362
45457
  tags: ["test-noise"],
46363
45458
  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",
46364
45459
  create: (context) => {
46365
- const isReactUseRefCall = (node) => isReactHookCall(node, "useRef", context.scopes);
46366
- const isReactUseEffectCall = (node) => isReactHookCall(node, "useEffect", context.scopes);
45460
+ const isReactUseRefCall = (node) => isReactApiCall(node, "useRef", context.scopes, {
45461
+ allowGlobalReactNamespace: true,
45462
+ allowUnboundBareCalls: true
45463
+ });
45464
+ const isReactUseEffectCall = (node) => isReactApiCall(node, "useEffect", context.scopes, {
45465
+ allowGlobalReactNamespace: true,
45466
+ allowUnboundBareCalls: true
45467
+ });
46367
45468
  return { CallExpression(node) {
46368
- if (!isReactUseEffectCall(node)) return;
45469
+ if (!isUseEffect(node)) return;
46369
45470
  const analysis = getProgramAnalysis(node);
46370
45471
  if (!analysis) return;
46371
45472
  if (hasCleanup(analysis, node)) return;
@@ -46417,11 +45518,11 @@ const noPassDataToParent = defineRule({
46417
45518
  if (argumentRef && resolveToFunction(argumentRef)) return [];
46418
45519
  }
46419
45520
  return getDownstreamRefs(analysis, argument);
46420
- }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) || isExternalSubscriptionHookRef(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
45521
+ }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
46421
45522
  if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
46422
45523
  if (!argsUpstreamRefs.some((argRef) => {
46423
45524
  if (isUseStateIdentifier(argRef.identifier)) return false;
46424
- if (isExternalSubscriptionHookRef(analysis, argRef)) return false;
45525
+ if (isExternalSubscriptionHookRef(argRef)) return false;
46425
45526
  if (isProp(analysis, argRef)) return false;
46426
45527
  if (isUseRefIdentifier(argRef.identifier)) return false;
46427
45528
  if (isRefCurrent(argRef)) return false;
@@ -46652,7 +45753,7 @@ const noPassLiveStateToParent = defineRule({
46652
45753
  tags: ["test-noise"],
46653
45754
  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",
46654
45755
  create: (context) => ({ CallExpression(node) {
46655
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
45756
+ if (!isUseEffect(node)) return;
46656
45757
  const analysis = getProgramAnalysis(node);
46657
45758
  if (!analysis) return;
46658
45759
  const effectFnRefs = getEffectFnRefs(analysis, node);
@@ -47067,7 +46168,7 @@ const hasPreviousValueDep = (effectNode, depElements) => {
47067
46168
  if (!isNodeOfType(element, "Identifier")) continue;
47068
46169
  const binding = findVariableInitializer(effectNode, element.name);
47069
46170
  if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) continue;
47070
- const calleeName = getCalleeName$1(binding.initializer);
46171
+ const calleeName = getCalleeName$2(binding.initializer);
47071
46172
  if (calleeName && PREVIOUS_VALUE_HOOK_PATTERN.test(calleeName)) return true;
47072
46173
  }
47073
46174
  return false;
@@ -47089,7 +46190,7 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
47089
46190
  if (!isNodeOfType(receiver, "Identifier")) return null;
47090
46191
  const binding = findVariableInitializer(callExpression, receiver.name);
47091
46192
  if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
47092
- if (getCalleeName$1(binding.initializer) !== "useRef") return null;
46193
+ if (getCalleeName$2(binding.initializer) !== "useRef") return null;
47093
46194
  const callbackArgument = binding.initializer.arguments?.[0];
47094
46195
  if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
47095
46196
  return isPropName(callbackArgument.name) ? callbackArgument.name : null;
@@ -47121,7 +46222,7 @@ const noPropCallbackInEffect = defineRule({
47121
46222
  return {
47122
46223
  ...propStackTracker.visitors,
47123
46224
  CallExpression(node) {
47124
- if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes) || (node.arguments?.length ?? 0) < 2) return;
46225
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
47125
46226
  const callback = getEffectCallback(node);
47126
46227
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
47127
46228
  const depsNode = node.arguments[1];
@@ -47985,69 +47086,6 @@ const noRedundantShouldComponentUpdate = defineRule({
47985
47086
  }
47986
47087
  });
47987
47088
  //#endregion
47988
- //#region src/plugin/rules/correctness/no-ref-callback-cleanup-before-react-19.ts
47989
- const resolveFunctionExpressions = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
47990
- const expression = stripParenExpression(rawExpression);
47991
- if (isFunctionLike$1(expression)) return expression.async || expression.generator ? [] : [expression];
47992
- if (isNodeOfType(expression, "ConditionalExpression")) {
47993
- if (isNodeOfType(expression.test, "Literal")) return resolveFunctionExpressions(expression.test.value ? expression.consequent : expression.alternate, scopes, visitedSymbolIds);
47994
- return [...resolveFunctionExpressions(expression.consequent, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.alternate, scopes, visitedSymbolIds)];
47995
- }
47996
- if (isNodeOfType(expression, "LogicalExpression")) {
47997
- if (isNodeOfType(expression.left, "Literal")) {
47998
- const isLeftTruthy = Boolean(expression.left.value);
47999
- if (expression.operator === "&&" && !isLeftTruthy) return [];
48000
- if (expression.operator === "||" && isLeftTruthy) return [];
48001
- if (expression.operator === "??" && expression.left.value !== null) return [];
48002
- }
48003
- if (expression.operator === "&&") return resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds);
48004
- return [...resolveFunctionExpressions(expression.left, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds)];
48005
- }
48006
- if (isNodeOfType(expression, "SequenceExpression")) {
48007
- const finalExpression = expression.expressions.at(-1);
48008
- return finalExpression ? resolveFunctionExpressions(finalExpression, scopes, visitedSymbolIds) : [];
48009
- }
48010
- if (isNodeOfType(expression, "CallExpression")) {
48011
- if (!isReactApiCall(expression, "useCallback", scopes)) return [];
48012
- const callback = expression.arguments[0];
48013
- return callback && !isNodeOfType(callback, "SpreadElement") ? resolveFunctionExpressions(callback, scopes, visitedSymbolIds) : [];
48014
- }
48015
- if (!isNodeOfType(expression, "Identifier")) return [];
48016
- const symbol = scopes.symbolFor(expression);
48017
- if (!symbol || visitedSymbolIds.has(symbol.id)) return [];
48018
- if (symbol.kind === "function" && isNodeOfType(symbol.declarationNode, "FunctionDeclaration") && symbol.references.every((reference) => reference.flag === "read")) return resolveFunctionExpressions(symbol.declarationNode, scopes, new Set([...visitedSymbolIds, symbol.id]));
48019
- const initializer = getDirectConstInitializer(symbol);
48020
- if (!initializer) return [];
48021
- return resolveFunctionExpressions(initializer, scopes, new Set([...visitedSymbolIds, symbol.id]));
48022
- };
48023
- const functionReturnsCleanupFunction = (functionExpression, scopes) => {
48024
- if (!isFunctionLike$1(functionExpression)) return false;
48025
- if (!isNodeOfType(functionExpression.body, "BlockStatement")) return resolveFunctionExpressions(functionExpression.body, scopes).length > 0;
48026
- return collectFunctionReturnStatements(functionExpression).some((returnStatement) => Boolean(returnStatement.argument && resolveFunctionExpressions(returnStatement.argument, scopes).length > 0));
48027
- };
48028
- const callbackReturnsCleanupFunction = (callback, scopes) => {
48029
- return resolveFunctionExpressions(callback, scopes).some((functionExpression) => functionReturnsCleanupFunction(functionExpression, scopes));
48030
- };
48031
- const noRefCallbackCleanupBeforeReact19 = defineRule({
48032
- id: "no-ref-callback-cleanup-before-react-19",
48033
- title: "Ref cleanup requires React 19",
48034
- requires: ["react:18"],
48035
- disabledWhen: ["react:19"],
48036
- severity: "warn",
48037
- recommendation: "React 18 ignores functions returned from ref callbacks. Handle cleanup when React calls the ref with `null`, or require React 19 before returning a cleanup function.",
48038
- create: (context) => ({ JSXAttribute(node) {
48039
- if (getJsxAttributeName(node.name) !== "ref") return;
48040
- if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
48041
- const callback = node.value.expression;
48042
- if (!callback || isNodeOfType(callback, "JSXEmptyExpression")) return;
48043
- if (!callbackReturnsCleanupFunction(callback, context.scopes)) return;
48044
- context.report({
48045
- node,
48046
- message: "This ref callback returns a cleanup function, but React 18 ignores ref cleanup returns, so the cleanup never runs. Handle detachment when React calls the ref with `null`, or require React 19."
48047
- });
48048
- } })
48049
- });
48050
- //#endregion
48051
47089
  //#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
48052
47090
  const REPEATED_ANCESTOR_TYPES = new Set([
48053
47091
  "DoWhileStatement",
@@ -49014,7 +48052,7 @@ const noResetAllStateOnPropChange = defineRule({
49014
48052
  tags: ["test-noise"],
49015
48053
  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",
49016
48054
  create: (context) => ({ CallExpression(node) {
49017
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
48055
+ if (!isUseEffect(node)) return;
49018
48056
  const analysis = getProgramAnalysis(node);
49019
48057
  if (!analysis) return;
49020
48058
  const effectFnRefs = getEffectFnRefs(analysis, node);
@@ -49283,7 +48321,7 @@ const isTanStackServerFnHandlerCall = (node) => {
49283
48321
  if (node.callee.property.name !== "handler") return false;
49284
48322
  let currentNode = node.callee.object;
49285
48323
  while (isNodeOfType(currentNode, "CallExpression")) {
49286
- const calleeName = getCalleeName$1(currentNode);
48324
+ const calleeName = getCalleeName$2(currentNode);
49287
48325
  if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) return true;
49288
48326
  if (!isNodeOfType(currentNode.callee, "MemberExpression")) return false;
49289
48327
  currentNode = currentNode.callee.object;
@@ -49784,7 +48822,7 @@ const noSelfUpdatingEffect = defineRule({
49784
48822
  create: (context) => {
49785
48823
  const checkFunctionScope = (functionBody) => {
49786
48824
  if (!functionBody || !isNodeOfType(functionBody, "BlockStatement")) return;
49787
- const useStateBindings = collectUseStateBindings(functionBody, context.scopes);
48825
+ const useStateBindings = collectUseStateBindings(functionBody);
49788
48826
  if (useStateBindings.length === 0) return;
49789
48827
  const setterNameToStateName = /* @__PURE__ */ new Map();
49790
48828
  for (const binding of useStateBindings) setterNameToStateName.set(binding.setterName, binding.valueName);
@@ -49793,7 +48831,7 @@ const noSelfUpdatingEffect = defineRule({
49793
48831
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
49794
48832
  const effectCall = unwrapDiscardedExpression(statement);
49795
48833
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
49796
- if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
48834
+ if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
49797
48835
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
49798
48836
  const dependencyStateNames = collectDependencyStateNames(effectCall.arguments[1]);
49799
48837
  if (dependencyStateNames.size === 0) continue;
@@ -49879,7 +48917,7 @@ const noSetStateInRender = defineRule({
49879
48917
  create: (context) => {
49880
48918
  const checkComponent = (componentBody) => {
49881
48919
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
49882
- const setterNames = new Set(collectUseStateBindings(componentBody, context.scopes).map((binding) => binding.setterName));
48920
+ const setterNames = new Set(collectUseStateBindings(componentBody).map((binding) => binding.setterName));
49883
48921
  if (setterNames.size === 0) return;
49884
48922
  for (const statement of componentBody.body ?? []) {
49885
48923
  const setterCall = isUnconditionalSetterCallStatement(statement, setterNames);
@@ -50128,10 +49166,10 @@ const collectTimerRefUsageFacts = (ownerScope, refName) => {
50128
49166
  });
50129
49167
  return facts;
50130
49168
  };
50131
- const isEffectCallbackFunction = (functionNode, scopes) => {
49169
+ const isEffectCallbackFunction = (functionNode) => {
50132
49170
  const parent = functionNode.parent;
50133
49171
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
50134
- return isReactHookCall(parent, EFFECT_HOOK_NAMES$1, scopes) && getEffectCallback(parent) === functionNode;
49172
+ return isHookCall$2(parent, EFFECT_HOOK_NAMES$1) && getEffectCallback(parent) === functionNode;
50135
49173
  };
50136
49174
  const doesEffectCallbackReturnName = (effectCallback, name) => {
50137
49175
  if (!isFunctionLike$1(effectCallback)) return false;
@@ -50150,24 +49188,24 @@ const isFunctionReturnedFromEffectCallback = (functionNode, effectCallback) => {
50150
49188
  const cleanupBindingName = getFunctionBindingName$1(functionNode);
50151
49189
  return cleanupBindingName !== null && doesEffectCallbackReturnName(effectCallback, cleanupBindingName);
50152
49190
  };
50153
- const isReturnedFromAnyEffectInScope = (functionNode, ownerScope, scopes) => {
49191
+ const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
50154
49192
  const cleanupBindingName = getFunctionBindingName$1(functionNode);
50155
49193
  if (cleanupBindingName === null) return false;
50156
49194
  let isReturnedFromEffect = false;
50157
49195
  walkAst(ownerScope, (child) => {
50158
49196
  if (isReturnedFromEffect) return false;
50159
- if (!isNodeOfType(child, "CallExpression") || !isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) return;
49197
+ if (!isNodeOfType(child, "CallExpression") || !isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
50160
49198
  const effectCallback = getEffectCallback(child);
50161
49199
  if (effectCallback && doesEffectCallbackReturnName(effectCallback, cleanupBindingName)) isReturnedFromEffect = true;
50162
49200
  });
50163
49201
  return isReturnedFromEffect;
50164
49202
  };
50165
- const isInsideEffectCleanupReturn = (node, ownerScope, scopes) => {
49203
+ const isInsideEffectCleanupReturn = (node, ownerScope) => {
50166
49204
  let functionNode = findEnclosingFunction$1(node);
50167
49205
  while (functionNode) {
50168
49206
  const outerFunction = findEnclosingFunction$1(functionNode);
50169
- if (outerFunction && isEffectCallbackFunction(outerFunction, scopes) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
50170
- if (isReturnedFromAnyEffectInScope(functionNode, ownerScope, scopes)) return true;
49207
+ if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
49208
+ if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
50171
49209
  functionNode = outerFunction;
50172
49210
  }
50173
49211
  return false;
@@ -50203,10 +49241,10 @@ const noStaleTimerRef = defineRule({
50203
49241
  if (isShadowedTimerGlobal(node)) return;
50204
49242
  const { clearCalleeName, refName } = clearCall;
50205
49243
  const refBinding = findVariableInitializer(node, refName);
50206
- if (!refBinding?.initializer || !isReactHookCall(refBinding.initializer, "useRef", context.scopes)) return;
49244
+ if (!refBinding?.initializer || !isHookCall$2(refBinding.initializer, "useRef")) return;
50207
49245
  const usageFacts = collectTimerRefUsageFacts(refBinding.scopeOwner, refName);
50208
49246
  if (!usageFacts.holdsScheduledTimerId || !usageFacts.hasPendingSignalRead) return;
50209
- if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner, context.scopes)) return;
49247
+ if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner)) return;
50210
49248
  if (hasRefCurrentReassignmentAfterClear(node, refName)) return;
50211
49249
  context.report({
50212
49250
  node,
@@ -56010,7 +55048,7 @@ const classifyCallableReadsInsideEffect = (callableIdentifier, effectCallback, c
56010
55048
  allReadsAreInSubHandlers = false;
56011
55049
  return;
56012
55050
  }
56013
- if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$1(subHandlerCall);
55051
+ if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$2(subHandlerCall);
56014
55052
  });
56015
55053
  return {
56016
55054
  hasAnyRead,
@@ -56033,7 +55071,7 @@ const preferUseEffectEvent = defineRule({
56033
55071
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
56034
55072
  const effectCall = statement.expression;
56035
55073
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
56036
- if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
55074
+ if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
56037
55075
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
56038
55076
  const depsNode = effectCall.arguments[1];
56039
55077
  if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
@@ -56065,11 +55103,11 @@ const preferUseEffectEvent = defineRule({
56065
55103
  });
56066
55104
  //#endregion
56067
55105
  //#region src/plugin/rules/state-and-effects/prefer-use-sync-external-store.ts
56068
- const findUseEffectsInComponent = (componentBody, scopes) => {
55106
+ const findUseEffectsInComponent = (componentBody) => {
56069
55107
  const effectCalls = [];
56070
55108
  if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
56071
55109
  for (const statement of componentBody.body ?? []) walkAst(statement, (child) => {
56072
- if (isNodeOfType(child, "CallExpression") && isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) effectCalls.push(child);
55110
+ if (isNodeOfType(child, "CallExpression") && isHookCall$2(child, EFFECT_HOOK_NAMES$1)) effectCalls.push(child);
56073
55111
  });
56074
55112
  return effectCalls;
56075
55113
  };
@@ -56272,7 +55310,7 @@ const preferUseSyncExternalStore = defineRule({
56272
55310
  };
56273
55311
  const checkComponent = (componentBody) => {
56274
55312
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
56275
- const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
55313
+ const useStateBindings = collectUseStateBindings(componentBody);
56276
55314
  if (useStateBindings.length === 0) return;
56277
55315
  const useStateInitializerByValueName = /* @__PURE__ */ new Map();
56278
55316
  for (const binding of useStateBindings) {
@@ -56285,7 +55323,7 @@ const preferUseSyncExternalStore = defineRule({
56285
55323
  }
56286
55324
  const setterNameToValueName = /* @__PURE__ */ new Map();
56287
55325
  for (const binding of useStateBindings) setterNameToValueName.set(binding.setterName, binding.valueName);
56288
- for (const effectCall of findUseEffectsInComponent(componentBody, context.scopes)) {
55326
+ for (const effectCall of findUseEffectsInComponent(componentBody)) {
56289
55327
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
56290
55328
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
56291
55329
  const depsNode = effectCall.arguments[1];
@@ -56327,7 +55365,7 @@ const preferUseSyncExternalStore = defineRule({
56327
55365
  })).filter((candidate) => candidate.storeName !== null);
56328
55366
  if (snapshotBindings.length === 0) return;
56329
55367
  const reportedDeclarators = /* @__PURE__ */ new Set();
56330
- for (const effectCall of findUseEffectsInComponent(componentBody, context.scopes)) {
55368
+ for (const effectCall of findUseEffectsInComponent(componentBody)) {
56331
55369
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
56332
55370
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
56333
55371
  const depsNode = effectCall.arguments[1];
@@ -56424,7 +55462,7 @@ const preferUseReducer = defineRule({
56424
55462
  create: (context) => {
56425
55463
  const reportCoUpdatedUseState = (body, componentName) => {
56426
55464
  if (!isNodeOfType(body, "BlockStatement")) return;
56427
- const bindings = collectUseStateBindings(body, context.scopes);
55465
+ const bindings = collectUseStateBindings(body);
56428
55466
  const setterNames = new Set(bindings.map((binding) => binding.setterName));
56429
55467
  if (setterNames.size < 5) return;
56430
55468
  const coUpdatedCount = findLargestCoUpdatedSetterGroup(body, setterNames, new Map(bindings.map((binding) => {
@@ -56640,7 +55678,7 @@ const QUERY_READ_METHOD_NAMES = new Set([
56640
55678
  ]);
56641
55679
  const isQueryCacheSourceCall = (initializer) => {
56642
55680
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
56643
- const hookName = getCalleeName$1(initializer);
55681
+ const hookName = getCalleeName$2(initializer);
56644
55682
  if (!hookName) return false;
56645
55683
  return hookName === "useQueryClient" || TRPC_UTILS_HOOK_PATTERN.test(hookName);
56646
55684
  };
@@ -56772,7 +55810,7 @@ const queryMutationMissingInvalidation = defineRule({
56772
55810
  },
56773
55811
  CallExpression(node) {
56774
55812
  if (!hasQueryReadUsage) {
56775
- const callName = getCalleeName$1(node);
55813
+ const callName = getCalleeName$2(node);
56776
55814
  if (callName && (QUERY_READ_HOOK_NAMES.has(callName) || QUERY_READ_METHOD_NAMES.has(callName) || TRPC_UTILS_HOOK_PATTERN.test(callName))) hasQueryReadUsage = true;
56777
55815
  }
56778
55816
  const calleeName = isNodeOfType(node.callee, "Identifier") ? node.callee.name : null;
@@ -57265,30 +56303,26 @@ const REMOVAL_MESSAGE_BY_REACT_API_NAME = new Map([
57265
56303
  ["useCallback", "This `useCallback` is dead weight, since React Compiler already caches every function here. Delete it."],
57266
56304
  ["memo", "This `memo()` is dead weight, since React Compiler already caches the component's output. Delete it."]
57267
56305
  ]);
57268
- const resolveReactApiNameForIdentifier = (callee, context) => {
56306
+ const resolveReactApiNameForIdentifier = (callee) => {
57269
56307
  if (!isNodeOfType(callee, "Identifier")) return null;
57270
- if (context.scopes.symbolFor(callee)?.kind !== "import") return null;
57271
56308
  const importedName = getImportedNameFromModule(callee, callee.name, "react");
57272
56309
  if (importedName && REMOVAL_MESSAGE_BY_REACT_API_NAME.has(importedName)) return importedName;
57273
56310
  return null;
57274
56311
  };
57275
- const resolveReactApiNameForMemberExpression = (callee, context) => {
56312
+ const resolveReactApiNameForMemberExpression = (callee) => {
57276
56313
  if (!isNodeOfType(callee, "MemberExpression")) return null;
57277
56314
  if (callee.computed) return null;
57278
- const namespaceIdentifier = stripParenExpression(callee.object);
56315
+ const namespaceIdentifier = callee.object;
57279
56316
  const propertyIdentifier = callee.property;
57280
56317
  if (!isNodeOfType(namespaceIdentifier, "Identifier")) return null;
57281
56318
  if (!isNodeOfType(propertyIdentifier, "Identifier")) return null;
57282
56319
  if (!REMOVAL_MESSAGE_BY_REACT_API_NAME.has(propertyIdentifier.name)) return null;
57283
56320
  const namespaceName = namespaceIdentifier.name;
57284
- if (context.scopes.symbolFor(namespaceIdentifier)?.kind === "import" && isImportedFromModule(namespaceIdentifier, namespaceName, "react")) return propertyIdentifier.name;
57285
- if (isCanonicalReactNamespaceName(namespaceName) && context.scopes.isGlobalReference(namespaceIdentifier)) return propertyIdentifier.name;
56321
+ if (isCanonicalReactNamespaceName(namespaceName)) return propertyIdentifier.name;
56322
+ if (isImportedFromModule(namespaceIdentifier, namespaceName, "react")) return propertyIdentifier.name;
57286
56323
  return null;
57287
56324
  };
57288
- const resolveReactApiNameForCallee = (callee, context) => {
57289
- const unwrappedCallee = stripParenExpression(callee);
57290
- return resolveReactApiNameForIdentifier(unwrappedCallee, context) ?? resolveReactApiNameForMemberExpression(unwrappedCallee, context);
57291
- };
56325
+ const resolveReactApiNameForCallee = (callee) => resolveReactApiNameForIdentifier(callee) ?? resolveReactApiNameForMemberExpression(callee);
57292
56326
  const isNullishComparatorArgument = (argumentNode) => isNodeOfType(argumentNode, "Identifier") && argumentNode.name === "undefined" || isNodeOfType(argumentNode, "Literal") && argumentNode.value === null;
57293
56327
  const COMPILER_INFERABLE_HOC_NAMES = new Set(["memo", "forwardRef"]);
57294
56328
  const calleeTrailingName = (callee) => {
@@ -57314,7 +56348,7 @@ const reactCompilerNoManualMemoization = defineRule({
57314
56348
  requires: ["react-compiler"],
57315
56349
  recommendation: "Delete the `useMemo` / `useCallback` / `memo` call and use the plain value or component. React Compiler caches it for you.",
57316
56350
  create: (context) => ({ CallExpression(node) {
57317
- const apiName = resolveReactApiNameForCallee(node.callee, context);
56351
+ const apiName = resolveReactApiNameForCallee(node.callee);
57318
56352
  if (!apiName) return;
57319
56353
  if (apiName === "memo") {
57320
56354
  const comparatorArgument = node.arguments?.[1];
@@ -58439,125 +57473,6 @@ const ASYNC_DATA_CALLEE_NAMES = new Set([
58439
57473
  const RESOURCE_LOAD_EVENT_ATTRIBUTE_PATTERN = /^on(?:Load|Error|Abort|Progress|CanPlay|Stalled|Suspend|Waiting|Ended)/;
58440
57474
  const JSX_EVENT_HANDLER_ATTRIBUTE_PATTERN = /^on[A-Z]/;
58441
57475
  const REDUX_DISPATCH_HOOK_PATTERN = /^use\w*Dispatch$/;
58442
- const FILE_READER_READ_METHOD_NAMES = new Set([
58443
- "readAsArrayBuffer",
58444
- "readAsBinaryString",
58445
- "readAsDataURL",
58446
- "readAsText"
58447
- ]);
58448
- const isGlobalFileReaderConstruction = (expression, context) => {
58449
- if (!expression) return false;
58450
- const unwrappedExpression = stripParenExpression(expression);
58451
- if (!isNodeOfType(unwrappedExpression, "NewExpression") || !isNodeOfType(unwrappedExpression.callee, "Identifier")) return false;
58452
- return unwrappedExpression.callee.name === "FileReader" && context.scopes.isGlobalReference(unwrappedExpression.callee);
58453
- };
58454
- const getFileReaderOriginStartBefore = (readerSymbol, readCall, context) => {
58455
- const readFunction = findEnclosingFunction$1(readCall);
58456
- let latestValue = null;
58457
- let latestStart = null;
58458
- if (readerSymbol.initializer && findEnclosingFunction$1(readerSymbol.declarationNode) === readFunction && readerSymbol.declarationNode.range[0] < readCall.range[0]) {
58459
- latestValue = readerSymbol.initializer;
58460
- latestStart = readerSymbol.declarationNode.range[0];
58461
- }
58462
- for (const reference of readerSymbol.references) {
58463
- if (reference.flag === "read" || reference.identifier.range[0] >= readCall.range[0] || latestStart !== null && reference.identifier.range[0] <= latestStart || findEnclosingFunction$1(reference.identifier) !== readFunction) continue;
58464
- const assignment = reference.identifier.parent;
58465
- if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== reference.identifier) continue;
58466
- latestValue = assignment.right;
58467
- latestStart = reference.identifier.range[0];
58468
- }
58469
- return isGlobalFileReaderConstruction(latestValue, context) ? latestStart : null;
58470
- };
58471
- const resolveLoadingCompletionFunction = (expression, context) => {
58472
- const directFunction = resolveExactLocalFunction(expression, context.scopes);
58473
- if (directFunction) return directFunction;
58474
- const unwrappedExpression = stripParenExpression(expression);
58475
- if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
58476
- const symbol = context.scopes.symbolFor(unwrappedExpression);
58477
- const initializer = symbol ? getDirectUnreassignedInitializer(symbol) : null;
58478
- if (!initializer || !isNodeOfType(initializer, "CallExpression") || !isReactApiCall(initializer, "useCallback", context.scopes)) return null;
58479
- const callback = initializer.arguments?.[0];
58480
- return callback && isFunctionLike$1(callback) ? callback : null;
58481
- };
58482
- const isSetterBooleanCall = (node, setterSymbol, value, context) => {
58483
- if (!isNodeOfType(node, "CallExpression")) return false;
58484
- const callee = stripParenExpression(node.callee);
58485
- const argument = node.arguments?.[0];
58486
- const unwrappedArgument = argument ? stripParenExpression(argument) : null;
58487
- return Boolean(isNodeOfType(callee, "Identifier") && context.scopes.symbolFor(callee) === setterSymbol && unwrappedArgument && isNodeOfType(unwrappedArgument, "Literal") && unwrappedArgument.value === value);
58488
- };
58489
- const functionClearsLoadingState = (functionNode, setterSymbol, context, visitedFunctions) => {
58490
- if (visitedFunctions.has(functionNode) || !isFunctionLike$1(functionNode)) return false;
58491
- visitedFunctions.add(functionNode);
58492
- let didClearLoadingState = false;
58493
- walkAst(functionNode.body, (child) => {
58494
- if (didClearLoadingState) return false;
58495
- if (child !== functionNode.body && isFunctionLike$1(child)) return false;
58496
- if (!isNodeOfType(child, "CallExpression")) return;
58497
- if (isSetterBooleanCall(child, setterSymbol, false, context)) {
58498
- didClearLoadingState = true;
58499
- return false;
58500
- }
58501
- const helperFunction = resolveLoadingCompletionFunction(child.callee, context);
58502
- if (helperFunction && functionClearsLoadingState(helperFunction, setterSymbol, context, visitedFunctions)) {
58503
- didClearLoadingState = true;
58504
- return false;
58505
- }
58506
- });
58507
- return didClearLoadingState;
58508
- };
58509
- const getLatestFileReaderCallbackBefore = (readCall, readerSymbol, propertyName, originStart, context) => {
58510
- const readFunction = findEnclosingFunction$1(readCall);
58511
- if (!readFunction || !isFunctionLike$1(readFunction)) return null;
58512
- let callback = null;
58513
- let callbackStart = originStart;
58514
- walkAst(readFunction.body, (child) => {
58515
- if (child !== readFunction.body && isFunctionLike$1(child)) return false;
58516
- if (!isNodeOfType(child, "AssignmentExpression") || child.operator !== "=" || child.range[0] >= readCall.range[0] || child.range[0] <= callbackStart || !isNodeOfType(child.left, "MemberExpression") || getStaticPropertyName(child.left) !== propertyName) return;
58517
- const receiver = stripParenExpression(child.left.object);
58518
- if (!isNodeOfType(receiver, "Identifier") || context.scopes.symbolFor(receiver) !== readerSymbol) return;
58519
- callback = child.right;
58520
- callbackStart = child.range[0];
58521
- });
58522
- return callback;
58523
- };
58524
- const setterStartsLoadingBefore = (readCall, setterSymbol, context) => {
58525
- const readFunction = findEnclosingFunction$1(readCall);
58526
- if (!readFunction || !isFunctionLike$1(readFunction)) return false;
58527
- let didStartLoading = false;
58528
- walkAst(readFunction.body, (child) => {
58529
- if (didStartLoading) return false;
58530
- if (child !== readFunction.body && isFunctionLike$1(child)) return false;
58531
- if (!isNodeOfType(child, "CallExpression") || child.range[0] >= readCall.range[0]) return;
58532
- if (isSetterBooleanCall(child, setterSymbol, true, context)) {
58533
- didStartLoading = true;
58534
- return false;
58535
- }
58536
- });
58537
- return didStartLoading;
58538
- };
58539
- const setterTracksFileReader = (functionBody, setterSymbol, context) => {
58540
- let didFindFileReaderLifecycle = false;
58541
- walkAst(functionBody, (child) => {
58542
- if (didFindFileReaderLifecycle) return false;
58543
- if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || !FILE_READER_READ_METHOD_NAMES.has(getStaticPropertyName(child.callee) ?? "")) return;
58544
- const receiver = stripParenExpression(child.callee.object);
58545
- if (!isNodeOfType(receiver, "Identifier")) return;
58546
- const readerSymbol = context.scopes.symbolFor(receiver);
58547
- if (!readerSymbol) return;
58548
- const originStart = getFileReaderOriginStartBefore(readerSymbol, child, context);
58549
- if (originStart === null || !setterStartsLoadingBefore(child, setterSymbol, context)) return;
58550
- const loadCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onload", originStart, context);
58551
- const errorCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onerror", originStart, context);
58552
- const loadFunction = loadCallback ? resolveLoadingCompletionFunction(loadCallback, context) : null;
58553
- const errorFunction = errorCallback ? resolveLoadingCompletionFunction(errorCallback, context) : null;
58554
- if (loadFunction && errorFunction && functionClearsLoadingState(loadFunction, setterSymbol, context, /* @__PURE__ */ new Set()) && functionClearsLoadingState(errorFunction, setterSymbol, context, /* @__PURE__ */ new Set())) {
58555
- didFindFileReaderLifecycle = true;
58556
- return false;
58557
- }
58558
- });
58559
- return didFindFileReaderLifecycle;
58560
- };
58561
57476
  const hasAsyncLoadingWork = (fnBody, setterName) => {
58562
57477
  let found = false;
58563
57478
  walkAst(fnBody, (child) => {
@@ -58731,8 +57646,6 @@ const renderingUsetransitionLoading = defineRule({
58731
57646
  const fnBody = enclosingFunctionBody(node);
58732
57647
  if (fnBody && hasAsyncLoadingWork(fnBody, setterName)) return;
58733
57648
  if (fnBody && setterName) {
58734
- const setterSymbol = isNodeOfType(secondBinding, "Identifier") ? context.scopes.symbolFor(secondBinding) : null;
58735
- if (setterSymbol && setterTracksFileReader(fnBody, setterSymbol, context)) return;
58736
57649
  if (setterEscapes(fnBody, setterName, node)) return;
58737
57650
  if (setterCalledAlongsideAsyncSignal(fnBody, setterName)) return;
58738
57651
  if (setterCalledInEventListenerHandler(fnBody, setterName)) return;
@@ -59064,7 +57977,7 @@ const rerenderDependencies = defineRule({
59064
57977
  severity: "error",
59065
57978
  recommendation: "Move it into a useMemo, useRef, or a constant outside the component so it stays the same between renders.",
59066
57979
  create: (context) => ({ CallExpression(node) {
59067
- if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes) || node.arguments.length < 2) return;
57980
+ if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
59068
57981
  const depsNode = node.arguments[1];
59069
57982
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
59070
57983
  for (const element of depsNode.elements ?? []) {
@@ -59319,7 +58232,7 @@ const rerenderLazyRefInit = defineRule({
59319
58232
  category: "Performance",
59320
58233
  recommendation: "Initialize the ref lazily so expensive values are not rebuilt and discarded on every render.",
59321
58234
  create: (context) => ({ CallExpression(node) {
59322
- if (!isReactHookCall(node, "useRef", context.scopes) || !node.arguments?.length) return;
58235
+ if (!isHookCall$2(node, "useRef") || !node.arguments?.length) return;
59323
58236
  const initializer = stripParenExpression(node.arguments[0]);
59324
58237
  const isPlainCall = isNodeOfType(initializer, "CallExpression");
59325
58238
  const isNewCall = isNodeOfType(initializer, "NewExpression");
@@ -59383,7 +58296,7 @@ const rerenderLazyStateInit = defineRule({
59383
58296
  category: "Performance",
59384
58297
  recommendation: "Wrap expensive initial state in an arrow function so the initializer does not rerun and get thrown away on every render.",
59385
58298
  create: (context) => ({ CallExpression(node) {
59386
- if (!isReactHookCall(node, "useState", context.scopes) || !node.arguments?.length) return;
58299
+ if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
59387
58300
  const initializer = findEagerInitializerCall(node.arguments[0]);
59388
58301
  if (!initializer) return;
59389
58302
  const isConstructor = isNodeOfType(initializer, "NewExpression");
@@ -60146,11 +59059,11 @@ const isInsideConditionTest = (identifier, stopAt) => {
60146
59059
  }
60147
59060
  return false;
60148
59061
  };
60149
- const collectEffectDependencyInfos = (componentBody, setterNames, scopes) => {
59062
+ const collectEffectDependencyInfos = (componentBody, setterNames) => {
60150
59063
  const effectInfos = [];
60151
59064
  walkAst(componentBody, (child) => {
60152
59065
  if (!isNodeOfType(child, "CallExpression")) return;
60153
- if (!isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) return;
59066
+ if (!isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
60154
59067
  const dependencyNames = /* @__PURE__ */ new Set();
60155
59068
  for (const argument of child.arguments ?? []) {
60156
59069
  if (!isNodeOfType(argument, "ArrayExpression")) continue;
@@ -60206,14 +59119,13 @@ const collectEffectDependencyInfos = (componentBody, setterNames, scopes) => {
60206
59119
  });
60207
59120
  return effectInfos;
60208
59121
  };
60209
- const collectCustomHookArgumentNames = (componentBody, scopes) => {
59122
+ const collectCustomHookArgumentNames = (componentBody) => {
60210
59123
  const argumentNames = /* @__PURE__ */ new Set();
60211
59124
  walkAst(componentBody, (child) => {
60212
59125
  if (!isNodeOfType(child, "CallExpression")) return;
60213
59126
  if (!isNodeOfType(child.callee, "Identifier")) return;
60214
59127
  const calleeName = child.callee.name;
60215
59128
  if (!isReactHookName(calleeName)) return;
60216
- if (isReactHookCall(child, BUILTIN_HOOK_NAMES, scopes)) return;
60217
59129
  if (BUILTIN_HOOK_NAMES.has(calleeName)) return;
60218
59130
  if (EFFECT_HOOK_NAMES$1.has(calleeName)) return;
60219
59131
  for (const argument of child.arguments ?? []) walkAst(argument, (argumentNode) => {
@@ -60254,21 +59166,21 @@ const rerenderStateOnlyInHandlers = defineRule({
60254
59166
  create: (context) => {
60255
59167
  const checkComponent = (componentBody) => {
60256
59168
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
60257
- const bindings = collectUseStateBindings(componentBody, context.scopes);
59169
+ const bindings = collectUseStateBindings(componentBody);
60258
59170
  if (bindings.length === 0) return;
60259
59171
  if (collectRenderReachableExpressions(componentBody).length === 0) return;
60260
- const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
59172
+ const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
60261
59173
  const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
60262
- const directRenderNames = collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames);
59174
+ const directRenderNames = collectRenderReachableNames(componentBody, eventHandlerReferenceNames);
60263
59175
  if (hasRenderPhaseNonHookCall(componentBody)) for (const voidMarkedName of collectTopLevelVoidMarkedNames(componentBody)) directRenderNames.add(voidMarkedName);
60264
59176
  const renderReachableNames = expandTransitiveDependencies(directRenderNames, dependencyGraph);
60265
59177
  const setterNames = new Set(bindings.map((binding) => binding.setterName));
60266
- const effectInfos = collectEffectDependencyInfos(componentBody, setterNames, context.scopes);
59178
+ const effectInfos = collectEffectDependencyInfos(componentBody, setterNames);
60267
59179
  const selfEchoValueNames = /* @__PURE__ */ new Set();
60268
59180
  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);
60269
59181
  const effectConsumedNames = /* @__PURE__ */ new Set();
60270
59182
  for (const effectInfo of effectInfos) for (const dependencyName of effectInfo.dependencyNames) if (!selfEchoValueNames.has(dependencyName)) effectConsumedNames.add(dependencyName);
60271
- for (const hookArgumentName of collectCustomHookArgumentNames(componentBody, context.scopes)) effectConsumedNames.add(hookArgumentName);
59183
+ for (const hookArgumentName of collectCustomHookArgumentNames(componentBody)) effectConsumedNames.add(hookArgumentName);
60272
59184
  for (const reachableName of expandTransitiveDependencies(effectConsumedNames, dependencyGraph)) renderReachableNames.add(reachableName);
60273
59185
  const calledSetterNames = /* @__PURE__ */ new Set();
60274
59186
  walkAst(componentBody, (child) => {
@@ -68169,7 +67081,7 @@ const declarationAwaitsGate = (declaration, context) => {
68169
67081
  if (!isNodeOfType(argument, "CallExpression")) continue;
68170
67082
  if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
68171
67083
  if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
68172
- const calleeName = getCalleeName$1(argument);
67084
+ const calleeName = getCalleeName$2(argument);
68173
67085
  if (!calleeName) continue;
68174
67086
  if (isAuthGuardName(calleeName)) return true;
68175
67087
  const [leadingToken] = tokenizeIdentifierWords(calleeName);
@@ -68809,7 +67721,7 @@ const walkServerFnChain = (outerNode) => {
68809
67721
  if (!isNodeOfType(outerNode.callee, "MemberExpression")) return result;
68810
67722
  let currentNode = stripParenExpression(outerNode.callee.object);
68811
67723
  while (isNodeOfType(currentNode, "CallExpression")) {
68812
- const calleeName = getCalleeName$1(currentNode);
67724
+ const calleeName = getCalleeName$2(currentNode);
68813
67725
  if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) {
68814
67726
  result.isServerFnChain = true;
68815
67727
  const optionsArgument = currentNode.arguments?.[0];
@@ -72941,17 +71853,6 @@ const reactDoctorRules = [
72941
71853
  requires: [...new Set(["react", ...noRedundantShouldComponentUpdate.requires ?? []])]
72942
71854
  }
72943
71855
  },
72944
- {
72945
- key: "react-doctor/no-ref-callback-cleanup-before-react-19",
72946
- id: "no-ref-callback-cleanup-before-react-19",
72947
- source: "react-doctor",
72948
- originallyExternal: false,
72949
- rule: {
72950
- ...noRefCallbackCleanupBeforeReact19,
72951
- framework: "global",
72952
- category: "Bugs"
72953
- }
72954
- },
72955
71856
  {
72956
71857
  key: "react-doctor/no-ref-current-in-render",
72957
71858
  id: "no-ref-current-in-render",