oxlint-plugin-react-doctor 0.7.9-dev.2450582 → 0.7.9-dev.2ba83c3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +0 -46
  2. package/dist/index.js +430 -1815
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -836,131 +836,22 @@ const getImportedName = (importSpecifier) => {
836
836
  if (isNodeOfType(imported, "Literal") && typeof imported.value === "string") return imported.value;
837
837
  };
838
838
  //#endregion
839
- //#region src/plugin/utils/get-static-property-key-name.ts
840
- const getStaticPropertyKeyName = (node, options = {}) => {
841
- if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition") && !isNodeOfType(node, "MemberExpression")) return null;
842
- const key = isNodeOfType(node, "MemberExpression") ? node.property : node.key;
843
- if (node.computed) {
844
- if (options.allowComputedString && isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value;
845
- if (options.allowComputedString && isNodeOfType(key, "TemplateLiteral") && key.expressions.length === 0) return key.quasis[0]?.value.cooked ?? key.quasis[0]?.value.raw ?? null;
846
- return null;
847
- }
848
- if (isNodeOfType(key, "Identifier")) return key.name;
849
- if (isNodeOfType(key, "Literal")) {
850
- if (typeof key.value === "string") return key.value;
851
- if (options.stringifyNonStringLiterals) return String(key.value);
852
- }
853
- return null;
854
- };
855
- //#endregion
856
- //#region src/plugin/utils/get-static-property-name.ts
857
- const getStaticPropertyName = (memberExpression) => {
858
- const property = memberExpression.property;
859
- if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
860
- if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
861
- if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
839
+ //#region src/plugin/utils/get-callee-name.ts
840
+ const getCalleeName$2 = (node) => {
841
+ if (!isNodeOfType(node, "CallExpression") && !isNodeOfType(node, "NewExpression")) return null;
842
+ if (isNodeOfType(node.callee, "Identifier")) return node.callee.name;
843
+ if (isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier")) return node.callee.property.name;
862
844
  return null;
863
845
  };
864
846
  //#endregion
865
- //#region src/plugin/utils/strip-paren-expression.ts
866
- const TRANSPARENT_EXPRESSION_WRAPPER_TYPES = new Set([
867
- "ParenthesizedExpression",
868
- "TSAsExpression",
869
- "TSSatisfiesExpression",
870
- "TSTypeAssertion",
871
- "TSNonNullExpression",
872
- "TSInstantiationExpression",
873
- "ChainExpression"
874
- ]);
875
- const stripParenExpression = (node) => {
876
- let current = node;
877
- while (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(current.type) && "expression" in current && current.expression) current = current.expression;
878
- return current;
879
- };
880
- //#endregion
881
- //#region src/plugin/utils/resolve-const-identifier-alias.ts
882
- const resolveConstIdentifierAlias = (identifier, scopes, allowPatternBinding = false) => {
883
- if (!isNodeOfType(identifier, "Identifier") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
884
- const visitedSymbolIds = /* @__PURE__ */ new Set();
885
- let symbol = scopes.symbolFor(identifier);
886
- while (symbol?.kind === "const") {
887
- if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return null;
888
- if (symbol.declarationNode.id !== symbol.bindingIdentifier) return allowPatternBinding ? symbol : null;
889
- visitedSymbolIds.add(symbol.id);
890
- const initializer = stripParenExpression(symbol.initializer);
891
- if (!isNodeOfType(initializer, "Identifier")) return symbol;
892
- symbol = scopes.symbolFor(initializer);
893
- }
894
- return symbol;
895
- };
896
- //#endregion
897
- //#region src/plugin/utils/is-react-api-call.ts
898
- const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? apiNames === apiName : apiNames.has(apiName);
899
- const isImportedFromReact = (symbol) => {
900
- if (symbol.kind !== "import") return false;
901
- const importDeclaration = symbol.declarationNode.parent;
902
- return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(importDeclaration.source.value));
903
- };
904
- const isNamedReactApiImport = (identifier, apiNames, scopes, resolveAliases) => {
905
- if (!isNodeOfType(identifier, "Identifier")) return false;
906
- const symbol = resolveAliases ? resolveConstIdentifierAlias(identifier, scopes) : scopes.symbolFor(identifier);
907
- if (!symbol || !isImportedFromReact(symbol)) return false;
908
- const importedName = getImportedName(symbol.declarationNode);
909
- return Boolean(importedName && includesApiName(apiNames, importedName));
910
- };
911
- const isReactNamespaceImport = (identifier, scopes) => {
912
- const symbol = resolveConstIdentifierAlias(identifier, scopes);
913
- if (!symbol || !isImportedFromReact(symbol)) return false;
914
- return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
915
- };
916
- const isReactNamespaceReceiver$1 = (receiver, scopes, options) => {
917
- if (!isNodeOfType(receiver, "Identifier")) return false;
918
- if (isReactNamespaceImport(receiver, scopes)) return true;
919
- return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
920
- };
921
- const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) => {
922
- const symbol = scopes.symbolFor(identifier);
923
- if (!symbol || symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
924
- const pattern = symbol.declarationNode.id;
925
- if (!isNodeOfType(pattern, "ObjectPattern")) return false;
926
- for (const property of pattern.properties) {
927
- if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
928
- const propertyName = getStaticPropertyKeyName(property);
929
- return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver$1(stripParenExpression(symbol.initializer), scopes, options));
930
- }
931
- return false;
932
- };
933
- const isReactApiCall = (node, apiNames, scopes, options = {}) => {
847
+ //#region src/plugin/utils/is-hook-call.ts
848
+ const isHookCall$2 = (node, hookName) => {
934
849
  if (!isNodeOfType(node, "CallExpression")) return false;
935
- return isReactApiCallee(node.callee, apiNames, scopes, options, /* @__PURE__ */ new Set());
936
- };
937
- const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds) => {
938
- const callee = stripParenExpression(rawCallee);
939
- if (options.resolveConditionalAliases && isNodeOfType(callee, "ConditionalExpression")) return isReactApiCallee(callee.consequent, apiNames, scopes, options, new Set(visitedSymbolIds)) && isReactApiCallee(callee.alternate, apiNames, scopes, options, new Set(visitedSymbolIds));
940
- if (isNodeOfType(callee, "Identifier")) {
941
- if (isNamedReactApiImport(callee, apiNames, scopes, Boolean(options.resolveNamedAliases))) return true;
942
- if (options.resolveNamedAliases && isDestructuredReactApiBinding(callee, apiNames, scopes, options)) return true;
943
- if (options.resolveConditionalAliases) {
944
- const symbol = scopes.symbolFor(callee);
945
- if (symbol?.kind === "const" && symbol.initializer && !visitedSymbolIds.has(symbol.id)) {
946
- visitedSymbolIds.add(symbol.id);
947
- return isReactApiCallee(symbol.initializer, apiNames, scopes, options, visitedSymbolIds);
948
- }
949
- }
950
- return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
951
- }
952
- if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
953
- return isReactNamespaceReceiver$1(stripParenExpression(callee.object), scopes, options);
850
+ const calleeName = getCalleeName$2(node);
851
+ if (!calleeName) return false;
852
+ return typeof hookName === "string" ? calleeName === hookName : hookName.has(calleeName);
954
853
  };
955
854
  //#endregion
956
- //#region src/plugin/utils/is-react-hook-call.ts
957
- const isReactHookCall = (node, hookNames, scopes) => isReactApiCall(node, hookNames, scopes, {
958
- allowGlobalReactNamespace: true,
959
- allowUnboundBareCalls: true,
960
- resolveConditionalAliases: true,
961
- resolveNamedAliases: true
962
- });
963
- //#endregion
964
855
  //#region src/plugin/utils/is-ast-node.ts
965
856
  const isAstNode = (value) => value !== null && typeof value === "object" && "type" in value && typeof value.type === "string";
966
857
  //#endregion
@@ -1030,12 +921,12 @@ const collectChildComponentNames = (element, into) => {
1030
921
  into.add(name);
1031
922
  });
1032
923
  };
1033
- const countEffectHookCalls = (body, scopes) => {
924
+ const countEffectHookCalls = (body) => {
1034
925
  if (!body) return 0;
1035
926
  let count = 0;
1036
927
  walkAst(body, (child) => {
1037
928
  if (!isNodeOfType(child, "CallExpression")) return;
1038
- if (isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) count++;
929
+ if (isHookCall$2(child, EFFECT_HOOK_NAMES$1)) count++;
1039
930
  });
1040
931
  return count;
1041
932
  };
@@ -1061,11 +952,11 @@ const getComponentEffectIndex = (programRoot) => {
1061
952
  componentEffectIndexCache.set(programRoot, index);
1062
953
  return index;
1063
954
  };
1064
- const getSameFileComponentEffectCount = (programRoot, componentName, scopes) => {
955
+ const getSameFileComponentEffectCount = (programRoot, componentName) => {
1065
956
  const index = getComponentEffectIndex(programRoot);
1066
957
  const cachedCount = index.effectCountByName.get(componentName);
1067
958
  if (cachedCount !== void 0) return cachedCount;
1068
- const count = countEffectHookCalls(index.bodyByName.get(componentName) ?? null, scopes);
959
+ const count = countEffectHookCalls(index.bodyByName.get(componentName) ?? null);
1069
960
  index.effectCountByName.set(componentName, count);
1070
961
  return count;
1071
962
  };
@@ -1125,7 +1016,7 @@ const activityWrapsEffectHeavySubtree = defineRule({
1125
1016
  let totalEffects = 0;
1126
1017
  const effectfulChildren = [];
1127
1018
  for (const componentName of childComponentNames) {
1128
- const effectCount = getSameFileComponentEffectCount(programRoot, componentName, context.scopes);
1019
+ const effectCount = getSameFileComponentEffectCount(programRoot, componentName);
1129
1020
  if (effectCount === 0) continue;
1130
1021
  totalEffects += effectCount;
1131
1022
  effectfulChildren.push(`<${componentName}>`);
@@ -1316,14 +1207,6 @@ const findVariableInitializer = (referenceNode, bindingName) => {
1316
1207
  return best;
1317
1208
  };
1318
1209
  //#endregion
1319
- //#region src/plugin/utils/get-callee-name.ts
1320
- const getCalleeName$1 = (node) => {
1321
- if (!isNodeOfType(node, "CallExpression") && !isNodeOfType(node, "NewExpression")) return null;
1322
- if (isNodeOfType(node.callee, "Identifier")) return node.callee.name;
1323
- if (isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier")) return node.callee.property.name;
1324
- return null;
1325
- };
1326
- //#endregion
1327
1210
  //#region src/plugin/utils/is-function-like.ts
1328
1211
  /**
1329
1212
  * Type-guard for the three "function-like" ESTree node shapes:
@@ -1337,6 +1220,37 @@ const getCalleeName$1 = (node) => {
1337
1220
  */
1338
1221
  const isFunctionLike$1 = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")));
1339
1222
  //#endregion
1223
+ //#region src/plugin/utils/strip-paren-expression.ts
1224
+ const TRANSPARENT_EXPRESSION_WRAPPER_TYPES = new Set([
1225
+ "ParenthesizedExpression",
1226
+ "TSAsExpression",
1227
+ "TSSatisfiesExpression",
1228
+ "TSTypeAssertion",
1229
+ "TSNonNullExpression",
1230
+ "TSInstantiationExpression",
1231
+ "ChainExpression"
1232
+ ]);
1233
+ const stripParenExpression = (node) => {
1234
+ let current = node;
1235
+ while (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(current.type) && "expression" in current && current.expression) current = current.expression;
1236
+ return current;
1237
+ };
1238
+ //#endregion
1239
+ //#region src/plugin/utils/resolve-const-identifier-alias.ts
1240
+ const resolveConstIdentifierAlias = (identifier, scopes) => {
1241
+ if (!isNodeOfType(identifier, "Identifier") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
1242
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
1243
+ let symbol = scopes.symbolFor(identifier);
1244
+ while (symbol?.kind === "const") {
1245
+ if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
1246
+ visitedSymbolIds.add(symbol.id);
1247
+ const initializer = stripParenExpression(symbol.initializer);
1248
+ if (!isNodeOfType(initializer, "Identifier")) return symbol;
1249
+ symbol = scopes.symbolFor(initializer);
1250
+ }
1251
+ return symbol;
1252
+ };
1253
+ //#endregion
1340
1254
  //#region src/plugin/utils/resolve-exact-local-function.ts
1341
1255
  const resolveExactLocalFunction = (expression, scopes) => {
1342
1256
  const unwrappedExpression = stripParenExpression(expression);
@@ -1382,17 +1296,9 @@ const getRootIdentifier$1 = (node, options) => {
1382
1296
  //#region src/plugin/utils/get-root-identifier-name.ts
1383
1297
  const getRootIdentifierName = (node, options) => getRootIdentifier$1(node, options)?.name ?? null;
1384
1298
  //#endregion
1385
- //#region src/plugin/utils/is-hook-call.ts
1386
- const isHookCall$2 = (node, hookName) => {
1387
- if (!isNodeOfType(node, "CallExpression")) return false;
1388
- const calleeName = getCalleeName$1(node);
1389
- if (!calleeName) return false;
1390
- return typeof hookName === "string" ? calleeName === hookName : hookName.has(calleeName);
1391
- };
1392
- //#endregion
1393
1299
  //#region src/plugin/rules/state-and-effects/advanced-event-handler-refs.ts
1394
- const REACT_STABLE_HANDLER_HOOK_NAMES = new Set(["useCallback", "useEffectEvent"]);
1395
- const CUSTOM_STABLE_HANDLER_HOOK_NAMES = new Set([
1300
+ const STABLE_HANDLER_HOOK_NAMES = new Set([
1301
+ "useCallback",
1396
1302
  "useEffectEvent",
1397
1303
  "useEvent",
1398
1304
  "useEventCallback",
@@ -1401,21 +1307,21 @@ const CUSTOM_STABLE_HANDLER_HOOK_NAMES = new Set([
1401
1307
  ]);
1402
1308
  const THROTTLED_HANDLER_HOOK_PATTERN = /^use\w*(?:Throttle|Debounce)/i;
1403
1309
  const isThrottledHandlerHookCall = (callNode) => {
1404
- const calleeName = getCalleeName$1(callNode);
1310
+ const calleeName = getCalleeName$2(callNode);
1405
1311
  return calleeName !== null && THROTTLED_HANDLER_HOOK_PATTERN.test(calleeName);
1406
1312
  };
1407
- const isEmptyDepsUseMemoCall = (callNode, scopes) => {
1408
- if (!isReactHookCall(callNode, "useMemo", scopes)) return false;
1313
+ const isEmptyDepsUseMemoCall = (callNode) => {
1314
+ if (!isHookCall$2(callNode, "useMemo")) return false;
1409
1315
  const memoDepsNode = callNode.arguments?.[1];
1410
1316
  return isNodeOfType(memoDepsNode, "ArrayExpression") && (memoDepsNode.elements?.length ?? 0) === 0;
1411
1317
  };
1412
- const isStableHandlerInitializer = (initializer, scopes) => {
1413
- if (isNodeOfType(initializer, "CallExpression")) return isReactHookCall(initializer, REACT_STABLE_HANDLER_HOOK_NAMES, scopes) || isHookCall$2(initializer, CUSTOM_STABLE_HANDLER_HOOK_NAMES) || isEmptyDepsUseMemoCall(initializer, scopes) || isThrottledHandlerHookCall(initializer);
1318
+ const isStableHandlerInitializer = (initializer) => {
1319
+ if (isNodeOfType(initializer, "CallExpression")) return isHookCall$2(initializer, STABLE_HANDLER_HOOK_NAMES) || isEmptyDepsUseMemoCall(initializer) || isThrottledHandlerHookCall(initializer);
1414
1320
  return isNodeOfType(initializer, "MemberExpression") && isNodeOfType(initializer.property, "Identifier") && initializer.property.name === "current";
1415
1321
  };
1416
- const isStableRefReceiverDep = (referenceNode, receiverDepName, scopes) => {
1322
+ const isStableRefReceiverDep = (referenceNode, receiverDepName) => {
1417
1323
  const receiverBinding = findVariableInitializer(referenceNode, receiverDepName);
1418
- return Boolean(receiverBinding?.initializer && isReactHookCall(receiverBinding.initializer, "useRef", scopes));
1324
+ return Boolean(receiverBinding?.initializer && isHookCall$2(receiverBinding.initializer, "useRef"));
1419
1325
  };
1420
1326
  const advancedEventHandlerRefs = defineRule({
1421
1327
  id: "advanced-event-handler-refs",
@@ -1425,7 +1331,7 @@ const advancedEventHandlerRefs = defineRule({
1425
1331
  category: "Performance",
1426
1332
  recommendation: "Store the handler in a ref and have the listener read `handlerRef.current()`. The subscription stays put while the latest handler still runs.",
1427
1333
  create: (context) => ({ CallExpression(node) {
1428
- if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes)) return;
1334
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
1429
1335
  if ((node.arguments?.length ?? 0) < 2) return;
1430
1336
  const callback = getEffectCallback(node);
1431
1337
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
@@ -1449,8 +1355,8 @@ const advancedEventHandlerRefs = defineRule({
1449
1355
  });
1450
1356
  if (!registeredHandlerName) return;
1451
1357
  const handlerBinding = findVariableInitializer(node, registeredHandlerName);
1452
- if (handlerBinding?.initializer && isStableHandlerInitializer(handlerBinding.initializer, context.scopes)) return;
1453
- if ([...depIdentifierNames].some((depName) => depName !== registeredHandlerName && subscriptionReceiverNames.has(depName) && !isStableRefReceiverDep(node, depName, context.scopes))) return;
1358
+ if (handlerBinding?.initializer && isStableHandlerInitializer(handlerBinding.initializer)) return;
1359
+ if ([...depIdentifierNames].some((depName) => depName !== registeredHandlerName && subscriptionReceiverNames.has(depName) && !isStableRefReceiverDep(node, depName))) return;
1454
1360
  context.report({
1455
1361
  node,
1456
1362
  message: `useEffect re-adds the "${registeredHandlerName}" listener every time the handler changes.`
@@ -1937,6 +1843,15 @@ const flattenJsxName$1 = (node) => {
1937
1843
  return null;
1938
1844
  };
1939
1845
  //#endregion
1846
+ //#region src/plugin/utils/get-static-property-name.ts
1847
+ const getStaticPropertyName = (memberExpression) => {
1848
+ const property = memberExpression.property;
1849
+ if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
1850
+ if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
1851
+ if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
1852
+ return null;
1853
+ };
1854
+ //#endregion
1940
1855
  //#region src/plugin/utils/is-generated-image-renderer-call.ts
1941
1856
  const GENERATED_IMAGE_RENDERER_MODULES = [
1942
1857
  "next/og",
@@ -4944,6 +4859,23 @@ const containsDirectAwait = (node) => {
4944
4859
  return foundAwait;
4945
4860
  };
4946
4861
  //#endregion
4862
+ //#region src/plugin/utils/get-static-property-key-name.ts
4863
+ const getStaticPropertyKeyName = (node, options = {}) => {
4864
+ if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition") && !isNodeOfType(node, "MemberExpression")) return null;
4865
+ const key = isNodeOfType(node, "MemberExpression") ? node.property : node.key;
4866
+ if (node.computed) {
4867
+ if (options.allowComputedString && isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value;
4868
+ if (options.allowComputedString && isNodeOfType(key, "TemplateLiteral") && key.expressions.length === 0) return key.quasis[0]?.value.cooked ?? key.quasis[0]?.value.raw ?? null;
4869
+ return null;
4870
+ }
4871
+ if (isNodeOfType(key, "Identifier")) return key.name;
4872
+ if (isNodeOfType(key, "Literal")) {
4873
+ if (typeof key.value === "string") return key.value;
4874
+ if (options.stringifyNonStringLiterals) return String(key.value);
4875
+ }
4876
+ return null;
4877
+ };
4878
+ //#endregion
4947
4879
  //#region src/plugin/utils/get-destructured-binding-property-name.ts
4948
4880
  const getDestructuredBindingPropertyName = (bindingIdentifier) => {
4949
4881
  let bindingNode = bindingIdentifier;
@@ -9008,6 +8940,65 @@ const hasVisibleBindingNamed = (node, bindingName, scopes) => {
9008
8940
  }
9009
8941
  };
9010
8942
  //#endregion
8943
+ //#region src/plugin/utils/is-react-api-call.ts
8944
+ const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? apiNames === apiName : apiNames.has(apiName);
8945
+ const isImportedFromReact = (symbol) => {
8946
+ if (symbol.kind !== "import") return false;
8947
+ const importDeclaration = symbol.declarationNode.parent;
8948
+ return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && typeof importDeclaration.source.value === "string" && REACT_RUNTIME_MODULE_SOURCES.has(importDeclaration.source.value));
8949
+ };
8950
+ const isNamedReactApiImport = (identifier, apiNames, scopes, resolveAliases) => {
8951
+ if (!isNodeOfType(identifier, "Identifier")) return false;
8952
+ const symbol = resolveAliases ? resolveConstIdentifierAlias(identifier, scopes) : scopes.symbolFor(identifier);
8953
+ if (!symbol || !isImportedFromReact(symbol)) return false;
8954
+ const importedName = getImportedName(symbol.declarationNode);
8955
+ return Boolean(importedName && includesApiName(apiNames, importedName));
8956
+ };
8957
+ const isReactNamespaceImport = (identifier, scopes) => {
8958
+ const symbol = resolveConstIdentifierAlias(identifier, scopes);
8959
+ if (!symbol || !isImportedFromReact(symbol)) return false;
8960
+ return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
8961
+ };
8962
+ const isReactNamespaceReceiver = (receiver, scopes, options) => {
8963
+ if (!isNodeOfType(receiver, "Identifier")) return false;
8964
+ if (isReactNamespaceImport(receiver, scopes)) return true;
8965
+ return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
8966
+ };
8967
+ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) => {
8968
+ const symbol = scopes.symbolFor(identifier);
8969
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
8970
+ const pattern = symbol.declarationNode.id;
8971
+ if (!isNodeOfType(pattern, "ObjectPattern")) return false;
8972
+ for (const property of pattern.properties) {
8973
+ if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
8974
+ const propertyName = getStaticPropertyKeyName(property);
8975
+ return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver(stripParenExpression(symbol.initializer), scopes, options));
8976
+ }
8977
+ return false;
8978
+ };
8979
+ const isReactApiCall = (node, apiNames, scopes, options = {}) => {
8980
+ if (!isNodeOfType(node, "CallExpression")) return false;
8981
+ return isReactApiCallee(node.callee, apiNames, scopes, options, /* @__PURE__ */ new Set());
8982
+ };
8983
+ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds) => {
8984
+ const callee = stripParenExpression(rawCallee);
8985
+ if (options.resolveConditionalAliases && isNodeOfType(callee, "ConditionalExpression")) return isReactApiCallee(callee.consequent, apiNames, scopes, options, new Set(visitedSymbolIds)) && isReactApiCallee(callee.alternate, apiNames, scopes, options, new Set(visitedSymbolIds));
8986
+ if (isNodeOfType(callee, "Identifier")) {
8987
+ if (isNamedReactApiImport(callee, apiNames, scopes, Boolean(options.resolveNamedAliases))) return true;
8988
+ if (options.resolveNamedAliases && isDestructuredReactApiBinding(callee, apiNames, scopes, options)) return true;
8989
+ if (options.resolveConditionalAliases) {
8990
+ const symbol = scopes.symbolFor(callee);
8991
+ if (symbol?.kind === "const" && symbol.initializer && !visitedSymbolIds.has(symbol.id)) {
8992
+ visitedSymbolIds.add(symbol.id);
8993
+ return isReactApiCallee(symbol.initializer, apiNames, scopes, options, visitedSymbolIds);
8994
+ }
8995
+ }
8996
+ return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
8997
+ }
8998
+ if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
8999
+ return isReactNamespaceReceiver(stripParenExpression(callee.object), scopes, options);
9000
+ };
9001
+ //#endregion
9011
9002
  //#region src/plugin/utils/is-proven-browser-api-receiver.ts
9012
9003
  const DOM_EVENT_TARGET_TYPE_NAMES = new Set([
9013
9004
  "AbortSignal",
@@ -13661,7 +13652,7 @@ const getPromiseChainCallForCallback = (candidate) => {
13661
13652
  if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
13662
13653
  return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
13663
13654
  };
13664
- const collectInvokedFunctions = (effectCallback, includePromiseCallbacks) => {
13655
+ const collectEffectInvokedFunctions = (effectCallback) => {
13665
13656
  const invokedFunctions = new Set([effectCallback]);
13666
13657
  const localFunctionBindings = /* @__PURE__ */ new Map();
13667
13658
  const calledBindingNames = /* @__PURE__ */ new Set();
@@ -13695,14 +13686,12 @@ const collectInvokedFunctions = (effectCallback, includePromiseCallbacks) => {
13695
13686
  calledBindingNames.add(callee.name);
13696
13687
  return;
13697
13688
  }
13698
- if (includePromiseCallbacks && isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
13689
+ if (isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
13699
13690
  });
13700
13691
  for (const calledName of calledBindingNames) enqueue(localFunctionBindings.get(calledName));
13701
13692
  }
13702
13693
  return invokedFunctions;
13703
13694
  };
13704
- const collectEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, true);
13705
- const collectSynchronouslyEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, false);
13706
13695
  //#endregion
13707
13696
  //#region src/plugin/utils/is-react-hook-name.ts
13708
13697
  const isReactHookName = (name) => {
@@ -13861,19 +13850,15 @@ const resolveReactRefSymbol = (memberExpression, scopes) => {
13861
13850
  if (!isNodeOfType(initializer, "CallExpression")) return null;
13862
13851
  return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
13863
13852
  };
13864
- const resolveReactRefCurrentOriginSymbol = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13853
+ const hasReactRefCurrentOrigin = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13865
13854
  const expression = stripParenExpression(node);
13866
- const refSymbol = resolveReactRefSymbol(expression, scopes);
13867
- if (refSymbol) return refSymbol;
13868
- if (!isNodeOfType(expression, "Identifier")) return null;
13869
- const symbol = scopes.symbolFor(expression);
13870
- if (!symbol || visitedSymbolIds.has(symbol.id)) return null;
13871
- const initializer = getDirectUnreassignedInitializer(symbol);
13872
- if (!initializer) return null;
13855
+ if (resolveReactRefSymbol(expression, scopes)) return true;
13856
+ if (!isNodeOfType(expression, "Identifier")) return false;
13857
+ const symbol = resolveConstIdentifierAlias(expression, scopes);
13858
+ if (!symbol?.initializer || visitedSymbolIds.has(symbol.id)) return false;
13873
13859
  visitedSymbolIds.add(symbol.id);
13874
- return resolveReactRefCurrentOriginSymbol(initializer, scopes, visitedSymbolIds);
13860
+ return hasReactRefCurrentOrigin(symbol.initializer, scopes, visitedSymbolIds);
13875
13861
  };
13876
- const hasReactRefCurrentOrigin = (node, scopes) => resolveReactRefCurrentOriginSymbol(node, scopes) !== null;
13877
13862
  //#endregion
13878
13863
  //#region src/plugin/utils/walk-inside-statement-blocks.ts
13879
13864
  const walkInsideStatementBlocks = (node, visitor) => {
@@ -13891,7 +13876,6 @@ const walkInsideStatementBlocks = (node, visitor) => {
13891
13876
  };
13892
13877
  //#endregion
13893
13878
  //#region src/plugin/rules/state-and-effects/utils/is-subscribe-like-call-expression.ts
13894
- const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
13895
13879
  const getSubscribeLikeMethodName = (node) => {
13896
13880
  if (!isNodeOfType(node, "CallExpression")) return null;
13897
13881
  if (!isNodeOfType(node.callee, "MemberExpression")) return null;
@@ -13902,11 +13886,6 @@ const isSubscribeLikeCallExpression = (node) => {
13902
13886
  const methodName = getSubscribeLikeMethodName(node);
13903
13887
  return methodName !== null && SUBSCRIPTION_METHOD_NAMES.has(methodName);
13904
13888
  };
13905
- const getSubscribeOrObserveMethodName = (node) => {
13906
- const methodName = getSubscribeLikeMethodName(node);
13907
- return methodName !== null && (SUBSCRIPTION_METHOD_NAMES.has(methodName) || methodName === "observe") ? methodName : null;
13908
- };
13909
- const isSubscribeOrObserveCallExpression = (node) => getSubscribeOrObserveMethodName(node) !== null;
13910
13889
  const isCleanupReturningSubscribeLikeCallExpression = (node) => {
13911
13890
  const methodName = getSubscribeLikeMethodName(node);
13912
13891
  if (methodName === null || !CLEANUP_RETURNING_SUBSCRIPTION_METHOD_NAMES.has(methodName)) return false;
@@ -13959,6 +13938,7 @@ const isNodeReachableWithinFunction = (node, context) => {
13959
13938
  };
13960
13939
  //#endregion
13961
13940
  //#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
13941
+ const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
13962
13942
  const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
13963
13943
  const REPLAYABLE_ITERATOR_COLLECTION_CACHE = /* @__PURE__ */ new WeakMap();
13964
13944
  const REACT_REF_EFFECT_ANALYSIS_CACHE = /* @__PURE__ */ new WeakMap();
@@ -13968,6 +13948,10 @@ const RESOURCE_NOUN_BY_KIND = {
13968
13948
  socket: "connection"
13969
13949
  };
13970
13950
  const isSocketConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && SOCKET_CONSTRUCTOR_NAMES_REQUIRING_CLEANUP.has(node.callee.name);
13951
+ const isSubscribeOrObserveCall = (node) => {
13952
+ if (isSubscribeLikeCallExpression(node)) return true;
13953
+ return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === OBSERVER_REGISTRATION_METHOD_NAME;
13954
+ };
13971
13955
  const resolveExpressionKey = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13972
13956
  if (!expression) return null;
13973
13957
  const unwrappedExpression = stripParenExpression(expression);
@@ -14069,13 +14053,12 @@ const findSubscribeLikeUsages = (callback, context) => {
14069
14053
  });
14070
14054
  return;
14071
14055
  }
14072
- const subscribeOrObserveMethodName = getSubscribeOrObserveMethodName(child);
14073
- if (subscribeOrObserveMethodName !== null) {
14056
+ if (isNodeOfType(child.callee, "MemberExpression") && isNodeOfType(child.callee.property, "Identifier") && (SUBSCRIPTION_METHOD_NAMES.has(child.callee.property.name) || child.callee.property.name === OBSERVER_REGISTRATION_METHOD_NAME)) {
14074
14057
  const registrationDetails = getCallRegistrationDetails(child, context);
14075
14058
  usages.push({
14076
14059
  kind: "subscribe",
14077
14060
  node: child,
14078
- resourceName: subscribeOrObserveMethodName,
14061
+ resourceName: child.callee.property.name,
14079
14062
  handleKey: findAssignedResourceKey(child, context),
14080
14063
  ...registrationDetails
14081
14064
  });
@@ -14357,39 +14340,6 @@ const findContainingCollectionKey = (resourceNode, context) => {
14357
14340
  }
14358
14341
  return null;
14359
14342
  };
14360
- const findPushedResourceCollectionKey = (usage, context) => {
14361
- if (!isNodeOfType(usage.node, "CallExpression")) return null;
14362
- const registrationCallee = stripParenExpression(usage.node.callee);
14363
- if (!isNodeOfType(registrationCallee, "MemberExpression") || registrationCallee.computed) return null;
14364
- const resourceIdentifier = stripParenExpression(registrationCallee.object);
14365
- if (!isPrivatePlainConstIdentifier(resourceIdentifier, context)) return null;
14366
- const resourceSymbol = context.scopes.symbolFor(resourceIdentifier);
14367
- if (!resourceSymbol) return null;
14368
- const pushCalls = resourceSymbol.references.flatMap((reference) => {
14369
- const referenceRoot = findTransparentExpressionRoot(reference.identifier);
14370
- const callNode = referenceRoot.parent;
14371
- if (!isNodeOfType(callNode, "CallExpression") || !callNode.arguments?.some((argument) => argument === referenceRoot)) return [];
14372
- const pushCallee = stripParenExpression(callNode.callee);
14373
- return isNodeOfType(pushCallee, "MemberExpression") && !pushCallee.computed && isNodeOfType(pushCallee.object, "Identifier") && isNodeOfType(pushCallee.property, "Identifier") && pushCallee.property.name === "push" ? [callNode] : [];
14374
- });
14375
- if (pushCalls.length !== 1) return null;
14376
- const pushCall = pushCalls[0];
14377
- if (findEnclosingFunction$1(pushCall) !== findEnclosingFunction$1(usage.node) || !doMatchingNodesCoverEveryPathAfterUsage(usage.node, [pushCall], context)) return null;
14378
- const pushCallee = stripParenExpression(pushCall.callee);
14379
- if (!isNodeOfType(pushCallee, "MemberExpression") || !isNodeOfType(pushCallee.object, "Identifier") || !isPrivatePlainConstIdentifier(pushCallee.object, context)) return null;
14380
- const collectionSymbol = context.scopes.symbolFor(pushCallee.object);
14381
- const collectionInitializer = collectionSymbol?.initializer ? stripParenExpression(collectionSymbol.initializer) : null;
14382
- if (!collectionSymbol || !isNodeOfType(collectionInitializer, "ArrayExpression") || (collectionInitializer.elements?.length ?? 0) !== 0 || findEnclosingFunction$1(collectionSymbol.declarationNode) !== findEnclosingFunction$1(usage.node)) return null;
14383
- return collectionSymbol.references.every((reference) => {
14384
- const referenceRoot = findTransparentExpressionRoot(reference.identifier);
14385
- const forOfStatement = referenceRoot.parent;
14386
- if (isNodeOfType(forOfStatement, "ForOfStatement") && forOfStatement.right === referenceRoot && forOfStatement.await !== true) return true;
14387
- const memberNode = referenceRoot.parent;
14388
- const callNode = memberNode?.parent;
14389
- if (!isNodeOfType(memberNode, "MemberExpression") || memberNode.object !== referenceRoot || memberNode.computed || !isNodeOfType(memberNode.property, "Identifier") || !isNodeOfType(callNode, "CallExpression") || callNode.callee !== memberNode) return false;
14390
- return memberNode.property.name === "forEach" || memberNode.property.name === "push";
14391
- }) ? resolveExpressionKey(pushCallee.object, context) : null;
14392
- };
14393
14343
  const isWithinAssignmentTarget = (identifier) => {
14394
14344
  let currentNode = identifier;
14395
14345
  let parentNode = currentNode.parent;
@@ -14448,14 +14398,6 @@ const isSynchronousIteratorCallback = (functionNode) => {
14448
14398
  if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments?.[1] === functionNode;
14449
14399
  return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments?.[0] === functionNode;
14450
14400
  };
14451
- const findEnclosingForEachCall = (node) => {
14452
- const callbackNode = findEnclosingFunction$1(node);
14453
- if (!callbackNode) return null;
14454
- const callNode = callbackNode.parent;
14455
- if (!isNodeOfType(callNode, "CallExpression") || callNode.arguments?.[0] !== callbackNode) return null;
14456
- const callee = stripParenExpression(callNode.callee);
14457
- return isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "forEach" ? callNode : null;
14458
- };
14459
14401
  const findDirectCallForReference = (identifier) => {
14460
14402
  const expressionRoot = findTransparentExpressionRoot(identifier);
14461
14403
  const callNode = expressionRoot.parent;
@@ -14470,7 +14412,7 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
14470
14412
  const callNode = findDirectCallForReference(reference.identifier);
14471
14413
  return callNode ? [callNode] : [];
14472
14414
  });
14473
- if (invocationCalls.length !== 1 || symbol.references.length !== 1) return null;
14415
+ if (invocationCalls.length !== 1) return null;
14474
14416
  const invocationCall = invocationCalls[0];
14475
14417
  return findEnclosingFunction$1(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
14476
14418
  };
@@ -14504,15 +14446,7 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
14504
14446
  if (cleanupChild !== cleanupFunction.body && isFunctionLike$1(cleanupChild) && !isSynchronousIteratorCallback(cleanupChild)) return false;
14505
14447
  const cleanupCall = isNodeOfType(cleanupChild, "ChainExpression") ? cleanupChild.expression : cleanupChild;
14506
14448
  if (doesReleaseCallMatchUsage(cleanupChild, usage, context)) {
14507
- const cleanupForEachCall = findEnclosingForEachCall(cleanupChild);
14508
- const cleanupCallee = isNodeOfType(cleanupCall, "CallExpression") ? stripParenExpression(cleanupCall.callee) : null;
14509
- const cleanupReceiverForOfStatement = isNodeOfType(cleanupCallee, "MemberExpression") ? findForOfStatementForIteratorExpression(cleanupCallee.object, context) : null;
14510
- const cleanupReceiverCollectionKey = cleanupReceiverForOfStatement ? resolveExpressionKey(cleanupReceiverForOfStatement.right, context) : isNodeOfType(cleanupCallee, "MemberExpression") ? resolveIteratorCollectionKey(cleanupCallee.object, context) : null;
14511
- if (cleanupReceiverCollectionKey !== null && findEnclosingFunction$1(cleanupChild) !== cleanupFunction) {
14512
- if (cleanupForEachCall && findPushedResourceCollectionKey(usage, context) === cleanupReceiverCollectionKey) matchingLoopOrHelperAnchors.push(cleanupForEachCall);
14513
- return;
14514
- }
14515
- const cleanupForOfStatement = findForOfStatementForIteratorExpression(isNodeOfType(cleanupCall, "CallExpression") ? cleanupCall.arguments?.[0] : null, context) ?? cleanupReceiverForOfStatement;
14449
+ const cleanupForOfStatement = findForOfStatementForIteratorExpression(isNodeOfType(cleanupCall, "CallExpression") ? cleanupCall.arguments?.[0] : null, context);
14516
14450
  if (!cleanupForOfStatement) {
14517
14451
  didCleanupFunctionMatch = true;
14518
14452
  return false;
@@ -14556,28 +14490,28 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
14556
14490
  });
14557
14491
  return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
14558
14492
  };
14559
- const doesTestRequireLiveExpressionKey = (test, expressionKey, context) => {
14560
- if (resolveExpressionKey(test, context) === expressionKey) return true;
14561
- const unwrappedTest = stripParenExpression(test);
14562
- if (!isNodeOfType(unwrappedTest, "BinaryExpression") || unwrappedTest.operator !== "!=" && unwrappedTest.operator !== "!==") return false;
14563
- const isNullishOperand = (operand) => {
14564
- const unwrappedOperand = stripParenExpression(operand);
14565
- return isNodeOfType(unwrappedOperand, "Literal") && unwrappedOperand.value === null || isNodeOfType(unwrappedOperand, "Identifier") && unwrappedOperand.name === "undefined" && context.scopes.isGlobalReference(unwrappedOperand);
14493
+ const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => {
14494
+ if (usage.handleKey === null) return null;
14495
+ const doesTestRequireLiveHandle = (test) => {
14496
+ if (resolveExpressionKey(test, context) === usage.handleKey) return true;
14497
+ const unwrappedTest = stripParenExpression(test);
14498
+ if (!isNodeOfType(unwrappedTest, "BinaryExpression") || unwrappedTest.operator !== "!=" && unwrappedTest.operator !== "!==") return false;
14499
+ const isNullishOperand = (operand) => {
14500
+ const unwrappedOperand = stripParenExpression(operand);
14501
+ return isNodeOfType(unwrappedOperand, "Literal") && unwrappedOperand.value === null || isNodeOfType(unwrappedOperand, "Identifier") && unwrappedOperand.name === "undefined" && context.scopes.isGlobalReference(unwrappedOperand);
14502
+ };
14503
+ return resolveExpressionKey(unwrappedTest.left, context) === usage.handleKey && isNullishOperand(unwrappedTest.right) || resolveExpressionKey(unwrappedTest.right, context) === usage.handleKey && isNullishOperand(unwrappedTest.left);
14566
14504
  };
14567
- return resolveExpressionKey(unwrappedTest.left, context) === expressionKey && isNullishOperand(unwrappedTest.right) || resolveExpressionKey(unwrappedTest.right, context) === expressionKey && isNullishOperand(unwrappedTest.left);
14568
- };
14569
- const findLiveExpressionGuardForRelease = (releaseCall, owner, expressionKey, context) => {
14570
14505
  let ancestor = releaseCall.parent;
14571
14506
  while (ancestor && ancestor !== owner) {
14572
14507
  if (isNodeOfType(ancestor, "IfStatement")) {
14573
- if (ancestor.alternate !== null || !doesTestRequireLiveExpressionKey(ancestor.test, expressionKey, context) || !doMatchingNodesCoverEveryPathAfterUsage(ancestor.consequent, [releaseCall], context)) return null;
14508
+ if (ancestor.alternate !== null || !doesTestRequireLiveHandle(ancestor.test) || !doMatchingNodesCoverEveryPathAfterUsage(ancestor.consequent, [releaseCall], context)) return null;
14574
14509
  return ancestor;
14575
14510
  }
14576
14511
  ancestor = ancestor.parent;
14577
14512
  }
14578
14513
  return null;
14579
14514
  };
14580
- const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => usage.handleKey === null ? null : findLiveExpressionGuardForRelease(releaseCall, owner, usage.handleKey, context);
14581
14515
  const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
14582
14516
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
14583
14517
  const functionCfg = context.cfg.cfgFor(callback);
@@ -14606,7 +14540,7 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
14606
14540
  walkAst(componentFunction.body, (child) => {
14607
14541
  if (didFindUnmountCleanup) return false;
14608
14542
  if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction) return;
14609
- if (!isReactHookCall(child, CLEANUP_EFFECT_HOOK_NAMES, context.scopes)) return;
14543
+ if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
14610
14544
  const dependencyList = child.arguments?.[1];
14611
14545
  if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
14612
14546
  const cleanupCallback = getEffectCallback(child);
@@ -14765,108 +14699,7 @@ const hasPotentialInterruptionAfterGuard = (callback, guardState, usageNode, con
14765
14699
  });
14766
14700
  return hasPotentialInterruption;
14767
14701
  };
14768
- const getNumericReactRefCurrentKey = (expression, context) => {
14769
- const refSymbol = resolveReactRefSymbol(stripParenExpression(expression), context.scopes);
14770
- const initializer = refSymbol?.initializer ? stripParenExpression(refSymbol.initializer) : null;
14771
- if (!isNodeOfType(initializer, "CallExpression")) return null;
14772
- const initialValue = initializer.arguments?.[0] ? stripParenExpression(initializer.arguments[0]) : null;
14773
- if (!isNodeOfType(initialValue, "Literal") || typeof initialValue.value !== "number") return null;
14774
- return resolveExpressionKey(expression, context);
14775
- };
14776
- const getBlockingGenerationKey = (expression, context) => {
14777
- const test = stripParenExpression(expression);
14778
- if (isNodeOfType(test, "LogicalExpression") && test.operator === "||") return getBlockingGenerationKey(test.left, context) ?? getBlockingGenerationKey(test.right, context);
14779
- if (!isNodeOfType(test, "BinaryExpression") || test.operator !== "!==" && test.operator !== "!=") return null;
14780
- const leftKey = getNumericReactRefCurrentKey(test.left, context);
14781
- const rightKey = getNumericReactRefCurrentKey(test.right, context);
14782
- const snapshotExpression = leftKey ? stripParenExpression(test.right) : stripParenExpression(test.left);
14783
- const key = leftKey ?? rightKey;
14784
- return key && isNodeOfType(snapshotExpression, "Identifier") ? key : null;
14785
- };
14786
- const findGenerationGuardKeyForDeferredUsage = (usageFunction, usageNode, context) => {
14787
- if (!isFunctionLike$1(usageFunction)) return null;
14788
- let generationKey = null;
14789
- walkAst(usageFunction.body, (child) => {
14790
- if (generationKey) return false;
14791
- if (child !== usageFunction.body && isFunctionLike$1(child)) return false;
14792
- if (!isNodeOfType(child, "IfStatement") || child.alternate) return;
14793
- const key = getBlockingGenerationKey(child.test, context);
14794
- if (!key || canNodeReachLaterNodeWithinFunction(child.consequent, usageNode, usageFunction, context) || !doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], usageFunction, context)) return;
14795
- generationKey = key;
14796
- });
14797
- return generationKey;
14798
- };
14799
- const isGenerationAdvance = (node, generationKey, context) => {
14800
- if (isNodeOfType(node, "UpdateExpression") && resolveExpressionKey(node.argument, context) === generationKey) return true;
14801
- if (!isNodeOfType(node, "AssignmentExpression") || resolveExpressionKey(node.left, context) !== generationKey || node.operator !== "+=" && node.operator !== "-=") return false;
14802
- const amount = stripParenExpression(node.right);
14803
- return isNodeOfType(amount, "Literal") && typeof amount.value === "number" && amount.value !== 0;
14804
- };
14805
- const functionAdvancesGeneration = (owner, generationKey, context) => {
14806
- if (!isFunctionLike$1(owner)) return false;
14807
- let didAdvanceGeneration = false;
14808
- walkAst(owner.body, (child) => {
14809
- if (didAdvanceGeneration) return false;
14810
- if (child !== owner.body && isFunctionLike$1(child)) return false;
14811
- if (isGenerationAdvance(child, generationKey, context)) {
14812
- didAdvanceGeneration = true;
14813
- return false;
14814
- }
14815
- });
14816
- return didAdvanceGeneration;
14817
- };
14818
- const cleanupReturnsReleaseUsage = (cleanupReturns, usage, context) => cleanupReturns.length > 0 && cleanupReturns.every((cleanupReturn) => {
14819
- if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
14820
- const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
14821
- return Boolean(cleanupFunction && isFunctionLike$1(cleanupFunction) && doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context));
14822
- });
14823
- const getOwnedFunctionReference = (reference, usageFunction, usageNode, callback, cleanupReturns, context) => {
14824
- const directCall = findDirectCallForReference(reference);
14825
- if (directCall) {
14826
- const referenceOwner = findEnclosingFunction$1(directCall);
14827
- if (referenceOwner && referenceOwner !== usageFunction && collectSynchronouslyEffectInvokedFunctions(callback).has(referenceOwner)) return { generationKey: null };
14828
- const generationKey = referenceOwner ? findGenerationGuardKeyForDeferredUsage(referenceOwner, directCall, context) : null;
14829
- return generationKey ? { generationKey } : null;
14830
- }
14831
- const referenceRoot = findTransparentExpressionRoot(reference);
14832
- const schedulerCall = referenceRoot.parent;
14833
- if (!isNodeOfType(schedulerCall, "CallExpression") || !schedulerCall.arguments.some((argument) => argument === referenceRoot) || !isNodeOfType(schedulerCall.callee, "Identifier") || schedulerCall.callee.name !== "setTimeout" || !context.scopes.isGlobalReference(schedulerCall.callee)) return null;
14834
- const schedulerUsage = {
14835
- kind: "timer",
14836
- node: schedulerCall,
14837
- resourceName: schedulerCall.callee.name,
14838
- handleKey: findAssignedResourceKey(schedulerCall, context),
14839
- receiverKey: null,
14840
- registrationVerbName: schedulerCall.callee.name,
14841
- eventKey: null,
14842
- handlerKey: null
14843
- };
14844
- const generationKey = findGenerationGuardKeyForDeferredUsage(usageFunction, usageNode, context);
14845
- return schedulerUsage.handleKey !== null && cleanupReturnsReleaseUsage(cleanupReturns, schedulerUsage, context) && generationKey ? { generationKey } : null;
14846
- };
14847
- const hasGuardedRefOwnedNestedCleanup = (callback, usage, cleanupReturns, context) => {
14848
- const usageFunction = findEnclosingFunction$1(usage.node);
14849
- const usageExpression = findTransparentExpressionRoot(usage.node);
14850
- const usageAssignment = usageExpression.parent;
14851
- if (usage.kind !== "subscribe" && usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usageAssignment, "AssignmentExpression") || usageAssignment.operator !== "=" || usageAssignment.right !== usageExpression || !resolveReactRefSymbol(stripParenExpression(usageAssignment.left), context.scopes) || !collectSynchronouslyEffectInvokedFunctions(callback).has(usageFunction) || !cleanupReturnsReleaseUsage(cleanupReturns, usage, context) || !doMatchingNodesCoverEveryPathFromFunctionEntry(callback, cleanupReturns, context)) return false;
14852
- const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
14853
- if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
14854
- const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
14855
- return cleanupFunction && isFunctionLike$1(cleanupFunction) ? [cleanupFunction] : [];
14856
- });
14857
- const bindingIdentifier = getFunctionBindingIdentifier$1(usageFunction);
14858
- const functionSymbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
14859
- if (!functionSymbol || functionSymbol.references.length === 0) return false;
14860
- const ownedReferences = functionSymbol.references.map((reference) => getOwnedFunctionReference(reference.identifier, usageFunction, usage.node, callback, cleanupReturns, context));
14861
- if (ownedReferences.some((reference) => reference === null)) return false;
14862
- const generationKeys = new Set(ownedReferences.flatMap((reference) => reference?.generationKey ? [reference.generationKey] : []));
14863
- if (generationKeys.size !== 1) return false;
14864
- const generationKey = generationKeys.values().next().value;
14865
- if (typeof generationKey !== "string") return false;
14866
- return [...collectSynchronouslyEffectInvokedFunctions(callback), ...cleanupFunctions].some((owner) => functionAdvancesGeneration(owner, generationKey, context));
14867
- };
14868
14702
  const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
14869
- if (hasGuardedRefOwnedNestedCleanup(callback, usage, cleanupReturns, context)) return true;
14870
14703
  const usageFunction = findEnclosingFunction$1(usage.node);
14871
14704
  const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
14872
14705
  if (usage.kind !== "timer" || usage.handleKey === null || !usageFunction || !isFunctionLike$1(usageFunction) || usageFunction === callback || usageFunction.async || usageFunction.generator || !isNodeOfType(usage.node, "CallExpression") || !isNodeOfType(usage.node.callee, "Identifier") || !context.scopes.isGlobalReference(usage.node.callee) || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
@@ -15037,7 +14870,7 @@ const getReleaseVerbName = (node) => {
15037
14870
  const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) => {
15038
14871
  const releaseFunction = findEnclosingFunction$1(releaseReceiver);
15039
14872
  const usageFunction = findEnclosingFunction$1(usage.node);
15040
- if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction, context) || !resolveReactRefCurrentOriginSymbol(releaseReceiver, context.scopes)) return false;
14873
+ if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction) || !hasReactRefCurrentOrigin(releaseReceiver, context.scopes)) return false;
15041
14874
  const controllerKey = getListenerAbortControllerKey(usage, context);
15042
14875
  const refCurrentKey = resolveExpressionKey(releaseReceiver, context);
15043
14876
  if (controllerKey === null || refCurrentKey === null) return false;
@@ -15057,62 +14890,6 @@ const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) =>
15057
14890
  const safeOwnershipAssignments = ownershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, previousAbortCalls, usageFunction, context));
15058
14891
  return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
15059
14892
  };
15060
- const isJsxRefAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && node.name.name === "ref";
15061
- const isFunctionForwardedToReactRef = (functionNode, context) => {
15062
- const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
15063
- if (!bindingIdentifier) return false;
15064
- const symbol = context.scopes.symbolFor(bindingIdentifier);
15065
- if (!symbol) return false;
15066
- return symbol.references.some((reference) => {
15067
- const referenceRoot = findTransparentExpressionRoot(reference.identifier);
15068
- const expressionContainer = referenceRoot.parent;
15069
- return Boolean(isNodeOfType(expressionContainer, "JSXExpressionContainer") && expressionContainer.expression === referenceRoot && isJsxRefAttribute(expressionContainer.parent));
15070
- });
15071
- };
15072
- const isFunctionReturnedFromReactHook = (functionNode, context, requireRefPropertyName) => {
15073
- const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
15074
- if (!bindingIdentifier) return false;
15075
- const symbol = context.scopes.symbolFor(bindingIdentifier);
15076
- if (!symbol) return false;
15077
- return symbol.references.some((reference) => {
15078
- const referenceRoot = findTransparentExpressionRoot(reference.identifier);
15079
- const property = referenceRoot.parent;
15080
- const propertyName = isNodeOfType(property, "Property") ? getStaticPropertyKeyName(property) : null;
15081
- if (!isNodeOfType(property, "Property") || property.value !== referenceRoot || !isNodeOfType(property.parent, "ObjectExpression") || requireRefPropertyName && propertyName !== "ref" && !propertyName?.endsWith("Ref")) return false;
15082
- const returnedObject = findTransparentExpressionRoot(property.parent);
15083
- const returnStatement = returnedObject.parent;
15084
- if (!isNodeOfType(returnStatement, "ReturnStatement") || returnStatement.argument !== returnedObject) return false;
15085
- const ownerFunction = findEnclosingFunction$1(returnStatement);
15086
- return Boolean(ownerFunction && isReactHookName(getFunctionBindingIdentifier$1(ownerFunction)?.name ?? ""));
15087
- });
15088
- };
15089
- const isFunctionUsedAsReactRef = (functionNode, context) => isFunctionForwardedToReactRef(functionNode, context) || isFunctionReturnedFromReactHook(functionNode, context, true);
15090
- const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
15091
- if (!isNodeOfType(usage.node, "CallExpression")) return false;
15092
- const usageFunction = findEnclosingFunction$1(usage.node);
15093
- if (!usageFunction || !isFunctionLike$1(usageFunction) || usageFunction !== findEnclosingFunction$1(releaseCall) || !isFunctionUsedAsReactRef(usageFunction, context)) return false;
15094
- const registrationCallee = stripParenExpression(usage.node.callee);
15095
- const releaseCallee = stripParenExpression(releaseCall.callee);
15096
- const releaseRefSymbol = isNodeOfType(releaseCallee, "MemberExpression") ? resolveReactRefCurrentOriginSymbol(releaseCallee.object, context.scopes) : null;
15097
- if (!isNodeOfType(registrationCallee, "MemberExpression") || registrationCallee.computed || !isNodeOfType(registrationCallee.property, "Identifier") || registrationCallee.property.name !== "addEventListener" || !isNodeOfType(releaseCallee, "MemberExpression") || releaseCallee.computed || !isNodeOfType(releaseCallee.property, "Identifier") || releaseCallee.property.name !== "removeEventListener" || !releaseRefSymbol) return false;
15098
- const registrationReceiverKey = resolveExpressionKey(stripParenExpression(registrationCallee.object), context);
15099
- const nodeParameterKey = resolveExpressionKey(usageFunction.params?.[0], context);
15100
- const releaseReceiverKey = resolveExpressionKey(releaseCallee.object, context);
15101
- if (registrationReceiverKey === null || registrationReceiverKey !== nodeParameterKey || releaseReceiverKey === null || usage.eventKey === null || usage.eventKey !== resolveExpressionKey(releaseCall.arguments?.[0], context) || usage.handlerKey === null || usage.handlerKey !== resolveExpressionKey(releaseCall.arguments?.[1], context)) return false;
15102
- const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
15103
- const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
15104
- if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
15105
- const releaseStart = getRangeStart(releaseCall);
15106
- const matchingOwnershipAssignments = [];
15107
- const usageFunctionBody = usageFunction.body;
15108
- walkAst(usageFunctionBody, (child) => {
15109
- if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
15110
- if (isNodeOfType(child, "AssignmentExpression") && child.operator === "=" && resolveReactRefSymbol(stripParenExpression(child.left), context.scopes)?.id === releaseRefSymbol.id && resolveExpressionKey(child.right, context) === registrationReceiverKey && releaseStart !== null && (getRangeStart(child) ?? -1) > releaseStart) matchingOwnershipAssignments.push(child);
15111
- });
15112
- const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
15113
- const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
15114
- return doMatchingNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
15115
- };
15116
14893
  const doesReleaseCallMatchUsage = (node, usage, context) => {
15117
14894
  const callNode = isNodeOfType(node, "ChainExpression") ? node.expression : node;
15118
14895
  if (!isNodeOfType(callNode, "CallExpression")) return false;
@@ -15129,21 +14906,14 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15129
14906
  if (!releaseVerbName) return false;
15130
14907
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
15131
14908
  const releaseReceiverKey = resolveExpressionKey(callee.object, context);
15132
- const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
15133
- const pairedReleaseVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
15134
- const pushedResourceCollectionKey = findPushedResourceCollectionKey(usage, context);
15135
- const releaseReceiverForOfStatement = findForOfStatementForIteratorExpression(callee.object, context);
15136
- const releaseReceiverCollectionKey = releaseReceiverForOfStatement ? resolveExpressionKey(releaseReceiverForOfStatement.right, context) : resolveIteratorCollectionKey(callee.object, context);
15137
- if (pairedReleaseVerbNames && matchesPairedReleaseVerb(releaseVerbName, pairedReleaseVerbNames) && pushedResourceCollectionKey !== null && pushedResourceCollectionKey === releaseReceiverCollectionKey && (releaseVerbName !== "unobserve" || usage.eventKey !== null && releaseEventKey === usage.eventKey)) return true;
15138
- if (isReactRefListenerReplacementRelease(callNode, usage, context)) return true;
15139
14909
  if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
15140
14910
  if (usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub" || releaseVerbName === "close" || releaseVerbName === "unwatch" || releaseVerbName === "unlisten" || BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(releaseVerbName))) return true;
15141
14911
  if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
15142
14912
  if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
15143
14913
  if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) return false;
15144
- if (usage.registrationVerbName === "subscribe" && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub") && usage.handleKey !== null && resolveExpressionKey(callNode.arguments?.[0], context) === usage.handleKey) return true;
15145
14914
  const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
15146
14915
  if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
14916
+ const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
15147
14917
  const usageEventArgument = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[0] : null;
15148
14918
  const releaseEventArgument = callNode.arguments?.[0];
15149
14919
  if (isAssignmentFormForOfIteratorReference(usageEventArgument, context) || isAssignmentFormForOfIteratorReference(releaseEventArgument, context)) return false;
@@ -15177,14 +14947,13 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15177
14947
  const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
15178
14948
  if (!releaseHandler) return releaseVerbName === "off";
15179
14949
  const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
15180
- const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignature ? 0 : 1] : null;
15181
- return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
14950
+ return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey;
15182
14951
  }
15183
14952
  if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
15184
14953
  return true;
15185
14954
  };
15186
14955
  const matchesPairedReleaseVerb = (releaseVerbName, pairedVerbNames) => pairedVerbNames.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName);
15187
- const isReturnedEffectCleanupFunction = (functionNode, context) => {
14956
+ const isReturnedEffectCleanupFunction = (functionNode) => {
15188
14957
  let currentNode = functionNode;
15189
14958
  let parentNode = currentNode.parent;
15190
14959
  while (isNodeOfType(parentNode, "ChainExpression") || isNodeOfType(parentNode, "TSAsExpression") || isNodeOfType(parentNode, "TSNonNullExpression")) {
@@ -15193,175 +14962,23 @@ const isReturnedEffectCleanupFunction = (functionNode, context) => {
15193
14962
  }
15194
14963
  const effectCallback = isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode ? findEnclosingFunction$1(parentNode) : isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode ? parentNode : null;
15195
14964
  const effectCall = effectCallback?.parent;
15196
- return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isReactHookCall(effectCall, CLEANUP_EFFECT_HOOK_NAMES, context.scopes));
14965
+ return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
15197
14966
  };
15198
14967
  const isPotentiallyReachableFunction = (functionNode, context) => {
15199
- if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode, context)) return true;
14968
+ if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode)) return true;
15200
14969
  const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
15201
14970
  if (!bindingIdentifier) return false;
15202
14971
  const symbol = context.scopes.symbolFor(bindingIdentifier);
15203
14972
  if (!symbol) return false;
15204
14973
  return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
15205
14974
  };
15206
- const findRetainedDisposerStorages = (disposerFunction, usage, context) => {
15207
- if (!isFunctionLike$1(disposerFunction) || disposerFunction.async || disposerFunction.generator) return [];
15208
- const usageFunction = findEnclosingFunction$1(usage.node);
15209
- if (!usageFunction || !isFunctionLike$1(usageFunction)) return [];
15210
- const assignments = /* @__PURE__ */ new Map();
15211
- const collectAssignment = (expression) => {
15212
- const expressionRoot = findTransparentExpressionRoot(expression);
15213
- const assignment = expressionRoot.parent;
15214
- if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.right !== expressionRoot) return;
15215
- const refSymbol = resolveReactRefSymbol(stripParenExpression(assignment.left), context.scopes);
15216
- const refCurrentKey = resolveExpressionKey(assignment.left, context);
15217
- const retainedFunction = findEnclosingFunction$1(assignment);
15218
- const assignmentStart = getRangeStart(assignment);
15219
- if (!refSymbol || !refCurrentKey || !retainedFunction || retainedFunction !== usageFunction || assignmentStart === null) return;
15220
- assignments.set(assignmentStart, {
15221
- assignmentNode: assignment,
15222
- refCurrentKey,
15223
- retainedFunction
15224
- });
15225
- };
15226
- collectAssignment(disposerFunction);
15227
- const bindingIdentifier = getFunctionBindingIdentifier$1(disposerFunction);
15228
- const symbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
15229
- for (const reference of symbol?.references ?? []) collectAssignment(reference.identifier);
15230
- walkAst(usageFunction.body, (child) => {
15231
- if (child !== usageFunction.body && isFunctionLike$1(child)) return false;
15232
- if (isNodeOfType(child, "AssignmentExpression") && resolveStableValue(child.right, context) === disposerFunction) collectAssignment(child.right);
15233
- });
15234
- return [...assignments.values()];
15235
- };
15236
- const isRetainedDisposerStorageEstablished = (storage, usage, context) => doMatchingNodesCoverEveryPathBeforeUsage(usage.node, [storage.assignmentNode], storage.retainedFunction, context) || doMatchingNodesCoverEveryPathAfterUsage(usage.node, [storage.assignmentNode], context);
15237
- const hasUnsafeRetainedDisposerOverwrite = (storage, usage, context) => {
15238
- let hasUnsafeOverwrite = false;
15239
- walkAst(storage.retainedFunction.body, (child) => {
15240
- if (hasUnsafeOverwrite) return false;
15241
- if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
15242
- if (!isNodeOfType(child, "AssignmentExpression") || child === storage.assignmentNode || resolveExpressionKey(child.left, context) !== storage.refCurrentKey || !canNodeReachLaterNodeWithinFunction(usage.node, child, storage.retainedFunction, context)) return;
15243
- const storedValue = resolveStableValue(child.right, context);
15244
- if (!storedValue || !isFunctionLike$1(storedValue) || !doesCleanupFunctionReleaseUsage(storedValue, usage, context)) {
15245
- hasUnsafeOverwrite = true;
15246
- return false;
15247
- }
15248
- });
15249
- return hasUnsafeOverwrite;
15250
- };
15251
- const hasEffectCleanupInvocation = (storage, usage, context) => {
15252
- const componentFunction = findEnclosingFunction$1(storage.retainedFunction);
15253
- if (!componentFunction || !isFunctionLike$1(componentFunction)) return false;
15254
- const cleanupFunctionInvokesRef = (cleanupFunction) => {
15255
- if (!isFunctionLike$1(cleanupFunction)) return false;
15256
- let didFindCleanupCall = false;
15257
- walkAst(cleanupFunction.body, (child) => {
15258
- if (didFindCleanupCall) return false;
15259
- if (child !== cleanupFunction.body && isFunctionLike$1(child)) return false;
15260
- if (isNodeOfType(child, "CallExpression") && resolveExpressionKey(child.callee, context) === storage.refCurrentKey) {
15261
- const callRoot = findTransparentExpressionRoot(child);
15262
- const callStatement = callRoot.parent;
15263
- const isDirectBlockStatement = isNodeOfType(cleanupFunction.body, "BlockStatement") && isNodeOfType(callStatement, "ExpressionStatement") && callStatement.parent === cleanupFunction.body;
15264
- const isConciseBody = cleanupFunction.body === callRoot;
15265
- if ((isDirectBlockStatement || isConciseBody) && !hasUnprovenReturnBeforeRefOwnedRelease(cleanupFunction, child, storage.refCurrentKey, context)) {
15266
- didFindCleanupCall = true;
15267
- return false;
15268
- }
15269
- }
15270
- });
15271
- return didFindCleanupCall;
15272
- };
15273
- const effectReturnsCleanup = (effectCallback) => {
15274
- if (!isFunctionLike$1(effectCallback)) return false;
15275
- if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
15276
- const cleanupFunction = resolveRefOwnedCleanupFunction(effectCallback.body, context);
15277
- return Boolean(cleanupFunction && cleanupFunctionInvokesRef(cleanupFunction));
15278
- }
15279
- const matchingReturns = [];
15280
- walkInsideStatementBlocks(effectCallback.body, (child) => {
15281
- if (!isNodeOfType(child, "ReturnStatement") || !child.argument) return;
15282
- const cleanupFunction = resolveRefOwnedCleanupFunction(child.argument, context);
15283
- if (!cleanupFunction || !cleanupFunctionInvokesRef(cleanupFunction)) return;
15284
- matchingReturns.push(child);
15285
- });
15286
- return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15287
- };
15288
- let didFindInvocation = false;
15289
- walkAst(componentFunction.body, (child) => {
15290
- if (didFindInvocation) return false;
15291
- if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
15292
- const effectCallback = getEffectCallback(child);
15293
- if (effectCallback && effectReturnsCleanup(effectCallback)) {
15294
- didFindInvocation = true;
15295
- return false;
15296
- }
15297
- });
15298
- return didFindInvocation;
15299
- };
15300
- const hasCallbackRefReplacementInvocation = (storage, usage, context) => {
15301
- const isReturnedCallbackRefShape = () => {
15302
- if (!isFunctionLike$1(storage.retainedFunction)) return false;
15303
- const callbackCall = findTransparentExpressionRoot(storage.retainedFunction).parent;
15304
- if (!isNodeOfType(callbackCall, "CallExpression") || !isReactApiCall(callbackCall, "useCallback", context.scopes)) return false;
15305
- const nodeParameter = storage.retainedFunction.params?.[0];
15306
- const nodeParameterKey = resolveExpressionKey(nodeParameter, context);
15307
- if (!nodeParameterKey || usage.receiverKey !== nodeParameterKey) return false;
15308
- if (!isFunctionReturnedFromReactHook(storage.retainedFunction, context, false)) return false;
15309
- const usageStart = getRangeStart(usage.node);
15310
- if (usageStart === null) return false;
15311
- let hasNullExit = false;
15312
- walkAst(storage.retainedFunction.body, (child) => {
15313
- if (hasNullExit) return false;
15314
- if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
15315
- if (!isNodeOfType(child, "IfStatement") || (getRangeStart(child) ?? usageStart) >= usageStart) return;
15316
- const test = stripParenExpression(child.test);
15317
- if (!isNodeOfType(test, "UnaryExpression") || test.operator !== "!" || resolveExpressionKey(test.argument, context) !== nodeParameterKey) return;
15318
- const consequent = child.consequent;
15319
- hasNullExit = isNodeOfType(consequent, "ReturnStatement") || isNodeOfType(consequent, "BlockStatement") && consequent.body.some((statement) => isNodeOfType(statement, "ReturnStatement"));
15320
- if (hasNullExit) return false;
15321
- });
15322
- return hasNullExit;
15323
- };
15324
- if (!isFunctionForwardedToReactRef(storage.retainedFunction, context) && !isReturnedCallbackRefShape()) return false;
15325
- const cleanupCalls = [];
15326
- walkAst(storage.retainedFunction.body, (child) => {
15327
- if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
15328
- if (isNodeOfType(child, "CallExpression") && resolveExpressionKey(child.callee, context) === storage.refCurrentKey) cleanupCalls.push(child);
15329
- });
15330
- return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, cleanupCalls, storage.retainedFunction, context);
15331
- };
15332
- const isRetainedDisposerRefRelease = (releaseNode, usage, context) => {
15333
- const disposerFunction = findEnclosingFunction$1(releaseNode);
15334
- if (!disposerFunction) return false;
15335
- return findRetainedDisposerStorages(disposerFunction, usage, context).some((storage) => isRetainedDisposerStorageEstablished(storage, usage, context) && !hasUnsafeRetainedDisposerOverwrite(storage, usage, context) && (hasEffectCleanupInvocation(storage, usage, context) || hasCallbackRefReplacementInvocation(storage, usage, context)));
15336
- };
15337
- const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
15338
- if (usage.kind !== "subscribe" || usage.registrationVerbName !== "addEventListener" || usage.receiverKey === null || usage.eventKey === null || !isNodeOfType(usage.node, "CallExpression") || !isFunctionLike$1(releaseFunction) || releaseFunction.async || releaseFunction.generator || !isNodeOfType(releaseFunction.body, "BlockStatement") || !doMatchingNodesCoverEveryPathFromFunctionEntry(releaseFunction, [releaseNode], context)) return false;
15339
- const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
15340
- const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
15341
- if (!isNodeOfType(releaseCall, "CallExpression")) return false;
15342
- const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
15343
- if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
15344
- const ownerFunction = findEnclosingFunction$1(releaseFunction);
15345
- if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
15346
- const triggerRegistrations = [];
15347
- walkAst(ownerFunction.body, (child) => {
15348
- if (child !== ownerFunction.body && isFunctionLike$1(child)) return false;
15349
- if (!isNodeOfType(child, "CallExpression")) return;
15350
- const registrationDetails = getCallRegistrationDetails(child, context);
15351
- if (registrationDetails.registrationVerbName === "addEventListener" && registrationDetails.receiverKey === usage.receiverKey && resolveStableValue(child.arguments?.[1], context) === releaseFunction) triggerRegistrations.push(child);
15352
- });
15353
- if (triggerRegistrations.some((triggerRegistration) => triggerRegistration === usage.node)) return true;
15354
- return doMatchingNodesCoverEveryPathAfterUsage(usage.node, triggerRegistrations, context) || doMatchingNodesCoverEveryPathBeforeUsage(usage.node, triggerRegistrations, ownerFunction, context);
15355
- };
15356
14975
  const isReleaseReachableForUsage = (releaseNode, usage, context) => {
15357
14976
  if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
15358
14977
  const releaseFunction = findEnclosingFunction$1(releaseNode);
15359
14978
  if (!releaseFunction) return true;
15360
14979
  if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
15361
- if (isRetainedDisposerRefRelease(releaseNode, usage, context)) return true;
15362
14980
  const usageFunction = findEnclosingFunction$1(usage.node);
15363
14981
  if (usageFunction && isFunctionLike$1(usageFunction) && getAssignedReactRefSymbol(usageFunction, context) && isCleanupFunctionReferencedByReturn(usageFunction, releaseFunction, context)) return isReactRefCallbackCleanupOwnedByEffect(usageFunction, releaseFunction, usage, context);
15364
- if (isSelfReleasingListenerRelease(releaseNode, releaseFunction, usage, context)) return true;
15365
14982
  return isPotentiallyReachableFunction(releaseFunction, context);
15366
14983
  };
15367
14984
  const fileContainsReleaseForUsage = (usage, context) => {
@@ -15693,7 +15310,7 @@ const findRetainedFunctionLeak = (retainedFunction, context, options) => {
15693
15310
  return false;
15694
15311
  }
15695
15312
  }
15696
- if (isSubscribeOrObserveCallExpression(child) && (!doesResourceResultEscape(child, allowReturnedResourceEscape, allowReturnedResourceEscape, context) || options?.requireCallableReturnedResource === true && !isCleanupReturningSubscribeLikeCallExpression(child))) {
15313
+ if (isSubscribeOrObserveCall(child) && (!doesResourceResultEscape(child, allowReturnedResourceEscape, allowReturnedResourceEscape, context) || options?.requireCallableReturnedResource === true && !isCleanupReturningSubscribeLikeCallExpression(child))) {
15697
15314
  const registrationDetails = getCallRegistrationDetails(child, context);
15698
15315
  const subscriptionUsage = {
15699
15316
  kind: "subscribe",
@@ -15901,7 +15518,7 @@ const isInlineRetainedHandlerFunction = (functionNode, context) => {
15901
15518
  if (!isFunctionLike$1(functionNode)) return false;
15902
15519
  const functionRoot = findTransparentExpressionRoot(functionNode);
15903
15520
  const callbackCall = functionRoot.parent;
15904
- if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot && isReactHookCall(callbackCall, "useCallback", context.scopes) && isDirectJsxEventHandlerValue(callbackCall)) return true;
15521
+ if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot && isHookCall$2(callbackCall, "useCallback") && isDirectJsxEventHandlerValue(callbackCall)) return true;
15905
15522
  const parentNode = functionNode.parent;
15906
15523
  if (isDirectJsxEventHandlerValue(functionNode)) return true;
15907
15524
  if (!isNodeOfType(parentNode, "Property") || parentNode.value !== functionNode || parentNode.computed) return false;
@@ -15937,12 +15554,12 @@ const effectNeedsCleanup = defineRule({
15937
15554
  };
15938
15555
  return {
15939
15556
  CallExpression(node) {
15940
- if (isReactHookCall(node, "useCallback", context.scopes)) {
15557
+ if (isHookCall$2(node, "useCallback")) {
15941
15558
  const retainedCallback = getEffectCallback(node);
15942
15559
  if (retainedCallback && !isInlineRetainedHandlerFunction(retainedCallback, context)) reportRetainedLeak(retainedCallback);
15943
15560
  return;
15944
15561
  }
15945
- if (!isReactHookCall(node, CLEANUP_EFFECT_HOOK_NAMES, context.scopes)) return;
15562
+ if (!isHookCall$2(node, CLEANUP_EFFECT_HOOK_NAMES)) return;
15946
15563
  const callback = getEffectCallback(node);
15947
15564
  if (!callback) return;
15948
15565
  const usages = removeSynchronouslyReleasedUsages(callback, findSubscribeLikeUsages(callback, context), context);
@@ -15950,7 +15567,7 @@ const effectNeedsCleanup = defineRule({
15950
15567
  const firstUsage = findFirstUsageWithoutCleanup(callback, usages, context);
15951
15568
  if (!firstUsage) return;
15952
15569
  const resourceNoun = RESOURCE_NOUN_BY_KIND[firstUsage.kind];
15953
- const hookName = getCalleeName$1(node) ?? "effect";
15570
+ const hookName = getCalleeName$2(node) ?? "effect";
15954
15571
  context.report({
15955
15572
  node,
15956
15573
  message: `\`${firstUsage.resourceName}\` creates a ${resourceNoun} in ${hookName} without guaranteed cleanup. Return a cleanup function that owns every allocation so it does not leak after unmount.`
@@ -17293,38 +16910,7 @@ const symbolHasStableImportedAlias = (symbol, scopes) => {
17293
16910
  const resolvedSymbol = resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes);
17294
16911
  return resolvedSymbol !== null && resolvedSymbol !== symbol && resolvedSymbol.kind === "import";
17295
16912
  };
17296
- const isAssignmentTarget = (node) => {
17297
- let currentNode = findTransparentExpressionRoot(node);
17298
- while (currentNode.parent) {
17299
- const parentNode = currentNode.parent;
17300
- if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
17301
- if (isNodeOfType(parentNode, "UpdateExpression")) return parentNode.argument === currentNode;
17302
- if (isNodeOfType(parentNode, "UnaryExpression")) return parentNode.operator === "delete" && parentNode.argument === currentNode;
17303
- if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
17304
- if (isNodeOfType(parentNode, "ArrayPattern") && parentNode.elements.some((element) => element === currentNode) || isNodeOfType(parentNode, "ObjectPattern") && parentNode.properties.some((property) => property === currentNode) || isNodeOfType(parentNode, "RestElement") && parentNode.argument === currentNode || isNodeOfType(parentNode, "AssignmentPattern") && parentNode.left === currentNode || isNodeOfType(parentNode, "Property") && parentNode.value === currentNode && isNodeOfType(parentNode.parent, "ObjectPattern")) {
17305
- currentNode = parentNode;
17306
- continue;
17307
- }
17308
- return false;
17309
- }
17310
- return false;
17311
- };
17312
- const symbolHasStableRefLazyInitialization = (symbol, scopes) => {
17313
- if (symbol.kind !== "const" || symbol.references.some((reference) => reference.flag !== "read")) return false;
17314
- const initializer = symbol.initializer ? unwrapExpression$3(symbol.initializer) : null;
17315
- if (!isNodeOfType(initializer, "AssignmentExpression") || initializer.operator !== "??=") return false;
17316
- const refSymbol = resolveReactRefSymbol(unwrapExpression$3(initializer.left), scopes);
17317
- if (!refSymbol) return false;
17318
- return refSymbol.references.every((reference) => {
17319
- const memberExpression = findTransparentExpressionRoot(reference.identifier).parent;
17320
- if (!isNodeOfType(memberExpression, "MemberExpression") || unwrapExpression$3(memberExpression.object) !== reference.identifier || getStaticPropertyName(memberExpression) !== "current") return false;
17321
- const referenceRoot = findTransparentExpressionRoot(memberExpression);
17322
- const parentNode = referenceRoot.parent;
17323
- if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.left === referenceRoot) return parentNode === initializer;
17324
- return !isAssignmentTarget(referenceRoot);
17325
- });
17326
- };
17327
- const symbolHasStableValue = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => symbolHasStableHookOrigin(symbol, scopes) || symbolHasStableRefLazyInitialization(symbol, scopes) || symbolHasStableImportedAlias(symbol, scopes) || symbolHasStableFunctionOrigin(symbol, scopes, visitedSymbolIds) || symbolHasStableMemoizedOrigin(symbol, scopes, visitedSymbolIds);
16913
+ const symbolHasStableValue = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => symbolHasStableHookOrigin(symbol, scopes) || symbolHasStableImportedAlias(symbol, scopes) || symbolHasStableFunctionOrigin(symbol, scopes, visitedSymbolIds) || symbolHasStableMemoizedOrigin(symbol, scopes, visitedSymbolIds);
17328
16914
  //#endregion
17329
16915
  //#region src/plugin/utils/symbol-has-react-use-effect-event-origin.ts
17330
16916
  const symbolHasReactUseEffectEventOrigin = (symbol, scopes) => {
@@ -17526,7 +17112,7 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allow
17526
17112
  keys.add(depKey);
17527
17113
  continue;
17528
17114
  }
17529
- const identitySourceKeys = resolvePureCalledFunctionSourceKeys(reference, symbol, scopes) ?? resolveRenderDerivedMutableSourceKeys(reference, symbol, scopes) ?? resolveReactiveIdentitySourceKeys(symbol, scopes);
17115
+ const identitySourceKeys = resolveReactiveIdentitySourceKeys(symbol, scopes);
17530
17116
  if (identitySourceKeys) {
17531
17117
  if (identitySourceKeys.size === 0) stableCapturedNames.add(depKey);
17532
17118
  for (const identitySourceKey of identitySourceKeys) keys.add(identitySourceKey);
@@ -17609,161 +17195,6 @@ const resolveReactiveIdentitySourceKeys = (symbol, scopes) => {
17609
17195
  if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier || symbol.references.some((reference) => reference.flag !== "read")) return null;
17610
17196
  return resolveIdentitySourceKeysFromExpression(symbol.initializer, scopes, new Set([symbol.id]));
17611
17197
  };
17612
- const isPureDerivedExpression = (expression) => {
17613
- const candidate = unwrapExpression$3(expression);
17614
- if (isNodeOfType(candidate, "Literal") || isNodeOfType(candidate, "Identifier")) return true;
17615
- if (isNodeOfType(candidate, "MemberExpression")) return isPureDerivedExpression(candidate.object) && (!candidate.computed || isPureDerivedExpression(candidate.property));
17616
- if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return isPureDerivedExpression(candidate.left) && isPureDerivedExpression(candidate.right);
17617
- if (isNodeOfType(candidate, "UnaryExpression")) return candidate.operator !== "delete" && isPureDerivedExpression(candidate.argument);
17618
- if (isNodeOfType(candidate, "ConditionalExpression")) return isPureDerivedExpression(candidate.test) && isPureDerivedExpression(candidate.consequent) && isPureDerivedExpression(candidate.alternate);
17619
- if (isNodeOfType(candidate, "TemplateLiteral")) return candidate.expressions.every((nestedExpression) => isPureDerivedExpression(nestedExpression));
17620
- return false;
17621
- };
17622
- const isPureDerivedStatement = (statement) => {
17623
- if (isNodeOfType(statement, "BlockStatement")) return statement.body.every((nestedStatement) => isPureDerivedStatement(nestedStatement));
17624
- if (isNodeOfType(statement, "ReturnStatement")) return !statement.argument || isPureDerivedExpression(statement.argument);
17625
- if (isNodeOfType(statement, "IfStatement")) return isPureDerivedExpression(statement.test) && isPureDerivedStatement(statement.consequent) && (!statement.alternate || isPureDerivedStatement(statement.alternate));
17626
- return false;
17627
- };
17628
- const isPureDerivedFunction = (functionNode) => {
17629
- if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return false;
17630
- if (functionNode.async || functionNode.generator) return false;
17631
- return isNodeOfType(functionNode.body, "BlockStatement") ? isPureDerivedStatement(functionNode.body) : isPureDerivedExpression(functionNode.body);
17632
- };
17633
- const resolvePureCalledFunctionSourceKeys = (reference, symbol, scopes) => {
17634
- if (symbol.references.some((symbolReference) => symbolReference.flag !== "read")) return null;
17635
- const referenceRoot = findTransparentExpressionRoot(reference.identifier);
17636
- const callExpression = referenceRoot.parent;
17637
- if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== referenceRoot) return null;
17638
- const functionNode = getFunctionValueNode(symbol);
17639
- if (!functionNode || !isPureDerivedFunction(functionNode)) return null;
17640
- const sourceKeys = /* @__PURE__ */ new Set();
17641
- for (const capturedReference of closureCaptures(functionNode, scopes)) {
17642
- const capturedSymbol = capturedReference.resolvedSymbol;
17643
- if (!capturedSymbol || capturedSymbol.id === symbol.id) continue;
17644
- if (isOutsideAllFunctions(capturedSymbol) || symbolHasStableValue(capturedSymbol, scopes)) continue;
17645
- const capturedKey = computeDepKey(capturedReference);
17646
- if (!capturedKey) return null;
17647
- if (capturedKey === capturedSymbol.name) {
17648
- const nestedSourceKeys = resolveReactiveIdentitySourceKeys(capturedSymbol, scopes);
17649
- if (nestedSourceKeys) {
17650
- for (const nestedSourceKey of nestedSourceKeys) sourceKeys.add(nestedSourceKey);
17651
- continue;
17652
- }
17653
- }
17654
- sourceKeys.add(capturedKey);
17655
- }
17656
- return sourceKeys.size > 0 ? sourceKeys : null;
17657
- };
17658
- const mergeDerivedExpressionSourceKeys = (expressions, scopes, visitedSymbolIds) => {
17659
- const sourceKeys = /* @__PURE__ */ new Set();
17660
- for (const expression of expressions) {
17661
- const expressionSourceKeys = resolveDerivedExpressionSourceKeys(expression, scopes, visitedSymbolIds);
17662
- if (!expressionSourceKeys) return null;
17663
- for (const expressionSourceKey of expressionSourceKeys) sourceKeys.add(expressionSourceKey);
17664
- }
17665
- return sourceKeys;
17666
- };
17667
- const resolveDerivedExpressionSourceKeys = (expression, scopes, visitedSymbolIds) => {
17668
- const candidate = unwrapExpression$3(expression);
17669
- if (isNodeOfType(candidate, "Literal")) return /* @__PURE__ */ new Set();
17670
- if (isNodeOfType(candidate, "Identifier")) {
17671
- if (scopes.isGlobalReference(candidate)) return /* @__PURE__ */ new Set();
17672
- const sourceSymbol = scopes.symbolFor(candidate);
17673
- if (!sourceSymbol) return null;
17674
- if (isOutsideAllFunctions(sourceSymbol) || symbolHasStableValue(sourceSymbol, scopes)) return /* @__PURE__ */ new Set();
17675
- if (sourceSymbol.kind === "const" && sourceSymbol.initializer && isNodeOfType(sourceSymbol.declarationNode, "VariableDeclarator") && sourceSymbol.declarationNode.id === sourceSymbol.bindingIdentifier && sourceSymbol.references.every((sourceReference) => sourceReference.flag === "read") && !visitedSymbolIds.has(sourceSymbol.id)) {
17676
- visitedSymbolIds.add(sourceSymbol.id);
17677
- const sourceKeys = resolveDerivedExpressionSourceKeys(sourceSymbol.initializer, scopes, visitedSymbolIds);
17678
- visitedSymbolIds.delete(sourceSymbol.id);
17679
- if (sourceKeys) return sourceKeys;
17680
- }
17681
- return new Set([sourceSymbol.name]);
17682
- }
17683
- if (isNodeOfType(candidate, "MemberExpression")) {
17684
- if (hasComputedMemberExpression(candidate)) return null;
17685
- const sourceKey = stringifyMemberChain(candidate);
17686
- const rootIdentifier = getMemberRootIdentifier(candidate);
17687
- const rootSymbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
17688
- if (!sourceKey || !rootSymbol) return null;
17689
- if (isOutsideAllFunctions(rootSymbol) || symbolHasStableValue(rootSymbol, scopes)) return /* @__PURE__ */ new Set();
17690
- return new Set([sourceKey]);
17691
- }
17692
- if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return mergeDerivedExpressionSourceKeys([candidate.left, candidate.right], scopes, visitedSymbolIds);
17693
- if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator !== "delete") return resolveDerivedExpressionSourceKeys(candidate.argument, scopes, visitedSymbolIds);
17694
- if (isNodeOfType(candidate, "ConditionalExpression")) return mergeDerivedExpressionSourceKeys([
17695
- candidate.test,
17696
- candidate.consequent,
17697
- candidate.alternate
17698
- ], scopes, visitedSymbolIds);
17699
- if (isNodeOfType(candidate, "TemplateLiteral")) return mergeDerivedExpressionSourceKeys(candidate.expressions, scopes, visitedSymbolIds);
17700
- if (isNodeOfType(candidate, "NewExpression")) {
17701
- const callee = unwrapExpression$3(candidate.callee);
17702
- if (!isNodeOfType(callee, "Identifier") || callee.name !== "Error" || !scopes.isGlobalReference(callee)) return null;
17703
- const argumentsToAnalyze = [];
17704
- for (const argument of candidate.arguments) {
17705
- if (!isAstNode(argument) || isNodeOfType(argument, "SpreadElement")) return null;
17706
- argumentsToAnalyze.push(argument);
17707
- }
17708
- return mergeDerivedExpressionSourceKeys(argumentsToAnalyze, scopes, visitedSymbolIds);
17709
- }
17710
- return null;
17711
- };
17712
- const resolveWriteControlSourceKeys = (assignment, boundaryFunction, scopes) => {
17713
- const sourceKeys = /* @__PURE__ */ new Set();
17714
- let currentNode = assignment;
17715
- while (currentNode.parent && currentNode.parent !== boundaryFunction) {
17716
- const parentNode = currentNode.parent;
17717
- if (isNodeOfType(parentNode, "IfStatement")) {
17718
- if (parentNode.test === currentNode) return null;
17719
- const testSourceKeys = resolveDerivedExpressionSourceKeys(parentNode.test, scopes, /* @__PURE__ */ new Set());
17720
- if (!testSourceKeys) return null;
17721
- for (const testSourceKey of testSourceKeys) sourceKeys.add(testSourceKey);
17722
- } else if (!isNodeOfType(parentNode, "ExpressionStatement") && !isNodeOfType(parentNode, "BlockStatement")) return null;
17723
- currentNode = parentNode;
17724
- }
17725
- return currentNode.parent === boundaryFunction ? sourceKeys : null;
17726
- };
17727
- const isReadOnlyInitialStateUse = (referenceNode, scopes) => {
17728
- const referenceRoot = findTransparentExpressionRoot(referenceNode);
17729
- const callExpression = referenceRoot.parent;
17730
- return isNodeOfType(callExpression, "CallExpression") && callExpression.arguments.some((argument) => argument === referenceRoot) && isReactApiCall(callExpression, "useState", scopes, {
17731
- allowGlobalReactNamespace: true,
17732
- allowUnboundBareCalls: true,
17733
- resolveNamedAliases: true
17734
- });
17735
- };
17736
- const resolveRenderDerivedMutableSourceKeys = (capturedReference, symbol, scopes) => {
17737
- if (symbol.kind !== "let" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
17738
- const boundaryFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
17739
- if (!boundaryFunction) return null;
17740
- const capturingFunction = findEnclosingFunction$1(capturedReference.identifier);
17741
- if (!capturingFunction || capturingFunction === boundaryFunction) return null;
17742
- const sourceKeys = /* @__PURE__ */ new Set();
17743
- if (symbol.initializer) {
17744
- const initializerSourceKeys = resolveDerivedExpressionSourceKeys(symbol.initializer, scopes, new Set([symbol.id]));
17745
- if (!initializerSourceKeys) return null;
17746
- for (const initializerSourceKey of initializerSourceKeys) sourceKeys.add(initializerSourceKey);
17747
- }
17748
- let writeCount = 0;
17749
- for (const symbolReference of symbol.references) {
17750
- if (symbolReference.flag === "read") {
17751
- if (findEnclosingFunction$1(symbolReference.identifier) !== capturingFunction && !isReadOnlyInitialStateUse(symbolReference.identifier, scopes)) return null;
17752
- continue;
17753
- }
17754
- if (symbolReference.flag !== "write") return null;
17755
- const referenceRoot = findTransparentExpressionRoot(symbolReference.identifier);
17756
- const assignment = referenceRoot.parent;
17757
- if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== referenceRoot || findEnclosingFunction$1(referenceRoot) !== boundaryFunction) return null;
17758
- const assignmentSourceKeys = resolveDerivedExpressionSourceKeys(assignment.right, scopes, new Set([symbol.id]));
17759
- const controlSourceKeys = resolveWriteControlSourceKeys(assignment, boundaryFunction, scopes);
17760
- if (!assignmentSourceKeys || !controlSourceKeys) return null;
17761
- for (const assignmentSourceKey of assignmentSourceKeys) sourceKeys.add(assignmentSourceKey);
17762
- for (const controlSourceKey of controlSourceKeys) sourceKeys.add(controlSourceKey);
17763
- writeCount += 1;
17764
- }
17765
- return writeCount > 0 && sourceKeys.size > 0 ? sourceKeys : null;
17766
- };
17767
17198
  const isUseCallbackResultDep = (node, scopes) => {
17768
17199
  const rootSymbol = getRootSymbol(node, scopes);
17769
17200
  const initializer = rootSymbol?.initializer ? unwrapExpression$3(rootSymbol.initializer) : null;
@@ -18507,7 +17938,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
18507
17938
  if (!isUsed) continue;
18508
17939
  const reportNode = declaredKeyToReportNode.get(declaredKey) ?? depsArgument;
18509
17940
  const rootSymbol = getRootSymbol(reportNode, context.scopes);
18510
- if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) || symbolHasStableValue(rootSymbol, context.scopes) || !isUnstableInitializer(rootSymbol.initializer)) continue;
17941
+ if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) || !isUnstableInitializer(rootSymbol.initializer)) continue;
18511
17942
  context.report({
18512
17943
  node: reportNode,
18513
17944
  message: buildUnstableDepMessage(hookName, declaredKey)
@@ -18823,7 +18254,7 @@ const flattenCalleeName = (callee) => {
18823
18254
  const PRAGMA = "React";
18824
18255
  const isReactFunctionCall = (node, expectedCall) => {
18825
18256
  if (!isNodeOfType(node, "CallExpression")) return false;
18826
- if (getCalleeName$1(node) !== expectedCall) return false;
18257
+ if (getCalleeName$2(node) !== expectedCall) return false;
18827
18258
  if (isNodeOfType(node.callee, "MemberExpression")) {
18828
18259
  const receiver = stripParenExpression(node.callee.object);
18829
18260
  return isNodeOfType(receiver, "Identifier") && receiver.name === PRAGMA;
@@ -19021,14 +18452,10 @@ const hookUseState = defineRule({
19021
18452
  create: (context) => {
19022
18453
  const { allowDestructuredState } = resolveSettings$40(context.settings);
19023
18454
  return { CallExpression(node) {
19024
- const callExpressionNode = node;
19025
- if (!isReactFunctionCall(callExpressionNode, "useState")) return;
19026
- const expressionRoot = findTransparentExpressionRoot(callExpressionNode);
19027
- const expressionParent = expressionRoot.parent;
19028
- if (isNodeOfType(expressionParent, "ReturnStatement")) return;
19029
- if (isNodeOfType(expressionParent, "ArrowFunctionExpression") && expressionParent.body === expressionRoot) return;
18455
+ if (!isReactFunctionCall(node, "useState")) return;
19030
18456
  const parent = node.parent;
19031
18457
  if (!parent) return;
18458
+ if (isNodeOfType(parent, "ReturnStatement")) return;
19032
18459
  if (!isNodeOfType(parent, "VariableDeclarator")) {
19033
18460
  context.report({
19034
18461
  node,
@@ -19136,8 +18563,8 @@ const hooksNoNanInDeps = defineRule({
19136
18563
  severity: "warn",
19137
18564
  recommendation: "Remove `NaN` (or `Number.NaN`) from the dependency array. If a value can be NaN at runtime, normalise it (`Number.isNaN(x) ? 0 : x`) before passing it.",
19138
18565
  create: (context) => ({ CallExpression(node) {
19139
- if (!isReactHookCall(node, HOOKS_WITH_DEP_ARRAY, context.scopes)) return;
19140
- const depsIndex = isReactHookCall(node, "useImperativeHandle", context.scopes) ? 2 : 1;
18566
+ if (!isHookCall$2(node, HOOKS_WITH_DEP_ARRAY)) return;
18567
+ const depsIndex = getCalleeName$2(node) === "useImperativeHandle" ? 2 : 1;
19141
18568
  const depsArgument = node.arguments[depsIndex];
19142
18569
  if (!depsArgument || !isNodeOfType(depsArgument, "ArrayExpression")) return;
19143
18570
  for (const element of depsArgument.elements) {
@@ -20308,7 +19735,7 @@ const isImportedSelectAtom = (callExpression) => {
20308
19735
  const isDeferredCallbackPosition$1 = (functionNode) => {
20309
19736
  const parent = functionNode.parent;
20310
19737
  if (isNodeOfType(parent, "CallExpression") && parent.arguments?.[0] === functionNode) {
20311
- const hookName = getCalleeName$1(parent);
19738
+ const hookName = getCalleeName$2(parent);
20312
19739
  if (hookName && MEMOIZING_HOOK_NAMES$1.has(hookName)) return true;
20313
19740
  if (hookName && EFFECT_HOOK_NAMES$1.has(hookName) && Boolean(parent.arguments?.[1])) return true;
20314
19741
  }
@@ -30379,7 +29806,7 @@ const DEFERRING_CALLEE_NAMES$1 = new Set([
30379
29806
  "on",
30380
29807
  "once"
30381
29808
  ]);
30382
- const getCalleeName = (callee) => {
29809
+ const getCalleeName$1 = (callee) => {
30383
29810
  if (!callee) return null;
30384
29811
  if (isNodeOfType(callee, "Identifier")) return callee.name;
30385
29812
  if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
@@ -30391,11 +29818,11 @@ const isDeferredCallbackPosition = (expression) => {
30391
29818
  const parent = parentOf(expression);
30392
29819
  if (!parent) return false;
30393
29820
  if (isNodeOfType(parent, "CallExpression") && argumentsInclude(parent.arguments, expression)) {
30394
- const name = getCalleeName(parent.callee);
29821
+ const name = getCalleeName$1(parent.callee);
30395
29822
  if (name && DEFERRING_CALLEE_NAMES$1.has(name)) return true;
30396
29823
  }
30397
29824
  if (isNodeOfType(parent, "NewExpression") && argumentsInclude(parent.arguments, expression)) {
30398
- const name = getCalleeName(parent.callee);
29825
+ const name = getCalleeName$1(parent.callee);
30399
29826
  if (name && (name.endsWith("Observer") || name === "Promise")) return true;
30400
29827
  }
30401
29828
  if (isNodeOfType(parent, "AssignmentExpression") && parent.right === expression && isNodeOfType(parent.left, "MemberExpression") && isNodeOfType(parent.left.property, "Identifier") && parent.left.property.name.startsWith("on")) return true;
@@ -30785,12 +30212,6 @@ const isReactNamespaceImportReference = (ref) => Boolean(ref?.resolved?.defs.som
30785
30212
  const importDeclaration = declarationNode.parent;
30786
30213
  return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
30787
30214
  }));
30788
- const isReactNamespaceReceiver = (analysis, node) => {
30789
- const receiver = stripParenExpression(node);
30790
- if (!isNodeOfType(receiver, "Identifier")) return false;
30791
- const namespaceReference = getRef(analysis, receiver);
30792
- return namespaceReference?.resolved ? isReactNamespaceImportReference(namespaceReference) : receiver.name === "React";
30793
- };
30794
30215
  const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
30795
30216
  if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return false;
30796
30217
  const callee = stripParenExpression(declarator.init.callee);
@@ -30799,22 +30220,34 @@ const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
30799
30220
  if (!reference?.resolved) return callee.name === hookName;
30800
30221
  return isReactNamedImportReference(reference, hookName);
30801
30222
  }
30802
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
30803
- return isReactNamespaceReceiver(analysis, callee.object);
30223
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
30224
+ const namespaceReference = getRef(analysis, callee.object);
30225
+ if (!namespaceReference?.resolved) return callee.object.name === "React";
30226
+ return isReactNamespaceImportReference(namespaceReference);
30804
30227
  };
30805
30228
  const isHookCallee$1 = (analysis, node, hookName) => {
30806
30229
  if (!node) return false;
30807
30230
  if (isNodeOfType(node, "Identifier")) {
30808
30231
  if (node.name === hookName) return true;
30809
30232
  if (isReactNamedImportReference(getRef(analysis, node), hookName)) return true;
30810
- const receiverRoot = findTransparentExpressionRoot(node);
30811
- const parent = receiverRoot.parent;
30812
- if (parent && isNodeOfType(parent, "MemberExpression") && parent.object === receiverRoot && isReactNamespaceReceiver(analysis, node) && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
30233
+ const parent = node.parent;
30234
+ if (parent && isNodeOfType(parent, "MemberExpression") && isNodeOfType(parent.object, "Identifier") && parent.object.name === "React" && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
30813
30235
  return false;
30814
30236
  }
30815
- if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
30237
+ if (isNodeOfType(node, "MemberExpression")) {
30238
+ const receiver = stripParenExpression(node.object);
30239
+ return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
30240
+ }
30816
30241
  return false;
30817
30242
  };
30243
+ const isUseEffect = (node) => {
30244
+ if (!node || !isNodeOfType(node, "CallExpression")) return false;
30245
+ const callee = node.callee;
30246
+ if (isNodeOfType(callee, "Identifier") && callee.name === "useEffect") return true;
30247
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
30248
+ const receiver = stripParenExpression(callee.object);
30249
+ return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useEffect";
30250
+ };
30818
30251
  const getEffectFn = (analysis, node) => {
30819
30252
  if (!isNodeOfType(node, "CallExpression")) return null;
30820
30253
  const fn = node.arguments?.[0];
@@ -30912,27 +30345,7 @@ const isRefCurrent = (ref) => {
30912
30345
  if (!isNodeOfType(parent.property, "Identifier")) return false;
30913
30346
  return parent.property.name === "current";
30914
30347
  };
30915
- const resolveStateSetterReference = (analysis, ref) => {
30916
- const visitedReferences = /* @__PURE__ */ new Set();
30917
- let currentReference = ref;
30918
- while (currentReference && !visitedReferences.has(currentReference)) {
30919
- if (isStateSetter(analysis, currentReference)) return currentReference;
30920
- visitedReferences.add(currentReference);
30921
- const definitions = currentReference.resolved?.defs ?? [];
30922
- if (definitions.length !== 1) return null;
30923
- const definitionNode = definitions[0].node;
30924
- if (!isNodeOfType(definitionNode, "VariableDeclarator")) return null;
30925
- if (!isNodeOfType(definitionNode.id, "Identifier")) return null;
30926
- const declaration = definitionNode.parent;
30927
- if (!isNodeOfType(declaration, "VariableDeclaration") || declaration.kind !== "const") return null;
30928
- if (!definitionNode.init) return null;
30929
- const initializer = stripParenExpression(definitionNode.init);
30930
- if (!isNodeOfType(initializer, "Identifier")) return null;
30931
- currentReference = getRef(analysis, initializer);
30932
- }
30933
- return null;
30934
- };
30935
- const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => resolveStateSetterReference(analysis, innerRef) !== null);
30348
+ const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isStateSetter(analysis, innerRef));
30936
30349
  const isSyncStateSetterCall = (analysis, ref, effectFn) => isStateSetterCall(analysis, ref) && isSynchronous(ref.identifier, effectFn) && !resolvesToAsyncFunction(ref);
30937
30350
  const HANDLER_NAMED_METHOD_PATTERN = /^(on|handle)[A-Z]/;
30938
30351
  const SYNCHRONOUS_CALLBACK_ARGUMENT_INDEX_BY_METHOD = new Map([
@@ -31083,11 +30496,9 @@ const isPropCallbackInvocationRef = (analysis, ref, options = {}) => {
31083
30496
  };
31084
30497
  const isRefCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isRefCurrent(innerRef) || isRef(analysis, innerRef));
31085
30498
  const getUseStateDecl = (analysis, ref) => {
31086
- const definition = getUpstreamRefs(analysis, ref).find((upstreamReference) => isState(analysis, upstreamReference) || isStateSetter(analysis, upstreamReference))?.resolved?.defs.find((candidateDefinition) => {
31087
- const definitionNode = candidateDefinition.node;
31088
- return isNodeOfType(definitionNode, "VariableDeclarator") && isNodeOfType(definitionNode.init, "CallExpression") && isHookCallee$1(analysis, definitionNode.init.callee, "useState");
31089
- });
31090
- return definition ? definition.node : null;
30499
+ let node = getUpstreamRefs(analysis, ref).find((upRef) => isHookCallee$1(analysis, upRef.identifier, "useState"))?.identifier;
30500
+ while (node && !isNodeOfType(node, "VariableDeclarator")) node = node.parent;
30501
+ return node ?? null;
31091
30502
  };
31092
30503
  const isCleanupReturnArgument = (analysis, node) => {
31093
30504
  if (isFunctionLike$1(node)) return true;
@@ -31250,88 +30661,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
31250
30661
  if (HANDLER_BINDING_NAME_PATTERN.test(bindingName)) return true;
31251
30662
  return isSetterWiredToJsxHandler(componentFunction, bindingName);
31252
30663
  };
31253
- const isSynchronousFunction = (functionNode) => {
31254
- const functionMetadata = functionNode;
31255
- return functionMetadata.async !== true && functionMetadata.generator !== true;
31256
- };
31257
- const findBindingVariable = (analysis, bindingIdentifier) => {
31258
- for (const scope of analysis.scopeManager.scopes) for (const variable of scope.variables) if (variable.identifiers.includes(bindingIdentifier)) return variable;
31259
- return null;
31260
- };
31261
- const getImmutableFunctionVariable = (analysis, componentFunction, functionNode) => {
31262
- if (!isSynchronousFunction(functionNode) || !isAstDescendant(functionNode, componentFunction)) return null;
31263
- const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
31264
- if (!bindingIdentifier) return null;
31265
- const variable = findBindingVariable(analysis, bindingIdentifier);
31266
- if (!variable || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && !reference.init)) return null;
31267
- const definition = variable.defs[0];
31268
- if (definition.type === "FunctionName") return definition.node === functionNode ? variable : null;
31269
- if (definition.type !== "Variable") return null;
31270
- const declarator = definition.node;
31271
- if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return null;
31272
- if (declarator.init === functionNode) return variable;
31273
- if (isNodeOfType(declarator.init, "CallExpression") && declarator.init.arguments?.[0] === functionNode && isGenuineReactHookDeclarator(analysis, declarator, "useCallback")) return variable;
31274
- return null;
31275
- };
31276
- const getJsxEventValueAttribute = (identifier) => {
31277
- const expression = findTransparentExpressionRoot(identifier);
31278
- const expressionContainer = expression.parent;
31279
- if (!isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== expression) return null;
31280
- const attribute = expressionContainer.parent;
31281
- if (!isNodeOfType(attribute, "JSXAttribute")) return null;
31282
- const attributeName = getJsxAttributeName(attribute.name);
31283
- return attributeName && isEventHandlerName(attributeName) ? attribute : null;
31284
- };
31285
- const getInlineJsxEventCallbackAttribute = (callExpression) => {
31286
- const callbackFunction = findEnclosingFunction$1(callExpression);
31287
- if (!callbackFunction || !isSynchronousFunction(callbackFunction)) return null;
31288
- return getJsxEventValueAttribute(callbackFunction);
31289
- };
31290
- const isReactHookDependencyReference = (identifier) => {
31291
- const expression = findTransparentExpressionRoot(identifier);
31292
- const dependencyArray = expression.parent;
31293
- if (!isNodeOfType(dependencyArray, "ArrayExpression") || !(dependencyArray.elements ?? []).includes(expression)) return false;
31294
- const hookCall = dependencyArray.parent;
31295
- if (!isNodeOfType(hookCall, "CallExpression") || hookCall.arguments?.[1] !== dependencyArray) return false;
31296
- const callee = hookCall.callee;
31297
- if (isNodeOfType(callee, "Identifier")) return /^use[A-Z0-9]/.test(callee.name);
31298
- return Boolean(isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && /^use[A-Z0-9]/.test(callee.property.name));
31299
- };
31300
- const hasReachableJsxEventCallPath = (analysis, context, componentFunction, functionVariable, visitedVariables) => {
31301
- if (visitedVariables.has(functionVariable)) return false;
31302
- const nextVisitedVariables = new Set(visitedVariables).add(functionVariable);
31303
- const callExpressions = [];
31304
- let hasDirectJsxEventReference = false;
31305
- for (const reference of functionVariable.references) {
31306
- if (reference.init) continue;
31307
- const identifier = reference.identifier;
31308
- if (reference.isWrite()) return false;
31309
- const jsxEventValueAttribute = getJsxEventValueAttribute(identifier);
31310
- if (jsxEventValueAttribute) {
31311
- if (isNodeReachableWithinFunction(jsxEventValueAttribute, context)) hasDirectJsxEventReference = true;
31312
- continue;
31313
- }
31314
- if (isReactHookDependencyReference(identifier)) continue;
31315
- const callExpression = getCallExpr(reference);
31316
- if (!callExpression) return false;
31317
- const jsxEventCallbackAttribute = getInlineJsxEventCallbackAttribute(callExpression);
31318
- if (jsxEventCallbackAttribute) {
31319
- if (isNodeReachableWithinFunction(callExpression, context) && isNodeReachableWithinFunction(jsxEventCallbackAttribute, context)) hasDirectJsxEventReference = true;
31320
- continue;
31321
- }
31322
- callExpressions.push(callExpression);
31323
- }
31324
- if (hasDirectJsxEventReference) return true;
31325
- for (const callExpression of callExpressions) {
31326
- if (!isNodeReachableWithinFunction(callExpression, context)) continue;
31327
- const callerFunction = findEnclosingFunction$1(callExpression);
31328
- if (!callerFunction || callerFunction === componentFunction) continue;
31329
- const callerVariable = getImmutableFunctionVariable(analysis, componentFunction, callerFunction);
31330
- if (callerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, callerVariable, nextVisitedVariables)) return true;
31331
- }
31332
- return false;
31333
- };
31334
- const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, includeDeferredWriters = false) => {
30664
+ const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
31335
30665
  if (!setterRef.resolved) return false;
31336
30666
  const componentFunction = findEnclosingFunction$1(effectNode);
31337
30667
  if (!componentFunction) return false;
@@ -31340,11 +30670,6 @@ const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, incl
31340
30670
  const identifier = reference.identifier;
31341
30671
  if (isAstDescendant(identifier, effectNode)) continue;
31342
30672
  if (isIndependentWriterIdentifier(componentFunction, identifier, includeDeferredWriters)) return true;
31343
- if (!isNodeReachableWithinFunction(identifier, context)) continue;
31344
- const writerFunction = findEnclosingFunction$1(identifier);
31345
- if (!writerFunction || writerFunction === componentFunction) continue;
31346
- const writerVariable = getImmutableFunctionVariable(analysis, componentFunction, writerFunction);
31347
- if (writerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, writerVariable, /* @__PURE__ */ new Set())) return true;
31348
30673
  }
31349
30674
  return false;
31350
30675
  };
@@ -32234,7 +31559,7 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
32234
31559
  }
32235
31560
  return false;
32236
31561
  };
32237
- const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFilename) => {
31562
+ const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
32238
31563
  const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
32239
31564
  if (frames.length === 0) return [];
32240
31565
  const effectHasCleanup = hasCleanup(analysis, effectNode);
@@ -32264,7 +31589,7 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
32264
31589
  for (const returnedExpression of returnedExpressions) mergeEvidence(valueEvidence, collectValueEvidence(analysis, returnedExpression, updaterFrame, remainingValueCallFrames));
32265
31590
  } else valueEvidence = collectValueEvidence(analysis, writtenValue, frame, remainingValueCallFrames);
32266
31591
  const sourceReferences = [...valueEvidence.sourceReferences].filter((sourceReference) => getUseStateDecl(analysis, sourceReference) !== stateDeclarator);
32267
- const hasIndependentWriter = hasUserInputSetterWriter(analysis, context, setterReference, effectNode, true);
31592
+ const hasIndependentWriter = hasUserInputSetterWriter(setterReference, effectNode, true);
32268
31593
  const doesMatchStateInitializer = matchesStateInitializer(analysis, callExpression, stateDeclarator);
32269
31594
  if (effectHasCleanup && (frame.isDeferred || valueEvidence.hasUnknownSource || valueEvidence.hasDeferredIntroducedValue || valueEvidence.readsExternalValue)) cleanupManagedStateDeclarators.add(stateDeclarator);
32270
31595
  const isRenderKnownCopy = sourceReferences.length > 0 && !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue && !hasIndependentWriter;
@@ -32276,7 +31601,6 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
32276
31601
  sourceReferences,
32277
31602
  isDeferred: frame.isDeferred,
32278
31603
  isRenderKnownCopy,
32279
- isSynchronousRenderValue: !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue,
32280
31604
  matchesStateInitializer: doesMatchStateInitializer,
32281
31605
  resetsSourceState: false
32282
31606
  });
@@ -32292,71 +31616,22 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
32292
31616
  });
32293
31617
  };
32294
31618
  //#endregion
32295
- //#region src/plugin/rules/state-and-effects/utils/has-deferred-or-external-effect-work.ts
32296
- const DEFERRED_MEMBER_NAMES = new Set([
32297
- "catch",
32298
- "finally",
32299
- "then"
32300
- ]);
32301
- const hasDeferredOrExternalEffectWork = (analysis, effectNode, scopes) => {
32302
- const effectFunction = getEffectFn(analysis, effectNode);
32303
- if (!effectFunction) return false;
32304
- if (containsFetchCall(effectFunction, { stopAtFunctionBoundary: true })) return true;
32305
- const effectInvokedFunctions = collectEffectInvokedFunctions(effectFunction);
32306
- let didFindDeferredOrExternalWork = false;
32307
- walkAst(effectFunction, (child) => {
32308
- if (didFindDeferredOrExternalWork) return false;
32309
- if (child !== effectFunction && isFunctionLike$1(child) && !effectInvokedFunctions.has(child)) return false;
32310
- if (isNodeOfType(child, "AssignmentExpression")) {
32311
- const assignmentTarget = child.left;
32312
- if ((isNodeOfType(assignmentTarget, "MemberExpression") ? getStaticPropertyName(assignmentTarget) : null)?.startsWith("on") && isFunctionLike$1(child.right)) {
32313
- didFindDeferredOrExternalWork = true;
32314
- return false;
32315
- }
32316
- }
32317
- if (!isNodeOfType(child, "CallExpression")) return;
32318
- if (isSubscribeOrObserveCallExpression(child)) {
32319
- didFindDeferredOrExternalWork = true;
32320
- return false;
32321
- }
32322
- const localFunction = resolveExactLocalFunction(child.callee, scopes);
32323
- if (isFunctionLike$1(localFunction) && localFunction.async) {
32324
- didFindDeferredOrExternalWork = true;
32325
- return false;
32326
- }
32327
- const callee = child.callee;
32328
- if (isNodeOfType(callee, "Identifier") && TIMER_AND_SCHEDULER_DIRECT_CALLEE_NAMES.has(callee.name)) {
32329
- didFindDeferredOrExternalWork = true;
32330
- return false;
32331
- }
32332
- const memberName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
32333
- if (memberName && DEFERRED_MEMBER_NAMES.has(memberName)) {
32334
- didFindDeferredOrExternalWork = true;
32335
- return false;
32336
- }
32337
- });
32338
- return didFindDeferredOrExternalWork;
32339
- };
32340
- //#endregion
32341
31619
  //#region src/plugin/rules/state-and-effects/no-adjust-state-on-prop-change.ts
32342
31620
  const noAdjustStateOnPropChange = defineRule({
32343
31621
  id: "no-adjust-state-on-prop-change",
32344
- title: "State adjusted after a prop changes",
31622
+ title: "State synced to a prop inside an effect",
32345
31623
  severity: "warn",
32346
31624
  tags: ["test-noise"],
32347
- recommendation: "Remove the adjustment effect by deriving values during render, resetting the component with a key, or updating related state in the event that changes the prop. Avoid tracking the previous prop in more state, which preserves the duplication. See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes",
31625
+ recommendation: "Adjust the state inline during render with a `prev`-prop comparison (`if (prop !== prevProp) { setPrevProp(prop); setX(...); }`), or refactor to remove the duplicated state. Routing the adjustment through a useEffect forces an extra render with a stale UI between the two commits. See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes",
32348
31626
  create: (context) => ({ CallExpression(node) {
32349
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
31627
+ if (!isUseEffect(node)) return;
32350
31628
  const analysis = getProgramAnalysis(node);
32351
31629
  if (!analysis) return;
32352
31630
  const dependencyReferences = getEffectDepsRefs(analysis, node);
32353
31631
  if (!dependencyReferences) return;
32354
31632
  if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
32355
- const facts = collectEffectStateWriteFacts(analysis, context, node, context.filename);
32356
- if (hasCleanup(analysis, node) || hasDeferredOrExternalEffectWork(analysis, node, context.scopes) || facts.some((fact) => fact.isDeferred)) return;
32357
- for (const fact of facts) {
32358
- if (!fact.isSynchronousRenderValue || fact.resetsSourceState) continue;
32359
- if (fact.sourceReferences.flatMap((reference) => getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) continue;
31633
+ for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
31634
+ if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
32360
31635
  context.report({
32361
31636
  node: fact.callExpression,
32362
31637
  message: "This effect adjusts state after a prop changes, so users briefly see the stale value."
@@ -34369,7 +33644,7 @@ const declarationBodyContainsHookCall = (symbol) => {
34369
33644
  walkAst(componentFunction, (descendant) => {
34370
33645
  if (didFindHookCall) return false;
34371
33646
  if (!isNodeOfType(descendant, "CallExpression")) return;
34372
- const calleeName = getCalleeName$1(descendant);
33647
+ const calleeName = getCalleeName$2(descendant);
34373
33648
  if (calleeName && isReactHookName(calleeName)) {
34374
33649
  didFindHookCall = true;
34375
33650
  return false;
@@ -34394,7 +33669,7 @@ const isReturnedFromUseCallbackAdapter = (callNode) => {
34394
33669
  if (isNodeOfType(parent, "ArrowFunctionExpression")) {
34395
33670
  if (parent.body !== current) return false;
34396
33671
  const grandparent = parent.parent;
34397
- return isNodeOfType(grandparent, "CallExpression") && getCalleeName$1(grandparent) === "useCallback" && grandparent.arguments.some((argumentNode) => argumentNode === parent);
33672
+ return isNodeOfType(grandparent, "CallExpression") && getCalleeName$2(grandparent) === "useCallback" && grandparent.arguments.some((argumentNode) => argumentNode === parent);
34398
33673
  }
34399
33674
  if (!isNodeOfType(parent, "ConditionalExpression") && !isNodeOfType(parent, "LogicalExpression")) return false;
34400
33675
  current = parent;
@@ -35076,7 +34351,7 @@ const noChainStateUpdates = defineRule({
35076
34351
  if (!callExpr) continue;
35077
34352
  if (!isReachableFromStateTrigger(callExpr)) continue;
35078
34353
  if (!readsPostMountValueThroughLocals(callExpr, effectFn, { ignoreBareRefCurrent: true })) continue;
35079
- const declarator = getUseStateDeclarator(resolveStateSetterReference(analysis, ref) ?? ref);
34354
+ const declarator = getUseStateDeclarator(ref);
35080
34355
  if (declarator) domSyncedStateDeclarators.add(declarator);
35081
34356
  }
35082
34357
  for (const ref of effectFnRefs) {
@@ -35085,7 +34360,7 @@ const noChainStateUpdates = defineRule({
35085
34360
  if (!callExpr) continue;
35086
34361
  if (!isReachableFromStateTrigger(callExpr)) continue;
35087
34362
  if (getArgsUpstreamRefs(analysis, ref).some((argRef) => isState(analysis, argRef))) continue;
35088
- const setterDeclarator = getUseStateDeclarator(resolveStateSetterReference(analysis, ref) ?? ref);
34363
+ const setterDeclarator = getUseStateDeclarator(ref);
35089
34364
  if (setterDeclarator && domSyncedStateDeclarators.has(setterDeclarator)) continue;
35090
34365
  const isSelfTargeting = setterDeclarator !== null && stateDepDeclarators.has(setterDeclarator);
35091
34366
  const setterArguments = isNodeOfType(callExpr, "CallExpression") ? callExpr.arguments ?? [] : [];
@@ -36926,10 +36201,10 @@ const noDerivedState = defineRule({
36926
36201
  for (const fact of collectRenderStateWriteFacts(analysis, componentBody, context.filename)) reportStateWrite(fact.callExpression, fact.stateDeclarator);
36927
36202
  } }).visitors,
36928
36203
  CallExpression(node) {
36929
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
36204
+ if (!isUseEffect(node)) return;
36930
36205
  const analysis = getProgramAnalysis(node);
36931
36206
  if (!analysis) return;
36932
- for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
36207
+ for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
36933
36208
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
36934
36209
  reportStateWrite(fact.callExpression, fact.stateDeclarator);
36935
36210
  }
@@ -36946,10 +36221,10 @@ const noDerivedStateEffect = defineRule({
36946
36221
  tags: ["test-noise"],
36947
36222
  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
36223
  create: (context) => ({ CallExpression(node) {
36949
- if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes)) return;
36224
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
36950
36225
  const analysis = getProgramAnalysis(node);
36951
36226
  if (!analysis) return;
36952
- if (!collectEffectStateWriteFacts(analysis, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
36227
+ if (!collectEffectStateWriteFacts(analysis, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
36953
36228
  context.report({
36954
36229
  node,
36955
36230
  message: "You pay an extra render for state you can derive from other values."
@@ -37069,7 +36344,7 @@ const getEnclosingEffectHookCallback = (node, componentFunction) => {
37069
36344
  if (isFunctionLike$1(cursor)) {
37070
36345
  const parent = cursor.parent ?? null;
37071
36346
  if (parent && isNodeOfType(parent, "CallExpression")) {
37072
- const calleeName = getCalleeName$1(parent);
36347
+ const calleeName = getCalleeName$2(parent);
37073
36348
  if (calleeName !== null && EFFECT_HOOK_NAME_PATTERN.test(calleeName) && (parent.arguments ?? []).some((argument) => argument === cursor)) return cursor;
37074
36349
  }
37075
36350
  }
@@ -37140,7 +36415,7 @@ const isNonHandlerHookCallback = (functionNode) => {
37140
36415
  const parent = functionNode.parent ?? null;
37141
36416
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
37142
36417
  if (!(parent.arguments ?? []).some((argument) => argument === functionNode)) return false;
37143
- const calleeName = getCalleeName$1(parent);
36418
+ const calleeName = getCalleeName$2(parent);
37144
36419
  return calleeName !== null && isReactHookName(calleeName) && calleeName !== "useCallback";
37145
36420
  };
37146
36421
  const isHandlerShapedReseed = (setterCall, componentFunction) => {
@@ -37222,7 +36497,7 @@ const noDerivedUseState = defineRule({
37222
36497
  return {
37223
36498
  ...propStackTracker.visitors,
37224
36499
  CallExpression(node) {
37225
- if (!isReactHookCall(node, "useState", context.scopes) || !node.arguments?.length) return;
36500
+ if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
37226
36501
  const seed = unwrapInitializerSeed(node.arguments[0]);
37227
36502
  const reportStalePropCopy = (propName) => {
37228
36503
  if (isIntentionalSnapshotState(node)) return;
@@ -37898,7 +37173,7 @@ const noDirectMutationState = defineRule({
37898
37173
  const isSetterIdentifier = (name) => SETTER_PATTERN.test(name);
37899
37174
  //#endregion
37900
37175
  //#region src/plugin/rules/state-and-effects/utils/collect-use-state-bindings.ts
37901
- const collectUseStateBindings = (componentBody, scopes) => {
37176
+ const collectUseStateBindings = (componentBody) => {
37902
37177
  const bindings = [];
37903
37178
  if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
37904
37179
  for (const statement of componentBody.body ?? []) {
@@ -37911,7 +37186,7 @@ const collectUseStateBindings = (componentBody, scopes) => {
37911
37186
  const setterElement = elements[1];
37912
37187
  if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
37913
37188
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
37914
- if (!isReactHookCall(declarator.init, "useState", scopes)) continue;
37189
+ if (!isHookCall$2(declarator.init, "useState")) continue;
37915
37190
  bindings.push({
37916
37191
  valueName: valueElement.name,
37917
37192
  setterName: setterElement.name,
@@ -38078,7 +37353,7 @@ const noDirectStateMutation = defineRule({
38078
37353
  create: (context) => {
38079
37354
  const checkComponent = (componentBody) => {
38080
37355
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
38081
- const bindings = collectUseStateBindings(componentBody, context.scopes);
37356
+ const bindings = collectUseStateBindings(componentBody);
38082
37357
  if (bindings.length === 0) return;
38083
37358
  const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
38084
37359
  const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
@@ -38638,31 +37913,25 @@ const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSub
38638
37913
  };
38639
37914
  //#endregion
38640
37915
  //#region src/plugin/rules/state-and-effects/no-effect-chain.ts
38641
- const findTopLevelEffectCalls = (componentBody, scopes) => {
37916
+ const findTopLevelEffectCalls = (componentBody) => {
38642
37917
  const effectCalls = [];
38643
37918
  if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
38644
37919
  for (const statement of componentBody.body ?? []) {
38645
37920
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
38646
37921
  const expression = unwrapDiscardedExpression(statement);
38647
37922
  if (!isNodeOfType(expression, "CallExpression")) continue;
38648
- if (!isReactHookCall(expression, EFFECT_HOOK_NAMES$1, scopes)) continue;
37923
+ if (!isHookCall$2(expression, EFFECT_HOOK_NAMES$1)) continue;
38649
37924
  effectCalls.push(expression);
38650
37925
  }
38651
37926
  return effectCalls;
38652
37927
  };
38653
- const collectDependencyStateSymbolIds = (effectNode, stateSymbolIds, scopes) => {
38654
- const dependencyStateSymbolIds = /* @__PURE__ */ new Set();
38655
- if (!isNodeOfType(effectNode, "CallExpression")) return dependencyStateSymbolIds;
37928
+ const collectDepIdentifierNames = (effectNode) => {
37929
+ const depNames = /* @__PURE__ */ new Set();
37930
+ if (!isNodeOfType(effectNode, "CallExpression")) return depNames;
38656
37931
  const depsNode = effectNode.arguments?.[1];
38657
- if (!isNodeOfType(depsNode, "ArrayExpression")) return dependencyStateSymbolIds;
38658
- for (const element of depsNode.elements ?? []) {
38659
- if (!element || isNodeOfType(element, "SpreadElement")) continue;
38660
- const rootIdentifier = getRootIdentifier$1(element);
38661
- if (!isNodeOfType(rootIdentifier, "Identifier")) continue;
38662
- const symbol = resolveConstIdentifierAlias(rootIdentifier, scopes, true);
38663
- if (symbol && stateSymbolIds.has(symbol.id)) dependencyStateSymbolIds.add(symbol.id);
38664
- }
38665
- return dependencyStateSymbolIds;
37932
+ if (!isNodeOfType(depsNode, "ArrayExpression")) return depNames;
37933
+ for (const element of depsNode.elements ?? []) if (isNodeOfType(element, "Identifier")) depNames.add(element.name);
37934
+ return depNames;
38666
37935
  };
38667
37936
  const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
38668
37937
  const analysisFunctions = new Set([effectCallback]);
@@ -38768,13 +38037,12 @@ const readStaticSetterValue = (setterCall, scopes) => {
38768
38037
  if (updater) return readStaticUpdaterReturnValue(updater, scopes);
38769
38038
  return readStaticEffectValue(argument, scopes, null, null);
38770
38039
  };
38771
- const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName, scopes) => {
38040
+ const collectStateWritesInEffect = (analysisFunctions, setterToStateName, scopes) => {
38772
38041
  const stateWrites = /* @__PURE__ */ new Map();
38773
38042
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
38774
38043
  if (!isNodeOfType(child, "CallExpression")) return;
38775
38044
  if (!isNodeOfType(child.callee, "Identifier")) return;
38776
- const setterSymbol = resolveConstIdentifierAlias(child.callee, scopes, true);
38777
- const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
38045
+ const stateName = setterToStateName.get(child.callee.name);
38778
38046
  if (!stateName) return;
38779
38047
  const writeInfo = stateWrites.get(stateName) ?? {
38780
38048
  values: /* @__PURE__ */ new Set(),
@@ -38860,12 +38128,11 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
38860
38128
  "keys",
38861
38129
  "values"
38862
38130
  ]);
38863
- const isFunctionShapedReturn = (returnedValue, setterToStateName, setterSymbolIdToStateName, scopes, isExplicitReturnStatement) => {
38131
+ const isFunctionShapedReturn = (returnedValue, setterToStateName, isExplicitReturnStatement) => {
38864
38132
  if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
38865
38133
  if (isNodeOfType(returnedValue, "CallExpression")) {
38866
38134
  if (isNodeOfType(returnedValue.callee, "Identifier")) {
38867
- const setterSymbol = resolveConstIdentifierAlias(returnedValue.callee, scopes, true);
38868
- if (setterToStateName.has(returnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
38135
+ if (setterToStateName.has(returnedValue.callee.name)) return false;
38869
38136
  if (isSetterIdentifier(returnedValue.callee.name)) return true;
38870
38137
  }
38871
38138
  return isCleanupReturn(returnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
@@ -38889,7 +38156,7 @@ const collectStorageHookSetterNames = (componentBody) => {
38889
38156
  for (const declarator of statement.declarations ?? []) {
38890
38157
  if (!isNodeOfType(declarator.id, "ArrayPattern")) continue;
38891
38158
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
38892
- const calleeName = getCalleeName$1(declarator.init);
38159
+ const calleeName = getCalleeName$2(declarator.init);
38893
38160
  if (!calleeName || !STORAGE_HOOK_PATTERN.test(calleeName)) continue;
38894
38161
  for (const element of declarator.id.elements ?? []) if (isNodeOfType(element, "Identifier") && isSetterIdentifier(element.name)) setterNames.add(element.name);
38895
38162
  }
@@ -39032,11 +38299,11 @@ const isExternalSyncNode = (node) => {
39032
38299
  const receiverRootName = getRootIdentifierName(node.callee.object);
39033
38300
  return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
39034
38301
  };
39035
- const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, scopes, allowCommittedDomSync) => {
38302
+ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, scopes, allowCommittedDomSync) => {
39036
38303
  if (!isFunctionLike$1(effectCallback)) return false;
39037
38304
  if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
39038
- if (isFunctionShapedReturn(effectCallback.body, setterToStateName, setterSymbolIdToStateName, scopes, false)) return true;
39039
- } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, setterSymbolIdToStateName, scopes, true)) return true;
38305
+ if (isFunctionShapedReturn(effectCallback.body, setterToStateName, false)) return true;
38306
+ } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, true)) return true;
39040
38307
  let didFindExternalCall = false;
39041
38308
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
39042
38309
  if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
@@ -39052,11 +38319,10 @@ const noEffectChain = defineRule({
39052
38319
  create: (context) => {
39053
38320
  const checkComponent = (componentBody) => {
39054
38321
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
39055
- const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
38322
+ const useStateBindings = collectUseStateBindings(componentBody);
39056
38323
  if (useStateBindings.length === 0) return;
39057
38324
  const setterToStateName = /* @__PURE__ */ new Map();
39058
38325
  const stateSymbolIds = /* @__PURE__ */ new Map();
39059
- const setterSymbolIdToStateName = /* @__PURE__ */ new Map();
39060
38326
  for (const binding of useStateBindings) {
39061
38327
  setterToStateName.set(binding.setterName, binding.valueName);
39062
38328
  if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
@@ -39065,27 +38331,21 @@ const noEffectChain = defineRule({
39065
38331
  const stateSymbol = context.scopes.symbolFor(stateIdentifier);
39066
38332
  if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
39067
38333
  }
39068
- const setterIdentifier = binding.declarator.id.elements[1];
39069
- if (isNodeOfType(setterIdentifier, "Identifier")) {
39070
- const setterSymbol = context.scopes.symbolFor(setterIdentifier);
39071
- if (setterSymbol) setterSymbolIdToStateName.set(setterSymbol.id, binding.valueName);
39072
- }
39073
38334
  }
39074
38335
  const storageSetterNames = collectStorageHookSetterNames(componentBody);
39075
- const stateSymbolIdSet = new Set(stateSymbolIds.values());
39076
38336
  const effectInfos = [];
39077
- for (const effectCall of findTopLevelEffectCalls(componentBody, context.scopes)) {
38337
+ for (const effectCall of findTopLevelEffectCalls(componentBody)) {
39078
38338
  const callback = getEffectCallback(effectCall, context.scopes);
39079
38339
  if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
39080
38340
  const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
39081
- const stateWrites = collectStateWritesInEffect(analysisFunctions, setterSymbolIdToStateName, context.scopes);
38341
+ const stateWrites = collectStateWritesInEffect(analysisFunctions, setterToStateName, context.scopes);
39082
38342
  const writtenStateNames = new Set(stateWrites.keys());
39083
38343
  effectInfos.push({
39084
38344
  node: effectCall,
39085
- dependencyStateSymbolIds: collectDependencyStateSymbolIds(effectCall, stateSymbolIdSet, context.scopes),
38345
+ depNames: collectDepIdentifierNames(effectCall),
39086
38346
  stateWrites,
39087
38347
  analysisFunctions,
39088
- isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
38348
+ isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
39089
38349
  });
39090
38350
  }
39091
38351
  if (effectInfos.length < 2) return;
@@ -39096,11 +38356,10 @@ const noEffectChain = defineRule({
39096
38356
  for (const readerEffect of effectInfos) {
39097
38357
  if (readerEffect === writerEffect) continue;
39098
38358
  if (readerEffect.isExternalSync) continue;
39099
- if (readerEffect.dependencyStateSymbolIds.size === 0) continue;
38359
+ if (readerEffect.depNames.size === 0) continue;
39100
38360
  let chainedStateName = null;
39101
38361
  for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
39102
- const writtenStateSymbolId = stateSymbolIds.get(writtenName);
39103
- if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
38362
+ if (!readerEffect.depNames.has(writtenName)) continue;
39104
38363
  if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
39105
38364
  chainedStateName = writtenName;
39106
38365
  break;
@@ -39377,7 +38636,7 @@ const noEffectEventHandler = defineRule({
39377
38636
  return {
39378
38637
  ...propStackTracker.visitors,
39379
38638
  CallExpression(node) {
39380
- if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes) || (node.arguments?.length ?? 0) < 2) return;
38639
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
39381
38640
  const callback = getEffectCallback(node);
39382
38641
  if (!callback) return;
39383
38642
  const analysis = getProgramAnalysis(node);
@@ -39497,14 +38756,14 @@ const noEffectEventInDeps = defineRule({
39497
38756
  if (!isNodeOfType(declaratorNode.id, "Identifier")) return;
39498
38757
  const initializer = declaratorNode.init;
39499
38758
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return;
39500
- if (!isReactHookCall(initializer, "useEffectEvent", context.scopes)) return;
38759
+ if (!isHookCall$2(initializer, "useEffectEvent")) return;
39501
38760
  if (isNonReactEffectEventCallee(initializer.callee, declaratorNode, context.scopes)) return;
39502
38761
  componentBindings.addBindingToCurrentFrame(declaratorNode.id.name);
39503
38762
  } });
39504
38763
  return {
39505
38764
  ...componentBindings.visitors,
39506
38765
  CallExpression(node) {
39507
- if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes) || node.arguments.length < 2) return;
38766
+ if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
39508
38767
  if (!componentBindings.isInsideComponent()) return;
39509
38768
  const depsNode = node.arguments[1];
39510
38769
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -39561,7 +38820,7 @@ const noEffectWithFreshDeps = defineRule({
39561
38820
  node: finding.reportNode,
39562
38821
  message: `A dependency inside this custom Hook changes every render because \`${finding.bindingName}\` is a new ${finding.kind} built fresh each time.`
39563
38822
  });
39564
- if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes)) return;
38823
+ if (!isHookCall$2(node, HOOKS_WITH_DEPS)) return;
39565
38824
  const args = node.arguments ?? [];
39566
38825
  if (args.length < 2) return;
39567
38826
  const depsNode = args[1];
@@ -39822,7 +39081,7 @@ const noEventHandler = defineRule({
39822
39081
  severity: "warn",
39823
39082
  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
39083
  create: (context) => ({ CallExpression(node) {
39825
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
39084
+ if (!isUseEffect(node)) return;
39826
39085
  const analysis = getProgramAnalysis(node);
39827
39086
  if (!analysis || hasCleanup(analysis, node)) return;
39828
39087
  const frames = collectBoundedEffectExecutionFrames(analysis, node);
@@ -40284,25 +39543,25 @@ const addDeclarationBindings = (statement, scope) => {
40284
39543
  }
40285
39544
  if (isNodeOfType(statement, "FunctionDeclaration") && statement.id) addPatternBindings(statement.id, scope);
40286
39545
  };
40287
- const collectRenderReachableNamesFromStatements = (statements, names, scope, scopes, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
39546
+ const collectRenderReachableNamesFromStatements = (statements, names, scope, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
40288
39547
  let hasReturn = false;
40289
- for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope, scopes, eventHandlerReferenceNames)) hasReturn = true;
39548
+ for (const statement of statements ?? []) if (collectRenderReachableNamesFromStatement(statement, names, scope, eventHandlerReferenceNames)) hasReturn = true;
40290
39549
  else addDeclarationBindings(statement, scope);
40291
39550
  return hasReturn;
40292
39551
  };
40293
- const collectRenderReachableNamesFromStatement = (statement, names, scope, scopes, eventHandlerReferenceNames) => {
39552
+ const collectRenderReachableNamesFromStatement = (statement, names, scope, eventHandlerReferenceNames) => {
40294
39553
  if (isNodeOfType(statement, "ReturnStatement")) {
40295
39554
  if (statement.argument) addNames(names, collectScopedReferenceNames(statement.argument, scope, eventHandlerReferenceNames));
40296
39555
  return true;
40297
39556
  }
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)) {
39557
+ 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
39558
  for (const argument of statement.expression.arguments ?? []) addNames(names, collectScopedReferenceNames(argument, scope, eventHandlerReferenceNames));
40300
39559
  return false;
40301
39560
  }
40302
- if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope), scopes, eventHandlerReferenceNames);
39561
+ if (isNodeOfType(statement, "BlockStatement")) return collectRenderReachableNamesFromStatements(statement.body, names, createBlockBindingScope(scope), eventHandlerReferenceNames);
40303
39562
  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;
39563
+ const consequentHasReturn = collectRenderReachableNamesFromStatement(statement.consequent, names, scope, eventHandlerReferenceNames);
39564
+ const alternateHasReturn = statement.alternate ? collectRenderReachableNamesFromStatement(statement.alternate, names, scope, eventHandlerReferenceNames) : false;
40306
39565
  if (consequentHasReturn || alternateHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
40307
39566
  return consequentHasReturn || alternateHasReturn;
40308
39567
  }
@@ -40310,7 +39569,7 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
40310
39569
  let hasReturn = false;
40311
39570
  for (const switchCase of statement.cases ?? []) {
40312
39571
  const caseScope = createBlockBindingScope(scope);
40313
- if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope, scopes, eventHandlerReferenceNames)) continue;
39572
+ if (!collectRenderReachableNamesFromStatements(switchCase.consequent, names, caseScope, eventHandlerReferenceNames)) continue;
40314
39573
  hasReturn = true;
40315
39574
  if (switchCase.test) addNames(names, collectScopedReferenceNames(switchCase.test, scope, eventHandlerReferenceNames));
40316
39575
  }
@@ -40318,25 +39577,25 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
40318
39577
  return hasReturn;
40319
39578
  }
40320
39579
  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;
39580
+ const blockHasReturn = collectRenderReachableNamesFromStatement(statement.block, names, scope, eventHandlerReferenceNames);
39581
+ const handlerHasReturn = statement.handler ? collectRenderReachableNamesFromStatement(statement.handler, names, scope, eventHandlerReferenceNames) : false;
39582
+ const finalizerHasReturn = statement.finalizer ? collectRenderReachableNamesFromStatement(statement.finalizer, names, scope, eventHandlerReferenceNames) : false;
40324
39583
  return blockHasReturn || handlerHasReturn || finalizerHasReturn;
40325
39584
  }
40326
39585
  if (isNodeOfType(statement, "CatchClause")) {
40327
39586
  const catchScope = createBlockBindingScope(scope);
40328
39587
  addPatternBindings(statement.param, catchScope);
40329
- return collectRenderReachableNamesFromStatement(statement.body, names, catchScope, scopes, eventHandlerReferenceNames);
39588
+ return collectRenderReachableNamesFromStatement(statement.body, names, catchScope, eventHandlerReferenceNames);
40330
39589
  }
40331
39590
  if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
40332
- const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, scopes, eventHandlerReferenceNames);
39591
+ const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
40333
39592
  if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.test, scope, eventHandlerReferenceNames));
40334
39593
  return bodyHasReturn;
40335
39594
  }
40336
39595
  if (isNodeOfType(statement, "ForStatement")) {
40337
39596
  const loopScope = createBlockBindingScope(scope);
40338
39597
  if (statement.init) addDeclarationBindings(statement.init, loopScope);
40339
- if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, scopes, eventHandlerReferenceNames)) return false;
39598
+ if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
40340
39599
  if (statement.init) addNames(names, collectScopedReferenceNames(statement.init, loopScope, eventHandlerReferenceNames));
40341
39600
  if (statement.test) addNames(names, collectScopedReferenceNames(statement.test, loopScope, eventHandlerReferenceNames));
40342
39601
  if (statement.update) addNames(names, collectScopedReferenceNames(statement.update, loopScope, eventHandlerReferenceNames));
@@ -40346,22 +39605,22 @@ const collectRenderReachableNamesFromStatement = (statement, names, scope, scope
40346
39605
  const rightNames = collectScopedReferenceNames(statement.right, scope, eventHandlerReferenceNames);
40347
39606
  const loopScope = createBlockBindingScope(scope);
40348
39607
  if (isNodeOfType(statement.left, "VariableDeclaration")) addDeclarationBindings(statement.left, loopScope);
40349
- if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, scopes, eventHandlerReferenceNames)) return false;
39608
+ if (!collectRenderReachableNamesFromStatement(statement.body, names, loopScope, eventHandlerReferenceNames)) return false;
40350
39609
  addNames(names, rightNames);
40351
39610
  return true;
40352
39611
  }
40353
- if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope, scopes, eventHandlerReferenceNames);
39612
+ if (isNodeOfType(statement, "LabeledStatement")) return collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
40354
39613
  if (isNodeOfType(statement, "WithStatement")) {
40355
- const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, scopes, eventHandlerReferenceNames);
39614
+ const bodyHasReturn = collectRenderReachableNamesFromStatement(statement.body, names, scope, eventHandlerReferenceNames);
40356
39615
  if (bodyHasReturn) addNames(names, collectScopedReferenceNames(statement.object, scope, eventHandlerReferenceNames));
40357
39616
  return bodyHasReturn;
40358
39617
  }
40359
39618
  return false;
40360
39619
  };
40361
- const collectRenderReachableNames = (componentBody, scopes, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
39620
+ const collectRenderReachableNames = (componentBody, eventHandlerReferenceNames = /* @__PURE__ */ new Set()) => {
40362
39621
  const names = /* @__PURE__ */ new Set();
40363
39622
  if (!isNodeOfType(componentBody, "BlockStatement")) return names;
40364
- collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(), scopes, eventHandlerReferenceNames);
39623
+ collectRenderReachableNamesFromStatements(componentBody.body, names, createComponentBindingScope(), eventHandlerReferenceNames);
40365
39624
  return names;
40366
39625
  };
40367
39626
  //#endregion
@@ -40384,36 +39643,42 @@ const expandTransitiveDependencies = (seedNames, dependencyGraph) => {
40384
39643
  };
40385
39644
  //#endregion
40386
39645
  //#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;
39646
+ const isUseCallbackCall = (node) => isNodeOfType(node, "CallExpression") && getCalleeName(node.callee) === "useCallback";
39647
+ const getCalleeName = (node) => {
39648
+ if (isNodeOfType(node, "Identifier")) return node.name;
39649
+ if (isNodeOfType(node, "MemberExpression")) return getStaticMemberPropertyName(node);
39650
+ return null;
39651
+ };
39652
+ const isFunctionLikeReference = (node, functionLikeLocalNames, scope) => {
39653
+ if (isInlineFunctionExpression(node) || isUseCallbackCall(node)) return true;
40389
39654
  if (isNodeOfType(node, "Identifier")) return functionLikeLocalNames.has(resolveBindingName(scope, node.name));
40390
39655
  const memberReferenceName = getStaticMemberReferenceName(node, (name) => resolveBindingName(scope, name));
40391
39656
  return Boolean(memberReferenceName && functionLikeLocalNames.has(memberReferenceName));
40392
39657
  };
40393
- const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope, scopes) => {
39658
+ const addObjectPropertyFunctionNames = (objectBindingName, node, functionLikeLocalNames, scope) => {
40394
39659
  if (!isNodeOfType(node, "ObjectExpression")) return;
40395
39660
  for (const property of node.properties ?? []) {
40396
39661
  if (!isNodeOfType(property, "Property")) continue;
40397
39662
  const propertyName = getStaticPropertyKeyName(property, { stringifyNonStringLiterals: true });
40398
39663
  if (!propertyName) continue;
40399
- if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope, scopes)) continue;
39664
+ if (!isFunctionLikeReference(property.value, functionLikeLocalNames, scope)) continue;
40400
39665
  functionLikeLocalNames.add(`${objectBindingName}.${propertyName}`);
40401
39666
  }
40402
39667
  };
40403
- const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope, scopes) => {
39668
+ const addVariableDeclarationFunctionNames = (statement, functionLikeLocalNames, scope) => {
40404
39669
  if (!isNodeOfType(statement, "VariableDeclaration")) return;
40405
39670
  const declarationScope = getVariableDeclarationScope(statement, scope);
40406
39671
  for (const declarator of statement.declarations ?? []) {
40407
39672
  const declaredBindingNames = addPatternBindings(declarator.id, declarationScope);
40408
39673
  if (!declarator.init) continue;
40409
- const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope, scopes);
39674
+ const isFunctionReference = isFunctionLikeReference(declarator.init, functionLikeLocalNames, scope);
40410
39675
  for (const declaredBindingName of declaredBindingNames) {
40411
39676
  if (isFunctionReference) functionLikeLocalNames.add(declaredBindingName);
40412
- addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope, scopes);
39677
+ addObjectPropertyFunctionNames(declaredBindingName, declarator.init, functionLikeLocalNames, scope);
40413
39678
  }
40414
39679
  }
40415
39680
  };
40416
- const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope, scopes) => {
39681
+ const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope) => {
40417
39682
  if (isNodeOfType(statement, "FunctionDeclaration")) {
40418
39683
  if (statement.id) {
40419
39684
  const declaredBindingNames = addPatternBindings(statement.id, scope);
@@ -40422,61 +39687,61 @@ const collectStatementFunctionNames = (statement, functionLikeLocalNames, scope,
40422
39687
  return;
40423
39688
  }
40424
39689
  if (isNodeOfType(statement, "VariableDeclaration")) {
40425
- addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope, scopes);
39690
+ addVariableDeclarationFunctionNames(statement, functionLikeLocalNames, scope);
40426
39691
  return;
40427
39692
  }
40428
39693
  if (isNodeOfType(statement, "BlockStatement")) {
40429
- collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope), scopes);
39694
+ collectStatementListFunctionNames(statement.body, functionLikeLocalNames, createBlockBindingScope(scope));
40430
39695
  return;
40431
39696
  }
40432
39697
  if (isNodeOfType(statement, "IfStatement")) {
40433
- collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope, scopes);
40434
- if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope, scopes);
39698
+ collectStatementFunctionNames(statement.consequent, functionLikeLocalNames, scope);
39699
+ if (statement.alternate) collectStatementFunctionNames(statement.alternate, functionLikeLocalNames, scope);
40435
39700
  return;
40436
39701
  }
40437
39702
  if (isNodeOfType(statement, "SwitchStatement")) {
40438
- for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope), scopes);
39703
+ for (const switchCase of statement.cases ?? []) collectStatementListFunctionNames(switchCase.consequent, functionLikeLocalNames, createBlockBindingScope(scope));
40439
39704
  return;
40440
39705
  }
40441
39706
  if (isNodeOfType(statement, "TryStatement")) {
40442
- collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope, scopes);
39707
+ collectStatementFunctionNames(statement.block, functionLikeLocalNames, scope);
40443
39708
  if (statement.handler) {
40444
39709
  const catchScope = createBlockBindingScope(scope);
40445
39710
  addPatternBindings(statement.handler.param, catchScope);
40446
- collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope, scopes);
39711
+ collectStatementFunctionNames(statement.handler.body, functionLikeLocalNames, catchScope);
40447
39712
  }
40448
- if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope, scopes);
39713
+ if (statement.finalizer) collectStatementFunctionNames(statement.finalizer, functionLikeLocalNames, scope);
40449
39714
  return;
40450
39715
  }
40451
39716
  if (isNodeOfType(statement, "ForStatement")) {
40452
39717
  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);
39718
+ if (statement.init && isNodeOfType(statement.init, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.init, functionLikeLocalNames, loopScope);
39719
+ collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
40455
39720
  return;
40456
39721
  }
40457
39722
  if (isNodeOfType(statement, "ForInStatement") || isNodeOfType(statement, "ForOfStatement")) {
40458
39723
  const loopScope = createBlockBindingScope(scope);
40459
- if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope, scopes);
39724
+ if (isNodeOfType(statement.left, "VariableDeclaration")) addVariableDeclarationFunctionNames(statement.left, functionLikeLocalNames, loopScope);
40460
39725
  else addPatternBindings(statement.left, loopScope);
40461
- collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope, scopes);
39726
+ collectStatementFunctionNames(statement.body, functionLikeLocalNames, loopScope);
40462
39727
  return;
40463
39728
  }
40464
39729
  if (isNodeOfType(statement, "WhileStatement") || isNodeOfType(statement, "DoWhileStatement")) {
40465
- collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope, scopes);
39730
+ collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
40466
39731
  return;
40467
39732
  }
40468
- if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope, scopes);
39733
+ if (isNodeOfType(statement, "LabeledStatement")) collectStatementFunctionNames(statement.body, functionLikeLocalNames, scope);
40469
39734
  };
40470
- const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope, scopes) => {
40471
- for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope, scopes);
39735
+ const collectStatementListFunctionNames = (statements, functionLikeLocalNames, scope) => {
39736
+ for (const statement of statements ?? []) collectStatementFunctionNames(statement, functionLikeLocalNames, scope);
40472
39737
  };
40473
- const collectFunctionLikeLocalNames = (componentBody, scopes) => {
39738
+ const collectFunctionLikeLocalNames = (componentBody) => {
40474
39739
  const functionLikeLocalNames = /* @__PURE__ */ new Set();
40475
39740
  if (!isNodeOfType(componentBody, "BlockStatement")) return functionLikeLocalNames;
40476
39741
  let previousSize = -1;
40477
39742
  while (previousSize !== functionLikeLocalNames.size) {
40478
39743
  previousSize = functionLikeLocalNames.size;
40479
- collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope(), scopes);
39744
+ collectStatementListFunctionNames(componentBody.body, functionLikeLocalNames, createComponentBindingScope());
40480
39745
  }
40481
39746
  return functionLikeLocalNames;
40482
39747
  };
@@ -40516,17 +39781,17 @@ const noEventTriggerState = defineRule({
40516
39781
  create: (context) => {
40517
39782
  const checkComponent = (componentBody) => {
40518
39783
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
40519
- const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
39784
+ const useStateBindings = collectUseStateBindings(componentBody);
40520
39785
  if (useStateBindings.length === 0) return;
40521
39786
  const analysis = getProgramAnalysis(componentBody);
40522
39787
  if (!analysis) return;
40523
39788
  const localStateNames = new Set(useStateBindings.map((binding) => binding.valueName));
40524
- const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
39789
+ const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
40525
39790
  const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
40526
- const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames), dependencyGraph);
39791
+ const renderReachableNames = expandTransitiveDependencies(collectRenderReachableNames(componentBody, eventHandlerReferenceNames), dependencyGraph);
40527
39792
  walkAst(componentBody, (effectCall) => {
40528
39793
  if (!isNodeOfType(effectCall, "CallExpression")) return;
40529
- if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) return;
39794
+ if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) return;
40530
39795
  if ((effectCall.arguments?.length ?? 0) < 2) return;
40531
39796
  const depsNode = effectCall.arguments[1];
40532
39797
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -40591,17 +39856,10 @@ const isShadowedByLocalBinding = (identifier) => {
40591
39856
  };
40592
39857
  const isRealFetchCall = (node) => {
40593
39858
  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";
39859
+ if (isNodeOfType(node.callee, "Identifier") && FETCH_CALLEE_NAMES.has(node.callee.name)) return !isShadowedByLocalBinding(node.callee);
39860
+ return isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && FETCH_MEMBER_OBJECTS.has(node.callee.object.name) && !isShadowedByLocalBinding(node.callee.object);
40604
39861
  };
39862
+ const isXmlHttpRequestConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "XMLHttpRequest";
40605
39863
  const isNetworkRequest = (node) => isRealFetchCall(node) || isXmlHttpRequestConstruction(node);
40606
39864
  const resolveLocalFunction = (expression, context) => {
40607
39865
  if (!expression) return null;
@@ -40917,17 +40175,6 @@ const DOM_MEASUREMENT_NAMES = new Set([
40917
40175
  "scrollHeight"
40918
40176
  ]);
40919
40177
  const MEASUREMENT_HELPER_CALLEE_PATTERN = /^(?:get|measure|read)\w*(?:Width|Height|Rect|Rects|Size|Bounds|Position)$/;
40920
- const IMPERATIVE_DOM_MUTATION_NAMES = new Set([
40921
- "blur",
40922
- "focus",
40923
- "restoreSelection",
40924
- "scroll",
40925
- "scrollBy",
40926
- "scrollIntoView",
40927
- "scrollTo",
40928
- "setRangeText",
40929
- "setSelectionRange"
40930
- ]);
40931
40178
  const subtreeReadsDomMeasurement = (root) => {
40932
40179
  if (!root) return false;
40933
40180
  let found = false;
@@ -40946,66 +40193,29 @@ const subtreeReadsDomMeasurement = (root) => {
40946
40193
  });
40947
40194
  return found;
40948
40195
  };
40949
- const collectFunctionNamesMatchingBody = (program, matchesBody) => {
40196
+ const collectMeasuringFunctionNames = (program) => {
40950
40197
  const names = /* @__PURE__ */ new Set();
40951
40198
  walkAst(program, (child) => {
40952
40199
  if (isNodeOfType(child, "FunctionDeclaration")) {
40953
- if (child.id && isNodeOfType(child.id, "Identifier") && matchesBody(child.body)) names.add(child.id.name);
40200
+ if (child.id && isNodeOfType(child.id, "Identifier") && subtreeReadsDomMeasurement(child.body)) names.add(child.id.name);
40954
40201
  return;
40955
40202
  }
40956
40203
  if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier")) return;
40957
40204
  let functionValue = child.init;
40958
40205
  if (functionValue && isNodeOfType(functionValue, "CallExpression") && isNodeOfType(functionValue.callee, "Identifier") && /^use[A-Z]/.test(functionValue.callee.name)) functionValue = functionValue.arguments?.[0];
40959
- if (functionValue && isFunctionLike$1(functionValue) && matchesBody(functionValue.body)) names.add(child.id.name);
40206
+ if (functionValue && isFunctionLike$1(functionValue) && subtreeReadsDomMeasurement(functionValue.body)) names.add(child.id.name);
40960
40207
  });
40961
40208
  return names;
40962
40209
  };
40963
- const collectMeasuringFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeReadsDomMeasurement);
40964
- const subtreeMutatesDomImperatively = (root) => {
40965
- if (!root || isFunctionLike$1(root)) return false;
40966
- let found = false;
40967
- walkAst(root, (child) => {
40968
- if (found) return false;
40969
- if (child !== root && isFunctionLike$1(child)) return false;
40970
- if (!isNodeOfType(child, "CallExpression")) return;
40971
- const callee = stripParenExpression(child.callee);
40972
- const propertyName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
40973
- if (propertyName !== null && IMPERATIVE_DOM_MUTATION_NAMES.has(propertyName)) {
40974
- found = true;
40975
- return false;
40976
- }
40977
- });
40978
- return found;
40979
- };
40980
- const collectImperativeDomFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeMutatesDomImperatively);
40981
- const callsAnyName = (root, names, shouldSkipNestedFunctions = false) => {
40982
- if (!root || names.size === 0 || shouldSkipNestedFunctions && isFunctionLike$1(root)) return false;
40210
+ const callsAnyName = (root, names) => {
40211
+ if (!root || names.size === 0) return false;
40983
40212
  let found = false;
40984
40213
  walkAst(root, (child) => {
40985
40214
  if (found) return false;
40986
- if (shouldSkipNestedFunctions && child !== root && isFunctionLike$1(child)) return false;
40987
40215
  if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && names.has(child.callee.name)) found = true;
40988
40216
  });
40989
40217
  return found;
40990
40218
  };
40991
- const isFollowedByImperativeDomMutation = (call, imperativeDomFunctionNames) => {
40992
- let statement = call;
40993
- let parent = statement.parent;
40994
- while (parent) {
40995
- const statements = isNodeOfType(parent, "BlockStatement") || isNodeOfType(parent, "Program") || isNodeOfType(parent, "StaticBlock") ? parent.body : isNodeOfType(parent, "SwitchCase") ? parent.consequent : null;
40996
- if (statements) {
40997
- const statementIndex = statements.findIndex((siblingStatement) => siblingStatement === statement);
40998
- if (statementIndex >= 0) {
40999
- const nextStatement = statements[statementIndex + 1];
41000
- return subtreeMutatesDomImperatively(nextStatement) || callsAnyName(nextStatement, imperativeDomFunctionNames, true);
41001
- }
41002
- }
41003
- if (isFunctionLike$1(parent) || parent.type.endsWith("Statement") && !isNodeOfType(parent, "ExpressionStatement")) return false;
41004
- statement = parent;
41005
- parent = parent.parent;
41006
- }
41007
- return false;
41008
- };
41009
40219
  const isInsideStartViewTransition = (node) => {
41010
40220
  let cursor = node.parent;
41011
40221
  while (cursor) {
@@ -41046,12 +40256,11 @@ const importsImperativeDomLibrary = (program) => {
41046
40256
  };
41047
40257
  const hasExemptFlushSyncCall = (program, localName) => {
41048
40258
  const measuringFunctionNames = collectMeasuringFunctionNames(program);
41049
- const imperativeDomFunctionNames = collectImperativeDomFunctionNames(program);
41050
40259
  let exempt = false;
41051
40260
  walkAst(program, (child) => {
41052
40261
  if (exempt) return false;
41053
40262
  if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "Identifier") || child.callee.name !== localName) return;
41054
- if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames) || isFollowedByImperativeDomMutation(child, imperativeDomFunctionNames)) {
40263
+ if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames)) {
41055
40264
  exempt = true;
41056
40265
  return false;
41057
40266
  }
@@ -41616,223 +40825,41 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
41616
40825
  if (leftResult === false && rightResult === false) return false;
41617
40826
  return null;
41618
40827
  };
41619
- const readHydrationConditionResult = (expression, context, runtime, state) => {
40828
+ const readHydrationConditionResult = (expression, context, runtime) => {
41620
40829
  const unwrappedExpression = stripParenExpression(expression);
41621
40830
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
41622
40831
  if (predicateMatch) return predicateMatch[`${runtime}Result`];
41623
40832
  const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
41624
40833
  if (staticResult !== null) return staticResult;
41625
- const expressionSymbol = isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression) : null;
41626
- const parameterValue = expressionSymbol ? state.parameterValuesBySymbolId.get(expressionSymbol.id) : null;
41627
- if (expressionSymbol && parameterValue && !state.visitedSymbolIds.has(expressionSymbol.id)) {
41628
- state.visitedSymbolIds.add(expressionSymbol.id);
41629
- const result = readHydrationConditionResult(parameterValue, context, runtime, state);
41630
- state.visitedSymbolIds.delete(expressionSymbol.id);
41631
- return result;
41632
- }
41633
- if (expressionSymbol && expressionSymbol.kind === "const" && expressionSymbol.initializer && expressionSymbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(expressionSymbol.id)) {
41634
- state.visitedSymbolIds.add(expressionSymbol.id);
41635
- const result = readHydrationConditionResult(expressionSymbol.initializer, context, runtime, state);
41636
- state.visitedSymbolIds.delete(expressionSymbol.id);
41637
- return result;
41638
- }
41639
- if (isNodeOfType(unwrappedExpression, "CallExpression")) {
41640
- const callArguments = unwrappedExpression.arguments ?? [];
41641
- if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
41642
- allowGlobalReactNamespace: true,
41643
- resolveNamedAliases: true
41644
- })) {
41645
- const callbackArgument = callArguments[0];
41646
- if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
41647
- const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
41648
- return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? readHydrationFunctionResult(callbackFunction, context, runtime, state) : null;
41649
- }
41650
- const callee = stripParenExpression(unwrappedExpression.callee);
41651
- if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return readHydrationConditionResult(callArguments[0], context, runtime, state);
41652
- const helperFunction = resolveExactLocalFunction(callee, context.scopes);
41653
- if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
41654
- const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
41655
- for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
41656
- const parameter = helperFunction.params[parameterIndex];
41657
- const argument = callArguments[parameterIndex];
41658
- if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
41659
- const parameterSymbol = context.scopes.symbolFor(parameter);
41660
- if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
41661
- }
41662
- return readHydrationFunctionResult(helperFunction, context, runtime, {
41663
- ...state,
41664
- parameterValuesBySymbolId
41665
- });
41666
- }
41667
40834
  if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
41668
- const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
40835
+ const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
41669
40836
  return argumentResult === null ? null : !argumentResult;
41670
40837
  }
41671
40838
  if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
41672
- return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime, state), readHydrationConditionResult(unwrappedExpression.right, context, runtime, state));
41673
- };
41674
- const readHydrationStatementResult = (statement, context, runtime, state) => {
41675
- if (isNodeOfType(statement, "ReturnStatement")) return {
41676
- didReturn: true,
41677
- value: statement.argument ? readHydrationConditionResult(statement.argument, context, runtime, state) : null
41678
- };
41679
- if (isNodeOfType(statement, "BlockStatement")) {
41680
- for (const childStatement of statement.body) {
41681
- const result = readHydrationStatementResult(childStatement, context, runtime, state);
41682
- if (result.didReturn) return result;
41683
- if (statementAlwaysExits(childStatement)) break;
41684
- }
41685
- return {
41686
- didReturn: false,
41687
- value: null
41688
- };
41689
- }
41690
- if (!isNodeOfType(statement, "IfStatement")) return {
41691
- didReturn: false,
41692
- value: null
41693
- };
41694
- const conditionResult = readHydrationConditionResult(statement.test, context, runtime, state);
41695
- if (conditionResult !== null) {
41696
- const selectedBranch = conditionResult ? statement.consequent : statement.alternate;
41697
- return selectedBranch ? readHydrationStatementResult(selectedBranch, context, runtime, state) : {
41698
- didReturn: false,
41699
- value: null
41700
- };
41701
- }
41702
- const consequentResult = readHydrationStatementResult(statement.consequent, context, runtime, state);
41703
- const alternateResult = statement.alternate ? readHydrationStatementResult(statement.alternate, context, runtime, state) : {
41704
- didReturn: false,
41705
- value: null
41706
- };
41707
- return consequentResult.didReturn && alternateResult.didReturn && consequentResult.value !== null && consequentResult.value === alternateResult.value ? consequentResult : {
41708
- didReturn: consequentResult.didReturn || alternateResult.didReturn,
41709
- value: null
41710
- };
41711
- };
41712
- const readHydrationFunctionResult = (functionNode, context, runtime, state) => {
41713
- if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
41714
- state.visitedFunctionNodes.add(functionNode);
41715
- const result = isNodeOfType(functionNode.body, "BlockStatement") ? readHydrationStatementResult(functionNode.body, context, runtime, state).value : readHydrationConditionResult(functionNode.body, context, runtime, state);
41716
- state.visitedFunctionNodes.delete(functionNode);
41717
- return result;
41718
- };
41719
- const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, scopes) => {
41720
- const left = stripParenExpression(leftExpression);
41721
- const right = stripParenExpression(rightExpression);
41722
- if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
41723
- const leftSymbol = scopes.symbolFor(left);
41724
- const rightSymbol = scopes.symbolFor(right);
41725
- return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
41726
- }
41727
- if (isNodeOfType(left, "MemberExpression") && isNodeOfType(right, "MemberExpression")) return doEquivalentExpressionBindingsMatch(left.object, right.object, scopes) && (!left.computed || doEquivalentExpressionBindingsMatch(left.property, right.property, scopes));
41728
- if (isNodeOfType(left, "CallExpression") && isNodeOfType(right, "CallExpression")) {
41729
- const rightArguments = right.arguments ?? [];
41730
- return doEquivalentExpressionBindingsMatch(left.callee, right.callee, scopes) && (left.arguments ?? []).every((argument, index) => {
41731
- const rightArgument = rightArguments[index];
41732
- return Boolean(rightArgument && doEquivalentExpressionBindingsMatch(argument, rightArgument, scopes));
41733
- });
41734
- }
41735
- return true;
41736
- };
41737
- const areHelperReturnValuesEquivalent = (leftValue, rightValue, context) => {
41738
- if (areExpressionsStructurallyEqual(leftValue, rightValue)) return doEquivalentExpressionBindingsMatch(leftValue, rightValue, context.scopes);
41739
- const leftBoolean = readInitialStateBoolean(leftValue, context.scopes);
41740
- const rightBoolean = readInitialStateBoolean(rightValue, context.scopes);
41741
- return leftBoolean !== null && rightBoolean !== null && leftBoolean === rightBoolean;
40839
+ return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
41742
40840
  };
41743
- const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
41744
- const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
41745
- return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
41746
- };
41747
- const matchHydrationConditionInternal = (expression, context, state) => {
40841
+ const matchHydrationCondition = (expression, context) => {
41748
40842
  const unwrappedExpression = stripParenExpression(expression);
41749
40843
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
41750
40844
  if (predicateMatch) return {
41751
40845
  predicateMatch,
41752
40846
  predicateNode: unwrappedExpression
41753
40847
  };
41754
- if (isNodeOfType(unwrappedExpression, "Identifier")) {
41755
- const symbol = context.scopes.symbolFor(unwrappedExpression);
41756
- const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
41757
- if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
41758
- state.visitedSymbolIds.add(symbol.id);
41759
- const match = matchHydrationConditionInternal(parameterValue, context, state);
41760
- state.visitedSymbolIds.delete(symbol.id);
41761
- return match;
41762
- }
41763
- if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
41764
- state.visitedSymbolIds.add(symbol.id);
41765
- const match = matchHydrationConditionInternal(symbol.initializer, context, state);
41766
- state.visitedSymbolIds.delete(symbol.id);
41767
- return match;
41768
- }
41769
- if (isNodeOfType(unwrappedExpression, "CallExpression")) {
41770
- const callArguments = unwrappedExpression.arguments ?? [];
41771
- if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
41772
- allowGlobalReactNamespace: true,
41773
- resolveNamedAliases: true
41774
- })) {
41775
- const callbackArgument = callArguments[0];
41776
- if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
41777
- const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
41778
- return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionResult(callbackFunction, context, state) : null;
41779
- }
41780
- const callee = stripParenExpression(unwrappedExpression.callee);
41781
- if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return matchHydrationConditionInternal(callArguments[0], context, state);
41782
- const helperFunction = resolveExactLocalFunction(callee, context.scopes);
41783
- if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
41784
- const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
41785
- for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
41786
- const parameter = helperFunction.params[parameterIndex];
41787
- const argument = callArguments[parameterIndex];
41788
- if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
41789
- const parameterSymbol = context.scopes.symbolFor(parameter);
41790
- if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
41791
- }
41792
- return matchHydrationFunctionResult(helperFunction, context, {
41793
- ...state,
41794
- parameterValuesBySymbolId
41795
- });
41796
- }
41797
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
40848
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
41798
40849
  if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
41799
- const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
41800
- const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
40850
+ const leftMatch = matchHydrationCondition(unwrappedExpression.left, context);
40851
+ const rightMatch = matchHydrationCondition(unwrappedExpression.right, context);
40852
+ if (leftMatch && rightMatch) {
40853
+ const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client");
40854
+ const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server");
40855
+ return clientResult !== null && serverResult !== null && clientResult !== serverResult ? leftMatch : null;
40856
+ }
41801
40857
  const nestedMatch = leftMatch ?? rightMatch;
41802
40858
  if (!nestedMatch) return null;
41803
- const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client", state);
41804
- const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server", state);
41805
- return clientResult !== null && serverResult !== null && clientResult === serverResult ? null : nestedMatch;
41806
- };
41807
- const matchHydrationReturningStatement = (statement, context, state) => {
41808
- if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? matchHydrationConditionInternal(statement.argument, context, state) : null;
41809
- if (isNodeOfType(statement, "IfStatement")) {
41810
- const conditionMatch = matchHydrationConditionInternal(statement.test, context, state);
41811
- const consequentValues = getReturnedValues(statement.consequent);
41812
- const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
41813
- if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
41814
- return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
41815
- }
41816
- if (!isNodeOfType(statement, "BlockStatement")) return null;
41817
- for (const childStatement of statement.body) {
41818
- const match = matchHydrationReturningStatement(childStatement, context, state);
41819
- if (match) return match;
41820
- if (statementAlwaysExits(childStatement)) break;
41821
- }
41822
- return null;
40859
+ const otherResult = readInitialStateBoolean(leftMatch ? unwrappedExpression.right : unwrappedExpression.left, context.scopes);
40860
+ if (unwrappedExpression.operator === "&&" && otherResult === false || unwrappedExpression.operator === "||" && otherResult === true) return null;
40861
+ return nestedMatch;
41823
40862
  };
41824
- const matchHydrationFunctionResult = (functionNode, context, state) => {
41825
- if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
41826
- state.visitedFunctionNodes.add(functionNode);
41827
- const match = isNodeOfType(functionNode.body, "BlockStatement") ? matchHydrationReturningStatement(functionNode.body, context, state) : matchHydrationConditionInternal(functionNode.body, context, state);
41828
- state.visitedFunctionNodes.delete(functionNode);
41829
- return match;
41830
- };
41831
- const matchHydrationCondition = (expression, context) => matchHydrationConditionInternal(expression, context, {
41832
- parameterValuesBySymbolId: /* @__PURE__ */ new Map(),
41833
- visitedFunctionNodes: /* @__PURE__ */ new Set(),
41834
- visitedSymbolIds: /* @__PURE__ */ new Set()
41835
- });
41836
40863
  const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
41837
40864
  const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
41838
40865
  if (!leftNode || !rightNode) return leftNode === rightNode;
@@ -41975,17 +41002,17 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
41975
41002
  const { predicateMatch, predicateNode } = conditionMatch;
41976
41003
  if (reportedNodes.has(predicateNode)) return;
41977
41004
  if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
41978
- const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes);
41005
+ const componentOrHookNode = findRenderPhaseComponentOrHook(predicateNode, context.scopes);
41979
41006
  if (!componentOrHookNode) return;
41980
41007
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
41981
- if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes)) return;
41008
+ if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
41982
41009
  if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
41983
- const attribute = findEnclosingJsxAttribute(conditionNode);
41010
+ const attribute = findEnclosingJsxAttribute(predicateNode);
41984
41011
  if (!attribute || isEventHandlerAttribute(attribute)) return;
41985
41012
  }
41986
- if (fileIsEmailTemplate || isGatedByFalsyInitialState(conditionNode, context.scopes)) return;
41987
- if (isAfterClientOnlyEarlyReturn(conditionNode, componentOrHookNode, context.scopes)) return;
41988
- const openingElement = findEnclosingJsxOpeningElement(conditionNode);
41013
+ if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
41014
+ if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
41015
+ const openingElement = findEnclosingJsxOpeningElement(predicateNode);
41989
41016
  if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
41990
41017
  if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
41991
41018
  if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
@@ -42362,12 +41389,12 @@ const noInitializeState = defineRule({
42362
41389
  tags: ["test-noise"],
42363
41390
  recommendation: "Pass the initial value directly to useState() instead of setting it from a mount-only useEffect. For SSR hydration, prefer useSyncExternalStore().",
42364
41391
  create: (context) => ({ CallExpression(node) {
42365
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
41392
+ if (!isUseEffect(node)) return;
42366
41393
  const dependencies = node.arguments?.[1];
42367
41394
  if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
42368
41395
  const analysis = getProgramAnalysis(node);
42369
41396
  if (!analysis) return;
42370
- for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
41397
+ for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
42371
41398
  if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
42372
41399
  const stateName = getStateName(fact.stateDeclarator);
42373
41400
  context.report({
@@ -42725,8 +41752,7 @@ const noJsxElementType = defineRule({
42725
41752
  create: (context) => {
42726
41753
  let isJsxImported = false;
42727
41754
  const flaggedAnnotations = [];
42728
- const collectComponentReturnType = (functionNode, returnType) => {
42729
- if (!(isNodeOfType(functionNode, "TSDeclareFunction") ? Boolean(functionNode.id && isReactComponentName(functionNode.id.name)) : isComponentFunction$1(functionNode))) return;
41755
+ const checkReturnType = (returnType) => {
42730
41756
  const typeAnnotation = extractReturnTypeAnnotation(returnType);
42731
41757
  if (!typeAnnotation) return;
42732
41758
  if (isJsxElementTypeReference(typeAnnotation)) flaggedAnnotations.push(typeAnnotation);
@@ -42736,16 +41762,19 @@ const noJsxElementType = defineRule({
42736
41762
  if (isJsxImportBinding(node)) isJsxImported = true;
42737
41763
  },
42738
41764
  FunctionDeclaration(node) {
42739
- collectComponentReturnType(node, node.returnType);
41765
+ checkReturnType(node.returnType);
42740
41766
  },
42741
41767
  ArrowFunctionExpression(node) {
42742
- collectComponentReturnType(node, node.returnType);
41768
+ checkReturnType(node.returnType);
42743
41769
  },
42744
41770
  FunctionExpression(node) {
42745
- collectComponentReturnType(node, node.returnType);
41771
+ checkReturnType(node.returnType);
42746
41772
  },
42747
41773
  TSDeclareFunction(node) {
42748
- collectComponentReturnType(node, node.returnType);
41774
+ checkReturnType(node.returnType);
41775
+ },
41776
+ TSMethodSignature(node) {
41777
+ checkReturnType(node.returnType);
42749
41778
  },
42750
41779
  "Program:exit"() {
42751
41780
  if (isJsxImported) return;
@@ -43974,7 +43003,7 @@ const noMirrorPropEffect = defineRule({
43974
43003
  const setterElement = elements[1];
43975
43004
  if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
43976
43005
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
43977
- if (!isReactHookCall(declarator.init, "useState", context.scopes)) continue;
43006
+ if (!isHookCall$2(declarator.init, "useState")) continue;
43978
43007
  const initializer = declarator.init.arguments?.[0];
43979
43008
  if (!initializer) continue;
43980
43009
  const propRootName = getPropRootName(initializer, propNames);
@@ -43992,7 +43021,7 @@ const noMirrorPropEffect = defineRule({
43992
43021
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
43993
43022
  const effectCall = unwrapDiscardedExpression(statement);
43994
43023
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
43995
- if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
43024
+ if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
43996
43025
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
43997
43026
  const depsNode = effectCall.arguments[1];
43998
43027
  if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
@@ -44401,7 +43430,7 @@ const noMultiComp = defineRule({
44401
43430
  });
44402
43431
  //#endregion
44403
43432
  //#region src/plugin/rules/state-and-effects/no-mutable-in-deps.ts
44404
- const collectUseRefBindingNames = (componentBody, scopes) => {
43433
+ const collectUseRefBindingNames = (componentBody) => {
44405
43434
  const useRefBindings = /* @__PURE__ */ new Set();
44406
43435
  if (!isNodeOfType(componentBody, "BlockStatement")) return useRefBindings;
44407
43436
  for (const statement of componentBody.body ?? []) {
@@ -44409,7 +43438,7 @@ const collectUseRefBindingNames = (componentBody, scopes) => {
44409
43438
  for (const declarator of statement.declarations ?? []) {
44410
43439
  if (!isNodeOfType(declarator.id, "Identifier")) continue;
44411
43440
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
44412
- if (!isReactHookCall(declarator.init, "useRef", scopes)) continue;
43441
+ if (!isHookCall$2(declarator.init, "useRef")) continue;
44413
43442
  useRefBindings.add(declarator.id.name);
44414
43443
  }
44415
43444
  }
@@ -44444,12 +43473,12 @@ const noMutableInDeps = defineRule({
44444
43473
  create: (context) => {
44445
43474
  const checkComponent = (componentBody, componentParams = []) => {
44446
43475
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
44447
- const useRefBindingNames = collectUseRefBindingNames(componentBody, context.scopes);
43476
+ const useRefBindingNames = collectUseRefBindingNames(componentBody);
44448
43477
  const localBindingNames = collectLocalBindingNames(componentBody);
44449
43478
  for (const param of componentParams) collectPatternNames(param, localBindingNames);
44450
43479
  walkAst(componentBody, (child) => {
44451
43480
  if (!isNodeOfType(child, "CallExpression")) return;
44452
- if (!isReactHookCall(child, HOOKS_WITH_DEPS, context.scopes)) return;
43481
+ if (!isHookCall$2(child, HOOKS_WITH_DEPS)) return;
44453
43482
  if ((child.arguments?.length ?? 0) < 2) return;
44454
43483
  const depsNode = child.arguments[1];
44455
43484
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
@@ -46279,7 +45308,6 @@ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
46279
45308
  "useMatchMedia",
46280
45309
  "useMediaJobProgress",
46281
45310
  "useMediaQuery",
46282
- "useMediaQueryState",
46283
45311
  "useResizeObserver",
46284
45312
  "useVisibility",
46285
45313
  "useWindowSize"
@@ -46316,30 +45344,9 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
46316
45344
  if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
46317
45345
  return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
46318
45346
  };
46319
- const getLocalHookExternalStateProof = (analysis, ref) => {
46320
- let hookFunction = resolveToFunction(ref);
46321
- if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
46322
- const definitionNode = definition.node;
46323
- if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
46324
- const initializer = stripParenExpression(definitionNode.init);
46325
- if (!isNodeOfType(initializer, "CallExpression")) continue;
46326
- const callee = stripParenExpression(initializer.callee);
46327
- if (!isNodeOfType(callee, "Identifier")) continue;
46328
- const calleeReference = getRef(analysis, callee);
46329
- if (!calleeReference) continue;
46330
- hookFunction = resolveToFunction(calleeReference);
46331
- if (hookFunction) break;
46332
- }
46333
- if (!hookFunction) return null;
46334
- const returnedReferences = collectFunctionReturnStatements(hookFunction).flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
46335
- if (returnedReferences.length === 0) return null;
46336
- return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
46337
- };
46338
- const isExternalSubscriptionHookRef = (analysis, ref) => {
45347
+ const isExternalSubscriptionHookRef = (ref) => {
46339
45348
  const identifier = ref.identifier;
46340
45349
  if (!isNodeOfType(identifier, "Identifier")) return false;
46341
- const localHookProof = getLocalHookExternalStateProof(analysis, ref);
46342
- if (localHookProof !== null) return localHookProof;
46343
45350
  if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
46344
45351
  return Boolean(ref.resolved?.defs.some((def) => {
46345
45352
  const node = def.node;
@@ -46362,10 +45369,16 @@ const noPassDataToParent = defineRule({
46362
45369
  tags: ["test-noise"],
46363
45370
  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
45371
  create: (context) => {
46365
- const isReactUseRefCall = (node) => isReactHookCall(node, "useRef", context.scopes);
46366
- const isReactUseEffectCall = (node) => isReactHookCall(node, "useEffect", context.scopes);
45372
+ const isReactUseRefCall = (node) => isReactApiCall(node, "useRef", context.scopes, {
45373
+ allowGlobalReactNamespace: true,
45374
+ allowUnboundBareCalls: true
45375
+ });
45376
+ const isReactUseEffectCall = (node) => isReactApiCall(node, "useEffect", context.scopes, {
45377
+ allowGlobalReactNamespace: true,
45378
+ allowUnboundBareCalls: true
45379
+ });
46367
45380
  return { CallExpression(node) {
46368
- if (!isReactUseEffectCall(node)) return;
45381
+ if (!isUseEffect(node)) return;
46369
45382
  const analysis = getProgramAnalysis(node);
46370
45383
  if (!analysis) return;
46371
45384
  if (hasCleanup(analysis, node)) return;
@@ -46417,11 +45430,11 @@ const noPassDataToParent = defineRule({
46417
45430
  if (argumentRef && resolveToFunction(argumentRef)) return [];
46418
45431
  }
46419
45432
  return getDownstreamRefs(analysis, argument);
46420
- }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) || isExternalSubscriptionHookRef(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
45433
+ }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
46421
45434
  if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
46422
45435
  if (!argsUpstreamRefs.some((argRef) => {
46423
45436
  if (isUseStateIdentifier(argRef.identifier)) return false;
46424
- if (isExternalSubscriptionHookRef(analysis, argRef)) return false;
45437
+ if (isExternalSubscriptionHookRef(argRef)) return false;
46425
45438
  if (isProp(analysis, argRef)) return false;
46426
45439
  if (isUseRefIdentifier(argRef.identifier)) return false;
46427
45440
  if (isRefCurrent(argRef)) return false;
@@ -46652,7 +45665,7 @@ const noPassLiveStateToParent = defineRule({
46652
45665
  tags: ["test-noise"],
46653
45666
  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
45667
  create: (context) => ({ CallExpression(node) {
46655
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
45668
+ if (!isUseEffect(node)) return;
46656
45669
  const analysis = getProgramAnalysis(node);
46657
45670
  if (!analysis) return;
46658
45671
  const effectFnRefs = getEffectFnRefs(analysis, node);
@@ -47067,7 +46080,7 @@ const hasPreviousValueDep = (effectNode, depElements) => {
47067
46080
  if (!isNodeOfType(element, "Identifier")) continue;
47068
46081
  const binding = findVariableInitializer(effectNode, element.name);
47069
46082
  if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) continue;
47070
- const calleeName = getCalleeName$1(binding.initializer);
46083
+ const calleeName = getCalleeName$2(binding.initializer);
47071
46084
  if (calleeName && PREVIOUS_VALUE_HOOK_PATTERN.test(calleeName)) return true;
47072
46085
  }
47073
46086
  return false;
@@ -47089,7 +46102,7 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
47089
46102
  if (!isNodeOfType(receiver, "Identifier")) return null;
47090
46103
  const binding = findVariableInitializer(callExpression, receiver.name);
47091
46104
  if (!binding?.initializer || !isNodeOfType(binding.initializer, "CallExpression")) return null;
47092
- if (getCalleeName$1(binding.initializer) !== "useRef") return null;
46105
+ if (getCalleeName$2(binding.initializer) !== "useRef") return null;
47093
46106
  const callbackArgument = binding.initializer.arguments?.[0];
47094
46107
  if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
47095
46108
  return isPropName(callbackArgument.name) ? callbackArgument.name : null;
@@ -47121,7 +46134,7 @@ const noPropCallbackInEffect = defineRule({
47121
46134
  return {
47122
46135
  ...propStackTracker.visitors,
47123
46136
  CallExpression(node) {
47124
- if (!isReactHookCall(node, EFFECT_HOOK_NAMES$1, context.scopes) || (node.arguments?.length ?? 0) < 2) return;
46137
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1) || (node.arguments?.length ?? 0) < 2) return;
47125
46138
  const callback = getEffectCallback(node);
47126
46139
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
47127
46140
  const depsNode = node.arguments[1];
@@ -47985,69 +46998,6 @@ const noRedundantShouldComponentUpdate = defineRule({
47985
46998
  }
47986
46999
  });
47987
47000
  //#endregion
47988
- //#region src/plugin/rules/correctness/no-ref-callback-cleanup-before-react-19.ts
47989
- const resolveFunctionExpressions = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
47990
- const expression = stripParenExpression(rawExpression);
47991
- if (isFunctionLike$1(expression)) return expression.async || expression.generator ? [] : [expression];
47992
- if (isNodeOfType(expression, "ConditionalExpression")) {
47993
- if (isNodeOfType(expression.test, "Literal")) return resolveFunctionExpressions(expression.test.value ? expression.consequent : expression.alternate, scopes, visitedSymbolIds);
47994
- return [...resolveFunctionExpressions(expression.consequent, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.alternate, scopes, visitedSymbolIds)];
47995
- }
47996
- if (isNodeOfType(expression, "LogicalExpression")) {
47997
- if (isNodeOfType(expression.left, "Literal")) {
47998
- const isLeftTruthy = Boolean(expression.left.value);
47999
- if (expression.operator === "&&" && !isLeftTruthy) return [];
48000
- if (expression.operator === "||" && isLeftTruthy) return [];
48001
- if (expression.operator === "??" && expression.left.value !== null) return [];
48002
- }
48003
- if (expression.operator === "&&") return resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds);
48004
- return [...resolveFunctionExpressions(expression.left, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds)];
48005
- }
48006
- if (isNodeOfType(expression, "SequenceExpression")) {
48007
- const finalExpression = expression.expressions.at(-1);
48008
- return finalExpression ? resolveFunctionExpressions(finalExpression, scopes, visitedSymbolIds) : [];
48009
- }
48010
- if (isNodeOfType(expression, "CallExpression")) {
48011
- if (!isReactApiCall(expression, "useCallback", scopes)) return [];
48012
- const callback = expression.arguments[0];
48013
- return callback && !isNodeOfType(callback, "SpreadElement") ? resolveFunctionExpressions(callback, scopes, visitedSymbolIds) : [];
48014
- }
48015
- if (!isNodeOfType(expression, "Identifier")) return [];
48016
- const symbol = scopes.symbolFor(expression);
48017
- if (!symbol || visitedSymbolIds.has(symbol.id)) return [];
48018
- if (symbol.kind === "function" && isNodeOfType(symbol.declarationNode, "FunctionDeclaration") && symbol.references.every((reference) => reference.flag === "read")) return resolveFunctionExpressions(symbol.declarationNode, scopes, new Set([...visitedSymbolIds, symbol.id]));
48019
- const initializer = getDirectConstInitializer(symbol);
48020
- if (!initializer) return [];
48021
- return resolveFunctionExpressions(initializer, scopes, new Set([...visitedSymbolIds, symbol.id]));
48022
- };
48023
- const functionReturnsCleanupFunction = (functionExpression, scopes) => {
48024
- if (!isFunctionLike$1(functionExpression)) return false;
48025
- if (!isNodeOfType(functionExpression.body, "BlockStatement")) return resolveFunctionExpressions(functionExpression.body, scopes).length > 0;
48026
- return collectFunctionReturnStatements(functionExpression).some((returnStatement) => Boolean(returnStatement.argument && resolveFunctionExpressions(returnStatement.argument, scopes).length > 0));
48027
- };
48028
- const callbackReturnsCleanupFunction = (callback, scopes) => {
48029
- return resolveFunctionExpressions(callback, scopes).some((functionExpression) => functionReturnsCleanupFunction(functionExpression, scopes));
48030
- };
48031
- const noRefCallbackCleanupBeforeReact19 = defineRule({
48032
- id: "no-ref-callback-cleanup-before-react-19",
48033
- title: "Ref cleanup requires React 19",
48034
- requires: ["react:18"],
48035
- disabledWhen: ["react:19"],
48036
- severity: "warn",
48037
- recommendation: "React 18 ignores functions returned from ref callbacks. Handle cleanup when React calls the ref with `null`, or require React 19 before returning a cleanup function.",
48038
- create: (context) => ({ JSXAttribute(node) {
48039
- if (getJsxAttributeName(node.name) !== "ref") return;
48040
- if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
48041
- const callback = node.value.expression;
48042
- if (!callback || isNodeOfType(callback, "JSXEmptyExpression")) return;
48043
- if (!callbackReturnsCleanupFunction(callback, context.scopes)) return;
48044
- context.report({
48045
- node,
48046
- message: "This ref callback returns a cleanup function, but React 18 ignores ref cleanup returns, so the cleanup never runs. Handle detachment when React calls the ref with `null`, or require React 19."
48047
- });
48048
- } })
48049
- });
48050
- //#endregion
48051
47001
  //#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
48052
47002
  const REPEATED_ANCESTOR_TYPES = new Set([
48053
47003
  "DoWhileStatement",
@@ -49014,7 +47964,7 @@ const noResetAllStateOnPropChange = defineRule({
49014
47964
  tags: ["test-noise"],
49015
47965
  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
47966
  create: (context) => ({ CallExpression(node) {
49017
- if (!isReactHookCall(node, "useEffect", context.scopes)) return;
47967
+ if (!isUseEffect(node)) return;
49018
47968
  const analysis = getProgramAnalysis(node);
49019
47969
  if (!analysis) return;
49020
47970
  const effectFnRefs = getEffectFnRefs(analysis, node);
@@ -49283,7 +48233,7 @@ const isTanStackServerFnHandlerCall = (node) => {
49283
48233
  if (node.callee.property.name !== "handler") return false;
49284
48234
  let currentNode = node.callee.object;
49285
48235
  while (isNodeOfType(currentNode, "CallExpression")) {
49286
- const calleeName = getCalleeName$1(currentNode);
48236
+ const calleeName = getCalleeName$2(currentNode);
49287
48237
  if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) return true;
49288
48238
  if (!isNodeOfType(currentNode.callee, "MemberExpression")) return false;
49289
48239
  currentNode = currentNode.callee.object;
@@ -49784,7 +48734,7 @@ const noSelfUpdatingEffect = defineRule({
49784
48734
  create: (context) => {
49785
48735
  const checkFunctionScope = (functionBody) => {
49786
48736
  if (!functionBody || !isNodeOfType(functionBody, "BlockStatement")) return;
49787
- const useStateBindings = collectUseStateBindings(functionBody, context.scopes);
48737
+ const useStateBindings = collectUseStateBindings(functionBody);
49788
48738
  if (useStateBindings.length === 0) return;
49789
48739
  const setterNameToStateName = /* @__PURE__ */ new Map();
49790
48740
  for (const binding of useStateBindings) setterNameToStateName.set(binding.setterName, binding.valueName);
@@ -49793,7 +48743,7 @@ const noSelfUpdatingEffect = defineRule({
49793
48743
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
49794
48744
  const effectCall = unwrapDiscardedExpression(statement);
49795
48745
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
49796
- if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
48746
+ if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
49797
48747
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
49798
48748
  const dependencyStateNames = collectDependencyStateNames(effectCall.arguments[1]);
49799
48749
  if (dependencyStateNames.size === 0) continue;
@@ -49879,7 +48829,7 @@ const noSetStateInRender = defineRule({
49879
48829
  create: (context) => {
49880
48830
  const checkComponent = (componentBody) => {
49881
48831
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
49882
- const setterNames = new Set(collectUseStateBindings(componentBody, context.scopes).map((binding) => binding.setterName));
48832
+ const setterNames = new Set(collectUseStateBindings(componentBody).map((binding) => binding.setterName));
49883
48833
  if (setterNames.size === 0) return;
49884
48834
  for (const statement of componentBody.body ?? []) {
49885
48835
  const setterCall = isUnconditionalSetterCallStatement(statement, setterNames);
@@ -50128,10 +49078,10 @@ const collectTimerRefUsageFacts = (ownerScope, refName) => {
50128
49078
  });
50129
49079
  return facts;
50130
49080
  };
50131
- const isEffectCallbackFunction = (functionNode, scopes) => {
49081
+ const isEffectCallbackFunction = (functionNode) => {
50132
49082
  const parent = functionNode.parent;
50133
49083
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
50134
- return isReactHookCall(parent, EFFECT_HOOK_NAMES$1, scopes) && getEffectCallback(parent) === functionNode;
49084
+ return isHookCall$2(parent, EFFECT_HOOK_NAMES$1) && getEffectCallback(parent) === functionNode;
50135
49085
  };
50136
49086
  const doesEffectCallbackReturnName = (effectCallback, name) => {
50137
49087
  if (!isFunctionLike$1(effectCallback)) return false;
@@ -50150,24 +49100,24 @@ const isFunctionReturnedFromEffectCallback = (functionNode, effectCallback) => {
50150
49100
  const cleanupBindingName = getFunctionBindingName$1(functionNode);
50151
49101
  return cleanupBindingName !== null && doesEffectCallbackReturnName(effectCallback, cleanupBindingName);
50152
49102
  };
50153
- const isReturnedFromAnyEffectInScope = (functionNode, ownerScope, scopes) => {
49103
+ const isReturnedFromAnyEffectInScope = (functionNode, ownerScope) => {
50154
49104
  const cleanupBindingName = getFunctionBindingName$1(functionNode);
50155
49105
  if (cleanupBindingName === null) return false;
50156
49106
  let isReturnedFromEffect = false;
50157
49107
  walkAst(ownerScope, (child) => {
50158
49108
  if (isReturnedFromEffect) return false;
50159
- if (!isNodeOfType(child, "CallExpression") || !isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) return;
49109
+ if (!isNodeOfType(child, "CallExpression") || !isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
50160
49110
  const effectCallback = getEffectCallback(child);
50161
49111
  if (effectCallback && doesEffectCallbackReturnName(effectCallback, cleanupBindingName)) isReturnedFromEffect = true;
50162
49112
  });
50163
49113
  return isReturnedFromEffect;
50164
49114
  };
50165
- const isInsideEffectCleanupReturn = (node, ownerScope, scopes) => {
49115
+ const isInsideEffectCleanupReturn = (node, ownerScope) => {
50166
49116
  let functionNode = findEnclosingFunction$1(node);
50167
49117
  while (functionNode) {
50168
49118
  const outerFunction = findEnclosingFunction$1(functionNode);
50169
- if (outerFunction && isEffectCallbackFunction(outerFunction, scopes) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
50170
- if (isReturnedFromAnyEffectInScope(functionNode, ownerScope, scopes)) return true;
49119
+ if (outerFunction && isEffectCallbackFunction(outerFunction) && isFunctionReturnedFromEffectCallback(functionNode, outerFunction)) return true;
49120
+ if (isReturnedFromAnyEffectInScope(functionNode, ownerScope)) return true;
50171
49121
  functionNode = outerFunction;
50172
49122
  }
50173
49123
  return false;
@@ -50203,10 +49153,10 @@ const noStaleTimerRef = defineRule({
50203
49153
  if (isShadowedTimerGlobal(node)) return;
50204
49154
  const { clearCalleeName, refName } = clearCall;
50205
49155
  const refBinding = findVariableInitializer(node, refName);
50206
- if (!refBinding?.initializer || !isReactHookCall(refBinding.initializer, "useRef", context.scopes)) return;
49156
+ if (!refBinding?.initializer || !isHookCall$2(refBinding.initializer, "useRef")) return;
50207
49157
  const usageFacts = collectTimerRefUsageFacts(refBinding.scopeOwner, refName);
50208
49158
  if (!usageFacts.holdsScheduledTimerId || !usageFacts.hasPendingSignalRead) return;
50209
- if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner, context.scopes)) return;
49159
+ if (isInsideEffectCleanupReturn(node, refBinding.scopeOwner)) return;
50210
49160
  if (hasRefCurrentReassignmentAfterClear(node, refName)) return;
50211
49161
  context.report({
50212
49162
  node,
@@ -56010,7 +54960,7 @@ const classifyCallableReadsInsideEffect = (callableIdentifier, effectCallback, c
56010
54960
  allReadsAreInSubHandlers = false;
56011
54961
  return;
56012
54962
  }
56013
- if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$1(subHandlerCall);
54963
+ if (firstSubHandlerName === null) firstSubHandlerName = getCalleeName$2(subHandlerCall);
56014
54964
  });
56015
54965
  return {
56016
54966
  hasAnyRead,
@@ -56033,7 +54983,7 @@ const preferUseEffectEvent = defineRule({
56033
54983
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
56034
54984
  const effectCall = statement.expression;
56035
54985
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
56036
- if (!isReactHookCall(effectCall, EFFECT_HOOK_NAMES$1, context.scopes)) continue;
54986
+ if (!isHookCall$2(effectCall, EFFECT_HOOK_NAMES$1)) continue;
56037
54987
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
56038
54988
  const depsNode = effectCall.arguments[1];
56039
54989
  if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
@@ -56065,11 +55015,11 @@ const preferUseEffectEvent = defineRule({
56065
55015
  });
56066
55016
  //#endregion
56067
55017
  //#region src/plugin/rules/state-and-effects/prefer-use-sync-external-store.ts
56068
- const findUseEffectsInComponent = (componentBody, scopes) => {
55018
+ const findUseEffectsInComponent = (componentBody) => {
56069
55019
  const effectCalls = [];
56070
55020
  if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
56071
55021
  for (const statement of componentBody.body ?? []) walkAst(statement, (child) => {
56072
- if (isNodeOfType(child, "CallExpression") && isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) effectCalls.push(child);
55022
+ if (isNodeOfType(child, "CallExpression") && isHookCall$2(child, EFFECT_HOOK_NAMES$1)) effectCalls.push(child);
56073
55023
  });
56074
55024
  return effectCalls;
56075
55025
  };
@@ -56272,7 +55222,7 @@ const preferUseSyncExternalStore = defineRule({
56272
55222
  };
56273
55223
  const checkComponent = (componentBody) => {
56274
55224
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
56275
- const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
55225
+ const useStateBindings = collectUseStateBindings(componentBody);
56276
55226
  if (useStateBindings.length === 0) return;
56277
55227
  const useStateInitializerByValueName = /* @__PURE__ */ new Map();
56278
55228
  for (const binding of useStateBindings) {
@@ -56285,7 +55235,7 @@ const preferUseSyncExternalStore = defineRule({
56285
55235
  }
56286
55236
  const setterNameToValueName = /* @__PURE__ */ new Map();
56287
55237
  for (const binding of useStateBindings) setterNameToValueName.set(binding.setterName, binding.valueName);
56288
- for (const effectCall of findUseEffectsInComponent(componentBody, context.scopes)) {
55238
+ for (const effectCall of findUseEffectsInComponent(componentBody)) {
56289
55239
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
56290
55240
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
56291
55241
  const depsNode = effectCall.arguments[1];
@@ -56327,7 +55277,7 @@ const preferUseSyncExternalStore = defineRule({
56327
55277
  })).filter((candidate) => candidate.storeName !== null);
56328
55278
  if (snapshotBindings.length === 0) return;
56329
55279
  const reportedDeclarators = /* @__PURE__ */ new Set();
56330
- for (const effectCall of findUseEffectsInComponent(componentBody, context.scopes)) {
55280
+ for (const effectCall of findUseEffectsInComponent(componentBody)) {
56331
55281
  if (!isNodeOfType(effectCall, "CallExpression")) continue;
56332
55282
  if ((effectCall.arguments?.length ?? 0) < 2) continue;
56333
55283
  const depsNode = effectCall.arguments[1];
@@ -56424,7 +55374,7 @@ const preferUseReducer = defineRule({
56424
55374
  create: (context) => {
56425
55375
  const reportCoUpdatedUseState = (body, componentName) => {
56426
55376
  if (!isNodeOfType(body, "BlockStatement")) return;
56427
- const bindings = collectUseStateBindings(body, context.scopes);
55377
+ const bindings = collectUseStateBindings(body);
56428
55378
  const setterNames = new Set(bindings.map((binding) => binding.setterName));
56429
55379
  if (setterNames.size < 5) return;
56430
55380
  const coUpdatedCount = findLargestCoUpdatedSetterGroup(body, setterNames, new Map(bindings.map((binding) => {
@@ -56640,7 +55590,7 @@ const QUERY_READ_METHOD_NAMES = new Set([
56640
55590
  ]);
56641
55591
  const isQueryCacheSourceCall = (initializer) => {
56642
55592
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
56643
- const hookName = getCalleeName$1(initializer);
55593
+ const hookName = getCalleeName$2(initializer);
56644
55594
  if (!hookName) return false;
56645
55595
  return hookName === "useQueryClient" || TRPC_UTILS_HOOK_PATTERN.test(hookName);
56646
55596
  };
@@ -56772,7 +55722,7 @@ const queryMutationMissingInvalidation = defineRule({
56772
55722
  },
56773
55723
  CallExpression(node) {
56774
55724
  if (!hasQueryReadUsage) {
56775
- const callName = getCalleeName$1(node);
55725
+ const callName = getCalleeName$2(node);
56776
55726
  if (callName && (QUERY_READ_HOOK_NAMES.has(callName) || QUERY_READ_METHOD_NAMES.has(callName) || TRPC_UTILS_HOOK_PATTERN.test(callName))) hasQueryReadUsage = true;
56777
55727
  }
56778
55728
  const calleeName = isNodeOfType(node.callee, "Identifier") ? node.callee.name : null;
@@ -57265,30 +56215,26 @@ const REMOVAL_MESSAGE_BY_REACT_API_NAME = new Map([
57265
56215
  ["useCallback", "This `useCallback` is dead weight, since React Compiler already caches every function here. Delete it."],
57266
56216
  ["memo", "This `memo()` is dead weight, since React Compiler already caches the component's output. Delete it."]
57267
56217
  ]);
57268
- const resolveReactApiNameForIdentifier = (callee, context) => {
56218
+ const resolveReactApiNameForIdentifier = (callee) => {
57269
56219
  if (!isNodeOfType(callee, "Identifier")) return null;
57270
- if (context.scopes.symbolFor(callee)?.kind !== "import") return null;
57271
56220
  const importedName = getImportedNameFromModule(callee, callee.name, "react");
57272
56221
  if (importedName && REMOVAL_MESSAGE_BY_REACT_API_NAME.has(importedName)) return importedName;
57273
56222
  return null;
57274
56223
  };
57275
- const resolveReactApiNameForMemberExpression = (callee, context) => {
56224
+ const resolveReactApiNameForMemberExpression = (callee) => {
57276
56225
  if (!isNodeOfType(callee, "MemberExpression")) return null;
57277
56226
  if (callee.computed) return null;
57278
- const namespaceIdentifier = stripParenExpression(callee.object);
56227
+ const namespaceIdentifier = callee.object;
57279
56228
  const propertyIdentifier = callee.property;
57280
56229
  if (!isNodeOfType(namespaceIdentifier, "Identifier")) return null;
57281
56230
  if (!isNodeOfType(propertyIdentifier, "Identifier")) return null;
57282
56231
  if (!REMOVAL_MESSAGE_BY_REACT_API_NAME.has(propertyIdentifier.name)) return null;
57283
56232
  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;
56233
+ if (isCanonicalReactNamespaceName(namespaceName)) return propertyIdentifier.name;
56234
+ if (isImportedFromModule(namespaceIdentifier, namespaceName, "react")) return propertyIdentifier.name;
57286
56235
  return null;
57287
56236
  };
57288
- const resolveReactApiNameForCallee = (callee, context) => {
57289
- const unwrappedCallee = stripParenExpression(callee);
57290
- return resolveReactApiNameForIdentifier(unwrappedCallee, context) ?? resolveReactApiNameForMemberExpression(unwrappedCallee, context);
57291
- };
56237
+ const resolveReactApiNameForCallee = (callee) => resolveReactApiNameForIdentifier(callee) ?? resolveReactApiNameForMemberExpression(callee);
57292
56238
  const isNullishComparatorArgument = (argumentNode) => isNodeOfType(argumentNode, "Identifier") && argumentNode.name === "undefined" || isNodeOfType(argumentNode, "Literal") && argumentNode.value === null;
57293
56239
  const COMPILER_INFERABLE_HOC_NAMES = new Set(["memo", "forwardRef"]);
57294
56240
  const calleeTrailingName = (callee) => {
@@ -57314,7 +56260,7 @@ const reactCompilerNoManualMemoization = defineRule({
57314
56260
  requires: ["react-compiler"],
57315
56261
  recommendation: "Delete the `useMemo` / `useCallback` / `memo` call and use the plain value or component. React Compiler caches it for you.",
57316
56262
  create: (context) => ({ CallExpression(node) {
57317
- const apiName = resolveReactApiNameForCallee(node.callee, context);
56263
+ const apiName = resolveReactApiNameForCallee(node.callee);
57318
56264
  if (!apiName) return;
57319
56265
  if (apiName === "memo") {
57320
56266
  const comparatorArgument = node.arguments?.[1];
@@ -58013,39 +56959,8 @@ const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node,
58013
56959
  destructureIndex: 1
58014
56960
  });
58015
56961
  //#endregion
58016
- //#region src/plugin/utils/unwrap-return-expression.ts
58017
- const unwrapReturnExpression = (node) => isNodeOfType(node, "ReturnStatement") && node.argument ? node.argument : node;
58018
- //#endregion
58019
56962
  //#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
58020
56963
  const USE_EFFECT_ONLY = new Set(["useEffect"]);
58021
- const USE_CALLBACK_ONLY = new Set(["useCallback"]);
58022
- const USE_STATE_ONLY = new Set(["useState"]);
58023
- const REACT_API_CALL_OPTIONS = {
58024
- allowGlobalReactNamespace: true,
58025
- allowUnboundBareCalls: true,
58026
- resolveNamedAliases: true
58027
- };
58028
- const expressionReadsDerivedSymbol = (context, expression, stateDerivedSymbolIds) => {
58029
- let readsDerivedSymbol = false;
58030
- walkAst(expression, (node) => {
58031
- if (readsDerivedSymbol) return false;
58032
- if (node !== expression && isFunctionLike$1(node)) return false;
58033
- if (isNodeOfType(node, "Identifier") && stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) readsDerivedSymbol = true;
58034
- });
58035
- return readsDerivedSymbol;
58036
- };
58037
- const getStaticObjectPropertyName = (property) => {
58038
- if (!isNodeOfType(property, "Property") || property.computed || property.method || property.kind !== "init") return null;
58039
- if (isNodeOfType(property.key, "Identifier")) return property.key.name;
58040
- if (isNodeOfType(property.key, "Literal") && (typeof property.key.value === "string" || typeof property.key.value === "number")) return String(property.key.value);
58041
- return null;
58042
- };
58043
- const isNonVisibleJsxSpreadProperty = (propertyName) => propertyName === "id" || propertyName.startsWith("aria-") || /^on[A-Z]/.test(propertyName);
58044
- const isTransparentAssignmentTarget = (identifier) => {
58045
- const expressionRoot = findTransparentExpressionRoot(identifier);
58046
- const parent = expressionRoot.parent;
58047
- return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expressionRoot || isNodeOfType(parent, "UpdateExpression") && parent.argument === expressionRoot || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expressionRoot);
58048
- };
58049
56964
  const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
58050
56965
  let readsCurrent = false;
58051
56966
  walkAst(argument, (child) => {
@@ -58097,166 +57012,6 @@ const isStateUsedOnlyInIdOrAriaAttributes = (setterCall, setterName) => {
58097
57012
  });
58098
57013
  return referenceCount > 0 && !nonAriaReferenceFound;
58099
57014
  };
58100
- const isGlobalWindowMember = (context, node, propertyName) => {
58101
- const member = stripParenExpression(node);
58102
- if (!isNodeOfType(member, "MemberExpression") || member.computed) return false;
58103
- const receiver = stripParenExpression(member.object);
58104
- return isNodeOfType(receiver, "Identifier") && receiver.name === "window" && context.scopes.isGlobalReference(receiver) && isNodeOfType(member.property, "Identifier") && member.property.name === propertyName;
58105
- };
58106
- const getDirectWindowWidthSetter = (context, statement) => {
58107
- const call = unwrapDiscardedExpression(statement);
58108
- if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 1) return null;
58109
- if (!isNodeOfType(call.callee, "Identifier") || !isSetterCall(call)) return null;
58110
- const argument = call.arguments[0];
58111
- return isGlobalWindowMember(context, argument, "innerWidth") ? call : null;
58112
- };
58113
- const getResizeListenerHandler = (context, statement, methodName) => {
58114
- const call = unwrapDiscardedExpression(statement);
58115
- if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 2) return null;
58116
- if (!isGlobalWindowMember(context, call.callee, methodName)) return null;
58117
- const eventName = call.arguments[0];
58118
- const handler = call.arguments[1];
58119
- if (!isNodeOfType(eventName, "Literal") || eventName.value !== "resize") return null;
58120
- return isNodeOfType(handler, "Identifier") ? handler : null;
58121
- };
58122
- const getCleanupResizeHandler = (context, statement) => {
58123
- if (!isNodeOfType(statement, "ReturnStatement") || !isFunctionLike$1(statement.argument)) return null;
58124
- const cleanupStatements = getCallbackStatements(statement.argument);
58125
- if (cleanupStatements.length !== 1) return null;
58126
- return getResizeListenerHandler(context, unwrapReturnExpression(cleanupStatements[0]), "removeEventListener");
58127
- };
58128
- const findExactViewportState = (context, componentFunction, setterCall) => {
58129
- if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
58130
- const componentBody = componentFunction.body;
58131
- if (!isNodeOfType(setterCall.callee, "Identifier")) return null;
58132
- const setterSymbol = context.scopes.symbolFor(setterCall.callee);
58133
- if (!setterSymbol || setterSymbol.kind !== "const" || !isNodeOfType(setterSymbol.declarationNode, "VariableDeclarator")) return null;
58134
- const declarator = setterSymbol.declarationNode;
58135
- if (!isNodeOfType(declarator.id, "ArrayPattern")) return null;
58136
- const stateIdentifier = declarator.id.elements?.[0];
58137
- const setterIdentifier = declarator.id.elements?.[1];
58138
- if (!isNodeOfType(stateIdentifier, "Identifier") || !isNodeOfType(setterIdentifier, "Identifier") || setterIdentifier !== setterSymbol.bindingIdentifier || !isNodeOfType(declarator.init, "CallExpression") || !isReactApiCall(declarator.init, USE_STATE_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return null;
58139
- const initializer = declarator.init.arguments?.[0];
58140
- if (!isNodeOfType(initializer, "Literal") || initializer.value !== 0) return null;
58141
- const stateSymbol = context.scopes.symbolFor(stateIdentifier);
58142
- if (!stateSymbol) return null;
58143
- const stateDerivedSymbolIds = new Set([stateSymbol.id]);
58144
- let didAddDerivedSymbol = true;
58145
- while (didAddDerivedSymbol) {
58146
- didAddDerivedSymbol = false;
58147
- for (const statement of componentBody.body ?? []) {
58148
- if (!isNodeOfType(statement, "VariableDeclaration")) continue;
58149
- for (const candidateDeclarator of statement.declarations ?? []) {
58150
- if (!isNodeOfType(candidateDeclarator.id, "Identifier") || !candidateDeclarator.init) continue;
58151
- const candidateInitializer = stripParenExpression(candidateDeclarator.init);
58152
- if (isFunctionLike$1(candidateInitializer) || isNodeOfType(candidateInitializer, "CallExpression") && isReactApiCall(candidateInitializer, USE_CALLBACK_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) continue;
58153
- if (!expressionReadsDerivedSymbol(context, candidateInitializer, stateDerivedSymbolIds)) continue;
58154
- const candidateSymbol = context.scopes.symbolFor(candidateDeclarator.id);
58155
- if (candidateSymbol?.kind === "const" && candidateSymbol.references.every((reference) => reference.flag === "read" && !isTransparentAssignmentTarget(reference.identifier)) && !stateDerivedSymbolIds.has(candidateSymbol.id)) {
58156
- stateDerivedSymbolIds.add(candidateSymbol.id);
58157
- didAddDerivedSymbol = true;
58158
- }
58159
- }
58160
- }
58161
- }
58162
- const staticSpreadVisibilityBySymbolId = /* @__PURE__ */ new Map();
58163
- const hasOnlyStaticObjectReferences = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
58164
- const symbol = context.scopes.symbolFor(identifier);
58165
- if (!symbol) return false;
58166
- if (visitedSymbolIds.has(symbol.id)) return true;
58167
- const nextVisitedSymbolIds = new Set(visitedSymbolIds);
58168
- nextVisitedSymbolIds.add(symbol.id);
58169
- let hasUnknownReference = false;
58170
- walkAst(componentBody, (node) => {
58171
- if (hasUnknownReference || !isNodeOfType(node, "Identifier") || context.scopes.symbolFor(node)?.id !== symbol.id || node === symbol.bindingIdentifier) return;
58172
- const referenceRoot = findTransparentExpressionRoot(node);
58173
- const parent = referenceRoot.parent;
58174
- if (isNodeOfType(parent, "JSXSpreadAttribute") && parent.argument === referenceRoot) return;
58175
- if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier") && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const" && hasOnlyStaticObjectReferences(parent.id, nextVisitedSymbolIds)) return;
58176
- hasUnknownReference = true;
58177
- return false;
58178
- });
58179
- return !hasUnknownReference;
58180
- };
58181
- const classifyStaticSpreadObject = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
58182
- const symbol = context.scopes.symbolFor(identifier);
58183
- if (!symbol || visitedSymbolIds.has(symbol.id)) return "unknown";
58184
- const cachedVisibility = staticSpreadVisibilityBySymbolId.get(symbol.id);
58185
- if (cachedVisibility) return cachedVisibility;
58186
- if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || symbol.declarationNode.id !== symbol.bindingIdentifier || !symbol.declarationNode.init) return "unknown";
58187
- if (!hasOnlyStaticObjectReferences(identifier)) return "unknown";
58188
- const initializer = stripParenExpression(symbol.declarationNode.init);
58189
- const nextVisitedSymbolIds = new Set(visitedSymbolIds);
58190
- nextVisitedSymbolIds.add(symbol.id);
58191
- if (isNodeOfType(initializer, "Identifier")) {
58192
- const visibility = classifyStaticSpreadObject(initializer, nextVisitedSymbolIds);
58193
- staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
58194
- return visibility;
58195
- }
58196
- if (!isNodeOfType(initializer, "ObjectExpression")) return "unknown";
58197
- let visibility = "non-visible";
58198
- for (const property of initializer.properties ?? []) {
58199
- const propertyName = getStaticObjectPropertyName(property);
58200
- if (!isNodeOfType(property, "Property") || !propertyName) {
58201
- visibility = "unknown";
58202
- break;
58203
- }
58204
- if (expressionReadsDerivedSymbol(context, property.value, stateDerivedSymbolIds) && !isNonVisibleJsxSpreadProperty(propertyName)) visibility = "visible";
58205
- }
58206
- staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
58207
- return visibility;
58208
- };
58209
- let hasNonAriaReference = false;
58210
- walkAst(componentBody, (node) => {
58211
- if (hasNonAriaReference) return false;
58212
- if (!isNodeOfType(node, "Identifier") || !stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) return;
58213
- if (findEnclosingFunction$1(node) !== componentFunction) return;
58214
- const parent = node.parent;
58215
- if (parent && (isNodeOfType(parent, "MemberExpression") && parent.property === node && !parent.computed || isNodeOfType(parent, "Property") && parent.key === node && !parent.computed)) return;
58216
- let cursor = parent;
58217
- while (cursor && cursor !== componentBody) {
58218
- if (isFunctionLike$1(cursor)) return;
58219
- if (isNodeOfType(cursor, "JSXSpreadAttribute")) {
58220
- if (isNodeOfType(node, "Identifier") && classifyStaticSpreadObject(node) === "visible") hasNonAriaReference = true;
58221
- return;
58222
- }
58223
- if (isNodeOfType(cursor, "JSXAttribute")) {
58224
- if (isEventHandlerAttribute(cursor)) return;
58225
- if (!isInsideIdOrAriaAttribute(node)) hasNonAriaReference = true;
58226
- return;
58227
- }
58228
- if (isNodeOfType(cursor, "ReturnStatement")) {
58229
- hasNonAriaReference = true;
58230
- return;
58231
- }
58232
- cursor = cursor.parent;
58233
- }
58234
- });
58235
- return hasNonAriaReference ? stateIdentifier.name : null;
58236
- };
58237
- const isExactViewportSubscriptionEffect = (context, effectCall, callback) => {
58238
- if (!isReactApiCall(effectCall, USE_EFFECT_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return false;
58239
- if (!isFunctionLike$1(callback) || callback.async || !isNodeOfType(callback.body, "BlockStatement")) return false;
58240
- const statements = getCallbackStatements(callback);
58241
- if (statements.length !== 4) return false;
58242
- const handlerDeclaration = statements[0];
58243
- if (!isNodeOfType(handlerDeclaration, "VariableDeclaration") || handlerDeclaration.kind !== "const" || handlerDeclaration.declarations?.length !== 1) return false;
58244
- const handlerDeclarator = handlerDeclaration.declarations[0];
58245
- if (!isNodeOfType(handlerDeclarator.id, "Identifier") || !isFunctionLike$1(handlerDeclarator.init)) return false;
58246
- const handlerStatements = getCallbackStatements(handlerDeclarator.init);
58247
- if (handlerStatements.length !== 1) return false;
58248
- const handlerSetter = getDirectWindowWidthSetter(context, unwrapReturnExpression(handlerStatements[0]));
58249
- const subscribedHandler = getResizeListenerHandler(context, statements[1], "addEventListener");
58250
- const immediateSetter = getDirectWindowWidthSetter(context, statements[2]);
58251
- const cleanupHandler = getCleanupResizeHandler(context, statements[3]);
58252
- if (!handlerSetter || !subscribedHandler || !immediateSetter || !cleanupHandler) return false;
58253
- const handlerSymbol = context.scopes.symbolFor(handlerDeclarator.id);
58254
- if (!handlerSymbol || context.scopes.symbolFor(subscribedHandler) !== handlerSymbol || context.scopes.symbolFor(cleanupHandler) !== handlerSymbol) return false;
58255
- if (!isNodeOfType(handlerSetter.callee, "Identifier") || !isNodeOfType(immediateSetter.callee, "Identifier") || context.scopes.symbolFor(handlerSetter.callee) !== context.scopes.symbolFor(immediateSetter.callee)) return false;
58256
- const componentFunction = findEnclosingFunction$1(effectCall);
58257
- if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return false;
58258
- return findExactViewportState(context, componentFunction, immediateSetter) !== null;
58259
- };
58260
57015
  const renderingHydrationNoFlicker = defineRule({
58261
57016
  id: "rendering-hydration-no-flicker",
58262
57017
  title: "useEffect setState flashes on mount",
@@ -58269,14 +57024,7 @@ const renderingHydrationNoFlicker = defineRule({
58269
57024
  if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
58270
57025
  const callback = getEffectCallback(node);
58271
57026
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
58272
- if (isExactViewportSubscriptionEffect(context, node, callback)) {
58273
- context.report({
58274
- node,
58275
- message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
58276
- });
58277
- return;
58278
- }
58279
- const bodyStatements = getCallbackStatements(callback);
57027
+ const bodyStatements = (isNodeOfType(callback.body, "BlockStatement") ? callback.body.body ?? [] : [callback.body]).filter((statement) => !isNoOpStatement(statement));
58280
57028
  if (bodyStatements.length !== 1) return;
58281
57029
  const soleStatement = bodyStatements[0];
58282
57030
  if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
@@ -58439,125 +57187,6 @@ const ASYNC_DATA_CALLEE_NAMES = new Set([
58439
57187
  const RESOURCE_LOAD_EVENT_ATTRIBUTE_PATTERN = /^on(?:Load|Error|Abort|Progress|CanPlay|Stalled|Suspend|Waiting|Ended)/;
58440
57188
  const JSX_EVENT_HANDLER_ATTRIBUTE_PATTERN = /^on[A-Z]/;
58441
57189
  const REDUX_DISPATCH_HOOK_PATTERN = /^use\w*Dispatch$/;
58442
- const FILE_READER_READ_METHOD_NAMES = new Set([
58443
- "readAsArrayBuffer",
58444
- "readAsBinaryString",
58445
- "readAsDataURL",
58446
- "readAsText"
58447
- ]);
58448
- const isGlobalFileReaderConstruction = (expression, context) => {
58449
- if (!expression) return false;
58450
- const unwrappedExpression = stripParenExpression(expression);
58451
- if (!isNodeOfType(unwrappedExpression, "NewExpression") || !isNodeOfType(unwrappedExpression.callee, "Identifier")) return false;
58452
- return unwrappedExpression.callee.name === "FileReader" && context.scopes.isGlobalReference(unwrappedExpression.callee);
58453
- };
58454
- const getFileReaderOriginStartBefore = (readerSymbol, readCall, context) => {
58455
- const readFunction = findEnclosingFunction$1(readCall);
58456
- let latestValue = null;
58457
- let latestStart = null;
58458
- if (readerSymbol.initializer && findEnclosingFunction$1(readerSymbol.declarationNode) === readFunction && readerSymbol.declarationNode.range[0] < readCall.range[0]) {
58459
- latestValue = readerSymbol.initializer;
58460
- latestStart = readerSymbol.declarationNode.range[0];
58461
- }
58462
- for (const reference of readerSymbol.references) {
58463
- if (reference.flag === "read" || reference.identifier.range[0] >= readCall.range[0] || latestStart !== null && reference.identifier.range[0] <= latestStart || findEnclosingFunction$1(reference.identifier) !== readFunction) continue;
58464
- const assignment = reference.identifier.parent;
58465
- if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== reference.identifier) continue;
58466
- latestValue = assignment.right;
58467
- latestStart = reference.identifier.range[0];
58468
- }
58469
- return isGlobalFileReaderConstruction(latestValue, context) ? latestStart : null;
58470
- };
58471
- const resolveLoadingCompletionFunction = (expression, context) => {
58472
- const directFunction = resolveExactLocalFunction(expression, context.scopes);
58473
- if (directFunction) return directFunction;
58474
- const unwrappedExpression = stripParenExpression(expression);
58475
- if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
58476
- const symbol = context.scopes.symbolFor(unwrappedExpression);
58477
- const initializer = symbol ? getDirectUnreassignedInitializer(symbol) : null;
58478
- if (!initializer || !isNodeOfType(initializer, "CallExpression") || !isReactApiCall(initializer, "useCallback", context.scopes)) return null;
58479
- const callback = initializer.arguments?.[0];
58480
- return callback && isFunctionLike$1(callback) ? callback : null;
58481
- };
58482
- const isSetterBooleanCall = (node, setterSymbol, value, context) => {
58483
- if (!isNodeOfType(node, "CallExpression")) return false;
58484
- const callee = stripParenExpression(node.callee);
58485
- const argument = node.arguments?.[0];
58486
- const unwrappedArgument = argument ? stripParenExpression(argument) : null;
58487
- return Boolean(isNodeOfType(callee, "Identifier") && context.scopes.symbolFor(callee) === setterSymbol && unwrappedArgument && isNodeOfType(unwrappedArgument, "Literal") && unwrappedArgument.value === value);
58488
- };
58489
- const functionClearsLoadingState = (functionNode, setterSymbol, context, visitedFunctions) => {
58490
- if (visitedFunctions.has(functionNode) || !isFunctionLike$1(functionNode)) return false;
58491
- visitedFunctions.add(functionNode);
58492
- let didClearLoadingState = false;
58493
- walkAst(functionNode.body, (child) => {
58494
- if (didClearLoadingState) return false;
58495
- if (child !== functionNode.body && isFunctionLike$1(child)) return false;
58496
- if (!isNodeOfType(child, "CallExpression")) return;
58497
- if (isSetterBooleanCall(child, setterSymbol, false, context)) {
58498
- didClearLoadingState = true;
58499
- return false;
58500
- }
58501
- const helperFunction = resolveLoadingCompletionFunction(child.callee, context);
58502
- if (helperFunction && functionClearsLoadingState(helperFunction, setterSymbol, context, visitedFunctions)) {
58503
- didClearLoadingState = true;
58504
- return false;
58505
- }
58506
- });
58507
- return didClearLoadingState;
58508
- };
58509
- const getLatestFileReaderCallbackBefore = (readCall, readerSymbol, propertyName, originStart, context) => {
58510
- const readFunction = findEnclosingFunction$1(readCall);
58511
- if (!readFunction || !isFunctionLike$1(readFunction)) return null;
58512
- let callback = null;
58513
- let callbackStart = originStart;
58514
- walkAst(readFunction.body, (child) => {
58515
- if (child !== readFunction.body && isFunctionLike$1(child)) return false;
58516
- if (!isNodeOfType(child, "AssignmentExpression") || child.operator !== "=" || child.range[0] >= readCall.range[0] || child.range[0] <= callbackStart || !isNodeOfType(child.left, "MemberExpression") || getStaticPropertyName(child.left) !== propertyName) return;
58517
- const receiver = stripParenExpression(child.left.object);
58518
- if (!isNodeOfType(receiver, "Identifier") || context.scopes.symbolFor(receiver) !== readerSymbol) return;
58519
- callback = child.right;
58520
- callbackStart = child.range[0];
58521
- });
58522
- return callback;
58523
- };
58524
- const setterStartsLoadingBefore = (readCall, setterSymbol, context) => {
58525
- const readFunction = findEnclosingFunction$1(readCall);
58526
- if (!readFunction || !isFunctionLike$1(readFunction)) return false;
58527
- let didStartLoading = false;
58528
- walkAst(readFunction.body, (child) => {
58529
- if (didStartLoading) return false;
58530
- if (child !== readFunction.body && isFunctionLike$1(child)) return false;
58531
- if (!isNodeOfType(child, "CallExpression") || child.range[0] >= readCall.range[0]) return;
58532
- if (isSetterBooleanCall(child, setterSymbol, true, context)) {
58533
- didStartLoading = true;
58534
- return false;
58535
- }
58536
- });
58537
- return didStartLoading;
58538
- };
58539
- const setterTracksFileReader = (functionBody, setterSymbol, context) => {
58540
- let didFindFileReaderLifecycle = false;
58541
- walkAst(functionBody, (child) => {
58542
- if (didFindFileReaderLifecycle) return false;
58543
- if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || !FILE_READER_READ_METHOD_NAMES.has(getStaticPropertyName(child.callee) ?? "")) return;
58544
- const receiver = stripParenExpression(child.callee.object);
58545
- if (!isNodeOfType(receiver, "Identifier")) return;
58546
- const readerSymbol = context.scopes.symbolFor(receiver);
58547
- if (!readerSymbol) return;
58548
- const originStart = getFileReaderOriginStartBefore(readerSymbol, child, context);
58549
- if (originStart === null || !setterStartsLoadingBefore(child, setterSymbol, context)) return;
58550
- const loadCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onload", originStart, context);
58551
- const errorCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onerror", originStart, context);
58552
- const loadFunction = loadCallback ? resolveLoadingCompletionFunction(loadCallback, context) : null;
58553
- const errorFunction = errorCallback ? resolveLoadingCompletionFunction(errorCallback, context) : null;
58554
- if (loadFunction && errorFunction && functionClearsLoadingState(loadFunction, setterSymbol, context, /* @__PURE__ */ new Set()) && functionClearsLoadingState(errorFunction, setterSymbol, context, /* @__PURE__ */ new Set())) {
58555
- didFindFileReaderLifecycle = true;
58556
- return false;
58557
- }
58558
- });
58559
- return didFindFileReaderLifecycle;
58560
- };
58561
57190
  const hasAsyncLoadingWork = (fnBody, setterName) => {
58562
57191
  let found = false;
58563
57192
  walkAst(fnBody, (child) => {
@@ -58731,8 +57360,6 @@ const renderingUsetransitionLoading = defineRule({
58731
57360
  const fnBody = enclosingFunctionBody(node);
58732
57361
  if (fnBody && hasAsyncLoadingWork(fnBody, setterName)) return;
58733
57362
  if (fnBody && setterName) {
58734
- const setterSymbol = isNodeOfType(secondBinding, "Identifier") ? context.scopes.symbolFor(secondBinding) : null;
58735
- if (setterSymbol && setterTracksFileReader(fnBody, setterSymbol, context)) return;
58736
57363
  if (setterEscapes(fnBody, setterName, node)) return;
58737
57364
  if (setterCalledAlongsideAsyncSignal(fnBody, setterName)) return;
58738
57365
  if (setterCalledInEventListenerHandler(fnBody, setterName)) return;
@@ -59064,7 +57691,7 @@ const rerenderDependencies = defineRule({
59064
57691
  severity: "error",
59065
57692
  recommendation: "Move it into a useMemo, useRef, or a constant outside the component so it stays the same between renders.",
59066
57693
  create: (context) => ({ CallExpression(node) {
59067
- if (!isReactHookCall(node, HOOKS_WITH_DEPS, context.scopes) || node.arguments.length < 2) return;
57694
+ if (!isHookCall$2(node, HOOKS_WITH_DEPS) || node.arguments.length < 2) return;
59068
57695
  const depsNode = node.arguments[1];
59069
57696
  if (!isNodeOfType(depsNode, "ArrayExpression")) return;
59070
57697
  for (const element of depsNode.elements ?? []) {
@@ -59319,7 +57946,7 @@ const rerenderLazyRefInit = defineRule({
59319
57946
  category: "Performance",
59320
57947
  recommendation: "Initialize the ref lazily so expensive values are not rebuilt and discarded on every render.",
59321
57948
  create: (context) => ({ CallExpression(node) {
59322
- if (!isReactHookCall(node, "useRef", context.scopes) || !node.arguments?.length) return;
57949
+ if (!isHookCall$2(node, "useRef") || !node.arguments?.length) return;
59323
57950
  const initializer = stripParenExpression(node.arguments[0]);
59324
57951
  const isPlainCall = isNodeOfType(initializer, "CallExpression");
59325
57952
  const isNewCall = isNodeOfType(initializer, "NewExpression");
@@ -59383,7 +58010,7 @@ const rerenderLazyStateInit = defineRule({
59383
58010
  category: "Performance",
59384
58011
  recommendation: "Wrap expensive initial state in an arrow function so the initializer does not rerun and get thrown away on every render.",
59385
58012
  create: (context) => ({ CallExpression(node) {
59386
- if (!isReactHookCall(node, "useState", context.scopes) || !node.arguments?.length) return;
58013
+ if (!isHookCall$2(node, "useState") || !node.arguments?.length) return;
59387
58014
  const initializer = findEagerInitializerCall(node.arguments[0]);
59388
58015
  if (!initializer) return;
59389
58016
  const isConstructor = isNodeOfType(initializer, "NewExpression");
@@ -60146,11 +58773,11 @@ const isInsideConditionTest = (identifier, stopAt) => {
60146
58773
  }
60147
58774
  return false;
60148
58775
  };
60149
- const collectEffectDependencyInfos = (componentBody, setterNames, scopes) => {
58776
+ const collectEffectDependencyInfos = (componentBody, setterNames) => {
60150
58777
  const effectInfos = [];
60151
58778
  walkAst(componentBody, (child) => {
60152
58779
  if (!isNodeOfType(child, "CallExpression")) return;
60153
- if (!isReactHookCall(child, EFFECT_HOOK_NAMES$1, scopes)) return;
58780
+ if (!isHookCall$2(child, EFFECT_HOOK_NAMES$1)) return;
60154
58781
  const dependencyNames = /* @__PURE__ */ new Set();
60155
58782
  for (const argument of child.arguments ?? []) {
60156
58783
  if (!isNodeOfType(argument, "ArrayExpression")) continue;
@@ -60206,14 +58833,13 @@ const collectEffectDependencyInfos = (componentBody, setterNames, scopes) => {
60206
58833
  });
60207
58834
  return effectInfos;
60208
58835
  };
60209
- const collectCustomHookArgumentNames = (componentBody, scopes) => {
58836
+ const collectCustomHookArgumentNames = (componentBody) => {
60210
58837
  const argumentNames = /* @__PURE__ */ new Set();
60211
58838
  walkAst(componentBody, (child) => {
60212
58839
  if (!isNodeOfType(child, "CallExpression")) return;
60213
58840
  if (!isNodeOfType(child.callee, "Identifier")) return;
60214
58841
  const calleeName = child.callee.name;
60215
58842
  if (!isReactHookName(calleeName)) return;
60216
- if (isReactHookCall(child, BUILTIN_HOOK_NAMES, scopes)) return;
60217
58843
  if (BUILTIN_HOOK_NAMES.has(calleeName)) return;
60218
58844
  if (EFFECT_HOOK_NAMES$1.has(calleeName)) return;
60219
58845
  for (const argument of child.arguments ?? []) walkAst(argument, (argumentNode) => {
@@ -60254,21 +58880,21 @@ const rerenderStateOnlyInHandlers = defineRule({
60254
58880
  create: (context) => {
60255
58881
  const checkComponent = (componentBody) => {
60256
58882
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
60257
- const bindings = collectUseStateBindings(componentBody, context.scopes);
58883
+ const bindings = collectUseStateBindings(componentBody);
60258
58884
  if (bindings.length === 0) return;
60259
58885
  if (collectRenderReachableExpressions(componentBody).length === 0) return;
60260
- const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody, context.scopes);
58886
+ const eventHandlerReferenceNames = collectFunctionLikeLocalNames(componentBody);
60261
58887
  const dependencyGraph = buildLocalDependencyGraph(componentBody, eventHandlerReferenceNames);
60262
- const directRenderNames = collectRenderReachableNames(componentBody, context.scopes, eventHandlerReferenceNames);
58888
+ const directRenderNames = collectRenderReachableNames(componentBody, eventHandlerReferenceNames);
60263
58889
  if (hasRenderPhaseNonHookCall(componentBody)) for (const voidMarkedName of collectTopLevelVoidMarkedNames(componentBody)) directRenderNames.add(voidMarkedName);
60264
58890
  const renderReachableNames = expandTransitiveDependencies(directRenderNames, dependencyGraph);
60265
58891
  const setterNames = new Set(bindings.map((binding) => binding.setterName));
60266
- const effectInfos = collectEffectDependencyInfos(componentBody, setterNames, context.scopes);
58892
+ const effectInfos = collectEffectDependencyInfos(componentBody, setterNames);
60267
58893
  const selfEchoValueNames = /* @__PURE__ */ new Set();
60268
58894
  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
58895
  const effectConsumedNames = /* @__PURE__ */ new Set();
60270
58896
  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);
58897
+ for (const hookArgumentName of collectCustomHookArgumentNames(componentBody)) effectConsumedNames.add(hookArgumentName);
60272
58898
  for (const reachableName of expandTransitiveDependencies(effectConsumedNames, dependencyGraph)) renderReachableNames.add(reachableName);
60273
58899
  const calledSetterNames = /* @__PURE__ */ new Set();
60274
58900
  walkAst(componentBody, (child) => {
@@ -68169,7 +66795,7 @@ const declarationAwaitsGate = (declaration, context) => {
68169
66795
  if (!isNodeOfType(argument, "CallExpression")) continue;
68170
66796
  if (hasPossibleStaticMemberCallWrite(argument, context.scopes)) return true;
68171
66797
  if (getOrderIndependentLocalFunction(argument, context.scopes) !== null) continue;
68172
- const calleeName = getCalleeName$1(argument);
66798
+ const calleeName = getCalleeName$2(argument);
68173
66799
  if (!calleeName) continue;
68174
66800
  if (isAuthGuardName(calleeName)) return true;
68175
66801
  const [leadingToken] = tokenizeIdentifierWords(calleeName);
@@ -68809,7 +67435,7 @@ const walkServerFnChain = (outerNode) => {
68809
67435
  if (!isNodeOfType(outerNode.callee, "MemberExpression")) return result;
68810
67436
  let currentNode = stripParenExpression(outerNode.callee.object);
68811
67437
  while (isNodeOfType(currentNode, "CallExpression")) {
68812
- const calleeName = getCalleeName$1(currentNode);
67438
+ const calleeName = getCalleeName$2(currentNode);
68813
67439
  if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) {
68814
67440
  result.isServerFnChain = true;
68815
67441
  const optionsArgument = currentNode.arguments?.[0];
@@ -72941,17 +71567,6 @@ const reactDoctorRules = [
72941
71567
  requires: [...new Set(["react", ...noRedundantShouldComponentUpdate.requires ?? []])]
72942
71568
  }
72943
71569
  },
72944
- {
72945
- key: "react-doctor/no-ref-callback-cleanup-before-react-19",
72946
- id: "no-ref-callback-cleanup-before-react-19",
72947
- source: "react-doctor",
72948
- originallyExternal: false,
72949
- rule: {
72950
- ...noRefCallbackCleanupBeforeReact19,
72951
- framework: "global",
72952
- category: "Bugs"
72953
- }
72954
- },
72955
71570
  {
72956
71571
  key: "react-doctor/no-ref-current-in-render",
72957
71572
  id: "no-ref-current-in-render",