oxlint-plugin-react-doctor 0.7.9-dev.8835214 → 0.7.9-dev.95f9937

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 +260 -2285
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1237,13 +1237,12 @@ const stripParenExpression = (node) => {
1237
1237
  };
1238
1238
  //#endregion
1239
1239
  //#region src/plugin/utils/resolve-const-identifier-alias.ts
1240
- const resolveConstIdentifierAlias = (identifier, scopes, allowPatternBinding = false) => {
1240
+ const resolveConstIdentifierAlias = (identifier, scopes) => {
1241
1241
  if (!isNodeOfType(identifier, "Identifier") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
1242
1242
  const visitedSymbolIds = /* @__PURE__ */ new Set();
1243
1243
  let symbol = scopes.symbolFor(identifier);
1244
1244
  while (symbol?.kind === "const") {
1245
- if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return null;
1246
- if (symbol.declarationNode.id !== symbol.bindingIdentifier) return allowPatternBinding ? symbol : null;
1245
+ if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
1247
1246
  visitedSymbolIds.add(symbol.id);
1248
1247
  const initializer = stripParenExpression(symbol.initializer);
1249
1248
  if (!isNodeOfType(initializer, "Identifier")) return symbol;
@@ -6737,28 +6736,6 @@ const asyncParallel = defineRule({
6737
6736
  }
6738
6737
  });
6739
6738
  //#endregion
6740
- //#region src/plugin/utils/collect-function-return-statements.ts
6741
- const collectFunctionReturnStatements = (functionNode) => {
6742
- if (!isFunctionLike$1(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return [];
6743
- const returnStatements = [];
6744
- walkAst(functionNode.body, (node) => {
6745
- if (node !== functionNode.body && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
6746
- if (isNodeOfType(node, "ReturnStatement")) returnStatements.push(node);
6747
- });
6748
- return returnStatements;
6749
- };
6750
- //#endregion
6751
- //#region src/plugin/utils/is-nullish-expression.ts
6752
- const isNullishExpression = (expression) => isNodeOfType(expression, "Literal") && expression.value === null || isNodeOfType(expression, "Identifier") && expression.name === "undefined" || isNodeOfType(expression, "UnaryExpression") && expression.operator === "void";
6753
- //#endregion
6754
- //#region src/plugin/utils/strip-this-parameter.ts
6755
- const stripThisParameter = (parameters) => {
6756
- const firstParameter = parameters[0];
6757
- if (!firstParameter) return parameters;
6758
- if (isNodeOfType(firstParameter, "Identifier") && firstParameter.name === "this") return parameters.slice(1);
6759
- return parameters;
6760
- };
6761
- //#endregion
6762
6739
  //#region src/plugin/rules/security/auth-token-in-web-storage.ts
6763
6740
  const MESSAGE$60 = "Storing an auth token in `localStorage`/`sessionStorage` exposes it to any XSS on the page: JavaScript can read web storage and exfiltrate the token. Keep tokens in an `HttpOnly`, `Secure`, `SameSite` cookie instead.";
6764
6741
  const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
@@ -6770,8 +6747,12 @@ const STORAGE_GLOBALS = new Set([
6770
6747
  const SENSITIVE_KEY_PATTERN = /token|jwt|secret|password|passwd|credential|api[-_]?key|bearer|private[-_]?key/i;
6771
6748
  const NON_AUTH_TOKEN_PATTERN = /csrf|xsrf|device|fcm|apns|push|design|tokeniz|syntax|css|theme|color/i;
6772
6749
  const STRONG_AUTH_KEY_PATTERN = /jwt|secret|password|passwd|credential|private[-_]?key|api[-_]?key|bearer|access[-_]?token|refresh[-_]?token|auth[-_]?token|id[-_]?token|session/i;
6750
+ const PRODUCT_API_KEY_RECORDS_PATTERN = /(?:^|[._:-])(?:created|saved|integration|mailing)[-_]?api[-_]?keys$/i;
6751
+ const PRODUCT_API_KEY_COLLECTION_PATTERN = /[._:-](?:created|generated|saved)[-_]?api[-_]?keys$/i;
6773
6752
  const isAuthCredentialKey = (key) => {
6753
+ if (PRODUCT_API_KEY_RECORDS_PATTERN.test(key)) return false;
6774
6754
  if (!SENSITIVE_KEY_PATTERN.test(key)) return false;
6755
+ if (PRODUCT_API_KEY_COLLECTION_PATTERN.test(key)) return false;
6775
6756
  if (NON_AUTH_TOKEN_PATTERN.test(key) && !STRONG_AUTH_KEY_PATTERN.test(key)) return false;
6776
6757
  return true;
6777
6758
  };
@@ -6780,63 +6761,11 @@ const isDirectWebStorageObject = (node) => {
6780
6761
  if (isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.object, "Identifier") && STORAGE_GLOBALS.has(node.object.name) && isNodeOfType(node.property, "Identifier")) return STORAGE_NAMES.has(node.property.name);
6781
6762
  return false;
6782
6763
  };
6783
- const immutableInitializer = (identifier, visitedIdentifiers = /* @__PURE__ */ new Set()) => {
6784
- if (visitedIdentifiers.has(identifier)) return null;
6785
- visitedIdentifiers.add(identifier);
6786
- const binding = findVariableInitializer(identifier, identifier.name);
6787
- if (!binding?.initializer) return null;
6788
- if (isNodeOfType(binding.initializer, "FunctionDeclaration")) return binding.initializer;
6789
- const declarator = binding.bindingIdentifier.parent;
6790
- if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return null;
6791
- const declaration = declarator.parent;
6792
- if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) return null;
6793
- if (declaration.kind !== "const") return null;
6794
- const initializer = stripParenExpression(binding.initializer);
6795
- if (!isNodeOfType(initializer, "Identifier")) return initializer;
6796
- return immutableInitializer(initializer, visitedIdentifiers) ?? initializer;
6797
- };
6798
- const isWebStorageFactoryResult = (node, visitedNodes) => {
6799
- const expression = stripParenExpression(node);
6800
- if (isWebStorageObject(expression, new Set(visitedNodes))) return true;
6801
- if (isNodeOfType(expression, "ConditionalExpression")) {
6802
- const consequent = stripParenExpression(expression.consequent);
6803
- const alternate = stripParenExpression(expression.alternate);
6804
- return (isNullishExpression(consequent) || isWebStorageFactoryResult(consequent, visitedNodes)) && (isNullishExpression(alternate) || isWebStorageFactoryResult(alternate, visitedNodes)) && (!isNullishExpression(consequent) || !isNullishExpression(alternate));
6805
- }
6806
- if (isNodeOfType(expression, "LogicalExpression")) {
6807
- if (expression.operator === "&&") return isWebStorageFactoryResult(expression.right, visitedNodes);
6808
- const left = stripParenExpression(expression.left);
6809
- const right = stripParenExpression(expression.right);
6810
- const isLeftStorage = isWebStorageFactoryResult(left, visitedNodes);
6811
- const isRightStorage = isWebStorageFactoryResult(right, visitedNodes);
6812
- return (isLeftStorage || isNullishExpression(left)) && (isRightStorage || isNullishExpression(right)) && (isLeftStorage || isRightStorage);
6813
- }
6814
- return false;
6815
- };
6816
- const isWebStorageObject = (node, visitedNodes = /* @__PURE__ */ new Set()) => {
6817
- const expression = stripParenExpression(node);
6818
- if (visitedNodes.has(expression)) return false;
6819
- visitedNodes.add(expression);
6820
- if (isDirectWebStorageObject(expression)) return true;
6821
- if (isNodeOfType(expression, "Identifier")) {
6822
- const initializer = immutableInitializer(expression);
6823
- return initializer ? isWebStorageObject(initializer, new Set(visitedNodes)) : false;
6824
- }
6825
- if (!isNodeOfType(expression, "CallExpression")) return false;
6826
- const callee = stripParenExpression(expression.callee);
6827
- if (!isNodeOfType(callee, "Identifier")) return false;
6828
- const factory = immutableInitializer(callee);
6829
- if (!isFunctionLike$1(factory)) return false;
6830
- if (isNodeOfType(factory, "ArrowFunctionExpression") && !isNodeOfType(factory.body, "BlockStatement")) return isWebStorageFactoryResult(factory.body, visitedNodes);
6831
- let didReturnWebStorage = false;
6832
- for (const returnStatement of collectFunctionReturnStatements(factory)) {
6833
- if (!returnStatement.argument) continue;
6834
- const strippedReturn = stripParenExpression(returnStatement.argument);
6835
- if (isNullishExpression(strippedReturn)) continue;
6836
- if (!isWebStorageFactoryResult(strippedReturn, visitedNodes)) return false;
6837
- didReturnWebStorage = true;
6838
- }
6839
- return didReturnWebStorage;
6764
+ const isWebStorageObject = (node) => {
6765
+ if (isDirectWebStorageObject(node)) return true;
6766
+ if (!isNodeOfType(node, "Identifier")) return false;
6767
+ const binding = findVariableInitializer(node, node.name);
6768
+ return binding?.initializer ? isDirectWebStorageObject(binding.initializer) : false;
6840
6769
  };
6841
6770
  const resolveStaticKeyString = (node) => {
6842
6771
  if (isNodeOfType(node, "Literal") && typeof node.value === "string") return node.value;
@@ -6856,56 +6785,6 @@ const staticMemberName = (member) => {
6856
6785
  if (member.computed && isNodeOfType(member.property, "Literal") && typeof member.property.value === "string") return member.property.value;
6857
6786
  return null;
6858
6787
  };
6859
- const parameterIndex = (expression, parameterSymbolIds, scopes, canUnwrapSerialization, visitedNodes = /* @__PURE__ */ new Set()) => {
6860
- const strippedExpression = stripParenExpression(expression);
6861
- if (visitedNodes.has(strippedExpression)) return null;
6862
- visitedNodes.add(strippedExpression);
6863
- if (isNodeOfType(strippedExpression, "Identifier")) {
6864
- const directSymbolId = scopes.symbolFor(strippedExpression)?.id;
6865
- const directIndex = directSymbolId === void 0 ? -1 : parameterSymbolIds.indexOf(directSymbolId);
6866
- if (directIndex !== -1) return directIndex;
6867
- const initializer = immutableInitializer(strippedExpression);
6868
- return initializer ? parameterIndex(initializer, parameterSymbolIds, scopes, canUnwrapSerialization, visitedNodes) : null;
6869
- }
6870
- if (canUnwrapSerialization && isNodeOfType(strippedExpression, "CallExpression") && isNodeOfType(strippedExpression.callee, "MemberExpression") && !strippedExpression.callee.computed && isNodeOfType(strippedExpression.callee.object, "Identifier") && strippedExpression.callee.object.name === "JSON" && isNodeOfType(strippedExpression.callee.property, "Identifier") && strippedExpression.callee.property.name === "stringify") {
6871
- const serializedArgument = strippedExpression.arguments[0];
6872
- return serializedArgument ? parameterIndex(serializedArgument, parameterSymbolIds, scopes, true, visitedNodes) : null;
6873
- }
6874
- return null;
6875
- };
6876
- const storageHelperSinkCache = /* @__PURE__ */ new WeakMap();
6877
- const findStorageHelperSinks = (functionNode, scopes) => {
6878
- const cachedSinks = storageHelperSinkCache.get(functionNode);
6879
- if (cachedSinks) return cachedSinks;
6880
- if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) {
6881
- storageHelperSinkCache.set(functionNode, []);
6882
- return [];
6883
- }
6884
- const parameterSymbolIds = stripThisParameter(functionNode.params).map((parameter) => {
6885
- const strippedParameter = stripParenExpression(parameter);
6886
- const identifier = isNodeOfType(strippedParameter, "Identifier") ? strippedParameter : isNodeOfType(strippedParameter, "AssignmentPattern") && isNodeOfType(strippedParameter.left, "Identifier") ? strippedParameter.left : null;
6887
- return identifier ? scopes.symbolFor(identifier)?.id ?? null : null;
6888
- });
6889
- const helperSinks = [];
6890
- walkAst(functionNode.body, (child) => {
6891
- if (child !== functionNode.body && isFunctionLike$1(child)) return false;
6892
- if (!isNodeOfType(child, "CallExpression")) return;
6893
- const callee = stripParenExpression(child.callee);
6894
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "setItem" || !isWebStorageObject(callee.object)) return;
6895
- const keyExpression = child.arguments[0];
6896
- const valueExpression = child.arguments[1];
6897
- if (!keyExpression || !valueExpression) return;
6898
- const keyParameterIndex = parameterIndex(keyExpression, parameterSymbolIds, scopes, false);
6899
- const valueParameterIndex = parameterIndex(valueExpression, parameterSymbolIds, scopes, true);
6900
- if (keyParameterIndex === null || valueParameterIndex === null) return;
6901
- helperSinks.push({
6902
- keyParameterIndex,
6903
- valueParameterIndex
6904
- });
6905
- });
6906
- storageHelperSinkCache.set(functionNode, helperSinks);
6907
- return helperSinks;
6908
- };
6909
6788
  const authTokenInWebStorage = defineRule({
6910
6789
  id: "auth-token-in-web-storage",
6911
6790
  title: "Auth token in web storage",
@@ -6913,23 +6792,14 @@ const authTokenInWebStorage = defineRule({
6913
6792
  recommendation: "Don't persist auth tokens (JWTs, access/refresh tokens, secrets) in `localStorage`/`sessionStorage`; they're readable by any XSS. Use an `HttpOnly` cookie set by the server.",
6914
6793
  create: skipNonProductionFiles((context) => ({
6915
6794
  CallExpression(node) {
6916
- const callee = stripParenExpression(node.callee);
6917
- const keyArguments = [];
6918
- if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "setItem" && isWebStorageObject(stripParenExpression(callee.object))) {
6919
- const keyArgument = node.arguments[0];
6920
- if (keyArgument) keyArguments.push(keyArgument);
6921
- } else if (isNodeOfType(callee, "Identifier")) {
6922
- const helperFunction = immutableInitializer(callee);
6923
- const helperSinks = helperFunction ? findStorageHelperSinks(helperFunction, context.scopes) : [];
6924
- for (const helperSink of helperSinks) {
6925
- const keyArgument = node.arguments[helperSink.keyParameterIndex];
6926
- if (keyArgument && node.arguments[helperSink.valueParameterIndex]) keyArguments.push(keyArgument);
6927
- }
6928
- }
6929
- if (!keyArguments.some((keyArgument) => {
6930
- const keyString = resolveStaticKeyString(keyArgument);
6931
- return keyString !== null && isAuthCredentialKey(keyString);
6932
- })) return;
6795
+ const callee = node.callee;
6796
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
6797
+ if (!isNodeOfType(callee.property, "Identifier") || callee.property.name !== "setItem") return;
6798
+ if (!isWebStorageObject(stripParenExpression(callee.object))) return;
6799
+ const keyArgument = node.arguments?.[0];
6800
+ if (!keyArgument) return;
6801
+ const keyString = resolveStaticKeyString(keyArgument);
6802
+ if (keyString === null || !isAuthCredentialKey(keyString)) return;
6933
6803
  context.report({
6934
6804
  node,
6935
6805
  message: MESSAGE$60
@@ -7177,6 +7047,9 @@ const isCreateElementCall = (node) => {
7177
7047
  return false;
7178
7048
  };
7179
7049
  //#endregion
7050
+ //#region src/plugin/utils/is-nullish-expression.ts
7051
+ const isNullishExpression = (expression) => isNodeOfType(expression, "Literal") && expression.value === null || isNodeOfType(expression, "Identifier") && expression.name === "undefined" || isNodeOfType(expression, "UnaryExpression") && expression.operator === "void";
7052
+ //#endregion
7180
7053
  //#region src/plugin/rules/react-builtins/button-has-type.ts
7181
7054
  const MISSING_MESSAGE$2 = "Your users can submit the form by accident because a `<button>` with no `type` defaults to submit.";
7182
7055
  const INVALID_MESSAGE = "This button has an invalid `type`, so the browser may treat it like a submit button.";
@@ -7638,7 +7511,6 @@ const areExpressionsStructurallyEqual = (a, b) => {
7638
7511
  if (a.type !== b.type) return false;
7639
7512
  if (isNodeOfType(a, "ThisExpression")) return true;
7640
7513
  if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
7641
- if (isNodeOfType(a, "PrivateIdentifier") && isNodeOfType(b, "PrivateIdentifier")) return a.name === b.name;
7642
7514
  if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
7643
7515
  if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
7644
7516
  if (a.computed !== b.computed) return false;
@@ -8694,6 +8566,17 @@ const createMethodMutationAnalysis = (context) => {
8694
8566
  //#region src/plugin/utils/is-member-property.ts
8695
8567
  const isMemberProperty = (node, propertyName) => Boolean(node && isNodeOfType(node, "MemberExpression") && isNodeOfType(node.property, "Identifier") && node.property.name === propertyName);
8696
8568
  //#endregion
8569
+ //#region src/plugin/utils/collect-function-return-statements.ts
8570
+ const collectFunctionReturnStatements = (functionNode) => {
8571
+ if (!isFunctionLike$1(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return [];
8572
+ const returnStatements = [];
8573
+ walkAst(functionNode.body, (node) => {
8574
+ if (node !== functionNode.body && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
8575
+ if (isNodeOfType(node, "ReturnStatement")) returnStatements.push(node);
8576
+ });
8577
+ return returnStatements;
8578
+ };
8579
+ //#endregion
8697
8580
  //#region src/plugin/utils/statement-always-exits.ts
8698
8581
  const statementAlwaysExits = (statement) => {
8699
8582
  if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement")) return true;
@@ -8960,7 +8843,7 @@ const isReactNamespaceImport = (identifier, scopes) => {
8960
8843
  if (!symbol || !isImportedFromReact(symbol)) return false;
8961
8844
  return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
8962
8845
  };
8963
- const isReactNamespaceReceiver$1 = (receiver, scopes, options) => {
8846
+ const isReactNamespaceReceiver = (receiver, scopes, options) => {
8964
8847
  if (!isNodeOfType(receiver, "Identifier")) return false;
8965
8848
  if (isReactNamespaceImport(receiver, scopes)) return true;
8966
8849
  return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
@@ -8973,7 +8856,7 @@ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) =>
8973
8856
  for (const property of pattern.properties) {
8974
8857
  if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
8975
8858
  const propertyName = getStaticPropertyKeyName(property);
8976
- return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver$1(stripParenExpression(symbol.initializer), scopes, options));
8859
+ return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver(stripParenExpression(symbol.initializer), scopes, options));
8977
8860
  }
8978
8861
  return false;
8979
8862
  };
@@ -8997,7 +8880,7 @@ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds
8997
8880
  return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
8998
8881
  }
8999
8882
  if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
9000
- return isReactNamespaceReceiver$1(stripParenExpression(callee.object), scopes, options);
8883
+ return isReactNamespaceReceiver(stripParenExpression(callee.object), scopes, options);
9001
8884
  };
9002
8885
  //#endregion
9003
8886
  //#region src/plugin/utils/is-proven-browser-api-receiver.ts
@@ -13653,7 +13536,7 @@ const getPromiseChainCallForCallback = (candidate) => {
13653
13536
  if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
13654
13537
  return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
13655
13538
  };
13656
- const collectInvokedFunctions = (effectCallback, includePromiseCallbacks) => {
13539
+ const collectEffectInvokedFunctions = (effectCallback) => {
13657
13540
  const invokedFunctions = new Set([effectCallback]);
13658
13541
  const localFunctionBindings = /* @__PURE__ */ new Map();
13659
13542
  const calledBindingNames = /* @__PURE__ */ new Set();
@@ -13687,14 +13570,12 @@ const collectInvokedFunctions = (effectCallback, includePromiseCallbacks) => {
13687
13570
  calledBindingNames.add(callee.name);
13688
13571
  return;
13689
13572
  }
13690
- if (includePromiseCallbacks && isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
13573
+ if (isPromiseChainCall(callee)) for (const callArgument of child.arguments ?? []) enqueue(callArgument);
13691
13574
  });
13692
13575
  for (const calledName of calledBindingNames) enqueue(localFunctionBindings.get(calledName));
13693
13576
  }
13694
13577
  return invokedFunctions;
13695
13578
  };
13696
- const collectEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, true);
13697
- const collectSynchronouslyEffectInvokedFunctions = (effectCallback) => collectInvokedFunctions(effectCallback, false);
13698
13579
  //#endregion
13699
13580
  //#region src/plugin/utils/is-react-hook-name.ts
13700
13581
  const isReactHookName = (name) => {
@@ -13853,19 +13734,15 @@ const resolveReactRefSymbol = (memberExpression, scopes) => {
13853
13734
  if (!isNodeOfType(initializer, "CallExpression")) return null;
13854
13735
  return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
13855
13736
  };
13856
- const resolveReactRefCurrentOriginSymbol = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13737
+ const hasReactRefCurrentOrigin = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13857
13738
  const expression = stripParenExpression(node);
13858
- const refSymbol = resolveReactRefSymbol(expression, scopes);
13859
- if (refSymbol) return refSymbol;
13860
- if (!isNodeOfType(expression, "Identifier")) return null;
13861
- const symbol = scopes.symbolFor(expression);
13862
- if (!symbol || visitedSymbolIds.has(symbol.id)) return null;
13863
- const initializer = getDirectUnreassignedInitializer(symbol);
13864
- if (!initializer) return null;
13739
+ if (resolveReactRefSymbol(expression, scopes)) return true;
13740
+ if (!isNodeOfType(expression, "Identifier")) return false;
13741
+ const symbol = resolveConstIdentifierAlias(expression, scopes);
13742
+ if (!symbol?.initializer || visitedSymbolIds.has(symbol.id)) return false;
13865
13743
  visitedSymbolIds.add(symbol.id);
13866
- return resolveReactRefCurrentOriginSymbol(initializer, scopes, visitedSymbolIds);
13744
+ return hasReactRefCurrentOrigin(symbol.initializer, scopes, visitedSymbolIds);
13867
13745
  };
13868
- const hasReactRefCurrentOrigin = (node, scopes) => resolveReactRefCurrentOriginSymbol(node, scopes) !== null;
13869
13746
  //#endregion
13870
13747
  //#region src/plugin/utils/walk-inside-statement-blocks.ts
13871
13748
  const walkInsideStatementBlocks = (node, visitor) => {
@@ -13883,7 +13760,6 @@ const walkInsideStatementBlocks = (node, visitor) => {
13883
13760
  };
13884
13761
  //#endregion
13885
13762
  //#region src/plugin/rules/state-and-effects/utils/is-subscribe-like-call-expression.ts
13886
- const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
13887
13763
  const getSubscribeLikeMethodName = (node) => {
13888
13764
  if (!isNodeOfType(node, "CallExpression")) return null;
13889
13765
  if (!isNodeOfType(node.callee, "MemberExpression")) return null;
@@ -13894,11 +13770,6 @@ const isSubscribeLikeCallExpression = (node) => {
13894
13770
  const methodName = getSubscribeLikeMethodName(node);
13895
13771
  return methodName !== null && SUBSCRIPTION_METHOD_NAMES.has(methodName);
13896
13772
  };
13897
- const getSubscribeOrObserveMethodName = (node) => {
13898
- const methodName = getSubscribeLikeMethodName(node);
13899
- return methodName !== null && (SUBSCRIPTION_METHOD_NAMES.has(methodName) || methodName === "observe") ? methodName : null;
13900
- };
13901
- const isSubscribeOrObserveCallExpression = (node) => getSubscribeOrObserveMethodName(node) !== null;
13902
13773
  const isCleanupReturningSubscribeLikeCallExpression = (node) => {
13903
13774
  const methodName = getSubscribeLikeMethodName(node);
13904
13775
  if (methodName === null || !CLEANUP_RETURNING_SUBSCRIPTION_METHOD_NAMES.has(methodName)) return false;
@@ -13951,6 +13822,7 @@ const isNodeReachableWithinFunction = (node, context) => {
13951
13822
  };
13952
13823
  //#endregion
13953
13824
  //#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
13825
+ const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
13954
13826
  const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
13955
13827
  const REPLAYABLE_ITERATOR_COLLECTION_CACHE = /* @__PURE__ */ new WeakMap();
13956
13828
  const REACT_REF_EFFECT_ANALYSIS_CACHE = /* @__PURE__ */ new WeakMap();
@@ -13960,6 +13832,10 @@ const RESOURCE_NOUN_BY_KIND = {
13960
13832
  socket: "connection"
13961
13833
  };
13962
13834
  const isSocketConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && SOCKET_CONSTRUCTOR_NAMES_REQUIRING_CLEANUP.has(node.callee.name);
13835
+ const isSubscribeOrObserveCall = (node) => {
13836
+ if (isSubscribeLikeCallExpression(node)) return true;
13837
+ return isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === OBSERVER_REGISTRATION_METHOD_NAME;
13838
+ };
13963
13839
  const resolveExpressionKey = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
13964
13840
  if (!expression) return null;
13965
13841
  const unwrappedExpression = stripParenExpression(expression);
@@ -14061,13 +13937,12 @@ const findSubscribeLikeUsages = (callback, context) => {
14061
13937
  });
14062
13938
  return;
14063
13939
  }
14064
- const subscribeOrObserveMethodName = getSubscribeOrObserveMethodName(child);
14065
- if (subscribeOrObserveMethodName !== null) {
13940
+ 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)) {
14066
13941
  const registrationDetails = getCallRegistrationDetails(child, context);
14067
13942
  usages.push({
14068
13943
  kind: "subscribe",
14069
13944
  node: child,
14070
- resourceName: subscribeOrObserveMethodName,
13945
+ resourceName: child.callee.property.name,
14071
13946
  handleKey: findAssignedResourceKey(child, context),
14072
13947
  ...registrationDetails
14073
13948
  });
@@ -14349,39 +14224,6 @@ const findContainingCollectionKey = (resourceNode, context) => {
14349
14224
  }
14350
14225
  return null;
14351
14226
  };
14352
- const findPushedResourceCollectionKey = (usage, context) => {
14353
- if (!isNodeOfType(usage.node, "CallExpression")) return null;
14354
- const registrationCallee = stripParenExpression(usage.node.callee);
14355
- if (!isNodeOfType(registrationCallee, "MemberExpression") || registrationCallee.computed) return null;
14356
- const resourceIdentifier = stripParenExpression(registrationCallee.object);
14357
- if (!isPrivatePlainConstIdentifier(resourceIdentifier, context)) return null;
14358
- const resourceSymbol = context.scopes.symbolFor(resourceIdentifier);
14359
- if (!resourceSymbol) return null;
14360
- const pushCalls = resourceSymbol.references.flatMap((reference) => {
14361
- const referenceRoot = findTransparentExpressionRoot(reference.identifier);
14362
- const callNode = referenceRoot.parent;
14363
- if (!isNodeOfType(callNode, "CallExpression") || !callNode.arguments?.some((argument) => argument === referenceRoot)) return [];
14364
- const pushCallee = stripParenExpression(callNode.callee);
14365
- return isNodeOfType(pushCallee, "MemberExpression") && !pushCallee.computed && isNodeOfType(pushCallee.object, "Identifier") && isNodeOfType(pushCallee.property, "Identifier") && pushCallee.property.name === "push" ? [callNode] : [];
14366
- });
14367
- if (pushCalls.length !== 1) return null;
14368
- const pushCall = pushCalls[0];
14369
- if (findEnclosingFunction$1(pushCall) !== findEnclosingFunction$1(usage.node) || !doMatchingNodesCoverEveryPathAfterUsage(usage.node, [pushCall], context)) return null;
14370
- const pushCallee = stripParenExpression(pushCall.callee);
14371
- if (!isNodeOfType(pushCallee, "MemberExpression") || !isNodeOfType(pushCallee.object, "Identifier") || !isPrivatePlainConstIdentifier(pushCallee.object, context)) return null;
14372
- const collectionSymbol = context.scopes.symbolFor(pushCallee.object);
14373
- const collectionInitializer = collectionSymbol?.initializer ? stripParenExpression(collectionSymbol.initializer) : null;
14374
- if (!collectionSymbol || !isNodeOfType(collectionInitializer, "ArrayExpression") || (collectionInitializer.elements?.length ?? 0) !== 0 || findEnclosingFunction$1(collectionSymbol.declarationNode) !== findEnclosingFunction$1(usage.node)) return null;
14375
- return collectionSymbol.references.every((reference) => {
14376
- const referenceRoot = findTransparentExpressionRoot(reference.identifier);
14377
- const forOfStatement = referenceRoot.parent;
14378
- if (isNodeOfType(forOfStatement, "ForOfStatement") && forOfStatement.right === referenceRoot && forOfStatement.await !== true) return true;
14379
- const memberNode = referenceRoot.parent;
14380
- const callNode = memberNode?.parent;
14381
- if (!isNodeOfType(memberNode, "MemberExpression") || memberNode.object !== referenceRoot || memberNode.computed || !isNodeOfType(memberNode.property, "Identifier") || !isNodeOfType(callNode, "CallExpression") || callNode.callee !== memberNode) return false;
14382
- return memberNode.property.name === "forEach" || memberNode.property.name === "push";
14383
- }) ? resolveExpressionKey(pushCallee.object, context) : null;
14384
- };
14385
14227
  const isWithinAssignmentTarget = (identifier) => {
14386
14228
  let currentNode = identifier;
14387
14229
  let parentNode = currentNode.parent;
@@ -14440,14 +14282,6 @@ const isSynchronousIteratorCallback = (functionNode) => {
14440
14282
  if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments?.[1] === functionNode;
14441
14283
  return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments?.[0] === functionNode;
14442
14284
  };
14443
- const findEnclosingForEachCall = (node) => {
14444
- const callbackNode = findEnclosingFunction$1(node);
14445
- if (!callbackNode) return null;
14446
- const callNode = callbackNode.parent;
14447
- if (!isNodeOfType(callNode, "CallExpression") || callNode.arguments?.[0] !== callbackNode) return null;
14448
- const callee = stripParenExpression(callNode.callee);
14449
- return isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "forEach" ? callNode : null;
14450
- };
14451
14285
  const findDirectCallForReference = (identifier) => {
14452
14286
  const expressionRoot = findTransparentExpressionRoot(identifier);
14453
14287
  const callNode = expressionRoot.parent;
@@ -14462,7 +14296,7 @@ const findSingleDirectInvocation = (functionNode, caller, context) => {
14462
14296
  const callNode = findDirectCallForReference(reference.identifier);
14463
14297
  return callNode ? [callNode] : [];
14464
14298
  });
14465
- if (invocationCalls.length !== 1 || symbol.references.length !== 1) return null;
14299
+ if (invocationCalls.length !== 1) return null;
14466
14300
  const invocationCall = invocationCalls[0];
14467
14301
  return findEnclosingFunction$1(invocationCall) === caller && isNodeReachableWithinFunction(invocationCall, context) ? invocationCall : null;
14468
14302
  };
@@ -14496,15 +14330,7 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
14496
14330
  if (cleanupChild !== cleanupFunction.body && isFunctionLike$1(cleanupChild) && !isSynchronousIteratorCallback(cleanupChild)) return false;
14497
14331
  const cleanupCall = isNodeOfType(cleanupChild, "ChainExpression") ? cleanupChild.expression : cleanupChild;
14498
14332
  if (doesReleaseCallMatchUsage(cleanupChild, usage, context)) {
14499
- const cleanupForEachCall = findEnclosingForEachCall(cleanupChild);
14500
- const cleanupCallee = isNodeOfType(cleanupCall, "CallExpression") ? stripParenExpression(cleanupCall.callee) : null;
14501
- const cleanupReceiverForOfStatement = isNodeOfType(cleanupCallee, "MemberExpression") ? findForOfStatementForIteratorExpression(cleanupCallee.object, context) : null;
14502
- const cleanupReceiverCollectionKey = cleanupReceiverForOfStatement ? resolveExpressionKey(cleanupReceiverForOfStatement.right, context) : isNodeOfType(cleanupCallee, "MemberExpression") ? resolveIteratorCollectionKey(cleanupCallee.object, context) : null;
14503
- if (cleanupReceiverCollectionKey !== null && findEnclosingFunction$1(cleanupChild) !== cleanupFunction) {
14504
- if (cleanupForEachCall && findPushedResourceCollectionKey(usage, context) === cleanupReceiverCollectionKey) matchingLoopOrHelperAnchors.push(cleanupForEachCall);
14505
- return;
14506
- }
14507
- const cleanupForOfStatement = findForOfStatementForIteratorExpression(isNodeOfType(cleanupCall, "CallExpression") ? cleanupCall.arguments?.[0] : null, context) ?? cleanupReceiverForOfStatement;
14333
+ const cleanupForOfStatement = findForOfStatementForIteratorExpression(isNodeOfType(cleanupCall, "CallExpression") ? cleanupCall.arguments?.[0] : null, context);
14508
14334
  if (!cleanupForOfStatement) {
14509
14335
  didCleanupFunctionMatch = true;
14510
14336
  return false;
@@ -14548,28 +14374,28 @@ const callbackReturnsCleanupForUsage = (callback, usage, context) => {
14548
14374
  });
14549
14375
  return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
14550
14376
  };
14551
- const doesTestRequireLiveExpressionKey = (test, expressionKey, context) => {
14552
- if (resolveExpressionKey(test, context) === expressionKey) return true;
14553
- const unwrappedTest = stripParenExpression(test);
14554
- if (!isNodeOfType(unwrappedTest, "BinaryExpression") || unwrappedTest.operator !== "!=" && unwrappedTest.operator !== "!==") return false;
14555
- const isNullishOperand = (operand) => {
14556
- const unwrappedOperand = stripParenExpression(operand);
14557
- return isNodeOfType(unwrappedOperand, "Literal") && unwrappedOperand.value === null || isNodeOfType(unwrappedOperand, "Identifier") && unwrappedOperand.name === "undefined" && context.scopes.isGlobalReference(unwrappedOperand);
14377
+ const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => {
14378
+ if (usage.handleKey === null) return null;
14379
+ const doesTestRequireLiveHandle = (test) => {
14380
+ if (resolveExpressionKey(test, context) === usage.handleKey) return true;
14381
+ const unwrappedTest = stripParenExpression(test);
14382
+ if (!isNodeOfType(unwrappedTest, "BinaryExpression") || unwrappedTest.operator !== "!=" && unwrappedTest.operator !== "!==") return false;
14383
+ const isNullishOperand = (operand) => {
14384
+ const unwrappedOperand = stripParenExpression(operand);
14385
+ return isNodeOfType(unwrappedOperand, "Literal") && unwrappedOperand.value === null || isNodeOfType(unwrappedOperand, "Identifier") && unwrappedOperand.name === "undefined" && context.scopes.isGlobalReference(unwrappedOperand);
14386
+ };
14387
+ return resolveExpressionKey(unwrappedTest.left, context) === usage.handleKey && isNullishOperand(unwrappedTest.right) || resolveExpressionKey(unwrappedTest.right, context) === usage.handleKey && isNullishOperand(unwrappedTest.left);
14558
14388
  };
14559
- return resolveExpressionKey(unwrappedTest.left, context) === expressionKey && isNullishOperand(unwrappedTest.right) || resolveExpressionKey(unwrappedTest.right, context) === expressionKey && isNullishOperand(unwrappedTest.left);
14560
- };
14561
- const findLiveExpressionGuardForRelease = (releaseCall, owner, expressionKey, context) => {
14562
14389
  let ancestor = releaseCall.parent;
14563
14390
  while (ancestor && ancestor !== owner) {
14564
14391
  if (isNodeOfType(ancestor, "IfStatement")) {
14565
- if (ancestor.alternate !== null || !doesTestRequireLiveExpressionKey(ancestor.test, expressionKey, context) || !doMatchingNodesCoverEveryPathAfterUsage(ancestor.consequent, [releaseCall], context)) return null;
14392
+ if (ancestor.alternate !== null || !doesTestRequireLiveHandle(ancestor.test) || !doMatchingNodesCoverEveryPathAfterUsage(ancestor.consequent, [releaseCall], context)) return null;
14566
14393
  return ancestor;
14567
14394
  }
14568
14395
  ancestor = ancestor.parent;
14569
14396
  }
14570
14397
  return null;
14571
14398
  };
14572
- const findDirectHandleGuardForRelease = (releaseCall, owner, usage, context) => usage.handleKey === null ? null : findLiveExpressionGuardForRelease(releaseCall, owner, usage.handleKey, context);
14573
14399
  const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
14574
14400
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
14575
14401
  const functionCfg = context.cfg.cfgFor(callback);
@@ -14757,108 +14583,7 @@ const hasPotentialInterruptionAfterGuard = (callback, guardState, usageNode, con
14757
14583
  });
14758
14584
  return hasPotentialInterruption;
14759
14585
  };
14760
- const getNumericReactRefCurrentKey = (expression, context) => {
14761
- const refSymbol = resolveReactRefSymbol(stripParenExpression(expression), context.scopes);
14762
- const initializer = refSymbol?.initializer ? stripParenExpression(refSymbol.initializer) : null;
14763
- if (!isNodeOfType(initializer, "CallExpression")) return null;
14764
- const initialValue = initializer.arguments?.[0] ? stripParenExpression(initializer.arguments[0]) : null;
14765
- if (!isNodeOfType(initialValue, "Literal") || typeof initialValue.value !== "number") return null;
14766
- return resolveExpressionKey(expression, context);
14767
- };
14768
- const getBlockingGenerationKey = (expression, context) => {
14769
- const test = stripParenExpression(expression);
14770
- if (isNodeOfType(test, "LogicalExpression") && test.operator === "||") return getBlockingGenerationKey(test.left, context) ?? getBlockingGenerationKey(test.right, context);
14771
- if (!isNodeOfType(test, "BinaryExpression") || test.operator !== "!==" && test.operator !== "!=") return null;
14772
- const leftKey = getNumericReactRefCurrentKey(test.left, context);
14773
- const rightKey = getNumericReactRefCurrentKey(test.right, context);
14774
- const snapshotExpression = leftKey ? stripParenExpression(test.right) : stripParenExpression(test.left);
14775
- const key = leftKey ?? rightKey;
14776
- return key && isNodeOfType(snapshotExpression, "Identifier") ? key : null;
14777
- };
14778
- const findGenerationGuardKeyForDeferredUsage = (usageFunction, usageNode, context) => {
14779
- if (!isFunctionLike$1(usageFunction)) return null;
14780
- let generationKey = null;
14781
- walkAst(usageFunction.body, (child) => {
14782
- if (generationKey) return false;
14783
- if (child !== usageFunction.body && isFunctionLike$1(child)) return false;
14784
- if (!isNodeOfType(child, "IfStatement") || child.alternate) return;
14785
- const key = getBlockingGenerationKey(child.test, context);
14786
- if (!key || canNodeReachLaterNodeWithinFunction(child.consequent, usageNode, usageFunction, context) || !doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], usageFunction, context)) return;
14787
- generationKey = key;
14788
- });
14789
- return generationKey;
14790
- };
14791
- const isGenerationAdvance = (node, generationKey, context) => {
14792
- if (isNodeOfType(node, "UpdateExpression") && resolveExpressionKey(node.argument, context) === generationKey) return true;
14793
- if (!isNodeOfType(node, "AssignmentExpression") || resolveExpressionKey(node.left, context) !== generationKey || node.operator !== "+=" && node.operator !== "-=") return false;
14794
- const amount = stripParenExpression(node.right);
14795
- return isNodeOfType(amount, "Literal") && typeof amount.value === "number" && amount.value !== 0;
14796
- };
14797
- const functionAdvancesGeneration = (owner, generationKey, context) => {
14798
- if (!isFunctionLike$1(owner)) return false;
14799
- let didAdvanceGeneration = false;
14800
- walkAst(owner.body, (child) => {
14801
- if (didAdvanceGeneration) return false;
14802
- if (child !== owner.body && isFunctionLike$1(child)) return false;
14803
- if (isGenerationAdvance(child, generationKey, context)) {
14804
- didAdvanceGeneration = true;
14805
- return false;
14806
- }
14807
- });
14808
- return didAdvanceGeneration;
14809
- };
14810
- const cleanupReturnsReleaseUsage = (cleanupReturns, usage, context) => cleanupReturns.length > 0 && cleanupReturns.every((cleanupReturn) => {
14811
- if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
14812
- const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
14813
- return Boolean(cleanupFunction && isFunctionLike$1(cleanupFunction) && doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context));
14814
- });
14815
- const getOwnedFunctionReference = (reference, usageFunction, usageNode, callback, cleanupReturns, context) => {
14816
- const directCall = findDirectCallForReference(reference);
14817
- if (directCall) {
14818
- const referenceOwner = findEnclosingFunction$1(directCall);
14819
- if (referenceOwner && referenceOwner !== usageFunction && collectSynchronouslyEffectInvokedFunctions(callback).has(referenceOwner)) return { generationKey: null };
14820
- const generationKey = referenceOwner ? findGenerationGuardKeyForDeferredUsage(referenceOwner, directCall, context) : null;
14821
- return generationKey ? { generationKey } : null;
14822
- }
14823
- const referenceRoot = findTransparentExpressionRoot(reference);
14824
- const schedulerCall = referenceRoot.parent;
14825
- 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;
14826
- const schedulerUsage = {
14827
- kind: "timer",
14828
- node: schedulerCall,
14829
- resourceName: schedulerCall.callee.name,
14830
- handleKey: findAssignedResourceKey(schedulerCall, context),
14831
- receiverKey: null,
14832
- registrationVerbName: schedulerCall.callee.name,
14833
- eventKey: null,
14834
- handlerKey: null
14835
- };
14836
- const generationKey = findGenerationGuardKeyForDeferredUsage(usageFunction, usageNode, context);
14837
- return schedulerUsage.handleKey !== null && cleanupReturnsReleaseUsage(cleanupReturns, schedulerUsage, context) && generationKey ? { generationKey } : null;
14838
- };
14839
- const hasGuardedRefOwnedNestedCleanup = (callback, usage, cleanupReturns, context) => {
14840
- const usageFunction = findEnclosingFunction$1(usage.node);
14841
- const usageExpression = findTransparentExpressionRoot(usage.node);
14842
- const usageAssignment = usageExpression.parent;
14843
- 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;
14844
- const cleanupFunctions = cleanupReturns.flatMap((cleanupReturn) => {
14845
- if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return [];
14846
- const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
14847
- return cleanupFunction && isFunctionLike$1(cleanupFunction) ? [cleanupFunction] : [];
14848
- });
14849
- const bindingIdentifier = getFunctionBindingIdentifier$1(usageFunction);
14850
- const functionSymbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
14851
- if (!functionSymbol || functionSymbol.references.length === 0) return false;
14852
- const ownedReferences = functionSymbol.references.map((reference) => getOwnedFunctionReference(reference.identifier, usageFunction, usage.node, callback, cleanupReturns, context));
14853
- if (ownedReferences.some((reference) => reference === null)) return false;
14854
- const generationKeys = new Set(ownedReferences.flatMap((reference) => reference?.generationKey ? [reference.generationKey] : []));
14855
- if (generationKeys.size !== 1) return false;
14856
- const generationKey = generationKeys.values().next().value;
14857
- if (typeof generationKey !== "string") return false;
14858
- return [...collectSynchronouslyEffectInvokedFunctions(callback), ...cleanupFunctions].some((owner) => functionAdvancesGeneration(owner, generationKey, context));
14859
- };
14860
14586
  const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
14861
- if (hasGuardedRefOwnedNestedCleanup(callback, usage, cleanupReturns, context)) return true;
14862
14587
  const usageFunction = findEnclosingFunction$1(usage.node);
14863
14588
  const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
14864
14589
  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;
@@ -15026,85 +14751,6 @@ const getReleaseVerbName = (node) => {
15026
14751
  }
15027
14752
  return null;
15028
14753
  };
15029
- const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) => {
15030
- const releaseFunction = findEnclosingFunction$1(releaseReceiver);
15031
- const usageFunction = findEnclosingFunction$1(usage.node);
15032
- if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction) || !resolveReactRefCurrentOriginSymbol(releaseReceiver, context.scopes)) return false;
15033
- const controllerKey = getListenerAbortControllerKey(usage, context);
15034
- const refCurrentKey = resolveExpressionKey(releaseReceiver, context);
15035
- if (controllerKey === null || refCurrentKey === null) return false;
15036
- const usageFunctionBody = usageFunction.body;
15037
- const previousAbortCalls = [];
15038
- const ownershipAssignments = [];
15039
- walkAst(usageFunctionBody, (child) => {
15040
- if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
15041
- if (isNodeOfType(child, "AssignmentExpression") && resolveExpressionKey(child.left, context) === refCurrentKey && resolveExpressionKey(child.right, context) === controllerKey) {
15042
- ownershipAssignments.push(child);
15043
- return;
15044
- }
15045
- if (!isNodeOfType(child, "CallExpression")) return;
15046
- const childCallee = isNodeOfType(child.callee, "ChainExpression") ? child.callee.expression : stripParenExpression(child.callee);
15047
- if (isNodeOfType(childCallee, "MemberExpression") && !childCallee.computed && isNodeOfType(childCallee.property, "Identifier") && childCallee.property.name === "abort" && resolveExpressionKey(childCallee.object, context) === refCurrentKey) previousAbortCalls.push(child);
15048
- });
15049
- const safeOwnershipAssignments = ownershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, previousAbortCalls, usageFunction, context));
15050
- return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
15051
- };
15052
- const isJsxRefAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && node.name.name === "ref";
15053
- const isFunctionForwardedToReactRef = (functionNode, context) => {
15054
- const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
15055
- if (!bindingIdentifier) return false;
15056
- const symbol = context.scopes.symbolFor(bindingIdentifier);
15057
- if (!symbol) return false;
15058
- return symbol.references.some((reference) => {
15059
- const referenceRoot = findTransparentExpressionRoot(reference.identifier);
15060
- const expressionContainer = referenceRoot.parent;
15061
- return Boolean(isNodeOfType(expressionContainer, "JSXExpressionContainer") && expressionContainer.expression === referenceRoot && isJsxRefAttribute(expressionContainer.parent));
15062
- });
15063
- };
15064
- const isFunctionReturnedFromReactHook = (functionNode, context, requireRefPropertyName) => {
15065
- const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
15066
- if (!bindingIdentifier) return false;
15067
- const symbol = context.scopes.symbolFor(bindingIdentifier);
15068
- if (!symbol) return false;
15069
- return symbol.references.some((reference) => {
15070
- const referenceRoot = findTransparentExpressionRoot(reference.identifier);
15071
- const property = referenceRoot.parent;
15072
- const propertyName = isNodeOfType(property, "Property") ? getStaticPropertyKeyName(property) : null;
15073
- if (!isNodeOfType(property, "Property") || property.value !== referenceRoot || !isNodeOfType(property.parent, "ObjectExpression") || requireRefPropertyName && propertyName !== "ref" && !propertyName?.endsWith("Ref")) return false;
15074
- const returnedObject = findTransparentExpressionRoot(property.parent);
15075
- const returnStatement = returnedObject.parent;
15076
- if (!isNodeOfType(returnStatement, "ReturnStatement") || returnStatement.argument !== returnedObject) return false;
15077
- const ownerFunction = findEnclosingFunction$1(returnStatement);
15078
- return Boolean(ownerFunction && isReactHookName(getFunctionBindingIdentifier$1(ownerFunction)?.name ?? ""));
15079
- });
15080
- };
15081
- const isFunctionUsedAsReactRef = (functionNode, context) => isFunctionForwardedToReactRef(functionNode, context) || isFunctionReturnedFromReactHook(functionNode, context, true);
15082
- const isReactRefListenerReplacementRelease = (releaseCall, usage, context) => {
15083
- if (!isNodeOfType(usage.node, "CallExpression")) return false;
15084
- const usageFunction = findEnclosingFunction$1(usage.node);
15085
- if (!usageFunction || !isFunctionLike$1(usageFunction) || usageFunction !== findEnclosingFunction$1(releaseCall) || !isFunctionUsedAsReactRef(usageFunction, context)) return false;
15086
- const registrationCallee = stripParenExpression(usage.node.callee);
15087
- const releaseCallee = stripParenExpression(releaseCall.callee);
15088
- const releaseRefSymbol = isNodeOfType(releaseCallee, "MemberExpression") ? resolveReactRefCurrentOriginSymbol(releaseCallee.object, context.scopes) : null;
15089
- 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;
15090
- const registrationReceiverKey = resolveExpressionKey(stripParenExpression(registrationCallee.object), context);
15091
- const nodeParameterKey = resolveExpressionKey(usageFunction.params?.[0], context);
15092
- const releaseReceiverKey = resolveExpressionKey(releaseCallee.object, context);
15093
- 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;
15094
- const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
15095
- const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
15096
- if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
15097
- const releaseStart = getRangeStart(releaseCall);
15098
- const matchingOwnershipAssignments = [];
15099
- const usageFunctionBody = usageFunction.body;
15100
- walkAst(usageFunctionBody, (child) => {
15101
- if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
15102
- 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);
15103
- });
15104
- const releaseAnchor = findLiveExpressionGuardForRelease(releaseCall, usageFunction, releaseReceiverKey, context) ?? releaseCall;
15105
- const safeOwnershipAssignments = matchingOwnershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, [releaseAnchor], usageFunction, context));
15106
- return doMatchingNodesCoverEveryPathFromFunctionEntry(usageFunction, [releaseAnchor], context) && doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
15107
- };
15108
14754
  const doesReleaseCallMatchUsage = (node, usage, context) => {
15109
14755
  const callNode = isNodeOfType(node, "ChainExpression") ? node.expression : node;
15110
14756
  if (!isNodeOfType(callNode, "CallExpression")) return false;
@@ -15121,21 +14767,13 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15121
14767
  if (!releaseVerbName) return false;
15122
14768
  if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
15123
14769
  const releaseReceiverKey = resolveExpressionKey(callee.object, context);
15124
- const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
15125
- const pairedReleaseVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
15126
- const pushedResourceCollectionKey = findPushedResourceCollectionKey(usage, context);
15127
- const releaseReceiverForOfStatement = findForOfStatementForIteratorExpression(callee.object, context);
15128
- const releaseReceiverCollectionKey = releaseReceiverForOfStatement ? resolveExpressionKey(releaseReceiverForOfStatement.right, context) : resolveIteratorCollectionKey(callee.object, context);
15129
- if (pairedReleaseVerbNames && matchesPairedReleaseVerb(releaseVerbName, pairedReleaseVerbNames) && pushedResourceCollectionKey !== null && pushedResourceCollectionKey === releaseReceiverCollectionKey && (releaseVerbName !== "unobserve" || usage.eventKey !== null && releaseEventKey === usage.eventKey)) return true;
15130
- if (isReactRefListenerReplacementRelease(callNode, usage, context)) return true;
15131
14770
  if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
15132
14771
  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;
15133
14772
  if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
15134
- if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
15135
14773
  if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) return false;
15136
- if (usage.registrationVerbName === "subscribe" && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub") && usage.handleKey !== null && resolveExpressionKey(callNode.arguments?.[0], context) === usage.handleKey) return true;
15137
14774
  const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
15138
14775
  if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
14776
+ const releaseEventKey = resolveExpressionKey(callNode.arguments?.[0], context);
15139
14777
  const usageEventArgument = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[0] : null;
15140
14778
  const releaseEventArgument = callNode.arguments?.[0];
15141
14779
  if (isAssignmentFormForOfIteratorReference(usageEventArgument, context) || isAssignmentFormForOfIteratorReference(releaseEventArgument, context)) return false;
@@ -15165,12 +14803,9 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
15165
14803
  return isNodeOfType(handlerArgument, "Literal") && handlerArgument.value === null;
15166
14804
  }
15167
14805
  if (releaseVerbName === "removeEventListener" || releaseVerbName === "removeListener" || releaseVerbName === "off") {
15168
- const usesUnaryListenerSignature = usage.registrationVerbName === "addListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1 && callNode.arguments?.length === 1;
15169
- const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
14806
+ const releaseHandler = callNode.arguments?.[1];
15170
14807
  if (!releaseHandler) return releaseVerbName === "off";
15171
- const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
15172
- const registrationHandler = isNodeOfType(usage.node, "CallExpression") ? usage.node.arguments?.[usesUnaryListenerSignature ? 0 : 1] : null;
15173
- return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey || registrationHandler !== null && resolveStableValue(releaseHandler, context) === resolveStableValue(registrationHandler, context);
14808
+ return usage.handlerKey !== null && resolveExpressionKey(releaseHandler, context) === usage.handlerKey;
15174
14809
  }
15175
14810
  if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
15176
14811
  return true;
@@ -15183,7 +14818,8 @@ const isReturnedEffectCleanupFunction = (functionNode) => {
15183
14818
  currentNode = parentNode;
15184
14819
  parentNode = currentNode.parent;
15185
14820
  }
15186
- const effectCallback = isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode ? findEnclosingFunction$1(parentNode) : isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode ? parentNode : null;
14821
+ if (!isNodeOfType(parentNode, "ReturnStatement") || parentNode.argument !== currentNode) return false;
14822
+ const effectCallback = findEnclosingFunction$1(parentNode);
15187
14823
  const effectCall = effectCallback?.parent;
15188
14824
  return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
15189
14825
  };
@@ -15195,165 +14831,13 @@ const isPotentiallyReachableFunction = (functionNode, context) => {
15195
14831
  if (!symbol) return false;
15196
14832
  return symbol.references.some((reference) => findEnclosingFunction$1(reference.identifier) !== functionNode);
15197
14833
  };
15198
- const findRetainedDisposerStorages = (disposerFunction, usage, context) => {
15199
- if (!isFunctionLike$1(disposerFunction) || disposerFunction.async || disposerFunction.generator) return [];
15200
- const usageFunction = findEnclosingFunction$1(usage.node);
15201
- if (!usageFunction || !isFunctionLike$1(usageFunction)) return [];
15202
- const assignments = /* @__PURE__ */ new Map();
15203
- const collectAssignment = (expression) => {
15204
- const expressionRoot = findTransparentExpressionRoot(expression);
15205
- const assignment = expressionRoot.parent;
15206
- if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.right !== expressionRoot) return;
15207
- const refSymbol = resolveReactRefSymbol(stripParenExpression(assignment.left), context.scopes);
15208
- const refCurrentKey = resolveExpressionKey(assignment.left, context);
15209
- const retainedFunction = findEnclosingFunction$1(assignment);
15210
- const assignmentStart = getRangeStart(assignment);
15211
- if (!refSymbol || !refCurrentKey || !retainedFunction || retainedFunction !== usageFunction || assignmentStart === null) return;
15212
- assignments.set(assignmentStart, {
15213
- assignmentNode: assignment,
15214
- refCurrentKey,
15215
- retainedFunction
15216
- });
15217
- };
15218
- collectAssignment(disposerFunction);
15219
- const bindingIdentifier = getFunctionBindingIdentifier$1(disposerFunction);
15220
- const symbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
15221
- for (const reference of symbol?.references ?? []) collectAssignment(reference.identifier);
15222
- walkAst(usageFunction.body, (child) => {
15223
- if (child !== usageFunction.body && isFunctionLike$1(child)) return false;
15224
- if (isNodeOfType(child, "AssignmentExpression") && resolveStableValue(child.right, context) === disposerFunction) collectAssignment(child.right);
15225
- });
15226
- return [...assignments.values()];
15227
- };
15228
- const isRetainedDisposerStorageEstablished = (storage, usage, context) => doMatchingNodesCoverEveryPathBeforeUsage(usage.node, [storage.assignmentNode], storage.retainedFunction, context) || doMatchingNodesCoverEveryPathAfterUsage(usage.node, [storage.assignmentNode], context);
15229
- const hasUnsafeRetainedDisposerOverwrite = (storage, usage, context) => {
15230
- let hasUnsafeOverwrite = false;
15231
- walkAst(storage.retainedFunction.body, (child) => {
15232
- if (hasUnsafeOverwrite) return false;
15233
- if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
15234
- if (!isNodeOfType(child, "AssignmentExpression") || child === storage.assignmentNode || resolveExpressionKey(child.left, context) !== storage.refCurrentKey || !canNodeReachLaterNodeWithinFunction(usage.node, child, storage.retainedFunction, context)) return;
15235
- const storedValue = resolveStableValue(child.right, context);
15236
- if (!storedValue || !isFunctionLike$1(storedValue) || !doesCleanupFunctionReleaseUsage(storedValue, usage, context)) {
15237
- hasUnsafeOverwrite = true;
15238
- return false;
15239
- }
15240
- });
15241
- return hasUnsafeOverwrite;
15242
- };
15243
- const hasEffectCleanupInvocation = (storage, usage, context) => {
15244
- const componentFunction = findEnclosingFunction$1(storage.retainedFunction);
15245
- if (!componentFunction || !isFunctionLike$1(componentFunction)) return false;
15246
- const cleanupFunctionInvokesRef = (cleanupFunction) => {
15247
- if (!isFunctionLike$1(cleanupFunction)) return false;
15248
- let didFindCleanupCall = false;
15249
- walkAst(cleanupFunction.body, (child) => {
15250
- if (didFindCleanupCall) return false;
15251
- if (child !== cleanupFunction.body && isFunctionLike$1(child)) return false;
15252
- if (isNodeOfType(child, "CallExpression") && resolveExpressionKey(child.callee, context) === storage.refCurrentKey) {
15253
- const callRoot = findTransparentExpressionRoot(child);
15254
- const callStatement = callRoot.parent;
15255
- const isDirectBlockStatement = isNodeOfType(cleanupFunction.body, "BlockStatement") && isNodeOfType(callStatement, "ExpressionStatement") && callStatement.parent === cleanupFunction.body;
15256
- const isConciseBody = cleanupFunction.body === callRoot;
15257
- if ((isDirectBlockStatement || isConciseBody) && !hasUnprovenReturnBeforeRefOwnedRelease(cleanupFunction, child, storage.refCurrentKey, context)) {
15258
- didFindCleanupCall = true;
15259
- return false;
15260
- }
15261
- }
15262
- });
15263
- return didFindCleanupCall;
15264
- };
15265
- const effectReturnsCleanup = (effectCallback) => {
15266
- if (!isFunctionLike$1(effectCallback)) return false;
15267
- if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
15268
- const cleanupFunction = resolveRefOwnedCleanupFunction(effectCallback.body, context);
15269
- return Boolean(cleanupFunction && cleanupFunctionInvokesRef(cleanupFunction));
15270
- }
15271
- const matchingReturns = [];
15272
- walkInsideStatementBlocks(effectCallback.body, (child) => {
15273
- if (!isNodeOfType(child, "ReturnStatement") || !child.argument) return;
15274
- const cleanupFunction = resolveRefOwnedCleanupFunction(child.argument, context);
15275
- if (!cleanupFunction || !cleanupFunctionInvokesRef(cleanupFunction)) return;
15276
- matchingReturns.push(child);
15277
- });
15278
- return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
15279
- };
15280
- let didFindInvocation = false;
15281
- walkAst(componentFunction.body, (child) => {
15282
- if (didFindInvocation) return false;
15283
- if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
15284
- const effectCallback = getEffectCallback(child);
15285
- if (effectCallback && effectReturnsCleanup(effectCallback)) {
15286
- didFindInvocation = true;
15287
- return false;
15288
- }
15289
- });
15290
- return didFindInvocation;
15291
- };
15292
- const hasCallbackRefReplacementInvocation = (storage, usage, context) => {
15293
- const isReturnedCallbackRefShape = () => {
15294
- if (!isFunctionLike$1(storage.retainedFunction)) return false;
15295
- const callbackCall = findTransparentExpressionRoot(storage.retainedFunction).parent;
15296
- if (!isNodeOfType(callbackCall, "CallExpression") || !isReactApiCall(callbackCall, "useCallback", context.scopes)) return false;
15297
- const nodeParameter = storage.retainedFunction.params?.[0];
15298
- const nodeParameterKey = resolveExpressionKey(nodeParameter, context);
15299
- if (!nodeParameterKey || usage.receiverKey !== nodeParameterKey) return false;
15300
- if (!isFunctionReturnedFromReactHook(storage.retainedFunction, context, false)) return false;
15301
- const usageStart = getRangeStart(usage.node);
15302
- if (usageStart === null) return false;
15303
- let hasNullExit = false;
15304
- walkAst(storage.retainedFunction.body, (child) => {
15305
- if (hasNullExit) return false;
15306
- if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
15307
- if (!isNodeOfType(child, "IfStatement") || (getRangeStart(child) ?? usageStart) >= usageStart) return;
15308
- const test = stripParenExpression(child.test);
15309
- if (!isNodeOfType(test, "UnaryExpression") || test.operator !== "!" || resolveExpressionKey(test.argument, context) !== nodeParameterKey) return;
15310
- const consequent = child.consequent;
15311
- hasNullExit = isNodeOfType(consequent, "ReturnStatement") || isNodeOfType(consequent, "BlockStatement") && consequent.body.some((statement) => isNodeOfType(statement, "ReturnStatement"));
15312
- if (hasNullExit) return false;
15313
- });
15314
- return hasNullExit;
15315
- };
15316
- if (!isFunctionForwardedToReactRef(storage.retainedFunction, context) && !isReturnedCallbackRefShape()) return false;
15317
- const cleanupCalls = [];
15318
- walkAst(storage.retainedFunction.body, (child) => {
15319
- if (child !== storage.retainedFunction.body && isFunctionLike$1(child)) return false;
15320
- if (isNodeOfType(child, "CallExpression") && resolveExpressionKey(child.callee, context) === storage.refCurrentKey) cleanupCalls.push(child);
15321
- });
15322
- return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, cleanupCalls, storage.retainedFunction, context);
15323
- };
15324
- const isRetainedDisposerRefRelease = (releaseNode, usage, context) => {
15325
- const disposerFunction = findEnclosingFunction$1(releaseNode);
15326
- if (!disposerFunction) return false;
15327
- return findRetainedDisposerStorages(disposerFunction, usage, context).some((storage) => isRetainedDisposerStorageEstablished(storage, usage, context) && !hasUnsafeRetainedDisposerOverwrite(storage, usage, context) && (hasEffectCleanupInvocation(storage, usage, context) || hasCallbackRefReplacementInvocation(storage, usage, context)));
15328
- };
15329
- const isSelfReleasingListenerRelease = (releaseNode, releaseFunction, usage, context) => {
15330
- 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;
15331
- const registrationCapture = resolveEventListenerCapture(usage.node.arguments?.[2], { allowIndeterminateEntries: true });
15332
- const releaseCall = isNodeOfType(releaseNode, "ChainExpression") ? releaseNode.expression : releaseNode;
15333
- if (!isNodeOfType(releaseCall, "CallExpression")) return false;
15334
- const releaseCapture = resolveEventListenerCapture(releaseCall.arguments?.[2], { allowIndeterminateEntries: true });
15335
- if (registrationCapture === null || releaseCapture === null || registrationCapture !== releaseCapture) return false;
15336
- const ownerFunction = findEnclosingFunction$1(releaseFunction);
15337
- if (!ownerFunction || !isFunctionLike$1(ownerFunction)) return false;
15338
- const triggerRegistrations = [];
15339
- walkAst(ownerFunction.body, (child) => {
15340
- if (child !== ownerFunction.body && isFunctionLike$1(child)) return false;
15341
- if (!isNodeOfType(child, "CallExpression")) return;
15342
- const registrationDetails = getCallRegistrationDetails(child, context);
15343
- if (registrationDetails.registrationVerbName === "addEventListener" && registrationDetails.receiverKey === usage.receiverKey && resolveStableValue(child.arguments?.[1], context) === releaseFunction) triggerRegistrations.push(child);
15344
- });
15345
- if (triggerRegistrations.some((triggerRegistration) => triggerRegistration === usage.node)) return true;
15346
- return doMatchingNodesCoverEveryPathAfterUsage(usage.node, triggerRegistrations, context) || doMatchingNodesCoverEveryPathBeforeUsage(usage.node, triggerRegistrations, ownerFunction, context);
15347
- };
15348
14834
  const isReleaseReachableForUsage = (releaseNode, usage, context) => {
15349
14835
  if (!isNodeReachableWithinFunction(releaseNode, context)) return false;
15350
14836
  const releaseFunction = findEnclosingFunction$1(releaseNode);
15351
14837
  if (!releaseFunction) return true;
15352
14838
  if (releaseFunction === findEnclosingFunction$1(usage.node)) return true;
15353
- if (isRetainedDisposerRefRelease(releaseNode, usage, context)) return true;
15354
14839
  const usageFunction = findEnclosingFunction$1(usage.node);
15355
14840
  if (usageFunction && isFunctionLike$1(usageFunction) && getAssignedReactRefSymbol(usageFunction, context) && isCleanupFunctionReferencedByReturn(usageFunction, releaseFunction, context)) return isReactRefCallbackCleanupOwnedByEffect(usageFunction, releaseFunction, usage, context);
15356
- if (isSelfReleasingListenerRelease(releaseNode, releaseFunction, usage, context)) return true;
15357
14841
  return isPotentiallyReachableFunction(releaseFunction, context);
15358
14842
  };
15359
14843
  const fileContainsReleaseForUsage = (usage, context) => {
@@ -15621,11 +15105,6 @@ const doesResourceResultEscape = (resourceNode, allowReturnedResourceEscape, all
15621
15105
  parentNode = currentNode.parent;
15622
15106
  continue;
15623
15107
  }
15624
- if (isNodeOfType(parentNode, "ConditionalExpression") && (parentNode.consequent === currentNode || parentNode.alternate === currentNode) || isNodeOfType(parentNode, "LogicalExpression") && (parentNode.right === currentNode || parentNode.left === currentNode && parentNode.operator !== "&&")) {
15625
- currentNode = parentNode;
15626
- parentNode = currentNode.parent;
15627
- continue;
15628
- }
15629
15108
  if (isNodeOfType(parentNode, "VariableDeclarator") && parentNode.init === currentNode && isNodeOfType(parentNode.id, "Identifier") && isNodeOfType(parentNode.parent, "VariableDeclaration") && parentNode.parent.kind === "const") {
15630
15109
  const ownerFunction = findEnclosingFunction$1(resourceNode);
15631
15110
  const resourceSymbol = context.scopes.symbolFor(parentNode.id);
@@ -15685,7 +15164,7 @@ const findRetainedFunctionLeak = (retainedFunction, context, options) => {
15685
15164
  return false;
15686
15165
  }
15687
15166
  }
15688
- if (isSubscribeOrObserveCallExpression(child) && (!doesResourceResultEscape(child, allowReturnedResourceEscape, allowReturnedResourceEscape, context) || options?.requireCallableReturnedResource === true && !isCleanupReturningSubscribeLikeCallExpression(child))) {
15167
+ if (isSubscribeOrObserveCall(child) && (!doesResourceResultEscape(child, allowReturnedResourceEscape, allowReturnedResourceEscape, context) || options?.requireCallableReturnedResource === true && !isCleanupReturningSubscribeLikeCallExpression(child))) {
15689
15168
  const registrationDetails = getCallRegistrationDetails(child, context);
15690
15169
  const subscriptionUsage = {
15691
15170
  kind: "subscribe",
@@ -17285,38 +16764,7 @@ const symbolHasStableImportedAlias = (symbol, scopes) => {
17285
16764
  const resolvedSymbol = resolveConstIdentifierAlias(symbol.bindingIdentifier, scopes);
17286
16765
  return resolvedSymbol !== null && resolvedSymbol !== symbol && resolvedSymbol.kind === "import";
17287
16766
  };
17288
- const isAssignmentTarget = (node) => {
17289
- let currentNode = findTransparentExpressionRoot(node);
17290
- while (currentNode.parent) {
17291
- const parentNode = currentNode.parent;
17292
- if (isNodeOfType(parentNode, "AssignmentExpression")) return parentNode.left === currentNode;
17293
- if (isNodeOfType(parentNode, "UpdateExpression")) return parentNode.argument === currentNode;
17294
- if (isNodeOfType(parentNode, "UnaryExpression")) return parentNode.operator === "delete" && parentNode.argument === currentNode;
17295
- if (isNodeOfType(parentNode, "ForInStatement") || isNodeOfType(parentNode, "ForOfStatement")) return parentNode.left === currentNode;
17296
- 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")) {
17297
- currentNode = parentNode;
17298
- continue;
17299
- }
17300
- return false;
17301
- }
17302
- return false;
17303
- };
17304
- const symbolHasStableRefLazyInitialization = (symbol, scopes) => {
17305
- if (symbol.kind !== "const" || symbol.references.some((reference) => reference.flag !== "read")) return false;
17306
- const initializer = symbol.initializer ? unwrapExpression$3(symbol.initializer) : null;
17307
- if (!isNodeOfType(initializer, "AssignmentExpression") || initializer.operator !== "??=") return false;
17308
- const refSymbol = resolveReactRefSymbol(unwrapExpression$3(initializer.left), scopes);
17309
- if (!refSymbol) return false;
17310
- return refSymbol.references.every((reference) => {
17311
- const memberExpression = findTransparentExpressionRoot(reference.identifier).parent;
17312
- if (!isNodeOfType(memberExpression, "MemberExpression") || unwrapExpression$3(memberExpression.object) !== reference.identifier || getStaticPropertyName(memberExpression) !== "current") return false;
17313
- const referenceRoot = findTransparentExpressionRoot(memberExpression);
17314
- const parentNode = referenceRoot.parent;
17315
- if (isNodeOfType(parentNode, "AssignmentExpression") && parentNode.left === referenceRoot) return parentNode === initializer;
17316
- return !isAssignmentTarget(referenceRoot);
17317
- });
17318
- };
17319
- 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);
16767
+ const symbolHasStableValue = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => symbolHasStableHookOrigin(symbol, scopes) || symbolHasStableImportedAlias(symbol, scopes) || symbolHasStableFunctionOrigin(symbol, scopes, visitedSymbolIds) || symbolHasStableMemoizedOrigin(symbol, scopes, visitedSymbolIds);
17320
16768
  //#endregion
17321
16769
  //#region src/plugin/utils/symbol-has-react-use-effect-event-origin.ts
17322
16770
  const symbolHasReactUseEffectEventOrigin = (symbol, scopes) => {
@@ -17518,7 +16966,7 @@ const collectCaptureDepKeys = (callback, scopes, declaredExactBindingKeys, allow
17518
16966
  keys.add(depKey);
17519
16967
  continue;
17520
16968
  }
17521
- const identitySourceKeys = resolvePureCalledFunctionSourceKeys(reference, symbol, scopes) ?? resolveRenderDerivedMutableSourceKeys(reference, symbol, scopes) ?? resolveReactiveIdentitySourceKeys(symbol, scopes);
16969
+ const identitySourceKeys = resolveReactiveIdentitySourceKeys(symbol, scopes);
17522
16970
  if (identitySourceKeys) {
17523
16971
  if (identitySourceKeys.size === 0) stableCapturedNames.add(depKey);
17524
16972
  for (const identitySourceKey of identitySourceKeys) keys.add(identitySourceKey);
@@ -17601,161 +17049,6 @@ const resolveReactiveIdentitySourceKeys = (symbol, scopes) => {
17601
17049
  if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier || symbol.references.some((reference) => reference.flag !== "read")) return null;
17602
17050
  return resolveIdentitySourceKeysFromExpression(symbol.initializer, scopes, new Set([symbol.id]));
17603
17051
  };
17604
- const isPureDerivedExpression = (expression) => {
17605
- const candidate = unwrapExpression$3(expression);
17606
- if (isNodeOfType(candidate, "Literal") || isNodeOfType(candidate, "Identifier")) return true;
17607
- if (isNodeOfType(candidate, "MemberExpression")) return isPureDerivedExpression(candidate.object) && (!candidate.computed || isPureDerivedExpression(candidate.property));
17608
- if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return isPureDerivedExpression(candidate.left) && isPureDerivedExpression(candidate.right);
17609
- if (isNodeOfType(candidate, "UnaryExpression")) return candidate.operator !== "delete" && isPureDerivedExpression(candidate.argument);
17610
- if (isNodeOfType(candidate, "ConditionalExpression")) return isPureDerivedExpression(candidate.test) && isPureDerivedExpression(candidate.consequent) && isPureDerivedExpression(candidate.alternate);
17611
- if (isNodeOfType(candidate, "TemplateLiteral")) return candidate.expressions.every((nestedExpression) => isPureDerivedExpression(nestedExpression));
17612
- return false;
17613
- };
17614
- const isPureDerivedStatement = (statement) => {
17615
- if (isNodeOfType(statement, "BlockStatement")) return statement.body.every((nestedStatement) => isPureDerivedStatement(nestedStatement));
17616
- if (isNodeOfType(statement, "ReturnStatement")) return !statement.argument || isPureDerivedExpression(statement.argument);
17617
- if (isNodeOfType(statement, "IfStatement")) return isPureDerivedExpression(statement.test) && isPureDerivedStatement(statement.consequent) && (!statement.alternate || isPureDerivedStatement(statement.alternate));
17618
- return false;
17619
- };
17620
- const isPureDerivedFunction = (functionNode) => {
17621
- if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return false;
17622
- if (functionNode.async || functionNode.generator) return false;
17623
- return isNodeOfType(functionNode.body, "BlockStatement") ? isPureDerivedStatement(functionNode.body) : isPureDerivedExpression(functionNode.body);
17624
- };
17625
- const resolvePureCalledFunctionSourceKeys = (reference, symbol, scopes) => {
17626
- if (symbol.references.some((symbolReference) => symbolReference.flag !== "read")) return null;
17627
- const referenceRoot = findTransparentExpressionRoot(reference.identifier);
17628
- const callExpression = referenceRoot.parent;
17629
- if (!isNodeOfType(callExpression, "CallExpression") || callExpression.callee !== referenceRoot) return null;
17630
- const functionNode = getFunctionValueNode(symbol);
17631
- if (!functionNode || !isPureDerivedFunction(functionNode)) return null;
17632
- const sourceKeys = /* @__PURE__ */ new Set();
17633
- for (const capturedReference of closureCaptures(functionNode, scopes)) {
17634
- const capturedSymbol = capturedReference.resolvedSymbol;
17635
- if (!capturedSymbol || capturedSymbol.id === symbol.id) continue;
17636
- if (isOutsideAllFunctions(capturedSymbol) || symbolHasStableValue(capturedSymbol, scopes)) continue;
17637
- const capturedKey = computeDepKey(capturedReference);
17638
- if (!capturedKey) return null;
17639
- if (capturedKey === capturedSymbol.name) {
17640
- const nestedSourceKeys = resolveReactiveIdentitySourceKeys(capturedSymbol, scopes);
17641
- if (nestedSourceKeys) {
17642
- for (const nestedSourceKey of nestedSourceKeys) sourceKeys.add(nestedSourceKey);
17643
- continue;
17644
- }
17645
- }
17646
- sourceKeys.add(capturedKey);
17647
- }
17648
- return sourceKeys.size > 0 ? sourceKeys : null;
17649
- };
17650
- const mergeDerivedExpressionSourceKeys = (expressions, scopes, visitedSymbolIds) => {
17651
- const sourceKeys = /* @__PURE__ */ new Set();
17652
- for (const expression of expressions) {
17653
- const expressionSourceKeys = resolveDerivedExpressionSourceKeys(expression, scopes, visitedSymbolIds);
17654
- if (!expressionSourceKeys) return null;
17655
- for (const expressionSourceKey of expressionSourceKeys) sourceKeys.add(expressionSourceKey);
17656
- }
17657
- return sourceKeys;
17658
- };
17659
- const resolveDerivedExpressionSourceKeys = (expression, scopes, visitedSymbolIds) => {
17660
- const candidate = unwrapExpression$3(expression);
17661
- if (isNodeOfType(candidate, "Literal")) return /* @__PURE__ */ new Set();
17662
- if (isNodeOfType(candidate, "Identifier")) {
17663
- if (scopes.isGlobalReference(candidate)) return /* @__PURE__ */ new Set();
17664
- const sourceSymbol = scopes.symbolFor(candidate);
17665
- if (!sourceSymbol) return null;
17666
- if (isOutsideAllFunctions(sourceSymbol) || symbolHasStableValue(sourceSymbol, scopes)) return /* @__PURE__ */ new Set();
17667
- 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)) {
17668
- visitedSymbolIds.add(sourceSymbol.id);
17669
- const sourceKeys = resolveDerivedExpressionSourceKeys(sourceSymbol.initializer, scopes, visitedSymbolIds);
17670
- visitedSymbolIds.delete(sourceSymbol.id);
17671
- if (sourceKeys) return sourceKeys;
17672
- }
17673
- return new Set([sourceSymbol.name]);
17674
- }
17675
- if (isNodeOfType(candidate, "MemberExpression")) {
17676
- if (hasComputedMemberExpression(candidate)) return null;
17677
- const sourceKey = stringifyMemberChain(candidate);
17678
- const rootIdentifier = getMemberRootIdentifier(candidate);
17679
- const rootSymbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
17680
- if (!sourceKey || !rootSymbol) return null;
17681
- if (isOutsideAllFunctions(rootSymbol) || symbolHasStableValue(rootSymbol, scopes)) return /* @__PURE__ */ new Set();
17682
- return new Set([sourceKey]);
17683
- }
17684
- if (isNodeOfType(candidate, "BinaryExpression") || isNodeOfType(candidate, "LogicalExpression")) return mergeDerivedExpressionSourceKeys([candidate.left, candidate.right], scopes, visitedSymbolIds);
17685
- if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator !== "delete") return resolveDerivedExpressionSourceKeys(candidate.argument, scopes, visitedSymbolIds);
17686
- if (isNodeOfType(candidate, "ConditionalExpression")) return mergeDerivedExpressionSourceKeys([
17687
- candidate.test,
17688
- candidate.consequent,
17689
- candidate.alternate
17690
- ], scopes, visitedSymbolIds);
17691
- if (isNodeOfType(candidate, "TemplateLiteral")) return mergeDerivedExpressionSourceKeys(candidate.expressions, scopes, visitedSymbolIds);
17692
- if (isNodeOfType(candidate, "NewExpression")) {
17693
- const callee = unwrapExpression$3(candidate.callee);
17694
- if (!isNodeOfType(callee, "Identifier") || callee.name !== "Error" || !scopes.isGlobalReference(callee)) return null;
17695
- const argumentsToAnalyze = [];
17696
- for (const argument of candidate.arguments) {
17697
- if (!isAstNode(argument) || isNodeOfType(argument, "SpreadElement")) return null;
17698
- argumentsToAnalyze.push(argument);
17699
- }
17700
- return mergeDerivedExpressionSourceKeys(argumentsToAnalyze, scopes, visitedSymbolIds);
17701
- }
17702
- return null;
17703
- };
17704
- const resolveWriteControlSourceKeys = (assignment, boundaryFunction, scopes) => {
17705
- const sourceKeys = /* @__PURE__ */ new Set();
17706
- let currentNode = assignment;
17707
- while (currentNode.parent && currentNode.parent !== boundaryFunction) {
17708
- const parentNode = currentNode.parent;
17709
- if (isNodeOfType(parentNode, "IfStatement")) {
17710
- if (parentNode.test === currentNode) return null;
17711
- const testSourceKeys = resolveDerivedExpressionSourceKeys(parentNode.test, scopes, /* @__PURE__ */ new Set());
17712
- if (!testSourceKeys) return null;
17713
- for (const testSourceKey of testSourceKeys) sourceKeys.add(testSourceKey);
17714
- } else if (!isNodeOfType(parentNode, "ExpressionStatement") && !isNodeOfType(parentNode, "BlockStatement")) return null;
17715
- currentNode = parentNode;
17716
- }
17717
- return currentNode.parent === boundaryFunction ? sourceKeys : null;
17718
- };
17719
- const isReadOnlyInitialStateUse = (referenceNode, scopes) => {
17720
- const referenceRoot = findTransparentExpressionRoot(referenceNode);
17721
- const callExpression = referenceRoot.parent;
17722
- return isNodeOfType(callExpression, "CallExpression") && callExpression.arguments.some((argument) => argument === referenceRoot) && isReactApiCall(callExpression, "useState", scopes, {
17723
- allowGlobalReactNamespace: true,
17724
- allowUnboundBareCalls: true,
17725
- resolveNamedAliases: true
17726
- });
17727
- };
17728
- const resolveRenderDerivedMutableSourceKeys = (capturedReference, symbol, scopes) => {
17729
- if (symbol.kind !== "let" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
17730
- const boundaryFunction = findEnclosingFunction$1(symbol.bindingIdentifier);
17731
- if (!boundaryFunction) return null;
17732
- const capturingFunction = findEnclosingFunction$1(capturedReference.identifier);
17733
- if (!capturingFunction || capturingFunction === boundaryFunction) return null;
17734
- const sourceKeys = /* @__PURE__ */ new Set();
17735
- if (symbol.initializer) {
17736
- const initializerSourceKeys = resolveDerivedExpressionSourceKeys(symbol.initializer, scopes, new Set([symbol.id]));
17737
- if (!initializerSourceKeys) return null;
17738
- for (const initializerSourceKey of initializerSourceKeys) sourceKeys.add(initializerSourceKey);
17739
- }
17740
- let writeCount = 0;
17741
- for (const symbolReference of symbol.references) {
17742
- if (symbolReference.flag === "read") {
17743
- if (findEnclosingFunction$1(symbolReference.identifier) !== capturingFunction && !isReadOnlyInitialStateUse(symbolReference.identifier, scopes)) return null;
17744
- continue;
17745
- }
17746
- if (symbolReference.flag !== "write") return null;
17747
- const referenceRoot = findTransparentExpressionRoot(symbolReference.identifier);
17748
- const assignment = referenceRoot.parent;
17749
- if (!isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== referenceRoot || findEnclosingFunction$1(referenceRoot) !== boundaryFunction) return null;
17750
- const assignmentSourceKeys = resolveDerivedExpressionSourceKeys(assignment.right, scopes, new Set([symbol.id]));
17751
- const controlSourceKeys = resolveWriteControlSourceKeys(assignment, boundaryFunction, scopes);
17752
- if (!assignmentSourceKeys || !controlSourceKeys) return null;
17753
- for (const assignmentSourceKey of assignmentSourceKeys) sourceKeys.add(assignmentSourceKey);
17754
- for (const controlSourceKey of controlSourceKeys) sourceKeys.add(controlSourceKey);
17755
- writeCount += 1;
17756
- }
17757
- return writeCount > 0 && sourceKeys.size > 0 ? sourceKeys : null;
17758
- };
17759
17052
  const isUseCallbackResultDep = (node, scopes) => {
17760
17053
  const rootSymbol = getRootSymbol(node, scopes);
17761
17054
  const initializer = rootSymbol?.initializer ? unwrapExpression$3(rootSymbol.initializer) : null;
@@ -18499,7 +17792,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
18499
17792
  if (!isUsed) continue;
18500
17793
  const reportNode = declaredKeyToReportNode.get(declaredKey) ?? depsArgument;
18501
17794
  const rootSymbol = getRootSymbol(reportNode, context.scopes);
18502
- if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) || symbolHasStableValue(rootSymbol, context.scopes) || !isUnstableInitializer(rootSymbol.initializer)) continue;
17795
+ if (!rootSymbol || !hasDirectIdentifierDeclarator(rootSymbol) || !isUnstableInitializer(rootSymbol.initializer)) continue;
18503
17796
  context.report({
18504
17797
  node: reportNode,
18505
17798
  message: buildUnstableDepMessage(hookName, declaredKey)
@@ -29983,7 +29276,7 @@ const nextjsNoVercelOgImport = defineRule({
29983
29276
  //#endregion
29984
29277
  //#region src/plugin/rules/a11y/no-access-key.ts
29985
29278
  const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
29986
- const isUndefinedIdentifier$1 = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
29279
+ const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
29987
29280
  const noAccessKey = defineRule({
29988
29281
  id: "no-access-key",
29989
29282
  title: "accessKey attribute used",
@@ -30008,7 +29301,7 @@ const noAccessKey = defineRule({
30008
29301
  if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
30009
29302
  const expression = attributeValue.expression;
30010
29303
  if (!expression || expression.type === "JSXEmptyExpression") return;
30011
- if (isUndefinedIdentifier$1(expression)) return;
29304
+ if (isUndefinedIdentifier(expression)) return;
30012
29305
  context.report({
30013
29306
  node: accessKey,
30014
29307
  message: MESSAGE$39
@@ -30773,12 +30066,6 @@ const isReactNamespaceImportReference = (ref) => Boolean(ref?.resolved?.defs.som
30773
30066
  const importDeclaration = declarationNode.parent;
30774
30067
  return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
30775
30068
  }));
30776
- const isReactNamespaceReceiver = (analysis, node) => {
30777
- const receiver = stripParenExpression(node);
30778
- if (!isNodeOfType(receiver, "Identifier")) return false;
30779
- const namespaceReference = getRef(analysis, receiver);
30780
- return namespaceReference?.resolved ? isReactNamespaceImportReference(namespaceReference) : receiver.name === "React";
30781
- };
30782
30069
  const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
30783
30070
  if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return false;
30784
30071
  const callee = stripParenExpression(declarator.init.callee);
@@ -30787,20 +30074,24 @@ const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
30787
30074
  if (!reference?.resolved) return callee.name === hookName;
30788
30075
  return isReactNamedImportReference(reference, hookName);
30789
30076
  }
30790
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
30791
- return isReactNamespaceReceiver(analysis, callee.object);
30077
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
30078
+ const namespaceReference = getRef(analysis, callee.object);
30079
+ if (!namespaceReference?.resolved) return callee.object.name === "React";
30080
+ return isReactNamespaceImportReference(namespaceReference);
30792
30081
  };
30793
30082
  const isHookCallee$1 = (analysis, node, hookName) => {
30794
30083
  if (!node) return false;
30795
30084
  if (isNodeOfType(node, "Identifier")) {
30796
30085
  if (node.name === hookName) return true;
30797
30086
  if (isReactNamedImportReference(getRef(analysis, node), hookName)) return true;
30798
- const receiverRoot = findTransparentExpressionRoot(node);
30799
- const parent = receiverRoot.parent;
30800
- if (parent && isNodeOfType(parent, "MemberExpression") && parent.object === receiverRoot && isReactNamespaceReceiver(analysis, node) && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
30087
+ const parent = node.parent;
30088
+ if (parent && isNodeOfType(parent, "MemberExpression") && isNodeOfType(parent.object, "Identifier") && parent.object.name === "React" && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
30801
30089
  return false;
30802
30090
  }
30803
- if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
30091
+ if (isNodeOfType(node, "MemberExpression")) {
30092
+ const receiver = stripParenExpression(node.object);
30093
+ return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
30094
+ }
30804
30095
  return false;
30805
30096
  };
30806
30097
  const isUseEffect = (node) => {
@@ -30908,27 +30199,7 @@ const isRefCurrent = (ref) => {
30908
30199
  if (!isNodeOfType(parent.property, "Identifier")) return false;
30909
30200
  return parent.property.name === "current";
30910
30201
  };
30911
- const resolveStateSetterReference = (analysis, ref) => {
30912
- const visitedReferences = /* @__PURE__ */ new Set();
30913
- let currentReference = ref;
30914
- while (currentReference && !visitedReferences.has(currentReference)) {
30915
- if (isStateSetter(analysis, currentReference)) return currentReference;
30916
- visitedReferences.add(currentReference);
30917
- const definitions = currentReference.resolved?.defs ?? [];
30918
- if (definitions.length !== 1) return null;
30919
- const definitionNode = definitions[0].node;
30920
- if (!isNodeOfType(definitionNode, "VariableDeclarator")) return null;
30921
- if (!isNodeOfType(definitionNode.id, "Identifier")) return null;
30922
- const declaration = definitionNode.parent;
30923
- if (!isNodeOfType(declaration, "VariableDeclaration") || declaration.kind !== "const") return null;
30924
- if (!definitionNode.init) return null;
30925
- const initializer = stripParenExpression(definitionNode.init);
30926
- if (!isNodeOfType(initializer, "Identifier")) return null;
30927
- currentReference = getRef(analysis, initializer);
30928
- }
30929
- return null;
30930
- };
30931
- const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => resolveStateSetterReference(analysis, innerRef) !== null);
30202
+ const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isStateSetter(analysis, innerRef));
30932
30203
  const isSyncStateSetterCall = (analysis, ref, effectFn) => isStateSetterCall(analysis, ref) && isSynchronous(ref.identifier, effectFn) && !resolvesToAsyncFunction(ref);
30933
30204
  const HANDLER_NAMED_METHOD_PATTERN = /^(on|handle)[A-Z]/;
30934
30205
  const SYNCHRONOUS_CALLBACK_ARGUMENT_INDEX_BY_METHOD = new Map([
@@ -31079,11 +30350,9 @@ const isPropCallbackInvocationRef = (analysis, ref, options = {}) => {
31079
30350
  };
31080
30351
  const isRefCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isRefCurrent(innerRef) || isRef(analysis, innerRef));
31081
30352
  const getUseStateDecl = (analysis, ref) => {
31082
- const definition = getUpstreamRefs(analysis, ref).find((upstreamReference) => isState(analysis, upstreamReference) || isStateSetter(analysis, upstreamReference))?.resolved?.defs.find((candidateDefinition) => {
31083
- const definitionNode = candidateDefinition.node;
31084
- return isNodeOfType(definitionNode, "VariableDeclarator") && isNodeOfType(definitionNode.init, "CallExpression") && isHookCallee$1(analysis, definitionNode.init.callee, "useState");
31085
- });
31086
- return definition ? definition.node : null;
30353
+ let node = getUpstreamRefs(analysis, ref).find((upRef) => isHookCallee$1(analysis, upRef.identifier, "useState"))?.identifier;
30354
+ while (node && !isNodeOfType(node, "VariableDeclarator")) node = node.parent;
30355
+ return node ?? null;
31087
30356
  };
31088
30357
  const isCleanupReturnArgument = (analysis, node) => {
31089
30358
  if (isFunctionLike$1(node)) return true;
@@ -31246,88 +30515,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
31246
30515
  if (HANDLER_BINDING_NAME_PATTERN.test(bindingName)) return true;
31247
30516
  return isSetterWiredToJsxHandler(componentFunction, bindingName);
31248
30517
  };
31249
- const isSynchronousFunction = (functionNode) => {
31250
- const functionMetadata = functionNode;
31251
- return functionMetadata.async !== true && functionMetadata.generator !== true;
31252
- };
31253
- const findBindingVariable = (analysis, bindingIdentifier) => {
31254
- for (const scope of analysis.scopeManager.scopes) for (const variable of scope.variables) if (variable.identifiers.includes(bindingIdentifier)) return variable;
31255
- return null;
31256
- };
31257
- const getImmutableFunctionVariable = (analysis, componentFunction, functionNode) => {
31258
- if (!isSynchronousFunction(functionNode) || !isAstDescendant(functionNode, componentFunction)) return null;
31259
- const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
31260
- if (!bindingIdentifier) return null;
31261
- const variable = findBindingVariable(analysis, bindingIdentifier);
31262
- if (!variable || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && !reference.init)) return null;
31263
- const definition = variable.defs[0];
31264
- if (definition.type === "FunctionName") return definition.node === functionNode ? variable : null;
31265
- if (definition.type !== "Variable") return null;
31266
- const declarator = definition.node;
31267
- if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return null;
31268
- if (declarator.init === functionNode) return variable;
31269
- if (isNodeOfType(declarator.init, "CallExpression") && declarator.init.arguments?.[0] === functionNode && isGenuineReactHookDeclarator(analysis, declarator, "useCallback")) return variable;
31270
- return null;
31271
- };
31272
- const getJsxEventValueAttribute = (identifier) => {
31273
- const expression = findTransparentExpressionRoot(identifier);
31274
- const expressionContainer = expression.parent;
31275
- if (!isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== expression) return null;
31276
- const attribute = expressionContainer.parent;
31277
- if (!isNodeOfType(attribute, "JSXAttribute")) return null;
31278
- const attributeName = getJsxAttributeName(attribute.name);
31279
- return attributeName && isEventHandlerName(attributeName) ? attribute : null;
31280
- };
31281
- const getInlineJsxEventCallbackAttribute = (callExpression) => {
31282
- const callbackFunction = findEnclosingFunction$1(callExpression);
31283
- if (!callbackFunction || !isSynchronousFunction(callbackFunction)) return null;
31284
- return getJsxEventValueAttribute(callbackFunction);
31285
- };
31286
- const isReactHookDependencyReference = (identifier) => {
31287
- const expression = findTransparentExpressionRoot(identifier);
31288
- const dependencyArray = expression.parent;
31289
- if (!isNodeOfType(dependencyArray, "ArrayExpression") || !(dependencyArray.elements ?? []).includes(expression)) return false;
31290
- const hookCall = dependencyArray.parent;
31291
- if (!isNodeOfType(hookCall, "CallExpression") || hookCall.arguments?.[1] !== dependencyArray) return false;
31292
- const callee = hookCall.callee;
31293
- if (isNodeOfType(callee, "Identifier")) return /^use[A-Z0-9]/.test(callee.name);
31294
- return Boolean(isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && /^use[A-Z0-9]/.test(callee.property.name));
31295
- };
31296
- const hasReachableJsxEventCallPath = (analysis, context, componentFunction, functionVariable, visitedVariables) => {
31297
- if (visitedVariables.has(functionVariable)) return false;
31298
- const nextVisitedVariables = new Set(visitedVariables).add(functionVariable);
31299
- const callExpressions = [];
31300
- let hasDirectJsxEventReference = false;
31301
- for (const reference of functionVariable.references) {
31302
- if (reference.init) continue;
31303
- const identifier = reference.identifier;
31304
- if (reference.isWrite()) return false;
31305
- const jsxEventValueAttribute = getJsxEventValueAttribute(identifier);
31306
- if (jsxEventValueAttribute) {
31307
- if (isNodeReachableWithinFunction(jsxEventValueAttribute, context)) hasDirectJsxEventReference = true;
31308
- continue;
31309
- }
31310
- if (isReactHookDependencyReference(identifier)) continue;
31311
- const callExpression = getCallExpr(reference);
31312
- if (!callExpression) return false;
31313
- const jsxEventCallbackAttribute = getInlineJsxEventCallbackAttribute(callExpression);
31314
- if (jsxEventCallbackAttribute) {
31315
- if (isNodeReachableWithinFunction(callExpression, context) && isNodeReachableWithinFunction(jsxEventCallbackAttribute, context)) hasDirectJsxEventReference = true;
31316
- continue;
31317
- }
31318
- callExpressions.push(callExpression);
31319
- }
31320
- if (hasDirectJsxEventReference) return true;
31321
- for (const callExpression of callExpressions) {
31322
- if (!isNodeReachableWithinFunction(callExpression, context)) continue;
31323
- const callerFunction = findEnclosingFunction$1(callExpression);
31324
- if (!callerFunction || callerFunction === componentFunction) continue;
31325
- const callerVariable = getImmutableFunctionVariable(analysis, componentFunction, callerFunction);
31326
- if (callerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, callerVariable, nextVisitedVariables)) return true;
31327
- }
31328
- return false;
31329
- };
31330
- const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, includeDeferredWriters = false) => {
30518
+ const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
31331
30519
  if (!setterRef.resolved) return false;
31332
30520
  const componentFunction = findEnclosingFunction$1(effectNode);
31333
30521
  if (!componentFunction) return false;
@@ -31336,11 +30524,6 @@ const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, incl
31336
30524
  const identifier = reference.identifier;
31337
30525
  if (isAstDescendant(identifier, effectNode)) continue;
31338
30526
  if (isIndependentWriterIdentifier(componentFunction, identifier, includeDeferredWriters)) return true;
31339
- if (!isNodeReachableWithinFunction(identifier, context)) continue;
31340
- const writerFunction = findEnclosingFunction$1(identifier);
31341
- if (!writerFunction || writerFunction === componentFunction) continue;
31342
- const writerVariable = getImmutableFunctionVariable(analysis, componentFunction, writerFunction);
31343
- if (writerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, writerVariable, /* @__PURE__ */ new Set())) return true;
31344
30527
  }
31345
30528
  return false;
31346
30529
  };
@@ -32230,7 +31413,7 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
32230
31413
  }
32231
31414
  return false;
32232
31415
  };
32233
- const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFilename) => {
31416
+ const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
32234
31417
  const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
32235
31418
  if (frames.length === 0) return [];
32236
31419
  const effectHasCleanup = hasCleanup(analysis, effectNode);
@@ -32260,7 +31443,7 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
32260
31443
  for (const returnedExpression of returnedExpressions) mergeEvidence(valueEvidence, collectValueEvidence(analysis, returnedExpression, updaterFrame, remainingValueCallFrames));
32261
31444
  } else valueEvidence = collectValueEvidence(analysis, writtenValue, frame, remainingValueCallFrames);
32262
31445
  const sourceReferences = [...valueEvidence.sourceReferences].filter((sourceReference) => getUseStateDecl(analysis, sourceReference) !== stateDeclarator);
32263
- const hasIndependentWriter = hasUserInputSetterWriter(analysis, context, setterReference, effectNode, true);
31446
+ const hasIndependentWriter = hasUserInputSetterWriter(setterReference, effectNode, true);
32264
31447
  const doesMatchStateInitializer = matchesStateInitializer(analysis, callExpression, stateDeclarator);
32265
31448
  if (effectHasCleanup && (frame.isDeferred || valueEvidence.hasUnknownSource || valueEvidence.hasDeferredIntroducedValue || valueEvidence.readsExternalValue)) cleanupManagedStateDeclarators.add(stateDeclarator);
32266
31449
  const isRenderKnownCopy = sourceReferences.length > 0 && !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue && !hasIndependentWriter;
@@ -32272,7 +31455,6 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
32272
31455
  sourceReferences,
32273
31456
  isDeferred: frame.isDeferred,
32274
31457
  isRenderKnownCopy,
32275
- isSynchronousRenderValue: !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue,
32276
31458
  matchesStateInitializer: doesMatchStateInitializer,
32277
31459
  resetsSourceState: false
32278
31460
  });
@@ -32288,76 +31470,22 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
32288
31470
  });
32289
31471
  };
32290
31472
  //#endregion
32291
- //#region src/plugin/rules/state-and-effects/utils/has-deferred-or-external-effect-work.ts
32292
- const DEFERRED_MEMBER_NAMES = new Set([
32293
- "catch",
32294
- "finally",
32295
- "then"
32296
- ]);
32297
- const hasDeferredOrExternalEffectWork = (analysis, effectNode, scopes) => {
32298
- const effectFunction = getEffectFn(analysis, effectNode);
32299
- if (!effectFunction) return false;
32300
- if (containsFetchCall(effectFunction, { stopAtFunctionBoundary: true })) return true;
32301
- const effectInvokedFunctions = collectEffectInvokedFunctions(effectFunction);
32302
- let didFindDeferredOrExternalWork = false;
32303
- walkAst(effectFunction, (child) => {
32304
- if (didFindDeferredOrExternalWork) return false;
32305
- if (child !== effectFunction && isFunctionLike$1(child) && !effectInvokedFunctions.has(child)) return false;
32306
- if (isNodeOfType(child, "AssignmentExpression")) {
32307
- const assignmentTarget = child.left;
32308
- if ((isNodeOfType(assignmentTarget, "MemberExpression") ? getStaticPropertyName(assignmentTarget) : null)?.startsWith("on") && isFunctionLike$1(child.right)) {
32309
- didFindDeferredOrExternalWork = true;
32310
- return false;
32311
- }
32312
- }
32313
- if (!isNodeOfType(child, "CallExpression")) return;
32314
- if (isSubscribeOrObserveCallExpression(child)) {
32315
- didFindDeferredOrExternalWork = true;
32316
- return false;
32317
- }
32318
- const localFunction = resolveExactLocalFunction(child.callee, scopes);
32319
- if (isFunctionLike$1(localFunction) && localFunction.async) {
32320
- didFindDeferredOrExternalWork = true;
32321
- return false;
32322
- }
32323
- const callee = child.callee;
32324
- if (isNodeOfType(callee, "Identifier") && TIMER_AND_SCHEDULER_DIRECT_CALLEE_NAMES.has(callee.name)) {
32325
- didFindDeferredOrExternalWork = true;
32326
- return false;
32327
- }
32328
- const memberName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
32329
- if (memberName && DEFERRED_MEMBER_NAMES.has(memberName)) {
32330
- didFindDeferredOrExternalWork = true;
32331
- return false;
32332
- }
32333
- });
32334
- return didFindDeferredOrExternalWork;
32335
- };
32336
- //#endregion
32337
31473
  //#region src/plugin/rules/state-and-effects/no-adjust-state-on-prop-change.ts
32338
31474
  const noAdjustStateOnPropChange = defineRule({
32339
31475
  id: "no-adjust-state-on-prop-change",
32340
- title: "State adjusted after a prop changes",
31476
+ title: "State synced to a prop inside an effect",
32341
31477
  severity: "warn",
32342
31478
  tags: ["test-noise"],
32343
- recommendation: "Remove the adjustment effect by deriving values during render, resetting the component with a key, or updating related state in the event that changes the prop. Avoid tracking the previous prop in more state, which preserves the duplication. See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes",
31479
+ recommendation: "Adjust the state inline during render with a `prev`-prop comparison (`if (prop !== prevProp) { setPrevProp(prop); setX(...); }`), or refactor to remove the duplicated state. Routing the adjustment through a useEffect forces an extra render with a stale UI between the two commits. See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes",
32344
31480
  create: (context) => ({ CallExpression(node) {
32345
- if (!isReactApiCall(node, "useEffect", context.scopes, {
32346
- allowGlobalReactNamespace: true,
32347
- allowUnboundBareCalls: true,
32348
- resolveConditionalAliases: true,
32349
- resolveNamedAliases: true
32350
- })) return;
31481
+ if (!isUseEffect(node)) return;
32351
31482
  const analysis = getProgramAnalysis(node);
32352
31483
  if (!analysis) return;
32353
31484
  const dependencyReferences = getEffectDepsRefs(analysis, node);
32354
31485
  if (!dependencyReferences) return;
32355
31486
  if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
32356
- const facts = collectEffectStateWriteFacts(analysis, context, node, context.filename);
32357
- if (hasCleanup(analysis, node) || hasDeferredOrExternalEffectWork(analysis, node, context.scopes) || facts.some((fact) => fact.isDeferred)) return;
32358
- for (const fact of facts) {
32359
- if (!fact.isSynchronousRenderValue || fact.resetsSourceState) continue;
32360
- if (fact.sourceReferences.flatMap((reference) => getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) continue;
31487
+ for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
31488
+ if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
32361
31489
  context.report({
32362
31490
  node: fact.callExpression,
32363
31491
  message: "This effect adjusts state after a prop changes, so users briefly see the stale value."
@@ -35077,7 +34205,7 @@ const noChainStateUpdates = defineRule({
35077
34205
  if (!callExpr) continue;
35078
34206
  if (!isReachableFromStateTrigger(callExpr)) continue;
35079
34207
  if (!readsPostMountValueThroughLocals(callExpr, effectFn, { ignoreBareRefCurrent: true })) continue;
35080
- const declarator = getUseStateDeclarator(resolveStateSetterReference(analysis, ref) ?? ref);
34208
+ const declarator = getUseStateDeclarator(ref);
35081
34209
  if (declarator) domSyncedStateDeclarators.add(declarator);
35082
34210
  }
35083
34211
  for (const ref of effectFnRefs) {
@@ -35086,7 +34214,7 @@ const noChainStateUpdates = defineRule({
35086
34214
  if (!callExpr) continue;
35087
34215
  if (!isReachableFromStateTrigger(callExpr)) continue;
35088
34216
  if (getArgsUpstreamRefs(analysis, ref).some((argRef) => isState(analysis, argRef))) continue;
35089
- const setterDeclarator = getUseStateDeclarator(resolveStateSetterReference(analysis, ref) ?? ref);
34217
+ const setterDeclarator = getUseStateDeclarator(ref);
35090
34218
  if (setterDeclarator && domSyncedStateDeclarators.has(setterDeclarator)) continue;
35091
34219
  const isSelfTargeting = setterDeclarator !== null && stateDepDeclarators.has(setterDeclarator);
35092
34220
  const setterArguments = isNodeOfType(callExpr, "CallExpression") ? callExpr.arguments ?? [] : [];
@@ -36927,15 +36055,10 @@ const noDerivedState = defineRule({
36927
36055
  for (const fact of collectRenderStateWriteFacts(analysis, componentBody, context.filename)) reportStateWrite(fact.callExpression, fact.stateDeclarator);
36928
36056
  } }).visitors,
36929
36057
  CallExpression(node) {
36930
- if (!isReactApiCall(node, "useEffect", context.scopes, {
36931
- allowGlobalReactNamespace: true,
36932
- allowUnboundBareCalls: true,
36933
- resolveConditionalAliases: true,
36934
- resolveNamedAliases: true
36935
- })) return;
36058
+ if (!isUseEffect(node)) return;
36936
36059
  const analysis = getProgramAnalysis(node);
36937
36060
  if (!analysis) return;
36938
- for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
36061
+ for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
36939
36062
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
36940
36063
  reportStateWrite(fact.callExpression, fact.stateDeclarator);
36941
36064
  }
@@ -36952,15 +36075,10 @@ const noDerivedStateEffect = defineRule({
36952
36075
  tags: ["test-noise"],
36953
36076
  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",
36954
36077
  create: (context) => ({ CallExpression(node) {
36955
- if (!isReactApiCall(node, EFFECT_HOOK_NAMES$1, context.scopes, {
36956
- allowGlobalReactNamespace: true,
36957
- allowUnboundBareCalls: true,
36958
- resolveConditionalAliases: true,
36959
- resolveNamedAliases: true
36960
- })) return;
36078
+ if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
36961
36079
  const analysis = getProgramAnalysis(node);
36962
36080
  if (!analysis) return;
36963
- if (!collectEffectStateWriteFacts(analysis, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
36081
+ if (!collectEffectStateWriteFacts(analysis, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
36964
36082
  context.report({
36965
36083
  node,
36966
36084
  message: "You pay an extra render for state you can derive from other values."
@@ -37436,20 +36554,9 @@ const noDidMountSetState = defineRule({
37436
36554
  }
37437
36555
  });
37438
36556
  //#endregion
37439
- //#region src/plugin/utils/find-enclosing-class.ts
37440
- const findEnclosingClass = (node) => {
37441
- let ancestor = node.parent;
37442
- while (ancestor) {
37443
- if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
37444
- ancestor = ancestor.parent ?? null;
37445
- }
37446
- return null;
37447
- };
37448
- //#endregion
37449
36557
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
37450
36558
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
37451
36559
  const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
37452
- const DIFFERENCE_OPERATORS = new Set(["!=", "!=="]);
37453
36560
  const EQUALITY_OPERATORS = new Set([
37454
36561
  "==",
37455
36562
  "===",
@@ -37461,8 +36568,6 @@ const FUNCTION_NODE_TYPES = new Set([
37461
36568
  "FunctionExpression",
37462
36569
  "ArrowFunctionExpression"
37463
36570
  ]);
37464
- const CLASS_NODE_TYPES = new Set(["ClassDeclaration", "ClassExpression"]);
37465
- const callbackRefFieldNamesByClass = /* @__PURE__ */ new WeakMap();
37466
36571
  const isLifecycleMethodFunction = (node) => {
37467
36572
  if (!FUNCTION_NODE_TYPES.has(node.type)) return false;
37468
36573
  const parent = node.parent;
@@ -37518,187 +36623,6 @@ const getStaticMemberName = (node) => {
37518
36623
  if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
37519
36624
  return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
37520
36625
  };
37521
- const getMemberIdentity = (property) => {
37522
- const propertyName = getPropertyKeyName$2(property);
37523
- if (propertyName !== void 0) return isNodeOfType(property, "PrivateIdentifier") ? `#${propertyName}` : propertyName;
37524
- return isNodeOfType(property, "Literal") && typeof property.value === "string" ? property.value : null;
37525
- };
37526
- const collectPreviousSourcePaths = (pattern, domain, members, previousSourcePaths) => {
37527
- if (!pattern) return;
37528
- const unwrappedPattern = stripParenExpression(pattern);
37529
- if (isNodeOfType(unwrappedPattern, "Identifier")) {
37530
- previousSourcePaths.set(unwrappedPattern.name, {
37531
- domain,
37532
- members: [...members],
37533
- source: "previous"
37534
- });
37535
- return;
37536
- }
37537
- if (isNodeOfType(unwrappedPattern, "AssignmentPattern")) {
37538
- collectPreviousSourcePaths(unwrappedPattern.left, domain, members, previousSourcePaths);
37539
- return;
37540
- }
37541
- if (!isNodeOfType(unwrappedPattern, "ObjectPattern")) return;
37542
- for (const property of unwrappedPattern.properties) {
37543
- if (!isNodeOfType(property, "Property")) continue;
37544
- const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
37545
- if (!propertyName) continue;
37546
- collectPreviousSourcePaths(property.value, domain, [...members, propertyName], previousSourcePaths);
37547
- }
37548
- };
37549
- const getStateSourcePath = (node, previousSourcePaths) => {
37550
- let currentNode = stripParenExpression(node);
37551
- const members = [];
37552
- while (isNodeOfType(currentNode, "MemberExpression")) {
37553
- const memberName = getStaticMemberName(currentNode);
37554
- if (!memberName) return null;
37555
- members.unshift(memberName);
37556
- currentNode = stripParenExpression(currentNode.object);
37557
- }
37558
- if (isNodeOfType(currentNode, "ThisExpression")) {
37559
- const [domain, ...pathMembers] = members;
37560
- if (domain !== "props" && domain !== "state") return null;
37561
- return {
37562
- domain,
37563
- members: pathMembers,
37564
- source: "current"
37565
- };
37566
- }
37567
- if (!isNodeOfType(currentNode, "Identifier")) return null;
37568
- const previousSourcePath = previousSourcePaths.get(currentNode.name);
37569
- return previousSourcePath ? {
37570
- ...previousSourcePath,
37571
- members: [...previousSourcePath.members, ...members]
37572
- } : null;
37573
- };
37574
- const haveMatchingStateSourcePaths = (left, right) => left.domain === right.domain && left.members.length === right.members.length && left.members.every((member, index) => member === right.members[index]);
37575
- const collectConjunctiveStateSourceComparisons = (test, previousSourcePaths, comparisons) => {
37576
- const expression = stripParenExpression(test);
37577
- if (isNodeOfType(expression, "LogicalExpression") && expression.operator === "&&") {
37578
- collectConjunctiveStateSourceComparisons(expression.left, previousSourcePaths, comparisons);
37579
- collectConjunctiveStateSourceComparisons(expression.right, previousSourcePaths, comparisons);
37580
- return;
37581
- }
37582
- if (!isNodeOfType(expression, "BinaryExpression") || !EQUALITY_OPERATORS.has(expression.operator)) return;
37583
- const leftPath = getStateSourcePath(expression.left, previousSourcePaths);
37584
- const rightPath = getStateSourcePath(expression.right, previousSourcePaths);
37585
- if (Boolean(leftPath) === Boolean(rightPath)) return;
37586
- const path = leftPath ?? rightPath;
37587
- if (!path) return;
37588
- comparisons.push({
37589
- comparedValue: leftPath ? expression.right : expression.left,
37590
- isDifference: DIFFERENCE_OPERATORS.has(expression.operator),
37591
- path
37592
- });
37593
- };
37594
- const isHistoricalToCurrentTransitionGuard = (test, previousSourcePaths) => {
37595
- const expression = stripParenExpression(test);
37596
- if (isNodeOfType(expression, "LogicalExpression") && expression.operator === "||") return isHistoricalToCurrentTransitionGuard(expression.left, previousSourcePaths) && isHistoricalToCurrentTransitionGuard(expression.right, previousSourcePaths);
37597
- const comparisons = [];
37598
- collectConjunctiveStateSourceComparisons(expression, previousSourcePaths, comparisons);
37599
- return comparisons.some((comparison, index) => comparisons.slice(index + 1).some((candidate) => comparison.path.source !== candidate.path.source && comparison.isDifference !== candidate.isDifference && haveMatchingStateSourcePaths(comparison.path, candidate.path) && areExpressionsStructurallyEqual(comparison.comparedValue, candidate.comparedValue)));
37600
- };
37601
- const getThisFieldName = (node) => {
37602
- const unwrappedNode = stripParenExpression(node);
37603
- if (!isNodeOfType(unwrappedNode, "MemberExpression") || unwrappedNode.computed === true || !isNodeOfType(stripParenExpression(unwrappedNode.object), "ThisExpression")) return null;
37604
- return getMemberIdentity(unwrappedNode.property);
37605
- };
37606
- const isUndefinedIdentifier = (node) => {
37607
- const unwrappedNode = stripParenExpression(node);
37608
- return isNodeOfType(unwrappedNode, "Identifier") && unwrappedNode.name === "undefined";
37609
- };
37610
- const isDirectRefParameterValue = (node, parameterSymbolId, scopes) => {
37611
- const unwrappedNode = stripParenExpression(node);
37612
- if (isNodeOfType(unwrappedNode, "Identifier")) return scopes.symbolFor(unwrappedNode)?.id === parameterSymbolId;
37613
- if (!isNodeOfType(unwrappedNode, "LogicalExpression") || unwrappedNode.operator !== "??") return false;
37614
- const left = stripParenExpression(unwrappedNode.left);
37615
- return isNodeOfType(left, "Identifier") && scopes.symbolFor(left)?.id === parameterSymbolId && isUndefinedIdentifier(unwrappedNode.right);
37616
- };
37617
- const getCallbackRefAssignedFields = (callback, scopes) => {
37618
- const firstParameter = (callback.params ?? [])[0];
37619
- if (!firstParameter) return /* @__PURE__ */ new Set();
37620
- const parameterIdentifier = isNodeOfType(firstParameter, "AssignmentPattern") ? firstParameter.left : firstParameter;
37621
- if (!isNodeOfType(parameterIdentifier, "Identifier")) return /* @__PURE__ */ new Set();
37622
- const parameterSymbolId = scopes.symbolFor(parameterIdentifier)?.id;
37623
- if (parameterSymbolId === void 0) return /* @__PURE__ */ new Set();
37624
- const body = callback.body;
37625
- if (!body) return /* @__PURE__ */ new Set();
37626
- const assignedFieldNames = /* @__PURE__ */ new Set();
37627
- walkAst(body, (node) => {
37628
- if (node !== body && (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node) || CLASS_NODE_TYPES.has(node.type))) return false;
37629
- const assignmentTarget = isNodeOfType(node, "AssignmentExpression") && node.left || isNodeOfType(node, "UpdateExpression") && node.argument || isNodeOfType(node, "UnaryExpression") && node.operator === "delete" && node.argument || null;
37630
- if (!assignmentTarget) return;
37631
- const fieldName = getThisFieldName(assignmentTarget);
37632
- if (!fieldName) return;
37633
- if (isNodeOfType(node, "AssignmentExpression") && node.operator === "=" && isDirectRefParameterValue(node.right, parameterSymbolId, scopes)) {
37634
- assignedFieldNames.add(fieldName);
37635
- return;
37636
- }
37637
- assignedFieldNames.delete(fieldName);
37638
- });
37639
- return assignedFieldNames;
37640
- };
37641
- const getClassMemberCallback = (classNode, memberName) => {
37642
- const classBody = classNode.body?.body ?? [];
37643
- for (const member of classBody) {
37644
- if (!isNodeOfType(member, "MethodDefinition") && !isNodeOfType(member, "PropertyDefinition")) continue;
37645
- if (member.static === true) continue;
37646
- const key = member.key;
37647
- if (getMemberIdentity(key) !== memberName) continue;
37648
- const value = member.value;
37649
- return value && FUNCTION_NODE_TYPES.has(value.type) ? value : null;
37650
- }
37651
- return null;
37652
- };
37653
- const collectCallbackRefFieldsFromExpression = (expression, classNode, fieldNames, scopes) => {
37654
- const unwrappedExpression = stripParenExpression(expression);
37655
- if (FUNCTION_NODE_TYPES.has(unwrappedExpression.type)) {
37656
- for (const fieldName of getCallbackRefAssignedFields(unwrappedExpression, scopes)) fieldNames.add(fieldName);
37657
- return;
37658
- }
37659
- const handlerName = getThisFieldName(unwrappedExpression);
37660
- if (handlerName) {
37661
- const callback = getClassMemberCallback(classNode, handlerName);
37662
- if (callback) for (const fieldName of getCallbackRefAssignedFields(callback, scopes)) fieldNames.add(fieldName);
37663
- return;
37664
- }
37665
- if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
37666
- collectCallbackRefFieldsFromExpression(unwrappedExpression.consequent, classNode, fieldNames, scopes);
37667
- collectCallbackRefFieldsFromExpression(unwrappedExpression.alternate, classNode, fieldNames, scopes);
37668
- return;
37669
- }
37670
- if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
37671
- if (unwrappedExpression.operator !== "&&") collectCallbackRefFieldsFromExpression(unwrappedExpression.left, classNode, fieldNames, scopes);
37672
- collectCallbackRefFieldsFromExpression(unwrappedExpression.right, classNode, fieldNames, scopes);
37673
- }
37674
- };
37675
- const getCallbackRefFieldNames = (classNode, scopes) => {
37676
- if (!classNode) return /* @__PURE__ */ new Set();
37677
- const cachedFieldNames = callbackRefFieldNamesByClass.get(classNode);
37678
- if (cachedFieldNames) return cachedFieldNames;
37679
- const fieldNames = /* @__PURE__ */ new Set();
37680
- const classBody = classNode.body;
37681
- if (classBody) walkAst(classBody, (node) => {
37682
- if (node !== classBody && CLASS_NODE_TYPES.has(node.type)) return false;
37683
- if (!isNodeOfType(node, "JSXAttribute") || !isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "ref" || !node.value || !isNodeOfType(node.value, "JSXExpressionContainer") || !node.value.expression) return;
37684
- collectCallbackRefFieldsFromExpression(node.value.expression, classNode, fieldNames, scopes);
37685
- });
37686
- callbackRefFieldNamesByClass.set(classNode, fieldNames);
37687
- return fieldNames;
37688
- };
37689
- const collectLifecycleWrittenFieldNames = (lifecycleFunction) => {
37690
- const fieldNames = /* @__PURE__ */ new Set();
37691
- const body = lifecycleFunction.body;
37692
- if (!body) return fieldNames;
37693
- walkAst(body, (node) => {
37694
- if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
37695
- const target = isNodeOfType(node, "AssignmentExpression") && node.left || isNodeOfType(node, "UpdateExpression") && node.argument || null;
37696
- if (!target) return;
37697
- const fieldName = getThisFieldName(target);
37698
- if (fieldName) fieldNames.add(fieldName);
37699
- });
37700
- return fieldNames;
37701
- };
37702
36626
  const getThisStateFieldName = (node) => {
37703
36627
  const unwrappedNode = stripParenExpression(node);
37704
36628
  if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
@@ -37716,17 +36640,15 @@ const collectLocalInitializers = (lifecycleFunction) => {
37716
36640
  });
37717
36641
  return initializers;
37718
36642
  };
37719
- const derivesFromPostMountValue = (node, localInitializers, callbackRefFieldNames, visitedNames = /* @__PURE__ */ new Set()) => {
36643
+ const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
37720
36644
  if (readsPostMountValue(node)) return true;
37721
- const fieldName = getThisFieldName(node);
37722
- if (fieldName && callbackRefFieldNames.has(fieldName)) return true;
37723
36645
  const referencedNames = /* @__PURE__ */ new Set();
37724
36646
  collectReferenceIdentifierNames(node, referencedNames);
37725
36647
  for (const referencedName of referencedNames) {
37726
36648
  if (visitedNames.has(referencedName)) continue;
37727
36649
  const initializer = localInitializers.get(referencedName);
37728
36650
  if (!initializer) continue;
37729
- if (derivesFromPostMountValue(initializer, localInitializers, callbackRefFieldNames, new Set([...visitedNames, referencedName]))) return true;
36651
+ if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
37730
36652
  }
37731
36653
  return false;
37732
36654
  };
@@ -37740,84 +36662,50 @@ const getSetStateFieldValue = (setStateCall, fieldName) => {
37740
36662
  }
37741
36663
  return null;
37742
36664
  };
37743
- const isConvergentPostMountGuard = (test, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch) => {
37744
- const expression = stripParenExpression(test);
37745
- if (isNodeOfType(expression, "LogicalExpression")) {
37746
- if (expression.operator !== "&&" && expression.operator !== "||") return false;
37747
- const leftIsConvergent = isConvergentPostMountGuard(expression.left, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch);
37748
- const rightIsConvergent = isConvergentPostMountGuard(expression.right, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch);
37749
- return isTruthfulBranch && expression.operator === "||" || !isTruthfulBranch && expression.operator === "&&" ? leftIsConvergent && rightIsConvergent : leftIsConvergent || rightIsConvergent;
37750
- }
37751
- if (!isNodeOfType(expression, "BinaryExpression") || !(isTruthfulBranch ? DIFFERENCE_OPERATORS.has(expression.operator) : EQUALITY_OPERATORS.has(expression.operator) && !DIFFERENCE_OPERATORS.has(expression.operator))) return false;
37752
- const leftFieldName = getThisStateFieldName(expression.left);
37753
- const rightFieldName = getThisStateFieldName(expression.right);
37754
- const fieldName = leftFieldName ?? rightFieldName;
37755
- const comparedValue = leftFieldName ? expression.right : expression.left;
37756
- if (!fieldName) return false;
37757
- const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
37758
- if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return false;
37759
- return isUndefinedIdentifier(comparedValue) || derivesFromPostMountValue(comparedValue, localInitializers, callbackRefFieldNames);
37760
- };
37761
- const containsPositiveStateFieldTest = (test, fieldName) => {
37762
- const unwrappedTest = stripParenExpression(test);
37763
- if (getThisStateFieldName(unwrappedTest) === fieldName) return true;
37764
- return isNodeOfType(unwrappedTest, "LogicalExpression") && unwrappedTest.operator === "&&" && (containsPositiveStateFieldTest(unwrappedTest.left, fieldName) || containsPositiveStateFieldTest(unwrappedTest.right, fieldName));
37765
- };
37766
- const isConvergentUndefinedClearGuard = (test, setStateCall) => {
37767
- if (!isNodeOfType(setStateCall, "CallExpression")) return false;
37768
- const argument = setStateCall.arguments?.[0];
37769
- if (!argument || !isNodeOfType(argument, "ObjectExpression")) return false;
37770
- for (const property of argument.properties ?? []) {
37771
- if (!isNodeOfType(property, "Property") || property.computed === true || !isUndefinedIdentifier(property.value)) continue;
37772
- const fieldName = isNodeOfType(property.key, "Identifier") && property.key.name || isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" && property.key.value || null;
37773
- if (fieldName && containsPositiveStateFieldTest(test, fieldName)) return true;
37774
- }
37775
- return false;
37776
- };
37777
- const isDiffGuardTest = (test, paramNames, derivedNames, isTruthfulBranch) => {
37778
- const expression = stripParenExpression(test);
37779
- if (isNodeOfType(expression, "LogicalExpression")) {
37780
- if (expression.operator !== "&&" && expression.operator !== "||") return false;
37781
- const leftIsDiffGuard = isDiffGuardTest(expression.left, paramNames, derivedNames, isTruthfulBranch);
37782
- const rightIsDiffGuard = isDiffGuardTest(expression.right, paramNames, derivedNames, isTruthfulBranch);
37783
- return isTruthfulBranch && expression.operator === "||" || !isTruthfulBranch && expression.operator === "&&" ? leftIsDiffGuard && rightIsDiffGuard : leftIsDiffGuard || rightIsDiffGuard;
37784
- }
37785
- if (!isNodeOfType(expression, "BinaryExpression") || !(isTruthfulBranch ? DIFFERENCE_OPERATORS.has(expression.operator) : EQUALITY_OPERATORS.has(expression.operator) && !DIFFERENCE_OPERATORS.has(expression.operator))) return false;
37786
- return isStatefulOperand(expression.left, paramNames, derivedNames) && isStatefulOperand(expression.right, paramNames, derivedNames) && (referencesAnyName(expression.left, paramNames) || referencesAnyName(expression.right, paramNames) || referencesAnyName(expression.left, derivedNames) || referencesAnyName(expression.right, derivedNames));
36665
+ const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
36666
+ let qualifies = false;
36667
+ walkAst(test, (node) => {
36668
+ if (qualifies) return false;
36669
+ if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
36670
+ const leftFieldName = getThisStateFieldName(node.left);
36671
+ const rightFieldName = getThisStateFieldName(node.right);
36672
+ const fieldName = leftFieldName ?? rightFieldName;
36673
+ const comparedValue = leftFieldName ? node.right : node.left;
36674
+ if (!fieldName || !leftFieldName && !rightFieldName) return;
36675
+ const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
36676
+ if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
36677
+ if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
36678
+ qualifies = true;
36679
+ return false;
36680
+ });
36681
+ return qualifies;
36682
+ };
36683
+ const isDiffGuardTest = (test, paramNames, derivedNames) => {
36684
+ if (referencesAnyName(test, paramNames)) return true;
36685
+ let qualifies = false;
36686
+ walkAst(test, (node) => {
36687
+ if (qualifies) return false;
36688
+ if (!isNodeOfType(node, "BinaryExpression")) return;
36689
+ if (!EQUALITY_OPERATORS.has(node.operator)) return;
36690
+ if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames) && (referencesAnyName(node.left, derivedNames) || referencesAnyName(node.right, derivedNames))) {
36691
+ qualifies = true;
36692
+ return false;
36693
+ }
36694
+ });
36695
+ return qualifies;
37787
36696
  };
37788
- const isInsideDiffGuard = (setStateCall, scopes) => {
36697
+ const isInsideDiffGuard = (setStateCall) => {
37789
36698
  const lifecycleFunction = findEnclosingLifecycleFunction(setStateCall);
37790
36699
  if (!lifecycleFunction) return false;
37791
36700
  const paramNames = /* @__PURE__ */ new Set();
37792
- const parameters = lifecycleFunction.params ?? [];
37793
- for (const param of parameters) collectPatternNames(param, paramNames);
37794
- const previousSourcePaths = /* @__PURE__ */ new Map();
37795
- const [previousPropsParameter, previousStateParameter] = parameters;
37796
- collectPreviousSourcePaths(previousPropsParameter, "props", [], previousSourcePaths);
37797
- collectPreviousSourcePaths(previousStateParameter, "state", [], previousSourcePaths);
36701
+ for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
37798
36702
  const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
37799
36703
  const localInitializers = collectLocalInitializers(lifecycleFunction);
37800
- const lifecycleWrittenFieldNames = collectLifecycleWrittenFieldNames(lifecycleFunction);
37801
- const callbackRefFieldNames = new Set([...getCallbackRefFieldNames(findEnclosingClass(lifecycleFunction), scopes)].filter((fieldName) => !lifecycleWrittenFieldNames.has(fieldName)));
37802
36704
  let child = setStateCall;
37803
36705
  let ancestor = setStateCall.parent;
37804
36706
  while (ancestor && ancestor !== lifecycleFunction) {
37805
- let guardTest = null;
37806
- let isTruthfulBranch = true;
37807
- if (isNodeOfType(ancestor, "IfStatement")) {
37808
- if (child === ancestor.consequent) guardTest = ancestor.test;
37809
- else if (child === ancestor.alternate) {
37810
- guardTest = ancestor.test;
37811
- isTruthfulBranch = false;
37812
- }
37813
- } else if (isNodeOfType(ancestor, "ConditionalExpression")) {
37814
- if (child === ancestor.consequent) guardTest = ancestor.test;
37815
- else if (child === ancestor.alternate) {
37816
- guardTest = ancestor.test;
37817
- isTruthfulBranch = false;
37818
- }
37819
- } else if (isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right) guardTest = ancestor.left;
37820
- if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames, isTruthfulBranch) || isTruthfulBranch && isHistoricalToCurrentTransitionGuard(guardTest, previousSourcePaths) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch) || isTruthfulBranch && isConvergentUndefinedClearGuard(guardTest, setStateCall))) return true;
36707
+ const guardTest = isNodeOfType(ancestor, "IfStatement") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "ConditionalExpression") && child !== ancestor.test && ancestor.test || isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right && ancestor.left || null;
36708
+ if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers))) return true;
37821
36709
  child = ancestor;
37822
36710
  ancestor = ancestor.parent ?? null;
37823
36711
  }
@@ -37839,7 +36727,7 @@ const noDidUpdateSetState = defineRule({
37839
36727
  if (!isNodeOfType(stripParenExpression(node.callee.object), "ThisExpression")) return;
37840
36728
  if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
37841
36729
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
37842
- if (isInsideDiffGuard(node, context.scopes)) return;
36730
+ if (isInsideDiffGuard(node)) return;
37843
36731
  context.report({
37844
36732
  node: node.callee,
37845
36733
  message: MESSAGE$27
@@ -37909,7 +36797,7 @@ const noDirectMutationState = defineRule({
37909
36797
  const isSetterIdentifier = (name) => SETTER_PATTERN.test(name);
37910
36798
  //#endregion
37911
36799
  //#region src/plugin/rules/state-and-effects/utils/collect-use-state-bindings.ts
37912
- const collectUseStateBindings = (componentBody, scopes) => {
36800
+ const collectUseStateBindings = (componentBody) => {
37913
36801
  const bindings = [];
37914
36802
  if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
37915
36803
  for (const statement of componentBody.body ?? []) {
@@ -37922,12 +36810,7 @@ const collectUseStateBindings = (componentBody, scopes) => {
37922
36810
  const setterElement = elements[1];
37923
36811
  if (!isNodeOfType(valueElement, "Identifier") || !isNodeOfType(setterElement, "Identifier") || !isSetterIdentifier(setterElement.name)) continue;
37924
36812
  if (!isNodeOfType(declarator.init, "CallExpression")) continue;
37925
- if (!(scopes ? isReactApiCall(declarator.init, "useState", scopes, {
37926
- allowGlobalReactNamespace: true,
37927
- allowUnboundBareCalls: true,
37928
- resolveConditionalAliases: true,
37929
- resolveNamedAliases: true
37930
- }) : isHookCall$2(declarator.init, "useState"))) continue;
36813
+ if (!isHookCall$2(declarator.init, "useState")) continue;
37931
36814
  bindings.push({
37932
36815
  valueName: valueElement.name,
37933
36816
  setterName: setterElement.name,
@@ -38654,36 +37537,25 @@ const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSub
38654
37537
  };
38655
37538
  //#endregion
38656
37539
  //#region src/plugin/rules/state-and-effects/no-effect-chain.ts
38657
- const findTopLevelEffectCalls = (componentBody, scopes) => {
37540
+ const findTopLevelEffectCalls = (componentBody) => {
38658
37541
  const effectCalls = [];
38659
37542
  if (!isNodeOfType(componentBody, "BlockStatement")) return effectCalls;
38660
37543
  for (const statement of componentBody.body ?? []) {
38661
37544
  if (!isNodeOfType(statement, "ExpressionStatement")) continue;
38662
37545
  const expression = unwrapDiscardedExpression(statement);
38663
37546
  if (!isNodeOfType(expression, "CallExpression")) continue;
38664
- if (!isReactApiCall(expression, EFFECT_HOOK_NAMES$1, scopes, {
38665
- allowGlobalReactNamespace: true,
38666
- allowUnboundBareCalls: true,
38667
- resolveConditionalAliases: true,
38668
- resolveNamedAliases: true
38669
- })) continue;
37547
+ if (!isHookCall$2(expression, EFFECT_HOOK_NAMES$1)) continue;
38670
37548
  effectCalls.push(expression);
38671
37549
  }
38672
37550
  return effectCalls;
38673
37551
  };
38674
- const collectDependencyStateSymbolIds = (effectNode, stateSymbolIds, scopes) => {
38675
- const dependencyStateSymbolIds = /* @__PURE__ */ new Set();
38676
- if (!isNodeOfType(effectNode, "CallExpression")) return dependencyStateSymbolIds;
37552
+ const collectDepIdentifierNames = (effectNode) => {
37553
+ const depNames = /* @__PURE__ */ new Set();
37554
+ if (!isNodeOfType(effectNode, "CallExpression")) return depNames;
38677
37555
  const depsNode = effectNode.arguments?.[1];
38678
- if (!isNodeOfType(depsNode, "ArrayExpression")) return dependencyStateSymbolIds;
38679
- for (const element of depsNode.elements ?? []) {
38680
- if (!element || isNodeOfType(element, "SpreadElement")) continue;
38681
- const rootIdentifier = getRootIdentifier$1(element);
38682
- if (!isNodeOfType(rootIdentifier, "Identifier")) continue;
38683
- const symbol = resolveConstIdentifierAlias(rootIdentifier, scopes, true);
38684
- if (symbol && stateSymbolIds.has(symbol.id)) dependencyStateSymbolIds.add(symbol.id);
38685
- }
38686
- return dependencyStateSymbolIds;
37556
+ if (!isNodeOfType(depsNode, "ArrayExpression")) return depNames;
37557
+ for (const element of depsNode.elements ?? []) if (isNodeOfType(element, "Identifier")) depNames.add(element.name);
37558
+ return depNames;
38687
37559
  };
38688
37560
  const collectSynchronouslyInvokedFunctions = (effectCallback, scopes) => {
38689
37561
  const analysisFunctions = new Set([effectCallback]);
@@ -38789,13 +37661,12 @@ const readStaticSetterValue = (setterCall, scopes) => {
38789
37661
  if (updater) return readStaticUpdaterReturnValue(updater, scopes);
38790
37662
  return readStaticEffectValue(argument, scopes, null, null);
38791
37663
  };
38792
- const collectStateWritesInEffect = (analysisFunctions, setterSymbolIdToStateName, scopes) => {
37664
+ const collectStateWritesInEffect = (analysisFunctions, setterToStateName, scopes) => {
38793
37665
  const stateWrites = /* @__PURE__ */ new Map();
38794
37666
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
38795
37667
  if (!isNodeOfType(child, "CallExpression")) return;
38796
37668
  if (!isNodeOfType(child.callee, "Identifier")) return;
38797
- const setterSymbol = resolveConstIdentifierAlias(child.callee, scopes, true);
38798
- const stateName = setterSymbol ? setterSymbolIdToStateName.get(setterSymbol.id) : void 0;
37669
+ const stateName = setterToStateName.get(child.callee.name);
38799
37670
  if (!stateName) return;
38800
37671
  const writeInfo = stateWrites.get(stateName) ?? {
38801
37672
  values: /* @__PURE__ */ new Set(),
@@ -38881,12 +37752,11 @@ const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
38881
37752
  "keys",
38882
37753
  "values"
38883
37754
  ]);
38884
- const isFunctionShapedReturn = (returnedValue, setterToStateName, setterSymbolIdToStateName, scopes, isExplicitReturnStatement) => {
37755
+ const isFunctionShapedReturn = (returnedValue, setterToStateName, isExplicitReturnStatement) => {
38885
37756
  if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
38886
37757
  if (isNodeOfType(returnedValue, "CallExpression")) {
38887
37758
  if (isNodeOfType(returnedValue.callee, "Identifier")) {
38888
- const setterSymbol = resolveConstIdentifierAlias(returnedValue.callee, scopes, true);
38889
- if (setterToStateName.has(returnedValue.callee.name) || setterSymbol && setterSymbolIdToStateName.has(setterSymbol.id)) return false;
37759
+ if (setterToStateName.has(returnedValue.callee.name)) return false;
38890
37760
  if (isSetterIdentifier(returnedValue.callee.name)) return true;
38891
37761
  }
38892
37762
  return isCleanupReturn(returnedValue, EMPTY_CLEANUP_NAME_SET, EMPTY_CLEANUP_NAME_SET, { allowOpaqueReturn: isExplicitReturnStatement });
@@ -39053,11 +37923,11 @@ const isExternalSyncNode = (node) => {
39053
37923
  const receiverRootName = getRootIdentifierName(node.callee.object);
39054
37924
  return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
39055
37925
  };
39056
- const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, scopes, allowCommittedDomSync) => {
37926
+ const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName, scopes, allowCommittedDomSync) => {
39057
37927
  if (!isFunctionLike$1(effectCallback)) return false;
39058
37928
  if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
39059
- if (isFunctionShapedReturn(effectCallback.body, setterToStateName, setterSymbolIdToStateName, scopes, false)) return true;
39060
- } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, setterSymbolIdToStateName, scopes, true)) return true;
37929
+ if (isFunctionShapedReturn(effectCallback.body, setterToStateName, false)) return true;
37930
+ } else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, true)) return true;
39061
37931
  let didFindExternalCall = false;
39062
37932
  visitSynchronousFunctionBodies(analysisFunctions, (child) => {
39063
37933
  if (isExternalSyncNode(child) || allowCommittedDomSync && isCommittedDomSyncNode(child, scopes)) didFindExternalCall = true;
@@ -39073,11 +37943,10 @@ const noEffectChain = defineRule({
39073
37943
  create: (context) => {
39074
37944
  const checkComponent = (componentBody) => {
39075
37945
  if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
39076
- const useStateBindings = collectUseStateBindings(componentBody, context.scopes);
37946
+ const useStateBindings = collectUseStateBindings(componentBody);
39077
37947
  if (useStateBindings.length === 0) return;
39078
37948
  const setterToStateName = /* @__PURE__ */ new Map();
39079
37949
  const stateSymbolIds = /* @__PURE__ */ new Map();
39080
- const setterSymbolIdToStateName = /* @__PURE__ */ new Map();
39081
37950
  for (const binding of useStateBindings) {
39082
37951
  setterToStateName.set(binding.setterName, binding.valueName);
39083
37952
  if (!isNodeOfType(binding.declarator.id, "ArrayPattern")) continue;
@@ -39086,27 +37955,21 @@ const noEffectChain = defineRule({
39086
37955
  const stateSymbol = context.scopes.symbolFor(stateIdentifier);
39087
37956
  if (stateSymbol) stateSymbolIds.set(binding.valueName, stateSymbol.id);
39088
37957
  }
39089
- const setterIdentifier = binding.declarator.id.elements[1];
39090
- if (isNodeOfType(setterIdentifier, "Identifier")) {
39091
- const setterSymbol = context.scopes.symbolFor(setterIdentifier);
39092
- if (setterSymbol) setterSymbolIdToStateName.set(setterSymbol.id, binding.valueName);
39093
- }
39094
37958
  }
39095
37959
  const storageSetterNames = collectStorageHookSetterNames(componentBody);
39096
- const stateSymbolIdSet = new Set(stateSymbolIds.values());
39097
37960
  const effectInfos = [];
39098
- for (const effectCall of findTopLevelEffectCalls(componentBody, context.scopes)) {
37961
+ for (const effectCall of findTopLevelEffectCalls(componentBody)) {
39099
37962
  const callback = getEffectCallback(effectCall, context.scopes);
39100
37963
  if (!callback || !isFunctionLike$1(callback) || callback.async) continue;
39101
37964
  const analysisFunctions = collectSynchronouslyInvokedFunctions(callback, context.scopes);
39102
- const stateWrites = collectStateWritesInEffect(analysisFunctions, setterSymbolIdToStateName, context.scopes);
37965
+ const stateWrites = collectStateWritesInEffect(analysisFunctions, setterToStateName, context.scopes);
39103
37966
  const writtenStateNames = new Set(stateWrites.keys());
39104
37967
  effectInfos.push({
39105
37968
  node: effectCall,
39106
- dependencyStateSymbolIds: collectDependencyStateSymbolIds(effectCall, stateSymbolIdSet, context.scopes),
37969
+ depNames: collectDepIdentifierNames(effectCall),
39107
37970
  stateWrites,
39108
37971
  analysisFunctions,
39109
- isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, setterSymbolIdToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
37972
+ isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName, context.scopes, writtenStateNames.size === 0) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
39110
37973
  });
39111
37974
  }
39112
37975
  if (effectInfos.length < 2) return;
@@ -39117,11 +37980,10 @@ const noEffectChain = defineRule({
39117
37980
  for (const readerEffect of effectInfos) {
39118
37981
  if (readerEffect === writerEffect) continue;
39119
37982
  if (readerEffect.isExternalSync) continue;
39120
- if (readerEffect.dependencyStateSymbolIds.size === 0) continue;
37983
+ if (readerEffect.depNames.size === 0) continue;
39121
37984
  let chainedStateName = null;
39122
37985
  for (const [writtenName, writeInfo] of writerEffect.stateWrites) {
39123
- const writtenStateSymbolId = stateSymbolIds.get(writtenName);
39124
- if (writtenStateSymbolId === void 0 || !readerEffect.dependencyStateSymbolIds.has(writtenStateSymbolId)) continue;
37986
+ if (!readerEffect.depNames.has(writtenName)) continue;
39125
37987
  if (!canStateWriteReachReaderWork(writeInfo, readerEffect, stateSymbolIds.get(writtenName) ?? null, context.scopes)) continue;
39126
37988
  chainedStateName = writtenName;
39127
37989
  break;
@@ -40937,17 +39799,6 @@ const DOM_MEASUREMENT_NAMES = new Set([
40937
39799
  "scrollHeight"
40938
39800
  ]);
40939
39801
  const MEASUREMENT_HELPER_CALLEE_PATTERN = /^(?:get|measure|read)\w*(?:Width|Height|Rect|Rects|Size|Bounds|Position)$/;
40940
- const IMPERATIVE_DOM_MUTATION_NAMES = new Set([
40941
- "blur",
40942
- "focus",
40943
- "restoreSelection",
40944
- "scroll",
40945
- "scrollBy",
40946
- "scrollIntoView",
40947
- "scrollTo",
40948
- "setRangeText",
40949
- "setSelectionRange"
40950
- ]);
40951
39802
  const subtreeReadsDomMeasurement = (root) => {
40952
39803
  if (!root) return false;
40953
39804
  let found = false;
@@ -40966,66 +39817,29 @@ const subtreeReadsDomMeasurement = (root) => {
40966
39817
  });
40967
39818
  return found;
40968
39819
  };
40969
- const collectFunctionNamesMatchingBody = (program, matchesBody) => {
39820
+ const collectMeasuringFunctionNames = (program) => {
40970
39821
  const names = /* @__PURE__ */ new Set();
40971
39822
  walkAst(program, (child) => {
40972
39823
  if (isNodeOfType(child, "FunctionDeclaration")) {
40973
- if (child.id && isNodeOfType(child.id, "Identifier") && matchesBody(child.body)) names.add(child.id.name);
39824
+ if (child.id && isNodeOfType(child.id, "Identifier") && subtreeReadsDomMeasurement(child.body)) names.add(child.id.name);
40974
39825
  return;
40975
39826
  }
40976
39827
  if (!isNodeOfType(child, "VariableDeclarator") || !isNodeOfType(child.id, "Identifier")) return;
40977
39828
  let functionValue = child.init;
40978
39829
  if (functionValue && isNodeOfType(functionValue, "CallExpression") && isNodeOfType(functionValue.callee, "Identifier") && /^use[A-Z]/.test(functionValue.callee.name)) functionValue = functionValue.arguments?.[0];
40979
- if (functionValue && isFunctionLike$1(functionValue) && matchesBody(functionValue.body)) names.add(child.id.name);
39830
+ if (functionValue && isFunctionLike$1(functionValue) && subtreeReadsDomMeasurement(functionValue.body)) names.add(child.id.name);
40980
39831
  });
40981
39832
  return names;
40982
39833
  };
40983
- const collectMeasuringFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeReadsDomMeasurement);
40984
- const subtreeMutatesDomImperatively = (root) => {
40985
- if (!root || isFunctionLike$1(root)) return false;
40986
- let found = false;
40987
- walkAst(root, (child) => {
40988
- if (found) return false;
40989
- if (child !== root && isFunctionLike$1(child)) return false;
40990
- if (!isNodeOfType(child, "CallExpression")) return;
40991
- const callee = stripParenExpression(child.callee);
40992
- const propertyName = isNodeOfType(callee, "MemberExpression") ? getStaticPropertyName(callee) : null;
40993
- if (propertyName !== null && IMPERATIVE_DOM_MUTATION_NAMES.has(propertyName)) {
40994
- found = true;
40995
- return false;
40996
- }
40997
- });
40998
- return found;
40999
- };
41000
- const collectImperativeDomFunctionNames = (program) => collectFunctionNamesMatchingBody(program, subtreeMutatesDomImperatively);
41001
- const callsAnyName = (root, names, shouldSkipNestedFunctions = false) => {
41002
- if (!root || names.size === 0 || shouldSkipNestedFunctions && isFunctionLike$1(root)) return false;
39834
+ const callsAnyName = (root, names) => {
39835
+ if (!root || names.size === 0) return false;
41003
39836
  let found = false;
41004
39837
  walkAst(root, (child) => {
41005
39838
  if (found) return false;
41006
- if (shouldSkipNestedFunctions && child !== root && isFunctionLike$1(child)) return false;
41007
39839
  if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && names.has(child.callee.name)) found = true;
41008
39840
  });
41009
39841
  return found;
41010
39842
  };
41011
- const isFollowedByImperativeDomMutation = (call, imperativeDomFunctionNames) => {
41012
- let statement = call;
41013
- let parent = statement.parent;
41014
- while (parent) {
41015
- const statements = isNodeOfType(parent, "BlockStatement") || isNodeOfType(parent, "Program") || isNodeOfType(parent, "StaticBlock") ? parent.body : isNodeOfType(parent, "SwitchCase") ? parent.consequent : null;
41016
- if (statements) {
41017
- const statementIndex = statements.findIndex((siblingStatement) => siblingStatement === statement);
41018
- if (statementIndex >= 0) {
41019
- const nextStatement = statements[statementIndex + 1];
41020
- return subtreeMutatesDomImperatively(nextStatement) || callsAnyName(nextStatement, imperativeDomFunctionNames, true);
41021
- }
41022
- }
41023
- if (isFunctionLike$1(parent) || parent.type.endsWith("Statement") && !isNodeOfType(parent, "ExpressionStatement")) return false;
41024
- statement = parent;
41025
- parent = parent.parent;
41026
- }
41027
- return false;
41028
- };
41029
39843
  const isInsideStartViewTransition = (node) => {
41030
39844
  let cursor = node.parent;
41031
39845
  while (cursor) {
@@ -41066,12 +39880,11 @@ const importsImperativeDomLibrary = (program) => {
41066
39880
  };
41067
39881
  const hasExemptFlushSyncCall = (program, localName) => {
41068
39882
  const measuringFunctionNames = collectMeasuringFunctionNames(program);
41069
- const imperativeDomFunctionNames = collectImperativeDomFunctionNames(program);
41070
39883
  let exempt = false;
41071
39884
  walkAst(program, (child) => {
41072
39885
  if (exempt) return false;
41073
39886
  if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "Identifier") || child.callee.name !== localName) return;
41074
- if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames) || isFollowedByImperativeDomMutation(child, imperativeDomFunctionNames)) {
39887
+ if (isInsideStartViewTransition(child) || enclosingFunctionChainReadsMeasurement(child, measuringFunctionNames)) {
41075
39888
  exempt = true;
41076
39889
  return false;
41077
39890
  }
@@ -41636,223 +40449,41 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
41636
40449
  if (leftResult === false && rightResult === false) return false;
41637
40450
  return null;
41638
40451
  };
41639
- const readHydrationConditionResult = (expression, context, runtime, state) => {
40452
+ const readHydrationConditionResult = (expression, context, runtime) => {
41640
40453
  const unwrappedExpression = stripParenExpression(expression);
41641
40454
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
41642
40455
  if (predicateMatch) return predicateMatch[`${runtime}Result`];
41643
40456
  const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
41644
40457
  if (staticResult !== null) return staticResult;
41645
- const expressionSymbol = isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression) : null;
41646
- const parameterValue = expressionSymbol ? state.parameterValuesBySymbolId.get(expressionSymbol.id) : null;
41647
- if (expressionSymbol && parameterValue && !state.visitedSymbolIds.has(expressionSymbol.id)) {
41648
- state.visitedSymbolIds.add(expressionSymbol.id);
41649
- const result = readHydrationConditionResult(parameterValue, context, runtime, state);
41650
- state.visitedSymbolIds.delete(expressionSymbol.id);
41651
- return result;
41652
- }
41653
- if (expressionSymbol && expressionSymbol.kind === "const" && expressionSymbol.initializer && expressionSymbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(expressionSymbol.id)) {
41654
- state.visitedSymbolIds.add(expressionSymbol.id);
41655
- const result = readHydrationConditionResult(expressionSymbol.initializer, context, runtime, state);
41656
- state.visitedSymbolIds.delete(expressionSymbol.id);
41657
- return result;
41658
- }
41659
- if (isNodeOfType(unwrappedExpression, "CallExpression")) {
41660
- const callArguments = unwrappedExpression.arguments ?? [];
41661
- if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
41662
- allowGlobalReactNamespace: true,
41663
- resolveNamedAliases: true
41664
- })) {
41665
- const callbackArgument = callArguments[0];
41666
- if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
41667
- const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
41668
- return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? readHydrationFunctionResult(callbackFunction, context, runtime, state) : null;
41669
- }
41670
- const callee = stripParenExpression(unwrappedExpression.callee);
41671
- 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);
41672
- const helperFunction = resolveExactLocalFunction(callee, context.scopes);
41673
- 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;
41674
- const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
41675
- for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
41676
- const parameter = helperFunction.params[parameterIndex];
41677
- const argument = callArguments[parameterIndex];
41678
- if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
41679
- const parameterSymbol = context.scopes.symbolFor(parameter);
41680
- if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
41681
- }
41682
- return readHydrationFunctionResult(helperFunction, context, runtime, {
41683
- ...state,
41684
- parameterValuesBySymbolId
41685
- });
41686
- }
41687
40458
  if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
41688
- const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
40459
+ const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
41689
40460
  return argumentResult === null ? null : !argumentResult;
41690
40461
  }
41691
40462
  if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
41692
- return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime, state), readHydrationConditionResult(unwrappedExpression.right, context, runtime, state));
41693
- };
41694
- const readHydrationStatementResult = (statement, context, runtime, state) => {
41695
- if (isNodeOfType(statement, "ReturnStatement")) return {
41696
- didReturn: true,
41697
- value: statement.argument ? readHydrationConditionResult(statement.argument, context, runtime, state) : null
41698
- };
41699
- if (isNodeOfType(statement, "BlockStatement")) {
41700
- for (const childStatement of statement.body) {
41701
- const result = readHydrationStatementResult(childStatement, context, runtime, state);
41702
- if (result.didReturn) return result;
41703
- if (statementAlwaysExits(childStatement)) break;
41704
- }
41705
- return {
41706
- didReturn: false,
41707
- value: null
41708
- };
41709
- }
41710
- if (!isNodeOfType(statement, "IfStatement")) return {
41711
- didReturn: false,
41712
- value: null
41713
- };
41714
- const conditionResult = readHydrationConditionResult(statement.test, context, runtime, state);
41715
- if (conditionResult !== null) {
41716
- const selectedBranch = conditionResult ? statement.consequent : statement.alternate;
41717
- return selectedBranch ? readHydrationStatementResult(selectedBranch, context, runtime, state) : {
41718
- didReturn: false,
41719
- value: null
41720
- };
41721
- }
41722
- const consequentResult = readHydrationStatementResult(statement.consequent, context, runtime, state);
41723
- const alternateResult = statement.alternate ? readHydrationStatementResult(statement.alternate, context, runtime, state) : {
41724
- didReturn: false,
41725
- value: null
41726
- };
41727
- return consequentResult.didReturn && alternateResult.didReturn && consequentResult.value !== null && consequentResult.value === alternateResult.value ? consequentResult : {
41728
- didReturn: consequentResult.didReturn || alternateResult.didReturn,
41729
- value: null
41730
- };
41731
- };
41732
- const readHydrationFunctionResult = (functionNode, context, runtime, state) => {
41733
- if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
41734
- state.visitedFunctionNodes.add(functionNode);
41735
- const result = isNodeOfType(functionNode.body, "BlockStatement") ? readHydrationStatementResult(functionNode.body, context, runtime, state).value : readHydrationConditionResult(functionNode.body, context, runtime, state);
41736
- state.visitedFunctionNodes.delete(functionNode);
41737
- return result;
40463
+ return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
41738
40464
  };
41739
- const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, scopes) => {
41740
- const left = stripParenExpression(leftExpression);
41741
- const right = stripParenExpression(rightExpression);
41742
- if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
41743
- const leftSymbol = scopes.symbolFor(left);
41744
- const rightSymbol = scopes.symbolFor(right);
41745
- return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
41746
- }
41747
- if (isNodeOfType(left, "MemberExpression") && isNodeOfType(right, "MemberExpression")) return doEquivalentExpressionBindingsMatch(left.object, right.object, scopes) && (!left.computed || doEquivalentExpressionBindingsMatch(left.property, right.property, scopes));
41748
- if (isNodeOfType(left, "CallExpression") && isNodeOfType(right, "CallExpression")) {
41749
- const rightArguments = right.arguments ?? [];
41750
- return doEquivalentExpressionBindingsMatch(left.callee, right.callee, scopes) && (left.arguments ?? []).every((argument, index) => {
41751
- const rightArgument = rightArguments[index];
41752
- return Boolean(rightArgument && doEquivalentExpressionBindingsMatch(argument, rightArgument, scopes));
41753
- });
41754
- }
41755
- return true;
41756
- };
41757
- const areHelperReturnValuesEquivalent = (leftValue, rightValue, context) => {
41758
- if (areExpressionsStructurallyEqual(leftValue, rightValue)) return doEquivalentExpressionBindingsMatch(leftValue, rightValue, context.scopes);
41759
- const leftBoolean = readInitialStateBoolean(leftValue, context.scopes);
41760
- const rightBoolean = readInitialStateBoolean(rightValue, context.scopes);
41761
- return leftBoolean !== null && rightBoolean !== null && leftBoolean === rightBoolean;
41762
- };
41763
- const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
41764
- const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
41765
- return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
41766
- };
41767
- const matchHydrationConditionInternal = (expression, context, state) => {
40465
+ const matchHydrationCondition = (expression, context) => {
41768
40466
  const unwrappedExpression = stripParenExpression(expression);
41769
40467
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
41770
40468
  if (predicateMatch) return {
41771
40469
  predicateMatch,
41772
40470
  predicateNode: unwrappedExpression
41773
40471
  };
41774
- if (isNodeOfType(unwrappedExpression, "Identifier")) {
41775
- const symbol = context.scopes.symbolFor(unwrappedExpression);
41776
- const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
41777
- if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
41778
- state.visitedSymbolIds.add(symbol.id);
41779
- const match = matchHydrationConditionInternal(parameterValue, context, state);
41780
- state.visitedSymbolIds.delete(symbol.id);
41781
- return match;
41782
- }
41783
- if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
41784
- state.visitedSymbolIds.add(symbol.id);
41785
- const match = matchHydrationConditionInternal(symbol.initializer, context, state);
41786
- state.visitedSymbolIds.delete(symbol.id);
41787
- return match;
41788
- }
41789
- if (isNodeOfType(unwrappedExpression, "CallExpression")) {
41790
- const callArguments = unwrappedExpression.arguments ?? [];
41791
- if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
41792
- allowGlobalReactNamespace: true,
41793
- resolveNamedAliases: true
41794
- })) {
41795
- const callbackArgument = callArguments[0];
41796
- if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
41797
- const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
41798
- return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionResult(callbackFunction, context, state) : null;
41799
- }
41800
- const callee = stripParenExpression(unwrappedExpression.callee);
41801
- if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return matchHydrationConditionInternal(callArguments[0], context, state);
41802
- const helperFunction = resolveExactLocalFunction(callee, context.scopes);
41803
- 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;
41804
- const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
41805
- for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
41806
- const parameter = helperFunction.params[parameterIndex];
41807
- const argument = callArguments[parameterIndex];
41808
- if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
41809
- const parameterSymbol = context.scopes.symbolFor(parameter);
41810
- if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
41811
- }
41812
- return matchHydrationFunctionResult(helperFunction, context, {
41813
- ...state,
41814
- parameterValuesBySymbolId
41815
- });
41816
- }
41817
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
40472
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
41818
40473
  if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
41819
- const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
41820
- const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
40474
+ const leftMatch = matchHydrationCondition(unwrappedExpression.left, context);
40475
+ const rightMatch = matchHydrationCondition(unwrappedExpression.right, context);
40476
+ if (leftMatch && rightMatch) {
40477
+ const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client");
40478
+ const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server");
40479
+ return clientResult !== null && serverResult !== null && clientResult !== serverResult ? leftMatch : null;
40480
+ }
41821
40481
  const nestedMatch = leftMatch ?? rightMatch;
41822
40482
  if (!nestedMatch) return null;
41823
- const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client", state);
41824
- const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server", state);
41825
- return clientResult !== null && serverResult !== null && clientResult === serverResult ? null : nestedMatch;
40483
+ const otherResult = readInitialStateBoolean(leftMatch ? unwrappedExpression.right : unwrappedExpression.left, context.scopes);
40484
+ if (unwrappedExpression.operator === "&&" && otherResult === false || unwrappedExpression.operator === "||" && otherResult === true) return null;
40485
+ return nestedMatch;
41826
40486
  };
41827
- const matchHydrationReturningStatement = (statement, context, state) => {
41828
- if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? matchHydrationConditionInternal(statement.argument, context, state) : null;
41829
- if (isNodeOfType(statement, "IfStatement")) {
41830
- const conditionMatch = matchHydrationConditionInternal(statement.test, context, state);
41831
- const consequentValues = getReturnedValues(statement.consequent);
41832
- const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
41833
- if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
41834
- return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
41835
- }
41836
- if (!isNodeOfType(statement, "BlockStatement")) return null;
41837
- for (const childStatement of statement.body) {
41838
- const match = matchHydrationReturningStatement(childStatement, context, state);
41839
- if (match) return match;
41840
- if (statementAlwaysExits(childStatement)) break;
41841
- }
41842
- return null;
41843
- };
41844
- const matchHydrationFunctionResult = (functionNode, context, state) => {
41845
- if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
41846
- state.visitedFunctionNodes.add(functionNode);
41847
- const match = isNodeOfType(functionNode.body, "BlockStatement") ? matchHydrationReturningStatement(functionNode.body, context, state) : matchHydrationConditionInternal(functionNode.body, context, state);
41848
- state.visitedFunctionNodes.delete(functionNode);
41849
- return match;
41850
- };
41851
- const matchHydrationCondition = (expression, context) => matchHydrationConditionInternal(expression, context, {
41852
- parameterValuesBySymbolId: /* @__PURE__ */ new Map(),
41853
- visitedFunctionNodes: /* @__PURE__ */ new Set(),
41854
- visitedSymbolIds: /* @__PURE__ */ new Set()
41855
- });
41856
40487
  const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
41857
40488
  const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
41858
40489
  if (!leftNode || !rightNode) return leftNode === rightNode;
@@ -41995,17 +40626,17 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
41995
40626
  const { predicateMatch, predicateNode } = conditionMatch;
41996
40627
  if (reportedNodes.has(predicateNode)) return;
41997
40628
  if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
41998
- const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes);
40629
+ const componentOrHookNode = findRenderPhaseComponentOrHook(predicateNode, context.scopes);
41999
40630
  if (!componentOrHookNode) return;
42000
40631
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
42001
- if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes)) return;
40632
+ if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
42002
40633
  if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
42003
- const attribute = findEnclosingJsxAttribute(conditionNode);
40634
+ const attribute = findEnclosingJsxAttribute(predicateNode);
42004
40635
  if (!attribute || isEventHandlerAttribute(attribute)) return;
42005
40636
  }
42006
- if (fileIsEmailTemplate || isGatedByFalsyInitialState(conditionNode, context.scopes)) return;
42007
- if (isAfterClientOnlyEarlyReturn(conditionNode, componentOrHookNode, context.scopes)) return;
42008
- const openingElement = findEnclosingJsxOpeningElement(conditionNode);
40637
+ if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
40638
+ if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
40639
+ const openingElement = findEnclosingJsxOpeningElement(predicateNode);
42009
40640
  if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
42010
40641
  if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
42011
40642
  if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
@@ -42387,7 +41018,7 @@ const noInitializeState = defineRule({
42387
41018
  if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
42388
41019
  const analysis = getProgramAnalysis(node);
42389
41020
  if (!analysis) return;
42390
- for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
41021
+ for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
42391
41022
  if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
42392
41023
  const stateName = getStateName(fact.stateDeclarator);
42393
41024
  context.report({
@@ -42745,8 +41376,7 @@ const noJsxElementType = defineRule({
42745
41376
  create: (context) => {
42746
41377
  let isJsxImported = false;
42747
41378
  const flaggedAnnotations = [];
42748
- const collectComponentReturnType = (functionNode, returnType) => {
42749
- if (!(isNodeOfType(functionNode, "TSDeclareFunction") ? Boolean(functionNode.id && isReactComponentName(functionNode.id.name)) : isComponentFunction$1(functionNode))) return;
41379
+ const checkReturnType = (returnType) => {
42750
41380
  const typeAnnotation = extractReturnTypeAnnotation(returnType);
42751
41381
  if (!typeAnnotation) return;
42752
41382
  if (isJsxElementTypeReference(typeAnnotation)) flaggedAnnotations.push(typeAnnotation);
@@ -42756,16 +41386,19 @@ const noJsxElementType = defineRule({
42756
41386
  if (isJsxImportBinding(node)) isJsxImported = true;
42757
41387
  },
42758
41388
  FunctionDeclaration(node) {
42759
- collectComponentReturnType(node, node.returnType);
41389
+ checkReturnType(node.returnType);
42760
41390
  },
42761
41391
  ArrowFunctionExpression(node) {
42762
- collectComponentReturnType(node, node.returnType);
41392
+ checkReturnType(node.returnType);
42763
41393
  },
42764
41394
  FunctionExpression(node) {
42765
- collectComponentReturnType(node, node.returnType);
41395
+ checkReturnType(node.returnType);
42766
41396
  },
42767
41397
  TSDeclareFunction(node) {
42768
- collectComponentReturnType(node, node.returnType);
41398
+ checkReturnType(node.returnType);
41399
+ },
41400
+ TSMethodSignature(node) {
41401
+ checkReturnType(node.returnType);
42769
41402
  },
42770
41403
  "Program:exit"() {
42771
41404
  if (isJsxImported) return;
@@ -45792,114 +44425,6 @@ const DATA_SINK_METHOD_NAMES = new Set([
45792
44425
  "deserialize"
45793
44426
  ]);
45794
44427
  //#endregion
45795
- //#region src/plugin/utils/get-transparent-react-callback-wrapper-argument.ts
45796
- const getTransparentReactCallbackWrapperArgument = (initializer, resultSymbol, scopes) => {
45797
- const callExpression = stripParenExpression(initializer);
45798
- if (!isNodeOfType(callExpression, "CallExpression")) return null;
45799
- const callbackArgument = callExpression.arguments[0];
45800
- if (!callbackArgument) return null;
45801
- if (resultSymbol && symbolHasReactUseEffectEventOrigin(resultSymbol, scopes)) return callbackArgument;
45802
- return isReactApiCall(callExpression, "useCallback", scopes, {
45803
- allowGlobalReactNamespace: true,
45804
- allowUnboundBareCalls: true
45805
- }) ? callbackArgument : null;
45806
- };
45807
- //#endregion
45808
- //#region src/plugin/rules/state-and-effects/utils/resolve-parent-callback-provenance.ts
45809
- const getDeclarationKind$1 = (declarator) => {
45810
- const declaration = declarator.parent;
45811
- return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
45812
- };
45813
- const hasMutableBindingWrite$2 = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
45814
- const mergeRequiredBranches = (leftNames, rightNames) => {
45815
- if (!leftNames || !rightNames) return null;
45816
- return new Set([...leftNames, ...rightNames]);
45817
- };
45818
- const getPropReferenceName = (analysis, identifier) => {
45819
- if (!isNodeOfType(identifier, "Identifier")) return null;
45820
- const reference = getRef(analysis, identifier);
45821
- if (!reference || !isProp(analysis, reference) || isWholePropsObjectReference(analysis, reference)) return null;
45822
- const bindingIdentifier = (reference.resolved?.defs.find((definition) => definition.type === "Parameter"))?.name;
45823
- return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? identifier.name;
45824
- };
45825
- const getSingleConstDeclarator = (reference) => {
45826
- if (!reference.resolved || hasMutableBindingWrite$2(reference)) return null;
45827
- const declarators = reference.resolved.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
45828
- if (declarators.length !== 1) return null;
45829
- const declarator = declarators[0];
45830
- if (!declarator || getDeclarationKind$1(declarator) !== "const") return null;
45831
- return declarator;
45832
- };
45833
- const resolveParentCallbackPropNames = (analysis, expression, scopes, visitedReferences, allowFunctionForwarder = false) => {
45834
- const unwrappedExpression = stripParenExpression(expression);
45835
- if (isFunctionLike$1(unwrappedExpression)) {
45836
- if (!allowFunctionForwarder || Boolean(unwrappedExpression.async)) return null;
45837
- const callbackNames = /* @__PURE__ */ new Set();
45838
- walkInsideStatementBlocks(unwrappedExpression.body, (child) => {
45839
- if (!isNodeOfType(child, "CallExpression")) return;
45840
- const resolvedNames = resolveParentCallbackPropNames(analysis, child.callee, scopes, new Set(visitedReferences), false);
45841
- if (!resolvedNames) return;
45842
- for (const resolvedName of resolvedNames) callbackNames.add(resolvedName);
45843
- });
45844
- return callbackNames.size > 0 ? callbackNames : null;
45845
- }
45846
- if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return mergeRequiredBranches(resolveParentCallbackPropNames(analysis, unwrappedExpression.consequent, scopes, new Set(visitedReferences), false), resolveParentCallbackPropNames(analysis, unwrappedExpression.alternate, scopes, new Set(visitedReferences), false));
45847
- if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return mergeRequiredBranches(resolveParentCallbackPropNames(analysis, unwrappedExpression.left, scopes, new Set(visitedReferences), false), resolveParentCallbackPropNames(analysis, unwrappedExpression.right, scopes, new Set(visitedReferences)));
45848
- if (isNodeOfType(unwrappedExpression, "Identifier")) {
45849
- const propName = getPropReferenceName(analysis, unwrappedExpression);
45850
- if (propName) return new Set([propName]);
45851
- const reference = getRef(analysis, unwrappedExpression);
45852
- if (!reference?.resolved || visitedReferences.has(reference.resolved)) return null;
45853
- const declarator = getSingleConstDeclarator(reference);
45854
- if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return null;
45855
- visitedReferences.add(reference.resolved);
45856
- const wrappedArgument = getTransparentReactCallbackWrapperArgument(declarator.init, scopes.symbolFor(unwrappedExpression), scopes);
45857
- const allowsFunctionForwarder = Boolean(wrappedArgument && !isReactApiCall(declarator.init, "useCallback", scopes, {
45858
- allowGlobalReactNamespace: true,
45859
- allowUnboundBareCalls: true
45860
- }));
45861
- return resolveParentCallbackPropNames(analysis, wrappedArgument ?? declarator.init, scopes, visitedReferences, allowsFunctionForwarder);
45862
- }
45863
- if (!isNodeOfType(unwrappedExpression, "MemberExpression")) return null;
45864
- const propertyName = getStaticMemberPropertyName(unwrappedExpression);
45865
- if (!propertyName) return null;
45866
- const receiver = stripParenExpression(unwrappedExpression.object);
45867
- if (!isNodeOfType(receiver, "Identifier")) return null;
45868
- const receiverReference = getRef(analysis, receiver);
45869
- if (!receiverReference?.resolved || visitedReferences.has(receiverReference.resolved)) return null;
45870
- if (isWholePropsObjectReference(analysis, receiverReference)) return new Set([propertyName]);
45871
- const declarator = getSingleConstDeclarator(receiverReference);
45872
- if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return null;
45873
- visitedReferences.add(receiverReference.resolved);
45874
- const initializer = stripParenExpression(declarator.init);
45875
- if (propertyName === "current" && isNodeOfType(initializer, "CallExpression")) {
45876
- if (!isReactApiCall(initializer, "useRef", scopes, {
45877
- allowGlobalReactNamespace: true,
45878
- allowUnboundBareCalls: true
45879
- })) return null;
45880
- const callbackArgument = initializer.arguments[0];
45881
- if (!callbackArgument) return null;
45882
- let callbackNames = resolveParentCallbackPropNames(analysis, callbackArgument, scopes, new Set(visitedReferences), false);
45883
- if (!callbackNames) return null;
45884
- for (const candidateReference of receiverReference.resolved.references) {
45885
- const candidateIdentifier = candidateReference.identifier;
45886
- const candidateMember = candidateIdentifier.parent;
45887
- if (!candidateMember || !isNodeOfType(candidateMember, "MemberExpression") || candidateMember.object !== candidateIdentifier || getStaticMemberPropertyName(candidateMember) !== "current") continue;
45888
- const assignment = candidateMember.parent;
45889
- if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.left !== candidateMember) continue;
45890
- if (assignment.operator !== "=") return null;
45891
- callbackNames = mergeRequiredBranches(callbackNames, resolveParentCallbackPropNames(analysis, assignment.right, scopes, new Set(visitedReferences), false));
45892
- if (!callbackNames) return null;
45893
- }
45894
- return callbackNames;
45895
- }
45896
- if (!isNodeOfType(initializer, "ObjectExpression")) return null;
45897
- const property = initializer.properties.find((candidateProperty) => isNodeOfType(candidateProperty, "Property") && getStaticPropertyKeyName(candidateProperty, { allowComputedString: true }) === propertyName);
45898
- if (!property || !isNodeOfType(property, "Property")) return null;
45899
- return resolveParentCallbackPropNames(analysis, property.value, scopes, visitedReferences, false);
45900
- };
45901
- const getParentCallbackPropNames = ({ analysis, expression, scopes }) => resolveParentCallbackPropNames(analysis, expression, scopes, /* @__PURE__ */ new Set(), false);
45902
- //#endregion
45903
44428
  //#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
45904
44429
  const isUseStateIdentifier = (identifier) => {
45905
44430
  if (!isNodeOfType(identifier, "Identifier")) return false;
@@ -45928,18 +44453,14 @@ const FUNCTION_WRAPPER_HOOK_NAMES$1 = new Set([
45928
44453
  "useStableCallback",
45929
44454
  "useCallbackRef"
45930
44455
  ]);
45931
- const getWrapperHookWrappedFunction = (initializer, resultSymbol, scopes) => {
44456
+ const getWrapperHookWrappedFunction = (initializer) => {
45932
44457
  if (!isNodeOfType(initializer, "CallExpression")) return null;
45933
- const transparentReactArgument = getTransparentReactCallbackWrapperArgument(initializer, resultSymbol, scopes);
45934
- if (transparentReactArgument) return transparentReactArgument;
45935
44458
  const callee = initializer.callee;
45936
44459
  const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") ? callee.property.name : null;
45937
44460
  if (!calleeName || !FUNCTION_WRAPPER_HOOK_NAMES$1.has(calleeName)) return null;
45938
44461
  const wrapped = initializer.arguments?.[0];
45939
- if (!wrapped) return null;
45940
- if (calleeName === "useEffectEvent") return null;
45941
- if (isFunctionLike$1(wrapped)) return wrapped;
45942
- return null;
44462
+ if (!wrapped || !isFunctionLike$1(wrapped)) return null;
44463
+ return wrapped;
45943
44464
  };
45944
44465
  const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
45945
44466
  const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
@@ -45949,29 +44470,16 @@ const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstre
45949
44470
  const innerParent = innerIdentifier.parent;
45950
44471
  return Boolean(innerParent && isNodeOfType(innerParent, "CallExpression") && innerParent.callee === innerIdentifier);
45951
44472
  });
45952
- const isDirectParentCallbackRef = (analysis, ref, scopes) => {
44473
+ const isDirectParentCallbackRef = (analysis, ref) => {
45953
44474
  if (isProp(analysis, ref)) return true;
45954
- if (hasMutableBindingWrite$1(ref)) {
45955
- if (!(ref.resolved?.references.filter((candidateReference) => candidateReference.isWrite() && !candidateReference.init) ?? []).every((candidateReference) => {
45956
- const candidateIdentifier = candidateReference.identifier;
45957
- const assignment = candidateIdentifier.parent;
45958
- if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== candidateIdentifier) return false;
45959
- const assignedReferences = getDownstreamRefs(analysis, assignment.right);
45960
- return assignedReferences.length > 0 && assignedReferences.every((assignedReference) => isProp(analysis, assignedReference));
45961
- })) return false;
45962
- }
45963
44475
  return Boolean(ref.resolved?.defs.some((def) => {
45964
44476
  const node = def.node;
45965
44477
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
45966
44478
  const initializer = unwrapChainExpression(node.init);
45967
- const wrappedFunction = getWrapperHookWrappedFunction(initializer, isNodeOfType(node.id, "Identifier") ? scopes.symbolFor(node.id) ?? null : null, scopes);
44479
+ const wrappedFunction = getWrapperHookWrappedFunction(initializer);
45968
44480
  if (wrappedFunction) {
45969
44481
  if (wrappedFunction.async) return false;
45970
- if (isFunctionLike$1(wrappedFunction)) return wrappedFunctionNotifiesParent(analysis, wrappedFunction);
45971
- const directName = getParentCallbackPropName(analysis, wrappedFunction);
45972
- const downstreamReferences = getDownstreamRefs(analysis, wrappedFunction);
45973
- if (directName !== null) return true;
45974
- return downstreamReferences.some((wrappedReference) => !hasMutableBindingWrite$1(wrappedReference) && getUpstreamRefs(analysis, wrappedReference).some((upstreamReference) => isProp(analysis, upstreamReference)));
44482
+ return wrappedFunctionNotifiesParent(analysis, wrappedFunction);
45975
44483
  }
45976
44484
  if (!isNodeOfType(initializer, "Identifier") && !isNodeOfType(initializer, "MemberExpression")) return false;
45977
44485
  return getDownstreamRefs(analysis, initializer).some((initializerRef) => getUpstreamRefs(analysis, initializerRef).some((upstreamRef) => isProp(analysis, upstreamRef)));
@@ -45981,7 +44489,7 @@ const getDeclarationKind = (declarator) => {
45981
44489
  const declaration = declarator.parent;
45982
44490
  return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
45983
44491
  };
45984
- const hasMutableBindingWrite$1 = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
44492
+ const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
45985
44493
  const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
45986
44494
  const unwrappedExpression = stripParenExpression(expression);
45987
44495
  if (isNodeOfType(unwrappedExpression, "Identifier")) {
@@ -45993,7 +44501,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
45993
44501
  const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
45994
44502
  return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
45995
44503
  }
45996
- if (hasMutableBindingWrite$1(callbackReference)) return null;
44504
+ if (hasMutableBindingWrite(callbackReference)) return null;
45997
44505
  const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
45998
44506
  if (definitions.length !== 1) return null;
45999
44507
  const declarator = definitions[0];
@@ -46059,7 +44567,7 @@ const getRefAliasDeclarator = (identifier) => {
46059
44567
  const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
46060
44568
  if (!isNodeOfType(receiver, "Identifier")) return null;
46061
44569
  const receiverReference = getRef(analysis, receiver);
46062
- if (!receiverReference?.resolved || hasMutableBindingWrite$1(receiverReference)) return null;
44570
+ if (!receiverReference?.resolved || hasMutableBindingWrite(receiverReference)) return null;
46063
44571
  const variables = /* @__PURE__ */ new Set();
46064
44572
  let currentVariable = receiverReference.resolved;
46065
44573
  let refCall = null;
@@ -46075,7 +44583,7 @@ const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
46075
44583
  }
46076
44584
  if (getDeclarationKind(declarator) !== "const" || !isNodeOfType(stripParenExpression(declarator.init), "Identifier")) return null;
46077
44585
  const upstreamReference = getRef(analysis, stripParenExpression(declarator.init));
46078
- if (!upstreamReference?.resolved || hasMutableBindingWrite$1(upstreamReference)) return null;
44586
+ if (!upstreamReference?.resolved || hasMutableBindingWrite(upstreamReference)) return null;
46079
44587
  currentVariable = upstreamReference.resolved;
46080
44588
  }
46081
44589
  if (!refCall) return null;
@@ -46150,7 +44658,7 @@ const isParentPropsContextMerge = (analysis, expression) => {
46150
44658
  while (isNodeOfType(currentExpression, "Identifier")) {
46151
44659
  const currentReference = getRef(analysis, currentExpression);
46152
44660
  const currentVariable = currentReference?.resolved;
46153
- if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite$1(currentReference)) return false;
44661
+ if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return false;
46154
44662
  visitedVariables.add(currentVariable);
46155
44663
  const definitions = currentVariable.defs.filter((definition) => isNodeOfType(definition.node, "VariableDeclarator"));
46156
44664
  if (definitions.length !== 1) return false;
@@ -46164,11 +44672,11 @@ const isParentPropsContextMerge = (analysis, expression) => {
46164
44672
  const propsExpression = stripParenExpression(propsSpread.argument);
46165
44673
  if (!isNodeOfType(propsExpression, "Identifier")) return false;
46166
44674
  const propsReference = getRef(analysis, propsExpression);
46167
- if (!propsReference?.resolved || !isWholePropsObjectReference(analysis, propsReference) || hasMutableBindingWrite$1(propsReference) || propsReference.resolved.references.some((candidateReference) => candidateReference !== propsReference)) return false;
44675
+ if (!propsReference?.resolved || !isWholePropsObjectReference(analysis, propsReference) || hasMutableBindingWrite(propsReference) || propsReference.resolved.references.some((candidateReference) => candidateReference !== propsReference)) return false;
46168
44676
  const contextExpression = stripParenExpression(contextSpread.argument);
46169
44677
  if (!isNodeOfType(contextExpression, "Identifier")) return false;
46170
44678
  const contextReference = getRef(analysis, contextExpression);
46171
- if (!contextReference?.resolved || hasMutableBindingWrite$1(contextReference) || contextReference.resolved.references.some((candidateReference) => !candidateReference.init && candidateReference !== contextReference)) return false;
44679
+ if (!contextReference?.resolved || hasMutableBindingWrite(contextReference) || contextReference.resolved.references.some((candidateReference) => !candidateReference.init && candidateReference !== contextReference)) return false;
46172
44680
  const contextInitializer = contextReference.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
46173
44681
  if (!contextInitializer || !isNodeOfType(contextInitializer, "VariableDeclarator") || getDeclarationKind(contextInitializer) !== "const" || !contextInitializer.init || !isNodeOfType(contextInitializer.init, "CallExpression")) return false;
46174
44682
  const contextHook = stripParenExpression(contextInitializer.init.callee);
@@ -46182,7 +44690,7 @@ const getImmutableParentCallbackPropName = (analysis, expression) => {
46182
44690
  while (isNodeOfType(currentExpression, "Identifier")) {
46183
44691
  const currentReference = getRef(analysis, currentExpression);
46184
44692
  const currentVariable = currentReference?.resolved;
46185
- if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite$1(currentReference)) return null;
44693
+ if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return null;
46186
44694
  visitedVariables.add(currentVariable);
46187
44695
  const definition = currentVariable.defs.length === 1 ? currentVariable.defs[0] : null;
46188
44696
  const bindingIdentifier = definition?.name;
@@ -46251,7 +44759,7 @@ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) =>
46251
44759
  while (isNodeOfType(currentExpression, "Identifier")) {
46252
44760
  const callbackReference = getRef(analysis, currentExpression);
46253
44761
  const callbackVariable = callbackReference?.resolved;
46254
- if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite$1(callbackReference)) return null;
44762
+ if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite(callbackReference)) return null;
46255
44763
  visitedVariables.add(callbackVariable);
46256
44764
  const definition = callbackVariable.defs.length === 1 ? callbackVariable.defs[0] : null;
46257
44765
  const declarator = definition?.node;
@@ -46271,11 +44779,10 @@ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) =>
46271
44779
  if (!propertyName || !COMMAND_PROP_NAME_PATTERN.test(propertyName)) return null;
46272
44780
  return refCurrentObjectPreservesCallbackProperty(analysis, currentExpression.object, propertyName, isReactUseRefCall) ? propertyName : null;
46273
44781
  };
46274
- const isWrapperHookCallbackRef = (analysis, ref, scopes) => Boolean(ref.resolved?.defs.some((def) => {
44782
+ const isWrapperHookCallbackRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
46275
44783
  const node = def.node;
46276
44784
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
46277
- const resultSymbol = isNodeOfType(node.id, "Identifier") ? scopes.symbolFor(node.id) ?? null : null;
46278
- return getWrapperHookWrappedFunction(unwrapChainExpression(node.init), resultSymbol, scopes) !== null;
44785
+ return getWrapperHookWrappedFunction(unwrapChainExpression(node.init)) !== null;
46279
44786
  }));
46280
44787
  const isHandlerBagArgument = (analysis, argument) => {
46281
44788
  if (!isNodeOfType(argument, "ObjectExpression")) return false;
@@ -46294,26 +44801,14 @@ const isHandlerBagArgument = (analysis, argument) => {
46294
44801
  };
46295
44802
  const getFunctionalUpdaterDataRefs = (analysis, updater) => getDownstreamRefs(analysis, updater).filter((updaterRef) => !updaterRef.resolved?.defs.some((def) => def.type === "Parameter" && def.node === updater));
46296
44803
  const HOOK_NAME_PATTERN$1 = /^use[A-Z0-9]/;
46297
- const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
44804
+ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES = new Set([
46298
44805
  "useIntersectionObserver",
46299
44806
  "useMatchMedia",
46300
- "useMediaJobProgress",
46301
44807
  "useMediaQuery",
46302
- "useMediaQueryState",
46303
44808
  "useResizeObserver",
46304
44809
  "useVisibility",
46305
44810
  "useWindowSize"
46306
44811
  ]);
46307
- const isCallbackPropReference = (analysis, ref) => {
46308
- if (!isProp(analysis, ref)) return false;
46309
- const identifier = ref.identifier;
46310
- if (!isNodeOfType(identifier, "Identifier")) return false;
46311
- if (!isWholePropsObjectReference(analysis, ref)) return HANDLER_NAMED_PROP_PATTERN.test(identifier.name);
46312
- const member = identifier.parent;
46313
- if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier) return false;
46314
- const propertyName = getStaticMemberPropertyName(member);
46315
- return Boolean(propertyName && HANDLER_NAMED_PROP_PATTERN.test(propertyName));
46316
- };
46317
44812
  const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
46318
44813
  const node = def.node;
46319
44814
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
@@ -46321,7 +44816,7 @@ const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs
46321
44816
  if (!isNodeOfType(init, "CallExpression")) return false;
46322
44817
  const callee = init.callee;
46323
44818
  if (!isNodeOfType(callee, "Identifier") || !HOOK_NAME_PATTERN$1.test(callee.name)) return false;
46324
- return (init.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
44819
+ return (init.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isProp(analysis, downstreamRef)));
46325
44820
  }));
46326
44821
  const isParentWiredHookResultArgument = (analysis, argument) => {
46327
44822
  if (!isNodeOfType(argument, "Identifier")) return false;
@@ -46334,40 +44829,19 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
46334
44829
  if (!isNodeOfType(identifier, "Identifier") || !HOOK_NAME_PATTERN$1.test(identifier.name)) return false;
46335
44830
  const parent = identifier.parent;
46336
44831
  if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
46337
- return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
44832
+ return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isProp(analysis, downstreamRef)));
46338
44833
  };
46339
- const getLocalHookExternalStateProof = (analysis, ref) => {
46340
- let hookFunction = resolveToFunction(ref);
46341
- if (!hookFunction) for (const definition of ref.resolved?.defs ?? []) {
46342
- const definitionNode = definition.node;
46343
- if (!isNodeOfType(definitionNode, "VariableDeclarator") || !definitionNode.init) continue;
46344
- const initializer = stripParenExpression(definitionNode.init);
46345
- if (!isNodeOfType(initializer, "CallExpression")) continue;
46346
- const callee = stripParenExpression(initializer.callee);
46347
- if (!isNodeOfType(callee, "Identifier")) continue;
46348
- const calleeReference = getRef(analysis, callee);
46349
- if (!calleeReference) continue;
46350
- hookFunction = resolveToFunction(calleeReference);
46351
- if (hookFunction) break;
46352
- }
46353
- if (!hookFunction) return null;
46354
- const returnedReferences = collectFunctionReturnStatements(hookFunction).flatMap((returnStatement) => returnStatement.argument ? getDownstreamRefs(analysis, returnStatement.argument) : []);
46355
- if (returnedReferences.length === 0) return null;
46356
- return returnedReferences.every((returnedReference) => isState(analysis, returnedReference) && isExternallyDrivenState(analysis, returnedReference));
46357
- };
46358
- const isExternalSubscriptionHookRef = (analysis, ref) => {
44834
+ const isExternalSubscriptionHookRef = (ref) => {
46359
44835
  const identifier = ref.identifier;
46360
44836
  if (!isNodeOfType(identifier, "Identifier")) return false;
46361
- const localHookProof = getLocalHookExternalStateProof(analysis, ref);
46362
- if (localHookProof !== null) return localHookProof;
46363
- if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
44837
+ if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(identifier.name) && isCalleePosition(identifier)) return true;
46364
44838
  return Boolean(ref.resolved?.defs.some((def) => {
46365
44839
  const node = def.node;
46366
44840
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
46367
44841
  const initializer = stripParenExpression(node.init);
46368
44842
  if (!isNodeOfType(initializer, "CallExpression")) return false;
46369
44843
  const callee = stripParenExpression(initializer.callee);
46370
- return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name);
44844
+ return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(callee.name);
46371
44845
  }));
46372
44846
  };
46373
44847
  const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
@@ -46403,22 +44877,16 @@ const noPassDataToParent = defineRule({
46403
44877
  const callExpr = getCallExpr(ref);
46404
44878
  if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
46405
44879
  const callbackRefProvenance = getCallbackRefProvenance(analysis, node, callExpr, isReactUseRefCall, isReactUseEffectCall);
44880
+ if (isRefCall(analysis, ref) && !callbackRefProvenance) continue;
46406
44881
  if (!isSynchronous(ref.identifier, effectFn)) continue;
46407
44882
  const calleeNode = unwrapChainExpression(callExpr.callee);
46408
44883
  const identifier = ref.identifier;
46409
- const resolvedCallbackPropNames = isNodeOfType(calleeNode, "MemberExpression") && getStaticMemberPropertyName(calleeNode) === "current" ? null : getParentCallbackPropNames({
46410
- analysis,
46411
- expression: calleeNode,
46412
- scopes: context.scopes
46413
- });
46414
- const callbackPropNames = callbackRefProvenance?.callbackPropNames ?? resolvedCallbackPropNames;
46415
- if (isRefCall(analysis, ref) && !callbackPropNames) continue;
46416
- if (callbackPropNames) {
46417
- if ([...callbackPropNames].some((callbackPropName) => COMMAND_PROP_NAME_PATTERN.test(callbackPropName))) continue;
44884
+ if (callbackRefProvenance) {
44885
+ if ([...callbackRefProvenance.callbackPropNames].some((callbackPropName) => COMMAND_PROP_NAME_PATTERN.test(callbackPropName))) continue;
46418
44886
  } else if (calleeNode === identifier) {
46419
44887
  const callbackPropName = getCommandCallbackPropName(analysis, identifier, isReactUseRefCall);
46420
44888
  if (callbackPropName && COMMAND_PROP_NAME_PATTERN.test(callbackPropName)) continue;
46421
- if (!isDirectParentCallbackRef(analysis, ref, context.scopes)) continue;
44889
+ if (!isDirectParentCallbackRef(analysis, ref)) continue;
46422
44890
  if (isNodeOfType(identifier, "Identifier") && COMMAND_PROP_NAME_PATTERN.test(identifier.name)) continue;
46423
44891
  } else if (isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === identifier) {
46424
44892
  if (!isWholePropsObjectReference(analysis, ref)) continue;
@@ -46426,10 +44894,10 @@ const noPassDataToParent = defineRule({
46426
44894
  } else continue;
46427
44895
  const methodName = getCallMethodName(calleeNode);
46428
44896
  const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
46429
- if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead && !callbackPropNames) continue;
44897
+ if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
46430
44898
  if (methodName && COMMAND_PROP_NAME_PATTERN.test(methodName)) continue;
46431
- if (!callbackPropNames && isNamespacedApiCallee(calleeNode)) continue;
46432
- const isSetterNamedCallee = callbackPropNames ? [...callbackPropNames].every((callbackPropName) => SETTER_NAMED_PROP_PATTERN.test(callbackPropName)) : Boolean((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) && SETTER_NAMED_PROP_PATTERN.test((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) ?? ""));
44899
+ if (!callbackRefProvenance && isNamespacedApiCallee(calleeNode)) continue;
44900
+ const isSetterNamedCallee = callbackRefProvenance ? [...callbackRefProvenance.callbackPropNames].every((callbackPropName) => SETTER_NAMED_PROP_PATTERN.test(callbackPropName)) : Boolean((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) && SETTER_NAMED_PROP_PATTERN.test((isNodeOfType(identifier, "Identifier") ? identifier.name : methodName) ?? ""));
46433
44901
  const isLeafRef = (argRef) => getUpstreamRefs(analysis, argRef).length === 1;
46434
44902
  const argsUpstreamRefs = (callExpr.arguments ?? []).flatMap((argument) => {
46435
44903
  if (isFunctionLike$1(argument)) {
@@ -46443,11 +44911,11 @@ const noPassDataToParent = defineRule({
46443
44911
  if (argumentRef && resolveToFunction(argumentRef)) return [];
46444
44912
  }
46445
44913
  return getDownstreamRefs(analysis, argument);
46446
- }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) || isExternalSubscriptionHookRef(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
46447
- if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
44914
+ }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
44915
+ if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
46448
44916
  if (!argsUpstreamRefs.some((argRef) => {
46449
44917
  if (isUseStateIdentifier(argRef.identifier)) return false;
46450
- if (isExternalSubscriptionHookRef(analysis, argRef)) return false;
44918
+ if (isExternalSubscriptionHookRef(argRef)) return false;
46451
44919
  if (isProp(analysis, argRef)) return false;
46452
44920
  if (isUseRefIdentifier(argRef.identifier)) return false;
46453
44921
  if (isRefCurrent(argRef)) return false;
@@ -46482,47 +44950,9 @@ const isCallResultConsumedAsArgument = (callExpression) => {
46482
44950
  return false;
46483
44951
  };
46484
44952
  //#endregion
46485
- //#region src/plugin/rules/state-and-effects/utils/is-custom-hook-state-result-reference.ts
46486
- const NON_STATE_CUSTOM_HOOK_NAMES = new Set([
46487
- "useCallbackRef",
46488
- "useEffectEvent",
46489
- "useEvent",
46490
- "useEventCallback",
46491
- "useLatest",
46492
- "useMemoizedFn",
46493
- "useStableCallback"
46494
- ]);
46495
- const EXTERNAL_SUBSCRIPTION_HOOK_NAMES = new Set([
46496
- "useIntersectionObserver",
46497
- "useMatchMedia",
46498
- "useMediaJobProgress",
46499
- "useMediaQuery",
46500
- "useResizeObserver",
46501
- "useVisibility",
46502
- "useWindowSize"
46503
- ]);
46504
- const getHookCalleeName = (initializer) => {
46505
- const unwrappedInitializer = stripParenExpression(initializer);
46506
- if (!isNodeOfType(unwrappedInitializer, "CallExpression")) return null;
46507
- const callee = stripParenExpression(unwrappedInitializer.callee);
46508
- if (isNodeOfType(callee, "Identifier")) return callee.name;
46509
- if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
46510
- return null;
46511
- };
46512
- const isCustomHookStateResultReference = (analysis, reference) => Boolean(reference.resolved?.defs.some((definition) => {
46513
- const declarator = definition.node;
46514
- if (!isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return false;
46515
- const calleeName = getHookCalleeName(declarator.init);
46516
- if (!calleeName || !HOOK_NAME_PATTERN$3.test(calleeName) || BUILTIN_HOOK_NAMES.has(calleeName) || NON_STATE_CUSTOM_HOOK_NAMES.has(calleeName) || EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(calleeName)) return false;
46517
- const initializer = stripParenExpression(declarator.init);
46518
- if (!isNodeOfType(initializer, "CallExpression")) return false;
46519
- return initializer.arguments.some((argument) => getDownstreamRefs(analysis, argument).some((argumentReference) => isProp(analysis, argumentReference)));
46520
- }));
46521
- //#endregion
46522
44953
  //#region src/plugin/rules/state-and-effects/no-pass-live-state-to-parent.ts
46523
44954
  const SETTER_NAMED_CALLBACK_PATTERN = /^set[A-Z]/;
46524
44955
  const DATA_FETCHING_CALLBACK_PATTERN = /^(fetch|refetch|load|query|request)([A-Z_]|$)/;
46525
- const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
46526
44956
  const getCallCalleeName = (callExpr) => {
46527
44957
  if (!isNodeOfType(callExpr, "CallExpression")) return null;
46528
44958
  const callee = callExpr.callee;
@@ -46567,10 +44997,6 @@ const collectUpstreamStateRefs = (analysis, ref, stateRefs, visited) => {
46567
44997
  stateRefs.push(ref);
46568
44998
  return;
46569
44999
  }
46570
- if (isCustomHookStateResultReference(analysis, ref)) {
46571
- stateRefs.push(ref);
46572
- return;
46573
- }
46574
45000
  for (const def of ref.resolved?.defs ?? []) {
46575
45001
  if (def.type === "ImportBinding" || def.type === "Parameter") continue;
46576
45002
  const defNode = def.node;
@@ -46600,32 +45026,6 @@ const collectPropCallbackBoundStateRefs = (analysis, ref, isPropCallbackRef) =>
46600
45026
  }
46601
45027
  return stateRefs;
46602
45028
  };
46603
- const collectDirectCallStateRefs = (analysis, callExpression) => {
46604
- const stateReferences = [];
46605
- for (const argument of callExpression.arguments) {
46606
- if (isFunctionLike$1(argument)) continue;
46607
- for (const argumentReference of getDownstreamRefs(analysis, argument)) {
46608
- if (resolveToFunction(argumentReference)) continue;
46609
- collectUpstreamStateRefs(analysis, argumentReference, stateReferences, /* @__PURE__ */ new Set());
46610
- }
46611
- }
46612
- return stateReferences;
46613
- };
46614
- const getTransparentWrapperPropReference = (analysis, reference, context) => {
46615
- for (const definition of reference.resolved?.defs ?? []) {
46616
- const declarator = definition.node;
46617
- if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
46618
- const resultSymbol = context.scopes.symbolFor(declarator.id);
46619
- const callbackArgument = getTransparentReactCallbackWrapperArgument(declarator.init, resultSymbol, context.scopes);
46620
- if (!callbackArgument) continue;
46621
- const callbackReferences = getDownstreamRefs(analysis, callbackArgument);
46622
- const callbackReference = callbackReferences.find((candidateReference) => isPropCallbackInvocationRef(analysis, candidateReference));
46623
- if (callbackReference) return callbackReference;
46624
- const propReference = callbackReferences.find((candidateReference) => isProp(analysis, candidateReference) && !candidateReference.resolved?.references.some((candidateUsage) => candidateUsage.isWrite() && !candidateUsage.init));
46625
- if (propReference) return propReference;
46626
- }
46627
- return null;
46628
- };
46629
45029
  const isSetterNamedCallbackReceivingData = (callbackRef) => {
46630
45030
  const callExpr = getCallExpr(callbackRef);
46631
45031
  if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) return false;
@@ -46661,16 +45061,6 @@ const resolvesToLocalHookReturnBinding = (ref) => Boolean(ref?.resolved?.defs?.s
46661
45061
  const calleeName = getInitializerCalleeName(node.init);
46662
45062
  return calleeName !== null && isReactHookName(calleeName) && !FUNCTION_WRAPPER_HOOK_NAMES.has(calleeName);
46663
45063
  }));
46664
- const getDirectLocalEffectHelper = (callExpression, effectFunction, context) => {
46665
- const helperFunction = resolveExactLocalFunction(callExpression.callee, context.scopes);
46666
- if (!helperFunction) return null;
46667
- let ancestor = callExpression.parent;
46668
- while (ancestor && ancestor !== effectFunction) {
46669
- if (isFunctionLike$1(ancestor)) return null;
46670
- ancestor = ancestor.parent;
46671
- }
46672
- return ancestor === effectFunction ? helperFunction : null;
46673
- };
46674
45064
  const noPassLiveStateToParent = defineRule({
46675
45065
  id: "no-pass-live-state-to-parent",
46676
45066
  title: "Live state pushed to parent via effect",
@@ -46685,32 +45075,20 @@ const noPassLiveStateToParent = defineRule({
46685
45075
  if (!effectFnRefs) return;
46686
45076
  const effectFn = getEffectFn(analysis, node);
46687
45077
  if (!effectFn) return;
46688
- const effectFunctionBody = isNodeOfType(effectFn, "ArrowFunctionExpression") || isNodeOfType(effectFn, "FunctionExpression") || isNodeOfType(effectFn, "FunctionDeclaration") ? effectFn.body : null;
46689
45078
  for (const ref of effectFnRefs) {
45079
+ const propCallbackRefs = getEventualCallRefsTo(analysis, ref, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef));
45080
+ if (propCallbackRefs.length === 0) continue;
45081
+ if (resolvesToLocalHookReturnBinding(ref)) continue;
45082
+ if (!isSynchronous(ref.identifier, effectFn)) continue;
46690
45083
  const callExpr = getCallExpr(ref);
46691
- if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
46692
- const directLocalEffectHelper = getDirectLocalEffectHelper(callExpr, effectFn, context);
46693
- const callGraphReferences = directLocalEffectHelper ? [ref, ...getDownstreamRefs(analysis, directLocalEffectHelper)] : [ref];
46694
- const resolvedCallbackPropNames = getParentCallbackPropNames({
46695
- analysis,
46696
- expression: callExpr.callee,
46697
- scopes: context.scopes
46698
- });
46699
- const callExpressionRoot = findTransparentExpressionRoot(callExpr);
46700
- const notificationCallbackPropNames = Boolean(resolvedCallbackPropNames && callExpr.arguments.length > 0 && (!isCallResultCapturedToLocal(callExpr) || isNodeOfType(callExpressionRoot.parent, "ReturnStatement") && callExpressionRoot.parent.parent === effectFunctionBody) && [...resolvedCallbackPropNames].every((callbackPropName) => !DATA_FETCHING_CALLBACK_PATTERN.test(callbackPropName))) ? resolvedCallbackPropNames : null;
46701
- if (!notificationCallbackPropNames && hasMutableBindingWrite(ref)) continue;
46702
- const propCallbackRefs = callGraphReferences.flatMap((callGraphReference) => getEventualCallRefsTo(analysis, callGraphReference, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef)));
46703
- const transparentPropReference = propCallbackRefs.length === 0 ? getTransparentWrapperPropReference(analysis, ref, context) : null;
46704
- if (propCallbackRefs.length === 0 && !transparentPropReference && !notificationCallbackPropNames) continue;
46705
- if (!notificationCallbackPropNames && resolvesToLocalHookReturnBinding(ref)) continue;
46706
- if (!isSynchronous(ref.identifier, effectFn) && !directLocalEffectHelper) continue;
45084
+ if (!callExpr) continue;
46707
45085
  if (isCallResultConsumedAsArgument(callExpr)) continue;
46708
45086
  const calleeNode = callExpr.callee;
46709
45087
  const methodName = calleeNode ? getCallMethodName(calleeNode) : null;
46710
45088
  const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && calleeNode && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
46711
- if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead && !notificationCallbackPropNames) continue;
46712
- if (!notificationCallbackPropNames && calleeNode && isNamespacedApiCallee(calleeNode)) continue;
46713
- const stateArgRefs = transparentPropReference || notificationCallbackPropNames ? collectDirectCallStateRefs(analysis, callExpr) : callGraphReferences.flatMap((callGraphReference) => collectPropCallbackBoundStateRefs(analysis, callGraphReference, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef)));
45089
+ if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
45090
+ if (calleeNode && isNamespacedApiCallee(calleeNode)) continue;
45091
+ const stateArgRefs = collectPropCallbackBoundStateRefs(analysis, ref, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef));
46714
45092
  const handsSetterNamedCallbackData = propCallbackRefs.some(isSetterNamedCallbackReceivingData);
46715
45093
  if (stateArgRefs.length === 0 && !handsSetterNamedCallbackData) continue;
46716
45094
  context.report({
@@ -47103,7 +45481,6 @@ const isStateLikeDependency = (analysis, element, isPropName) => {
47103
45481
  if (!analysis) return true;
47104
45482
  const reference = getRef(analysis, element);
47105
45483
  if (!reference) return true;
47106
- if (isCustomHookStateResultReference(analysis, reference)) return true;
47107
45484
  const upstreamReferences = getUpstreamRefs(analysis, reference);
47108
45485
  if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
47109
45486
  return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
@@ -47120,22 +45497,6 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
47120
45497
  if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
47121
45498
  return isPropName(callbackArgument.name) ? callbackArgument.name : null;
47122
45499
  };
47123
- const getTransparentWrappedPropCallbackName = (callExpression, context, isPropName) => {
47124
- const callee = stripParenExpression(callExpression.callee);
47125
- if (!isNodeOfType(callee, "Identifier")) return null;
47126
- const binding = findVariableInitializer(callExpression, callee.name);
47127
- if (!binding?.initializer) return null;
47128
- const resultSymbol = context.scopes.symbolFor(callee);
47129
- const callbackArgument = getTransparentReactCallbackWrapperArgument(binding.initializer, resultSymbol, context.scopes);
47130
- if (!callbackArgument) return null;
47131
- const callbackSource = stripParenExpression(callbackArgument);
47132
- if (isNodeOfType(callbackSource, "Identifier")) return isPropName(callbackSource.name, callbackSource) ? callbackSource.name : null;
47133
- if (!isNodeOfType(callbackSource, "MemberExpression")) return null;
47134
- const receiver = stripParenExpression(callbackSource.object);
47135
- const propertyName = getStaticPropertyName(callbackSource);
47136
- if (!isNodeOfType(receiver, "Identifier") || !propertyName) return null;
47137
- return isPropName(receiver.name, receiver) ? propertyName : null;
47138
- };
47139
45500
  const noPropCallbackInEffect = defineRule({
47140
45501
  id: "no-prop-callback-in-effect",
47141
45502
  title: "Parent kept in sync with a callback effect",
@@ -47169,16 +45530,9 @@ const noPropCallbackInEffect = defineRule({
47169
45530
  walkInsideStatementBlocks(callback.body, (child) => {
47170
45531
  if (!isNodeOfType(child, "CallExpression")) return;
47171
45532
  const directCallee = stripParenExpression(child.callee);
47172
- const resolvedCallbackPropNames = analysis && propStackTracker.getCurrentPropNames().size > 0 ? getParentCallbackPropNames({
47173
- analysis,
47174
- expression: directCallee,
47175
- scopes: context.scopes
47176
- }) : null;
47177
- const calleeName = resolvedCallbackPropNames && [...resolvedCallbackPropNames][0] || isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName) || getTransparentWrappedPropCallbackName(child, context, propStackTracker.isPropName);
45533
+ const calleeName = isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName);
47178
45534
  if (!calleeName) return;
47179
- const callExpressionRoot = findTransparentExpressionRoot(child);
47180
- const isDirectEffectReturn = isNodeOfType(callExpressionRoot.parent, "ReturnStatement") && callExpressionRoot.parent.parent === callback.body;
47181
- if (!isResultDiscardedCall(child) && !isDirectEffectReturn) return;
45535
+ if (!isResultDiscardedCall(child)) return;
47182
45536
  if (reportedNodes.has(child)) return;
47183
45537
  reportedNodes.add(child);
47184
45538
  context.report({
@@ -48011,69 +46365,6 @@ const noRedundantShouldComponentUpdate = defineRule({
48011
46365
  }
48012
46366
  });
48013
46367
  //#endregion
48014
- //#region src/plugin/rules/correctness/no-ref-callback-cleanup-before-react-19.ts
48015
- const resolveFunctionExpressions = (rawExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
48016
- const expression = stripParenExpression(rawExpression);
48017
- if (isFunctionLike$1(expression)) return expression.async || expression.generator ? [] : [expression];
48018
- if (isNodeOfType(expression, "ConditionalExpression")) {
48019
- if (isNodeOfType(expression.test, "Literal")) return resolveFunctionExpressions(expression.test.value ? expression.consequent : expression.alternate, scopes, visitedSymbolIds);
48020
- return [...resolveFunctionExpressions(expression.consequent, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.alternate, scopes, visitedSymbolIds)];
48021
- }
48022
- if (isNodeOfType(expression, "LogicalExpression")) {
48023
- if (isNodeOfType(expression.left, "Literal")) {
48024
- const isLeftTruthy = Boolean(expression.left.value);
48025
- if (expression.operator === "&&" && !isLeftTruthy) return [];
48026
- if (expression.operator === "||" && isLeftTruthy) return [];
48027
- if (expression.operator === "??" && expression.left.value !== null) return [];
48028
- }
48029
- if (expression.operator === "&&") return resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds);
48030
- return [...resolveFunctionExpressions(expression.left, scopes, visitedSymbolIds), ...resolveFunctionExpressions(expression.right, scopes, visitedSymbolIds)];
48031
- }
48032
- if (isNodeOfType(expression, "SequenceExpression")) {
48033
- const finalExpression = expression.expressions.at(-1);
48034
- return finalExpression ? resolveFunctionExpressions(finalExpression, scopes, visitedSymbolIds) : [];
48035
- }
48036
- if (isNodeOfType(expression, "CallExpression")) {
48037
- if (!isReactApiCall(expression, "useCallback", scopes)) return [];
48038
- const callback = expression.arguments[0];
48039
- return callback && !isNodeOfType(callback, "SpreadElement") ? resolveFunctionExpressions(callback, scopes, visitedSymbolIds) : [];
48040
- }
48041
- if (!isNodeOfType(expression, "Identifier")) return [];
48042
- const symbol = scopes.symbolFor(expression);
48043
- if (!symbol || visitedSymbolIds.has(symbol.id)) return [];
48044
- 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]));
48045
- const initializer = getDirectConstInitializer(symbol);
48046
- if (!initializer) return [];
48047
- return resolveFunctionExpressions(initializer, scopes, new Set([...visitedSymbolIds, symbol.id]));
48048
- };
48049
- const functionReturnsCleanupFunction = (functionExpression, scopes) => {
48050
- if (!isFunctionLike$1(functionExpression)) return false;
48051
- if (!isNodeOfType(functionExpression.body, "BlockStatement")) return resolveFunctionExpressions(functionExpression.body, scopes).length > 0;
48052
- return collectFunctionReturnStatements(functionExpression).some((returnStatement) => Boolean(returnStatement.argument && resolveFunctionExpressions(returnStatement.argument, scopes).length > 0));
48053
- };
48054
- const callbackReturnsCleanupFunction = (callback, scopes) => {
48055
- return resolveFunctionExpressions(callback, scopes).some((functionExpression) => functionReturnsCleanupFunction(functionExpression, scopes));
48056
- };
48057
- const noRefCallbackCleanupBeforeReact19 = defineRule({
48058
- id: "no-ref-callback-cleanup-before-react-19",
48059
- title: "Ref cleanup requires React 19",
48060
- requires: ["react:18"],
48061
- disabledWhen: ["react:19"],
48062
- severity: "warn",
48063
- 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.",
48064
- create: (context) => ({ JSXAttribute(node) {
48065
- if (getJsxAttributeName(node.name) !== "ref") return;
48066
- if (!isNodeOfType(node.value, "JSXExpressionContainer")) return;
48067
- const callback = node.value.expression;
48068
- if (!callback || isNodeOfType(callback, "JSXEmptyExpression")) return;
48069
- if (!callbackReturnsCleanupFunction(callback, context.scopes)) return;
48070
- context.report({
48071
- node,
48072
- 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."
48073
- });
48074
- } })
48075
- });
48076
- //#endregion
48077
46368
  //#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
48078
46369
  const REPEATED_ANCESTOR_TYPES = new Set([
48079
46370
  "DoWhileStatement",
@@ -54834,6 +53125,12 @@ const isInsideEs6Component$1 = (methodDefinition) => {
54834
53125
  if (!owningClass) return false;
54835
53126
  return isPreactOrReactComponentClass(owningClass);
54836
53127
  };
53128
+ const stripThisParameter = (params) => {
53129
+ const first = params[0];
53130
+ if (!first) return params;
53131
+ if (isNodeOfType(first, "Identifier") && first.name === "this") return params.slice(1);
53132
+ return params;
53133
+ };
54837
53134
  const preactNoRenderArguments = defineRule({
54838
53135
  id: "preact-no-render-arguments",
54839
53136
  title: "render() reads props from arguments",
@@ -58035,39 +56332,8 @@ const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node,
58035
56332
  destructureIndex: 1
58036
56333
  });
58037
56334
  //#endregion
58038
- //#region src/plugin/utils/unwrap-return-expression.ts
58039
- const unwrapReturnExpression = (node) => isNodeOfType(node, "ReturnStatement") && node.argument ? node.argument : node;
58040
- //#endregion
58041
56335
  //#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
58042
56336
  const USE_EFFECT_ONLY = new Set(["useEffect"]);
58043
- const USE_CALLBACK_ONLY = new Set(["useCallback"]);
58044
- const USE_STATE_ONLY = new Set(["useState"]);
58045
- const REACT_API_CALL_OPTIONS = {
58046
- allowGlobalReactNamespace: true,
58047
- allowUnboundBareCalls: true,
58048
- resolveNamedAliases: true
58049
- };
58050
- const expressionReadsDerivedSymbol = (context, expression, stateDerivedSymbolIds) => {
58051
- let readsDerivedSymbol = false;
58052
- walkAst(expression, (node) => {
58053
- if (readsDerivedSymbol) return false;
58054
- if (node !== expression && isFunctionLike$1(node)) return false;
58055
- if (isNodeOfType(node, "Identifier") && stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) readsDerivedSymbol = true;
58056
- });
58057
- return readsDerivedSymbol;
58058
- };
58059
- const getStaticObjectPropertyName = (property) => {
58060
- if (!isNodeOfType(property, "Property") || property.computed || property.method || property.kind !== "init") return null;
58061
- if (isNodeOfType(property.key, "Identifier")) return property.key.name;
58062
- if (isNodeOfType(property.key, "Literal") && (typeof property.key.value === "string" || typeof property.key.value === "number")) return String(property.key.value);
58063
- return null;
58064
- };
58065
- const isNonVisibleJsxSpreadProperty = (propertyName) => propertyName === "id" || propertyName.startsWith("aria-") || /^on[A-Z]/.test(propertyName);
58066
- const isTransparentAssignmentTarget = (identifier) => {
58067
- const expressionRoot = findTransparentExpressionRoot(identifier);
58068
- const parent = expressionRoot.parent;
58069
- return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expressionRoot || isNodeOfType(parent, "UpdateExpression") && parent.argument === expressionRoot || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expressionRoot);
58070
- };
58071
56337
  const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
58072
56338
  let readsCurrent = false;
58073
56339
  walkAst(argument, (child) => {
@@ -58119,166 +56385,6 @@ const isStateUsedOnlyInIdOrAriaAttributes = (setterCall, setterName) => {
58119
56385
  });
58120
56386
  return referenceCount > 0 && !nonAriaReferenceFound;
58121
56387
  };
58122
- const isGlobalWindowMember = (context, node, propertyName) => {
58123
- const member = stripParenExpression(node);
58124
- if (!isNodeOfType(member, "MemberExpression") || member.computed) return false;
58125
- const receiver = stripParenExpression(member.object);
58126
- return isNodeOfType(receiver, "Identifier") && receiver.name === "window" && context.scopes.isGlobalReference(receiver) && isNodeOfType(member.property, "Identifier") && member.property.name === propertyName;
58127
- };
58128
- const getDirectWindowWidthSetter = (context, statement) => {
58129
- const call = unwrapDiscardedExpression(statement);
58130
- if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 1) return null;
58131
- if (!isNodeOfType(call.callee, "Identifier") || !isSetterCall(call)) return null;
58132
- const argument = call.arguments[0];
58133
- return isGlobalWindowMember(context, argument, "innerWidth") ? call : null;
58134
- };
58135
- const getResizeListenerHandler = (context, statement, methodName) => {
58136
- const call = unwrapDiscardedExpression(statement);
58137
- if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 2) return null;
58138
- if (!isGlobalWindowMember(context, call.callee, methodName)) return null;
58139
- const eventName = call.arguments[0];
58140
- const handler = call.arguments[1];
58141
- if (!isNodeOfType(eventName, "Literal") || eventName.value !== "resize") return null;
58142
- return isNodeOfType(handler, "Identifier") ? handler : null;
58143
- };
58144
- const getCleanupResizeHandler = (context, statement) => {
58145
- if (!isNodeOfType(statement, "ReturnStatement") || !isFunctionLike$1(statement.argument)) return null;
58146
- const cleanupStatements = getCallbackStatements(statement.argument);
58147
- if (cleanupStatements.length !== 1) return null;
58148
- return getResizeListenerHandler(context, unwrapReturnExpression(cleanupStatements[0]), "removeEventListener");
58149
- };
58150
- const findExactViewportState = (context, componentFunction, setterCall) => {
58151
- if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
58152
- const componentBody = componentFunction.body;
58153
- if (!isNodeOfType(setterCall.callee, "Identifier")) return null;
58154
- const setterSymbol = context.scopes.symbolFor(setterCall.callee);
58155
- if (!setterSymbol || setterSymbol.kind !== "const" || !isNodeOfType(setterSymbol.declarationNode, "VariableDeclarator")) return null;
58156
- const declarator = setterSymbol.declarationNode;
58157
- if (!isNodeOfType(declarator.id, "ArrayPattern")) return null;
58158
- const stateIdentifier = declarator.id.elements?.[0];
58159
- const setterIdentifier = declarator.id.elements?.[1];
58160
- 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;
58161
- const initializer = declarator.init.arguments?.[0];
58162
- if (!isNodeOfType(initializer, "Literal") || initializer.value !== 0) return null;
58163
- const stateSymbol = context.scopes.symbolFor(stateIdentifier);
58164
- if (!stateSymbol) return null;
58165
- const stateDerivedSymbolIds = new Set([stateSymbol.id]);
58166
- let didAddDerivedSymbol = true;
58167
- while (didAddDerivedSymbol) {
58168
- didAddDerivedSymbol = false;
58169
- for (const statement of componentBody.body ?? []) {
58170
- if (!isNodeOfType(statement, "VariableDeclaration")) continue;
58171
- for (const candidateDeclarator of statement.declarations ?? []) {
58172
- if (!isNodeOfType(candidateDeclarator.id, "Identifier") || !candidateDeclarator.init) continue;
58173
- const candidateInitializer = stripParenExpression(candidateDeclarator.init);
58174
- if (isFunctionLike$1(candidateInitializer) || isNodeOfType(candidateInitializer, "CallExpression") && isReactApiCall(candidateInitializer, USE_CALLBACK_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) continue;
58175
- if (!expressionReadsDerivedSymbol(context, candidateInitializer, stateDerivedSymbolIds)) continue;
58176
- const candidateSymbol = context.scopes.symbolFor(candidateDeclarator.id);
58177
- if (candidateSymbol?.kind === "const" && candidateSymbol.references.every((reference) => reference.flag === "read" && !isTransparentAssignmentTarget(reference.identifier)) && !stateDerivedSymbolIds.has(candidateSymbol.id)) {
58178
- stateDerivedSymbolIds.add(candidateSymbol.id);
58179
- didAddDerivedSymbol = true;
58180
- }
58181
- }
58182
- }
58183
- }
58184
- const staticSpreadVisibilityBySymbolId = /* @__PURE__ */ new Map();
58185
- const hasOnlyStaticObjectReferences = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
58186
- const symbol = context.scopes.symbolFor(identifier);
58187
- if (!symbol) return false;
58188
- if (visitedSymbolIds.has(symbol.id)) return true;
58189
- const nextVisitedSymbolIds = new Set(visitedSymbolIds);
58190
- nextVisitedSymbolIds.add(symbol.id);
58191
- let hasUnknownReference = false;
58192
- walkAst(componentBody, (node) => {
58193
- if (hasUnknownReference || !isNodeOfType(node, "Identifier") || context.scopes.symbolFor(node)?.id !== symbol.id || node === symbol.bindingIdentifier) return;
58194
- const referenceRoot = findTransparentExpressionRoot(node);
58195
- const parent = referenceRoot.parent;
58196
- if (isNodeOfType(parent, "JSXSpreadAttribute") && parent.argument === referenceRoot) return;
58197
- if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier") && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const" && hasOnlyStaticObjectReferences(parent.id, nextVisitedSymbolIds)) return;
58198
- hasUnknownReference = true;
58199
- return false;
58200
- });
58201
- return !hasUnknownReference;
58202
- };
58203
- const classifyStaticSpreadObject = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
58204
- const symbol = context.scopes.symbolFor(identifier);
58205
- if (!symbol || visitedSymbolIds.has(symbol.id)) return "unknown";
58206
- const cachedVisibility = staticSpreadVisibilityBySymbolId.get(symbol.id);
58207
- if (cachedVisibility) return cachedVisibility;
58208
- if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || symbol.declarationNode.id !== symbol.bindingIdentifier || !symbol.declarationNode.init) return "unknown";
58209
- if (!hasOnlyStaticObjectReferences(identifier)) return "unknown";
58210
- const initializer = stripParenExpression(symbol.declarationNode.init);
58211
- const nextVisitedSymbolIds = new Set(visitedSymbolIds);
58212
- nextVisitedSymbolIds.add(symbol.id);
58213
- if (isNodeOfType(initializer, "Identifier")) {
58214
- const visibility = classifyStaticSpreadObject(initializer, nextVisitedSymbolIds);
58215
- staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
58216
- return visibility;
58217
- }
58218
- if (!isNodeOfType(initializer, "ObjectExpression")) return "unknown";
58219
- let visibility = "non-visible";
58220
- for (const property of initializer.properties ?? []) {
58221
- const propertyName = getStaticObjectPropertyName(property);
58222
- if (!isNodeOfType(property, "Property") || !propertyName) {
58223
- visibility = "unknown";
58224
- break;
58225
- }
58226
- if (expressionReadsDerivedSymbol(context, property.value, stateDerivedSymbolIds) && !isNonVisibleJsxSpreadProperty(propertyName)) visibility = "visible";
58227
- }
58228
- staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
58229
- return visibility;
58230
- };
58231
- let hasNonAriaReference = false;
58232
- walkAst(componentBody, (node) => {
58233
- if (hasNonAriaReference) return false;
58234
- if (!isNodeOfType(node, "Identifier") || !stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) return;
58235
- if (findEnclosingFunction$1(node) !== componentFunction) return;
58236
- const parent = node.parent;
58237
- if (parent && (isNodeOfType(parent, "MemberExpression") && parent.property === node && !parent.computed || isNodeOfType(parent, "Property") && parent.key === node && !parent.computed)) return;
58238
- let cursor = parent;
58239
- while (cursor && cursor !== componentBody) {
58240
- if (isFunctionLike$1(cursor)) return;
58241
- if (isNodeOfType(cursor, "JSXSpreadAttribute")) {
58242
- if (isNodeOfType(node, "Identifier") && classifyStaticSpreadObject(node) === "visible") hasNonAriaReference = true;
58243
- return;
58244
- }
58245
- if (isNodeOfType(cursor, "JSXAttribute")) {
58246
- if (isEventHandlerAttribute(cursor)) return;
58247
- if (!isInsideIdOrAriaAttribute(node)) hasNonAriaReference = true;
58248
- return;
58249
- }
58250
- if (isNodeOfType(cursor, "ReturnStatement")) {
58251
- hasNonAriaReference = true;
58252
- return;
58253
- }
58254
- cursor = cursor.parent;
58255
- }
58256
- });
58257
- return hasNonAriaReference ? stateIdentifier.name : null;
58258
- };
58259
- const isExactViewportSubscriptionEffect = (context, effectCall, callback) => {
58260
- if (!isReactApiCall(effectCall, USE_EFFECT_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return false;
58261
- if (!isFunctionLike$1(callback) || callback.async || !isNodeOfType(callback.body, "BlockStatement")) return false;
58262
- const statements = getCallbackStatements(callback);
58263
- if (statements.length !== 4) return false;
58264
- const handlerDeclaration = statements[0];
58265
- if (!isNodeOfType(handlerDeclaration, "VariableDeclaration") || handlerDeclaration.kind !== "const" || handlerDeclaration.declarations?.length !== 1) return false;
58266
- const handlerDeclarator = handlerDeclaration.declarations[0];
58267
- if (!isNodeOfType(handlerDeclarator.id, "Identifier") || !isFunctionLike$1(handlerDeclarator.init)) return false;
58268
- const handlerStatements = getCallbackStatements(handlerDeclarator.init);
58269
- if (handlerStatements.length !== 1) return false;
58270
- const handlerSetter = getDirectWindowWidthSetter(context, unwrapReturnExpression(handlerStatements[0]));
58271
- const subscribedHandler = getResizeListenerHandler(context, statements[1], "addEventListener");
58272
- const immediateSetter = getDirectWindowWidthSetter(context, statements[2]);
58273
- const cleanupHandler = getCleanupResizeHandler(context, statements[3]);
58274
- if (!handlerSetter || !subscribedHandler || !immediateSetter || !cleanupHandler) return false;
58275
- const handlerSymbol = context.scopes.symbolFor(handlerDeclarator.id);
58276
- if (!handlerSymbol || context.scopes.symbolFor(subscribedHandler) !== handlerSymbol || context.scopes.symbolFor(cleanupHandler) !== handlerSymbol) return false;
58277
- if (!isNodeOfType(handlerSetter.callee, "Identifier") || !isNodeOfType(immediateSetter.callee, "Identifier") || context.scopes.symbolFor(handlerSetter.callee) !== context.scopes.symbolFor(immediateSetter.callee)) return false;
58278
- const componentFunction = findEnclosingFunction$1(effectCall);
58279
- if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return false;
58280
- return findExactViewportState(context, componentFunction, immediateSetter) !== null;
58281
- };
58282
56388
  const renderingHydrationNoFlicker = defineRule({
58283
56389
  id: "rendering-hydration-no-flicker",
58284
56390
  title: "useEffect setState flashes on mount",
@@ -58291,14 +56397,7 @@ const renderingHydrationNoFlicker = defineRule({
58291
56397
  if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
58292
56398
  const callback = getEffectCallback(node);
58293
56399
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
58294
- if (isExactViewportSubscriptionEffect(context, node, callback)) {
58295
- context.report({
58296
- node,
58297
- message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
58298
- });
58299
- return;
58300
- }
58301
- const bodyStatements = getCallbackStatements(callback);
56400
+ const bodyStatements = (isNodeOfType(callback.body, "BlockStatement") ? callback.body.body ?? [] : [callback.body]).filter((statement) => !isNoOpStatement(statement));
58302
56401
  if (bodyStatements.length !== 1) return;
58303
56402
  const soleStatement = bodyStatements[0];
58304
56403
  if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
@@ -58461,125 +56560,6 @@ const ASYNC_DATA_CALLEE_NAMES = new Set([
58461
56560
  const RESOURCE_LOAD_EVENT_ATTRIBUTE_PATTERN = /^on(?:Load|Error|Abort|Progress|CanPlay|Stalled|Suspend|Waiting|Ended)/;
58462
56561
  const JSX_EVENT_HANDLER_ATTRIBUTE_PATTERN = /^on[A-Z]/;
58463
56562
  const REDUX_DISPATCH_HOOK_PATTERN = /^use\w*Dispatch$/;
58464
- const FILE_READER_READ_METHOD_NAMES = new Set([
58465
- "readAsArrayBuffer",
58466
- "readAsBinaryString",
58467
- "readAsDataURL",
58468
- "readAsText"
58469
- ]);
58470
- const isGlobalFileReaderConstruction = (expression, context) => {
58471
- if (!expression) return false;
58472
- const unwrappedExpression = stripParenExpression(expression);
58473
- if (!isNodeOfType(unwrappedExpression, "NewExpression") || !isNodeOfType(unwrappedExpression.callee, "Identifier")) return false;
58474
- return unwrappedExpression.callee.name === "FileReader" && context.scopes.isGlobalReference(unwrappedExpression.callee);
58475
- };
58476
- const getFileReaderOriginStartBefore = (readerSymbol, readCall, context) => {
58477
- const readFunction = findEnclosingFunction$1(readCall);
58478
- let latestValue = null;
58479
- let latestStart = null;
58480
- if (readerSymbol.initializer && findEnclosingFunction$1(readerSymbol.declarationNode) === readFunction && readerSymbol.declarationNode.range[0] < readCall.range[0]) {
58481
- latestValue = readerSymbol.initializer;
58482
- latestStart = readerSymbol.declarationNode.range[0];
58483
- }
58484
- for (const reference of readerSymbol.references) {
58485
- if (reference.flag === "read" || reference.identifier.range[0] >= readCall.range[0] || latestStart !== null && reference.identifier.range[0] <= latestStart || findEnclosingFunction$1(reference.identifier) !== readFunction) continue;
58486
- const assignment = reference.identifier.parent;
58487
- if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== reference.identifier) continue;
58488
- latestValue = assignment.right;
58489
- latestStart = reference.identifier.range[0];
58490
- }
58491
- return isGlobalFileReaderConstruction(latestValue, context) ? latestStart : null;
58492
- };
58493
- const resolveLoadingCompletionFunction = (expression, context) => {
58494
- const directFunction = resolveExactLocalFunction(expression, context.scopes);
58495
- if (directFunction) return directFunction;
58496
- const unwrappedExpression = stripParenExpression(expression);
58497
- if (!isNodeOfType(unwrappedExpression, "Identifier")) return null;
58498
- const symbol = context.scopes.symbolFor(unwrappedExpression);
58499
- const initializer = symbol ? getDirectUnreassignedInitializer(symbol) : null;
58500
- if (!initializer || !isNodeOfType(initializer, "CallExpression") || !isReactApiCall(initializer, "useCallback", context.scopes)) return null;
58501
- const callback = initializer.arguments?.[0];
58502
- return callback && isFunctionLike$1(callback) ? callback : null;
58503
- };
58504
- const isSetterBooleanCall = (node, setterSymbol, value, context) => {
58505
- if (!isNodeOfType(node, "CallExpression")) return false;
58506
- const callee = stripParenExpression(node.callee);
58507
- const argument = node.arguments?.[0];
58508
- const unwrappedArgument = argument ? stripParenExpression(argument) : null;
58509
- return Boolean(isNodeOfType(callee, "Identifier") && context.scopes.symbolFor(callee) === setterSymbol && unwrappedArgument && isNodeOfType(unwrappedArgument, "Literal") && unwrappedArgument.value === value);
58510
- };
58511
- const functionClearsLoadingState = (functionNode, setterSymbol, context, visitedFunctions) => {
58512
- if (visitedFunctions.has(functionNode) || !isFunctionLike$1(functionNode)) return false;
58513
- visitedFunctions.add(functionNode);
58514
- let didClearLoadingState = false;
58515
- walkAst(functionNode.body, (child) => {
58516
- if (didClearLoadingState) return false;
58517
- if (child !== functionNode.body && isFunctionLike$1(child)) return false;
58518
- if (!isNodeOfType(child, "CallExpression")) return;
58519
- if (isSetterBooleanCall(child, setterSymbol, false, context)) {
58520
- didClearLoadingState = true;
58521
- return false;
58522
- }
58523
- const helperFunction = resolveLoadingCompletionFunction(child.callee, context);
58524
- if (helperFunction && functionClearsLoadingState(helperFunction, setterSymbol, context, visitedFunctions)) {
58525
- didClearLoadingState = true;
58526
- return false;
58527
- }
58528
- });
58529
- return didClearLoadingState;
58530
- };
58531
- const getLatestFileReaderCallbackBefore = (readCall, readerSymbol, propertyName, originStart, context) => {
58532
- const readFunction = findEnclosingFunction$1(readCall);
58533
- if (!readFunction || !isFunctionLike$1(readFunction)) return null;
58534
- let callback = null;
58535
- let callbackStart = originStart;
58536
- walkAst(readFunction.body, (child) => {
58537
- if (child !== readFunction.body && isFunctionLike$1(child)) return false;
58538
- 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;
58539
- const receiver = stripParenExpression(child.left.object);
58540
- if (!isNodeOfType(receiver, "Identifier") || context.scopes.symbolFor(receiver) !== readerSymbol) return;
58541
- callback = child.right;
58542
- callbackStart = child.range[0];
58543
- });
58544
- return callback;
58545
- };
58546
- const setterStartsLoadingBefore = (readCall, setterSymbol, context) => {
58547
- const readFunction = findEnclosingFunction$1(readCall);
58548
- if (!readFunction || !isFunctionLike$1(readFunction)) return false;
58549
- let didStartLoading = false;
58550
- walkAst(readFunction.body, (child) => {
58551
- if (didStartLoading) return false;
58552
- if (child !== readFunction.body && isFunctionLike$1(child)) return false;
58553
- if (!isNodeOfType(child, "CallExpression") || child.range[0] >= readCall.range[0]) return;
58554
- if (isSetterBooleanCall(child, setterSymbol, true, context)) {
58555
- didStartLoading = true;
58556
- return false;
58557
- }
58558
- });
58559
- return didStartLoading;
58560
- };
58561
- const setterTracksFileReader = (functionBody, setterSymbol, context) => {
58562
- let didFindFileReaderLifecycle = false;
58563
- walkAst(functionBody, (child) => {
58564
- if (didFindFileReaderLifecycle) return false;
58565
- if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || !FILE_READER_READ_METHOD_NAMES.has(getStaticPropertyName(child.callee) ?? "")) return;
58566
- const receiver = stripParenExpression(child.callee.object);
58567
- if (!isNodeOfType(receiver, "Identifier")) return;
58568
- const readerSymbol = context.scopes.symbolFor(receiver);
58569
- if (!readerSymbol) return;
58570
- const originStart = getFileReaderOriginStartBefore(readerSymbol, child, context);
58571
- if (originStart === null || !setterStartsLoadingBefore(child, setterSymbol, context)) return;
58572
- const loadCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onload", originStart, context);
58573
- const errorCallback = getLatestFileReaderCallbackBefore(child, readerSymbol, "onerror", originStart, context);
58574
- const loadFunction = loadCallback ? resolveLoadingCompletionFunction(loadCallback, context) : null;
58575
- const errorFunction = errorCallback ? resolveLoadingCompletionFunction(errorCallback, context) : null;
58576
- if (loadFunction && errorFunction && functionClearsLoadingState(loadFunction, setterSymbol, context, /* @__PURE__ */ new Set()) && functionClearsLoadingState(errorFunction, setterSymbol, context, /* @__PURE__ */ new Set())) {
58577
- didFindFileReaderLifecycle = true;
58578
- return false;
58579
- }
58580
- });
58581
- return didFindFileReaderLifecycle;
58582
- };
58583
56563
  const hasAsyncLoadingWork = (fnBody, setterName) => {
58584
56564
  let found = false;
58585
56565
  walkAst(fnBody, (child) => {
@@ -58753,8 +56733,6 @@ const renderingUsetransitionLoading = defineRule({
58753
56733
  const fnBody = enclosingFunctionBody(node);
58754
56734
  if (fnBody && hasAsyncLoadingWork(fnBody, setterName)) return;
58755
56735
  if (fnBody && setterName) {
58756
- const setterSymbol = isNodeOfType(secondBinding, "Identifier") ? context.scopes.symbolFor(secondBinding) : null;
58757
- if (setterSymbol && setterTracksFileReader(fnBody, setterSymbol, context)) return;
58758
56736
  if (setterEscapes(fnBody, setterName, node)) return;
58759
56737
  if (setterCalledAlongsideAsyncSignal(fnBody, setterName)) return;
58760
56738
  if (setterCalledInEventListenerHandler(fnBody, setterName)) return;
@@ -68251,6 +66229,14 @@ const isStateKey = (key) => {
68251
66229
  if (isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value === "state";
68252
66230
  return false;
68253
66231
  };
66232
+ const findEnclosingClass = (node) => {
66233
+ let ancestor = node.parent;
66234
+ while (ancestor) {
66235
+ if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
66236
+ ancestor = ancestor.parent ?? null;
66237
+ }
66238
+ return null;
66239
+ };
68254
66240
  const isInConstructor = (node) => {
68255
66241
  let ancestor = node.parent;
68256
66242
  while (ancestor) {
@@ -72962,17 +70948,6 @@ const reactDoctorRules = [
72962
70948
  requires: [...new Set(["react", ...noRedundantShouldComponentUpdate.requires ?? []])]
72963
70949
  }
72964
70950
  },
72965
- {
72966
- key: "react-doctor/no-ref-callback-cleanup-before-react-19",
72967
- id: "no-ref-callback-cleanup-before-react-19",
72968
- source: "react-doctor",
72969
- originallyExternal: false,
72970
- rule: {
72971
- ...noRefCallbackCleanupBeforeReact19,
72972
- framework: "global",
72973
- category: "Bugs"
72974
- }
72975
- },
72976
70951
  {
72977
70952
  key: "react-doctor/no-ref-current-in-render",
72978
70953
  id: "no-ref-current-in-render",