oxlint-plugin-react-doctor 0.7.9-dev.44d3ad8 → 0.7.9-dev.4f3848b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +336 -504
  2. 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,38 @@ 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, allowPatternBinding = false) => {
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")) return null;
1246
+ if (symbol.declarationNode.id !== symbol.bindingIdentifier) return allowPatternBinding ? symbol : null;
1247
+ visitedSymbolIds.add(symbol.id);
1248
+ const initializer = stripParenExpression(symbol.initializer);
1249
+ if (!isNodeOfType(initializer, "Identifier")) return symbol;
1250
+ symbol = scopes.symbolFor(initializer);
1251
+ }
1252
+ return symbol;
1253
+ };
1254
+ //#endregion
1340
1255
  //#region src/plugin/utils/resolve-exact-local-function.ts
1341
1256
  const resolveExactLocalFunction = (expression, scopes) => {
1342
1257
  const unwrappedExpression = stripParenExpression(expression);
@@ -1382,17 +1297,9 @@ const getRootIdentifier$1 = (node, options) => {
1382
1297
  //#region src/plugin/utils/get-root-identifier-name.ts
1383
1298
  const getRootIdentifierName = (node, options) => getRootIdentifier$1(node, options)?.name ?? null;
1384
1299
  //#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
1300
  //#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([
1301
+ const STABLE_HANDLER_HOOK_NAMES = new Set([
1302
+ "useCallback",
1396
1303
  "useEffectEvent",
1397
1304
  "useEvent",
1398
1305
  "useEventCallback",
@@ -1401,21 +1308,21 @@ const CUSTOM_STABLE_HANDLER_HOOK_NAMES = new Set([
1401
1308
  ]);
1402
1309
  const THROTTLED_HANDLER_HOOK_PATTERN = /^use\w*(?:Throttle|Debounce)/i;
1403
1310
  const isThrottledHandlerHookCall = (callNode) => {
1404
- const calleeName = getCalleeName$1(callNode);
1311
+ const calleeName = getCalleeName$2(callNode);
1405
1312
  return calleeName !== null && THROTTLED_HANDLER_HOOK_PATTERN.test(calleeName);
1406
1313
  };
1407
- const isEmptyDepsUseMemoCall = (callNode, scopes) => {
1408
- if (!isReactHookCall(callNode, "useMemo", scopes)) return false;
1314
+ const isEmptyDepsUseMemoCall = (callNode) => {
1315
+ if (!isHookCall$2(callNode, "useMemo")) return false;
1409
1316
  const memoDepsNode = callNode.arguments?.[1];
1410
1317
  return isNodeOfType(memoDepsNode, "ArrayExpression") && (memoDepsNode.elements?.length ?? 0) === 0;
1411
1318
  };
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);
1319
+ const isStableHandlerInitializer = (initializer) => {
1320
+ if (isNodeOfType(initializer, "CallExpression")) return isHookCall$2(initializer, STABLE_HANDLER_HOOK_NAMES) || isEmptyDepsUseMemoCall(initializer) || isThrottledHandlerHookCall(initializer);
1414
1321
  return isNodeOfType(initializer, "MemberExpression") && isNodeOfType(initializer.property, "Identifier") && initializer.property.name === "current";
1415
1322
  };
1416
- const isStableRefReceiverDep = (referenceNode, receiverDepName, scopes) => {
1323
+ const isStableRefReceiverDep = (referenceNode, receiverDepName) => {
1417
1324
  const receiverBinding = findVariableInitializer(referenceNode, receiverDepName);
1418
- return Boolean(receiverBinding?.initializer && isReactHookCall(receiverBinding.initializer, "useRef", scopes));
1325
+ return Boolean(receiverBinding?.initializer && isHookCall$2(receiverBinding.initializer, "useRef"));
1419
1326
  };
1420
1327
  const advancedEventHandlerRefs = defineRule({
1421
1328
  id: "advanced-event-handler-refs",
@@ -1425,7 +1332,7 @@ const advancedEventHandlerRefs = defineRule({
1425
1332
  category: "Performance",
1426
1333
  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
1334
  create: (context) => ({ CallExpression(node) {
1428
- if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes)) return;
1335
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
1429
1336
  if ((node.arguments?.length ?? 0) < 2) return;
1430
1337
  const callback = getEffectCallback(node);
1431
1338
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
@@ -1449,8 +1356,8 @@ const advancedEventHandlerRefs = defineRule({
1449
1356
  });
1450
1357
  if (!registeredHandlerName) return;
1451
1358
  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;
1359
+ if (handlerBinding?.initializer && isStableHandlerInitializer(handlerBinding.initializer)) return;
1360
+ if ([...depIdentifierNames].some((depName) => depName !== registeredHandlerName && subscriptionReceiverNames.has(depName) && !isStableRefReceiverDep(node, depName))) return;
1454
1361
  context.report({
1455
1362
  node,
1456
1363
  message: `useEffect re-adds the "${registeredHandlerName}" listener every time the handler changes.`
@@ -1937,6 +1844,15 @@ const flattenJsxName$1 = (node) => {
1937
1844
  return null;
1938
1845
  };
1939
1846
  //#endregion
1847
+ //#region src/plugin/utils/get-static-property-name.ts
1848
+ const getStaticPropertyName = (memberExpression) => {
1849
+ const property = memberExpression.property;
1850
+ if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
1851
+ if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
1852
+ if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
1853
+ return null;
1854
+ };
1855
+ //#endregion
1940
1856
  //#region src/plugin/utils/is-generated-image-renderer-call.ts
1941
1857
  const GENERATED_IMAGE_RENDERER_MODULES = [
1942
1858
  "next/og",
@@ -4944,6 +4860,23 @@ const containsDirectAwait = (node) => {
4944
4860
  return foundAwait;
4945
4861
  };
4946
4862
  //#endregion
4863
+ //#region src/plugin/utils/get-static-property-key-name.ts
4864
+ const getStaticPropertyKeyName = (node, options = {}) => {
4865
+ if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition") && !isNodeOfType(node, "MemberExpression")) return null;
4866
+ const key = isNodeOfType(node, "MemberExpression") ? node.property : node.key;
4867
+ if (node.computed) {
4868
+ if (options.allowComputedString && isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value;
4869
+ if (options.allowComputedString && isNodeOfType(key, "TemplateLiteral") && key.expressions.length === 0) return key.quasis[0]?.value.cooked ?? key.quasis[0]?.value.raw ?? null;
4870
+ return null;
4871
+ }
4872
+ if (isNodeOfType(key, "Identifier")) return key.name;
4873
+ if (isNodeOfType(key, "Literal")) {
4874
+ if (typeof key.value === "string") return key.value;
4875
+ if (options.stringifyNonStringLiterals) return String(key.value);
4876
+ }
4877
+ return null;
4878
+ };
4879
+ //#endregion
4947
4880
  //#region src/plugin/utils/get-destructured-binding-property-name.ts
4948
4881
  const getDestructuredBindingPropertyName = (bindingIdentifier) => {
4949
4882
  let bindingNode = bindingIdentifier;
@@ -9008,6 +8941,65 @@ const hasVisibleBindingNamed = (node, bindingName, scopes) => {
9008
8941
  }
9009
8942
  };
9010
8943
  //#endregion
8944
+ //#region src/plugin/utils/is-react-api-call.ts
8945
+ const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? apiNames === apiName : apiNames.has(apiName);
8946
+ const isImportedFromReact = (symbol) => {
8947
+ if (symbol.kind !== "import") return false;
8948
+ const importDeclaration = symbol.declarationNode.parent;
8949
+ return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(importDeclaration.source.value));
8950
+ };
8951
+ const isNamedReactApiImport = (identifier, apiNames, scopes, resolveAliases) => {
8952
+ if (!isNodeOfType(identifier, "Identifier")) return false;
8953
+ const symbol = resolveAliases ? resolveConstIdentifierAlias(identifier, scopes) : scopes.symbolFor(identifier);
8954
+ if (!symbol || !isImportedFromReact(symbol)) return false;
8955
+ const importedName = getImportedName(symbol.declarationNode);
8956
+ return Boolean(importedName && includesApiName(apiNames, importedName));
8957
+ };
8958
+ const isReactNamespaceImport = (identifier, scopes) => {
8959
+ const symbol = resolveConstIdentifierAlias(identifier, scopes);
8960
+ if (!symbol || !isImportedFromReact(symbol)) return false;
8961
+ return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
8962
+ };
8963
+ const isReactNamespaceReceiver$1 = (receiver, scopes, options) => {
8964
+ if (!isNodeOfType(receiver, "Identifier")) return false;
8965
+ if (isReactNamespaceImport(receiver, scopes)) return true;
8966
+ return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
8967
+ };
8968
+ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) => {
8969
+ const symbol = scopes.symbolFor(identifier);
8970
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
8971
+ const pattern = symbol.declarationNode.id;
8972
+ if (!isNodeOfType(pattern, "ObjectPattern")) return false;
8973
+ for (const property of pattern.properties) {
8974
+ if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
8975
+ const propertyName = getStaticPropertyKeyName(property);
8976
+ return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver$1(stripParenExpression(symbol.initializer), scopes, options));
8977
+ }
8978
+ return false;
8979
+ };
8980
+ const isReactApiCall = (node, apiNames, scopes, options = {}) => {
8981
+ if (!isNodeOfType(node, "CallExpression")) return false;
8982
+ return isReactApiCallee(node.callee, apiNames, scopes, options, /* @__PURE__ */ new Set());
8983
+ };
8984
+ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds) => {
8985
+ const callee = stripParenExpression(rawCallee);
8986
+ 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));
8987
+ if (isNodeOfType(callee, "Identifier")) {
8988
+ if (isNamedReactApiImport(callee, apiNames, scopes, Boolean(options.resolveNamedAliases))) return true;
8989
+ if (options.resolveNamedAliases && isDestructuredReactApiBinding(callee, apiNames, scopes, options)) return true;
8990
+ if (options.resolveConditionalAliases) {
8991
+ const symbol = scopes.symbolFor(callee);
8992
+ if (symbol?.kind === "const" && symbol.initializer && !visitedSymbolIds.has(symbol.id)) {
8993
+ visitedSymbolIds.add(symbol.id);
8994
+ return isReactApiCallee(symbol.initializer, apiNames, scopes, options, visitedSymbolIds);
8995
+ }
8996
+ }
8997
+ return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
8998
+ }
8999
+ if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
9000
+ return isReactNamespaceReceiver$1(stripParenExpression(callee.object), scopes, options);
9001
+ };
9002
+ //#endregion
9011
9003
  //#region src/plugin/utils/is-proven-browser-api-receiver.ts
9012
9004
  const DOM_EVENT_TARGET_TYPE_NAMES = new Set([
9013
9005
  "AbortSignal",
@@ -13891,7 +13883,6 @@ const walkInsideStatementBlocks = (node, visitor) => {
13891
13883
  };
13892
13884
  //#endregion
13893
13885
  //#region src/plugin/rules/state-and-effects/utils/is-subscribe-like-call-expression.ts
13894
- const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
13895
13886
  const getSubscribeLikeMethodName = (node) => {
13896
13887
  if (!isNodeOfType(node, "CallExpression")) return null;
13897
13888
  if (!isNodeOfType(node.callee, "MemberExpression")) return null;
@@ -13902,11 +13893,6 @@ const isSubscribeLikeCallExpression = (node) => {
13902
13893
  const methodName = getSubscribeLikeMethodName(node);
13903
13894
  return methodName !== null && SUBSCRIPTION_METHOD_NAMES.has(methodName);
13904
13895
  };
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
13896
  const isCleanupReturningSubscribeLikeCallExpression = (node) => {
13911
13897
  const methodName = getSubscribeLikeMethodName(node);
13912
13898
  if (methodName === null || !CLEANUP_RETURNING_SUBSCRIPTION_METHOD_NAMES.has(methodName)) return false;
@@ -13959,6 +13945,7 @@ const isNodeReachableWithinFunction = (node, context) => {
13959
13945
  };
13960
13946
  //#endregion
13961
13947
  //#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
13948
+ const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
13962
13949
  const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
13963
13950
  const REPLAYABLE_ITERATOR_COLLECTION_CACHE = /* @__PURE__ */ new WeakMap();
13964
13951
  const REACT_REF_EFFECT_ANALYSIS_CACHE = /* @__PURE__ */ new WeakMap();
@@ -13968,6 +13955,10 @@ const RESOURCE_NOUN_BY_KIND = {
13968
13955
  socket: "connection"
13969
13956
  };
13970
13957
  const isSocketConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && SOCKET_CONSTRUCTOR_NAMES_REQUIRING_CLEANUP.has(node.callee.name);
13958
+ const isSubscribeOrObserveCall = (node) => {
13959
+ if (isSubscribeLikeCallExpression(node)) return true;
13960
+ return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === OBSERVER_REGISTRATION_METHOD_NAME;
13961
+ };
13971
13962
  const resolveExpressionKey = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13972
13963
  if (!expression) return null;
13973
13964
  const unwrappedExpression = stripParenExpression(expression);
@@ -14069,13 +14060,12 @@ const findSubscribeLikeUsages = (callback, context) => {
14069
14060
  });
14070
14061
  return;
14071
14062
  }
14072
- const subscribeOrObserveMethodName = getSubscribeOrObserveMethodName(child);
14073
- if (subscribeOrObserveMethodName !== null) {
14063
+ 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
14064
  const registrationDetails = getCallRegistrationDetails(child, context);
14075
14065
  usages.push({
14076
14066
  kind: "subscribe",
14077
14067
  node: child,
14078
- resourceName: subscribeOrObserveMethodName,
14068
+ resourceName: child.callee.property.name,
14079
14069
  handleKey: findAssignedResourceKey(child, context),
14080
14070
  ...registrationDetails
14081
14071
  });
@@ -14606,7 +14596,7 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
14606
14596
  walkAst(componentFunction.body, (child) => {
14607
14597
  if (didFindUnmountCleanup) return false;
14608
14598
  if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction) return;
14609
- if (!isReactHookCall(child, CLEANUP_EFFECT_HOOK_NAMES, context.scopes)) return;
14599
+ if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
14610
14600
  const dependencyList = child.arguments?.[1];
14611
14601
  if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
14612
14602
  const cleanupCallback = getEffectCallback(child);
@@ -15037,7 +15027,7 @@ const getReleaseVerbName = (node) => {
15037
15027
  const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) => {
15038
15028
  const releaseFunction = findEnclosingFunction$1(releaseReceiver);
15039
15029
  const usageFunction = findEnclosingFunction$1(usage.node);
15040
- if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction, context) || !resolveReactRefCurrentOriginSymbol(releaseReceiver, context.scopes)) return false;
15030
+ if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction) || !resolveReactRefCurrentOriginSymbol(releaseReceiver, context.scopes)) return false;
15041
15031
  const controllerKey = getListenerAbortControllerKey(usage, context);
15042
15032
  const refCurrentKey = resolveExpressionKey(releaseReceiver, context);
15043
15033
  if (controllerKey === null || refCurrentKey === null) return false;
@@ -15184,7 +15174,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15184
15174
  return true;
15185
15175
  };
15186
15176
  const matchesPairedReleaseVerb = (releaseVerbName, pairedVerbNames) => pairedVerbNames.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName);
15187
- const isReturnedEffectCleanupFunction = (functionNode, context) => {
15177
+ const isReturnedEffectCleanupFunction = (functionNode) => {
15188
15178
  let currentNode = functionNode;
15189
15179
  let parentNode = currentNode.parent;
15190
15180
  while (isNodeOfType(parentNode, "ChainExpression") || isNodeOfType(parentNode, "TSAsExpression") || isNodeOfType(parentNode, "TSNonNullExpression")) {
@@ -15193,10 +15183,10 @@ const isReturnedEffectCleanupFunction = (functionNode, context) => {
15193
15183
  }
15194
15184
  const effectCallback = isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode ? findEnclosingFunction$1(parentNode) : isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode ? parentNode : null;
15195
15185
  const effectCall = effectCallback?.parent;
15196
- return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isReactHookCall(effectCall, CLEANUP_EFFECT_HOOK_NAMES, context.scopes));
15186
+ return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
15197
15187
  };
15198
15188
  const isPotentiallyReachableFunction = (functionNode, context) => {
15199
- if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode, context)) return true;
15189
+ if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode)) return true;
15200
15190
  const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
15201
15191
  if (!bindingIdentifier) return false;
15202
15192
  const symbol = context.scopes.symbolFor(bindingIdentifier);
@@ -15693,7 +15683,7 @@ const findRetainedFunctionLeak = (retainedFunction, context, options) => {
15693
15683
  return false;
15694
15684
  }
15695
15685
  }
15696
- if (isSubscribeOrObserveCallExpression(child) && (!doesResourceResultEscape(child, allowReturnedResourceEscape, allowReturnedResourceEscape, context) || options?.requireCallableReturnedResource === true && !isCleanupReturningSubscribeLikeCallExpression(child))) {
15686
+ if (isSubscribeOrObserveCall(child) && (!doesResourceResultEscape(child, allowReturnedResourceEscape, allowReturnedResourceEscape, context) || options?.requireCallableReturnedResource === true && !isCleanupReturningSubscribeLikeCallExpression(child))) {
15697
15687
  const registrationDetails = getCallRegistrationDetails(child, context);
15698
15688
  const subscriptionUsage = {
15699
15689
  kind: "subscribe",
@@ -15901,7 +15891,7 @@ const isInlineRetainedHandlerFunction = (functionNode, context) => {
15901
15891
  if (!isFunctionLike$1(functionNode)) return false;
15902
15892
  const functionRoot = findTransparentExpressionRoot(functionNode);
15903
15893
  const callbackCall = functionRoot.parent;
15904
- if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot && isReactHookCall(callbackCall, "useCallback", context.scopes) && isDirectJsxEventHandlerValue(callbackCall)) return true;
15894
+ if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot && isHookCall$2(callbackCall, "useCallback") && isDirectJsxEventHandlerValue(callbackCall)) return true;
15905
15895
  const parentNode = functionNode.parent;
15906
15896
  if (isDirectJsxEventHandlerValue(functionNode)) return true;
15907
15897
  if (!isNodeOfType(parentNode, "Property") || parentNode.value !== functionNode || parentNode.computed) return false;
@@ -15937,12 +15927,12 @@ const effectNeedsCleanup = defineRule({
15937
15927
  };
15938
15928
  return {
15939
15929
  CallExpression(node) {
15940
- if (isReactHookCall(node, "useCallback", context.scopes)) {
15930
+ if (isHookCall$2(node, "useCallback")) {
15941
15931
  const retainedCallback = getEffectCallback(node);
15942
15932
  if (retainedCallback && !isInlineRetainedHandlerFunction(retainedCallback, context)) reportRetainedLeak(retainedCallback);
15943
15933
  return;
15944
15934
  }
15945
- if (!isReactHookCall(node, CLEANUP_EFFECT_HOOK_NAMES, context.scopes)) return;
15935
+ if (!isHookCall$2(node, CLEANUP_EFFECT_HOOK_NAMES)) return;
15946
15936
  const callback = getEffectCallback(node);
15947
15937
  if (!callback) return;
15948
15938
  const usages = removeSynchronouslyReleasedUsages(callback, findSubscribeLikeUsages(callback, context), context);
@@ -15950,7 +15940,7 @@ const effectNeedsCleanup = defineRule({
15950
15940
  const firstUsage = findFirstUsageWithoutCleanup(callback, usages, context);
15951
15941
  if (!firstUsage) return;
15952
15942
  const resourceNoun = RESOURCE_NOUN_BY_KIND[firstUsage.kind];
15953
- const hookName = getCalleeName$1(node) ?? "effect";
15943
+ const hookName = getCalleeName$2(node) ?? "effect";
15954
15944
  context.report({
15955
15945
  node,
15956
15946
  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 +17283,7 @@ const symbolHasStableImportedAlias = (symbol, scopes) => {
17293
17283
  const resolvedSymbol = resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes);
17294
17284
  return resolvedSymbol !== null && resolvedSymbol !== symbol && resolvedSymbol.kind === "import";
17295
17285
  };
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);
17286
+ const symbolHasStableValue = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => symbolHasStableHookOrigin(symbol, scopes) || symbolHasStableImportedAlias(symbol, scopes) || symbolHasStableFunctionOrigin(symbol, scopes, visitedSymbolIds) || symbolHasStableMemoizedOrigin(symbol, scopes, visitedSymbolIds);
17328
17287
  //#endregion
17329
17288
  //#region src/plugin/utils/symbol-has-react-use-effect-event-origin.ts
17330
17289
  const symbolHasReactUseEffectEventOrigin = (symbol, scopes) => {
@@ -18507,7 +18466,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
18507
18466
  if (!isUsed) continue;
18508
18467
  const reportNode = declaredKeyToReportNode.get(declaredKey) ?? depsArgument;
18509
18468
  const rootSymbol = getRootSymbol(reportNode, context.scopes);
18510
- if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) || symbolHasStableValue(rootSymbol, context.scopes) || !isUnstableInitializer(rootSymbol.initializer)) continue;
18469
+ if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) || !isUnstableInitializer(rootSymbol.initializer)) continue;
18511
18470
  context.report({
18512
18471
  node: reportNode,
18513
18472
  message: buildUnstableDepMessage(hookName, declaredKey)
@@ -18823,7 +18782,7 @@ const flattenCalleeName = (callee) => {
18823
18782
  const PRAGMA = "React";
18824
18783
  const isReactFunctionCall = (node, expectedCall) => {
18825
18784
  if (!isNodeOfType(node, "CallExpression")) return false;
18826
- if (getCalleeName$1(node) !== expectedCall) return false;
18785
+ if (getCalleeName$2(node) !== expectedCall) return false;
18827
18786
  if (isNodeOfType(node.callee, "MemberExpression")) {
18828
18787
  const receiver = stripParenExpression(node.callee.object);
18829
18788
  return isNodeOfType(receiver, "Identifier") && receiver.name === PRAGMA;
@@ -19132,8 +19091,8 @@ const hooksNoNanInDeps = defineRule({
19132
19091
  severity: "warn",
19133
19092
  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.",
19134
19093
  create: (context) => ({ CallExpression(node) {
19135
- if (!isReactHookCall(node, HOOKS_WITH_DEP_ARRAY, context.scopes)) return;
19136
- const depsIndex = isReactHookCall(node, "useImperativeHandle", context.scopes) ? 2 : 1;
19094
+ if (!isHookCall$2(node, HOOKS_WITH_DEP_ARRAY)) return;
19095
+ const depsIndex = getCalleeName$2(node) === "useImperativeHandle" ? 2 : 1;
19137
19096
  const depsArgument = node.arguments[depsIndex];
19138
19097
  if (!depsArgument || !isNodeOfType(depsArgument, "ArrayExpression")) return;
19139
19098
  for (const element of depsArgument.elements) {
@@ -20304,7 +20263,7 @@ const isImportedSelectAtom = (callExpression) => {
20304
20263
  const isDeferredCallbackPosition$1 = (functionNode) => {
20305
20264
  const parent = functionNode.parent;
20306
20265
  if (isNodeOfType(parent, "CallExpression") && parent.arguments?.[0] === functionNode) {
20307
- const hookName = getCalleeName$1(parent);
20266
+ const hookName = getCalleeName$2(parent);
20308
20267
  if (hookName && MEMOIZING_HOOK_NAMES$1.has(hookName)) return true;
20309
20268
  if (hookName && EFFECT_HOOK_NAMES$1.has(hookName) && Boolean(parent.arguments?.[1])) return true;
20310
20269
  }
@@ -30375,7 +30334,7 @@ const DEFERRING_CALLEE_NAMES$1 = new Set([
30375
30334
  "on",
30376
30335
  "once"
30377
30336
  ]);
30378
- const getCalleeName = (callee) => {
30337
+ const getCalleeName$1 = (callee) => {
30379
30338
  if (!callee) return null;
30380
30339
  if (isNodeOfType(callee, "Identifier")) return callee.name;
30381
30340
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
@@ -30387,11 +30346,11 @@ const isDeferredCallbackPosition = (expression) => {
30387
30346
  const parent = parentOf(expression);
30388
30347
  if (!parent) return false;
30389
30348
  if (isNodeOfType(parent, "CallExpression") && argumentsInclude(parent.arguments, expression)) {
30390
- const name = getCalleeName(parent.callee);
30349
+ const name = getCalleeName$1(parent.callee);
30391
30350
  if (name && DEFERRING_CALLEE_NAMES$1.has(name)) return true;
30392
30351
  }
30393
30352
  if (isNodeOfType(parent, "NewExpression") && argumentsInclude(parent.arguments, expression)) {
30394
- const name = getCalleeName(parent.callee);
30353
+ const name = getCalleeName$1(parent.callee);
30395
30354
  if (name && (name.endsWith("Observer") || name === "Promise")) return true;
30396
30355
  }
30397
30356
  if (isNodeOfType(parent, "AssignmentExpression") && parent.right === expression && isNodeOfType(parent.left, "MemberExpression") && isNodeOfType(parent.left.property, "Identifier") && parent.left.property.name.startsWith("on")) return true;
@@ -30811,6 +30770,14 @@ const isHookCallee$1 = (analysis, node, hookName) => {
30811
30770
  if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
30812
30771
  return false;
30813
30772
  };
30773
+ const isUseEffect = (node) => {
30774
+ if (!node || !isNodeOfType(node, "CallExpression")) return false;
30775
+ const callee = node.callee;
30776
+ if (isNodeOfType(callee, "Identifier") && callee.name === "useEffect") return true;
30777
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
30778
+ const receiver = stripParenExpression(callee.object);
30779
+ return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useEffect";
30780
+ };
30814
30781
  const getEffectFn = (analysis, node) => {
30815
30782
  if (!isNodeOfType(node, "CallExpression")) return null;
30816
30783
  const fn = node.arguments?.[0];
@@ -32272,7 +32239,6 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
32272
32239
  sourceReferences,
32273
32240
  isDeferred: frame.isDeferred,
32274
32241
  isRenderKnownCopy,
32275
- isSynchronousRenderValue: !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue,
32276
32242
  matchesStateInitializer: doesMatchStateInitializer,
32277
32243
  resetsSourceState: false
32278
32244
  });
@@ -32288,71 +32254,27 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
32288
32254
  });
32289
32255
  };
32290
32256
  //#endregion
32291
- //#region src/plugin/rules/state-and-effects/utils/has-deferred-or-external-effect-work.ts
32292
- const DEFERRED_MEMBER_NAMES = new Set([
32293
- "catch",
32294
- "finally",
32295
- "then"
32296
- ]);
32297
- const hasDeferredOrExternalEffectWork = (analysis, effectNode, scopes) => {
32298
- const effectFunction = getEffectFn(analysis, effectNode);
32299
- if (!effectFunction) return false;
32300
- if (containsFetchCall(effectFunction, { stopAtFunctionBoundary: true })) return true;
32301
- const effectInvokedFunctions = collectEffectInvokedFunctions(effectFunction);
32302
- let didFindDeferredOrExternalWork = false;
32303
- walkAst(effectFunction, (child) => {
32304
- if (didFindDeferredOrExternalWork) return false;
32305
- if (child !== effectFunction && isFunctionLike$1(child) && !effectInvokedFunctions.has(child)) return false;
32306
- if (isNodeOfType(child, "AssignmentExpression")) {
32307
- const assignmentTarget = child.left;
32308
- if ((isNodeOfType(assignmentTarget, "MemberExpression") ? getStaticPropertyName(assignmentTarget) : null)?.startsWith("on") && isFunctionLike$1(child.right)) {
32309
- didFindDeferredOrExternalWork = true;
32310
- return false;
32311
- }
32312
- }
32313
- if (!isNodeOfType(child, "CallExpression")) return;
32314
- if (isSubscribeOrObserveCallExpression(child)) {
32315
- didFindDeferredOrExternalWork = true;
32316
- return false;
32317
- }
32318
- const localFunction = resolveExactLocalFunction(child.callee, scopes);
32319
- if (isFunctionLike$1(localFunction) && localFunction.async) {
32320
- didFindDeferredOrExternalWork = true;
32321
- return false;
32322
- }
32323
- const callee = child.callee;
32324
- if (isNodeOfType(callee, "Identifier") && TIMER_AND_SCHEDULER_DIRECT_CALLEE_NAMES.has(callee.name)) {
32325
- didFindDeferredOrExternalWork = true;
32326
- return false;
32327
- }
32328
- const memberName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
32329
- if (memberName && DEFERRED_MEMBER_NAMES.has(memberName)) {
32330
- didFindDeferredOrExternalWork = true;
32331
- return false;
32332
- }
32333
- });
32334
- return didFindDeferredOrExternalWork;
32335
- };
32336
- //#endregion
32337
32257
  //#region src/plugin/rules/state-and-effects/no-adjust-state-on-prop-change.ts
32338
32258
  const noAdjustStateOnPropChange = defineRule({
32339
32259
  id: "no-adjust-state-on-prop-change",
32340
- title: "State adjusted after a prop changes",
32260
+ title: "State synced to a prop inside an effect",
32341
32261
  severity: "warn",
32342
32262
  tags: ["test-noise"],
32343
- recommendation: "Remove the adjustment effect by deriving values during render, resetting the component with a key, or updating related state in the event that changes the prop. Avoid tracking the previous prop in more state, which preserves the duplication. See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes",
32263
+ 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",
32344
32264
  create: (context) => ({ CallExpression(node) {
32345
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
32265
+ if (!isReactApiCall(node, "useEffect", context.scopes, {
32266
+ allowGlobalReactNamespace: true,
32267
+ allowUnboundBareCalls: true,
32268
+ resolveConditionalAliases: true,
32269
+ resolveNamedAliases: true
32270
+ })) return;
32346
32271
  const analysis = getProgramAnalysis(node);
32347
32272
  if (!analysis) return;
32348
32273
  const dependencyReferences = getEffectDepsRefs(analysis, node);
32349
32274
  if (!dependencyReferences) return;
32350
32275
  if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
32351
- const facts = collectEffectStateWriteFacts(analysis, context, node, context.filename);
32352
- if (hasCleanup(analysis, node) || hasDeferredOrExternalEffectWork(analysis, node, context.scopes) || facts.some((fact) => fact.isDeferred)) return;
32353
- for (const fact of facts) {
32354
- if (!fact.isSynchronousRenderValue || fact.resetsSourceState) continue;
32355
- if (fact.sourceReferences.flatMap((reference) => getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) continue;
32276
+ for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
32277
+ if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
32356
32278
  context.report({
32357
32279
  node: fact.callExpression,
32358
32280
  message: "This effect adjusts state after a prop changes, so users briefly see the stale value."
@@ -34365,7 +34287,7 @@ const declarationBodyContainsHookCall = (symbol) => {
34365
34287
  walkAst(componentFunction, (descendant) => {
34366
34288
  if (didFindHookCall) return false;
34367
34289
  if (!isNodeOfType(descendant, "CallExpression")) return;
34368
- const calleeName = getCalleeName$1(descendant);
34290
+ const calleeName = getCalleeName$2(descendant);
34369
34291
  if (calleeName && isReactHookName(calleeName)) {
34370
34292
  didFindHookCall = true;
34371
34293
  return false;
@@ -34390,7 +34312,7 @@ const isReturnedFromUseCallbackAdapter = (callNode) => {
34390
34312
  if (isNodeOfType(parent, "ArrowFunctionExpression")) {
34391
34313
  if (parent.body !== current) return false;
34392
34314
  const grandparent = parent.parent;
34393
- return isNodeOfType(grandparent, "CallExpression") && getCalleeName$1(grandparent) === "useCallback" && grandparent.arguments.some((argumentNode) => argumentNode === parent);
34315
+ return isNodeOfType(grandparent, "CallExpression") && getCalleeName$2(grandparent) === "useCallback" && grandparent.arguments.some((argumentNode) => argumentNode === parent);
34394
34316
  }
34395
34317
  if (!isNodeOfType(parent, "ConditionalExpression") && !isNodeOfType(parent, "LogicalExpression")) return false;
34396
34318
  current = parent;
@@ -36922,7 +36844,12 @@ const noDerivedState = defineRule({
36922
36844
  for (const fact of collectRenderStateWriteFacts(analysis, componentBody, context.filename)) reportStateWrite(fact.callExpression, fact.stateDeclarator);
36923
36845
  } }).visitors,
36924
36846
  CallExpression(node) {
36925
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
36847
+ if (!isReactApiCall(node, "useEffect", context.scopes, {
36848
+ allowGlobalReactNamespace: true,
36849
+ allowUnboundBareCalls: true,
36850
+ resolveConditionalAliases: true,
36851
+ resolveNamedAliases: true
36852
+ })) return;
36926
36853
  const analysis = getProgramAnalysis(node);
36927
36854
  if (!analysis) return;
36928
36855
  for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
@@ -36942,7 +36869,12 @@ const noDerivedStateEffect = defineRule({
36942
36869
  tags: ["test-noise"],
36943
36870
  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",
36944
36871
  create: (context) => ({ CallExpression(node) {
36945
- if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes)) return;
36872
+ if (!isReactApiCall(node, EFFECT_HOOK_NAMES$1, context.scopes, {
36873
+ allowGlobalReactNamespace: true,
36874
+ allowUnboundBareCalls: true,
36875
+ resolveConditionalAliases: true,
36876
+ resolveNamedAliases: true
36877
+ })) return;
36946
36878
  const analysis = getProgramAnalysis(node);
36947
36879
  if (!analysis) return;
36948
36880
  if (!collectEffectStateWriteFacts(analysis, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
@@ -37065,7 +36997,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
37065
36997
  if (isFunctionLike$1(cursor)) {
37066
36998
  const parent = cursor.parent ?? null;
37067
36999
  if (parent && isNodeOfType(parent, "CallExpression")) {
37068
- const calleeName = getCalleeName$1(parent);
37000
+ const calleeName = getCalleeName$2(parent);
37069
37001
  if (calleeName !== null && EFFECT_HOOK_NAME_PATTERN.test(calleeName) && (parent.arguments ?? []).some((argument) => argument === cursor)) return cursor;
37070
37002
  }
37071
37003
  }
@@ -37136,7 +37068,7 @@ const isNonHandlerHookCallback = (functionNode) => {
37136
37068
  const parent = functionNode.parent ?? null;
37137
37069
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
37138
37070
  if (!(parent.arguments ?? []).some((argument) => argument === functionNode)) return false;
37139
- const calleeName = getCalleeName$1(parent);
37071
+ const calleeName = getCalleeName$2(parent);
37140
37072
  return calleeName !== null && isReactHookName(calleeName) && calleeName !== "useCallback";
37141
37073
  };
37142
37074
  const isHandlerShapedReseed = (setterCall, componentFunction) => {
@@ -37218,7 +37150,7 @@ const noDerivedUseState = defineRule({
37218
37150
  return {
37219
37151
  ...propStackTracker.visitors,
37220
37152
  CallExpression(node) {
37221
- if (!isReactHookCall(node, "useState", context.scopes) || !node.arguments?.length) return;
37153
+ if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
37222
37154
  const seed = unwrapInitializerSeed(node.arguments[0]);
37223
37155
  const reportStalePropCopy = (propName) => {
37224
37156
  if (isIntentionalSnapshotState(node)) return;
@@ -37907,7 +37839,12 @@ const collectUseStateBindings = (componentBody, scopes) => {
37907
37839
  const setterElement = elements[1];
37908
37840
  if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
37909
37841
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
37910
- if (!isReactHookCall(declarator.init, "useState", scopes)) continue;
37842
+ if (!(scopes ? isReactApiCall(declarator.init, "useState", scopes, {
37843
+ allowGlobalReactNamespace: true,
37844
+ allowUnboundBareCalls: true,
37845
+ resolveConditionalAliases: true,
37846
+ resolveNamedAliases: true
37847
+ }) : isHookCall$2(declarator.init, "useState"))) continue;
37911
37848
  bindings.push({
37912
37849
  valueName: valueElement.name,
37913
37850
  setterName: setterElement.name,
@@ -38074,7 +38011,7 @@ const noDirectStateMutation = defineRule({
38074
38011
  create: (context) => {
38075
38012
  const checkComponent = (componentBody) => {
38076
38013
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
38077
- const bindings = collectUseStateBindings(componentBody, context.scopes);
38014
+ const bindings = collectUseStateBindings(componentBody);
38078
38015
  if (bindings.length === 0) return;
38079
38016
  const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
38080
38017
  const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
@@ -38641,7 +38578,12 @@ const findTopLevelEffectCalls = (componentBody, scopes) => {
38641
38578
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
38642
38579
  const expression = unwrapDiscardedExpression(statement);
38643
38580
  if (!isNodeOfType(expression, "CallExpression")) continue;
38644
- if (!isReactHookCall(expression, EFFECT_HOOK_NAMES$1, scopes)) continue;
38581
+ if (!isReactApiCall(expression, EFFECT_HOOK_NAMES$1, scopes, {
38582
+ allowGlobalReactNamespace: true,
38583
+ allowUnboundBareCalls: true,
38584
+ resolveConditionalAliases: true,
38585
+ resolveNamedAliases: true
38586
+ })) continue;
38645
38587
  effectCalls.push(expression);
38646
38588
  }
38647
38589
  return effectCalls;
@@ -38885,7 +38827,7 @@ const collectStorageHookSetterNames = (componentBody) => {
38885
38827
  for (const declarator of statement.declarations ?? []) {
38886
38828
  if (!isNodeOfType(declarator.id, "ArrayPattern")) continue;
38887
38829
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
38888
- const calleeName = getCalleeName$1(declarator.init);
38830
+ const calleeName = getCalleeName$2(declarator.init);
38889
38831
  if (!calleeName || !STORAGE_HOOK_PATTERN.test(calleeName)) continue;
38890
38832
  for (const element of declarator.id.elements ?? []) if (isNodeOfType(element, "Identifier") && isSetterIdentifier(element.name)) setterNames.add(element.name);
38891
38833
  }
@@ -39373,7 +39315,7 @@ const noEffectEventHandler = defineRule({
39373
39315
  return {
39374
39316
  ...propStackTracker.visitors,
39375
39317
  CallExpression(node) {
39376
- if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes) || (node.arguments?.length ?? 0) < 2) return;
39318
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
39377
39319
  const callback = getEffectCallback(node);
39378
39320
  if (!callback) return;
39379
39321
  const analysis = getProgramAnalysis(node);
@@ -39493,14 +39435,14 @@ const noEffectEventInDeps = defineRule({
39493
39435
  if (!isNodeOfType(declaratorNode.id, "Identifier")) return;
39494
39436
  const initializer = declaratorNode.init;
39495
39437
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return;
39496
- if (!isReactHookCall(initializer, "useEffectEvent", context.scopes)) return;
39438
+ if (!isHookCall$2(initializer, "useEffectEvent")) return;
39497
39439
  if (isNonReactEffectEventCallee(initializer.callee, declaratorNode, context.scopes)) return;
39498
39440
  componentBindings.addBindingToCurrentFrame(declaratorNode.id.name);
39499
39441
  } });
39500
39442
  return {
39501
39443
  ...componentBindings.visitors,
39502
39444
  CallExpression(node) {
39503
- if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes) || node.arguments.length < 2) return;
39445
+ if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
39504
39446
  if (!componentBindings.isInsideComponent()) return;
39505
39447
  const depsNode = node.arguments[1];
39506
39448
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -39557,7 +39499,7 @@ const noEffectWithFreshDeps = defineRule({
39557
39499
  node: finding.reportNode,
39558
39500
  message: `A dependency inside this custom Hook changes every render because \`${finding.bindingName}\` is a new ${finding.kind} built fresh each time.`
39559
39501
  });
39560
- if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes)) return;
39502
+ if (!isHookCall$2(node, HOOKS_WITH_DEPS)) return;
39561
39503
  const args = node.arguments ?? [];
39562
39504
  if (args.length < 2) return;
39563
39505
  const depsNode = args[1];
@@ -39818,7 +39760,7 @@ const noEventHandler = defineRule({
39818
39760
  severity: "warn",
39819
39761
  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",
39820
39762
  create: (context) => ({ CallExpression(node) {
39821
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
39763
+ if (!isUseEffect(node)) return;
39822
39764
  const analysis = getProgramAnalysis(node);
39823
39765
  if (!analysis || hasCleanup(analysis, node)) return;
39824
39766
  const frames = collectBoundedEffectExecutionFrames(analysis, node);
@@ -40280,25 +40222,25 @@ const addDeclarationBindings = (statement, scope) => {
40280
40222
  }
40281
40223
  if (isNodeOfType(statement, "FunctionDeclaration") && statement.id) addPatternBindings(statement.id, scope);
40282
40224
  };
40283
- const collectRenderReachableNamesFromStatements = (statements, names, scope, scopes, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
40225
+ const collectRenderReachableNamesFromStatements = (statements, names, scope, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
40284
40226
  let hasReturn = false;
40285
- for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope, scopes, eventHandlerReferenceNames)) hasReturn = true;
40227
+ for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope, eventHandlerReferenceNames)) hasReturn = true;
40286
40228
  else addDeclarationBindings(statement, scope);
40287
40229
  return hasReturn;
40288
40230
  };
40289
- const collectRenderReachableNamesFromStatement = (statement, names, scope, scopes, eventHandlerReferenceNames) => {
40231
+ const collectRenderReachableNamesFromStatement = (statement, names, scope, eventHandlerReferenceNames) => {
40290
40232
  if (isNodeOfType(statement, "ReturnStatement")) {
40291
40233
  if (statement.argument) addNames(names, collectScopedReferenceNames(statement.argument, scope, eventHandlerReferenceNames));
40292
40234
  return true;
40293
40235
  }
40294
- if (isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "CallExpression") && (isNodeOfType(statement.expression.callee, "Identifier") && isReactHookName(statement.expression.callee.name) || isReactHookCall(statement.expression, BUILTIN_HOOK_NAMES, scopes)) && !isReactHookCall(statement.expression, EFFECT_HOOK_NAMES$1, scopes)) {
40236
+ 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)) {
40295
40237
  for (const argument of statement.expression.arguments ?? []) addNames(names, collectScopedReferenceNames(argument, scope, eventHandlerReferenceNames));
40296
40238
  return false;
40297
40239
  }
40298
- if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope), scopes, eventHandlerReferenceNames);
40240
+ if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope), eventHandlerReferenceNames);
40299
40241
  if (isNodeOfType(statement, "IfStatement")) {
40300
- const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope, scopes, eventHandlerReferenceNames);
40301
- const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope, scopes, eventHandlerReferenceNames) : false;
40242
+ const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope, eventHandlerReferenceNames);
40243
+ const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope, eventHandlerReferenceNames) : false;
40302
40244
  if (consequentHasReturn || alternateHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
40303
40245
  return consequentHasReturn || alternateHasReturn;
40304
40246
  }
@@ -40306,7 +40248,7 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
40306
40248
  let hasReturn = false;
40307
40249
  for (const switchCase of statement.cases ?? []) {
40308
40250
  const caseScope = createBlockBindingScope(scope);
40309
- if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope, scopes, eventHandlerReferenceNames)) continue;
40251
+ if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope, eventHandlerReferenceNames)) continue;
40310
40252
  hasReturn = true;
40311
40253
  if (switchCase.test) addNames(names, collectScopedReferenceNames(switchCase.test, scope, eventHandlerReferenceNames));
40312
40254
  }
@@ -40314,25 +40256,25 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
40314
40256
  return hasReturn;
40315
40257
  }
40316
40258
  if (isNodeOfType(statement, "TryStatement")) {
40317
- const blockHasReturn = collectRenderReachableNamesFromStatement(statement.block, names, scope, scopes, eventHandlerReferenceNames);
40318
- const handlerHasReturn = statement.handler ? collectRenderReachableNamesFromStatement(statement.handler, names, scope, scopes, eventHandlerReferenceNames) : false;
40319
- const finalizerHasReturn = statement.finalizer ? collectRenderReachableNamesFromStatement(statement.finalizer, names, scope, scopes, eventHandlerReferenceNames) : false;
40259
+ const blockHasReturn = collectRenderReachableNamesFromStatement(statement.block, names, scope, eventHandlerReferenceNames);
40260
+ const handlerHasReturn = statement.handler ? collectRenderReachableNamesFromStatement(statement.handler, names, scope, eventHandlerReferenceNames) : false;
40261
+ const finalizerHasReturn = statement.finalizer ? collectRenderReachableNamesFromStatement(statement.finalizer, names, scope, eventHandlerReferenceNames) : false;
40320
40262
  return blockHasReturn || handlerHasReturn || finalizerHasReturn;
40321
40263
  }
40322
40264
  if (isNodeOfType(statement, "CatchClause")) {
40323
40265
  const catchScope = createBlockBindingScope(scope);
40324
40266
  addPatternBindings(statement.param, catchScope);
40325
- return collectRenderReachableNamesFromStatement(statement.body, names, catchScope, scopes, eventHandlerReferenceNames);
40267
+ return collectRenderReachableNamesFromStatement(statement.body, names, catchScope, eventHandlerReferenceNames);
40326
40268
  }
40327
40269
  if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
40328
- const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, scopes, eventHandlerReferenceNames);
40270
+ const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
40329
40271
  if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
40330
40272
  return bodyHasReturn;
40331
40273
  }
40332
40274
  if (isNodeOfType(statement, "ForStatement")) {
40333
40275
  const loopScope = createBlockBindingScope(scope);
40334
40276
  if (statement.init) addDeclarationBindings(statement.init, loopScope);
40335
- if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, scopes, eventHandlerReferenceNames)) return false;
40277
+ if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
40336
40278
  if (statement.init) addNames(names, collectScopedReferenceNames(statement.init, loopScope, eventHandlerReferenceNames));
40337
40279
  if (statement.test) addNames(names, collectScopedReferenceNames(statement.test, loopScope, eventHandlerReferenceNames));
40338
40280
  if (statement.update) addNames(names, collectScopedReferenceNames(statement.update, loopScope, eventHandlerReferenceNames));
@@ -40342,22 +40284,22 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
40342
40284
  const rightNames = collectScopedReferenceNames(statement.right, scope, eventHandlerReferenceNames);
40343
40285
  const loopScope = createBlockBindingScope(scope);
40344
40286
  if (isNodeOfType(statement.left, "VariableDeclaration")) addDeclarationBindings(statement.left, loopScope);
40345
- if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, scopes, eventHandlerReferenceNames)) return false;
40287
+ if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
40346
40288
  addNames(names, rightNames);
40347
40289
  return true;
40348
40290
  }
40349
- if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope, scopes, eventHandlerReferenceNames);
40291
+ if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
40350
40292
  if (isNodeOfType(statement, "WithStatement")) {
40351
- const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, scopes, eventHandlerReferenceNames);
40293
+ const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
40352
40294
  if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.object, scope, eventHandlerReferenceNames));
40353
40295
  return bodyHasReturn;
40354
40296
  }
40355
40297
  return false;
40356
40298
  };
40357
- const collectRenderReachableNames = (componentBody, scopes, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
40299
+ const collectRenderReachableNames = (componentBody, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
40358
40300
  const names = /* @__PURE__ */ new Set();
40359
40301
  if (!isNodeOfType(componentBody, "BlockStatement")) return names;
40360
- collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(), scopes, eventHandlerReferenceNames);
40302
+ collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(), eventHandlerReferenceNames);
40361
40303
  return names;
40362
40304
  };
40363
40305
  //#endregion
@@ -40380,36 +40322,42 @@ const expandTransitiveDependencies = (seedNames, dependencyGraph) => {
40380
40322
  };
40381
40323
  //#endregion
40382
40324
  //#region src/plugin/rules/state-and-effects/utils/collect-function-like-local-names.ts
40383
- const isFunctionLikeReference = (node, functionLikeLocalNames, scope, scopes) => {
40384
- if (isInlineFunctionExpression(node) || isReactHookCall(node, "useCallback", scopes)) return true;
40325
+ const isUseCallbackCall = (node) => isNodeOfType(node, "CallExpression") && getCalleeName(node.callee) === "useCallback";
40326
+ const getCalleeName = (node) => {
40327
+ if (isNodeOfType(node, "Identifier")) return node.name;
40328
+ if (isNodeOfType(node, "MemberExpression")) return getStaticMemberPropertyName(node);
40329
+ return null;
40330
+ };
40331
+ const isFunctionLikeReference = (node, functionLikeLocalNames, scope) => {
40332
+ if (isInlineFunctionExpression(node) || isUseCallbackCall(node)) return true;
40385
40333
  if (isNodeOfType(node, "Identifier")) return functionLikeLocalNames.has(resolveBindingName(scope, node.name));
40386
40334
  const memberReferenceName = getStaticMemberReferenceName(node, (name) => resolveBindingName(scope, name));
40387
40335
  return Boolean(memberReferenceName && functionLikeLocalNames.has(memberReferenceName));
40388
40336
  };
40389
- const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope, scopes) => {
40337
+ const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope) => {
40390
40338
  if (!isNodeOfType(node, "ObjectExpression")) return;
40391
40339
  for (const property of node.properties ?? []) {
40392
40340
  if (!isNodeOfType(property, "Property")) continue;
40393
40341
  const propertyName = getStaticPropertyKeyName(property, { stringifyNonStringLiterals: true });
40394
40342
  if (!propertyName) continue;
40395
- if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope, scopes)) continue;
40343
+ if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope)) continue;
40396
40344
  functionLikeLocalNames.add(`${objectBindingName}.${propertyName}`);
40397
40345
  }
40398
40346
  };
40399
- const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope, scopes) => {
40347
+ const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope) => {
40400
40348
  if (!isNodeOfType(statement, "VariableDeclaration")) return;
40401
40349
  const declarationScope = getVariableDeclarationScope(statement, scope);
40402
40350
  for (const declarator of statement.declarations ?? []) {
40403
40351
  const declaredBindingNames = addPatternBindings(declarator.id, declarationScope);
40404
40352
  if (!declarator.init) continue;
40405
- const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope, scopes);
40353
+ const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope);
40406
40354
  for (const declaredBindingName of declaredBindingNames) {
40407
40355
  if (isFunctionReference) functionLikeLocalNames.add(declaredBindingName);
40408
- addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope, scopes);
40356
+ addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope);
40409
40357
  }
40410
40358
  }
40411
40359
  };
40412
- const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope, scopes) => {
40360
+ const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope) => {
40413
40361
  if (isNodeOfType(statement, "FunctionDeclaration")) {
40414
40362
  if (statement.id) {
40415
40363
  const declaredBindingNames = addPatternBindings(statement.id, scope);
@@ -40418,61 +40366,61 @@ const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope,
40418
40366
  return;
40419
40367
  }
40420
40368
  if (isNodeOfType(statement, "VariableDeclaration")) {
40421
- addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope, scopes);
40369
+ addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope);
40422
40370
  return;
40423
40371
  }
40424
40372
  if (isNodeOfType(statement, "BlockStatement")) {
40425
- collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope), scopes);
40373
+ collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope));
40426
40374
  return;
40427
40375
  }
40428
40376
  if (isNodeOfType(statement, "IfStatement")) {
40429
- collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope, scopes);
40430
- if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope, scopes);
40377
+ collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope);
40378
+ if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope);
40431
40379
  return;
40432
40380
  }
40433
40381
  if (isNodeOfType(statement, "SwitchStatement")) {
40434
- for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope), scopes);
40382
+ for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope));
40435
40383
  return;
40436
40384
  }
40437
40385
  if (isNodeOfType(statement, "TryStatement")) {
40438
- collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope, scopes);
40386
+ collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope);
40439
40387
  if (statement.handler) {
40440
40388
  const catchScope = createBlockBindingScope(scope);
40441
40389
  addPatternBindings(statement.handler.param, catchScope);
40442
- collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope, scopes);
40390
+ collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope);
40443
40391
  }
40444
- if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope, scopes);
40392
+ if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope);
40445
40393
  return;
40446
40394
  }
40447
40395
  if (isNodeOfType(statement, "ForStatement")) {
40448
40396
  const loopScope = createBlockBindingScope(scope);
40449
- if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope, scopes);
40450
- collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope, scopes);
40397
+ if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope);
40398
+ collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
40451
40399
  return;
40452
40400
  }
40453
40401
  if (isNodeOfType(statement, "ForInStatement") || isNodeOfType(statement, "ForOfStatement")) {
40454
40402
  const loopScope = createBlockBindingScope(scope);
40455
- if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope, scopes);
40403
+ if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope);
40456
40404
  else addPatternBindings(statement.left, loopScope);
40457
- collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope, scopes);
40405
+ collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
40458
40406
  return;
40459
40407
  }
40460
40408
  if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
40461
- collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope, scopes);
40409
+ collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
40462
40410
  return;
40463
40411
  }
40464
- if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope, scopes);
40412
+ if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
40465
40413
  };
40466
- const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope, scopes) => {
40467
- for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope, scopes);
40414
+ const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope) => {
40415
+ for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope);
40468
40416
  };
40469
- const collectFunctionLikeLocalNames = (componentBody, scopes) => {
40417
+ const collectFunctionLikeLocalNames = (componentBody) => {
40470
40418
  const functionLikeLocalNames = /* @__PURE__ */ new Set();
40471
40419
  if (!isNodeOfType(componentBody, "BlockStatement")) return functionLikeLocalNames;
40472
40420
  let previousSize = -1;
40473
40421
  while (previousSize !== functionLikeLocalNames.size) {
40474
40422
  previousSize = functionLikeLocalNames.size;
40475
- collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope(), scopes);
40423
+ collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope());
40476
40424
  }
40477
40425
  return functionLikeLocalNames;
40478
40426
  };
@@ -40512,17 +40460,17 @@ const noEventTriggerState = defineRule({
40512
40460
  create: (context) => {
40513
40461
  const checkComponent = (componentBody) => {
40514
40462
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
40515
- const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
40463
+ const useStateBindings = collectUseStateBindings(componentBody);
40516
40464
  if (useStateBindings.length === 0) return;
40517
40465
  const analysis = getProgramAnalysis(componentBody);
40518
40466
  if (!analysis) return;
40519
40467
  const localStateNames = new Set(useStateBindings.map((binding) => binding.valueName));
40520
- const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
40468
+ const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
40521
40469
  const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
40522
- const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames), dependencyGraph);
40470
+ const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody, eventHandlerReferenceNames), dependencyGraph);
40523
40471
  walkAst(componentBody, (effectCall) => {
40524
40472
  if (!isNodeOfType(effectCall, "CallExpression")) return;
40525
- if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) return;
40473
+ if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) return;
40526
40474
  if ((effectCall.arguments?.length ?? 0) < 2) return;
40527
40475
  const depsNode = effectCall.arguments[1];
40528
40476
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -42351,7 +42299,7 @@ const noInitializeState = defineRule({
42351
42299
  tags: ["test-noise"],
42352
42300
  recommendation: "Pass the initial value directly to useState() instead of setting it from a mount-only useEffect. For SSR hydration, prefer useSyncExternalStore().",
42353
42301
  create: (context) => ({ CallExpression(node) {
42354
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
42302
+ if (!isUseEffect(node)) return;
42355
42303
  const dependencies = node.arguments?.[1];
42356
42304
  if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
42357
42305
  const analysis = getProgramAnalysis(node);
@@ -43963,7 +43911,7 @@ const noMirrorPropEffect = defineRule({
43963
43911
  const setterElement = elements[1];
43964
43912
  if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
43965
43913
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
43966
- if (!isReactHookCall(declarator.init, "useState", context.scopes)) continue;
43914
+ if (!isHookCall$2(declarator.init, "useState")) continue;
43967
43915
  const initializer = declarator.init.arguments?.[0];
43968
43916
  if (!initializer) continue;
43969
43917
  const propRootName = getPropRootName(initializer, propNames);
@@ -43981,7 +43929,7 @@ const noMirrorPropEffect = defineRule({
43981
43929
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
43982
43930
  const effectCall = unwrapDiscardedExpression(statement);
43983
43931
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
43984
- if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
43932
+ if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
43985
43933
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
43986
43934
  const depsNode = effectCall.arguments[1];
43987
43935
  if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
@@ -44390,7 +44338,7 @@ const noMultiComp = defineRule({
44390
44338
  });
44391
44339
  //#endregion
44392
44340
  //#region src/plugin/rules/state-and-effects/no-mutable-in-deps.ts
44393
- const collectUseRefBindingNames = (componentBody, scopes) => {
44341
+ const collectUseRefBindingNames = (componentBody) => {
44394
44342
  const useRefBindings = /* @__PURE__ */ new Set();
44395
44343
  if (!isNodeOfType(componentBody, "BlockStatement")) return useRefBindings;
44396
44344
  for (const statement of componentBody.body ?? []) {
@@ -44398,7 +44346,7 @@ const collectUseRefBindingNames = (componentBody, scopes) => {
44398
44346
  for (const declarator of statement.declarations ?? []) {
44399
44347
  if (!isNodeOfType(declarator.id, "Identifier")) continue;
44400
44348
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
44401
- if (!isReactHookCall(declarator.init, "useRef", scopes)) continue;
44349
+ if (!isHookCall$2(declarator.init, "useRef")) continue;
44402
44350
  useRefBindings.add(declarator.id.name);
44403
44351
  }
44404
44352
  }
@@ -44433,12 +44381,12 @@ const noMutableInDeps = defineRule({
44433
44381
  create: (context) => {
44434
44382
  const checkComponent = (componentBody, componentParams = []) => {
44435
44383
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
44436
- const useRefBindingNames = collectUseRefBindingNames(componentBody, context.scopes);
44384
+ const useRefBindingNames = collectUseRefBindingNames(componentBody);
44437
44385
  const localBindingNames = collectLocalBindingNames(componentBody);
44438
44386
  for (const param of componentParams) collectPatternNames(param, localBindingNames);
44439
44387
  walkAst(componentBody, (child) => {
44440
44388
  if (!isNodeOfType(child, "CallExpression")) return;
44441
- if (!isReactHookCall(child, HOOKS_WITH_DEPS, context.scopes)) return;
44389
+ if (!isHookCall$2(child, HOOKS_WITH_DEPS)) return;
44442
44390
  if ((child.arguments?.length ?? 0) < 2) return;
44443
44391
  const depsNode = child.arguments[1];
44444
44392
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -46351,10 +46299,16 @@ const noPassDataToParent = defineRule({
46351
46299
  tags: ["test-noise"],
46352
46300
  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",
46353
46301
  create: (context) => {
46354
- const isReactUseRefCall = (node) => isReactHookCall(node, "useRef", context.scopes);
46355
- const isReactUseEffectCall = (node) => isReactHookCall(node, "useEffect", context.scopes);
46302
+ const isReactUseRefCall = (node) => isReactApiCall(node, "useRef", context.scopes, {
46303
+ allowGlobalReactNamespace: true,
46304
+ allowUnboundBareCalls: true
46305
+ });
46306
+ const isReactUseEffectCall = (node) => isReactApiCall(node, "useEffect", context.scopes, {
46307
+ allowGlobalReactNamespace: true,
46308
+ allowUnboundBareCalls: true
46309
+ });
46356
46310
  return { CallExpression(node) {
46357
- if (!isReactUseEffectCall(node)) return;
46311
+ if (!isUseEffect(node)) return;
46358
46312
  const analysis = getProgramAnalysis(node);
46359
46313
  if (!analysis) return;
46360
46314
  if (hasCleanup(analysis, node)) return;
@@ -46641,7 +46595,7 @@ const noPassLiveStateToParent = defineRule({
46641
46595
  tags: ["test-noise"],
46642
46596
  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",
46643
46597
  create: (context) => ({ CallExpression(node) {
46644
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
46598
+ if (!isUseEffect(node)) return;
46645
46599
  const analysis = getProgramAnalysis(node);
46646
46600
  if (!analysis) return;
46647
46601
  const effectFnRefs = getEffectFnRefs(analysis, node);
@@ -47056,7 +47010,7 @@ const hasPreviousValueDep = (effectNode, depElements) => {
47056
47010
  if (!isNodeOfType(element, "Identifier")) continue;
47057
47011
  const binding = findVariableInitializer(effectNode, element.name);
47058
47012
  if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) continue;
47059
- const calleeName = getCalleeName$1(binding.initializer);
47013
+ const calleeName = getCalleeName$2(binding.initializer);
47060
47014
  if (calleeName && PREVIOUS_VALUE_HOOK_PATTERN.test(calleeName)) return true;
47061
47015
  }
47062
47016
  return false;
@@ -47078,7 +47032,7 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
47078
47032
  if (!isNodeOfType(receiver, "Identifier")) return null;
47079
47033
  const binding = findVariableInitializer(callExpression, receiver.name);
47080
47034
  if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
47081
- if (getCalleeName$1(binding.initializer) !== "useRef") return null;
47035
+ if (getCalleeName$2(binding.initializer) !== "useRef") return null;
47082
47036
  const callbackArgument = binding.initializer.arguments?.[0];
47083
47037
  if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
47084
47038
  return isPropName(callbackArgument.name) ? callbackArgument.name : null;
@@ -47110,7 +47064,7 @@ const noPropCallbackInEffect = defineRule({
47110
47064
  return {
47111
47065
  ...propStackTracker.visitors,
47112
47066
  CallExpression(node) {
47113
- if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes) || (node.arguments?.length ?? 0) < 2) return;
47067
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
47114
47068
  const callback = getEffectCallback(node);
47115
47069
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
47116
47070
  const depsNode = node.arguments[1];
@@ -49003,7 +48957,7 @@ const noResetAllStateOnPropChange = defineRule({
49003
48957
  tags: ["test-noise"],
49004
48958
  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",
49005
48959
  create: (context) => ({ CallExpression(node) {
49006
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
48960
+ if (!isUseEffect(node)) return;
49007
48961
  const analysis = getProgramAnalysis(node);
49008
48962
  if (!analysis) return;
49009
48963
  const effectFnRefs = getEffectFnRefs(analysis, node);
@@ -49272,7 +49226,7 @@ const isTanStackServerFnHandlerCall = (node) => {
49272
49226
  if (node.callee.property.name !== "handler") return false;
49273
49227
  let currentNode = node.callee.object;
49274
49228
  while (isNodeOfType(currentNode, "CallExpression")) {
49275
- const calleeName = getCalleeName$1(currentNode);
49229
+ const calleeName = getCalleeName$2(currentNode);
49276
49230
  if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) return true;
49277
49231
  if (!isNodeOfType(currentNode.callee, "MemberExpression")) return false;
49278
49232
  currentNode = currentNode.callee.object;
@@ -49773,7 +49727,7 @@ const noSelfUpdatingEffect = defineRule({
49773
49727
  create: (context) => {
49774
49728
  const checkFunctionScope = (functionBody) => {
49775
49729
  if (!functionBody || !isNodeOfType(functionBody, "BlockStatement")) return;
49776
- const useStateBindings = collectUseStateBindings(functionBody, context.scopes);
49730
+ const useStateBindings = collectUseStateBindings(functionBody);
49777
49731
  if (useStateBindings.length === 0) return;
49778
49732
  const setterNameToStateName = /* @__PURE__ */ new Map();
49779
49733
  for (const binding of useStateBindings) setterNameToStateName.set(binding.setterName, binding.valueName);
@@ -49782,7 +49736,7 @@ const noSelfUpdatingEffect = defineRule({
49782
49736
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
49783
49737
  const effectCall = unwrapDiscardedExpression(statement);
49784
49738
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
49785
- if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
49739
+ if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
49786
49740
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
49787
49741
  const dependencyStateNames = collectDependencyStateNames(effectCall.arguments[1]);
49788
49742
  if (dependencyStateNames.size === 0) continue;
@@ -49868,7 +49822,7 @@ const noSetStateInRender = defineRule({
49868
49822
  create: (context) => {
49869
49823
  const checkComponent = (componentBody) => {
49870
49824
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
49871
- const setterNames = new Set(collectUseStateBindings(componentBody, context.scopes).map((binding) => binding.setterName));
49825
+ const setterNames = new Set(collectUseStateBindings(componentBody).map((binding) => binding.setterName));
49872
49826
  if (setterNames.size === 0) return;
49873
49827
  for (const statement of componentBody.body ?? []) {
49874
49828
  const setterCall = isUnconditionalSetterCallStatement(statement, setterNames);
@@ -50117,10 +50071,10 @@ const collectTimerRefUsageFacts = (ownerScope, refName) => {
50117
50071
  });
50118
50072
  return facts;
50119
50073
  };
50120
- const isEffectCallbackFunction = (functionNode, scopes) => {
50074
+ const isEffectCallbackFunction = (functionNode) => {
50121
50075
  const parent = functionNode.parent;
50122
50076
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
50123
- return isReactHookCall(parent, EFFECT_HOOK_NAMES$1, scopes) && getEffectCallback(parent) === functionNode;
50077
+ return isHookCall$2(parent, EFFECT_HOOK_NAMES$1) && getEffectCallback(parent) === functionNode;
50124
50078
  };
50125
50079
  const doesEffectCallbackReturnName = (effectCallback, name) => {
50126
50080
  if (!isFunctionLike$1(effectCallback)) return false;
@@ -50139,24 +50093,24 @@ const isFunctionReturnedFromEffectCallback = (functionNode, effectCallback) => {
50139
50093
  const cleanupBindingName = getFunctionBindingName$1(functionNode);
50140
50094
  return cleanupBindingName !== null && doesEffectCallbackReturnName(effectCallback, cleanupBindingName);
50141
50095
  };
50142
- const isReturnedFromAnyEffectInScope = (functionNode, ownerScope, scopes) => {
50096
+ const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
50143
50097
  const cleanupBindingName = getFunctionBindingName$1(functionNode);
50144
50098
  if (cleanupBindingName === null) return false;
50145
50099
  let isReturnedFromEffect = false;
50146
50100
  walkAst(ownerScope, (child) => {
50147
50101
  if (isReturnedFromEffect) return false;
50148
- if (!isNodeOfType(child, "CallExpression") || !isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) return;
50102
+ if (!isNodeOfType(child, "CallExpression") || !isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
50149
50103
  const effectCallback = getEffectCallback(child);
50150
50104
  if (effectCallback && doesEffectCallbackReturnName(effectCallback, cleanupBindingName)) isReturnedFromEffect = true;
50151
50105
  });
50152
50106
  return isReturnedFromEffect;
50153
50107
  };
50154
- const isInsideEffectCleanupReturn = (node, ownerScope, scopes) => {
50108
+ const isInsideEffectCleanupReturn = (node, ownerScope) => {
50155
50109
  let functionNode = findEnclosingFunction$1(node);
50156
50110
  while (functionNode) {
50157
50111
  const outerFunction = findEnclosingFunction$1(functionNode);
50158
- if (outerFunction && isEffectCallbackFunction(outerFunction, scopes) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
50159
- if (isReturnedFromAnyEffectInScope(functionNode, ownerScope, scopes)) return true;
50112
+ if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
50113
+ if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
50160
50114
  functionNode = outerFunction;
50161
50115
  }
50162
50116
  return false;
@@ -50192,10 +50146,10 @@ const noStaleTimerRef = defineRule({
50192
50146
  if (isShadowedTimerGlobal(node)) return;
50193
50147
  const { clearCalleeName, refName } = clearCall;
50194
50148
  const refBinding = findVariableInitializer(node, refName);
50195
- if (!refBinding?.initializer || !isReactHookCall(refBinding.initializer, "useRef", context.scopes)) return;
50149
+ if (!refBinding?.initializer || !isHookCall$2(refBinding.initializer, "useRef")) return;
50196
50150
  const usageFacts = collectTimerRefUsageFacts(refBinding.scopeOwner, refName);
50197
50151
  if (!usageFacts.holdsScheduledTimerId || !usageFacts.hasPendingSignalRead) return;
50198
- if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner, context.scopes)) return;
50152
+ if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner)) return;
50199
50153
  if (hasRefCurrentReassignmentAfterClear(node, refName)) return;
50200
50154
  context.report({
50201
50155
  node,
@@ -55999,7 +55953,7 @@ const classifyCallableReadsInsideEffect = (callableIdentifier, effectCallback, c
55999
55953
  allReadsAreInSubHandlers = false;
56000
55954
  return;
56001
55955
  }
56002
- if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$1(subHandlerCall);
55956
+ if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$2(subHandlerCall);
56003
55957
  });
56004
55958
  return {
56005
55959
  hasAnyRead,
@@ -56022,7 +55976,7 @@ const preferUseEffectEvent = defineRule({
56022
55976
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
56023
55977
  const effectCall = statement.expression;
56024
55978
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
56025
- if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
55979
+ if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
56026
55980
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
56027
55981
  const depsNode = effectCall.arguments[1];
56028
55982
  if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
@@ -56054,11 +56008,11 @@ const preferUseEffectEvent = defineRule({
56054
56008
  });
56055
56009
  //#endregion
56056
56010
  //#region src/plugin/rules/state-and-effects/prefer-use-sync-external-store.ts
56057
- const findUseEffectsInComponent = (componentBody, scopes) => {
56011
+ const findUseEffectsInComponent = (componentBody) => {
56058
56012
  const effectCalls = [];
56059
56013
  if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
56060
56014
  for (const statement of componentBody.body ?? []) walkAst(statement, (child) => {
56061
- if (isNodeOfType(child, "CallExpression") && isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) effectCalls.push(child);
56015
+ if (isNodeOfType(child, "CallExpression") && isHookCall$2(child, EFFECT_HOOK_NAMES$1)) effectCalls.push(child);
56062
56016
  });
56063
56017
  return effectCalls;
56064
56018
  };
@@ -56261,7 +56215,7 @@ const preferUseSyncExternalStore = defineRule({
56261
56215
  };
56262
56216
  const checkComponent = (componentBody) => {
56263
56217
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
56264
- const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
56218
+ const useStateBindings = collectUseStateBindings(componentBody);
56265
56219
  if (useStateBindings.length === 0) return;
56266
56220
  const useStateInitializerByValueName = /* @__PURE__ */ new Map();
56267
56221
  for (const binding of useStateBindings) {
@@ -56274,7 +56228,7 @@ const preferUseSyncExternalStore = defineRule({
56274
56228
  }
56275
56229
  const setterNameToValueName = /* @__PURE__ */ new Map();
56276
56230
  for (const binding of useStateBindings) setterNameToValueName.set(binding.setterName, binding.valueName);
56277
- for (const effectCall of findUseEffectsInComponent(componentBody, context.scopes)) {
56231
+ for (const effectCall of findUseEffectsInComponent(componentBody)) {
56278
56232
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
56279
56233
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
56280
56234
  const depsNode = effectCall.arguments[1];
@@ -56316,7 +56270,7 @@ const preferUseSyncExternalStore = defineRule({
56316
56270
  })).filter((candidate) => candidate.storeName !== null);
56317
56271
  if (snapshotBindings.length === 0) return;
56318
56272
  const reportedDeclarators = /* @__PURE__ */ new Set();
56319
- for (const effectCall of findUseEffectsInComponent(componentBody, context.scopes)) {
56273
+ for (const effectCall of findUseEffectsInComponent(componentBody)) {
56320
56274
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
56321
56275
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
56322
56276
  const depsNode = effectCall.arguments[1];
@@ -56413,7 +56367,7 @@ const preferUseReducer = defineRule({
56413
56367
  create: (context) => {
56414
56368
  const reportCoUpdatedUseState = (body, componentName) => {
56415
56369
  if (!isNodeOfType(body, "BlockStatement")) return;
56416
- const bindings = collectUseStateBindings(body, context.scopes);
56370
+ const bindings = collectUseStateBindings(body);
56417
56371
  const setterNames = new Set(bindings.map((binding) => binding.setterName));
56418
56372
  if (setterNames.size < 5) return;
56419
56373
  const coUpdatedCount = findLargestCoUpdatedSetterGroup(body, setterNames, new Map(bindings.map((binding) => {
@@ -56629,7 +56583,7 @@ const QUERY_READ_METHOD_NAMES = new Set([
56629
56583
  ]);
56630
56584
  const isQueryCacheSourceCall = (initializer) => {
56631
56585
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
56632
- const hookName = getCalleeName$1(initializer);
56586
+ const hookName = getCalleeName$2(initializer);
56633
56587
  if (!hookName) return false;
56634
56588
  return hookName === "useQueryClient" || TRPC_UTILS_HOOK_PATTERN.test(hookName);
56635
56589
  };
@@ -56761,7 +56715,7 @@ const queryMutationMissingInvalidation = defineRule({
56761
56715
  },
56762
56716
  CallExpression(node) {
56763
56717
  if (!hasQueryReadUsage) {
56764
- const callName = getCalleeName$1(node);
56718
+ const callName = getCalleeName$2(node);
56765
56719
  if (callName && (QUERY_READ_HOOK_NAMES.has(callName) || QUERY_READ_METHOD_NAMES.has(callName) || TRPC_UTILS_HOOK_PATTERN.test(callName))) hasQueryReadUsage = true;
56766
56720
  }
56767
56721
  const calleeName = isNodeOfType(node.callee, "Identifier") ? node.callee.name : null;
@@ -58424,125 +58378,6 @@ const ASYNC_DATA_CALLEE_NAMES = new Set([
58424
58378
  const RESOURCE_LOAD_EVENT_ATTRIBUTE_PATTERN = /^on(?:Load|Error|Abort|Progress|CanPlay|Stalled|Suspend|Waiting|Ended)/;
58425
58379
  const JSX_EVENT_HANDLER_ATTRIBUTE_PATTERN = /^on[A-Z]/;
58426
58380
  const REDUX_DISPATCH_HOOK_PATTERN = /^use\w*Dispatch$/;
58427
- const FILE_READER_READ_METHOD_NAMES = new Set([
58428
- "readAsArrayBuffer",
58429
- "readAsBinaryString",
58430
- "readAsDataURL",
58431
- "readAsText"
58432
- ]);
58433
- const isGlobalFileReaderConstruction = (expression, context) => {
58434
- if (!expression) return false;
58435
- const unwrappedExpression = stripParenExpression(expression);
58436
- if (!isNodeOfType(unwrappedExpression, "NewExpression") || !isNodeOfType(unwrappedExpression.callee, "Identifier")) return false;
58437
- return unwrappedExpression.callee.name === "FileReader" && context.scopes.isGlobalReference(unwrappedExpression.callee);
58438
- };
58439
- const getFileReaderOriginStartBefore = (readerSymbol, readCall, context) => {
58440
- const readFunction = findEnclosingFunction$1(readCall);
58441
- let latestValue = null;
58442
- let latestStart = null;
58443
- if (readerSymbol.initializer && findEnclosingFunction$1(readerSymbol.declarationNode) === readFunction && readerSymbol.declarationNode.range[0] < readCall.range[0]) {
58444
- latestValue = readerSymbol.initializer;
58445
- latestStart = readerSymbol.declarationNode.range[0];
58446
- }
58447
- for (const reference of readerSymbol.references) {
58448
- if (reference.flag === "read" || reference.identifier.range[0] >= readCall.range[0] || latestStart !== null && reference.identifier.range[0] <= latestStart || findEnclosingFunction$1(reference.identifier) !== readFunction) continue;
58449
- const assignment = reference.identifier.parent;
58450
- if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== reference.identifier) continue;
58451
- latestValue = assignment.right;
58452
- latestStart = reference.identifier.range[0];
58453
- }
58454
- return isGlobalFileReaderConstruction(latestValue, context) ? latestStart : null;
58455
- };
58456
- const resolveLoadingCompletionFunction = (expression, context) => {
58457
- const directFunction = resolveExactLocalFunction(expression, context.scopes);
58458
- if (directFunction) return directFunction;
58459
- const unwrappedExpression = stripParenExpression(expression);
58460
- if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
58461
- const symbol = context.scopes.symbolFor(unwrappedExpression);
58462
- const initializer = symbol ? getDirectUnreassignedInitializer(symbol) : null;
58463
- if (!initializer || !isNodeOfType(initializer, "CallExpression") || !isReactApiCall(initializer, "useCallback", context.scopes)) return null;
58464
- const callback = initializer.arguments?.[0];
58465
- return callback && isFunctionLike$1(callback) ? callback : null;
58466
- };
58467
- const isSetterBooleanCall = (node, setterSymbol, value, context) => {
58468
- if (!isNodeOfType(node, "CallExpression")) return false;
58469
- const callee = stripParenExpression(node.callee);
58470
- const argument = node.arguments?.[0];
58471
- const unwrappedArgument = argument ? stripParenExpression(argument) : null;
58472
- return Boolean(isNodeOfType(callee, "Identifier") && context.scopes.symbolFor(callee) === setterSymbol && unwrappedArgument && isNodeOfType(unwrappedArgument, "Literal") && unwrappedArgument.value === value);
58473
- };
58474
- const functionClearsLoadingState = (functionNode, setterSymbol, context, visitedFunctions) => {
58475
- if (visitedFunctions.has(functionNode) || !isFunctionLike$1(functionNode)) return false;
58476
- visitedFunctions.add(functionNode);
58477
- let didClearLoadingState = false;
58478
- walkAst(functionNode.body, (child) => {
58479
- if (didClearLoadingState) return false;
58480
- if (child !== functionNode.body && isFunctionLike$1(child)) return false;
58481
- if (!isNodeOfType(child, "CallExpression")) return;
58482
- if (isSetterBooleanCall(child, setterSymbol, false, context)) {
58483
- didClearLoadingState = true;
58484
- return false;
58485
- }
58486
- const helperFunction = resolveLoadingCompletionFunction(child.callee, context);
58487
- if (helperFunction && functionClearsLoadingState(helperFunction, setterSymbol, context, visitedFunctions)) {
58488
- didClearLoadingState = true;
58489
- return false;
58490
- }
58491
- });
58492
- return didClearLoadingState;
58493
- };
58494
- const getLatestFileReaderCallbackBefore = (readCall, readerSymbol, propertyName, originStart, context) => {
58495
- const readFunction = findEnclosingFunction$1(readCall);
58496
- if (!readFunction || !isFunctionLike$1(readFunction)) return null;
58497
- let callback = null;
58498
- let callbackStart = originStart;
58499
- walkAst(readFunction.body, (child) => {
58500
- if (child !== readFunction.body && isFunctionLike$1(child)) return false;
58501
- if (!isNodeOfType(child, "AssignmentExpression") || child.operator !== "=" || child.range[0] >= readCall.range[0] || child.range[0] <= callbackStart || !isNodeOfType(child.left, "MemberExpression") || getStaticPropertyName(child.left) !== propertyName) return;
58502
- const receiver = stripParenExpression(child.left.object);
58503
- if (!isNodeOfType(receiver, "Identifier") || context.scopes.symbolFor(receiver) !== readerSymbol) return;
58504
- callback = child.right;
58505
- callbackStart = child.range[0];
58506
- });
58507
- return callback;
58508
- };
58509
- const setterStartsLoadingBefore = (readCall, setterSymbol, context) => {
58510
- const readFunction = findEnclosingFunction$1(readCall);
58511
- if (!readFunction || !isFunctionLike$1(readFunction)) return false;
58512
- let didStartLoading = false;
58513
- walkAst(readFunction.body, (child) => {
58514
- if (didStartLoading) return false;
58515
- if (child !== readFunction.body && isFunctionLike$1(child)) return false;
58516
- if (!isNodeOfType(child, "CallExpression") || child.range[0] >= readCall.range[0]) return;
58517
- if (isSetterBooleanCall(child, setterSymbol, true, context)) {
58518
- didStartLoading = true;
58519
- return false;
58520
- }
58521
- });
58522
- return didStartLoading;
58523
- };
58524
- const setterTracksFileReader = (functionBody, setterSymbol, context) => {
58525
- let didFindFileReaderLifecycle = false;
58526
- walkAst(functionBody, (child) => {
58527
- if (didFindFileReaderLifecycle) return false;
58528
- if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || !FILE_READER_READ_METHOD_NAMES.has(getStaticPropertyName(child.callee) ?? "")) return;
58529
- const receiver = stripParenExpression(child.callee.object);
58530
- if (!isNodeOfType(receiver, "Identifier")) return;
58531
- const readerSymbol = context.scopes.symbolFor(receiver);
58532
- if (!readerSymbol) return;
58533
- const originStart = getFileReaderOriginStartBefore(readerSymbol, child, context);
58534
- if (originStart === null || !setterStartsLoadingBefore(child, setterSymbol, context)) return;
58535
- const loadCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onload", originStart, context);
58536
- const errorCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onerror", originStart, context);
58537
- const loadFunction = loadCallback ? resolveLoadingCompletionFunction(loadCallback, context) : null;
58538
- const errorFunction = errorCallback ? resolveLoadingCompletionFunction(errorCallback, context) : null;
58539
- if (loadFunction && errorFunction && functionClearsLoadingState(loadFunction, setterSymbol, context, /* @__PURE__ */ new Set()) && functionClearsLoadingState(errorFunction, setterSymbol, context, /* @__PURE__ */ new Set())) {
58540
- didFindFileReaderLifecycle = true;
58541
- return false;
58542
- }
58543
- });
58544
- return didFindFileReaderLifecycle;
58545
- };
58546
58381
  const hasAsyncLoadingWork = (fnBody, setterName) => {
58547
58382
  let found = false;
58548
58383
  walkAst(fnBody, (child) => {
@@ -58716,8 +58551,6 @@ const renderingUsetransitionLoading = defineRule({
58716
58551
  const fnBody = enclosingFunctionBody(node);
58717
58552
  if (fnBody && hasAsyncLoadingWork(fnBody, setterName)) return;
58718
58553
  if (fnBody && setterName) {
58719
- const setterSymbol = isNodeOfType(secondBinding, "Identifier") ? context.scopes.symbolFor(secondBinding) : null;
58720
- if (setterSymbol && setterTracksFileReader(fnBody, setterSymbol, context)) return;
58721
58554
  if (setterEscapes(fnBody, setterName, node)) return;
58722
58555
  if (setterCalledAlongsideAsyncSignal(fnBody, setterName)) return;
58723
58556
  if (setterCalledInEventListenerHandler(fnBody, setterName)) return;
@@ -59049,7 +58882,7 @@ const rerenderDependencies = defineRule({
59049
58882
  severity: "error",
59050
58883
  recommendation: "Move it into a useMemo, useRef, or a constant outside the component so it stays the same between renders.",
59051
58884
  create: (context) => ({ CallExpression(node) {
59052
- if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes) || node.arguments.length < 2) return;
58885
+ if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
59053
58886
  const depsNode = node.arguments[1];
59054
58887
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
59055
58888
  for (const element of depsNode.elements ?? []) {
@@ -59304,7 +59137,7 @@ const rerenderLazyRefInit = defineRule({
59304
59137
  category: "Performance",
59305
59138
  recommendation: "Initialize the ref lazily so expensive values are not rebuilt and discarded on every render.",
59306
59139
  create: (context) => ({ CallExpression(node) {
59307
- if (!isReactHookCall(node, "useRef", context.scopes) || !node.arguments?.length) return;
59140
+ if (!isHookCall$2(node, "useRef") || !node.arguments?.length) return;
59308
59141
  const initializer = stripParenExpression(node.arguments[0]);
59309
59142
  const isPlainCall = isNodeOfType(initializer, "CallExpression");
59310
59143
  const isNewCall = isNodeOfType(initializer, "NewExpression");
@@ -59368,7 +59201,7 @@ const rerenderLazyStateInit = defineRule({
59368
59201
  category: "Performance",
59369
59202
  recommendation: "Wrap expensive initial state in an arrow function so the initializer does not rerun and get thrown away on every render.",
59370
59203
  create: (context) => ({ CallExpression(node) {
59371
- if (!isReactHookCall(node, "useState", context.scopes) || !node.arguments?.length) return;
59204
+ if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
59372
59205
  const initializer = findEagerInitializerCall(node.arguments[0]);
59373
59206
  if (!initializer) return;
59374
59207
  const isConstructor = isNodeOfType(initializer, "NewExpression");
@@ -60131,11 +59964,11 @@ const isInsideConditionTest = (identifier, stopAt) => {
60131
59964
  }
60132
59965
  return false;
60133
59966
  };
60134
- const collectEffectDependencyInfos = (componentBody, setterNames, scopes) => {
59967
+ const collectEffectDependencyInfos = (componentBody, setterNames) => {
60135
59968
  const effectInfos = [];
60136
59969
  walkAst(componentBody, (child) => {
60137
59970
  if (!isNodeOfType(child, "CallExpression")) return;
60138
- if (!isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) return;
59971
+ if (!isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
60139
59972
  const dependencyNames = /* @__PURE__ */ new Set();
60140
59973
  for (const argument of child.arguments ?? []) {
60141
59974
  if (!isNodeOfType(argument, "ArrayExpression")) continue;
@@ -60191,14 +60024,13 @@ const collectEffectDependencyInfos = (componentBody, setterNames, scopes) => {
60191
60024
  });
60192
60025
  return effectInfos;
60193
60026
  };
60194
- const collectCustomHookArgumentNames = (componentBody, scopes) => {
60027
+ const collectCustomHookArgumentNames = (componentBody) => {
60195
60028
  const argumentNames = /* @__PURE__ */ new Set();
60196
60029
  walkAst(componentBody, (child) => {
60197
60030
  if (!isNodeOfType(child, "CallExpression")) return;
60198
60031
  if (!isNodeOfType(child.callee, "Identifier")) return;
60199
60032
  const calleeName = child.callee.name;
60200
60033
  if (!isReactHookName(calleeName)) return;
60201
- if (isReactHookCall(child, BUILTIN_HOOK_NAMES, scopes)) return;
60202
60034
  if (BUILTIN_HOOK_NAMES.has(calleeName)) return;
60203
60035
  if (EFFECT_HOOK_NAMES$1.has(calleeName)) return;
60204
60036
  for (const argument of child.arguments ?? []) walkAst(argument, (argumentNode) => {
@@ -60239,21 +60071,21 @@ const rerenderStateOnlyInHandlers = defineRule({
60239
60071
  create: (context) => {
60240
60072
  const checkComponent = (componentBody) => {
60241
60073
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
60242
- const bindings = collectUseStateBindings(componentBody, context.scopes);
60074
+ const bindings = collectUseStateBindings(componentBody);
60243
60075
  if (bindings.length === 0) return;
60244
60076
  if (collectRenderReachableExpressions(componentBody).length === 0) return;
60245
- const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
60077
+ const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
60246
60078
  const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
60247
- const directRenderNames = collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames);
60079
+ const directRenderNames = collectRenderReachableNames(componentBody, eventHandlerReferenceNames);
60248
60080
  if (hasRenderPhaseNonHookCall(componentBody)) for (const voidMarkedName of collectTopLevelVoidMarkedNames(componentBody)) directRenderNames.add(voidMarkedName);
60249
60081
  const renderReachableNames = expandTransitiveDependencies(directRenderNames, dependencyGraph);
60250
60082
  const setterNames = new Set(bindings.map((binding) => binding.setterName));
60251
- const effectInfos = collectEffectDependencyInfos(componentBody, setterNames, context.scopes);
60083
+ const effectInfos = collectEffectDependencyInfos(componentBody, setterNames);
60252
60084
  const selfEchoValueNames = /* @__PURE__ */ new Set();
60253
60085
  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);
60254
60086
  const effectConsumedNames = /* @__PURE__ */ new Set();
60255
60087
  for (const effectInfo of effectInfos) for (const dependencyName of effectInfo.dependencyNames) if (!selfEchoValueNames.has(dependencyName)) effectConsumedNames.add(dependencyName);
60256
- for (const hookArgumentName of collectCustomHookArgumentNames(componentBody, context.scopes)) effectConsumedNames.add(hookArgumentName);
60088
+ for (const hookArgumentName of collectCustomHookArgumentNames(componentBody)) effectConsumedNames.add(hookArgumentName);
60257
60089
  for (const reachableName of expandTransitiveDependencies(effectConsumedNames, dependencyGraph)) renderReachableNames.add(reachableName);
60258
60090
  const calledSetterNames = /* @__PURE__ */ new Set();
60259
60091
  walkAst(componentBody, (child) => {
@@ -68154,7 +67986,7 @@ const declarationAwaitsGate = (declaration, context) => {
68154
67986
  if (!isNodeOfType(argument, "CallExpression")) continue;
68155
67987
  if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
68156
67988
  if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
68157
- const calleeName = getCalleeName$1(argument);
67989
+ const calleeName = getCalleeName$2(argument);
68158
67990
  if (!calleeName) continue;
68159
67991
  if (isAuthGuardName(calleeName)) return true;
68160
67992
  const [leadingToken] = tokenizeIdentifierWords(calleeName);
@@ -68794,7 +68626,7 @@ const walkServerFnChain = (outerNode) => {
68794
68626
  if (!isNodeOfType(outerNode.callee, "MemberExpression")) return result;
68795
68627
  let currentNode = stripParenExpression(outerNode.callee.object);
68796
68628
  while (isNodeOfType(currentNode, "CallExpression")) {
68797
- const calleeName = getCalleeName$1(currentNode);
68629
+ const calleeName = getCalleeName$2(currentNode);
68798
68630
  if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) {
68799
68631
  result.isServerFnChain = true;
68800
68632
  const optionsArgument = currentNode.arguments?.[0];