oxlint-plugin-react-doctor 0.7.4-dev.11333b9 → 0.7.4-dev.2b5a18e

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 +60 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10589,6 +10589,19 @@ const isUnstableInitializer = (node) => {
10589
10589
  if (isNodeOfType(stripped, "LogicalExpression")) return isUnstableInitializer(stripped.left) || isUnstableInitializer(stripped.right);
10590
10590
  return isNodeOfType(stripped, "ObjectExpression") || isNodeOfType(stripped, "ArrayExpression") || isNodeOfType(stripped, "ClassExpression") || isNodeOfType(stripped, "ClassDeclaration") || isNodeOfType(stripped, "JSXElement") || isNodeOfType(stripped, "JSXFragment") || isNodeOfType(stripped, "AssignmentExpression") || isNodeOfType(stripped, "NewExpression");
10591
10591
  };
10592
+ const isPotentiallyFreshComparedValue = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
10593
+ const candidate = unwrapExpression$3(node);
10594
+ if (isUnstableInitializer(candidate)) return true;
10595
+ if (isNodeOfType(candidate, "ConditionalExpression")) return isPotentiallyFreshComparedValue(candidate.consequent, scopes, visitedSymbolIds) || isPotentiallyFreshComparedValue(candidate.alternate, scopes, visitedSymbolIds);
10596
+ if (isNodeOfType(candidate, "LogicalExpression")) return isPotentiallyFreshComparedValue(candidate.left, scopes, visitedSymbolIds) || isPotentiallyFreshComparedValue(candidate.right, scopes, visitedSymbolIds);
10597
+ if (!isNodeOfType(candidate, "Identifier")) return false;
10598
+ const symbol = scopes.symbolFor(candidate);
10599
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
10600
+ if (symbol.kind === "let" || symbol.kind === "var") return true;
10601
+ if (symbol.kind !== "const" || !symbol.initializer) return false;
10602
+ visitedSymbolIds.add(symbol.id);
10603
+ return isPotentiallyFreshComparedValue(symbol.initializer, scopes, visitedSymbolIds);
10604
+ };
10592
10605
  const isExtraDepAllowedForHook = (hookName, node, scopes) => {
10593
10606
  if (!isExtraReactiveDepAllowed(node, scopes)) return false;
10594
10607
  if (EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS.has(hookName)) return true;
@@ -10602,6 +10615,52 @@ const isStableSetterLikeSymbol = (symbol, scopes) => {
10602
10615
  if (!symbolHasStableHookOrigin(symbol, scopes)) return false;
10603
10616
  return symbol.name.startsWith("set") || symbol.name.startsWith("dispatch") || symbol.name.startsWith("startTransition");
10604
10617
  };
10618
+ const isConvergingFunctionalUpdater = (node, scopes) => {
10619
+ const updater = unwrapExpression$3(node);
10620
+ if (!isNodeOfType(updater, "ArrowFunctionExpression") && !isNodeOfType(updater, "FunctionExpression")) return false;
10621
+ const previousValueParameter = updater.params?.[0];
10622
+ if (!previousValueParameter || !isNodeOfType(previousValueParameter, "Identifier")) return false;
10623
+ let returnedExpression = updater.body;
10624
+ if (isNodeOfType(updater.body, "BlockStatement")) {
10625
+ returnedExpression = null;
10626
+ for (const statement of updater.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument) {
10627
+ returnedExpression = statement.argument;
10628
+ break;
10629
+ }
10630
+ }
10631
+ const conditional = returnedExpression ? unwrapExpression$3(returnedExpression) : null;
10632
+ if (!conditional || !isNodeOfType(conditional, "ConditionalExpression")) return false;
10633
+ const test = unwrapExpression$3(conditional.test);
10634
+ if (!isNodeOfType(test, "BinaryExpression") || ![
10635
+ "===",
10636
+ "!==",
10637
+ "==",
10638
+ "!="
10639
+ ].includes(test.operator)) return false;
10640
+ const isPreviousValue = (expression) => {
10641
+ const candidate = unwrapExpression$3(expression);
10642
+ return isNodeOfType(candidate, "Identifier") && candidate.name === previousValueParameter.name;
10643
+ };
10644
+ let comparedValue = null;
10645
+ if (isPreviousValue(test.left)) comparedValue = test.right;
10646
+ else if (isPreviousValue(test.right)) comparedValue = test.left;
10647
+ if (!comparedValue) return false;
10648
+ if (isPotentiallyFreshComparedValue(comparedValue, scopes)) return false;
10649
+ const isSameComparedValue = (expression) => {
10650
+ const candidate = unwrapExpression$3(expression);
10651
+ const compared = unwrapExpression$3(comparedValue);
10652
+ if (isNodeOfType(candidate, "Identifier") && isNodeOfType(compared, "Identifier")) return candidate.name === compared.name;
10653
+ if (isNodeOfType(candidate, "Literal") && isNodeOfType(compared, "Literal")) return candidate.value === compared.value;
10654
+ return false;
10655
+ };
10656
+ return test.operator === "===" || test.operator === "==" ? isPreviousValue(conditional.consequent) && isSameComparedValue(conditional.alternate) : isSameComparedValue(conditional.consequent) && isPreviousValue(conditional.alternate);
10657
+ };
10658
+ const isGuardedStableSetterCall = (identifier, symbol, scopes) => {
10659
+ const parent = identifier.parent;
10660
+ if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier || !isStableSetterLikeSymbol(symbol, scopes)) return false;
10661
+ const writtenValue = parent.arguments?.[0];
10662
+ return Boolean(writtenValue && isConvergingFunctionalUpdater(writtenValue, scopes));
10663
+ };
10605
10664
  const findStableSetterReference = (node, scopes) => {
10606
10665
  let setterName = null;
10607
10666
  const visit = (current) => {
@@ -10609,7 +10668,7 @@ const findStableSetterReference = (node, scopes) => {
10609
10668
  if (current !== node && (isNodeOfType(current, "FunctionDeclaration") || isNodeOfType(current, "FunctionExpression") || isNodeOfType(current, "ArrowFunctionExpression"))) return;
10610
10669
  if (isNodeOfType(current, "Identifier")) {
10611
10670
  const symbol = scopes.referenceFor(current)?.resolvedSymbol;
10612
- if (symbol && isStableSetterLikeSymbol(symbol, scopes)) {
10671
+ if (symbol && isStableSetterLikeSymbol(symbol, scopes) && !isGuardedStableSetterCall(current, symbol, scopes)) {
10613
10672
  setterName = symbol.name;
10614
10673
  return;
10615
10674
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.4-dev.11333b9",
3
+ "version": "0.7.4-dev.2b5a18e",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",