oxlint-plugin-react-doctor 0.7.9-dev.b51022f → 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 +875 -109
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7637,6 +7637,7 @@ const areExpressionsStructurallyEqual = (a, b) => {
7637
7637
  if (a.type !== b.type) return false;
7638
7638
  if (isNodeOfType(a, "ThisExpression")) return true;
7639
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;
7640
7641
  if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
7641
7642
  if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
7642
7643
  if (a.computed !== b.computed) return false;
@@ -8958,7 +8959,7 @@ const isReactNamespaceImport = (identifier, scopes) => {
8958
8959
  if (!symbol || !isImportedFromReact(symbol)) return false;
8959
8960
  return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
8960
8961
  };
8961
- const isReactNamespaceReceiver = (receiver, scopes, options) => {
8962
+ const isReactNamespaceReceiver$1 = (receiver, scopes, options) => {
8962
8963
  if (!isNodeOfType(receiver, "Identifier")) return false;
8963
8964
  if (isReactNamespaceImport(receiver, scopes)) return true;
8964
8965
  return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
@@ -8971,7 +8972,7 @@ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) =>
8971
8972
  for (const property of pattern.properties) {
8972
8973
  if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
8973
8974
  const propertyName = getStaticPropertyKeyName(property);
8974
- 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));
8975
8976
  }
8976
8977
  return false;
8977
8978
  };
@@ -8995,7 +8996,7 @@ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds
8995
8996
  return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
8996
8997
  }
8997
8998
  if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
8998
- return isReactNamespaceReceiver(stripParenExpression(callee.object), scopes, options);
8999
+ return isReactNamespaceReceiver$1(stripParenExpression(callee.object), scopes, options);
8999
9000
  };
9000
9001
  //#endregion
9001
9002
  //#region src/plugin/utils/is-proven-browser-api-receiver.ts
@@ -29421,7 +29422,7 @@ const nextjsNoVercelOgImport = defineRule({
29421
29422
  //#endregion
29422
29423
  //#region src/plugin/rules/a11y/no-access-key.ts
29423
29424
  const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
29424
- const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
29425
+ const isUndefinedIdentifier$1 = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
29425
29426
  const noAccessKey = defineRule({
29426
29427
  id: "no-access-key",
29427
29428
  title: "accessKey attribute used",
@@ -29446,7 +29447,7 @@ const noAccessKey = defineRule({
29446
29447
  if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
29447
29448
  const expression = attributeValue.expression;
29448
29449
  if (!expression || expression.type === "JSXEmptyExpression") return;
29449
- if (isUndefinedIdentifier(expression)) return;
29450
+ if (isUndefinedIdentifier$1(expression)) return;
29450
29451
  context.report({
29451
29452
  node: accessKey,
29452
29453
  message: MESSAGE$39
@@ -30211,6 +30212,12 @@ const isReactNamespaceImportReference = (ref) => Boolean(ref?.resolved?.defs.som
30211
30212
  const importDeclaration = declarationNode.parent;
30212
30213
  return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
30213
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
+ };
30214
30221
  const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
30215
30222
  if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return false;
30216
30223
  const callee = stripParenExpression(declarator.init.callee);
@@ -30219,24 +30226,20 @@ const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
30219
30226
  if (!reference?.resolved) return callee.name === hookName;
30220
30227
  return isReactNamedImportReference(reference, hookName);
30221
30228
  }
30222
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
30223
- const namespaceReference = getRef(analysis, callee.object);
30224
- if (!namespaceReference?.resolved) return callee.object.name === "React";
30225
- 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);
30226
30231
  };
30227
30232
  const isHookCallee$1 = (analysis, node, hookName) => {
30228
30233
  if (!node) return false;
30229
30234
  if (isNodeOfType(node, "Identifier")) {
30230
30235
  if (node.name === hookName) return true;
30231
30236
  if (isReactNamedImportReference(getRef(analysis, node), hookName)) return true;
30232
- const parent = node.parent;
30233
- 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;
30234
30240
  return false;
30235
30241
  }
30236
- if (isNodeOfType(node, "MemberExpression")) {
30237
- const receiver = stripParenExpression(node.object);
30238
- return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
30239
- }
30242
+ if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
30240
30243
  return false;
30241
30244
  };
30242
30245
  const isUseEffect = (node) => {
@@ -30660,7 +30663,88 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
30660
30663
  if (HANDLER_BINDING_NAME_PATTERN.test(bindingName)) return true;
30661
30664
  return isSetterWiredToJsxHandler(componentFunction, bindingName);
30662
30665
  };
30663
- 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) => {
30664
30748
  if (!setterRef.resolved) return false;
30665
30749
  const componentFunction = findEnclosingFunction$1(effectNode);
30666
30750
  if (!componentFunction) return false;
@@ -30669,6 +30753,11 @@ const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters
30669
30753
  const identifier = reference.identifier;
30670
30754
  if (isAstDescendant(identifier, effectNode)) continue;
30671
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;
30672
30761
  }
30673
30762
  return false;
30674
30763
  };
@@ -31558,7 +31647,7 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
31558
31647
  }
31559
31648
  return false;
31560
31649
  };
31561
- const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
31650
+ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFilename) => {
31562
31651
  const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
31563
31652
  if (frames.length === 0) return [];
31564
31653
  const effectHasCleanup = hasCleanup(analysis, effectNode);
@@ -31588,7 +31677,7 @@ const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) =>
31588
31677
  for (const returnedExpression of returnedExpressions) mergeEvidence(valueEvidence, collectValueEvidence(analysis, returnedExpression, updaterFrame, remainingValueCallFrames));
31589
31678
  } else valueEvidence = collectValueEvidence(analysis, writtenValue, frame, remainingValueCallFrames);
31590
31679
  const sourceReferences = [...valueEvidence.sourceReferences].filter((sourceReference) => getUseStateDecl(analysis, sourceReference) !== stateDeclarator);
31591
- const hasIndependentWriter = hasUserInputSetterWriter(setterReference, effectNode, true);
31680
+ const hasIndependentWriter = hasUserInputSetterWriter(analysis, context, setterReference, effectNode, true);
31592
31681
  const doesMatchStateInitializer = matchesStateInitializer(analysis, callExpression, stateDeclarator);
31593
31682
  if (effectHasCleanup && (frame.isDeferred || valueEvidence.hasUnknownSource || valueEvidence.hasDeferredIntroducedValue || valueEvidence.readsExternalValue)) cleanupManagedStateDeclarators.add(stateDeclarator);
31594
31683
  const isRenderKnownCopy = sourceReferences.length > 0 && !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue && !hasIndependentWriter;
@@ -31629,7 +31718,7 @@ const noAdjustStateOnPropChange = defineRule({
31629
31718
  const dependencyReferences = getEffectDepsRefs(analysis, node);
31630
31719
  if (!dependencyReferences) return;
31631
31720
  if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
31632
- for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
31721
+ for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
31633
31722
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
31634
31723
  context.report({
31635
31724
  node: fact.callExpression,
@@ -36203,7 +36292,7 @@ const noDerivedState = defineRule({
36203
36292
  if (!isUseEffect(node)) return;
36204
36293
  const analysis = getProgramAnalysis(node);
36205
36294
  if (!analysis) return;
36206
- for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
36295
+ for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
36207
36296
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
36208
36297
  reportStateWrite(fact.callExpression, fact.stateDeclarator);
36209
36298
  }
@@ -36223,7 +36312,7 @@ const noDerivedStateEffect = defineRule({
36223
36312
  if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
36224
36313
  const analysis = getProgramAnalysis(node);
36225
36314
  if (!analysis) return;
36226
- 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;
36227
36316
  context.report({
36228
36317
  node,
36229
36318
  message: "You pay an extra render for state you can derive from other values."
@@ -36699,9 +36788,20 @@ const noDidMountSetState = defineRule({
36699
36788
  }
36700
36789
  });
36701
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
36702
36801
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
36703
36802
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
36704
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(["!=", "!=="]);
36705
36805
  const EQUALITY_OPERATORS = new Set([
36706
36806
  "==",
36707
36807
  "===",
@@ -36713,6 +36813,8 @@ const FUNCTION_NODE_TYPES = new Set([
36713
36813
  "FunctionExpression",
36714
36814
  "ArrowFunctionExpression"
36715
36815
  ]);
36816
+ const CLASS_NODE_TYPES = new Set(["ClassDeclaration", "ClassExpression"]);
36817
+ const callbackRefFieldNamesByClass = /* @__PURE__ */ new WeakMap();
36716
36818
  const isLifecycleMethodFunction = (node) => {
36717
36819
  if (!FUNCTION_NODE_TYPES.has(node.type)) return false;
36718
36820
  const parent = node.parent;
@@ -36768,6 +36870,187 @@ const getStaticMemberName = (node) => {
36768
36870
  if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
36769
36871
  return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
36770
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
+ };
36771
37054
  const getThisStateFieldName = (node) => {
36772
37055
  const unwrappedNode = stripParenExpression(node);
36773
37056
  if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
@@ -36785,15 +37068,17 @@ const collectLocalInitializers = (lifecycleFunction) => {
36785
37068
  });
36786
37069
  return initializers;
36787
37070
  };
36788
- const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
37071
+ const derivesFromPostMountValue = (node, localInitializers, callbackRefFieldNames, visitedNames = /* @__PURE__ */ new Set()) => {
36789
37072
  if (readsPostMountValue(node)) return true;
37073
+ const fieldName = getThisFieldName(node);
37074
+ if (fieldName && callbackRefFieldNames.has(fieldName)) return true;
36790
37075
  const referencedNames = /* @__PURE__ */ new Set();
36791
37076
  collectReferenceIdentifierNames(node, referencedNames);
36792
37077
  for (const referencedName of referencedNames) {
36793
37078
  if (visitedNames.has(referencedName)) continue;
36794
37079
  const initializer = localInitializers.get(referencedName);
36795
37080
  if (!initializer) continue;
36796
- if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
37081
+ if (derivesFromPostMountValue(initializer, localInitializers, callbackRefFieldNames, new Set([...visitedNames, referencedName]))) return true;
36797
37082
  }
36798
37083
  return false;
36799
37084
  };
@@ -36807,50 +37092,84 @@ const getSetStateFieldValue = (setStateCall, fieldName) => {
36807
37092
  }
36808
37093
  return null;
36809
37094
  };
36810
- const isConvergentPostMountGuard = (test, setStateCall, localInitializers) => {
36811
- let qualifies = false;
36812
- walkAst(test, (node) => {
36813
- if (qualifies) return false;
36814
- if (!isNodeOfType(node, "BinaryExpression") || !EQUALITY_OPERATORS.has(node.operator)) return;
36815
- const leftFieldName = getThisStateFieldName(node.left);
36816
- const rightFieldName = getThisStateFieldName(node.right);
36817
- const fieldName = leftFieldName ?? rightFieldName;
36818
- const comparedValue = leftFieldName ? node.right : node.left;
36819
- if (!fieldName || !leftFieldName && !rightFieldName) return;
36820
- const assignedValue = getSetStateFieldValue(setStateCall, fieldName);
36821
- if (!assignedValue || !areExpressionsStructurallyEqual(comparedValue, assignedValue)) return;
36822
- if (!derivesFromPostMountValue(comparedValue, localInitializers)) return;
36823
- qualifies = true;
36824
- return false;
36825
- });
36826
- return qualifies;
36827
- };
36828
- const isDiffGuardTest = (test, paramNames, derivedNames) => {
36829
- if (referencesAnyName(test, paramNames)) return true;
36830
- let qualifies = false;
36831
- walkAst(test, (node) => {
36832
- if (qualifies) return false;
36833
- if (!isNodeOfType(node, "BinaryExpression")) return;
36834
- if (!EQUALITY_OPERATORS.has(node.operator)) return;
36835
- if (isStatefulOperand(node.left, paramNames, derivedNames) && isStatefulOperand(node.right, paramNames, derivedNames) && (referencesAnyName(node.left, derivedNames) || referencesAnyName(node.right, derivedNames))) {
36836
- qualifies = true;
36837
- return false;
36838
- }
36839
- });
36840
- 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));
37117
+ };
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));
36841
37139
  };
36842
- const isInsideDiffGuard = (setStateCall) => {
37140
+ const isInsideDiffGuard = (setStateCall, scopes) => {
36843
37141
  const lifecycleFunction = findEnclosingLifecycleFunction(setStateCall);
36844
37142
  if (!lifecycleFunction) return false;
36845
37143
  const paramNames = /* @__PURE__ */ new Set();
36846
- 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);
36847
37150
  const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
36848
37151
  const localInitializers = collectLocalInitializers(lifecycleFunction);
37152
+ const lifecycleWrittenFieldNames = collectLifecycleWrittenFieldNames(lifecycleFunction);
37153
+ const callbackRefFieldNames = new Set([...getCallbackRefFieldNames(findEnclosingClass(lifecycleFunction), scopes)].filter((fieldName) => !lifecycleWrittenFieldNames.has(fieldName)));
36849
37154
  let child = setStateCall;
36850
37155
  let ancestor = setStateCall.parent;
36851
37156
  while (ancestor && ancestor !== lifecycleFunction) {
36852
- 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;
36853
- 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;
36854
37173
  child = ancestor;
36855
37174
  ancestor = ancestor.parent ?? null;
36856
37175
  }
@@ -36872,7 +37191,7 @@ const noDidUpdateSetState = defineRule({
36872
37191
  if (!isNodeOfType(stripParenExpression(node.callee.object), "ThisExpression")) return;
36873
37192
  if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
36874
37193
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
36875
- if (isInsideDiffGuard(node)) return;
37194
+ if (isInsideDiffGuard(node, context.scopes)) return;
36876
37195
  context.report({
36877
37196
  node: node.callee,
36878
37197
  message: MESSAGE$27
@@ -41163,7 +41482,7 @@ const noInitializeState = defineRule({
41163
41482
  if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
41164
41483
  const analysis = getProgramAnalysis(node);
41165
41484
  if (!analysis) return;
41166
- for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
41485
+ for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
41167
41486
  if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
41168
41487
  const stateName = getStateName(fact.stateDeclarator);
41169
41488
  context.report({
@@ -44570,6 +44889,114 @@ const DATA_SINK_METHOD_NAMES = new Set([
44570
44889
  "deserialize"
44571
44890
  ]);
44572
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
44573
45000
  //#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
44574
45001
  const isUseStateIdentifier = (identifier) => {
44575
45002
  if (!isNodeOfType(identifier, "Identifier")) return false;
@@ -44598,14 +45025,18 @@ const FUNCTION_WRAPPER_HOOK_NAMES$1 = new Set([
44598
45025
  "useStableCallback",
44599
45026
  "useCallbackRef"
44600
45027
  ]);
44601
- const getWrapperHookWrappedFunction = (initializer) => {
45028
+ const getWrapperHookWrappedFunction = (initializer, resultSymbol, scopes) => {
44602
45029
  if (!isNodeOfType(initializer, "CallExpression")) return null;
45030
+ const transparentReactArgument = getTransparentReactCallbackWrapperArgument(initializer, resultSymbol, scopes);
45031
+ if (transparentReactArgument) return transparentReactArgument;
44603
45032
  const callee = initializer.callee;
44604
45033
  const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") ? callee.property.name : null;
44605
45034
  if (!calleeName || !FUNCTION_WRAPPER_HOOK_NAMES$1.has(calleeName)) return null;
44606
45035
  const wrapped = initializer.arguments?.[0];
44607
- if (!wrapped || !isFunctionLike$1(wrapped)) return null;
44608
- return wrapped;
45036
+ if (!wrapped) return null;
45037
+ if (calleeName === "useEffectEvent") return null;
45038
+ if (isFunctionLike$1(wrapped)) return wrapped;
45039
+ return null;
44609
45040
  };
44610
45041
  const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
44611
45042
  const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
@@ -44615,16 +45046,29 @@ const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstre
44615
45046
  const innerParent = innerIdentifier.parent;
44616
45047
  return Boolean(innerParent && isNodeOfType(innerParent, "CallExpression") && innerParent.callee === innerIdentifier);
44617
45048
  });
44618
- const isDirectParentCallbackRef = (analysis, ref) => {
45049
+ const isDirectParentCallbackRef = (analysis, ref, scopes) => {
44619
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
+ }
44620
45060
  return Boolean(ref.resolved?.defs.some((def) => {
44621
45061
  const node = def.node;
44622
45062
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
44623
45063
  const initializer = unwrapChainExpression(node.init);
44624
- const wrappedFunction = getWrapperHookWrappedFunction(initializer);
45064
+ const wrappedFunction = getWrapperHookWrappedFunction(initializer, isNodeOfType(node.id, "Identifier") ? scopes.symbolFor(node.id) ?? null : null, scopes);
44625
45065
  if (wrappedFunction) {
44626
45066
  if (wrappedFunction.async) return false;
44627
- 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)));
44628
45072
  }
44629
45073
  if (!isNodeOfType(initializer, "Identifier") && !isNodeOfType(initializer, "MemberExpression")) return false;
44630
45074
  return getDownstreamRefs(analysis, initializer).some((initializerRef) => getUpstreamRefs(analysis, initializerRef).some((upstreamRef) => isProp(analysis, upstreamRef)));
@@ -44634,7 +45078,7 @@ const getDeclarationKind = (declarator) => {
44634
45078
  const declaration = declarator.parent;
44635
45079
  return declaration && isNodeOfType(declaration, "VariableDeclaration") ? declaration.kind : null;
44636
45080
  };
44637
- 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));
44638
45082
  const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @__PURE__ */ new Set()) => {
44639
45083
  const unwrappedExpression = stripParenExpression(expression);
44640
45084
  if (isNodeOfType(unwrappedExpression, "Identifier")) {
@@ -44646,7 +45090,7 @@ const getParentCallbackPropName = (analysis, expression, visitedVariables = /* @
44646
45090
  const bindingIdentifier = callbackVariable.defs.find((definition) => definition.type === "Parameter")?.name;
44647
45091
  return (bindingIdentifier && getDestructuredBindingPropertyName(bindingIdentifier)) ?? unwrappedExpression.name;
44648
45092
  }
44649
- if (hasMutableBindingWrite(callbackReference)) return null;
45093
+ if (hasMutableBindingWrite$1(callbackReference)) return null;
44650
45094
  const definitions = callbackVariable.defs.map((definition) => definition.node).filter((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
44651
45095
  if (definitions.length !== 1) return null;
44652
45096
  const declarator = definitions[0];
@@ -44712,7 +45156,7 @@ const getRefAliasDeclarator = (identifier) => {
44712
45156
  const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
44713
45157
  if (!isNodeOfType(receiver, "Identifier")) return null;
44714
45158
  const receiverReference = getRef(analysis, receiver);
44715
- if (!receiverReference?.resolved || hasMutableBindingWrite(receiverReference)) return null;
45159
+ if (!receiverReference?.resolved || hasMutableBindingWrite$1(receiverReference)) return null;
44716
45160
  const variables = /* @__PURE__ */ new Set();
44717
45161
  let currentVariable = receiverReference.resolved;
44718
45162
  let refCall = null;
@@ -44728,7 +45172,7 @@ const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
44728
45172
  }
44729
45173
  if (getDeclarationKind(declarator) !== "const" || !isNodeOfType(stripParenExpression(declarator.init), "Identifier")) return null;
44730
45174
  const upstreamReference = getRef(analysis, stripParenExpression(declarator.init));
44731
- if (!upstreamReference?.resolved || hasMutableBindingWrite(upstreamReference)) return null;
45175
+ if (!upstreamReference?.resolved || hasMutableBindingWrite$1(upstreamReference)) return null;
44732
45176
  currentVariable = upstreamReference.resolved;
44733
45177
  }
44734
45178
  if (!refCall) return null;
@@ -44803,7 +45247,7 @@ const isParentPropsContextMerge = (analysis, expression) => {
44803
45247
  while (isNodeOfType(currentExpression, "Identifier")) {
44804
45248
  const currentReference = getRef(analysis, currentExpression);
44805
45249
  const currentVariable = currentReference?.resolved;
44806
- if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return false;
45250
+ if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite$1(currentReference)) return false;
44807
45251
  visitedVariables.add(currentVariable);
44808
45252
  const definitions = currentVariable.defs.filter((definition) => isNodeOfType(definition.node, "VariableDeclarator"));
44809
45253
  if (definitions.length !== 1) return false;
@@ -44817,11 +45261,11 @@ const isParentPropsContextMerge = (analysis, expression) => {
44817
45261
  const propsExpression = stripParenExpression(propsSpread.argument);
44818
45262
  if (!isNodeOfType(propsExpression, "Identifier")) return false;
44819
45263
  const propsReference = getRef(analysis, propsExpression);
44820
- 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;
44821
45265
  const contextExpression = stripParenExpression(contextSpread.argument);
44822
45266
  if (!isNodeOfType(contextExpression, "Identifier")) return false;
44823
45267
  const contextReference = getRef(analysis, contextExpression);
44824
- 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;
44825
45269
  const contextInitializer = contextReference.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
44826
45270
  if (!contextInitializer || !isNodeOfType(contextInitializer, "VariableDeclarator") || getDeclarationKind(contextInitializer) !== "const" || !contextInitializer.init || !isNodeOfType(contextInitializer.init, "CallExpression")) return false;
44827
45271
  const contextHook = stripParenExpression(contextInitializer.init.callee);
@@ -44835,7 +45279,7 @@ const getImmutableParentCallbackPropName = (analysis, expression) => {
44835
45279
  while (isNodeOfType(currentExpression, "Identifier")) {
44836
45280
  const currentReference = getRef(analysis, currentExpression);
44837
45281
  const currentVariable = currentReference?.resolved;
44838
- if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return null;
45282
+ if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite$1(currentReference)) return null;
44839
45283
  visitedVariables.add(currentVariable);
44840
45284
  const definition = currentVariable.defs.length === 1 ? currentVariable.defs[0] : null;
44841
45285
  const bindingIdentifier = definition?.name;
@@ -44904,7 +45348,7 @@ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) =>
44904
45348
  while (isNodeOfType(currentExpression, "Identifier")) {
44905
45349
  const callbackReference = getRef(analysis, currentExpression);
44906
45350
  const callbackVariable = callbackReference?.resolved;
44907
- if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite(callbackReference)) return null;
45351
+ if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite$1(callbackReference)) return null;
44908
45352
  visitedVariables.add(callbackVariable);
44909
45353
  const definition = callbackVariable.defs.length === 1 ? callbackVariable.defs[0] : null;
44910
45354
  const declarator = definition?.node;
@@ -44924,10 +45368,11 @@ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) =>
44924
45368
  if (!propertyName || !COMMAND_PROP_NAME_PATTERN.test(propertyName)) return null;
44925
45369
  return refCurrentObjectPreservesCallbackProperty(analysis, currentExpression.object, propertyName, isReactUseRefCall) ? propertyName : null;
44926
45370
  };
44927
- const isWrapperHookCallbackRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
45371
+ const isWrapperHookCallbackRef = (analysis, ref, scopes) => Boolean(ref.resolved?.defs.some((def) => {
44928
45372
  const node = def.node;
44929
45373
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
44930
- 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;
44931
45376
  }));
44932
45377
  const isHandlerBagArgument = (analysis, argument) => {
44933
45378
  if (!isNodeOfType(argument, "ObjectExpression")) return false;
@@ -44946,14 +45391,25 @@ const isHandlerBagArgument = (analysis, argument) => {
44946
45391
  };
44947
45392
  const getFunctionalUpdaterDataRefs = (analysis, updater) => getDownstreamRefs(analysis, updater).filter((updaterRef) => !updaterRef.resolved?.defs.some((def) => def.type === "Parameter" && def.node === updater));
44948
45393
  const HOOK_NAME_PATTERN$1 = /^use[A-Z0-9]/;
44949
- const EXTERNAL_SUBSCRIPTION_HOOK_NAMES = new Set([
45394
+ const EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1 = new Set([
44950
45395
  "useIntersectionObserver",
44951
45396
  "useMatchMedia",
45397
+ "useMediaJobProgress",
44952
45398
  "useMediaQuery",
44953
45399
  "useResizeObserver",
44954
45400
  "useVisibility",
44955
45401
  "useWindowSize"
44956
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
+ };
44957
45413
  const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
44958
45414
  const node = def.node;
44959
45415
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
@@ -44961,7 +45417,7 @@ const isParentWiredHookResultRef = (analysis, ref) => Boolean(ref.resolved?.defs
44961
45417
  if (!isNodeOfType(init, "CallExpression")) return false;
44962
45418
  const callee = init.callee;
44963
45419
  if (!isNodeOfType(callee, "Identifier") || !HOOK_NAME_PATTERN$1.test(callee.name)) return false;
44964
- 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)));
44965
45421
  }));
44966
45422
  const isParentWiredHookResultArgument = (analysis, argument) => {
44967
45423
  if (!isNodeOfType(argument, "Identifier")) return false;
@@ -44974,19 +45430,19 @@ const isParentWiredHookCalleeRef = (analysis, ref) => {
44974
45430
  if (!isNodeOfType(identifier, "Identifier") || !HOOK_NAME_PATTERN$1.test(identifier.name)) return false;
44975
45431
  const parent = identifier.parent;
44976
45432
  if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier) return false;
44977
- 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)));
44978
45434
  };
44979
45435
  const isExternalSubscriptionHookRef = (ref) => {
44980
45436
  const identifier = ref.identifier;
44981
45437
  if (!isNodeOfType(identifier, "Identifier")) return false;
44982
- 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;
44983
45439
  return Boolean(ref.resolved?.defs.some((def) => {
44984
45440
  const node = def.node;
44985
45441
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
44986
45442
  const initializer = stripParenExpression(node.init);
44987
45443
  if (!isNodeOfType(initializer, "CallExpression")) return false;
44988
45444
  const callee = stripParenExpression(initializer.callee);
44989
- return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES.has(callee.name);
45445
+ return isNodeOfType(callee, "Identifier") && EXTERNAL_SUBSCRIPTION_HOOK_NAMES$1.has(callee.name);
44990
45446
  }));
44991
45447
  };
44992
45448
  const isImportBindingRef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "ImportBinding"));
@@ -45022,16 +45478,22 @@ const noPassDataToParent = defineRule({
45022
45478
  const callExpr = getCallExpr(ref);
45023
45479
  if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
45024
45480
  const callbackRefProvenance = getCallbackRefProvenance(analysis, node, callExpr, isReactUseRefCall, isReactUseEffectCall);
45025
- if (isRefCall(analysis, ref) && !callbackRefProvenance) continue;
45026
45481
  if (!isSynchronous(ref.identifier, effectFn)) continue;
45027
45482
  const calleeNode = unwrapChainExpression(callExpr.callee);
45028
45483
  const identifier = ref.identifier;
45029
- if (callbackRefProvenance) {
45030
- 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;
45031
45493
  } else if (calleeNode === identifier) {
45032
45494
  const callbackPropName = getCommandCallbackPropName(analysis, identifier, isReactUseRefCall);
45033
45495
  if (callbackPropName && COMMAND_PROP_NAME_PATTERN.test(callbackPropName)) continue;
45034
- if (!isDirectParentCallbackRef(analysis, ref)) continue;
45496
+ if (!isDirectParentCallbackRef(analysis, ref, context.scopes)) continue;
45035
45497
  if (isNodeOfType(identifier, "Identifier") && COMMAND_PROP_NAME_PATTERN.test(identifier.name)) continue;
45036
45498
  } else if (isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === identifier) {
45037
45499
  if (!isWholePropsObjectReference(analysis, ref)) continue;
@@ -45039,10 +45501,10 @@ const noPassDataToParent = defineRule({
45039
45501
  } else continue;
45040
45502
  const methodName = getCallMethodName(calleeNode);
45041
45503
  const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
45042
- if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
45504
+ if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead && !callbackPropNames) continue;
45043
45505
  if (methodName && COMMAND_PROP_NAME_PATTERN.test(methodName)) continue;
45044
- if (!callbackRefProvenance && isNamespacedApiCallee(calleeNode)) continue;
45045
- 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) ?? ""));
45046
45508
  const isLeafRef = (argRef) => getUpstreamRefs(analysis, argRef).length === 1;
45047
45509
  const argsUpstreamRefs = (callExpr.arguments ?? []).flatMap((argument) => {
45048
45510
  if (isFunctionLike$1(argument)) {
@@ -45057,7 +45519,7 @@ const noPassDataToParent = defineRule({
45057
45519
  }
45058
45520
  return getDownstreamRefs(analysis, argument);
45059
45521
  }).flatMap((argumentRef) => isExternallyDrivenState(analysis, argumentRef) ? [] : getUpstreamRefs(analysis, argumentRef)).filter(isLeafRef);
45060
- 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));
45061
45523
  if (!argsUpstreamRefs.some((argRef) => {
45062
45524
  if (isUseStateIdentifier(argRef.identifier)) return false;
45063
45525
  if (isExternalSubscriptionHookRef(argRef)) return false;
@@ -45095,9 +45557,47 @@ const isCallResultConsumedAsArgument = (callExpression) => {
45095
45557
  return false;
45096
45558
  };
45097
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
45098
45597
  //#region src/plugin/rules/state-and-effects/no-pass-live-state-to-parent.ts
45099
45598
  const SETTER_NAMED_CALLBACK_PATTERN = /^set[A-Z]/;
45100
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));
45101
45601
  const getCallCalleeName = (callExpr) => {
45102
45602
  if (!isNodeOfType(callExpr, "CallExpression")) return null;
45103
45603
  const callee = callExpr.callee;
@@ -45142,6 +45642,10 @@ const collectUpstreamStateRefs = (analysis, ref, stateRefs, visited) => {
45142
45642
  stateRefs.push(ref);
45143
45643
  return;
45144
45644
  }
45645
+ if (isCustomHookStateResultReference(analysis, ref)) {
45646
+ stateRefs.push(ref);
45647
+ return;
45648
+ }
45145
45649
  for (const def of ref.resolved?.defs ?? []) {
45146
45650
  if (def.type === "ImportBinding" || def.type === "Parameter") continue;
45147
45651
  const defNode = def.node;
@@ -45171,6 +45675,32 @@ const collectPropCallbackBoundStateRefs = (analysis, ref, isPropCallbackRef) =>
45171
45675
  }
45172
45676
  return stateRefs;
45173
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
+ };
45174
45704
  const isSetterNamedCallbackReceivingData = (callbackRef) => {
45175
45705
  const callExpr = getCallExpr(callbackRef);
45176
45706
  if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) return false;
@@ -45206,6 +45736,16 @@ const resolvesToLocalHookReturnBinding = (ref) => Boolean(ref?.resolved?.defs?.s
45206
45736
  const calleeName = getInitializerCalleeName(node.init);
45207
45737
  return calleeName !== null && isReactHookName(calleeName) && !FUNCTION_WRAPPER_HOOK_NAMES.has(calleeName);
45208
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
+ };
45209
45749
  const noPassLiveStateToParent = defineRule({
45210
45750
  id: "no-pass-live-state-to-parent",
45211
45751
  title: "Live state pushed to parent via effect",
@@ -45220,20 +45760,32 @@ const noPassLiveStateToParent = defineRule({
45220
45760
  if (!effectFnRefs) return;
45221
45761
  const effectFn = getEffectFn(analysis, node);
45222
45762
  if (!effectFn) return;
45763
+ const effectFunctionBody = isNodeOfType(effectFn, "ArrowFunctionExpression") || isNodeOfType(effectFn, "FunctionExpression") || isNodeOfType(effectFn, "FunctionDeclaration") ? effectFn.body : null;
45223
45764
  for (const ref of effectFnRefs) {
45224
- const propCallbackRefs = getEventualCallRefsTo(analysis, ref, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef));
45225
- if (propCallbackRefs.length === 0) continue;
45226
- if (resolvesToLocalHookReturnBinding(ref)) continue;
45227
- if (!isSynchronous(ref.identifier, effectFn)) continue;
45228
45765
  const callExpr = getCallExpr(ref);
45229
- 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;
45230
45782
  if (isCallResultConsumedAsArgument(callExpr)) continue;
45231
45783
  const calleeNode = callExpr.callee;
45232
45784
  const methodName = calleeNode ? getCallMethodName(calleeNode) : null;
45233
45785
  const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && calleeNode && isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === ref.identifier && isWholePropsObjectReference(analysis, ref));
45234
- if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
45235
- if (calleeNode && isNamespacedApiCallee(calleeNode)) continue;
45236
- 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)));
45237
45789
  const handsSetterNamedCallbackData = propCallbackRefs.some(isSetterNamedCallbackReceivingData);
45238
45790
  if (stateArgRefs.length === 0 && !handsSetterNamedCallbackData) continue;
45239
45791
  context.report({
@@ -45626,6 +46178,7 @@ const isStateLikeDependency = (analysis, element, isPropName) => {
45626
46178
  if (!analysis) return true;
45627
46179
  const reference = getRef(analysis, element);
45628
46180
  if (!reference) return true;
46181
+ if (isCustomHookStateResultReference(analysis, reference)) return true;
45629
46182
  const upstreamReferences = getUpstreamRefs(analysis, reference);
45630
46183
  if (upstreamReferences.some((upstreamReference) => isState(analysis, upstreamReference))) return true;
45631
46184
  return !upstreamReferences.some((upstreamReference) => isProp(analysis, upstreamReference));
@@ -45642,6 +46195,22 @@ const getRefHeldPropCallbackName = (callExpression, isPropName) => {
45642
46195
  if (!callbackArgument || !isNodeOfType(callbackArgument, "Identifier")) return null;
45643
46196
  return isPropName(callbackArgument.name) ? callbackArgument.name : null;
45644
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
+ };
45645
46214
  const noPropCallbackInEffect = defineRule({
45646
46215
  id: "no-prop-callback-in-effect",
45647
46216
  title: "Parent kept in sync with a callback effect",
@@ -45675,9 +46244,16 @@ const noPropCallbackInEffect = defineRule({
45675
46244
  walkInsideStatementBlocks(callback.body, (child) => {
45676
46245
  if (!isNodeOfType(child, "CallExpression")) return;
45677
46246
  const directCallee = stripParenExpression(child.callee);
45678
- 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);
45679
46253
  if (!calleeName) return;
45680
- 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;
45681
46257
  if (reportedNodes.has(child)) return;
45682
46258
  reportedNodes.add(child);
45683
46259
  context.report({
@@ -56471,8 +57047,39 @@ const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node,
56471
57047
  destructureIndex: 1
56472
57048
  });
56473
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
56474
57053
  //#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
56475
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
+ };
56476
57083
  const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
56477
57084
  let readsCurrent = false;
56478
57085
  walkAst(argument, (child) => {
@@ -56524,6 +57131,166 @@ const isStateUsedOnlyInIdOrAriaAttributes = (setterCall, setterName) => {
56524
57131
  });
56525
57132
  return referenceCount > 0 && !nonAriaReferenceFound;
56526
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
+ };
56527
57294
  const renderingHydrationNoFlicker = defineRule({
56528
57295
  id: "rendering-hydration-no-flicker",
56529
57296
  title: "useEffect setState flashes on mount",
@@ -56536,7 +57303,14 @@ const renderingHydrationNoFlicker = defineRule({
56536
57303
  if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
56537
57304
  const callback = getEffectCallback(node);
56538
57305
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
56539
- 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);
56540
57314
  if (bodyStatements.length !== 1) return;
56541
57315
  const soleStatement = bodyStatements[0];
56542
57316
  if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
@@ -66368,14 +67142,6 @@ const isStateKey = (key) => {
66368
67142
  if (isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value === "state";
66369
67143
  return false;
66370
67144
  };
66371
- const findEnclosingClass = (node) => {
66372
- let ancestor = node.parent;
66373
- while (ancestor) {
66374
- if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
66375
- ancestor = ancestor.parent ?? null;
66376
- }
66377
- return null;
66378
- };
66379
67145
  const isInConstructor = (node) => {
66380
67146
  let ancestor = node.parent;
66381
67147
  while (ancestor) {