oxlint-plugin-react-doctor 0.7.9-dev.7e6cdf9 → 0.7.9-dev.8835214

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 +334 -313
  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",
@@ -14606,7 +14598,7 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
14606
14598
  walkAst(componentFunction.body, (child) => {
14607
14599
  if (didFindUnmountCleanup) return false;
14608
14600
  if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction) return;
14609
- if (!isReactHookCall(child, CLEANUP_EFFECT_HOOK_NAMES, context.scopes)) return;
14601
+ if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
14610
14602
  const dependencyList = child.arguments?.[1];
14611
14603
  if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
14612
14604
  const cleanupCallback = getEffectCallback(child);
@@ -15037,7 +15029,7 @@ const getReleaseVerbName = (node) => {
15037
15029
  const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) => {
15038
15030
  const releaseFunction = findEnclosingFunction$1(releaseReceiver);
15039
15031
  const usageFunction = findEnclosingFunction$1(usage.node);
15040
- if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction, context) || !resolveReactRefCurrentOriginSymbol(releaseReceiver, context.scopes)) return false;
15032
+ if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction) || !resolveReactRefCurrentOriginSymbol(releaseReceiver, context.scopes)) return false;
15041
15033
  const controllerKey = getListenerAbortControllerKey(usage, context);
15042
15034
  const refCurrentKey = resolveExpressionKey(releaseReceiver, context);
15043
15035
  if (controllerKey === null || refCurrentKey === null) return false;
@@ -15184,7 +15176,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15184
15176
  return true;
15185
15177
  };
15186
15178
  const matchesPairedReleaseVerb = (releaseVerbName, pairedVerbNames) => pairedVerbNames.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName);
15187
- const isReturnedEffectCleanupFunction = (functionNode, context) => {
15179
+ const isReturnedEffectCleanupFunction = (functionNode) => {
15188
15180
  let currentNode = functionNode;
15189
15181
  let parentNode = currentNode.parent;
15190
15182
  while (isNodeOfType(parentNode, "ChainExpression") || isNodeOfType(parentNode, "TSAsExpression") || isNodeOfType(parentNode, "TSNonNullExpression")) {
@@ -15193,10 +15185,10 @@ const isReturnedEffectCleanupFunction = (functionNode, context) => {
15193
15185
  }
15194
15186
  const effectCallback = isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode ? findEnclosingFunction$1(parentNode) : isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode ? parentNode : null;
15195
15187
  const effectCall = effectCallback?.parent;
15196
- return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isReactHookCall(effectCall, CLEANUP_EFFECT_HOOK_NAMES, context.scopes));
15188
+ return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
15197
15189
  };
15198
15190
  const isPotentiallyReachableFunction = (functionNode, context) => {
15199
- if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode, context)) return true;
15191
+ if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode)) return true;
15200
15192
  const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
15201
15193
  if (!bindingIdentifier) return false;
15202
15194
  const symbol = context.scopes.symbolFor(bindingIdentifier);
@@ -15901,7 +15893,7 @@ const isInlineRetainedHandlerFunction = (functionNode, context) => {
15901
15893
  if (!isFunctionLike$1(functionNode)) return false;
15902
15894
  const functionRoot = findTransparentExpressionRoot(functionNode);
15903
15895
  const callbackCall = functionRoot.parent;
15904
- if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot && isReactHookCall(callbackCall, "useCallback", context.scopes) && isDirectJsxEventHandlerValue(callbackCall)) return true;
15896
+ if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot && isHookCall$2(callbackCall, "useCallback") && isDirectJsxEventHandlerValue(callbackCall)) return true;
15905
15897
  const parentNode = functionNode.parent;
15906
15898
  if (isDirectJsxEventHandlerValue(functionNode)) return true;
15907
15899
  if (!isNodeOfType(parentNode, "Property") || parentNode.value !== functionNode || parentNode.computed) return false;
@@ -15937,12 +15929,12 @@ const effectNeedsCleanup = defineRule({
15937
15929
  };
15938
15930
  return {
15939
15931
  CallExpression(node) {
15940
- if (isReactHookCall(node, "useCallback", context.scopes)) {
15932
+ if (isHookCall$2(node, "useCallback")) {
15941
15933
  const retainedCallback = getEffectCallback(node);
15942
15934
  if (retainedCallback && !isInlineRetainedHandlerFunction(retainedCallback, context)) reportRetainedLeak(retainedCallback);
15943
15935
  return;
15944
15936
  }
15945
- if (!isReactHookCall(node, CLEANUP_EFFECT_HOOK_NAMES, context.scopes)) return;
15937
+ if (!isHookCall$2(node, CLEANUP_EFFECT_HOOK_NAMES)) return;
15946
15938
  const callback = getEffectCallback(node);
15947
15939
  if (!callback) return;
15948
15940
  const usages = removeSynchronouslyReleasedUsages(callback, findSubscribeLikeUsages(callback, context), context);
@@ -15950,7 +15942,7 @@ const effectNeedsCleanup = defineRule({
15950
15942
  const firstUsage = findFirstUsageWithoutCleanup(callback, usages, context);
15951
15943
  if (!firstUsage) return;
15952
15944
  const resourceNoun = RESOURCE_NOUN_BY_KIND[firstUsage.kind];
15953
- const hookName = getCalleeName$1(node) ?? "effect";
15945
+ const hookName = getCalleeName$2(node) ?? "effect";
15954
15946
  context.report({
15955
15947
  node,
15956
15948
  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.`
@@ -18823,7 +18815,7 @@ const flattenCalleeName = (callee) => {
18823
18815
  const PRAGMA = "React";
18824
18816
  const isReactFunctionCall = (node, expectedCall) => {
18825
18817
  if (!isNodeOfType(node, "CallExpression")) return false;
18826
- if (getCalleeName$1(node) !== expectedCall) return false;
18818
+ if (getCalleeName$2(node) !== expectedCall) return false;
18827
18819
  if (isNodeOfType(node.callee, "MemberExpression")) {
18828
18820
  const receiver = stripParenExpression(node.callee.object);
18829
18821
  return isNodeOfType(receiver, "Identifier") && receiver.name === PRAGMA;
@@ -19021,14 +19013,10 @@ const hookUseState = defineRule({
19021
19013
  create: (context) => {
19022
19014
  const { allowDestructuredState } = resolveSettings$40(context.settings);
19023
19015
  return { CallExpression(node) {
19024
- const callExpressionNode = node;
19025
- if (!isReactFunctionCall(callExpressionNode, "useState")) return;
19026
- const expressionRoot = findTransparentExpressionRoot(callExpressionNode);
19027
- const expressionParent = expressionRoot.parent;
19028
- if (isNodeOfType(expressionParent, "ReturnStatement")) return;
19029
- if (isNodeOfType(expressionParent, "ArrowFunctionExpression") && expressionParent.body === expressionRoot) return;
19016
+ if (!isReactFunctionCall(node, "useState")) return;
19030
19017
  const parent = node.parent;
19031
19018
  if (!parent) return;
19019
+ if (isNodeOfType(parent, "ReturnStatement")) return;
19032
19020
  if (!isNodeOfType(parent, "VariableDeclarator")) {
19033
19021
  context.report({
19034
19022
  node,
@@ -19136,8 +19124,8 @@ const hooksNoNanInDeps = defineRule({
19136
19124
  severity: "warn",
19137
19125
  recommendation: "Remove `NaN` (or `Number.NaN`) from the dependency array. If a value can be NaN at runtime, normalise it (`Number.isNaN(x) ? 0 : x`) before passing it.",
19138
19126
  create: (context) => ({ CallExpression(node) {
19139
- if (!isReactHookCall(node, HOOKS_WITH_DEP_ARRAY, context.scopes)) return;
19140
- const depsIndex = isReactHookCall(node, "useImperativeHandle", context.scopes) ? 2 : 1;
19127
+ if (!isHookCall$2(node, HOOKS_WITH_DEP_ARRAY)) return;
19128
+ const depsIndex = getCalleeName$2(node) === "useImperativeHandle" ? 2 : 1;
19141
19129
  const depsArgument = node.arguments[depsIndex];
19142
19130
  if (!depsArgument || !isNodeOfType(depsArgument, "ArrayExpression")) return;
19143
19131
  for (const element of depsArgument.elements) {
@@ -20308,7 +20296,7 @@ const isImportedSelectAtom = (callExpression) => {
20308
20296
  const isDeferredCallbackPosition$1 = (functionNode) => {
20309
20297
  const parent = functionNode.parent;
20310
20298
  if (isNodeOfType(parent, "CallExpression") && parent.arguments?.[0] === functionNode) {
20311
- const hookName = getCalleeName$1(parent);
20299
+ const hookName = getCalleeName$2(parent);
20312
20300
  if (hookName && MEMOIZING_HOOK_NAMES$1.has(hookName)) return true;
20313
20301
  if (hookName && EFFECT_HOOK_NAMES$1.has(hookName) && Boolean(parent.arguments?.[1])) return true;
20314
20302
  }
@@ -30379,7 +30367,7 @@ const DEFERRING_CALLEE_NAMES$1 = new Set([
30379
30367
  "on",
30380
30368
  "once"
30381
30369
  ]);
30382
- const getCalleeName = (callee) => {
30370
+ const getCalleeName$1 = (callee) => {
30383
30371
  if (!callee) return null;
30384
30372
  if (isNodeOfType(callee, "Identifier")) return callee.name;
30385
30373
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
@@ -30391,11 +30379,11 @@ const isDeferredCallbackPosition = (expression) => {
30391
30379
  const parent = parentOf(expression);
30392
30380
  if (!parent) return false;
30393
30381
  if (isNodeOfType(parent, "CallExpression") && argumentsInclude(parent.arguments, expression)) {
30394
- const name = getCalleeName(parent.callee);
30382
+ const name = getCalleeName$1(parent.callee);
30395
30383
  if (name && DEFERRING_CALLEE_NAMES$1.has(name)) return true;
30396
30384
  }
30397
30385
  if (isNodeOfType(parent, "NewExpression") && argumentsInclude(parent.arguments, expression)) {
30398
- const name = getCalleeName(parent.callee);
30386
+ const name = getCalleeName$1(parent.callee);
30399
30387
  if (name && (name.endsWith("Observer") || name === "Promise")) return true;
30400
30388
  }
30401
30389
  if (isNodeOfType(parent, "AssignmentExpression") && parent.right === expression && isNodeOfType(parent.left, "MemberExpression") && isNodeOfType(parent.left.property, "Identifier") && parent.left.property.name.startsWith("on")) return true;
@@ -30815,6 +30803,14 @@ const isHookCallee$1 = (analysis, node, hookName) => {
30815
30803
  if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
30816
30804
  return false;
30817
30805
  };
30806
+ const isUseEffect = (node) => {
30807
+ if (!node || !isNodeOfType(node, "CallExpression")) return false;
30808
+ const callee = node.callee;
30809
+ if (isNodeOfType(callee, "Identifier") && callee.name === "useEffect") return true;
30810
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
30811
+ const receiver = stripParenExpression(callee.object);
30812
+ return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useEffect";
30813
+ };
30818
30814
  const getEffectFn = (analysis, node) => {
30819
30815
  if (!isNodeOfType(node, "CallExpression")) return null;
30820
30816
  const fn = node.arguments?.[0];
@@ -32346,7 +32342,12 @@ const noAdjustStateOnPropChange = defineRule({
32346
32342
  tags: ["test-noise"],
32347
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",
32348
32344
  create: (context) => ({ CallExpression(node) {
32349
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
32345
+ if (!isReactApiCall(node, "useEffect", context.scopes, {
32346
+ allowGlobalReactNamespace: true,
32347
+ allowUnboundBareCalls: true,
32348
+ resolveConditionalAliases: true,
32349
+ resolveNamedAliases: true
32350
+ })) return;
32350
32351
  const analysis = getProgramAnalysis(node);
32351
32352
  if (!analysis) return;
32352
32353
  const dependencyReferences = getEffectDepsRefs(analysis, node);
@@ -34369,7 +34370,7 @@ const declarationBodyContainsHookCall = (symbol) => {
34369
34370
  walkAst(componentFunction, (descendant) => {
34370
34371
  if (didFindHookCall) return false;
34371
34372
  if (!isNodeOfType(descendant, "CallExpression")) return;
34372
- const calleeName = getCalleeName$1(descendant);
34373
+ const calleeName = getCalleeName$2(descendant);
34373
34374
  if (calleeName && isReactHookName(calleeName)) {
34374
34375
  didFindHookCall = true;
34375
34376
  return false;
@@ -34394,7 +34395,7 @@ const isReturnedFromUseCallbackAdapter = (callNode) => {
34394
34395
  if (isNodeOfType(parent, "ArrowFunctionExpression")) {
34395
34396
  if (parent.body !== current) return false;
34396
34397
  const grandparent = parent.parent;
34397
- return isNodeOfType(grandparent, "CallExpression") && getCalleeName$1(grandparent) === "useCallback" && grandparent.arguments.some((argumentNode) => argumentNode === parent);
34398
+ return isNodeOfType(grandparent, "CallExpression") && getCalleeName$2(grandparent) === "useCallback" && grandparent.arguments.some((argumentNode) => argumentNode === parent);
34398
34399
  }
34399
34400
  if (!isNodeOfType(parent, "ConditionalExpression") && !isNodeOfType(parent, "LogicalExpression")) return false;
34400
34401
  current = parent;
@@ -36926,7 +36927,12 @@ const noDerivedState = defineRule({
36926
36927
  for (const fact of collectRenderStateWriteFacts(analysis, componentBody, context.filename)) reportStateWrite(fact.callExpression, fact.stateDeclarator);
36927
36928
  } }).visitors,
36928
36929
  CallExpression(node) {
36929
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
36930
+ if (!isReactApiCall(node, "useEffect", context.scopes, {
36931
+ allowGlobalReactNamespace: true,
36932
+ allowUnboundBareCalls: true,
36933
+ resolveConditionalAliases: true,
36934
+ resolveNamedAliases: true
36935
+ })) return;
36930
36936
  const analysis = getProgramAnalysis(node);
36931
36937
  if (!analysis) return;
36932
36938
  for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
@@ -36946,7 +36952,12 @@ const noDerivedStateEffect = defineRule({
36946
36952
  tags: ["test-noise"],
36947
36953
  recommendation: "Work out derived values while rendering: `const x = fn(dep)`. To reset a component's state when a prop changes, give it a key prop: `<Component key={prop} />`. See https://react.dev/learn/you-might-not-need-an-effect",
36948
36954
  create: (context) => ({ CallExpression(node) {
36949
- if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes)) return;
36955
+ if (!isReactApiCall(node, EFFECT_HOOK_NAMES$1, context.scopes, {
36956
+ allowGlobalReactNamespace: true,
36957
+ allowUnboundBareCalls: true,
36958
+ resolveConditionalAliases: true,
36959
+ resolveNamedAliases: true
36960
+ })) return;
36950
36961
  const analysis = getProgramAnalysis(node);
36951
36962
  if (!analysis) return;
36952
36963
  if (!collectEffectStateWriteFacts(analysis, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
@@ -37069,7 +37080,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
37069
37080
  if (isFunctionLike$1(cursor)) {
37070
37081
  const parent = cursor.parent ?? null;
37071
37082
  if (parent && isNodeOfType(parent, "CallExpression")) {
37072
- const calleeName = getCalleeName$1(parent);
37083
+ const calleeName = getCalleeName$2(parent);
37073
37084
  if (calleeName !== null && EFFECT_HOOK_NAME_PATTERN.test(calleeName) && (parent.arguments ?? []).some((argument) => argument === cursor)) return cursor;
37074
37085
  }
37075
37086
  }
@@ -37140,7 +37151,7 @@ const isNonHandlerHookCallback = (functionNode) => {
37140
37151
  const parent = functionNode.parent ?? null;
37141
37152
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
37142
37153
  if (!(parent.arguments ?? []).some((argument) => argument === functionNode)) return false;
37143
- const calleeName = getCalleeName$1(parent);
37154
+ const calleeName = getCalleeName$2(parent);
37144
37155
  return calleeName !== null && isReactHookName(calleeName) && calleeName !== "useCallback";
37145
37156
  };
37146
37157
  const isHandlerShapedReseed = (setterCall, componentFunction) => {
@@ -37222,7 +37233,7 @@ const noDerivedUseState = defineRule({
37222
37233
  return {
37223
37234
  ...propStackTracker.visitors,
37224
37235
  CallExpression(node) {
37225
- if (!isReactHookCall(node, "useState", context.scopes) || !node.arguments?.length) return;
37236
+ if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
37226
37237
  const seed = unwrapInitializerSeed(node.arguments[0]);
37227
37238
  const reportStalePropCopy = (propName) => {
37228
37239
  if (isIntentionalSnapshotState(node)) return;
@@ -37911,7 +37922,12 @@ const collectUseStateBindings = (componentBody, scopes) => {
37911
37922
  const setterElement = elements[1];
37912
37923
  if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
37913
37924
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
37914
- if (!isReactHookCall(declarator.init, "useState", scopes)) continue;
37925
+ if (!(scopes ? isReactApiCall(declarator.init, "useState", scopes, {
37926
+ allowGlobalReactNamespace: true,
37927
+ allowUnboundBareCalls: true,
37928
+ resolveConditionalAliases: true,
37929
+ resolveNamedAliases: true
37930
+ }) : isHookCall$2(declarator.init, "useState"))) continue;
37915
37931
  bindings.push({
37916
37932
  valueName: valueElement.name,
37917
37933
  setterName: setterElement.name,
@@ -38078,7 +38094,7 @@ const noDirectStateMutation = defineRule({
38078
38094
  create: (context) => {
38079
38095
  const checkComponent = (componentBody) => {
38080
38096
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
38081
- const bindings = collectUseStateBindings(componentBody, context.scopes);
38097
+ const bindings = collectUseStateBindings(componentBody);
38082
38098
  if (bindings.length === 0) return;
38083
38099
  const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
38084
38100
  const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
@@ -38645,7 +38661,12 @@ const findTopLevelEffectCalls = (componentBody, scopes) => {
38645
38661
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
38646
38662
  const expression = unwrapDiscardedExpression(statement);
38647
38663
  if (!isNodeOfType(expression, "CallExpression")) continue;
38648
- if (!isReactHookCall(expression, EFFECT_HOOK_NAMES$1, scopes)) continue;
38664
+ if (!isReactApiCall(expression, EFFECT_HOOK_NAMES$1, scopes, {
38665
+ allowGlobalReactNamespace: true,
38666
+ allowUnboundBareCalls: true,
38667
+ resolveConditionalAliases: true,
38668
+ resolveNamedAliases: true
38669
+ })) continue;
38649
38670
  effectCalls.push(expression);
38650
38671
  }
38651
38672
  return effectCalls;
@@ -38889,7 +38910,7 @@ const collectStorageHookSetterNames = (componentBody) => {
38889
38910
  for (const declarator of statement.declarations ?? []) {
38890
38911
  if (!isNodeOfType(declarator.id, "ArrayPattern")) continue;
38891
38912
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
38892
- const calleeName = getCalleeName$1(declarator.init);
38913
+ const calleeName = getCalleeName$2(declarator.init);
38893
38914
  if (!calleeName || !STORAGE_HOOK_PATTERN.test(calleeName)) continue;
38894
38915
  for (const element of declarator.id.elements ?? []) if (isNodeOfType(element, "Identifier") && isSetterIdentifier(element.name)) setterNames.add(element.name);
38895
38916
  }
@@ -39377,7 +39398,7 @@ const noEffectEventHandler = defineRule({
39377
39398
  return {
39378
39399
  ...propStackTracker.visitors,
39379
39400
  CallExpression(node) {
39380
- if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes) || (node.arguments?.length ?? 0) < 2) return;
39401
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
39381
39402
  const callback = getEffectCallback(node);
39382
39403
  if (!callback) return;
39383
39404
  const analysis = getProgramAnalysis(node);
@@ -39497,14 +39518,14 @@ const noEffectEventInDeps = defineRule({
39497
39518
  if (!isNodeOfType(declaratorNode.id, "Identifier")) return;
39498
39519
  const initializer = declaratorNode.init;
39499
39520
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return;
39500
- if (!isReactHookCall(initializer, "useEffectEvent", context.scopes)) return;
39521
+ if (!isHookCall$2(initializer, "useEffectEvent")) return;
39501
39522
  if (isNonReactEffectEventCallee(initializer.callee, declaratorNode, context.scopes)) return;
39502
39523
  componentBindings.addBindingToCurrentFrame(declaratorNode.id.name);
39503
39524
  } });
39504
39525
  return {
39505
39526
  ...componentBindings.visitors,
39506
39527
  CallExpression(node) {
39507
- if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes) || node.arguments.length < 2) return;
39528
+ if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
39508
39529
  if (!componentBindings.isInsideComponent()) return;
39509
39530
  const depsNode = node.arguments[1];
39510
39531
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -39561,7 +39582,7 @@ const noEffectWithFreshDeps = defineRule({
39561
39582
  node: finding.reportNode,
39562
39583
  message: `A dependency inside this custom Hook changes every render because \`${finding.bindingName}\` is a new ${finding.kind} built fresh each time.`
39563
39584
  });
39564
- if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes)) return;
39585
+ if (!isHookCall$2(node, HOOKS_WITH_DEPS)) return;
39565
39586
  const args = node.arguments ?? [];
39566
39587
  if (args.length < 2) return;
39567
39588
  const depsNode = args[1];
@@ -39822,7 +39843,7 @@ const noEventHandler = defineRule({
39822
39843
  severity: "warn",
39823
39844
  recommendation: "Run the side effect in the event handler that triggers it, instead of watching its state from a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#sharing-logic-between-event-handlers",
39824
39845
  create: (context) => ({ CallExpression(node) {
39825
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
39846
+ if (!isUseEffect(node)) return;
39826
39847
  const analysis = getProgramAnalysis(node);
39827
39848
  if (!analysis || hasCleanup(analysis, node)) return;
39828
39849
  const frames = collectBoundedEffectExecutionFrames(analysis, node);
@@ -40284,25 +40305,25 @@ const addDeclarationBindings = (statement, scope) => {
40284
40305
  }
40285
40306
  if (isNodeOfType(statement, "FunctionDeclaration") && statement.id) addPatternBindings(statement.id, scope);
40286
40307
  };
40287
- const collectRenderReachableNamesFromStatements = (statements, names, scope, scopes, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
40308
+ const collectRenderReachableNamesFromStatements = (statements, names, scope, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
40288
40309
  let hasReturn = false;
40289
- for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope, scopes, eventHandlerReferenceNames)) hasReturn = true;
40310
+ for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope, eventHandlerReferenceNames)) hasReturn = true;
40290
40311
  else addDeclarationBindings(statement, scope);
40291
40312
  return hasReturn;
40292
40313
  };
40293
- const collectRenderReachableNamesFromStatement = (statement, names, scope, scopes, eventHandlerReferenceNames) => {
40314
+ const collectRenderReachableNamesFromStatement = (statement, names, scope, eventHandlerReferenceNames) => {
40294
40315
  if (isNodeOfType(statement, "ReturnStatement")) {
40295
40316
  if (statement.argument) addNames(names, collectScopedReferenceNames(statement.argument, scope, eventHandlerReferenceNames));
40296
40317
  return true;
40297
40318
  }
40298
- if (isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "CallExpression") && (isNodeOfType(statement.expression.callee, "Identifier") && isReactHookName(statement.expression.callee.name) || isReactHookCall(statement.expression, BUILTIN_HOOK_NAMES, scopes)) && !isReactHookCall(statement.expression, EFFECT_HOOK_NAMES$1, scopes)) {
40319
+ if (isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "CallExpression") && isNodeOfType(statement.expression.callee, "Identifier") && isReactHookName(statement.expression.callee.name) && !EFFECT_HOOK_NAMES$1.has(statement.expression.callee.name)) {
40299
40320
  for (const argument of statement.expression.arguments ?? []) addNames(names, collectScopedReferenceNames(argument, scope, eventHandlerReferenceNames));
40300
40321
  return false;
40301
40322
  }
40302
- if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope), scopes, eventHandlerReferenceNames);
40323
+ if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope), eventHandlerReferenceNames);
40303
40324
  if (isNodeOfType(statement, "IfStatement")) {
40304
- const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope, scopes, eventHandlerReferenceNames);
40305
- const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope, scopes, eventHandlerReferenceNames) : false;
40325
+ const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope, eventHandlerReferenceNames);
40326
+ const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope, eventHandlerReferenceNames) : false;
40306
40327
  if (consequentHasReturn || alternateHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
40307
40328
  return consequentHasReturn || alternateHasReturn;
40308
40329
  }
@@ -40310,7 +40331,7 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
40310
40331
  let hasReturn = false;
40311
40332
  for (const switchCase of statement.cases ?? []) {
40312
40333
  const caseScope = createBlockBindingScope(scope);
40313
- if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope, scopes, eventHandlerReferenceNames)) continue;
40334
+ if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope, eventHandlerReferenceNames)) continue;
40314
40335
  hasReturn = true;
40315
40336
  if (switchCase.test) addNames(names, collectScopedReferenceNames(switchCase.test, scope, eventHandlerReferenceNames));
40316
40337
  }
@@ -40318,25 +40339,25 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
40318
40339
  return hasReturn;
40319
40340
  }
40320
40341
  if (isNodeOfType(statement, "TryStatement")) {
40321
- const blockHasReturn = collectRenderReachableNamesFromStatement(statement.block, names, scope, scopes, eventHandlerReferenceNames);
40322
- const handlerHasReturn = statement.handler ? collectRenderReachableNamesFromStatement(statement.handler, names, scope, scopes, eventHandlerReferenceNames) : false;
40323
- const finalizerHasReturn = statement.finalizer ? collectRenderReachableNamesFromStatement(statement.finalizer, names, scope, scopes, eventHandlerReferenceNames) : false;
40342
+ const blockHasReturn = collectRenderReachableNamesFromStatement(statement.block, names, scope, eventHandlerReferenceNames);
40343
+ const handlerHasReturn = statement.handler ? collectRenderReachableNamesFromStatement(statement.handler, names, scope, eventHandlerReferenceNames) : false;
40344
+ const finalizerHasReturn = statement.finalizer ? collectRenderReachableNamesFromStatement(statement.finalizer, names, scope, eventHandlerReferenceNames) : false;
40324
40345
  return blockHasReturn || handlerHasReturn || finalizerHasReturn;
40325
40346
  }
40326
40347
  if (isNodeOfType(statement, "CatchClause")) {
40327
40348
  const catchScope = createBlockBindingScope(scope);
40328
40349
  addPatternBindings(statement.param, catchScope);
40329
- return collectRenderReachableNamesFromStatement(statement.body, names, catchScope, scopes, eventHandlerReferenceNames);
40350
+ return collectRenderReachableNamesFromStatement(statement.body, names, catchScope, eventHandlerReferenceNames);
40330
40351
  }
40331
40352
  if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
40332
- const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, scopes, eventHandlerReferenceNames);
40353
+ const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
40333
40354
  if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
40334
40355
  return bodyHasReturn;
40335
40356
  }
40336
40357
  if (isNodeOfType(statement, "ForStatement")) {
40337
40358
  const loopScope = createBlockBindingScope(scope);
40338
40359
  if (statement.init) addDeclarationBindings(statement.init, loopScope);
40339
- if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, scopes, eventHandlerReferenceNames)) return false;
40360
+ if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
40340
40361
  if (statement.init) addNames(names, collectScopedReferenceNames(statement.init, loopScope, eventHandlerReferenceNames));
40341
40362
  if (statement.test) addNames(names, collectScopedReferenceNames(statement.test, loopScope, eventHandlerReferenceNames));
40342
40363
  if (statement.update) addNames(names, collectScopedReferenceNames(statement.update, loopScope, eventHandlerReferenceNames));
@@ -40346,22 +40367,22 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
40346
40367
  const rightNames = collectScopedReferenceNames(statement.right, scope, eventHandlerReferenceNames);
40347
40368
  const loopScope = createBlockBindingScope(scope);
40348
40369
  if (isNodeOfType(statement.left, "VariableDeclaration")) addDeclarationBindings(statement.left, loopScope);
40349
- if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, scopes, eventHandlerReferenceNames)) return false;
40370
+ if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
40350
40371
  addNames(names, rightNames);
40351
40372
  return true;
40352
40373
  }
40353
- if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope, scopes, eventHandlerReferenceNames);
40374
+ if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
40354
40375
  if (isNodeOfType(statement, "WithStatement")) {
40355
- const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, scopes, eventHandlerReferenceNames);
40376
+ const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
40356
40377
  if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.object, scope, eventHandlerReferenceNames));
40357
40378
  return bodyHasReturn;
40358
40379
  }
40359
40380
  return false;
40360
40381
  };
40361
- const collectRenderReachableNames = (componentBody, scopes, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
40382
+ const collectRenderReachableNames = (componentBody, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
40362
40383
  const names = /* @__PURE__ */ new Set();
40363
40384
  if (!isNodeOfType(componentBody, "BlockStatement")) return names;
40364
- collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(), scopes, eventHandlerReferenceNames);
40385
+ collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(), eventHandlerReferenceNames);
40365
40386
  return names;
40366
40387
  };
40367
40388
  //#endregion
@@ -40384,36 +40405,42 @@ const expandTransitiveDependencies = (seedNames, dependencyGraph) => {
40384
40405
  };
40385
40406
  //#endregion
40386
40407
  //#region src/plugin/rules/state-and-effects/utils/collect-function-like-local-names.ts
40387
- const isFunctionLikeReference = (node, functionLikeLocalNames, scope, scopes) => {
40388
- if (isInlineFunctionExpression(node) || isReactHookCall(node, "useCallback", scopes)) return true;
40408
+ const isUseCallbackCall = (node) => isNodeOfType(node, "CallExpression") && getCalleeName(node.callee) === "useCallback";
40409
+ const getCalleeName = (node) => {
40410
+ if (isNodeOfType(node, "Identifier")) return node.name;
40411
+ if (isNodeOfType(node, "MemberExpression")) return getStaticMemberPropertyName(node);
40412
+ return null;
40413
+ };
40414
+ const isFunctionLikeReference = (node, functionLikeLocalNames, scope) => {
40415
+ if (isInlineFunctionExpression(node) || isUseCallbackCall(node)) return true;
40389
40416
  if (isNodeOfType(node, "Identifier")) return functionLikeLocalNames.has(resolveBindingName(scope, node.name));
40390
40417
  const memberReferenceName = getStaticMemberReferenceName(node, (name) => resolveBindingName(scope, name));
40391
40418
  return Boolean(memberReferenceName && functionLikeLocalNames.has(memberReferenceName));
40392
40419
  };
40393
- const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope, scopes) => {
40420
+ const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope) => {
40394
40421
  if (!isNodeOfType(node, "ObjectExpression")) return;
40395
40422
  for (const property of node.properties ?? []) {
40396
40423
  if (!isNodeOfType(property, "Property")) continue;
40397
40424
  const propertyName = getStaticPropertyKeyName(property, { stringifyNonStringLiterals: true });
40398
40425
  if (!propertyName) continue;
40399
- if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope, scopes)) continue;
40426
+ if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope)) continue;
40400
40427
  functionLikeLocalNames.add(`${objectBindingName}.${propertyName}`);
40401
40428
  }
40402
40429
  };
40403
- const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope, scopes) => {
40430
+ const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope) => {
40404
40431
  if (!isNodeOfType(statement, "VariableDeclaration")) return;
40405
40432
  const declarationScope = getVariableDeclarationScope(statement, scope);
40406
40433
  for (const declarator of statement.declarations ?? []) {
40407
40434
  const declaredBindingNames = addPatternBindings(declarator.id, declarationScope);
40408
40435
  if (!declarator.init) continue;
40409
- const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope, scopes);
40436
+ const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope);
40410
40437
  for (const declaredBindingName of declaredBindingNames) {
40411
40438
  if (isFunctionReference) functionLikeLocalNames.add(declaredBindingName);
40412
- addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope, scopes);
40439
+ addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope);
40413
40440
  }
40414
40441
  }
40415
40442
  };
40416
- const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope, scopes) => {
40443
+ const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope) => {
40417
40444
  if (isNodeOfType(statement, "FunctionDeclaration")) {
40418
40445
  if (statement.id) {
40419
40446
  const declaredBindingNames = addPatternBindings(statement.id, scope);
@@ -40422,61 +40449,61 @@ const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope,
40422
40449
  return;
40423
40450
  }
40424
40451
  if (isNodeOfType(statement, "VariableDeclaration")) {
40425
- addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope, scopes);
40452
+ addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope);
40426
40453
  return;
40427
40454
  }
40428
40455
  if (isNodeOfType(statement, "BlockStatement")) {
40429
- collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope), scopes);
40456
+ collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope));
40430
40457
  return;
40431
40458
  }
40432
40459
  if (isNodeOfType(statement, "IfStatement")) {
40433
- collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope, scopes);
40434
- if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope, scopes);
40460
+ collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope);
40461
+ if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope);
40435
40462
  return;
40436
40463
  }
40437
40464
  if (isNodeOfType(statement, "SwitchStatement")) {
40438
- for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope), scopes);
40465
+ for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope));
40439
40466
  return;
40440
40467
  }
40441
40468
  if (isNodeOfType(statement, "TryStatement")) {
40442
- collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope, scopes);
40469
+ collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope);
40443
40470
  if (statement.handler) {
40444
40471
  const catchScope = createBlockBindingScope(scope);
40445
40472
  addPatternBindings(statement.handler.param, catchScope);
40446
- collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope, scopes);
40473
+ collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope);
40447
40474
  }
40448
- if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope, scopes);
40475
+ if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope);
40449
40476
  return;
40450
40477
  }
40451
40478
  if (isNodeOfType(statement, "ForStatement")) {
40452
40479
  const loopScope = createBlockBindingScope(scope);
40453
- if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope, scopes);
40454
- collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope, scopes);
40480
+ if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope);
40481
+ collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
40455
40482
  return;
40456
40483
  }
40457
40484
  if (isNodeOfType(statement, "ForInStatement") || isNodeOfType(statement, "ForOfStatement")) {
40458
40485
  const loopScope = createBlockBindingScope(scope);
40459
- if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope, scopes);
40486
+ if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope);
40460
40487
  else addPatternBindings(statement.left, loopScope);
40461
- collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope, scopes);
40488
+ collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
40462
40489
  return;
40463
40490
  }
40464
40491
  if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
40465
- collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope, scopes);
40492
+ collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
40466
40493
  return;
40467
40494
  }
40468
- if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope, scopes);
40495
+ if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
40469
40496
  };
40470
- const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope, scopes) => {
40471
- for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope, scopes);
40497
+ const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope) => {
40498
+ for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope);
40472
40499
  };
40473
- const collectFunctionLikeLocalNames = (componentBody, scopes) => {
40500
+ const collectFunctionLikeLocalNames = (componentBody) => {
40474
40501
  const functionLikeLocalNames = /* @__PURE__ */ new Set();
40475
40502
  if (!isNodeOfType(componentBody, "BlockStatement")) return functionLikeLocalNames;
40476
40503
  let previousSize = -1;
40477
40504
  while (previousSize !== functionLikeLocalNames.size) {
40478
40505
  previousSize = functionLikeLocalNames.size;
40479
- collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope(), scopes);
40506
+ collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope());
40480
40507
  }
40481
40508
  return functionLikeLocalNames;
40482
40509
  };
@@ -40516,17 +40543,17 @@ const noEventTriggerState = defineRule({
40516
40543
  create: (context) => {
40517
40544
  const checkComponent = (componentBody) => {
40518
40545
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
40519
- const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
40546
+ const useStateBindings = collectUseStateBindings(componentBody);
40520
40547
  if (useStateBindings.length === 0) return;
40521
40548
  const analysis = getProgramAnalysis(componentBody);
40522
40549
  if (!analysis) return;
40523
40550
  const localStateNames = new Set(useStateBindings.map((binding) => binding.valueName));
40524
- const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
40551
+ const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
40525
40552
  const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
40526
- const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames), dependencyGraph);
40553
+ const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody, eventHandlerReferenceNames), dependencyGraph);
40527
40554
  walkAst(componentBody, (effectCall) => {
40528
40555
  if (!isNodeOfType(effectCall, "CallExpression")) return;
40529
- if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) return;
40556
+ if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) return;
40530
40557
  if ((effectCall.arguments?.length ?? 0) < 2) return;
40531
40558
  const depsNode = effectCall.arguments[1];
40532
40559
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -40591,17 +40618,10 @@ const isShadowedByLocalBinding = (identifier) => {
40591
40618
  };
40592
40619
  const isRealFetchCall = (node) => {
40593
40620
  if (!isNodeOfType(node, "CallExpression")) return false;
40594
- const callee = stripParenExpression(node.callee);
40595
- if (isNodeOfType(callee, "Identifier") && FETCH_CALLEE_NAMES.has(callee.name)) return !isShadowedByLocalBinding(callee);
40596
- if (!isNodeOfType(callee, "MemberExpression")) return false;
40597
- const receiver = stripParenExpression(callee.object);
40598
- return isNodeOfType(receiver, "Identifier") && FETCH_MEMBER_OBJECTS.has(receiver.name) && !isShadowedByLocalBinding(receiver);
40599
- };
40600
- const isXmlHttpRequestConstruction = (node) => {
40601
- if (!isNodeOfType(node, "NewExpression")) return false;
40602
- const callee = stripParenExpression(node.callee);
40603
- return isNodeOfType(callee, "Identifier") && callee.name === "XMLHttpRequest";
40621
+ if (isNodeOfType(node.callee, "Identifier") && FETCH_CALLEE_NAMES.has(node.callee.name)) return !isShadowedByLocalBinding(node.callee);
40622
+ return isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && FETCH_MEMBER_OBJECTS.has(node.callee.object.name) && !isShadowedByLocalBinding(node.callee.object);
40604
40623
  };
40624
+ const isXmlHttpRequestConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "XMLHttpRequest";
40605
40625
  const isNetworkRequest = (node) => isRealFetchCall(node) || isXmlHttpRequestConstruction(node);
40606
40626
  const resolveLocalFunction = (expression, context) => {
40607
40627
  if (!expression) return null;
@@ -42362,7 +42382,7 @@ const noInitializeState = defineRule({
42362
42382
  tags: ["test-noise"],
42363
42383
  recommendation: "Pass the initial value directly to useState() instead of setting it from a mount-only useEffect. For SSR hydration, prefer useSyncExternalStore().",
42364
42384
  create: (context) => ({ CallExpression(node) {
42365
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
42385
+ if (!isUseEffect(node)) return;
42366
42386
  const dependencies = node.arguments?.[1];
42367
42387
  if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
42368
42388
  const analysis = getProgramAnalysis(node);
@@ -43974,7 +43994,7 @@ const noMirrorPropEffect = defineRule({
43974
43994
  const setterElement = elements[1];
43975
43995
  if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
43976
43996
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
43977
- if (!isReactHookCall(declarator.init, "useState", context.scopes)) continue;
43997
+ if (!isHookCall$2(declarator.init, "useState")) continue;
43978
43998
  const initializer = declarator.init.arguments?.[0];
43979
43999
  if (!initializer) continue;
43980
44000
  const propRootName = getPropRootName(initializer, propNames);
@@ -43992,7 +44012,7 @@ const noMirrorPropEffect = defineRule({
43992
44012
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
43993
44013
  const effectCall = unwrapDiscardedExpression(statement);
43994
44014
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
43995
- if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
44015
+ if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
43996
44016
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
43997
44017
  const depsNode = effectCall.arguments[1];
43998
44018
  if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
@@ -44401,7 +44421,7 @@ const noMultiComp = defineRule({
44401
44421
  });
44402
44422
  //#endregion
44403
44423
  //#region src/plugin/rules/state-and-effects/no-mutable-in-deps.ts
44404
- const collectUseRefBindingNames = (componentBody, scopes) => {
44424
+ const collectUseRefBindingNames = (componentBody) => {
44405
44425
  const useRefBindings = /* @__PURE__ */ new Set();
44406
44426
  if (!isNodeOfType(componentBody, "BlockStatement")) return useRefBindings;
44407
44427
  for (const statement of componentBody.body ?? []) {
@@ -44409,7 +44429,7 @@ const collectUseRefBindingNames = (componentBody, scopes) => {
44409
44429
  for (const declarator of statement.declarations ?? []) {
44410
44430
  if (!isNodeOfType(declarator.id, "Identifier")) continue;
44411
44431
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
44412
- if (!isReactHookCall(declarator.init, "useRef", scopes)) continue;
44432
+ if (!isHookCall$2(declarator.init, "useRef")) continue;
44413
44433
  useRefBindings.add(declarator.id.name);
44414
44434
  }
44415
44435
  }
@@ -44444,12 +44464,12 @@ const noMutableInDeps = defineRule({
44444
44464
  create: (context) => {
44445
44465
  const checkComponent = (componentBody, componentParams = []) => {
44446
44466
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
44447
- const useRefBindingNames = collectUseRefBindingNames(componentBody, context.scopes);
44467
+ const useRefBindingNames = collectUseRefBindingNames(componentBody);
44448
44468
  const localBindingNames = collectLocalBindingNames(componentBody);
44449
44469
  for (const param of componentParams) collectPatternNames(param, localBindingNames);
44450
44470
  walkAst(componentBody, (child) => {
44451
44471
  if (!isNodeOfType(child, "CallExpression")) return;
44452
- if (!isReactHookCall(child, HOOKS_WITH_DEPS, context.scopes)) return;
44472
+ if (!isHookCall$2(child, HOOKS_WITH_DEPS)) return;
44453
44473
  if ((child.arguments?.length ?? 0) < 2) return;
44454
44474
  const depsNode = child.arguments[1];
44455
44475
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -46362,10 +46382,16 @@ const noPassDataToParent = defineRule({
46362
46382
  tags: ["test-noise"],
46363
46383
  recommendation: "Fetch the data in the parent and pass it down as a prop (or return it from the hook), instead of handing it back up through a prop callback in a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#passing-data-to-the-parent",
46364
46384
  create: (context) => {
46365
- const isReactUseRefCall = (node) => isReactHookCall(node, "useRef", context.scopes);
46366
- const isReactUseEffectCall = (node) => isReactHookCall(node, "useEffect", context.scopes);
46385
+ const isReactUseRefCall = (node) => isReactApiCall(node, "useRef", context.scopes, {
46386
+ allowGlobalReactNamespace: true,
46387
+ allowUnboundBareCalls: true
46388
+ });
46389
+ const isReactUseEffectCall = (node) => isReactApiCall(node, "useEffect", context.scopes, {
46390
+ allowGlobalReactNamespace: true,
46391
+ allowUnboundBareCalls: true
46392
+ });
46367
46393
  return { CallExpression(node) {
46368
- if (!isReactUseEffectCall(node)) return;
46394
+ if (!isUseEffect(node)) return;
46369
46395
  const analysis = getProgramAnalysis(node);
46370
46396
  if (!analysis) return;
46371
46397
  if (hasCleanup(analysis, node)) return;
@@ -46652,7 +46678,7 @@ const noPassLiveStateToParent = defineRule({
46652
46678
  tags: ["test-noise"],
46653
46679
  recommendation: "Move the state up to the parent (or return it from the hook), instead of handing it back up through a prop callback in a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#notifying-parent-components-about-state-changes",
46654
46680
  create: (context) => ({ CallExpression(node) {
46655
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
46681
+ if (!isUseEffect(node)) return;
46656
46682
  const analysis = getProgramAnalysis(node);
46657
46683
  if (!analysis) return;
46658
46684
  const effectFnRefs = getEffectFnRefs(analysis, node);
@@ -47067,7 +47093,7 @@ const hasPreviousValueDep = (effectNode, depElements) => {
47067
47093
  if (!isNodeOfType(element, "Identifier")) continue;
47068
47094
  const binding = findVariableInitializer(effectNode, element.name);
47069
47095
  if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) continue;
47070
- const calleeName = getCalleeName$1(binding.initializer);
47096
+ const calleeName = getCalleeName$2(binding.initializer);
47071
47097
  if (calleeName && PREVIOUS_VALUE_HOOK_PATTERN.test(calleeName)) return true;
47072
47098
  }
47073
47099
  return false;
@@ -47089,7 +47115,7 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
47089
47115
  if (!isNodeOfType(receiver, "Identifier")) return null;
47090
47116
  const binding = findVariableInitializer(callExpression, receiver.name);
47091
47117
  if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
47092
- if (getCalleeName$1(binding.initializer) !== "useRef") return null;
47118
+ if (getCalleeName$2(binding.initializer) !== "useRef") return null;
47093
47119
  const callbackArgument = binding.initializer.arguments?.[0];
47094
47120
  if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
47095
47121
  return isPropName(callbackArgument.name) ? callbackArgument.name : null;
@@ -47121,7 +47147,7 @@ const noPropCallbackInEffect = defineRule({
47121
47147
  return {
47122
47148
  ...propStackTracker.visitors,
47123
47149
  CallExpression(node) {
47124
- if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes) || (node.arguments?.length ?? 0) < 2) return;
47150
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
47125
47151
  const callback = getEffectCallback(node);
47126
47152
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
47127
47153
  const depsNode = node.arguments[1];
@@ -49014,7 +49040,7 @@ const noResetAllStateOnPropChange = defineRule({
49014
49040
  tags: ["test-noise"],
49015
49041
  recommendation: "Pass the prop as `key` so React resets the component for you when the prop changes, instead of clearing every state value by hand in a useEffect. See https://react.dev/learn/you-might-not-need-an-effect#resetting-all-state-when-a-prop-changes",
49016
49042
  create: (context) => ({ CallExpression(node) {
49017
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
49043
+ if (!isUseEffect(node)) return;
49018
49044
  const analysis = getProgramAnalysis(node);
49019
49045
  if (!analysis) return;
49020
49046
  const effectFnRefs = getEffectFnRefs(analysis, node);
@@ -49283,7 +49309,7 @@ const isTanStackServerFnHandlerCall = (node) => {
49283
49309
  if (node.callee.property.name !== "handler") return false;
49284
49310
  let currentNode = node.callee.object;
49285
49311
  while (isNodeOfType(currentNode, "CallExpression")) {
49286
- const calleeName = getCalleeName$1(currentNode);
49312
+ const calleeName = getCalleeName$2(currentNode);
49287
49313
  if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) return true;
49288
49314
  if (!isNodeOfType(currentNode.callee, "MemberExpression")) return false;
49289
49315
  currentNode = currentNode.callee.object;
@@ -49784,7 +49810,7 @@ const noSelfUpdatingEffect = defineRule({
49784
49810
  create: (context) => {
49785
49811
  const checkFunctionScope = (functionBody) => {
49786
49812
  if (!functionBody || !isNodeOfType(functionBody, "BlockStatement")) return;
49787
- const useStateBindings = collectUseStateBindings(functionBody, context.scopes);
49813
+ const useStateBindings = collectUseStateBindings(functionBody);
49788
49814
  if (useStateBindings.length === 0) return;
49789
49815
  const setterNameToStateName = /* @__PURE__ */ new Map();
49790
49816
  for (const binding of useStateBindings) setterNameToStateName.set(binding.setterName, binding.valueName);
@@ -49793,7 +49819,7 @@ const noSelfUpdatingEffect = defineRule({
49793
49819
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
49794
49820
  const effectCall = unwrapDiscardedExpression(statement);
49795
49821
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
49796
- if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
49822
+ if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
49797
49823
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
49798
49824
  const dependencyStateNames = collectDependencyStateNames(effectCall.arguments[1]);
49799
49825
  if (dependencyStateNames.size === 0) continue;
@@ -49879,7 +49905,7 @@ const noSetStateInRender = defineRule({
49879
49905
  create: (context) => {
49880
49906
  const checkComponent = (componentBody) => {
49881
49907
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
49882
- const setterNames = new Set(collectUseStateBindings(componentBody, context.scopes).map((binding) => binding.setterName));
49908
+ const setterNames = new Set(collectUseStateBindings(componentBody).map((binding) => binding.setterName));
49883
49909
  if (setterNames.size === 0) return;
49884
49910
  for (const statement of componentBody.body ?? []) {
49885
49911
  const setterCall = isUnconditionalSetterCallStatement(statement, setterNames);
@@ -50128,10 +50154,10 @@ const collectTimerRefUsageFacts = (ownerScope, refName) => {
50128
50154
  });
50129
50155
  return facts;
50130
50156
  };
50131
- const isEffectCallbackFunction = (functionNode, scopes) => {
50157
+ const isEffectCallbackFunction = (functionNode) => {
50132
50158
  const parent = functionNode.parent;
50133
50159
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
50134
- return isReactHookCall(parent, EFFECT_HOOK_NAMES$1, scopes) && getEffectCallback(parent) === functionNode;
50160
+ return isHookCall$2(parent, EFFECT_HOOK_NAMES$1) && getEffectCallback(parent) === functionNode;
50135
50161
  };
50136
50162
  const doesEffectCallbackReturnName = (effectCallback, name) => {
50137
50163
  if (!isFunctionLike$1(effectCallback)) return false;
@@ -50150,24 +50176,24 @@ const isFunctionReturnedFromEffectCallback = (functionNode, effectCallback) => {
50150
50176
  const cleanupBindingName = getFunctionBindingName$1(functionNode);
50151
50177
  return cleanupBindingName !== null && doesEffectCallbackReturnName(effectCallback, cleanupBindingName);
50152
50178
  };
50153
- const isReturnedFromAnyEffectInScope = (functionNode, ownerScope, scopes) => {
50179
+ const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
50154
50180
  const cleanupBindingName = getFunctionBindingName$1(functionNode);
50155
50181
  if (cleanupBindingName === null) return false;
50156
50182
  let isReturnedFromEffect = false;
50157
50183
  walkAst(ownerScope, (child) => {
50158
50184
  if (isReturnedFromEffect) return false;
50159
- if (!isNodeOfType(child, "CallExpression") || !isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) return;
50185
+ if (!isNodeOfType(child, "CallExpression") || !isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
50160
50186
  const effectCallback = getEffectCallback(child);
50161
50187
  if (effectCallback && doesEffectCallbackReturnName(effectCallback, cleanupBindingName)) isReturnedFromEffect = true;
50162
50188
  });
50163
50189
  return isReturnedFromEffect;
50164
50190
  };
50165
- const isInsideEffectCleanupReturn = (node, ownerScope, scopes) => {
50191
+ const isInsideEffectCleanupReturn = (node, ownerScope) => {
50166
50192
  let functionNode = findEnclosingFunction$1(node);
50167
50193
  while (functionNode) {
50168
50194
  const outerFunction = findEnclosingFunction$1(functionNode);
50169
- if (outerFunction && isEffectCallbackFunction(outerFunction, scopes) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
50170
- if (isReturnedFromAnyEffectInScope(functionNode, ownerScope, scopes)) return true;
50195
+ if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
50196
+ if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
50171
50197
  functionNode = outerFunction;
50172
50198
  }
50173
50199
  return false;
@@ -50203,10 +50229,10 @@ const noStaleTimerRef = defineRule({
50203
50229
  if (isShadowedTimerGlobal(node)) return;
50204
50230
  const { clearCalleeName, refName } = clearCall;
50205
50231
  const refBinding = findVariableInitializer(node, refName);
50206
- if (!refBinding?.initializer || !isReactHookCall(refBinding.initializer, "useRef", context.scopes)) return;
50232
+ if (!refBinding?.initializer || !isHookCall$2(refBinding.initializer, "useRef")) return;
50207
50233
  const usageFacts = collectTimerRefUsageFacts(refBinding.scopeOwner, refName);
50208
50234
  if (!usageFacts.holdsScheduledTimerId || !usageFacts.hasPendingSignalRead) return;
50209
- if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner, context.scopes)) return;
50235
+ if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner)) return;
50210
50236
  if (hasRefCurrentReassignmentAfterClear(node, refName)) return;
50211
50237
  context.report({
50212
50238
  node,
@@ -56010,7 +56036,7 @@ const classifyCallableReadsInsideEffect = (callableIdentifier, effectCallback, c
56010
56036
  allReadsAreInSubHandlers = false;
56011
56037
  return;
56012
56038
  }
56013
- if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$1(subHandlerCall);
56039
+ if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$2(subHandlerCall);
56014
56040
  });
56015
56041
  return {
56016
56042
  hasAnyRead,
@@ -56033,7 +56059,7 @@ const preferUseEffectEvent = defineRule({
56033
56059
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
56034
56060
  const effectCall = statement.expression;
56035
56061
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
56036
- if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
56062
+ if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
56037
56063
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
56038
56064
  const depsNode = effectCall.arguments[1];
56039
56065
  if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
@@ -56065,11 +56091,11 @@ const preferUseEffectEvent = defineRule({
56065
56091
  });
56066
56092
  //#endregion
56067
56093
  //#region src/plugin/rules/state-and-effects/prefer-use-sync-external-store.ts
56068
- const findUseEffectsInComponent = (componentBody, scopes) => {
56094
+ const findUseEffectsInComponent = (componentBody) => {
56069
56095
  const effectCalls = [];
56070
56096
  if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
56071
56097
  for (const statement of componentBody.body ?? []) walkAst(statement, (child) => {
56072
- if (isNodeOfType(child, "CallExpression") && isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) effectCalls.push(child);
56098
+ if (isNodeOfType(child, "CallExpression") && isHookCall$2(child, EFFECT_HOOK_NAMES$1)) effectCalls.push(child);
56073
56099
  });
56074
56100
  return effectCalls;
56075
56101
  };
@@ -56272,7 +56298,7 @@ const preferUseSyncExternalStore = defineRule({
56272
56298
  };
56273
56299
  const checkComponent = (componentBody) => {
56274
56300
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
56275
- const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
56301
+ const useStateBindings = collectUseStateBindings(componentBody);
56276
56302
  if (useStateBindings.length === 0) return;
56277
56303
  const useStateInitializerByValueName = /* @__PURE__ */ new Map();
56278
56304
  for (const binding of useStateBindings) {
@@ -56285,7 +56311,7 @@ const preferUseSyncExternalStore = defineRule({
56285
56311
  }
56286
56312
  const setterNameToValueName = /* @__PURE__ */ new Map();
56287
56313
  for (const binding of useStateBindings) setterNameToValueName.set(binding.setterName, binding.valueName);
56288
- for (const effectCall of findUseEffectsInComponent(componentBody, context.scopes)) {
56314
+ for (const effectCall of findUseEffectsInComponent(componentBody)) {
56289
56315
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
56290
56316
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
56291
56317
  const depsNode = effectCall.arguments[1];
@@ -56327,7 +56353,7 @@ const preferUseSyncExternalStore = defineRule({
56327
56353
  })).filter((candidate) => candidate.storeName !== null);
56328
56354
  if (snapshotBindings.length === 0) return;
56329
56355
  const reportedDeclarators = /* @__PURE__ */ new Set();
56330
- for (const effectCall of findUseEffectsInComponent(componentBody, context.scopes)) {
56356
+ for (const effectCall of findUseEffectsInComponent(componentBody)) {
56331
56357
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
56332
56358
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
56333
56359
  const depsNode = effectCall.arguments[1];
@@ -56424,7 +56450,7 @@ const preferUseReducer = defineRule({
56424
56450
  create: (context) => {
56425
56451
  const reportCoUpdatedUseState = (body, componentName) => {
56426
56452
  if (!isNodeOfType(body, "BlockStatement")) return;
56427
- const bindings = collectUseStateBindings(body, context.scopes);
56453
+ const bindings = collectUseStateBindings(body);
56428
56454
  const setterNames = new Set(bindings.map((binding) => binding.setterName));
56429
56455
  if (setterNames.size < 5) return;
56430
56456
  const coUpdatedCount = findLargestCoUpdatedSetterGroup(body, setterNames, new Map(bindings.map((binding) => {
@@ -56640,7 +56666,7 @@ const QUERY_READ_METHOD_NAMES = new Set([
56640
56666
  ]);
56641
56667
  const isQueryCacheSourceCall = (initializer) => {
56642
56668
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
56643
- const hookName = getCalleeName$1(initializer);
56669
+ const hookName = getCalleeName$2(initializer);
56644
56670
  if (!hookName) return false;
56645
56671
  return hookName === "useQueryClient" || TRPC_UTILS_HOOK_PATTERN.test(hookName);
56646
56672
  };
@@ -56772,7 +56798,7 @@ const queryMutationMissingInvalidation = defineRule({
56772
56798
  },
56773
56799
  CallExpression(node) {
56774
56800
  if (!hasQueryReadUsage) {
56775
- const callName = getCalleeName$1(node);
56801
+ const callName = getCalleeName$2(node);
56776
56802
  if (callName && (QUERY_READ_HOOK_NAMES.has(callName) || QUERY_READ_METHOD_NAMES.has(callName) || TRPC_UTILS_HOOK_PATTERN.test(callName))) hasQueryReadUsage = true;
56777
56803
  }
56778
56804
  const calleeName = isNodeOfType(node.callee, "Identifier") ? node.callee.name : null;
@@ -57265,30 +57291,26 @@ const REMOVAL_MESSAGE_BY_REACT_API_NAME = new Map([
57265
57291
  ["useCallback", "This `useCallback` is dead weight, since React Compiler already caches every function here. Delete it."],
57266
57292
  ["memo", "This `memo()` is dead weight, since React Compiler already caches the component's output. Delete it."]
57267
57293
  ]);
57268
- const resolveReactApiNameForIdentifier = (callee, context) => {
57294
+ const resolveReactApiNameForIdentifier = (callee) => {
57269
57295
  if (!isNodeOfType(callee, "Identifier")) return null;
57270
- if (context.scopes.symbolFor(callee)?.kind !== "import") return null;
57271
57296
  const importedName = getImportedNameFromModule(callee, callee.name, "react");
57272
57297
  if (importedName && REMOVAL_MESSAGE_BY_REACT_API_NAME.has(importedName)) return importedName;
57273
57298
  return null;
57274
57299
  };
57275
- const resolveReactApiNameForMemberExpression = (callee, context) => {
57300
+ const resolveReactApiNameForMemberExpression = (callee) => {
57276
57301
  if (!isNodeOfType(callee, "MemberExpression")) return null;
57277
57302
  if (callee.computed) return null;
57278
- const namespaceIdentifier = stripParenExpression(callee.object);
57303
+ const namespaceIdentifier = callee.object;
57279
57304
  const propertyIdentifier = callee.property;
57280
57305
  if (!isNodeOfType(namespaceIdentifier, "Identifier")) return null;
57281
57306
  if (!isNodeOfType(propertyIdentifier, "Identifier")) return null;
57282
57307
  if (!REMOVAL_MESSAGE_BY_REACT_API_NAME.has(propertyIdentifier.name)) return null;
57283
57308
  const namespaceName = namespaceIdentifier.name;
57284
- if (context.scopes.symbolFor(namespaceIdentifier)?.kind === "import" && isImportedFromModule(namespaceIdentifier, namespaceName, "react")) return propertyIdentifier.name;
57285
- if (isCanonicalReactNamespaceName(namespaceName) && context.scopes.isGlobalReference(namespaceIdentifier)) return propertyIdentifier.name;
57309
+ if (isCanonicalReactNamespaceName(namespaceName)) return propertyIdentifier.name;
57310
+ if (isImportedFromModule(namespaceIdentifier, namespaceName, "react")) return propertyIdentifier.name;
57286
57311
  return null;
57287
57312
  };
57288
- const resolveReactApiNameForCallee = (callee, context) => {
57289
- const unwrappedCallee = stripParenExpression(callee);
57290
- return resolveReactApiNameForIdentifier(unwrappedCallee, context) ?? resolveReactApiNameForMemberExpression(unwrappedCallee, context);
57291
- };
57313
+ const resolveReactApiNameForCallee = (callee) => resolveReactApiNameForIdentifier(callee) ?? resolveReactApiNameForMemberExpression(callee);
57292
57314
  const isNullishComparatorArgument = (argumentNode) => isNodeOfType(argumentNode, "Identifier") && argumentNode.name === "undefined" || isNodeOfType(argumentNode, "Literal") && argumentNode.value === null;
57293
57315
  const COMPILER_INFERABLE_HOC_NAMES = new Set(["memo", "forwardRef"]);
57294
57316
  const calleeTrailingName = (callee) => {
@@ -57314,7 +57336,7 @@ const reactCompilerNoManualMemoization = defineRule({
57314
57336
  requires: ["react-compiler"],
57315
57337
  recommendation: "Delete the `useMemo` / `useCallback` / `memo` call and use the plain value or component. React Compiler caches it for you.",
57316
57338
  create: (context) => ({ CallExpression(node) {
57317
- const apiName = resolveReactApiNameForCallee(node.callee, context);
57339
+ const apiName = resolveReactApiNameForCallee(node.callee);
57318
57340
  if (!apiName) return;
57319
57341
  if (apiName === "memo") {
57320
57342
  const comparatorArgument = node.arguments?.[1];
@@ -59064,7 +59086,7 @@ const rerenderDependencies = defineRule({
59064
59086
  severity: "error",
59065
59087
  recommendation: "Move it into a useMemo, useRef, or a constant outside the component so it stays the same between renders.",
59066
59088
  create: (context) => ({ CallExpression(node) {
59067
- if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes) || node.arguments.length < 2) return;
59089
+ if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
59068
59090
  const depsNode = node.arguments[1];
59069
59091
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
59070
59092
  for (const element of depsNode.elements ?? []) {
@@ -59319,7 +59341,7 @@ const rerenderLazyRefInit = defineRule({
59319
59341
  category: "Performance",
59320
59342
  recommendation: "Initialize the ref lazily so expensive values are not rebuilt and discarded on every render.",
59321
59343
  create: (context) => ({ CallExpression(node) {
59322
- if (!isReactHookCall(node, "useRef", context.scopes) || !node.arguments?.length) return;
59344
+ if (!isHookCall$2(node, "useRef") || !node.arguments?.length) return;
59323
59345
  const initializer = stripParenExpression(node.arguments[0]);
59324
59346
  const isPlainCall = isNodeOfType(initializer, "CallExpression");
59325
59347
  const isNewCall = isNodeOfType(initializer, "NewExpression");
@@ -59383,7 +59405,7 @@ const rerenderLazyStateInit = defineRule({
59383
59405
  category: "Performance",
59384
59406
  recommendation: "Wrap expensive initial state in an arrow function so the initializer does not rerun and get thrown away on every render.",
59385
59407
  create: (context) => ({ CallExpression(node) {
59386
- if (!isReactHookCall(node, "useState", context.scopes) || !node.arguments?.length) return;
59408
+ if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
59387
59409
  const initializer = findEagerInitializerCall(node.arguments[0]);
59388
59410
  if (!initializer) return;
59389
59411
  const isConstructor = isNodeOfType(initializer, "NewExpression");
@@ -60146,11 +60168,11 @@ const isInsideConditionTest = (identifier, stopAt) => {
60146
60168
  }
60147
60169
  return false;
60148
60170
  };
60149
- const collectEffectDependencyInfos = (componentBody, setterNames, scopes) => {
60171
+ const collectEffectDependencyInfos = (componentBody, setterNames) => {
60150
60172
  const effectInfos = [];
60151
60173
  walkAst(componentBody, (child) => {
60152
60174
  if (!isNodeOfType(child, "CallExpression")) return;
60153
- if (!isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) return;
60175
+ if (!isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
60154
60176
  const dependencyNames = /* @__PURE__ */ new Set();
60155
60177
  for (const argument of child.arguments ?? []) {
60156
60178
  if (!isNodeOfType(argument, "ArrayExpression")) continue;
@@ -60206,14 +60228,13 @@ const collectEffectDependencyInfos = (componentBody, setterNames, scopes) => {
60206
60228
  });
60207
60229
  return effectInfos;
60208
60230
  };
60209
- const collectCustomHookArgumentNames = (componentBody, scopes) => {
60231
+ const collectCustomHookArgumentNames = (componentBody) => {
60210
60232
  const argumentNames = /* @__PURE__ */ new Set();
60211
60233
  walkAst(componentBody, (child) => {
60212
60234
  if (!isNodeOfType(child, "CallExpression")) return;
60213
60235
  if (!isNodeOfType(child.callee, "Identifier")) return;
60214
60236
  const calleeName = child.callee.name;
60215
60237
  if (!isReactHookName(calleeName)) return;
60216
- if (isReactHookCall(child, BUILTIN_HOOK_NAMES, scopes)) return;
60217
60238
  if (BUILTIN_HOOK_NAMES.has(calleeName)) return;
60218
60239
  if (EFFECT_HOOK_NAMES$1.has(calleeName)) return;
60219
60240
  for (const argument of child.arguments ?? []) walkAst(argument, (argumentNode) => {
@@ -60254,21 +60275,21 @@ const rerenderStateOnlyInHandlers = defineRule({
60254
60275
  create: (context) => {
60255
60276
  const checkComponent = (componentBody) => {
60256
60277
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
60257
- const bindings = collectUseStateBindings(componentBody, context.scopes);
60278
+ const bindings = collectUseStateBindings(componentBody);
60258
60279
  if (bindings.length === 0) return;
60259
60280
  if (collectRenderReachableExpressions(componentBody).length === 0) return;
60260
- const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
60281
+ const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
60261
60282
  const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
60262
- const directRenderNames = collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames);
60283
+ const directRenderNames = collectRenderReachableNames(componentBody, eventHandlerReferenceNames);
60263
60284
  if (hasRenderPhaseNonHookCall(componentBody)) for (const voidMarkedName of collectTopLevelVoidMarkedNames(componentBody)) directRenderNames.add(voidMarkedName);
60264
60285
  const renderReachableNames = expandTransitiveDependencies(directRenderNames, dependencyGraph);
60265
60286
  const setterNames = new Set(bindings.map((binding) => binding.setterName));
60266
- const effectInfos = collectEffectDependencyInfos(componentBody, setterNames, context.scopes);
60287
+ const effectInfos = collectEffectDependencyInfos(componentBody, setterNames);
60267
60288
  const selfEchoValueNames = /* @__PURE__ */ new Set();
60268
60289
  for (const binding of bindings) if (effectInfos.some((effectInfo) => effectInfo.dependencyNames.has(binding.valueName) && effectInfo.synchronouslyCalledFunctionNames.has(binding.setterName) && !effectInfo.payloadReadNames.has(binding.valueName) && !effectInfo.nestedCallbackCalledFunctionNames.has(binding.setterName))) selfEchoValueNames.add(binding.valueName);
60269
60290
  const effectConsumedNames = /* @__PURE__ */ new Set();
60270
60291
  for (const effectInfo of effectInfos) for (const dependencyName of effectInfo.dependencyNames) if (!selfEchoValueNames.has(dependencyName)) effectConsumedNames.add(dependencyName);
60271
- for (const hookArgumentName of collectCustomHookArgumentNames(componentBody, context.scopes)) effectConsumedNames.add(hookArgumentName);
60292
+ for (const hookArgumentName of collectCustomHookArgumentNames(componentBody)) effectConsumedNames.add(hookArgumentName);
60272
60293
  for (const reachableName of expandTransitiveDependencies(effectConsumedNames, dependencyGraph)) renderReachableNames.add(reachableName);
60273
60294
  const calledSetterNames = /* @__PURE__ */ new Set();
60274
60295
  walkAst(componentBody, (child) => {
@@ -68169,7 +68190,7 @@ const declarationAwaitsGate = (declaration, context) => {
68169
68190
  if (!isNodeOfType(argument, "CallExpression")) continue;
68170
68191
  if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
68171
68192
  if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
68172
- const calleeName = getCalleeName$1(argument);
68193
+ const calleeName = getCalleeName$2(argument);
68173
68194
  if (!calleeName) continue;
68174
68195
  if (isAuthGuardName(calleeName)) return true;
68175
68196
  const [leadingToken] = tokenizeIdentifierWords(calleeName);
@@ -68809,7 +68830,7 @@ const walkServerFnChain = (outerNode) => {
68809
68830
  if (!isNodeOfType(outerNode.callee, "MemberExpression")) return result;
68810
68831
  let currentNode = stripParenExpression(outerNode.callee.object);
68811
68832
  while (isNodeOfType(currentNode, "CallExpression")) {
68812
- const calleeName = getCalleeName$1(currentNode);
68833
+ const calleeName = getCalleeName$2(currentNode);
68813
68834
  if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) {
68814
68835
  result.isServerFnChain = true;
68815
68836
  const optionsArgument = currentNode.arguments?.[0];