oxlint-plugin-react-doctor 0.7.9-dev.b392093 → 0.7.9-dev.bd3655c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1056 -150
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6736,6 +6736,28 @@ const asyncParallel = defineRule({
6736
6736
  }
6737
6737
  });
6738
6738
  //#endregion
6739
+ //#region src/plugin/utils/collect-function-return-statements.ts
6740
+ const collectFunctionReturnStatements = (functionNode) => {
6741
+ if (!isFunctionLike$1(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return [];
6742
+ const returnStatements = [];
6743
+ walkAst(functionNode.body, (node) => {
6744
+ if (node !== functionNode.body && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
6745
+ if (isNodeOfType(node, "ReturnStatement")) returnStatements.push(node);
6746
+ });
6747
+ return returnStatements;
6748
+ };
6749
+ //#endregion
6750
+ //#region src/plugin/utils/is-nullish-expression.ts
6751
+ const isNullishExpression = (expression) => isNodeOfType(expression, "Literal") && expression.value === null || isNodeOfType(expression, "Identifier") && expression.name === "undefined" || isNodeOfType(expression, "UnaryExpression") && expression.operator === "void";
6752
+ //#endregion
6753
+ //#region src/plugin/utils/strip-this-parameter.ts
6754
+ const stripThisParameter = (parameters) => {
6755
+ const firstParameter = parameters[0];
6756
+ if (!firstParameter) return parameters;
6757
+ if (isNodeOfType(firstParameter, "Identifier") && firstParameter.name === "this") return parameters.slice(1);
6758
+ return parameters;
6759
+ };
6760
+ //#endregion
6739
6761
  //#region src/plugin/rules/security/auth-token-in-web-storage.ts
6740
6762
  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.";
6741
6763
  const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
@@ -6747,12 +6769,8 @@ const STORAGE_GLOBALS = new Set([
6747
6769
  const SENSITIVE_KEY_PATTERN = /token|jwt|secret|password|passwd|credential|api[-_]?key|bearer|private[-_]?key/i;
6748
6770
  const NON_AUTH_TOKEN_PATTERN = /csrf|xsrf|device|fcm|apns|push|design|tokeniz|syntax|css|theme|color/i;
6749
6771
  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;
6752
6772
  const isAuthCredentialKey = (key) => {
6753
- if (PRODUCT_API_KEY_RECORDS_PATTERN.test(key)) return false;
6754
6773
  if (!SENSITIVE_KEY_PATTERN.test(key)) return false;
6755
- if (PRODUCT_API_KEY_COLLECTION_PATTERN.test(key)) return false;
6756
6774
  if (NON_AUTH_TOKEN_PATTERN.test(key) && !STRONG_AUTH_KEY_PATTERN.test(key)) return false;
6757
6775
  return true;
6758
6776
  };
@@ -6761,11 +6779,63 @@ const isDirectWebStorageObject = (node) => {
6761
6779
  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);
6762
6780
  return false;
6763
6781
  };
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;
6782
+ const immutableInitializer = (identifier, visitedIdentifiers = /* @__PURE__ */ new Set()) => {
6783
+ if (visitedIdentifiers.has(identifier)) return null;
6784
+ visitedIdentifiers.add(identifier);
6785
+ const binding = findVariableInitializer(identifier, identifier.name);
6786
+ if (!binding?.initializer) return null;
6787
+ if (isNodeOfType(binding.initializer, "FunctionDeclaration")) return binding.initializer;
6788
+ const declarator = binding.bindingIdentifier.parent;
6789
+ if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return null;
6790
+ const declaration = declarator.parent;
6791
+ if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) return null;
6792
+ if (declaration.kind !== "const") return null;
6793
+ const initializer = stripParenExpression(binding.initializer);
6794
+ if (!isNodeOfType(initializer, "Identifier")) return initializer;
6795
+ return immutableInitializer(initializer, visitedIdentifiers) ?? initializer;
6796
+ };
6797
+ const isWebStorageFactoryResult = (node, visitedNodes) => {
6798
+ const expression = stripParenExpression(node);
6799
+ if (isWebStorageObject(expression, new Set(visitedNodes))) return true;
6800
+ if (isNodeOfType(expression, "ConditionalExpression")) {
6801
+ const consequent = stripParenExpression(expression.consequent);
6802
+ const alternate = stripParenExpression(expression.alternate);
6803
+ return (isNullishExpression(consequent) || isWebStorageFactoryResult(consequent, visitedNodes)) && (isNullishExpression(alternate) || isWebStorageFactoryResult(alternate, visitedNodes)) && (!isNullishExpression(consequent) || !isNullishExpression(alternate));
6804
+ }
6805
+ if (isNodeOfType(expression, "LogicalExpression")) {
6806
+ if (expression.operator === "&&") return isWebStorageFactoryResult(expression.right, visitedNodes);
6807
+ const left = stripParenExpression(expression.left);
6808
+ const right = stripParenExpression(expression.right);
6809
+ const isLeftStorage = isWebStorageFactoryResult(left, visitedNodes);
6810
+ const isRightStorage = isWebStorageFactoryResult(right, visitedNodes);
6811
+ return (isLeftStorage || isNullishExpression(left)) && (isRightStorage || isNullishExpression(right)) && (isLeftStorage || isRightStorage);
6812
+ }
6813
+ return false;
6814
+ };
6815
+ const isWebStorageObject = (node, visitedNodes = /* @__PURE__ */ new Set()) => {
6816
+ const expression = stripParenExpression(node);
6817
+ if (visitedNodes.has(expression)) return false;
6818
+ visitedNodes.add(expression);
6819
+ if (isDirectWebStorageObject(expression)) return true;
6820
+ if (isNodeOfType(expression, "Identifier")) {
6821
+ const initializer = immutableInitializer(expression);
6822
+ return initializer ? isWebStorageObject(initializer, new Set(visitedNodes)) : false;
6823
+ }
6824
+ if (!isNodeOfType(expression, "CallExpression")) return false;
6825
+ const callee = stripParenExpression(expression.callee);
6826
+ if (!isNodeOfType(callee, "Identifier")) return false;
6827
+ const factory = immutableInitializer(callee);
6828
+ if (!isFunctionLike$1(factory)) return false;
6829
+ if (isNodeOfType(factory, "ArrowFunctionExpression") && !isNodeOfType(factory.body, "BlockStatement")) return isWebStorageFactoryResult(factory.body, visitedNodes);
6830
+ let didReturnWebStorage = false;
6831
+ for (const returnStatement of collectFunctionReturnStatements(factory)) {
6832
+ if (!returnStatement.argument) continue;
6833
+ const strippedReturn = stripParenExpression(returnStatement.argument);
6834
+ if (isNullishExpression(strippedReturn)) continue;
6835
+ if (!isWebStorageFactoryResult(strippedReturn, visitedNodes)) return false;
6836
+ didReturnWebStorage = true;
6837
+ }
6838
+ return didReturnWebStorage;
6769
6839
  };
6770
6840
  const resolveStaticKeyString = (node) => {
6771
6841
  if (isNodeOfType(node, "Literal") && typeof node.value === "string") return node.value;
@@ -6785,6 +6855,56 @@ const staticMemberName = (member) => {
6785
6855
  if (member.computed && isNodeOfType(member.property, "Literal") && typeof member.property.value === "string") return member.property.value;
6786
6856
  return null;
6787
6857
  };
6858
+ const parameterIndex = (expression, parameterSymbolIds, scopes, canUnwrapSerialization, visitedNodes = /* @__PURE__ */ new Set()) => {
6859
+ const strippedExpression = stripParenExpression(expression);
6860
+ if (visitedNodes.has(strippedExpression)) return null;
6861
+ visitedNodes.add(strippedExpression);
6862
+ if (isNodeOfType(strippedExpression, "Identifier")) {
6863
+ const directSymbolId = scopes.symbolFor(strippedExpression)?.id;
6864
+ const directIndex = directSymbolId === void 0 ? -1 : parameterSymbolIds.indexOf(directSymbolId);
6865
+ if (directIndex !== -1) return directIndex;
6866
+ const initializer = immutableInitializer(strippedExpression);
6867
+ return initializer ? parameterIndex(initializer, parameterSymbolIds, scopes, canUnwrapSerialization, visitedNodes) : null;
6868
+ }
6869
+ 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") {
6870
+ const serializedArgument = strippedExpression.arguments[0];
6871
+ return serializedArgument ? parameterIndex(serializedArgument, parameterSymbolIds, scopes, true, visitedNodes) : null;
6872
+ }
6873
+ return null;
6874
+ };
6875
+ const storageHelperSinkCache = /* @__PURE__ */ new WeakMap();
6876
+ const findStorageHelperSinks = (functionNode, scopes) => {
6877
+ const cachedSinks = storageHelperSinkCache.get(functionNode);
6878
+ if (cachedSinks) return cachedSinks;
6879
+ if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) {
6880
+ storageHelperSinkCache.set(functionNode, []);
6881
+ return [];
6882
+ }
6883
+ const parameterSymbolIds = stripThisParameter(functionNode.params).map((parameter) => {
6884
+ const strippedParameter = stripParenExpression(parameter);
6885
+ const identifier = isNodeOfType(strippedParameter, "Identifier") ? strippedParameter : isNodeOfType(strippedParameter, "AssignmentPattern") && isNodeOfType(strippedParameter.left, "Identifier") ? strippedParameter.left : null;
6886
+ return identifier ? scopes.symbolFor(identifier)?.id ?? null : null;
6887
+ });
6888
+ const helperSinks = [];
6889
+ walkAst(functionNode.body, (child) => {
6890
+ if (child !== functionNode.body && isFunctionLike$1(child)) return false;
6891
+ if (!isNodeOfType(child, "CallExpression")) return;
6892
+ const callee = stripParenExpression(child.callee);
6893
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== "setItem" || !isWebStorageObject(callee.object)) return;
6894
+ const keyExpression = child.arguments[0];
6895
+ const valueExpression = child.arguments[1];
6896
+ if (!keyExpression || !valueExpression) return;
6897
+ const keyParameterIndex = parameterIndex(keyExpression, parameterSymbolIds, scopes, false);
6898
+ const valueParameterIndex = parameterIndex(valueExpression, parameterSymbolIds, scopes, true);
6899
+ if (keyParameterIndex === null || valueParameterIndex === null) return;
6900
+ helperSinks.push({
6901
+ keyParameterIndex,
6902
+ valueParameterIndex
6903
+ });
6904
+ });
6905
+ storageHelperSinkCache.set(functionNode, helperSinks);
6906
+ return helperSinks;
6907
+ };
6788
6908
  const authTokenInWebStorage = defineRule({
6789
6909
  id: "auth-token-in-web-storage",
6790
6910
  title: "Auth token in web storage",
@@ -6792,14 +6912,23 @@ const authTokenInWebStorage = defineRule({
6792
6912
  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.",
6793
6913
  create: skipNonProductionFiles((context) => ({
6794
6914
  CallExpression(node) {
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;
6915
+ const callee = stripParenExpression(node.callee);
6916
+ const keyArguments = [];
6917
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "setItem" && isWebStorageObject(stripParenExpression(callee.object))) {
6918
+ const keyArgument = node.arguments[0];
6919
+ if (keyArgument) keyArguments.push(keyArgument);
6920
+ } else if (isNodeOfType(callee, "Identifier")) {
6921
+ const helperFunction = immutableInitializer(callee);
6922
+ const helperSinks = helperFunction ? findStorageHelperSinks(helperFunction, context.scopes) : [];
6923
+ for (const helperSink of helperSinks) {
6924
+ const keyArgument = node.arguments[helperSink.keyParameterIndex];
6925
+ if (keyArgument && node.arguments[helperSink.valueParameterIndex]) keyArguments.push(keyArgument);
6926
+ }
6927
+ }
6928
+ if (!keyArguments.some((keyArgument) => {
6929
+ const keyString = resolveStaticKeyString(keyArgument);
6930
+ return keyString !== null && isAuthCredentialKey(keyString);
6931
+ })) return;
6803
6932
  context.report({
6804
6933
  node,
6805
6934
  message: MESSAGE$60
@@ -7047,9 +7176,6 @@ const isCreateElementCall = (node) => {
7047
7176
  return false;
7048
7177
  };
7049
7178
  //#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
7053
7179
  //#region src/plugin/rules/react-builtins/button-has-type.ts
7054
7180
  const MISSING_MESSAGE$2 = "Your users can submit the form by accident because a `<button>` with no `type` defaults to submit.";
7055
7181
  const INVALID_MESSAGE = "This button has an invalid `type`, so the browser may treat it like a submit button.";
@@ -7511,6 +7637,7 @@ const areExpressionsStructurallyEqual = (a, b) => {
7511
7637
  if (a.type !== b.type) return false;
7512
7638
  if (isNodeOfType(a, "ThisExpression")) return true;
7513
7639
  if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
7640
+ if (isNodeOfType(a, "PrivateIdentifier") && isNodeOfType(b, "PrivateIdentifier")) return a.name === b.name;
7514
7641
  if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
7515
7642
  if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
7516
7643
  if (a.computed !== b.computed) return false;
@@ -8566,17 +8693,6 @@ const createMethodMutationAnalysis = (context) => {
8566
8693
  //#region src/plugin/utils/is-member-property.ts
8567
8694
  const isMemberProperty = (node, propertyName) => Boolean(node && isNodeOfType(node, "MemberExpression") && isNodeOfType(node.property, "Identifier") && node.property.name === propertyName);
8568
8695
  //#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
8580
8696
  //#region src/plugin/utils/statement-always-exits.ts
8581
8697
  const statementAlwaysExits = (statement) => {
8582
8698
  if (isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement")) return true;
@@ -8843,7 +8959,7 @@ const isReactNamespaceImport = (identifier, scopes) => {
8843
8959
  if (!symbol || !isImportedFromReact(symbol)) return false;
8844
8960
  return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
8845
8961
  };
8846
- const isReactNamespaceReceiver = (receiver, scopes, options) => {
8962
+ const isReactNamespaceReceiver$1 = (receiver, scopes, options) => {
8847
8963
  if (!isNodeOfType(receiver, "Identifier")) return false;
8848
8964
  if (isReactNamespaceImport(receiver, scopes)) return true;
8849
8965
  return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
@@ -8856,7 +8972,7 @@ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) =>
8856
8972
  for (const property of pattern.properties) {
8857
8973
  if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
8858
8974
  const propertyName = getStaticPropertyKeyName(property);
8859
- return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver(stripParenExpression(symbol.initializer), scopes, options));
8975
+ return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver$1(stripParenExpression(symbol.initializer), scopes, options));
8860
8976
  }
8861
8977
  return false;
8862
8978
  };
@@ -8880,7 +8996,7 @@ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds
8880
8996
  return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
8881
8997
  }
8882
8998
  if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
8883
- return isReactNamespaceReceiver(stripParenExpression(callee.object), scopes, options);
8999
+ return isReactNamespaceReceiver$1(stripParenExpression(callee.object), scopes, options);
8884
9000
  };
8885
9001
  //#endregion
8886
9002
  //#region src/plugin/utils/is-proven-browser-api-receiver.ts
@@ -14751,6 +14867,29 @@ const getReleaseVerbName = (node) => {
14751
14867
  }
14752
14868
  return null;
14753
14869
  };
14870
+ const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) => {
14871
+ const releaseFunction = findEnclosingFunction$1(releaseReceiver);
14872
+ const usageFunction = findEnclosingFunction$1(usage.node);
14873
+ if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction) || !hasReactRefCurrentOrigin(releaseReceiver, context.scopes)) return false;
14874
+ const controllerKey = getListenerAbortControllerKey(usage, context);
14875
+ const refCurrentKey = resolveExpressionKey(releaseReceiver, context);
14876
+ if (controllerKey === null || refCurrentKey === null) return false;
14877
+ const usageFunctionBody = usageFunction.body;
14878
+ const previousAbortCalls = [];
14879
+ const ownershipAssignments = [];
14880
+ walkAst(usageFunctionBody, (child) => {
14881
+ if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
14882
+ if (isNodeOfType(child, "AssignmentExpression") && resolveExpressionKey(child.left, context) === refCurrentKey && resolveExpressionKey(child.right, context) === controllerKey) {
14883
+ ownershipAssignments.push(child);
14884
+ return;
14885
+ }
14886
+ if (!isNodeOfType(child, "CallExpression")) return;
14887
+ const childCallee = isNodeOfType(child.callee, "ChainExpression") ? child.callee.expression : stripParenExpression(child.callee);
14888
+ if (isNodeOfType(childCallee, "MemberExpression") && !childCallee.computed && isNodeOfType(childCallee.property, "Identifier") && childCallee.property.name === "abort" && resolveExpressionKey(childCallee.object, context) === refCurrentKey) previousAbortCalls.push(child);
14889
+ });
14890
+ const safeOwnershipAssignments = ownershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, previousAbortCalls, usageFunction, context));
14891
+ return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
14892
+ };
14754
14893
  const doesReleaseCallMatchUsage = (node, usage, context) => {
14755
14894
  const callNode = isNodeOfType(node, "ChainExpression") ? node.expression : node;
14756
14895
  if (!isNodeOfType(callNode, "CallExpression")) return false;
@@ -14770,6 +14909,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
14770
14909
  if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
14771
14910
  if (usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub" || releaseVerbName === "close" || releaseVerbName === "unwatch" || releaseVerbName === "unlisten" || BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(releaseVerbName))) return true;
14772
14911
  if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
14912
+ if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
14773
14913
  if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) return false;
14774
14914
  const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
14775
14915
  if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
@@ -14803,9 +14943,11 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
14803
14943
  return isNodeOfType(handlerArgument, "Literal") && handlerArgument.value === null;
14804
14944
  }
14805
14945
  if (releaseVerbName === "removeEventListener" || releaseVerbName === "removeListener" || releaseVerbName === "off") {
14806
- const releaseHandler = callNode.arguments?.[1];
14946
+ const usesUnaryListenerSignature = usage.registrationVerbName === "addListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1 && callNode.arguments?.length === 1;
14947
+ const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
14807
14948
  if (!releaseHandler) return releaseVerbName === "off";
14808
- return usage.handlerKey !== null && resolveExpressionKey(releaseHandler, context) === usage.handlerKey;
14949
+ const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
14950
+ return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey;
14809
14951
  }
14810
14952
  if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
14811
14953
  return true;
@@ -14818,8 +14960,7 @@ const isReturnedEffectCleanupFunction = (functionNode) => {
14818
14960
  currentNode = parentNode;
14819
14961
  parentNode = currentNode.parent;
14820
14962
  }
14821
- if (!isNodeOfType(parentNode, "ReturnStatement") || parentNode.argument !== currentNode) return false;
14822
- const effectCallback = findEnclosingFunction$1(parentNode);
14963
+ const effectCallback = isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode ? findEnclosingFunction$1(parentNode) : isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode ? parentNode : null;
14823
14964
  const effectCall = effectCallback?.parent;
14824
14965
  return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
14825
14966
  };
@@ -15105,6 +15246,11 @@ const doesResourceResultEscape = (resourceNode, allowReturnedResourceEscape, all
15105
15246
  parentNode = currentNode.parent;
15106
15247
  continue;
15107
15248
  }
15249
+ if (isNodeOfType(parentNode, "ConditionalExpression") && (parentNode.consequent === currentNode || parentNode.alternate === currentNode) || isNodeOfType(parentNode, "LogicalExpression") && (parentNode.right === currentNode || parentNode.left === currentNode && parentNode.operator !== "&&")) {
15250
+ currentNode = parentNode;
15251
+ parentNode = currentNode.parent;
15252
+ continue;
15253
+ }
15108
15254
  if (isNodeOfType(parentNode, "VariableDeclarator") && parentNode.init === currentNode && isNodeOfType(parentNode.id, "Identifier") && isNodeOfType(parentNode.parent, "VariableDeclaration") && parentNode.parent.kind === "const") {
15109
15255
  const ownerFunction = findEnclosingFunction$1(resourceNode);
15110
15256
  const resourceSymbol = context.scopes.symbolFor(parentNode.id);
@@ -29276,7 +29422,7 @@ const nextjsNoVercelOgImport = defineRule({
29276
29422
  //#endregion
29277
29423
  //#region src/plugin/rules/a11y/no-access-key.ts
29278
29424
  const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
29279
- const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
29425
+ const isUndefinedIdentifier$1 = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
29280
29426
  const noAccessKey = defineRule({
29281
29427
  id: "no-access-key",
29282
29428
  title: "accessKey attribute used",
@@ -29301,7 +29447,7 @@ const noAccessKey = defineRule({
29301
29447
  if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
29302
29448
  const expression = attributeValue.expression;
29303
29449
  if (!expression || expression.type === "JSXEmptyExpression") return;
29304
- if (isUndefinedIdentifier(expression)) return;
29450
+ if (isUndefinedIdentifier$1(expression)) return;
29305
29451
  context.report({
29306
29452
  node: accessKey,
29307
29453
  message: MESSAGE$39
@@ -30066,6 +30212,12 @@ const isReactNamespaceImportReference = (ref) => Boolean(ref?.resolved?.defs.som
30066
30212
  const importDeclaration = declarationNode.parent;
30067
30213
  return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
30068
30214
  }));
30215
+ const isReactNamespaceReceiver = (analysis, node) => {
30216
+ const receiver = stripParenExpression(node);
30217
+ if (!isNodeOfType(receiver, "Identifier")) return false;
30218
+ const namespaceReference = getRef(analysis, receiver);
30219
+ return namespaceReference?.resolved ? isReactNamespaceImportReference(namespaceReference) : receiver.name === "React";
30220
+ };
30069
30221
  const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
30070
30222
  if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return false;
30071
30223
  const callee = stripParenExpression(declarator.init.callee);
@@ -30074,24 +30226,20 @@ const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
30074
30226
  if (!reference?.resolved) return callee.name === hookName;
30075
30227
  return isReactNamedImportReference(reference, hookName);
30076
30228
  }
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);
30229
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
30230
+ return isReactNamespaceReceiver(analysis, callee.object);
30081
30231
  };
30082
30232
  const isHookCallee$1 = (analysis, node, hookName) => {
30083
30233
  if (!node) return false;
30084
30234
  if (isNodeOfType(node, "Identifier")) {
30085
30235
  if (node.name === hookName) return true;
30086
30236
  if (isReactNamedImportReference(getRef(analysis, node), 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;
30237
+ const receiverRoot = findTransparentExpressionRoot(node);
30238
+ const parent = receiverRoot.parent;
30239
+ if (parent && isNodeOfType(parent, "MemberExpression") && parent.object === receiverRoot && isReactNamespaceReceiver(analysis, node) && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
30089
30240
  return false;
30090
30241
  }
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
- }
30242
+ if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
30095
30243
  return false;
30096
30244
  };
30097
30245
  const isUseEffect = (node) => {
@@ -30515,7 +30663,88 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
30515
30663
  if (HANDLER_BINDING_NAME_PATTERN.test(bindingName)) return true;
30516
30664
  return isSetterWiredToJsxHandler(componentFunction, bindingName);
30517
30665
  };
30518
- const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
30666
+ const isSynchronousFunction = (functionNode) => {
30667
+ const functionMetadata = functionNode;
30668
+ return functionMetadata.async !== true && functionMetadata.generator !== true;
30669
+ };
30670
+ const findBindingVariable = (analysis, bindingIdentifier) => {
30671
+ for (const scope of analysis.scopeManager.scopes) for (const variable of scope.variables) if (variable.identifiers.includes(bindingIdentifier)) return variable;
30672
+ return null;
30673
+ };
30674
+ const getImmutableFunctionVariable = (analysis, componentFunction, functionNode) => {
30675
+ if (!isSynchronousFunction(functionNode) || !isAstDescendant(functionNode, componentFunction)) return null;
30676
+ const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
30677
+ if (!bindingIdentifier) return null;
30678
+ const variable = findBindingVariable(analysis, bindingIdentifier);
30679
+ if (!variable || variable.defs.length !== 1 || variable.references.some((reference) => reference.isWrite() && !reference.init)) return null;
30680
+ const definition = variable.defs[0];
30681
+ if (definition.type === "FunctionName") return definition.node === functionNode ? variable : null;
30682
+ if (definition.type !== "Variable") return null;
30683
+ const declarator = definition.node;
30684
+ if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.parent, "VariableDeclaration") || declarator.parent.kind !== "const") return null;
30685
+ if (declarator.init === functionNode) return variable;
30686
+ if (isNodeOfType(declarator.init, "CallExpression") && declarator.init.arguments?.[0] === functionNode && isGenuineReactHookDeclarator(analysis, declarator, "useCallback")) return variable;
30687
+ return null;
30688
+ };
30689
+ const getJsxEventValueAttribute = (identifier) => {
30690
+ const expression = findTransparentExpressionRoot(identifier);
30691
+ const expressionContainer = expression.parent;
30692
+ if (!isNodeOfType(expressionContainer, "JSXExpressionContainer") || expressionContainer.expression !== expression) return null;
30693
+ const attribute = expressionContainer.parent;
30694
+ if (!isNodeOfType(attribute, "JSXAttribute")) return null;
30695
+ const attributeName = getJsxAttributeName(attribute.name);
30696
+ return attributeName && isEventHandlerName(attributeName) ? attribute : null;
30697
+ };
30698
+ const getInlineJsxEventCallbackAttribute = (callExpression) => {
30699
+ const callbackFunction = findEnclosingFunction$1(callExpression);
30700
+ if (!callbackFunction || !isSynchronousFunction(callbackFunction)) return null;
30701
+ return getJsxEventValueAttribute(callbackFunction);
30702
+ };
30703
+ const isReactHookDependencyReference = (identifier) => {
30704
+ const expression = findTransparentExpressionRoot(identifier);
30705
+ const dependencyArray = expression.parent;
30706
+ if (!isNodeOfType(dependencyArray, "ArrayExpression") || !(dependencyArray.elements ?? []).includes(expression)) return false;
30707
+ const hookCall = dependencyArray.parent;
30708
+ if (!isNodeOfType(hookCall, "CallExpression") || hookCall.arguments?.[1] !== dependencyArray) return false;
30709
+ const callee = hookCall.callee;
30710
+ if (isNodeOfType(callee, "Identifier")) return /^use[A-Z0-9]/.test(callee.name);
30711
+ return Boolean(isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && /^use[A-Z0-9]/.test(callee.property.name));
30712
+ };
30713
+ const hasReachableJsxEventCallPath = (analysis, context, componentFunction, functionVariable, visitedVariables) => {
30714
+ if (visitedVariables.has(functionVariable)) return false;
30715
+ const nextVisitedVariables = new Set(visitedVariables).add(functionVariable);
30716
+ const callExpressions = [];
30717
+ let hasDirectJsxEventReference = false;
30718
+ for (const reference of functionVariable.references) {
30719
+ if (reference.init) continue;
30720
+ const identifier = reference.identifier;
30721
+ if (reference.isWrite()) return false;
30722
+ const jsxEventValueAttribute = getJsxEventValueAttribute(identifier);
30723
+ if (jsxEventValueAttribute) {
30724
+ if (isNodeReachableWithinFunction(jsxEventValueAttribute, context)) hasDirectJsxEventReference = true;
30725
+ continue;
30726
+ }
30727
+ if (isReactHookDependencyReference(identifier)) continue;
30728
+ const callExpression = getCallExpr(reference);
30729
+ if (!callExpression) return false;
30730
+ const jsxEventCallbackAttribute = getInlineJsxEventCallbackAttribute(callExpression);
30731
+ if (jsxEventCallbackAttribute) {
30732
+ if (isNodeReachableWithinFunction(callExpression, context) && isNodeReachableWithinFunction(jsxEventCallbackAttribute, context)) hasDirectJsxEventReference = true;
30733
+ continue;
30734
+ }
30735
+ callExpressions.push(callExpression);
30736
+ }
30737
+ if (hasDirectJsxEventReference) return true;
30738
+ for (const callExpression of callExpressions) {
30739
+ if (!isNodeReachableWithinFunction(callExpression, context)) continue;
30740
+ const callerFunction = findEnclosingFunction$1(callExpression);
30741
+ if (!callerFunction || callerFunction === componentFunction) continue;
30742
+ const callerVariable = getImmutableFunctionVariable(analysis, componentFunction, callerFunction);
30743
+ if (callerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, callerVariable, nextVisitedVariables)) return true;
30744
+ }
30745
+ return false;
30746
+ };
30747
+ const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, includeDeferredWriters = false) => {
30519
30748
  if (!setterRef.resolved) return false;
30520
30749
  const componentFunction = findEnclosingFunction$1(effectNode);
30521
30750
  if (!componentFunction) return false;
@@ -30524,6 +30753,11 @@ const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters
30524
30753
  const identifier = reference.identifier;
30525
30754
  if (isAstDescendant(identifier, effectNode)) continue;
30526
30755
  if (isIndependentWriterIdentifier(componentFunction, identifier, includeDeferredWriters)) return true;
30756
+ if (!isNodeReachableWithinFunction(identifier, context)) continue;
30757
+ const writerFunction = findEnclosingFunction$1(identifier);
30758
+ if (!writerFunction || writerFunction === componentFunction) continue;
30759
+ const writerVariable = getImmutableFunctionVariable(analysis, componentFunction, writerFunction);
30760
+ if (writerVariable && hasReachableJsxEventCallPath(analysis, context, componentFunction, writerVariable, /* @__PURE__ */ new Set())) return true;
30527
30761
  }
30528
30762
  return false;
30529
30763
  };
@@ -31413,7 +31647,7 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
31413
31647
  }
31414
31648
  return false;
31415
31649
  };
31416
- const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
31650
+ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFilename) => {
31417
31651
  const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
31418
31652
  if (frames.length === 0) return [];
31419
31653
  const effectHasCleanup = hasCleanup(analysis, effectNode);
@@ -31443,7 +31677,7 @@ const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) =>
31443
31677
  for (const returnedExpression of returnedExpressions) mergeEvidence(valueEvidence, collectValueEvidence(analysis, returnedExpression, updaterFrame, remainingValueCallFrames));
31444
31678
  } else valueEvidence = collectValueEvidence(analysis, writtenValue, frame, remainingValueCallFrames);
31445
31679
  const sourceReferences = [...valueEvidence.sourceReferences].filter((sourceReference) => getUseStateDecl(analysis, sourceReference) !== stateDeclarator);
31446
- const hasIndependentWriter = hasUserInputSetterWriter(setterReference, effectNode, true);
31680
+ const hasIndependentWriter = hasUserInputSetterWriter(analysis, context, setterReference, effectNode, true);
31447
31681
  const doesMatchStateInitializer = matchesStateInitializer(analysis, callExpression, stateDeclarator);
31448
31682
  if (effectHasCleanup && (frame.isDeferred || valueEvidence.hasUnknownSource || valueEvidence.hasDeferredIntroducedValue || valueEvidence.readsExternalValue)) cleanupManagedStateDeclarators.add(stateDeclarator);
31449
31683
  const isRenderKnownCopy = sourceReferences.length > 0 && !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue && !hasIndependentWriter;
@@ -31484,7 +31718,7 @@ const noAdjustStateOnPropChange = defineRule({
31484
31718
  const dependencyReferences = getEffectDepsRefs(analysis, node);
31485
31719
  if (!dependencyReferences) return;
31486
31720
  if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
31487
- for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
31721
+ for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
31488
31722
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
31489
31723
  context.report({
31490
31724
  node: fact.callExpression,
@@ -34171,6 +34405,7 @@ const noChainStateUpdates = defineRule({
34171
34405
  id: "no-chain-state-updates",
34172
34406
  title: "State updates chained through effects",
34173
34407
  severity: "warn",
34408
+ disabledWhen: ["react:18"],
34174
34409
  tags: ["test-noise"],
34175
34410
  recommendation: "Set all the related state together in the event handler that starts it, instead of having one useEffect react to a state change and set more state. See https://react.dev/learn/you-might-not-need-an-effect#chains-of-computations",
34176
34411
  create: (context) => ({ CallExpression(node) {
@@ -36057,7 +36292,7 @@ const noDerivedState = defineRule({
36057
36292
  if (!isUseEffect(node)) return;
36058
36293
  const analysis = getProgramAnalysis(node);
36059
36294
  if (!analysis) return;
36060
- for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
36295
+ for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
36061
36296
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
36062
36297
  reportStateWrite(fact.callExpression, fact.stateDeclarator);
36063
36298
  }
@@ -36077,7 +36312,7 @@ const noDerivedStateEffect = defineRule({
36077
36312
  if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
36078
36313
  const analysis = getProgramAnalysis(node);
36079
36314
  if (!analysis) return;
36080
- if (!collectEffectStateWriteFacts(analysis, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
36315
+ if (!collectEffectStateWriteFacts(analysis, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
36081
36316
  context.report({
36082
36317
  node,
36083
36318
  message: "You pay an extra render for state you can derive from other values."
@@ -36553,9 +36788,20 @@ const noDidMountSetState = defineRule({
36553
36788
  }
36554
36789
  });
36555
36790
  //#endregion
36791
+ //#region src/plugin/utils/find-enclosing-class.ts
36792
+ const findEnclosingClass = (node) => {
36793
+ let ancestor = node.parent;
36794
+ while (ancestor) {
36795
+ if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
36796
+ ancestor = ancestor.parent ?? null;
36797
+ }
36798
+ return null;
36799
+ };
36800
+ //#endregion
36556
36801
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
36557
36802
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
36558
36803
  const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
36804
+ const DIFFERENCE_OPERATORS = new Set(["!=", "!=="]);
36559
36805
  const EQUALITY_OPERATORS = new Set([
36560
36806
  "==",
36561
36807
  "===",
@@ -36567,6 +36813,8 @@ const FUNCTION_NODE_TYPES = new Set([
36567
36813
  "FunctionExpression",
36568
36814
  "ArrowFunctionExpression"
36569
36815
  ]);
36816
+ const CLASS_NODE_TYPES = new Set(["ClassDeclaration", "ClassExpression"]);
36817
+ const callbackRefFieldNamesByClass = /* @__PURE__ */ new WeakMap();
36570
36818
  const isLifecycleMethodFunction = (node) => {
36571
36819
  if (!FUNCTION_NODE_TYPES.has(node.type)) return false;
36572
36820
  const parent = node.parent;
@@ -36622,6 +36870,187 @@ const getStaticMemberName = (node) => {
36622
36870
  if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
36623
36871
  return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
36624
36872
  };
36873
+ const getMemberIdentity = (property) => {
36874
+ const propertyName = getPropertyKeyName$2(property);
36875
+ if (propertyName !== void 0) return isNodeOfType(property, "PrivateIdentifier") ? `#${propertyName}` : propertyName;
36876
+ return isNodeOfType(property, "Literal") && typeof property.value === "string" ? property.value : null;
36877
+ };
36878
+ const collectPreviousSourcePaths = (pattern, domain, members, previousSourcePaths) => {
36879
+ if (!pattern) return;
36880
+ const unwrappedPattern = stripParenExpression(pattern);
36881
+ if (isNodeOfType(unwrappedPattern, "Identifier")) {
36882
+ previousSourcePaths.set(unwrappedPattern.name, {
36883
+ domain,
36884
+ members: [...members],
36885
+ source: "previous"
36886
+ });
36887
+ return;
36888
+ }
36889
+ if (isNodeOfType(unwrappedPattern, "AssignmentPattern")) {
36890
+ collectPreviousSourcePaths(unwrappedPattern.left, domain, members, previousSourcePaths);
36891
+ return;
36892
+ }
36893
+ if (!isNodeOfType(unwrappedPattern, "ObjectPattern")) return;
36894
+ for (const property of unwrappedPattern.properties) {
36895
+ if (!isNodeOfType(property, "Property")) continue;
36896
+ const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
36897
+ if (!propertyName) continue;
36898
+ collectPreviousSourcePaths(property.value, domain, [...members, propertyName], previousSourcePaths);
36899
+ }
36900
+ };
36901
+ const getStateSourcePath = (node, previousSourcePaths) => {
36902
+ let currentNode = stripParenExpression(node);
36903
+ const members = [];
36904
+ while (isNodeOfType(currentNode, "MemberExpression")) {
36905
+ const memberName = getStaticMemberName(currentNode);
36906
+ if (!memberName) return null;
36907
+ members.unshift(memberName);
36908
+ currentNode = stripParenExpression(currentNode.object);
36909
+ }
36910
+ if (isNodeOfType(currentNode, "ThisExpression")) {
36911
+ const [domain, ...pathMembers] = members;
36912
+ if (domain !== "props" && domain !== "state") return null;
36913
+ return {
36914
+ domain,
36915
+ members: pathMembers,
36916
+ source: "current"
36917
+ };
36918
+ }
36919
+ if (!isNodeOfType(currentNode, "Identifier")) return null;
36920
+ const previousSourcePath = previousSourcePaths.get(currentNode.name);
36921
+ return previousSourcePath ? {
36922
+ ...previousSourcePath,
36923
+ members: [...previousSourcePath.members, ...members]
36924
+ } : null;
36925
+ };
36926
+ const haveMatchingStateSourcePaths = (left, right) => left.domain === right.domain && left.members.length === right.members.length && left.members.every((member, index) => member === right.members[index]);
36927
+ const collectConjunctiveStateSourceComparisons = (test, previousSourcePaths, comparisons) => {
36928
+ const expression = stripParenExpression(test);
36929
+ if (isNodeOfType(expression, "LogicalExpression") && expression.operator === "&&") {
36930
+ collectConjunctiveStateSourceComparisons(expression.left, previousSourcePaths, comparisons);
36931
+ collectConjunctiveStateSourceComparisons(expression.right, previousSourcePaths, comparisons);
36932
+ return;
36933
+ }
36934
+ if (!isNodeOfType(expression, "BinaryExpression") || !EQUALITY_OPERATORS.has(expression.operator)) return;
36935
+ const leftPath = getStateSourcePath(expression.left, previousSourcePaths);
36936
+ const rightPath = getStateSourcePath(expression.right, previousSourcePaths);
36937
+ if (Boolean(leftPath) === Boolean(rightPath)) return;
36938
+ const path = leftPath ?? rightPath;
36939
+ if (!path) return;
36940
+ comparisons.push({
36941
+ comparedValue: leftPath ? expression.right : expression.left,
36942
+ isDifference: DIFFERENCE_OPERATORS.has(expression.operator),
36943
+ path
36944
+ });
36945
+ };
36946
+ const isHistoricalToCurrentTransitionGuard = (test, previousSourcePaths) => {
36947
+ const expression = stripParenExpression(test);
36948
+ if (isNodeOfType(expression, "LogicalExpression") && expression.operator === "||") return isHistoricalToCurrentTransitionGuard(expression.left, previousSourcePaths) && isHistoricalToCurrentTransitionGuard(expression.right, previousSourcePaths);
36949
+ const comparisons = [];
36950
+ collectConjunctiveStateSourceComparisons(expression, previousSourcePaths, comparisons);
36951
+ 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)));
36952
+ };
36953
+ const getThisFieldName = (node) => {
36954
+ const unwrappedNode = stripParenExpression(node);
36955
+ if (!isNodeOfType(unwrappedNode, "MemberExpression") || unwrappedNode.computed === true || !isNodeOfType(stripParenExpression(unwrappedNode.object), "ThisExpression")) return null;
36956
+ return getMemberIdentity(unwrappedNode.property);
36957
+ };
36958
+ const isUndefinedIdentifier = (node) => {
36959
+ const unwrappedNode = stripParenExpression(node);
36960
+ return isNodeOfType(unwrappedNode, "Identifier") && unwrappedNode.name === "undefined";
36961
+ };
36962
+ const isDirectRefParameterValue = (node, parameterSymbolId, scopes) => {
36963
+ const unwrappedNode = stripParenExpression(node);
36964
+ if (isNodeOfType(unwrappedNode, "Identifier")) return scopes.symbolFor(unwrappedNode)?.id === parameterSymbolId;
36965
+ if (!isNodeOfType(unwrappedNode, "LogicalExpression") || unwrappedNode.operator !== "??") return false;
36966
+ const left = stripParenExpression(unwrappedNode.left);
36967
+ return isNodeOfType(left, "Identifier") && scopes.symbolFor(left)?.id === parameterSymbolId && isUndefinedIdentifier(unwrappedNode.right);
36968
+ };
36969
+ const getCallbackRefAssignedFields = (callback, scopes) => {
36970
+ const firstParameter = (callback.params ?? [])[0];
36971
+ if (!firstParameter) return /* @__PURE__ */ new Set();
36972
+ const parameterIdentifier = isNodeOfType(firstParameter, "AssignmentPattern") ? firstParameter.left : firstParameter;
36973
+ if (!isNodeOfType(parameterIdentifier, "Identifier")) return /* @__PURE__ */ new Set();
36974
+ const parameterSymbolId = scopes.symbolFor(parameterIdentifier)?.id;
36975
+ if (parameterSymbolId === void 0) return /* @__PURE__ */ new Set();
36976
+ const body = callback.body;
36977
+ if (!body) return /* @__PURE__ */ new Set();
36978
+ const assignedFieldNames = /* @__PURE__ */ new Set();
36979
+ walkAst(body, (node) => {
36980
+ if (node !== body && (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node) || CLASS_NODE_TYPES.has(node.type))) return false;
36981
+ const assignmentTarget = isNodeOfType(node, "AssignmentExpression") && node.left || isNodeOfType(node, "UpdateExpression") && node.argument || isNodeOfType(node, "UnaryExpression") && node.operator === "delete" && node.argument || null;
36982
+ if (!assignmentTarget) return;
36983
+ const fieldName = getThisFieldName(assignmentTarget);
36984
+ if (!fieldName) return;
36985
+ if (isNodeOfType(node, "AssignmentExpression") && node.operator === "=" && isDirectRefParameterValue(node.right, parameterSymbolId, scopes)) {
36986
+ assignedFieldNames.add(fieldName);
36987
+ return;
36988
+ }
36989
+ assignedFieldNames.delete(fieldName);
36990
+ });
36991
+ return assignedFieldNames;
36992
+ };
36993
+ const getClassMemberCallback = (classNode, memberName) => {
36994
+ const classBody = classNode.body?.body ?? [];
36995
+ for (const member of classBody) {
36996
+ if (!isNodeOfType(member, "MethodDefinition") && !isNodeOfType(member, "PropertyDefinition")) continue;
36997
+ if (member.static === true) continue;
36998
+ const key = member.key;
36999
+ if (getMemberIdentity(key) !== memberName) continue;
37000
+ const value = member.value;
37001
+ return value && FUNCTION_NODE_TYPES.has(value.type) ? value : null;
37002
+ }
37003
+ return null;
37004
+ };
37005
+ const collectCallbackRefFieldsFromExpression = (expression, classNode, fieldNames, scopes) => {
37006
+ const unwrappedExpression = stripParenExpression(expression);
37007
+ if (FUNCTION_NODE_TYPES.has(unwrappedExpression.type)) {
37008
+ for (const fieldName of getCallbackRefAssignedFields(unwrappedExpression, scopes)) fieldNames.add(fieldName);
37009
+ return;
37010
+ }
37011
+ const handlerName = getThisFieldName(unwrappedExpression);
37012
+ if (handlerName) {
37013
+ const callback = getClassMemberCallback(classNode, handlerName);
37014
+ if (callback) for (const fieldName of getCallbackRefAssignedFields(callback, scopes)) fieldNames.add(fieldName);
37015
+ return;
37016
+ }
37017
+ if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) {
37018
+ collectCallbackRefFieldsFromExpression(unwrappedExpression.consequent, classNode, fieldNames, scopes);
37019
+ collectCallbackRefFieldsFromExpression(unwrappedExpression.alternate, classNode, fieldNames, scopes);
37020
+ return;
37021
+ }
37022
+ if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
37023
+ if (unwrappedExpression.operator !== "&&") collectCallbackRefFieldsFromExpression(unwrappedExpression.left, classNode, fieldNames, scopes);
37024
+ collectCallbackRefFieldsFromExpression(unwrappedExpression.right, classNode, fieldNames, scopes);
37025
+ }
37026
+ };
37027
+ const getCallbackRefFieldNames = (classNode, scopes) => {
37028
+ if (!classNode) return /* @__PURE__ */ new Set();
37029
+ const cachedFieldNames = callbackRefFieldNamesByClass.get(classNode);
37030
+ if (cachedFieldNames) return cachedFieldNames;
37031
+ const fieldNames = /* @__PURE__ */ new Set();
37032
+ const classBody = classNode.body;
37033
+ if (classBody) walkAst(classBody, (node) => {
37034
+ if (node !== classBody && CLASS_NODE_TYPES.has(node.type)) return false;
37035
+ if (!isNodeOfType(node, "JSXAttribute") || !isNodeOfType(node.name, "JSXIdentifier") || node.name.name !== "ref" || !node.value || !isNodeOfType(node.value, "JSXExpressionContainer") || !node.value.expression) return;
37036
+ collectCallbackRefFieldsFromExpression(node.value.expression, classNode, fieldNames, scopes);
37037
+ });
37038
+ callbackRefFieldNamesByClass.set(classNode, fieldNames);
37039
+ return fieldNames;
37040
+ };
37041
+ const collectLifecycleWrittenFieldNames = (lifecycleFunction) => {
37042
+ const fieldNames = /* @__PURE__ */ new Set();
37043
+ const body = lifecycleFunction.body;
37044
+ if (!body) return fieldNames;
37045
+ walkAst(body, (node) => {
37046
+ if (FUNCTION_NODE_TYPES.has(node.type) && !isImmediatelyInvokedFunction(node)) return false;
37047
+ const target = isNodeOfType(node, "AssignmentExpression") && node.left || isNodeOfType(node, "UpdateExpression") && node.argument || null;
37048
+ if (!target) return;
37049
+ const fieldName = getThisFieldName(target);
37050
+ if (fieldName) fieldNames.add(fieldName);
37051
+ });
37052
+ return fieldNames;
37053
+ };
36625
37054
  const getThisStateFieldName = (node) => {
36626
37055
  const unwrappedNode = stripParenExpression(node);
36627
37056
  if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
@@ -36639,15 +37068,17 @@ const collectLocalInitializers = (lifecycleFunction) => {
36639
37068
  });
36640
37069
  return initializers;
36641
37070
  };
36642
- const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
37071
+ const derivesFromPostMountValue = (node, localInitializers, callbackRefFieldNames, visitedNames = /* @__PURE__ */ new Set()) => {
36643
37072
  if (readsPostMountValue(node)) return true;
37073
+ const fieldName = getThisFieldName(node);
37074
+ if (fieldName && callbackRefFieldNames.has(fieldName)) return true;
36644
37075
  const referencedNames = /* @__PURE__ */ new Set();
36645
37076
  collectReferenceIdentifierNames(node, referencedNames);
36646
37077
  for (const referencedName of referencedNames) {
36647
37078
  if (visitedNames.has(referencedName)) continue;
36648
37079
  const initializer = localInitializers.get(referencedName);
36649
37080
  if (!initializer) continue;
36650
- if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
37081
+ if (derivesFromPostMountValue(initializer, localInitializers, callbackRefFieldNames, new Set([...visitedNames, referencedName]))) return true;
36651
37082
  }
36652
37083
  return false;
36653
37084
  };
@@ -36661,50 +37092,84 @@ const getSetStateFieldValue = (setStateCall, fieldName) => {
36661
37092
  }
36662
37093
  return null;
36663
37094
  };
36664
- const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
36665
- let qualifies = false;
36666
- walkAst(test, (node) => {
36667
- if (qualifies) return false;
36668
- if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
36669
- const leftFieldName = getThisStateFieldName(node.left);
36670
- const rightFieldName = getThisStateFieldName(node.right);
36671
- const fieldName = leftFieldName ?? rightFieldName;
36672
- const comparedValue = leftFieldName ? node.right : node.left;
36673
- if (!fieldName || !leftFieldName && !rightFieldName) return;
36674
- const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
36675
- if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
36676
- if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
36677
- qualifies = true;
36678
- return false;
36679
- });
36680
- return qualifies;
36681
- };
36682
- const isDiffGuardTest = (test, paramNames, derivedNames) => {
36683
- if (referencesAnyName(test, paramNames)) return true;
36684
- let qualifies = false;
36685
- walkAst(test, (node) => {
36686
- if (qualifies) return false;
36687
- if (!isNodeOfType(node, "BinaryExpression")) return;
36688
- if (!EQUALITY_OPERATORS.has(node.operator)) return;
36689
- if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames) && (referencesAnyName(node.left, derivedNames) || referencesAnyName(node.right, derivedNames))) {
36690
- qualifies = true;
36691
- return false;
36692
- }
36693
- });
36694
- return qualifies;
37095
+ const isConvergentPostMountGuard = (test, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch) => {
37096
+ const expression = stripParenExpression(test);
37097
+ if (isNodeOfType(expression, "LogicalExpression")) {
37098
+ if (expression.operator !== "&&" && expression.operator !== "||") return false;
37099
+ const leftIsConvergent = isConvergentPostMountGuard(expression.left, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch);
37100
+ const rightIsConvergent = isConvergentPostMountGuard(expression.right, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch);
37101
+ return isTruthfulBranch && expression.operator === "||" || !isTruthfulBranch && expression.operator === "&&" ? leftIsConvergent && rightIsConvergent : leftIsConvergent || rightIsConvergent;
37102
+ }
37103
+ if (!isNodeOfType(expression, "BinaryExpression") || !(isTruthfulBranch ? DIFFERENCE_OPERATORS.has(expression.operator) : EQUALITY_OPERATORS.has(expression.operator) && !DIFFERENCE_OPERATORS.has(expression.operator))) return false;
37104
+ const leftFieldName = getThisStateFieldName(expression.left);
37105
+ const rightFieldName = getThisStateFieldName(expression.right);
37106
+ const fieldName = leftFieldName ?? rightFieldName;
37107
+ const comparedValue = leftFieldName ? expression.right : expression.left;
37108
+ if (!fieldName) return false;
37109
+ const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
37110
+ if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return false;
37111
+ return isUndefinedIdentifier(comparedValue) || derivesFromPostMountValue(comparedValue, localInitializers, callbackRefFieldNames);
37112
+ };
37113
+ const containsPositiveStateFieldTest = (test, fieldName) => {
37114
+ const unwrappedTest = stripParenExpression(test);
37115
+ if (getThisStateFieldName(unwrappedTest) === fieldName) return true;
37116
+ return isNodeOfType(unwrappedTest, "LogicalExpression") && unwrappedTest.operator === "&&" && (containsPositiveStateFieldTest(unwrappedTest.left, fieldName) || containsPositiveStateFieldTest(unwrappedTest.right, fieldName));
36695
37117
  };
36696
- const isInsideDiffGuard = (setStateCall) => {
37118
+ const isConvergentUndefinedClearGuard = (test, setStateCall) => {
37119
+ if (!isNodeOfType(setStateCall, "CallExpression")) return false;
37120
+ const argument = setStateCall.arguments?.[0];
37121
+ if (!argument || !isNodeOfType(argument, "ObjectExpression")) return false;
37122
+ for (const property of argument.properties ?? []) {
37123
+ if (!isNodeOfType(property, "Property") || property.computed === true || !isUndefinedIdentifier(property.value)) continue;
37124
+ const fieldName = isNodeOfType(property.key, "Identifier") && property.key.name || isNodeOfType(property.key, "Literal") && typeof property.key.value === "string" && property.key.value || null;
37125
+ if (fieldName && containsPositiveStateFieldTest(test, fieldName)) return true;
37126
+ }
37127
+ return false;
37128
+ };
37129
+ const isDiffGuardTest = (test, paramNames, derivedNames, isTruthfulBranch) => {
37130
+ const expression = stripParenExpression(test);
37131
+ if (isNodeOfType(expression, "LogicalExpression")) {
37132
+ if (expression.operator !== "&&" && expression.operator !== "||") return false;
37133
+ const leftIsDiffGuard = isDiffGuardTest(expression.left, paramNames, derivedNames, isTruthfulBranch);
37134
+ const rightIsDiffGuard = isDiffGuardTest(expression.right, paramNames, derivedNames, isTruthfulBranch);
37135
+ return isTruthfulBranch && expression.operator === "||" || !isTruthfulBranch && expression.operator === "&&" ? leftIsDiffGuard && rightIsDiffGuard : leftIsDiffGuard || rightIsDiffGuard;
37136
+ }
37137
+ if (!isNodeOfType(expression, "BinaryExpression") || !(isTruthfulBranch ? DIFFERENCE_OPERATORS.has(expression.operator) : EQUALITY_OPERATORS.has(expression.operator) && !DIFFERENCE_OPERATORS.has(expression.operator))) return false;
37138
+ 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));
37139
+ };
37140
+ const isInsideDiffGuard = (setStateCall, scopes) => {
36697
37141
  const lifecycleFunction = findEnclosingLifecycleFunction(setStateCall);
36698
37142
  if (!lifecycleFunction) return false;
36699
37143
  const paramNames = /* @__PURE__ */ new Set();
36700
- for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
37144
+ const parameters = lifecycleFunction.params ?? [];
37145
+ for (const param of parameters) collectPatternNames(param, paramNames);
37146
+ const previousSourcePaths = /* @__PURE__ */ new Map();
37147
+ const [previousPropsParameter, previousStateParameter] = parameters;
37148
+ collectPreviousSourcePaths(previousPropsParameter, "props", [], previousSourcePaths);
37149
+ collectPreviousSourcePaths(previousStateParameter, "state", [], previousSourcePaths);
36701
37150
  const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
36702
37151
  const localInitializers = collectLocalInitializers(lifecycleFunction);
37152
+ const lifecycleWrittenFieldNames = collectLifecycleWrittenFieldNames(lifecycleFunction);
37153
+ const callbackRefFieldNames = new Set([...getCallbackRefFieldNames(findEnclosingClass(lifecycleFunction), scopes)].filter((fieldName) => !lifecycleWrittenFieldNames.has(fieldName)));
36703
37154
  let child = setStateCall;
36704
37155
  let ancestor = setStateCall.parent;
36705
37156
  while (ancestor && ancestor !== lifecycleFunction) {
36706
- 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;
36707
- if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers))) return true;
37157
+ let guardTest = null;
37158
+ let isTruthfulBranch = true;
37159
+ if (isNodeOfType(ancestor, "IfStatement")) {
37160
+ if (child === ancestor.consequent) guardTest = ancestor.test;
37161
+ else if (child === ancestor.alternate) {
37162
+ guardTest = ancestor.test;
37163
+ isTruthfulBranch = false;
37164
+ }
37165
+ } else if (isNodeOfType(ancestor, "ConditionalExpression")) {
37166
+ if (child === ancestor.consequent) guardTest = ancestor.test;
37167
+ else if (child === ancestor.alternate) {
37168
+ guardTest = ancestor.test;
37169
+ isTruthfulBranch = false;
37170
+ }
37171
+ } else if (isNodeOfType(ancestor, "LogicalExpression") && ancestor.operator === "&&" && child === ancestor.right) guardTest = ancestor.left;
37172
+ if (guardTest && (isDiffGuardTest(guardTest, paramNames, derivedNames, isTruthfulBranch) || isTruthfulBranch && isHistoricalToCurrentTransitionGuard(guardTest, previousSourcePaths) || isConvergentPostMountGuard(guardTest, setStateCall, localInitializers, callbackRefFieldNames, isTruthfulBranch) || isTruthfulBranch && isConvergentUndefinedClearGuard(guardTest, setStateCall))) return true;
36708
37173
  child = ancestor;
36709
37174
  ancestor = ancestor.parent ?? null;
36710
37175
  }
@@ -36726,7 +37191,7 @@ const noDidUpdateSetState = defineRule({
36726
37191
  if (!isNodeOfType(stripParenExpression(node.callee.object), "ThisExpression")) return;
36727
37192
  if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
36728
37193
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
36729
- if (isInsideDiffGuard(node)) return;
37194
+ if (isInsideDiffGuard(node, context.scopes)) return;
36730
37195
  context.report({
36731
37196
  node: node.callee,
36732
37197
  message: MESSAGE$27
@@ -41017,7 +41482,7 @@ const noInitializeState = defineRule({
41017
41482
  if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
41018
41483
  const analysis = getProgramAnalysis(node);
41019
41484
  if (!analysis) return;
41020
- for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
41485
+ for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
41021
41486
  if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
41022
41487
  const stateName = getStateName(fact.stateDeclarator);
41023
41488
  context.report({
@@ -44424,6 +44889,114 @@ const DATA_SINK_METHOD_NAMES = new Set([
44424
44889
  "deserialize"
44425
44890
  ]);
44426
44891
  //#endregion
44892
+ //#region src/plugin/utils/get-transparent-react-callback-wrapper-argument.ts
44893
+ const getTransparentReactCallbackWrapperArgument = (initializer, resultSymbol, scopes) => {
44894
+ const callExpression = stripParenExpression(initializer);
44895
+ if (!isNodeOfType(callExpression, "CallExpression")) return null;
44896
+ const callbackArgument = callExpression.arguments[0];
44897
+ if (!callbackArgument) return null;
44898
+ if (resultSymbol && symbolHasReactUseEffectEventOrigin(resultSymbol, scopes)) return callbackArgument;
44899
+ return isReactApiCall(callExpression, "useCallback", scopes, {
44900
+ allowGlobalReactNamespace: true,
44901
+ allowUnboundBareCalls: true
44902
+ }) ? callbackArgument : null;
44903
+ };
44904
+ //#endregion
44905
+ //#region src/plugin/rules/state-and-effects/utils/resolve-parent-callback-provenance.ts
44906
+ const getDeclarationKind$1 = (declarator) => {
44907
+ const declaration = declarator.parent;
44908
+ return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
44909
+ };
44910
+ const hasMutableBindingWrite$2 = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
44911
+ const mergeRequiredBranches = (leftNames, rightNames) => {
44912
+ if (!leftNames || !rightNames) return null;
44913
+ return new Set([...leftNames, ...rightNames]);
44914
+ };
44915
+ const getPropReferenceName = (analysis, identifier) => {
44916
+ if (!isNodeOfType(identifier, "Identifier")) return null;
44917
+ const reference = getRef(analysis, identifier);
44918
+ if (!reference || !isProp(analysis, reference) || isWholePropsObjectReference(analysis, reference)) return null;
44919
+ const bindingIdentifier = (reference.resolved?.defs.find((definition) => definition.type === "Parameter"))?.name;
44920
+ return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? identifier.name;
44921
+ };
44922
+ const getSingleConstDeclarator = (reference) => {
44923
+ if (!reference.resolved || hasMutableBindingWrite$2(reference)) return null;
44924
+ const declarators = reference.resolved.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
44925
+ if (declarators.length !== 1) return null;
44926
+ const declarator = declarators[0];
44927
+ if (!declarator || getDeclarationKind$1(declarator) !== "const") return null;
44928
+ return declarator;
44929
+ };
44930
+ const resolveParentCallbackPropNames = (analysis, expression, scopes, visitedReferences, allowFunctionForwarder = false) => {
44931
+ const unwrappedExpression = stripParenExpression(expression);
44932
+ if (isFunctionLike$1(unwrappedExpression)) {
44933
+ if (!allowFunctionForwarder || Boolean(unwrappedExpression.async)) return null;
44934
+ const callbackNames = /* @__PURE__ */ new Set();
44935
+ walkInsideStatementBlocks(unwrappedExpression.body, (child) => {
44936
+ if (!isNodeOfType(child, "CallExpression")) return;
44937
+ const resolvedNames = resolveParentCallbackPropNames(analysis, child.callee, scopes, new Set(visitedReferences), false);
44938
+ if (!resolvedNames) return;
44939
+ for (const resolvedName of resolvedNames) callbackNames.add(resolvedName);
44940
+ });
44941
+ return callbackNames.size > 0 ? callbackNames : null;
44942
+ }
44943
+ if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return mergeRequiredBranches(resolveParentCallbackPropNames(analysis, unwrappedExpression.consequent, scopes, new Set(visitedReferences), false), resolveParentCallbackPropNames(analysis, unwrappedExpression.alternate, scopes, new Set(visitedReferences), false));
44944
+ if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return mergeRequiredBranches(resolveParentCallbackPropNames(analysis, unwrappedExpression.left, scopes, new Set(visitedReferences), false), resolveParentCallbackPropNames(analysis, unwrappedExpression.right, scopes, new Set(visitedReferences)));
44945
+ if (isNodeOfType(unwrappedExpression, "Identifier")) {
44946
+ const propName = getPropReferenceName(analysis, unwrappedExpression);
44947
+ if (propName) return new Set([propName]);
44948
+ const reference = getRef(analysis, unwrappedExpression);
44949
+ if (!reference?.resolved || visitedReferences.has(reference.resolved)) return null;
44950
+ const declarator = getSingleConstDeclarator(reference);
44951
+ if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return null;
44952
+ visitedReferences.add(reference.resolved);
44953
+ const wrappedArgument = getTransparentReactCallbackWrapperArgument(declarator.init, scopes.symbolFor(unwrappedExpression), scopes);
44954
+ const allowsFunctionForwarder = Boolean(wrappedArgument && !isReactApiCall(declarator.init, "useCallback", scopes, {
44955
+ allowGlobalReactNamespace: true,
44956
+ allowUnboundBareCalls: true
44957
+ }));
44958
+ return resolveParentCallbackPropNames(analysis, wrappedArgument ?? declarator.init, scopes, visitedReferences, allowsFunctionForwarder);
44959
+ }
44960
+ if (!isNodeOfType(unwrappedExpression, "MemberExpression")) return null;
44961
+ const propertyName = getStaticMemberPropertyName(unwrappedExpression);
44962
+ if (!propertyName) return null;
44963
+ const receiver = stripParenExpression(unwrappedExpression.object);
44964
+ if (!isNodeOfType(receiver, "Identifier")) return null;
44965
+ const receiverReference = getRef(analysis, receiver);
44966
+ if (!receiverReference?.resolved || visitedReferences.has(receiverReference.resolved)) return null;
44967
+ if (isWholePropsObjectReference(analysis, receiverReference)) return new Set([propertyName]);
44968
+ const declarator = getSingleConstDeclarator(receiverReference);
44969
+ if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return null;
44970
+ visitedReferences.add(receiverReference.resolved);
44971
+ const initializer = stripParenExpression(declarator.init);
44972
+ if (propertyName === "current" && isNodeOfType(initializer, "CallExpression")) {
44973
+ if (!isReactApiCall(initializer, "useRef", scopes, {
44974
+ allowGlobalReactNamespace: true,
44975
+ allowUnboundBareCalls: true
44976
+ })) return null;
44977
+ const callbackArgument = initializer.arguments[0];
44978
+ if (!callbackArgument) return null;
44979
+ let callbackNames = resolveParentCallbackPropNames(analysis, callbackArgument, scopes, new Set(visitedReferences), false);
44980
+ if (!callbackNames) return null;
44981
+ for (const candidateReference of receiverReference.resolved.references) {
44982
+ const candidateIdentifier = candidateReference.identifier;
44983
+ const candidateMember = candidateIdentifier.parent;
44984
+ if (!candidateMember || !isNodeOfType(candidateMember, "MemberExpression") || candidateMember.object !== candidateIdentifier || getStaticMemberPropertyName(candidateMember) !== "current") continue;
44985
+ const assignment = candidateMember.parent;
44986
+ if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.left !== candidateMember) continue;
44987
+ if (assignment.operator !== "=") return null;
44988
+ callbackNames = mergeRequiredBranches(callbackNames, resolveParentCallbackPropNames(analysis, assignment.right, scopes, new Set(visitedReferences), false));
44989
+ if (!callbackNames) return null;
44990
+ }
44991
+ return callbackNames;
44992
+ }
44993
+ if (!isNodeOfType(initializer, "ObjectExpression")) return null;
44994
+ const property = initializer.properties.find((candidateProperty) => isNodeOfType(candidateProperty, "Property") && getStaticPropertyKeyName(candidateProperty, { allowComputedString: true }) === propertyName);
44995
+ if (!property || !isNodeOfType(property, "Property")) return null;
44996
+ return resolveParentCallbackPropNames(analysis, property.value, scopes, visitedReferences, false);
44997
+ };
44998
+ const getParentCallbackPropNames = ({ analysis, expression, scopes }) => resolveParentCallbackPropNames(analysis, expression, scopes, /* @__PURE__ */ new Set(), false);
44999
+ //#endregion
44427
45000
  //#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
44428
45001
  const isUseStateIdentifier = (identifier) => {
44429
45002
  if (!isNodeOfType(identifier, "Identifier")) return false;
@@ -44452,14 +45025,18 @@ const FUNCTION_WRAPPER_HOOK_NAMES$1 = new Set([
44452
45025
  "useStableCallback",
44453
45026
  "useCallbackRef"
44454
45027
  ]);
44455
- const getWrapperHookWrappedFunction = (initializer) => {
45028
+ const getWrapperHookWrappedFunction = (initializer, resultSymbol, scopes) => {
44456
45029
  if (!isNodeOfType(initializer, "CallExpression")) return null;
45030
+ const transparentReactArgument = getTransparentReactCallbackWrapperArgument(initializer, resultSymbol, scopes);
45031
+ if (transparentReactArgument) return transparentReactArgument;
44457
45032
  const callee = initializer.callee;
44458
45033
  const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") ? callee.property.name : null;
44459
45034
  if (!calleeName || !FUNCTION_WRAPPER_HOOK_NAMES$1.has(calleeName)) return null;
44460
45035
  const wrapped = initializer.arguments?.[0];
44461
- if (!wrapped || !isFunctionLike$1(wrapped)) return null;
44462
- return wrapped;
45036
+ if (!wrapped) return null;
45037
+ if (calleeName === "useEffectEvent") return null;
45038
+ if (isFunctionLike$1(wrapped)) return wrapped;
45039
+ return null;
44463
45040
  };
44464
45041
  const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
44465
45042
  const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
@@ -44469,16 +45046,29 @@ const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstre
44469
45046
  const innerParent = innerIdentifier.parent;
44470
45047
  return Boolean(innerParent && isNodeOfType(innerParent, "CallExpression") && innerParent.callee === innerIdentifier);
44471
45048
  });
44472
- const isDirectParentCallbackRef = (analysis, ref) => {
45049
+ const isDirectParentCallbackRef = (analysis, ref, scopes) => {
44473
45050
  if (isProp(analysis, ref)) return true;
45051
+ if (hasMutableBindingWrite$1(ref)) {
45052
+ if (!(ref.resolved?.references.filter((candidateReference) => candidateReference.isWrite() && !candidateReference.init) ?? []).every((candidateReference) => {
45053
+ const candidateIdentifier = candidateReference.identifier;
45054
+ const assignment = candidateIdentifier.parent;
45055
+ if (!assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== candidateIdentifier) return false;
45056
+ const assignedReferences = getDownstreamRefs(analysis, assignment.right);
45057
+ return assignedReferences.length > 0 && assignedReferences.every((assignedReference) => isProp(analysis, assignedReference));
45058
+ })) return false;
45059
+ }
44474
45060
  return Boolean(ref.resolved?.defs.some((def) => {
44475
45061
  const node = def.node;
44476
45062
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
44477
45063
  const initializer = unwrapChainExpression(node.init);
44478
- const wrappedFunction = getWrapperHookWrappedFunction(initializer);
45064
+ const wrappedFunction = getWrapperHookWrappedFunction(initializer, isNodeOfType(node.id, "Identifier") ? scopes.symbolFor(node.id) ?? null : null, scopes);
44479
45065
  if (wrappedFunction) {
44480
45066
  if (wrappedFunction.async) return false;
44481
- return wrappedFunctionNotifiesParent(analysis, wrappedFunction);
45067
+ if (isFunctionLike$1(wrappedFunction)) return wrappedFunctionNotifiesParent(analysis, wrappedFunction);
45068
+ const directName = getParentCallbackPropName(analysis, wrappedFunction);
45069
+ const downstreamReferences = getDownstreamRefs(analysis, wrappedFunction);
45070
+ if (directName !== null) return true;
45071
+ return downstreamReferences.some((wrappedReference) => !hasMutableBindingWrite$1(wrappedReference) && getUpstreamRefs(analysis, wrappedReference).some((upstreamReference) => isProp(analysis, upstreamReference)));
44482
45072
  }
44483
45073
  if (!isNodeOfType(initializer, "Identifier") && !isNodeOfType(initializer, "MemberExpression")) return false;
44484
45074
  return getDownstreamRefs(analysis, initializer).some((initializerRef) => getUpstreamRefs(analysis, initializerRef).some((upstreamRef) => isProp(analysis, upstreamRef)));
@@ -44488,7 +45078,7 @@ const getDeclarationKind = (declarator) => {
44488
45078
  const declaration = declarator.parent;
44489
45079
  return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
44490
45080
  };
44491
- const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
45081
+ const hasMutableBindingWrite$1 = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
44492
45082
  const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
44493
45083
  const unwrappedExpression = stripParenExpression(expression);
44494
45084
  if (isNodeOfType(unwrappedExpression, "Identifier")) {
@@ -44500,7 +45090,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
44500
45090
  const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
44501
45091
  return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
44502
45092
  }
44503
- if (hasMutableBindingWrite(callbackReference)) return null;
45093
+ if (hasMutableBindingWrite$1(callbackReference)) return null;
44504
45094
  const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
44505
45095
  if (definitions.length !== 1) return null;
44506
45096
  const declarator = definitions[0];
@@ -44566,7 +45156,7 @@ const getRefAliasDeclarator = (identifier) => {
44566
45156
  const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
44567
45157
  if (!isNodeOfType(receiver, "Identifier")) return null;
44568
45158
  const receiverReference = getRef(analysis, receiver);
44569
- if (!receiverReference?.resolved || hasMutableBindingWrite(receiverReference)) return null;
45159
+ if (!receiverReference?.resolved || hasMutableBindingWrite$1(receiverReference)) return null;
44570
45160
  const variables = /* @__PURE__ */ new Set();
44571
45161
  let currentVariable = receiverReference.resolved;
44572
45162
  let refCall = null;
@@ -44582,7 +45172,7 @@ const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
44582
45172
  }
44583
45173
  if (getDeclarationKind(declarator) !== "const" || !isNodeOfType(stripParenExpression(declarator.init), "Identifier")) return null;
44584
45174
  const upstreamReference = getRef(analysis, stripParenExpression(declarator.init));
44585
- if (!upstreamReference?.resolved || hasMutableBindingWrite(upstreamReference)) return null;
45175
+ if (!upstreamReference?.resolved || hasMutableBindingWrite$1(upstreamReference)) return null;
44586
45176
  currentVariable = upstreamReference.resolved;
44587
45177
  }
44588
45178
  if (!refCall) return null;
@@ -44657,7 +45247,7 @@ const isParentPropsContextMerge = (analysis, expression) => {
44657
45247
  while (isNodeOfType(currentExpression, "Identifier")) {
44658
45248
  const currentReference = getRef(analysis, currentExpression);
44659
45249
  const currentVariable = currentReference?.resolved;
44660
- if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return false;
45250
+ if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite$1(currentReference)) return false;
44661
45251
  visitedVariables.add(currentVariable);
44662
45252
  const definitions = currentVariable.defs.filter((definition) => isNodeOfType(definition.node, "VariableDeclarator"));
44663
45253
  if (definitions.length !== 1) return false;
@@ -44671,11 +45261,11 @@ const isParentPropsContextMerge = (analysis, expression) => {
44671
45261
  const propsExpression = stripParenExpression(propsSpread.argument);
44672
45262
  if (!isNodeOfType(propsExpression, "Identifier")) return false;
44673
45263
  const propsReference = getRef(analysis, propsExpression);
44674
- if (!propsReference?.resolved || !isWholePropsObjectReference(analysis, propsReference) || hasMutableBindingWrite(propsReference) || propsReference.resolved.references.some((candidateReference) => candidateReference !== propsReference)) return false;
45264
+ if (!propsReference?.resolved || !isWholePropsObjectReference(analysis, propsReference) || hasMutableBindingWrite$1(propsReference) || propsReference.resolved.references.some((candidateReference) => candidateReference !== propsReference)) return false;
44675
45265
  const contextExpression = stripParenExpression(contextSpread.argument);
44676
45266
  if (!isNodeOfType(contextExpression, "Identifier")) return false;
44677
45267
  const contextReference = getRef(analysis, contextExpression);
44678
- if (!contextReference?.resolved || hasMutableBindingWrite(contextReference) || contextReference.resolved.references.some((candidateReference) => !candidateReference.init && candidateReference !== contextReference)) return false;
45268
+ if (!contextReference?.resolved || hasMutableBindingWrite$1(contextReference) || contextReference.resolved.references.some((candidateReference) => !candidateReference.init && candidateReference !== contextReference)) return false;
44679
45269
  const contextInitializer = contextReference.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
44680
45270
  if (!contextInitializer || !isNodeOfType(contextInitializer, "VariableDeclarator") || getDeclarationKind(contextInitializer) !== "const" || !contextInitializer.init || !isNodeOfType(contextInitializer.init, "CallExpression")) return false;
44681
45271
  const contextHook = stripParenExpression(contextInitializer.init.callee);
@@ -44689,7 +45279,7 @@ const getImmutableParentCallbackPropName = (analysis, expression) => {
44689
45279
  while (isNodeOfType(currentExpression, "Identifier")) {
44690
45280
  const currentReference = getRef(analysis, currentExpression);
44691
45281
  const currentVariable = currentReference?.resolved;
44692
- if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return null;
45282
+ if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite$1(currentReference)) return null;
44693
45283
  visitedVariables.add(currentVariable);
44694
45284
  const definition = currentVariable.defs.length === 1 ? currentVariable.defs[0] : null;
44695
45285
  const bindingIdentifier = definition?.name;
@@ -44758,7 +45348,7 @@ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) =>
44758
45348
  while (isNodeOfType(currentExpression, "Identifier")) {
44759
45349
  const callbackReference = getRef(analysis, currentExpression);
44760
45350
  const callbackVariable = callbackReference?.resolved;
44761
- if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite(callbackReference)) return null;
45351
+ if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite$1(callbackReference)) return null;
44762
45352
  visitedVariables.add(callbackVariable);
44763
45353
  const definition = callbackVariable.defs.length === 1 ? callbackVariable.defs[0] : null;
44764
45354
  const declarator = definition?.node;
@@ -44778,10 +45368,11 @@ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) =>
44778
45368
  if (!propertyName || !COMMAND_PROP_NAME_PATTERN.test(propertyName)) return null;
44779
45369
  return refCurrentObjectPreservesCallbackProperty(analysis, currentExpression.object, propertyName, isReactUseRefCall) ? propertyName : null;
44780
45370
  };
44781
- const isWrapperHookCallbackRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
45371
+ const isWrapperHookCallbackRef = (analysis, ref, scopes) => Boolean(ref.resolved?.defs.some((def) => {
44782
45372
  const node = def.node;
44783
45373
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
44784
- return getWrapperHookWrappedFunction(unwrapChainExpression(node.init)) !== null;
45374
+ const resultSymbol = isNodeOfType(node.id, "Identifier") ? scopes.symbolFor(node.id) ?? null : null;
45375
+ return getWrapperHookWrappedFunction(unwrapChainExpression(node.init), resultSymbol, scopes) !== null;
44785
45376
  }));
44786
45377
  const isHandlerBagArgument = (analysis, argument) => {
44787
45378
  if (!isNodeOfType(argument, "ObjectExpression")) return false;
@@ -44800,14 +45391,25 @@ const isHandlerBagArgument = (analysis, argument) => {
44800
45391
  };
44801
45392
  const getFunctionalUpdaterDataRefs = (analysis, updater) => getDownstreamRefs(analysis, updater).filter((updaterRef) => !updaterRef.resolved?.defs.some((def) => def.type === "Parameter" && def.node === updater));
44802
45393
  const HOOK_NAME_PATTERN$1 = /^use[A-Z0-9]/;
44803
- const EXTERNAL_SUBSCRIPTION_HOOK_NAMES = new Set([
45394
+ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
44804
45395
  "useIntersectionObserver",
44805
45396
  "useMatchMedia",
45397
+ "useMediaJobProgress",
44806
45398
  "useMediaQuery",
44807
45399
  "useResizeObserver",
44808
45400
  "useVisibility",
44809
45401
  "useWindowSize"
44810
45402
  ]);
45403
+ const isCallbackPropReference = (analysis, ref) => {
45404
+ if (!isProp(analysis, ref)) return false;
45405
+ const identifier = ref.identifier;
45406
+ if (!isNodeOfType(identifier, "Identifier")) return false;
45407
+ if (!isWholePropsObjectReference(analysis, ref)) return HANDLER_NAMED_PROP_PATTERN.test(identifier.name);
45408
+ const member = identifier.parent;
45409
+ if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier) return false;
45410
+ const propertyName = getStaticMemberPropertyName(member);
45411
+ return Boolean(propertyName && HANDLER_NAMED_PROP_PATTERN.test(propertyName));
45412
+ };
44811
45413
  const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
44812
45414
  const node = def.node;
44813
45415
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
@@ -44815,7 +45417,7 @@ const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs
44815
45417
  if (!isNodeOfType(init, "CallExpression")) return false;
44816
45418
  const callee = init.callee;
44817
45419
  if (!isNodeOfType(callee, "Identifier") || !HOOK_NAME_PATTERN$1.test(callee.name)) return false;
44818
- return (init.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isProp(analysis, downstreamRef)));
45420
+ return (init.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
44819
45421
  }));
44820
45422
  const isParentWiredHookResultArgument = (analysis, argument) => {
44821
45423
  if (!isNodeOfType(argument, "Identifier")) return false;
@@ -44828,19 +45430,19 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
44828
45430
  if (!isNodeOfType(identifier, "Identifier") || !HOOK_NAME_PATTERN$1.test(identifier.name)) return false;
44829
45431
  const parent = identifier.parent;
44830
45432
  if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
44831
- return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isProp(analysis, downstreamRef)));
45433
+ return (parent.arguments ?? []).some((hookArgument) => getDownstreamRefs(analysis, hookArgument).some((downstreamRef) => isCallbackPropReference(analysis, downstreamRef)));
44832
45434
  };
44833
45435
  const isExternalSubscriptionHookRef = (ref) => {
44834
45436
  const identifier = ref.identifier;
44835
45437
  if (!isNodeOfType(identifier, "Identifier")) return false;
44836
- if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(identifier.name) && isCalleePosition(identifier)) return true;
45438
+ if (EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(identifier.name) && isCalleePosition(identifier)) return true;
44837
45439
  return Boolean(ref.resolved?.defs.some((def) => {
44838
45440
  const node = def.node;
44839
45441
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
44840
45442
  const initializer = stripParenExpression(node.init);
44841
45443
  if (!isNodeOfType(initializer, "CallExpression")) return false;
44842
45444
  const callee = stripParenExpression(initializer.callee);
44843
- return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(callee.name);
45445
+ return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name);
44844
45446
  }));
44845
45447
  };
44846
45448
  const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
@@ -44876,16 +45478,22 @@ const noPassDataToParent = defineRule({
44876
45478
  const callExpr = getCallExpr(ref);
44877
45479
  if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
44878
45480
  const callbackRefProvenance = getCallbackRefProvenance(analysis, node, callExpr, isReactUseRefCall, isReactUseEffectCall);
44879
- if (isRefCall(analysis, ref) && !callbackRefProvenance) continue;
44880
45481
  if (!isSynchronous(ref.identifier, effectFn)) continue;
44881
45482
  const calleeNode = unwrapChainExpression(callExpr.callee);
44882
45483
  const identifier = ref.identifier;
44883
- if (callbackRefProvenance) {
44884
- if ([...callbackRefProvenance.callbackPropNames].some((callbackPropName) => COMMAND_PROP_NAME_PATTERN.test(callbackPropName))) continue;
45484
+ const resolvedCallbackPropNames = isNodeOfType(calleeNode, "MemberExpression") && getStaticMemberPropertyName(calleeNode) === "current" ? null : getParentCallbackPropNames({
45485
+ analysis,
45486
+ expression: calleeNode,
45487
+ scopes: context.scopes
45488
+ });
45489
+ const callbackPropNames = callbackRefProvenance?.callbackPropNames ?? resolvedCallbackPropNames;
45490
+ if (isRefCall(analysis, ref) && !callbackPropNames) continue;
45491
+ if (callbackPropNames) {
45492
+ if ([...callbackPropNames].some((callbackPropName) => COMMAND_PROP_NAME_PATTERN.test(callbackPropName))) continue;
44885
45493
  } else if (calleeNode === identifier) {
44886
45494
  const callbackPropName = getCommandCallbackPropName(analysis, identifier, isReactUseRefCall);
44887
45495
  if (callbackPropName && COMMAND_PROP_NAME_PATTERN.test(callbackPropName)) continue;
44888
- if (!isDirectParentCallbackRef(analysis, ref)) continue;
45496
+ if (!isDirectParentCallbackRef(analysis, ref, context.scopes)) continue;
44889
45497
  if (isNodeOfType(identifier, "Identifier") && COMMAND_PROP_NAME_PATTERN.test(identifier.name)) continue;
44890
45498
  } else if (isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === identifier) {
44891
45499
  if (!isWholePropsObjectReference(analysis, ref)) continue;
@@ -44893,10 +45501,10 @@ const noPassDataToParent = defineRule({
44893
45501
  } else continue;
44894
45502
  const methodName = getCallMethodName(calleeNode);
44895
45503
  const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
44896
- if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
45504
+ if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead && !callbackPropNames) continue;
44897
45505
  if (methodName && COMMAND_PROP_NAME_PATTERN.test(methodName)) continue;
44898
- if (!callbackRefProvenance && isNamespacedApiCallee(calleeNode)) continue;
44899
- 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) ?? ""));
45506
+ if (!callbackPropNames && isNamespacedApiCallee(calleeNode)) continue;
45507
+ 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) ?? ""));
44900
45508
  const isLeafRef = (argRef) => getUpstreamRefs(analysis, argRef).length === 1;
44901
45509
  const argsUpstreamRefs = (callExpr.arguments ?? []).flatMap((argument) => {
44902
45510
  if (isFunctionLike$1(argument)) {
@@ -44911,7 +45519,7 @@ const noPassDataToParent = defineRule({
44911
45519
  }
44912
45520
  return getDownstreamRefs(analysis, argument);
44913
45521
  }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
44914
- if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
45522
+ if (calleeNode === identifier && isWrapperHookCallbackRef(analysis, ref, context.scopes)) argsUpstreamRefs.push(...getArgsUpstreamRefs(analysis, ref).filter(isLeafRef));
44915
45523
  if (!argsUpstreamRefs.some((argRef) => {
44916
45524
  if (isUseStateIdentifier(argRef.identifier)) return false;
44917
45525
  if (isExternalSubscriptionHookRef(argRef)) return false;
@@ -44949,9 +45557,47 @@ const isCallResultConsumedAsArgument = (callExpression) => {
44949
45557
  return false;
44950
45558
  };
44951
45559
  //#endregion
45560
+ //#region src/plugin/rules/state-and-effects/utils/is-custom-hook-state-result-reference.ts
45561
+ const NON_STATE_CUSTOM_HOOK_NAMES = new Set([
45562
+ "useCallbackRef",
45563
+ "useEffectEvent",
45564
+ "useEvent",
45565
+ "useEventCallback",
45566
+ "useLatest",
45567
+ "useMemoizedFn",
45568
+ "useStableCallback"
45569
+ ]);
45570
+ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES = new Set([
45571
+ "useIntersectionObserver",
45572
+ "useMatchMedia",
45573
+ "useMediaJobProgress",
45574
+ "useMediaQuery",
45575
+ "useResizeObserver",
45576
+ "useVisibility",
45577
+ "useWindowSize"
45578
+ ]);
45579
+ const getHookCalleeName = (initializer) => {
45580
+ const unwrappedInitializer = stripParenExpression(initializer);
45581
+ if (!isNodeOfType(unwrappedInitializer, "CallExpression")) return null;
45582
+ const callee = stripParenExpression(unwrappedInitializer.callee);
45583
+ if (isNodeOfType(callee, "Identifier")) return callee.name;
45584
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
45585
+ return null;
45586
+ };
45587
+ const isCustomHookStateResultReference = (analysis, reference) => Boolean(reference.resolved?.defs.some((definition) => {
45588
+ const declarator = definition.node;
45589
+ if (!isNodeOfType(declarator, "VariableDeclarator") || !declarator.init) return false;
45590
+ const calleeName = getHookCalleeName(declarator.init);
45591
+ 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;
45592
+ const initializer = stripParenExpression(declarator.init);
45593
+ if (!isNodeOfType(initializer, "CallExpression")) return false;
45594
+ return initializer.arguments.some((argument) => getDownstreamRefs(analysis, argument).some((argumentReference) => isProp(analysis, argumentReference)));
45595
+ }));
45596
+ //#endregion
44952
45597
  //#region src/plugin/rules/state-and-effects/no-pass-live-state-to-parent.ts
44953
45598
  const SETTER_NAMED_CALLBACK_PATTERN = /^set[A-Z]/;
44954
45599
  const DATA_FETCHING_CALLBACK_PATTERN = /^(fetch|refetch|load|query|request)([A-Z_]|$)/;
45600
+ const hasMutableBindingWrite = (reference) => Boolean(reference.resolved?.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init));
44955
45601
  const getCallCalleeName = (callExpr) => {
44956
45602
  if (!isNodeOfType(callExpr, "CallExpression")) return null;
44957
45603
  const callee = callExpr.callee;
@@ -44996,6 +45642,10 @@ const collectUpstreamStateRefs = (analysis, ref, stateRefs, visited) => {
44996
45642
  stateRefs.push(ref);
44997
45643
  return;
44998
45644
  }
45645
+ if (isCustomHookStateResultReference(analysis, ref)) {
45646
+ stateRefs.push(ref);
45647
+ return;
45648
+ }
44999
45649
  for (const def of ref.resolved?.defs ?? []) {
45000
45650
  if (def.type === "ImportBinding" || def.type === "Parameter") continue;
45001
45651
  const defNode = def.node;
@@ -45025,6 +45675,32 @@ const collectPropCallbackBoundStateRefs = (analysis, ref, isPropCallbackRef) =>
45025
45675
  }
45026
45676
  return stateRefs;
45027
45677
  };
45678
+ const collectDirectCallStateRefs = (analysis, callExpression) => {
45679
+ const stateReferences = [];
45680
+ for (const argument of callExpression.arguments) {
45681
+ if (isFunctionLike$1(argument)) continue;
45682
+ for (const argumentReference of getDownstreamRefs(analysis, argument)) {
45683
+ if (resolveToFunction(argumentReference)) continue;
45684
+ collectUpstreamStateRefs(analysis, argumentReference, stateReferences, /* @__PURE__ */ new Set());
45685
+ }
45686
+ }
45687
+ return stateReferences;
45688
+ };
45689
+ const getTransparentWrapperPropReference = (analysis, reference, context) => {
45690
+ for (const definition of reference.resolved?.defs ?? []) {
45691
+ const declarator = definition.node;
45692
+ if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.id, "Identifier") || !declarator.init) continue;
45693
+ const resultSymbol = context.scopes.symbolFor(declarator.id);
45694
+ const callbackArgument = getTransparentReactCallbackWrapperArgument(declarator.init, resultSymbol, context.scopes);
45695
+ if (!callbackArgument) continue;
45696
+ const callbackReferences = getDownstreamRefs(analysis, callbackArgument);
45697
+ const callbackReference = callbackReferences.find((candidateReference) => isPropCallbackInvocationRef(analysis, candidateReference));
45698
+ if (callbackReference) return callbackReference;
45699
+ const propReference = callbackReferences.find((candidateReference) => isProp(analysis, candidateReference) && !candidateReference.resolved?.references.some((candidateUsage) => candidateUsage.isWrite() && !candidateUsage.init));
45700
+ if (propReference) return propReference;
45701
+ }
45702
+ return null;
45703
+ };
45028
45704
  const isSetterNamedCallbackReceivingData = (callbackRef) => {
45029
45705
  const callExpr = getCallExpr(callbackRef);
45030
45706
  if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) return false;
@@ -45060,6 +45736,16 @@ const resolvesToLocalHookReturnBinding = (ref) => Boolean(ref?.resolved?.defs?.s
45060
45736
  const calleeName = getInitializerCalleeName(node.init);
45061
45737
  return calleeName !== null && isReactHookName(calleeName) && !FUNCTION_WRAPPER_HOOK_NAMES.has(calleeName);
45062
45738
  }));
45739
+ const getDirectLocalEffectHelper = (callExpression, effectFunction, context) => {
45740
+ const helperFunction = resolveExactLocalFunction(callExpression.callee, context.scopes);
45741
+ if (!helperFunction) return null;
45742
+ let ancestor = callExpression.parent;
45743
+ while (ancestor && ancestor !== effectFunction) {
45744
+ if (isFunctionLike$1(ancestor)) return null;
45745
+ ancestor = ancestor.parent;
45746
+ }
45747
+ return ancestor === effectFunction ? helperFunction : null;
45748
+ };
45063
45749
  const noPassLiveStateToParent = defineRule({
45064
45750
  id: "no-pass-live-state-to-parent",
45065
45751
  title: "Live state pushed to parent via effect",
@@ -45074,20 +45760,32 @@ const noPassLiveStateToParent = defineRule({
45074
45760
  if (!effectFnRefs) return;
45075
45761
  const effectFn = getEffectFn(analysis, node);
45076
45762
  if (!effectFn) return;
45763
+ const effectFunctionBody = isNodeOfType(effectFn, "ArrowFunctionExpression") || isNodeOfType(effectFn, "FunctionExpression") || isNodeOfType(effectFn, "FunctionDeclaration") ? effectFn.body : null;
45077
45764
  for (const ref of effectFnRefs) {
45078
- const propCallbackRefs = getEventualCallRefsTo(analysis, ref, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef));
45079
- if (propCallbackRefs.length === 0) continue;
45080
- if (resolvesToLocalHookReturnBinding(ref)) continue;
45081
- if (!isSynchronous(ref.identifier, effectFn)) continue;
45082
45765
  const callExpr = getCallExpr(ref);
45083
- if (!callExpr) continue;
45766
+ if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
45767
+ const directLocalEffectHelper = getDirectLocalEffectHelper(callExpr, effectFn, context);
45768
+ const callGraphReferences = directLocalEffectHelper ? [ref, ...getDownstreamRefs(analysis, directLocalEffectHelper)] : [ref];
45769
+ const resolvedCallbackPropNames = getParentCallbackPropNames({
45770
+ analysis,
45771
+ expression: callExpr.callee,
45772
+ scopes: context.scopes
45773
+ });
45774
+ const callExpressionRoot = findTransparentExpressionRoot(callExpr);
45775
+ 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;
45776
+ if (!notificationCallbackPropNames && hasMutableBindingWrite(ref)) continue;
45777
+ const propCallbackRefs = callGraphReferences.flatMap((callGraphReference) => getEventualCallRefsTo(analysis, callGraphReference, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef)));
45778
+ const transparentPropReference = propCallbackRefs.length === 0 ? getTransparentWrapperPropReference(analysis, ref, context) : null;
45779
+ if (propCallbackRefs.length === 0 && !transparentPropReference && !notificationCallbackPropNames) continue;
45780
+ if (!notificationCallbackPropNames && resolvesToLocalHookReturnBinding(ref)) continue;
45781
+ if (!isSynchronous(ref.identifier, effectFn) && !directLocalEffectHelper) continue;
45084
45782
  if (isCallResultConsumedAsArgument(callExpr)) continue;
45085
45783
  const calleeNode = callExpr.callee;
45086
45784
  const methodName = calleeNode ? getCallMethodName(calleeNode) : null;
45087
45785
  const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && calleeNode && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
45088
- if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
45089
- if (calleeNode && isNamespacedApiCallee(calleeNode)) continue;
45090
- const stateArgRefs = collectPropCallbackBoundStateRefs(analysis, ref, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef));
45786
+ if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead && !notificationCallbackPropNames) continue;
45787
+ if (!notificationCallbackPropNames && calleeNode && isNamespacedApiCallee(calleeNode)) continue;
45788
+ const stateArgRefs = transparentPropReference || notificationCallbackPropNames ? collectDirectCallStateRefs(analysis, callExpr) : callGraphReferences.flatMap((callGraphReference) => collectPropCallbackBoundStateRefs(analysis, callGraphReference, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef)));
45091
45789
  const handsSetterNamedCallbackData = propCallbackRefs.some(isSetterNamedCallbackReceivingData);
45092
45790
  if (stateArgRefs.length === 0 && !handsSetterNamedCallbackData) continue;
45093
45791
  context.report({
@@ -45480,6 +46178,7 @@ const isStateLikeDependency = (analysis, element, isPropName) => {
45480
46178
  if (!analysis) return true;
45481
46179
  const reference = getRef(analysis, element);
45482
46180
  if (!reference) return true;
46181
+ if (isCustomHookStateResultReference(analysis, reference)) return true;
45483
46182
  const upstreamReferences = getUpstreamRefs(analysis, reference);
45484
46183
  if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
45485
46184
  return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
@@ -45496,6 +46195,22 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
45496
46195
  if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
45497
46196
  return isPropName(callbackArgument.name) ? callbackArgument.name : null;
45498
46197
  };
46198
+ const getTransparentWrappedPropCallbackName = (callExpression, context, isPropName) => {
46199
+ const callee = stripParenExpression(callExpression.callee);
46200
+ if (!isNodeOfType(callee, "Identifier")) return null;
46201
+ const binding = findVariableInitializer(callExpression, callee.name);
46202
+ if (!binding?.initializer) return null;
46203
+ const resultSymbol = context.scopes.symbolFor(callee);
46204
+ const callbackArgument = getTransparentReactCallbackWrapperArgument(binding.initializer, resultSymbol, context.scopes);
46205
+ if (!callbackArgument) return null;
46206
+ const callbackSource = stripParenExpression(callbackArgument);
46207
+ if (isNodeOfType(callbackSource, "Identifier")) return isPropName(callbackSource.name, callbackSource) ? callbackSource.name : null;
46208
+ if (!isNodeOfType(callbackSource, "MemberExpression")) return null;
46209
+ const receiver = stripParenExpression(callbackSource.object);
46210
+ const propertyName = getStaticPropertyName(callbackSource);
46211
+ if (!isNodeOfType(receiver, "Identifier") || !propertyName) return null;
46212
+ return isPropName(receiver.name, receiver) ? propertyName : null;
46213
+ };
45499
46214
  const noPropCallbackInEffect = defineRule({
45500
46215
  id: "no-prop-callback-in-effect",
45501
46216
  title: "Parent kept in sync with a callback effect",
@@ -45529,9 +46244,16 @@ const noPropCallbackInEffect = defineRule({
45529
46244
  walkInsideStatementBlocks(callback.body, (child) => {
45530
46245
  if (!isNodeOfType(child, "CallExpression")) return;
45531
46246
  const directCallee = stripParenExpression(child.callee);
45532
- const calleeName = isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName);
46247
+ const resolvedCallbackPropNames = analysis && propStackTracker.getCurrentPropNames().size > 0 ? getParentCallbackPropNames({
46248
+ analysis,
46249
+ expression: directCallee,
46250
+ scopes: context.scopes
46251
+ }) : null;
46252
+ const calleeName = resolvedCallbackPropNames && [...resolvedCallbackPropNames][0] || isNodeOfType(directCallee, "Identifier") && propStackTracker.isPropName(directCallee.name) && directCallee.name || getRefHeldPropCallbackName(child, propStackTracker.isPropName) || getTransparentWrappedPropCallbackName(child, context, propStackTracker.isPropName);
45533
46253
  if (!calleeName) return;
45534
- if (!isResultDiscardedCall(child)) return;
46254
+ const callExpressionRoot = findTransparentExpressionRoot(child);
46255
+ const isDirectEffectReturn = isNodeOfType(callExpressionRoot.parent, "ReturnStatement") && callExpressionRoot.parent.parent === callback.body;
46256
+ if (!isResultDiscardedCall(child) && !isDirectEffectReturn) return;
45535
46257
  if (reportedNodes.has(child)) return;
45536
46258
  reportedNodes.add(child);
45537
46259
  context.report({
@@ -53124,12 +53846,6 @@ const isInsideEs6Component$1 = (methodDefinition) => {
53124
53846
  if (!owningClass) return false;
53125
53847
  return isPreactOrReactComponentClass(owningClass);
53126
53848
  };
53127
- const stripThisParameter = (params) => {
53128
- const first = params[0];
53129
- if (!first) return params;
53130
- if (isNodeOfType(first, "Identifier") && first.name === "this") return params.slice(1);
53131
- return params;
53132
- };
53133
53849
  const preactNoRenderArguments = defineRule({
53134
53850
  id: "preact-no-render-arguments",
53135
53851
  title: "render() reads props from arguments",
@@ -56331,8 +57047,39 @@ const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node,
56331
57047
  destructureIndex: 1
56332
57048
  });
56333
57049
  //#endregion
57050
+ //#region src/plugin/utils/unwrap-return-expression.ts
57051
+ const unwrapReturnExpression = (node) => isNodeOfType(node, "ReturnStatement") && node.argument ? node.argument : node;
57052
+ //#endregion
56334
57053
  //#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
56335
57054
  const USE_EFFECT_ONLY = new Set(["useEffect"]);
57055
+ const USE_CALLBACK_ONLY = new Set(["useCallback"]);
57056
+ const USE_STATE_ONLY = new Set(["useState"]);
57057
+ const REACT_API_CALL_OPTIONS = {
57058
+ allowGlobalReactNamespace: true,
57059
+ allowUnboundBareCalls: true,
57060
+ resolveNamedAliases: true
57061
+ };
57062
+ const expressionReadsDerivedSymbol = (context, expression, stateDerivedSymbolIds) => {
57063
+ let readsDerivedSymbol = false;
57064
+ walkAst(expression, (node) => {
57065
+ if (readsDerivedSymbol) return false;
57066
+ if (node !== expression && isFunctionLike$1(node)) return false;
57067
+ if (isNodeOfType(node, "Identifier") && stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) readsDerivedSymbol = true;
57068
+ });
57069
+ return readsDerivedSymbol;
57070
+ };
57071
+ const getStaticObjectPropertyName = (property) => {
57072
+ if (!isNodeOfType(property, "Property") || property.computed || property.method || property.kind !== "init") return null;
57073
+ if (isNodeOfType(property.key, "Identifier")) return property.key.name;
57074
+ if (isNodeOfType(property.key, "Literal") && (typeof property.key.value === "string" || typeof property.key.value === "number")) return String(property.key.value);
57075
+ return null;
57076
+ };
57077
+ const isNonVisibleJsxSpreadProperty = (propertyName) => propertyName === "id" || propertyName.startsWith("aria-") || /^on[A-Z]/.test(propertyName);
57078
+ const isTransparentAssignmentTarget = (identifier) => {
57079
+ const expressionRoot = findTransparentExpressionRoot(identifier);
57080
+ const parent = expressionRoot.parent;
57081
+ return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expressionRoot || isNodeOfType(parent, "UpdateExpression") && parent.argument === expressionRoot || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expressionRoot);
57082
+ };
56336
57083
  const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
56337
57084
  let readsCurrent = false;
56338
57085
  walkAst(argument, (child) => {
@@ -56384,6 +57131,166 @@ const isStateUsedOnlyInIdOrAriaAttributes = (setterCall, setterName) => {
56384
57131
  });
56385
57132
  return referenceCount > 0 && !nonAriaReferenceFound;
56386
57133
  };
57134
+ const isGlobalWindowMember = (context, node, propertyName) => {
57135
+ const member = stripParenExpression(node);
57136
+ if (!isNodeOfType(member, "MemberExpression") || member.computed) return false;
57137
+ const receiver = stripParenExpression(member.object);
57138
+ return isNodeOfType(receiver, "Identifier") && receiver.name === "window" && context.scopes.isGlobalReference(receiver) && isNodeOfType(member.property, "Identifier") && member.property.name === propertyName;
57139
+ };
57140
+ const getDirectWindowWidthSetter = (context, statement) => {
57141
+ const call = unwrapDiscardedExpression(statement);
57142
+ if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 1) return null;
57143
+ if (!isNodeOfType(call.callee, "Identifier") || !isSetterCall(call)) return null;
57144
+ const argument = call.arguments[0];
57145
+ return isGlobalWindowMember(context, argument, "innerWidth") ? call : null;
57146
+ };
57147
+ const getResizeListenerHandler = (context, statement, methodName) => {
57148
+ const call = unwrapDiscardedExpression(statement);
57149
+ if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 2) return null;
57150
+ if (!isGlobalWindowMember(context, call.callee, methodName)) return null;
57151
+ const eventName = call.arguments[0];
57152
+ const handler = call.arguments[1];
57153
+ if (!isNodeOfType(eventName, "Literal") || eventName.value !== "resize") return null;
57154
+ return isNodeOfType(handler, "Identifier") ? handler : null;
57155
+ };
57156
+ const getCleanupResizeHandler = (context, statement) => {
57157
+ if (!isNodeOfType(statement, "ReturnStatement") || !isFunctionLike$1(statement.argument)) return null;
57158
+ const cleanupStatements = getCallbackStatements(statement.argument);
57159
+ if (cleanupStatements.length !== 1) return null;
57160
+ return getResizeListenerHandler(context, unwrapReturnExpression(cleanupStatements[0]), "removeEventListener");
57161
+ };
57162
+ const findExactViewportState = (context, componentFunction, setterCall) => {
57163
+ if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
57164
+ const componentBody = componentFunction.body;
57165
+ if (!isNodeOfType(setterCall.callee, "Identifier")) return null;
57166
+ const setterSymbol = context.scopes.symbolFor(setterCall.callee);
57167
+ if (!setterSymbol || setterSymbol.kind !== "const" || !isNodeOfType(setterSymbol.declarationNode, "VariableDeclarator")) return null;
57168
+ const declarator = setterSymbol.declarationNode;
57169
+ if (!isNodeOfType(declarator.id, "ArrayPattern")) return null;
57170
+ const stateIdentifier = declarator.id.elements?.[0];
57171
+ const setterIdentifier = declarator.id.elements?.[1];
57172
+ 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;
57173
+ const initializer = declarator.init.arguments?.[0];
57174
+ if (!isNodeOfType(initializer, "Literal") || initializer.value !== 0) return null;
57175
+ const stateSymbol = context.scopes.symbolFor(stateIdentifier);
57176
+ if (!stateSymbol) return null;
57177
+ const stateDerivedSymbolIds = new Set([stateSymbol.id]);
57178
+ let didAddDerivedSymbol = true;
57179
+ while (didAddDerivedSymbol) {
57180
+ didAddDerivedSymbol = false;
57181
+ for (const statement of componentBody.body ?? []) {
57182
+ if (!isNodeOfType(statement, "VariableDeclaration")) continue;
57183
+ for (const candidateDeclarator of statement.declarations ?? []) {
57184
+ if (!isNodeOfType(candidateDeclarator.id, "Identifier") || !candidateDeclarator.init) continue;
57185
+ const candidateInitializer = stripParenExpression(candidateDeclarator.init);
57186
+ if (isFunctionLike$1(candidateInitializer) || isNodeOfType(candidateInitializer, "CallExpression") && isReactApiCall(candidateInitializer, USE_CALLBACK_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) continue;
57187
+ if (!expressionReadsDerivedSymbol(context, candidateInitializer, stateDerivedSymbolIds)) continue;
57188
+ const candidateSymbol = context.scopes.symbolFor(candidateDeclarator.id);
57189
+ if (candidateSymbol?.kind === "const" && candidateSymbol.references.every((reference) => reference.flag === "read" && !isTransparentAssignmentTarget(reference.identifier)) && !stateDerivedSymbolIds.has(candidateSymbol.id)) {
57190
+ stateDerivedSymbolIds.add(candidateSymbol.id);
57191
+ didAddDerivedSymbol = true;
57192
+ }
57193
+ }
57194
+ }
57195
+ }
57196
+ const staticSpreadVisibilityBySymbolId = /* @__PURE__ */ new Map();
57197
+ const hasOnlyStaticObjectReferences = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
57198
+ const symbol = context.scopes.symbolFor(identifier);
57199
+ if (!symbol) return false;
57200
+ if (visitedSymbolIds.has(symbol.id)) return true;
57201
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
57202
+ nextVisitedSymbolIds.add(symbol.id);
57203
+ let hasUnknownReference = false;
57204
+ walkAst(componentBody, (node) => {
57205
+ if (hasUnknownReference || !isNodeOfType(node, "Identifier") || context.scopes.symbolFor(node)?.id !== symbol.id || node === symbol.bindingIdentifier) return;
57206
+ const referenceRoot = findTransparentExpressionRoot(node);
57207
+ const parent = referenceRoot.parent;
57208
+ if (isNodeOfType(parent, "JSXSpreadAttribute") && parent.argument === referenceRoot) return;
57209
+ if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier") && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const" && hasOnlyStaticObjectReferences(parent.id, nextVisitedSymbolIds)) return;
57210
+ hasUnknownReference = true;
57211
+ return false;
57212
+ });
57213
+ return !hasUnknownReference;
57214
+ };
57215
+ const classifyStaticSpreadObject = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
57216
+ const symbol = context.scopes.symbolFor(identifier);
57217
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return "unknown";
57218
+ const cachedVisibility = staticSpreadVisibilityBySymbolId.get(symbol.id);
57219
+ if (cachedVisibility) return cachedVisibility;
57220
+ if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || symbol.declarationNode.id !== symbol.bindingIdentifier || !symbol.declarationNode.init) return "unknown";
57221
+ if (!hasOnlyStaticObjectReferences(identifier)) return "unknown";
57222
+ const initializer = stripParenExpression(symbol.declarationNode.init);
57223
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
57224
+ nextVisitedSymbolIds.add(symbol.id);
57225
+ if (isNodeOfType(initializer, "Identifier")) {
57226
+ const visibility = classifyStaticSpreadObject(initializer, nextVisitedSymbolIds);
57227
+ staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
57228
+ return visibility;
57229
+ }
57230
+ if (!isNodeOfType(initializer, "ObjectExpression")) return "unknown";
57231
+ let visibility = "non-visible";
57232
+ for (const property of initializer.properties ?? []) {
57233
+ const propertyName = getStaticObjectPropertyName(property);
57234
+ if (!isNodeOfType(property, "Property") || !propertyName) {
57235
+ visibility = "unknown";
57236
+ break;
57237
+ }
57238
+ if (expressionReadsDerivedSymbol(context, property.value, stateDerivedSymbolIds) && !isNonVisibleJsxSpreadProperty(propertyName)) visibility = "visible";
57239
+ }
57240
+ staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
57241
+ return visibility;
57242
+ };
57243
+ let hasNonAriaReference = false;
57244
+ walkAst(componentBody, (node) => {
57245
+ if (hasNonAriaReference) return false;
57246
+ if (!isNodeOfType(node, "Identifier") || !stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) return;
57247
+ if (findEnclosingFunction$1(node) !== componentFunction) return;
57248
+ const parent = node.parent;
57249
+ if (parent && (isNodeOfType(parent, "MemberExpression") && parent.property === node && !parent.computed || isNodeOfType(parent, "Property") && parent.key === node && !parent.computed)) return;
57250
+ let cursor = parent;
57251
+ while (cursor && cursor !== componentBody) {
57252
+ if (isFunctionLike$1(cursor)) return;
57253
+ if (isNodeOfType(cursor, "JSXSpreadAttribute")) {
57254
+ if (isNodeOfType(node, "Identifier") && classifyStaticSpreadObject(node) === "visible") hasNonAriaReference = true;
57255
+ return;
57256
+ }
57257
+ if (isNodeOfType(cursor, "JSXAttribute")) {
57258
+ if (isEventHandlerAttribute(cursor)) return;
57259
+ if (!isInsideIdOrAriaAttribute(node)) hasNonAriaReference = true;
57260
+ return;
57261
+ }
57262
+ if (isNodeOfType(cursor, "ReturnStatement")) {
57263
+ hasNonAriaReference = true;
57264
+ return;
57265
+ }
57266
+ cursor = cursor.parent;
57267
+ }
57268
+ });
57269
+ return hasNonAriaReference ? stateIdentifier.name : null;
57270
+ };
57271
+ const isExactViewportSubscriptionEffect = (context, effectCall, callback) => {
57272
+ if (!isReactApiCall(effectCall, USE_EFFECT_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return false;
57273
+ if (!isFunctionLike$1(callback) || callback.async || !isNodeOfType(callback.body, "BlockStatement")) return false;
57274
+ const statements = getCallbackStatements(callback);
57275
+ if (statements.length !== 4) return false;
57276
+ const handlerDeclaration = statements[0];
57277
+ if (!isNodeOfType(handlerDeclaration, "VariableDeclaration") || handlerDeclaration.kind !== "const" || handlerDeclaration.declarations?.length !== 1) return false;
57278
+ const handlerDeclarator = handlerDeclaration.declarations[0];
57279
+ if (!isNodeOfType(handlerDeclarator.id, "Identifier") || !isFunctionLike$1(handlerDeclarator.init)) return false;
57280
+ const handlerStatements = getCallbackStatements(handlerDeclarator.init);
57281
+ if (handlerStatements.length !== 1) return false;
57282
+ const handlerSetter = getDirectWindowWidthSetter(context, unwrapReturnExpression(handlerStatements[0]));
57283
+ const subscribedHandler = getResizeListenerHandler(context, statements[1], "addEventListener");
57284
+ const immediateSetter = getDirectWindowWidthSetter(context, statements[2]);
57285
+ const cleanupHandler = getCleanupResizeHandler(context, statements[3]);
57286
+ if (!handlerSetter || !subscribedHandler || !immediateSetter || !cleanupHandler) return false;
57287
+ const handlerSymbol = context.scopes.symbolFor(handlerDeclarator.id);
57288
+ if (!handlerSymbol || context.scopes.symbolFor(subscribedHandler) !== handlerSymbol || context.scopes.symbolFor(cleanupHandler) !== handlerSymbol) return false;
57289
+ if (!isNodeOfType(handlerSetter.callee, "Identifier") || !isNodeOfType(immediateSetter.callee, "Identifier") || context.scopes.symbolFor(handlerSetter.callee) !== context.scopes.symbolFor(immediateSetter.callee)) return false;
57290
+ const componentFunction = findEnclosingFunction$1(effectCall);
57291
+ if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return false;
57292
+ return findExactViewportState(context, componentFunction, immediateSetter) !== null;
57293
+ };
56387
57294
  const renderingHydrationNoFlicker = defineRule({
56388
57295
  id: "rendering-hydration-no-flicker",
56389
57296
  title: "useEffect setState flashes on mount",
@@ -56396,7 +57303,14 @@ const renderingHydrationNoFlicker = defineRule({
56396
57303
  if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
56397
57304
  const callback = getEffectCallback(node);
56398
57305
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
56399
- const bodyStatements = (isNodeOfType(callback.body, "BlockStatement") ? callback.body.body ?? [] : [callback.body]).filter((statement) => !isNoOpStatement(statement));
57306
+ if (isExactViewportSubscriptionEffect(context, node, callback)) {
57307
+ context.report({
57308
+ node,
57309
+ message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
57310
+ });
57311
+ return;
57312
+ }
57313
+ const bodyStatements = getCallbackStatements(callback);
56400
57314
  if (bodyStatements.length !== 1) return;
56401
57315
  const soleStatement = bodyStatements[0];
56402
57316
  if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
@@ -66228,14 +67142,6 @@ const isStateKey = (key) => {
66228
67142
  if (isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value === "state";
66229
67143
  return false;
66230
67144
  };
66231
- const findEnclosingClass = (node) => {
66232
- let ancestor = node.parent;
66233
- while (ancestor) {
66234
- if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
66235
- ancestor = ancestor.parent ?? null;
66236
- }
66237
- return null;
66238
- };
66239
67145
  const isInConstructor = (node) => {
66240
67146
  let ancestor = node.parent;
66241
67147
  while (ancestor) {