oxlint-plugin-react-doctor 0.7.9-dev.0ff57fa → 0.7.9-dev.11388bf

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 +90 -781
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7637,7 +7637,6 @@ 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;
7641
7640
  if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
7642
7641
  if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
7643
7642
  if (a.computed !== b.computed) return false;
@@ -8959,7 +8958,7 @@ const isReactNamespaceImport = (identifier, scopes) => {
8959
8958
  if (!symbol || !isImportedFromReact(symbol)) return false;
8960
8959
  return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
8961
8960
  };
8962
- const isReactNamespaceReceiver$1 = (receiver, scopes, options) => {
8961
+ const isReactNamespaceReceiver = (receiver, scopes, options) => {
8963
8962
  if (!isNodeOfType(receiver, "Identifier")) return false;
8964
8963
  if (isReactNamespaceImport(receiver, scopes)) return true;
8965
8964
  return Boolean(options.allowGlobalReactNamespace && receiver.name === "React" && scopes.isGlobalReference(receiver));
@@ -8972,7 +8971,7 @@ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) =>
8972
8971
  for (const property of pattern.properties) {
8973
8972
  if (!isNodeOfType(property, "Property") || property.value !== symbol.bindingIdentifier) continue;
8974
8973
  const propertyName = getStaticPropertyKeyName(property);
8975
- return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver$1(stripParenExpression(symbol.initializer), scopes, options));
8974
+ return Boolean(propertyName && includesApiName(apiNames, propertyName) && isReactNamespaceReceiver(stripParenExpression(symbol.initializer), scopes, options));
8976
8975
  }
8977
8976
  return false;
8978
8977
  };
@@ -8996,7 +8995,7 @@ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds
8996
8995
  return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
8997
8996
  }
8998
8997
  if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
8999
- return isReactNamespaceReceiver$1(stripParenExpression(callee.object), scopes, options);
8998
+ return isReactNamespaceReceiver(stripParenExpression(callee.object), scopes, options);
9000
8999
  };
9001
9000
  //#endregion
9002
9001
  //#region src/plugin/utils/is-proven-browser-api-receiver.ts
@@ -29422,7 +29421,7 @@ const nextjsNoVercelOgImport = defineRule({
29422
29421
  //#endregion
29423
29422
  //#region src/plugin/rules/a11y/no-access-key.ts
29424
29423
  const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
29425
- const isUndefinedIdentifier$1 = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
29424
+ const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
29426
29425
  const noAccessKey = defineRule({
29427
29426
  id: "no-access-key",
29428
29427
  title: "accessKey attribute used",
@@ -29447,7 +29446,7 @@ const noAccessKey = defineRule({
29447
29446
  if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
29448
29447
  const expression = attributeValue.expression;
29449
29448
  if (!expression || expression.type === "JSXEmptyExpression") return;
29450
- if (isUndefinedIdentifier$1(expression)) return;
29449
+ if (isUndefinedIdentifier(expression)) return;
29451
29450
  context.report({
29452
29451
  node: accessKey,
29453
29452
  message: MESSAGE$39
@@ -30212,12 +30211,6 @@ const isReactNamespaceImportReference = (ref) => Boolean(ref?.resolved?.defs.som
30212
30211
  const importDeclaration = declarationNode.parent;
30213
30212
  return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
30214
30213
  }));
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
- };
30221
30214
  const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
30222
30215
  if (!isNodeOfType(declarator, "VariableDeclarator") || !isNodeOfType(declarator.init, "CallExpression")) return false;
30223
30216
  const callee = stripParenExpression(declarator.init.callee);
@@ -30226,20 +30219,24 @@ const isGenuineReactHookDeclarator = (analysis, declarator, hookName) => {
30226
30219
  if (!reference?.resolved) return callee.name === hookName;
30227
30220
  return isReactNamedImportReference(reference, hookName);
30228
30221
  }
30229
- if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || callee.property.name !== hookName) return false;
30230
- return isReactNamespaceReceiver(analysis, callee.object);
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);
30231
30226
  };
30232
30227
  const isHookCallee$1 = (analysis, node, hookName) => {
30233
30228
  if (!node) return false;
30234
30229
  if (isNodeOfType(node, "Identifier")) {
30235
30230
  if (node.name === hookName) return true;
30236
30231
  if (isReactNamedImportReference(getRef(analysis, node), 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;
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;
30240
30234
  return false;
30241
30235
  }
30242
- if (isNodeOfType(node, "MemberExpression")) return isReactNamespaceReceiver(analysis, node.object) && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
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
+ }
30243
30240
  return false;
30244
30241
  };
30245
30242
  const isUseEffect = (node) => {
@@ -30663,88 +30660,7 @@ const isIndependentWriterIdentifier = (componentFunction, identifier, includeDef
30663
30660
  if (HANDLER_BINDING_NAME_PATTERN.test(bindingName)) return true;
30664
30661
  return isSetterWiredToJsxHandler(componentFunction, bindingName);
30665
30662
  };
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) => {
30663
+ const hasUserInputSetterWriter = (setterRef, effectNode, includeDeferredWriters = false) => {
30748
30664
  if (!setterRef.resolved) return false;
30749
30665
  const componentFunction = findEnclosingFunction$1(effectNode);
30750
30666
  if (!componentFunction) return false;
@@ -30753,11 +30669,6 @@ const hasUserInputSetterWriter = (analysis, context, setterRef, effectNode, incl
30753
30669
  const identifier = reference.identifier;
30754
30670
  if (isAstDescendant(identifier, effectNode)) continue;
30755
30671
  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;
30761
30672
  }
30762
30673
  return false;
30763
30674
  };
@@ -31647,7 +31558,7 @@ const areInMutuallyExclusiveBranches = (leftNode, rightNode) => {
31647
31558
  }
31648
31559
  return false;
31649
31560
  };
31650
- const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFilename) => {
31561
+ const collectEffectStateWriteFacts = (analysis, effectNode, currentFilename) => {
31651
31562
  const frames = collectBoundedEffectExecutionFrames(analysis, effectNode, currentFilename);
31652
31563
  if (frames.length === 0) return [];
31653
31564
  const effectHasCleanup = hasCleanup(analysis, effectNode);
@@ -31677,7 +31588,7 @@ const collectEffectStateWriteFacts = (analysis, context, effectNode, currentFile
31677
31588
  for (const returnedExpression of returnedExpressions) mergeEvidence(valueEvidence, collectValueEvidence(analysis, returnedExpression, updaterFrame, remainingValueCallFrames));
31678
31589
  } else valueEvidence = collectValueEvidence(analysis, writtenValue, frame, remainingValueCallFrames);
31679
31590
  const sourceReferences = [...valueEvidence.sourceReferences].filter((sourceReference) => getUseStateDecl(analysis, sourceReference) !== stateDeclarator);
31680
- const hasIndependentWriter = hasUserInputSetterWriter(analysis, context, setterReference, effectNode, true);
31591
+ const hasIndependentWriter = hasUserInputSetterWriter(setterReference, effectNode, true);
31681
31592
  const doesMatchStateInitializer = matchesStateInitializer(analysis, callExpression, stateDeclarator);
31682
31593
  if (effectHasCleanup && (frame.isDeferred || valueEvidence.hasUnknownSource || valueEvidence.hasDeferredIntroducedValue || valueEvidence.readsExternalValue)) cleanupManagedStateDeclarators.add(stateDeclarator);
31683
31594
  const isRenderKnownCopy = sourceReferences.length > 0 && !frame.isDeferred && !valueEvidence.hasUnknownSource && !valueEvidence.hasDeferredIntroducedValue && !valueEvidence.readsExternalValue && !hasIndependentWriter;
@@ -31718,7 +31629,7 @@ const noAdjustStateOnPropChange = defineRule({
31718
31629
  const dependencyReferences = getEffectDepsRefs(analysis, node);
31719
31630
  if (!dependencyReferences) return;
31720
31631
  if (!dependencyReferences.flatMap((reference) => isState(analysis, reference) ? [] : getUpstreamRefs(analysis, reference)).some((reference) => isProp(analysis, reference))) return;
31721
- for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
31632
+ for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
31722
31633
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
31723
31634
  context.report({
31724
31635
  node: fact.callExpression,
@@ -36292,7 +36203,7 @@ const noDerivedState = defineRule({
36292
36203
  if (!isUseEffect(node)) return;
36293
36204
  const analysis = getProgramAnalysis(node);
36294
36205
  if (!analysis) return;
36295
- for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
36206
+ for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
36296
36207
  if (!fact.isRenderKnownCopy || fact.resetsSourceState) continue;
36297
36208
  reportStateWrite(fact.callExpression, fact.stateDeclarator);
36298
36209
  }
@@ -36312,7 +36223,7 @@ const noDerivedStateEffect = defineRule({
36312
36223
  if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
36313
36224
  const analysis = getProgramAnalysis(node);
36314
36225
  if (!analysis) return;
36315
- if (!collectEffectStateWriteFacts(analysis, context, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
36226
+ if (!collectEffectStateWriteFacts(analysis, node, context.filename).find((fact) => fact.isRenderKnownCopy && !fact.resetsSourceState)) return;
36316
36227
  context.report({
36317
36228
  node,
36318
36229
  message: "You pay an extra render for state you can derive from other values."
@@ -36788,20 +36699,9 @@ const noDidMountSetState = defineRule({
36788
36699
  }
36789
36700
  });
36790
36701
  //#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
36801
36702
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
36802
36703
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
36803
36704
  const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
36804
- const DIFFERENCE_OPERATORS = new Set(["!=", "!=="]);
36805
36705
  const EQUALITY_OPERATORS = new Set([
36806
36706
  "==",
36807
36707
  "===",
@@ -36813,8 +36713,6 @@ const FUNCTION_NODE_TYPES = new Set([
36813
36713
  "FunctionExpression",
36814
36714
  "ArrowFunctionExpression"
36815
36715
  ]);
36816
- const CLASS_NODE_TYPES = new Set(["ClassDeclaration", "ClassExpression"]);
36817
- const callbackRefFieldNamesByClass = /* @__PURE__ */ new WeakMap();
36818
36716
  const isLifecycleMethodFunction = (node) => {
36819
36717
  if (!FUNCTION_NODE_TYPES.has(node.type)) return false;
36820
36718
  const parent = node.parent;
@@ -36870,187 +36768,6 @@ const getStaticMemberName = (node) => {
36870
36768
  if (!isNodeOfType(node, "MemberExpression") || node.computed === true) return null;
36871
36769
  return isNodeOfType(node.property, "Identifier") ? node.property.name : null;
36872
36770
  };
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
- };
37054
36771
  const getThisStateFieldName = (node) => {
37055
36772
  const unwrappedNode = stripParenExpression(node);
37056
36773
  if (!isNodeOfType(unwrappedNode, "MemberExpression")) return null;
@@ -37068,17 +36785,15 @@ const collectLocalInitializers = (lifecycleFunction) => {
37068
36785
  });
37069
36786
  return initializers;
37070
36787
  };
37071
- const derivesFromPostMountValue = (node, localInitializers, callbackRefFieldNames, visitedNames = /* @__PURE__ */ new Set()) => {
36788
+ const derivesFromPostMountValue = (node, localInitializers, visitedNames = /* @__PURE__ */ new Set()) => {
37072
36789
  if (readsPostMountValue(node)) return true;
37073
- const fieldName = getThisFieldName(node);
37074
- if (fieldName && callbackRefFieldNames.has(fieldName)) return true;
37075
36790
  const referencedNames = /* @__PURE__ */ new Set();
37076
36791
  collectReferenceIdentifierNames(node, referencedNames);
37077
36792
  for (const referencedName of referencedNames) {
37078
36793
  if (visitedNames.has(referencedName)) continue;
37079
36794
  const initializer = localInitializers.get(referencedName);
37080
36795
  if (!initializer) continue;
37081
- if (derivesFromPostMountValue(initializer, localInitializers, callbackRefFieldNames, new Set([...visitedNames, referencedName]))) return true;
36796
+ if (derivesFromPostMountValue(initializer, localInitializers, new Set([...visitedNames, referencedName]))) return true;
37082
36797
  }
37083
36798
  return false;
37084
36799
  };
@@ -37092,84 +36807,50 @@ const getSetStateFieldValue = (setStateCall, fieldName) => {
37092
36807
  }
37093
36808
  return null;
37094
36809
  };
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));
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;
37139
36841
  };
37140
- const isInsideDiffGuard = (setStateCall, scopes) => {
36842
+ const isInsideDiffGuard = (setStateCall) => {
37141
36843
  const lifecycleFunction = findEnclosingLifecycleFunction(setStateCall);
37142
36844
  if (!lifecycleFunction) return false;
37143
36845
  const paramNames = /* @__PURE__ */ new Set();
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);
36846
+ for (const param of lifecycleFunction.params ?? []) collectPatternNames(param, paramNames);
37150
36847
  const derivedNames = collectDiffSourceLocalNames(lifecycleFunction, paramNames);
37151
36848
  const localInitializers = collectLocalInitializers(lifecycleFunction);
37152
- const lifecycleWrittenFieldNames = collectLifecycleWrittenFieldNames(lifecycleFunction);
37153
- const callbackRefFieldNames = new Set([...getCallbackRefFieldNames(findEnclosingClass(lifecycleFunction), scopes)].filter((fieldName) => !lifecycleWrittenFieldNames.has(fieldName)));
37154
36849
  let child = setStateCall;
37155
36850
  let ancestor = setStateCall.parent;
37156
36851
  while (ancestor && ancestor !== lifecycleFunction) {
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;
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;
37173
36854
  child = ancestor;
37174
36855
  ancestor = ancestor.parent ?? null;
37175
36856
  }
@@ -37191,7 +36872,7 @@ const noDidUpdateSetState = defineRule({
37191
36872
  if (!isNodeOfType(stripParenExpression(node.callee.object), "ThisExpression")) return;
37192
36873
  if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
37193
36874
  if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
37194
- if (isInsideDiffGuard(node, context.scopes)) return;
36875
+ if (isInsideDiffGuard(node)) return;
37195
36876
  context.report({
37196
36877
  node: node.callee,
37197
36878
  message: MESSAGE$27
@@ -40913,223 +40594,41 @@ const readLogicalConditionResult = (operator, leftResult, rightResult) => {
40913
40594
  if (leftResult === false && rightResult === false) return false;
40914
40595
  return null;
40915
40596
  };
40916
- const readHydrationConditionResult = (expression, context, runtime, state) => {
40597
+ const readHydrationConditionResult = (expression, context, runtime) => {
40917
40598
  const unwrappedExpression = stripParenExpression(expression);
40918
40599
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
40919
40600
  if (predicateMatch) return predicateMatch[`${runtime}Result`];
40920
40601
  const staticResult = readInitialStateBoolean(unwrappedExpression, context.scopes);
40921
40602
  if (staticResult !== null) return staticResult;
40922
- const expressionSymbol = isNodeOfType(unwrappedExpression, "Identifier") ? context.scopes.symbolFor(unwrappedExpression) : null;
40923
- const parameterValue = expressionSymbol ? state.parameterValuesBySymbolId.get(expressionSymbol.id) : null;
40924
- if (expressionSymbol && parameterValue && !state.visitedSymbolIds.has(expressionSymbol.id)) {
40925
- state.visitedSymbolIds.add(expressionSymbol.id);
40926
- const result = readHydrationConditionResult(parameterValue, context, runtime, state);
40927
- state.visitedSymbolIds.delete(expressionSymbol.id);
40928
- return result;
40929
- }
40930
- if (expressionSymbol && expressionSymbol.kind === "const" && expressionSymbol.initializer && expressionSymbol.references.every((reference) => reference.flag === "read") && !state.visitedSymbolIds.has(expressionSymbol.id)) {
40931
- state.visitedSymbolIds.add(expressionSymbol.id);
40932
- const result = readHydrationConditionResult(expressionSymbol.initializer, context, runtime, state);
40933
- state.visitedSymbolIds.delete(expressionSymbol.id);
40934
- return result;
40935
- }
40936
- if (isNodeOfType(unwrappedExpression, "CallExpression")) {
40937
- const callArguments = unwrappedExpression.arguments ?? [];
40938
- if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
40939
- allowGlobalReactNamespace: true,
40940
- resolveNamedAliases: true
40941
- })) {
40942
- const callbackArgument = callArguments[0];
40943
- if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
40944
- const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
40945
- return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? readHydrationFunctionResult(callbackFunction, context, runtime, state) : null;
40946
- }
40947
- const callee = stripParenExpression(unwrappedExpression.callee);
40948
- if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return readHydrationConditionResult(callArguments[0], context, runtime, state);
40949
- const helperFunction = resolveExactLocalFunction(callee, context.scopes);
40950
- if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
40951
- const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
40952
- for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
40953
- const parameter = helperFunction.params[parameterIndex];
40954
- const argument = callArguments[parameterIndex];
40955
- if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
40956
- const parameterSymbol = context.scopes.symbolFor(parameter);
40957
- if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
40958
- }
40959
- return readHydrationFunctionResult(helperFunction, context, runtime, {
40960
- ...state,
40961
- parameterValuesBySymbolId
40962
- });
40963
- }
40964
40603
  if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") {
40965
- const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime, state);
40604
+ const argumentResult = readHydrationConditionResult(unwrappedExpression.argument, context, runtime);
40966
40605
  return argumentResult === null ? null : !argumentResult;
40967
40606
  }
40968
40607
  if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
40969
- return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime, state), readHydrationConditionResult(unwrappedExpression.right, context, runtime, state));
40970
- };
40971
- const readHydrationStatementResult = (statement, context, runtime, state) => {
40972
- if (isNodeOfType(statement, "ReturnStatement")) return {
40973
- didReturn: true,
40974
- value: statement.argument ? readHydrationConditionResult(statement.argument, context, runtime, state) : null
40975
- };
40976
- if (isNodeOfType(statement, "BlockStatement")) {
40977
- for (const childStatement of statement.body) {
40978
- const result = readHydrationStatementResult(childStatement, context, runtime, state);
40979
- if (result.didReturn) return result;
40980
- if (statementAlwaysExits(childStatement)) break;
40981
- }
40982
- return {
40983
- didReturn: false,
40984
- value: null
40985
- };
40986
- }
40987
- if (!isNodeOfType(statement, "IfStatement")) return {
40988
- didReturn: false,
40989
- value: null
40990
- };
40991
- const conditionResult = readHydrationConditionResult(statement.test, context, runtime, state);
40992
- if (conditionResult !== null) {
40993
- const selectedBranch = conditionResult ? statement.consequent : statement.alternate;
40994
- return selectedBranch ? readHydrationStatementResult(selectedBranch, context, runtime, state) : {
40995
- didReturn: false,
40996
- value: null
40997
- };
40998
- }
40999
- const consequentResult = readHydrationStatementResult(statement.consequent, context, runtime, state);
41000
- const alternateResult = statement.alternate ? readHydrationStatementResult(statement.alternate, context, runtime, state) : {
41001
- didReturn: false,
41002
- value: null
41003
- };
41004
- return consequentResult.didReturn && alternateResult.didReturn && consequentResult.value !== null && consequentResult.value === alternateResult.value ? consequentResult : {
41005
- didReturn: consequentResult.didReturn || alternateResult.didReturn,
41006
- value: null
41007
- };
41008
- };
41009
- const readHydrationFunctionResult = (functionNode, context, runtime, state) => {
41010
- if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
41011
- state.visitedFunctionNodes.add(functionNode);
41012
- const result = isNodeOfType(functionNode.body, "BlockStatement") ? readHydrationStatementResult(functionNode.body, context, runtime, state).value : readHydrationConditionResult(functionNode.body, context, runtime, state);
41013
- state.visitedFunctionNodes.delete(functionNode);
41014
- return result;
41015
- };
41016
- const doEquivalentExpressionBindingsMatch = (leftExpression, rightExpression, scopes) => {
41017
- const left = stripParenExpression(leftExpression);
41018
- const right = stripParenExpression(rightExpression);
41019
- if (isNodeOfType(left, "Identifier") && isNodeOfType(right, "Identifier")) {
41020
- const leftSymbol = scopes.symbolFor(left);
41021
- const rightSymbol = scopes.symbolFor(right);
41022
- return leftSymbol || rightSymbol ? leftSymbol?.id === rightSymbol?.id : true;
41023
- }
41024
- if (isNodeOfType(left, "MemberExpression") && isNodeOfType(right, "MemberExpression")) return doEquivalentExpressionBindingsMatch(left.object, right.object, scopes) && (!left.computed || doEquivalentExpressionBindingsMatch(left.property, right.property, scopes));
41025
- if (isNodeOfType(left, "CallExpression") && isNodeOfType(right, "CallExpression")) {
41026
- const rightArguments = right.arguments ?? [];
41027
- return doEquivalentExpressionBindingsMatch(left.callee, right.callee, scopes) && (left.arguments ?? []).every((argument, index) => {
41028
- const rightArgument = rightArguments[index];
41029
- return Boolean(rightArgument && doEquivalentExpressionBindingsMatch(argument, rightArgument, scopes));
41030
- });
41031
- }
41032
- return true;
40608
+ return readLogicalConditionResult(unwrappedExpression.operator, readHydrationConditionResult(unwrappedExpression.left, context, runtime), readHydrationConditionResult(unwrappedExpression.right, context, runtime));
41033
40609
  };
41034
- const areHelperReturnValuesEquivalent = (leftValue, rightValue, context) => {
41035
- if (areExpressionsStructurallyEqual(leftValue, rightValue)) return doEquivalentExpressionBindingsMatch(leftValue, rightValue, context.scopes);
41036
- const leftBoolean = readInitialStateBoolean(leftValue, context.scopes);
41037
- const rightBoolean = readInitialStateBoolean(rightValue, context.scopes);
41038
- return leftBoolean !== null && rightBoolean !== null && leftBoolean === rightBoolean;
41039
- };
41040
- const doHelperReturnValuesDiffer = (leftValues, rightValues, context) => {
41041
- const everyValueHasEquivalent = (values, candidateValues) => values.every((value) => candidateValues.some((candidateValue) => areHelperReturnValuesEquivalent(value, candidateValue, context)));
41042
- return !everyValueHasEquivalent(leftValues, rightValues) || !everyValueHasEquivalent(rightValues, leftValues);
41043
- };
41044
- const matchHydrationConditionInternal = (expression, context, state) => {
40610
+ const matchHydrationCondition = (expression, context) => {
41045
40611
  const unwrappedExpression = stripParenExpression(expression);
41046
40612
  const predicateMatch = matchBrowserPredicate(unwrappedExpression, context);
41047
40613
  if (predicateMatch) return {
41048
40614
  predicateMatch,
41049
40615
  predicateNode: unwrappedExpression
41050
40616
  };
41051
- if (isNodeOfType(unwrappedExpression, "Identifier")) {
41052
- const symbol = context.scopes.symbolFor(unwrappedExpression);
41053
- const parameterValue = symbol ? state.parameterValuesBySymbolId.get(symbol.id) : null;
41054
- if (symbol && parameterValue && !state.visitedSymbolIds.has(symbol.id)) {
41055
- state.visitedSymbolIds.add(symbol.id);
41056
- const match = matchHydrationConditionInternal(parameterValue, context, state);
41057
- state.visitedSymbolIds.delete(symbol.id);
41058
- return match;
41059
- }
41060
- if (!symbol || symbol.kind !== "const" || !symbol.initializer || symbol.references.some((reference) => reference.flag !== "read") || state.visitedSymbolIds.has(symbol.id)) return null;
41061
- state.visitedSymbolIds.add(symbol.id);
41062
- const match = matchHydrationConditionInternal(symbol.initializer, context, state);
41063
- state.visitedSymbolIds.delete(symbol.id);
41064
- return match;
41065
- }
41066
- if (isNodeOfType(unwrappedExpression, "CallExpression")) {
41067
- const callArguments = unwrappedExpression.arguments ?? [];
41068
- if (isReactApiCall(unwrappedExpression, "useMemo", context.scopes, {
41069
- allowGlobalReactNamespace: true,
41070
- resolveNamedAliases: true
41071
- })) {
41072
- const callbackArgument = callArguments[0];
41073
- if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
41074
- const callbackFunction = resolveExactLocalFunction(callbackArgument, context.scopes);
41075
- return isFunctionLike$1(callbackFunction) && callbackFunction.params.length === 0 ? matchHydrationFunctionResult(callbackFunction, context, state) : null;
41076
- }
41077
- const callee = stripParenExpression(unwrappedExpression.callee);
41078
- if (isNodeOfType(callee, "Identifier") && callee.name === "Boolean" && context.scopes.isGlobalReference(callee) && callArguments.length === 1 && !isNodeOfType(callArguments[0], "SpreadElement")) return matchHydrationConditionInternal(callArguments[0], context, state);
41079
- const helperFunction = resolveExactLocalFunction(callee, context.scopes);
41080
- if (!isFunctionLike$1(helperFunction) || helperFunction.async || isNodeOfType(helperFunction, "FunctionDeclaration") && helperFunction.generator || isNodeOfType(helperFunction, "FunctionExpression") && helperFunction.generator || helperFunction.params.some((parameter) => !isNodeOfType(parameter, "Identifier")) || callArguments.some((argument) => isNodeOfType(argument, "SpreadElement"))) return null;
41081
- const parameterValuesBySymbolId = new Map(state.parameterValuesBySymbolId);
41082
- for (let parameterIndex = 0; parameterIndex < helperFunction.params.length; parameterIndex++) {
41083
- const parameter = helperFunction.params[parameterIndex];
41084
- const argument = callArguments[parameterIndex];
41085
- if (!argument || !isNodeOfType(parameter, "Identifier")) continue;
41086
- const parameterSymbol = context.scopes.symbolFor(parameter);
41087
- if (parameterSymbol) parameterValuesBySymbolId.set(parameterSymbol.id, argument);
41088
- }
41089
- return matchHydrationFunctionResult(helperFunction, context, {
41090
- ...state,
41091
- parameterValuesBySymbolId
41092
- });
41093
- }
41094
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationConditionInternal(unwrappedExpression.argument, context, state);
40617
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return matchHydrationCondition(unwrappedExpression.argument, context);
41095
40618
  if (!isNodeOfType(unwrappedExpression, "LogicalExpression") || unwrappedExpression.operator !== "&&" && unwrappedExpression.operator !== "||") return null;
41096
- const leftMatch = matchHydrationConditionInternal(unwrappedExpression.left, context, state);
41097
- const rightMatch = matchHydrationConditionInternal(unwrappedExpression.right, context, state);
40619
+ const leftMatch = matchHydrationCondition(unwrappedExpression.left, context);
40620
+ const rightMatch = matchHydrationCondition(unwrappedExpression.right, context);
40621
+ if (leftMatch && rightMatch) {
40622
+ const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client");
40623
+ const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server");
40624
+ return clientResult !== null && serverResult !== null && clientResult !== serverResult ? leftMatch : null;
40625
+ }
41098
40626
  const nestedMatch = leftMatch ?? rightMatch;
41099
40627
  if (!nestedMatch) return null;
41100
- const clientResult = readHydrationConditionResult(unwrappedExpression, context, "client", state);
41101
- const serverResult = readHydrationConditionResult(unwrappedExpression, context, "server", state);
41102
- return clientResult !== null && serverResult !== null && clientResult === serverResult ? null : nestedMatch;
41103
- };
41104
- const matchHydrationReturningStatement = (statement, context, state) => {
41105
- if (isNodeOfType(statement, "ReturnStatement")) return statement.argument ? matchHydrationConditionInternal(statement.argument, context, state) : null;
41106
- if (isNodeOfType(statement, "IfStatement")) {
41107
- const conditionMatch = matchHydrationConditionInternal(statement.test, context, state);
41108
- const consequentValues = getReturnedValues(statement.consequent);
41109
- const alternateValues = statement.alternate ? getReturnedValues(statement.alternate) : findFollowingReturnedValues(statement);
41110
- if (conditionMatch && consequentValues.length > 0 && alternateValues.length > 0 && doHelperReturnValuesDiffer(consequentValues, alternateValues, context)) return conditionMatch;
41111
- return matchHydrationReturningStatement(statement.consequent, context, state) ?? (statement.alternate ? matchHydrationReturningStatement(statement.alternate, context, state) : null);
41112
- }
41113
- if (!isNodeOfType(statement, "BlockStatement")) return null;
41114
- for (const childStatement of statement.body) {
41115
- const match = matchHydrationReturningStatement(childStatement, context, state);
41116
- if (match) return match;
41117
- if (statementAlwaysExits(childStatement)) break;
41118
- }
41119
- return null;
40628
+ const otherResult = readInitialStateBoolean(leftMatch ? unwrappedExpression.right : unwrappedExpression.left, context.scopes);
40629
+ if (unwrappedExpression.operator === "&&" && otherResult === false || unwrappedExpression.operator === "||" && otherResult === true) return null;
40630
+ return nestedMatch;
41120
40631
  };
41121
- const matchHydrationFunctionResult = (functionNode, context, state) => {
41122
- if (!isFunctionLike$1(functionNode) || state.visitedFunctionNodes.has(functionNode)) return null;
41123
- state.visitedFunctionNodes.add(functionNode);
41124
- const match = isNodeOfType(functionNode.body, "BlockStatement") ? matchHydrationReturningStatement(functionNode.body, context, state) : matchHydrationConditionInternal(functionNode.body, context, state);
41125
- state.visitedFunctionNodes.delete(functionNode);
41126
- return match;
41127
- };
41128
- const matchHydrationCondition = (expression, context) => matchHydrationConditionInternal(expression, context, {
41129
- parameterValuesBySymbolId: /* @__PURE__ */ new Map(),
41130
- visitedFunctionNodes: /* @__PURE__ */ new Set(),
41131
- visitedSymbolIds: /* @__PURE__ */ new Set()
41132
- });
41133
40632
  const areNodeArraysEquivalent = (leftNodes, rightNodes) => leftNodes.length === rightNodes.length && leftNodes.every((leftNode, index) => areRenderedBranchesEquivalent(leftNode, rightNodes[index]));
41134
40633
  const areRenderedBranchesEquivalent = (leftNode, rightNode) => {
41135
40634
  if (!leftNode || !rightNode) return leftNode === rightNode;
@@ -41272,17 +40771,17 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
41272
40771
  const { predicateMatch, predicateNode } = conditionMatch;
41273
40772
  if (reportedNodes.has(predicateNode)) return;
41274
40773
  if (rightBranch && areRenderedBranchesEquivalent(leftBranch, rightBranch)) return;
41275
- const componentOrHookNode = findRenderPhaseComponentOrHook(conditionNode, context.scopes);
40774
+ const componentOrHookNode = findRenderPhaseComponentOrHook(predicateNode, context.scopes);
41276
40775
  if (!componentOrHookNode) return;
41277
40776
  if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
41278
- if (requiresRenderedContext && !isInRenderedOutput(conditionNode, componentOrHookNode, context.scopes)) return;
40777
+ if (requiresRenderedContext && !isInRenderedOutput(predicateNode, componentOrHookNode, context.scopes)) return;
41279
40778
  if (!isRenderedValue(leftBranch) && (!rightBranch || !isRenderedValue(rightBranch))) {
41280
- const attribute = findEnclosingJsxAttribute(conditionNode);
40779
+ const attribute = findEnclosingJsxAttribute(predicateNode);
41281
40780
  if (!attribute || isEventHandlerAttribute(attribute)) return;
41282
40781
  }
41283
- if (fileIsEmailTemplate || isGatedByFalsyInitialState(conditionNode, context.scopes)) return;
41284
- if (isAfterClientOnlyEarlyReturn(conditionNode, componentOrHookNode, context.scopes)) return;
41285
- const openingElement = findEnclosingJsxOpeningElement(conditionNode);
40782
+ if (fileIsEmailTemplate || isGatedByFalsyInitialState(predicateNode, context.scopes)) return;
40783
+ if (isAfterClientOnlyEarlyReturn(predicateNode, componentOrHookNode, context.scopes)) return;
40784
+ const openingElement = findEnclosingJsxOpeningElement(predicateNode);
41286
40785
  if (hasSuppressHydrationWarningAttribute(openingElement) && !isStructuralRenderedValue(leftBranch) && !isStructuralRenderedValue(rightBranch)) return;
41287
40786
  if (branchRootsSuppressSameElement(leftBranch, rightBranch)) return;
41288
40787
  if (isGeneratedImageRenderContext(context, openingElement ?? leftBranch)) return;
@@ -41664,7 +41163,7 @@ const noInitializeState = defineRule({
41664
41163
  if (!dependencies || !isNodeOfType(dependencies, "ArrayExpression") || (dependencies.elements ?? []).length !== 0) return;
41665
41164
  const analysis = getProgramAnalysis(node);
41666
41165
  if (!analysis) return;
41667
- for (const fact of collectEffectStateWriteFacts(analysis, context, node, context.filename)) {
41166
+ for (const fact of collectEffectStateWriteFacts(analysis, node, context.filename)) {
41668
41167
  if (!fact.isRenderKnownCopy || fact.matchesStateInitializer || fact.resetsSourceState) continue;
41669
41168
  const stateName = getStateName(fact.stateDeclarator);
41670
41169
  context.report({
@@ -57229,39 +56728,8 @@ const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node,
57229
56728
  destructureIndex: 1
57230
56729
  });
57231
56730
  //#endregion
57232
- //#region src/plugin/utils/unwrap-return-expression.ts
57233
- const unwrapReturnExpression = (node) => isNodeOfType(node, "ReturnStatement") && node.argument ? node.argument : node;
57234
- //#endregion
57235
56731
  //#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
57236
56732
  const USE_EFFECT_ONLY = new Set(["useEffect"]);
57237
- const USE_CALLBACK_ONLY = new Set(["useCallback"]);
57238
- const USE_STATE_ONLY = new Set(["useState"]);
57239
- const REACT_API_CALL_OPTIONS = {
57240
- allowGlobalReactNamespace: true,
57241
- allowUnboundBareCalls: true,
57242
- resolveNamedAliases: true
57243
- };
57244
- const expressionReadsDerivedSymbol = (context, expression, stateDerivedSymbolIds) => {
57245
- let readsDerivedSymbol = false;
57246
- walkAst(expression, (node) => {
57247
- if (readsDerivedSymbol) return false;
57248
- if (node !== expression && isFunctionLike$1(node)) return false;
57249
- if (isNodeOfType(node, "Identifier") && stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) readsDerivedSymbol = true;
57250
- });
57251
- return readsDerivedSymbol;
57252
- };
57253
- const getStaticObjectPropertyName = (property) => {
57254
- if (!isNodeOfType(property, "Property") || property.computed || property.method || property.kind !== "init") return null;
57255
- if (isNodeOfType(property.key, "Identifier")) return property.key.name;
57256
- if (isNodeOfType(property.key, "Literal") && (typeof property.key.value === "string" || typeof property.key.value === "number")) return String(property.key.value);
57257
- return null;
57258
- };
57259
- const isNonVisibleJsxSpreadProperty = (propertyName) => propertyName === "id" || propertyName.startsWith("aria-") || /^on[A-Z]/.test(propertyName);
57260
- const isTransparentAssignmentTarget = (identifier) => {
57261
- const expressionRoot = findTransparentExpressionRoot(identifier);
57262
- const parent = expressionRoot.parent;
57263
- return Boolean(isNodeOfType(parent, "AssignmentExpression") && parent.left === expressionRoot || isNodeOfType(parent, "UpdateExpression") && parent.argument === expressionRoot || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expressionRoot);
57264
- };
57265
56733
  const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
57266
56734
  let readsCurrent = false;
57267
56735
  walkAst(argument, (child) => {
@@ -57313,166 +56781,6 @@ const isStateUsedOnlyInIdOrAriaAttributes = (setterCall, setterName) => {
57313
56781
  });
57314
56782
  return referenceCount > 0 && !nonAriaReferenceFound;
57315
56783
  };
57316
- const isGlobalWindowMember = (context, node, propertyName) => {
57317
- const member = stripParenExpression(node);
57318
- if (!isNodeOfType(member, "MemberExpression") || member.computed) return false;
57319
- const receiver = stripParenExpression(member.object);
57320
- return isNodeOfType(receiver, "Identifier") && receiver.name === "window" && context.scopes.isGlobalReference(receiver) && isNodeOfType(member.property, "Identifier") && member.property.name === propertyName;
57321
- };
57322
- const getDirectWindowWidthSetter = (context, statement) => {
57323
- const call = unwrapDiscardedExpression(statement);
57324
- if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 1) return null;
57325
- if (!isNodeOfType(call.callee, "Identifier") || !isSetterCall(call)) return null;
57326
- const argument = call.arguments[0];
57327
- return isGlobalWindowMember(context, argument, "innerWidth") ? call : null;
57328
- };
57329
- const getResizeListenerHandler = (context, statement, methodName) => {
57330
- const call = unwrapDiscardedExpression(statement);
57331
- if (!isNodeOfType(call, "CallExpression") || call.arguments?.length !== 2) return null;
57332
- if (!isGlobalWindowMember(context, call.callee, methodName)) return null;
57333
- const eventName = call.arguments[0];
57334
- const handler = call.arguments[1];
57335
- if (!isNodeOfType(eventName, "Literal") || eventName.value !== "resize") return null;
57336
- return isNodeOfType(handler, "Identifier") ? handler : null;
57337
- };
57338
- const getCleanupResizeHandler = (context, statement) => {
57339
- if (!isNodeOfType(statement, "ReturnStatement") || !isFunctionLike$1(statement.argument)) return null;
57340
- const cleanupStatements = getCallbackStatements(statement.argument);
57341
- if (cleanupStatements.length !== 1) return null;
57342
- return getResizeListenerHandler(context, unwrapReturnExpression(cleanupStatements[0]), "removeEventListener");
57343
- };
57344
- const findExactViewportState = (context, componentFunction, setterCall) => {
57345
- if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
57346
- const componentBody = componentFunction.body;
57347
- if (!isNodeOfType(setterCall.callee, "Identifier")) return null;
57348
- const setterSymbol = context.scopes.symbolFor(setterCall.callee);
57349
- if (!setterSymbol || setterSymbol.kind !== "const" || !isNodeOfType(setterSymbol.declarationNode, "VariableDeclarator")) return null;
57350
- const declarator = setterSymbol.declarationNode;
57351
- if (!isNodeOfType(declarator.id, "ArrayPattern")) return null;
57352
- const stateIdentifier = declarator.id.elements?.[0];
57353
- const setterIdentifier = declarator.id.elements?.[1];
57354
- 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;
57355
- const initializer = declarator.init.arguments?.[0];
57356
- if (!isNodeOfType(initializer, "Literal") || initializer.value !== 0) return null;
57357
- const stateSymbol = context.scopes.symbolFor(stateIdentifier);
57358
- if (!stateSymbol) return null;
57359
- const stateDerivedSymbolIds = new Set([stateSymbol.id]);
57360
- let didAddDerivedSymbol = true;
57361
- while (didAddDerivedSymbol) {
57362
- didAddDerivedSymbol = false;
57363
- for (const statement of componentBody.body ?? []) {
57364
- if (!isNodeOfType(statement, "VariableDeclaration")) continue;
57365
- for (const candidateDeclarator of statement.declarations ?? []) {
57366
- if (!isNodeOfType(candidateDeclarator.id, "Identifier") || !candidateDeclarator.init) continue;
57367
- const candidateInitializer = stripParenExpression(candidateDeclarator.init);
57368
- if (isFunctionLike$1(candidateInitializer) || isNodeOfType(candidateInitializer, "CallExpression") && isReactApiCall(candidateInitializer, USE_CALLBACK_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) continue;
57369
- if (!expressionReadsDerivedSymbol(context, candidateInitializer, stateDerivedSymbolIds)) continue;
57370
- const candidateSymbol = context.scopes.symbolFor(candidateDeclarator.id);
57371
- if (candidateSymbol?.kind === "const" && candidateSymbol.references.every((reference) => reference.flag === "read" && !isTransparentAssignmentTarget(reference.identifier)) && !stateDerivedSymbolIds.has(candidateSymbol.id)) {
57372
- stateDerivedSymbolIds.add(candidateSymbol.id);
57373
- didAddDerivedSymbol = true;
57374
- }
57375
- }
57376
- }
57377
- }
57378
- const staticSpreadVisibilityBySymbolId = /* @__PURE__ */ new Map();
57379
- const hasOnlyStaticObjectReferences = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
57380
- const symbol = context.scopes.symbolFor(identifier);
57381
- if (!symbol) return false;
57382
- if (visitedSymbolIds.has(symbol.id)) return true;
57383
- const nextVisitedSymbolIds = new Set(visitedSymbolIds);
57384
- nextVisitedSymbolIds.add(symbol.id);
57385
- let hasUnknownReference = false;
57386
- walkAst(componentBody, (node) => {
57387
- if (hasUnknownReference || !isNodeOfType(node, "Identifier") || context.scopes.symbolFor(node)?.id !== symbol.id || node === symbol.bindingIdentifier) return;
57388
- const referenceRoot = findTransparentExpressionRoot(node);
57389
- const parent = referenceRoot.parent;
57390
- if (isNodeOfType(parent, "JSXSpreadAttribute") && parent.argument === referenceRoot) return;
57391
- if (isNodeOfType(parent, "VariableDeclarator") && parent.init === referenceRoot && isNodeOfType(parent.id, "Identifier") && isNodeOfType(parent.parent, "VariableDeclaration") && parent.parent.kind === "const" && hasOnlyStaticObjectReferences(parent.id, nextVisitedSymbolIds)) return;
57392
- hasUnknownReference = true;
57393
- return false;
57394
- });
57395
- return !hasUnknownReference;
57396
- };
57397
- const classifyStaticSpreadObject = (identifier, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
57398
- const symbol = context.scopes.symbolFor(identifier);
57399
- if (!symbol || visitedSymbolIds.has(symbol.id)) return "unknown";
57400
- const cachedVisibility = staticSpreadVisibilityBySymbolId.get(symbol.id);
57401
- if (cachedVisibility) return cachedVisibility;
57402
- if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || symbol.declarationNode.id !== symbol.bindingIdentifier || !symbol.declarationNode.init) return "unknown";
57403
- if (!hasOnlyStaticObjectReferences(identifier)) return "unknown";
57404
- const initializer = stripParenExpression(symbol.declarationNode.init);
57405
- const nextVisitedSymbolIds = new Set(visitedSymbolIds);
57406
- nextVisitedSymbolIds.add(symbol.id);
57407
- if (isNodeOfType(initializer, "Identifier")) {
57408
- const visibility = classifyStaticSpreadObject(initializer, nextVisitedSymbolIds);
57409
- staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
57410
- return visibility;
57411
- }
57412
- if (!isNodeOfType(initializer, "ObjectExpression")) return "unknown";
57413
- let visibility = "non-visible";
57414
- for (const property of initializer.properties ?? []) {
57415
- const propertyName = getStaticObjectPropertyName(property);
57416
- if (!isNodeOfType(property, "Property") || !propertyName) {
57417
- visibility = "unknown";
57418
- break;
57419
- }
57420
- if (expressionReadsDerivedSymbol(context, property.value, stateDerivedSymbolIds) && !isNonVisibleJsxSpreadProperty(propertyName)) visibility = "visible";
57421
- }
57422
- staticSpreadVisibilityBySymbolId.set(symbol.id, visibility);
57423
- return visibility;
57424
- };
57425
- let hasNonAriaReference = false;
57426
- walkAst(componentBody, (node) => {
57427
- if (hasNonAriaReference) return false;
57428
- if (!isNodeOfType(node, "Identifier") || !stateDerivedSymbolIds.has(context.scopes.symbolFor(node)?.id ?? -1)) return;
57429
- if (findEnclosingFunction$1(node) !== componentFunction) return;
57430
- const parent = node.parent;
57431
- if (parent && (isNodeOfType(parent, "MemberExpression") && parent.property === node && !parent.computed || isNodeOfType(parent, "Property") && parent.key === node && !parent.computed)) return;
57432
- let cursor = parent;
57433
- while (cursor && cursor !== componentBody) {
57434
- if (isFunctionLike$1(cursor)) return;
57435
- if (isNodeOfType(cursor, "JSXSpreadAttribute")) {
57436
- if (isNodeOfType(node, "Identifier") && classifyStaticSpreadObject(node) === "visible") hasNonAriaReference = true;
57437
- return;
57438
- }
57439
- if (isNodeOfType(cursor, "JSXAttribute")) {
57440
- if (isEventHandlerAttribute(cursor)) return;
57441
- if (!isInsideIdOrAriaAttribute(node)) hasNonAriaReference = true;
57442
- return;
57443
- }
57444
- if (isNodeOfType(cursor, "ReturnStatement")) {
57445
- hasNonAriaReference = true;
57446
- return;
57447
- }
57448
- cursor = cursor.parent;
57449
- }
57450
- });
57451
- return hasNonAriaReference ? stateIdentifier.name : null;
57452
- };
57453
- const isExactViewportSubscriptionEffect = (context, effectCall, callback) => {
57454
- if (!isReactApiCall(effectCall, USE_EFFECT_ONLY, context.scopes, REACT_API_CALL_OPTIONS)) return false;
57455
- if (!isFunctionLike$1(callback) || callback.async || !isNodeOfType(callback.body, "BlockStatement")) return false;
57456
- const statements = getCallbackStatements(callback);
57457
- if (statements.length !== 4) return false;
57458
- const handlerDeclaration = statements[0];
57459
- if (!isNodeOfType(handlerDeclaration, "VariableDeclaration") || handlerDeclaration.kind !== "const" || handlerDeclaration.declarations?.length !== 1) return false;
57460
- const handlerDeclarator = handlerDeclaration.declarations[0];
57461
- if (!isNodeOfType(handlerDeclarator.id, "Identifier") || !isFunctionLike$1(handlerDeclarator.init)) return false;
57462
- const handlerStatements = getCallbackStatements(handlerDeclarator.init);
57463
- if (handlerStatements.length !== 1) return false;
57464
- const handlerSetter = getDirectWindowWidthSetter(context, unwrapReturnExpression(handlerStatements[0]));
57465
- const subscribedHandler = getResizeListenerHandler(context, statements[1], "addEventListener");
57466
- const immediateSetter = getDirectWindowWidthSetter(context, statements[2]);
57467
- const cleanupHandler = getCleanupResizeHandler(context, statements[3]);
57468
- if (!handlerSetter || !subscribedHandler || !immediateSetter || !cleanupHandler) return false;
57469
- const handlerSymbol = context.scopes.symbolFor(handlerDeclarator.id);
57470
- if (!handlerSymbol || context.scopes.symbolFor(subscribedHandler) !== handlerSymbol || context.scopes.symbolFor(cleanupHandler) !== handlerSymbol) return false;
57471
- if (!isNodeOfType(handlerSetter.callee, "Identifier") || !isNodeOfType(immediateSetter.callee, "Identifier") || context.scopes.symbolFor(handlerSetter.callee) !== context.scopes.symbolFor(immediateSetter.callee)) return false;
57472
- const componentFunction = findEnclosingFunction$1(effectCall);
57473
- if (!isFunctionLike$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return false;
57474
- return findExactViewportState(context, componentFunction, immediateSetter) !== null;
57475
- };
57476
56784
  const renderingHydrationNoFlicker = defineRule({
57477
56785
  id: "rendering-hydration-no-flicker",
57478
56786
  title: "useEffect setState flashes on mount",
@@ -57485,14 +56793,7 @@ const renderingHydrationNoFlicker = defineRule({
57485
56793
  if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
57486
56794
  const callback = getEffectCallback(node);
57487
56795
  if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
57488
- if (isExactViewportSubscriptionEffect(context, node, callback)) {
57489
- context.report({
57490
- node,
57491
- message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
57492
- });
57493
- return;
57494
- }
57495
- const bodyStatements = getCallbackStatements(callback);
56796
+ const bodyStatements = (isNodeOfType(callback.body, "BlockStatement") ? callback.body.body ?? [] : [callback.body]).filter((statement) => !isNoOpStatement(statement));
57496
56797
  if (bodyStatements.length !== 1) return;
57497
56798
  const soleStatement = bodyStatements[0];
57498
56799
  if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
@@ -67324,6 +66625,14 @@ const isStateKey = (key) => {
67324
66625
  if (isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value === "state";
67325
66626
  return false;
67326
66627
  };
66628
+ const findEnclosingClass = (node) => {
66629
+ let ancestor = node.parent;
66630
+ while (ancestor) {
66631
+ if (isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) return ancestor;
66632
+ ancestor = ancestor.parent ?? null;
66633
+ }
66634
+ return null;
66635
+ };
67327
66636
  const isInConstructor = (node) => {
67328
66637
  let ancestor = node.parent;
67329
66638
  while (ancestor) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.9-dev.0ff57fa",
3
+ "version": "0.7.9-dev.11388bf",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",