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

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 +34 -4
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -14751,6 +14751,29 @@ const getReleaseVerbName = (node) => {
14751
14751
  }
14752
14752
  return null;
14753
14753
  };
14754
+ const isRetainedAbortControllerRefRelease = (releaseReceiver, usage, context) => {
14755
+ const releaseFunction = findEnclosingFunction$1(releaseReceiver);
14756
+ const usageFunction = findEnclosingFunction$1(usage.node);
14757
+ if (!releaseFunction || !usageFunction || !isFunctionLike$1(usageFunction) || !isReturnedEffectCleanupFunction(releaseFunction) || !hasReactRefCurrentOrigin(releaseReceiver, context.scopes)) return false;
14758
+ const controllerKey = getListenerAbortControllerKey(usage, context);
14759
+ const refCurrentKey = resolveExpressionKey(releaseReceiver, context);
14760
+ if (controllerKey === null || refCurrentKey === null) return false;
14761
+ const usageFunctionBody = usageFunction.body;
14762
+ const previousAbortCalls = [];
14763
+ const ownershipAssignments = [];
14764
+ walkAst(usageFunctionBody, (child) => {
14765
+ if (child !== usageFunctionBody && isFunctionLike$1(child)) return false;
14766
+ if (isNodeOfType(child, "AssignmentExpression") && resolveExpressionKey(child.left, context) === refCurrentKey && resolveExpressionKey(child.right, context) === controllerKey) {
14767
+ ownershipAssignments.push(child);
14768
+ return;
14769
+ }
14770
+ if (!isNodeOfType(child, "CallExpression")) return;
14771
+ const childCallee = isNodeOfType(child.callee, "ChainExpression") ? child.callee.expression : stripParenExpression(child.callee);
14772
+ if (isNodeOfType(childCallee, "MemberExpression") && !childCallee.computed && isNodeOfType(childCallee.property, "Identifier") && childCallee.property.name === "abort" && resolveExpressionKey(childCallee.object, context) === refCurrentKey) previousAbortCalls.push(child);
14773
+ });
14774
+ const safeOwnershipAssignments = ownershipAssignments.filter((assignment) => doMatchingNodesCoverEveryPathBeforeUsage(assignment, previousAbortCalls, usageFunction, context));
14775
+ return doMatchingNodesCoverEveryPathBeforeUsage(usage.node, safeOwnershipAssignments, usageFunction, context);
14776
+ };
14754
14777
  const doesReleaseCallMatchUsage = (node, usage, context) => {
14755
14778
  const callNode = isNodeOfType(node, "ChainExpression") ? node.expression : node;
14756
14779
  if (!isNodeOfType(callNode, "CallExpression")) return false;
@@ -14770,6 +14793,7 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
14770
14793
  if (usage.kind === "socket") return usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
14771
14794
  if (usage.handleKey !== null && releaseReceiverKey === usage.handleKey && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub" || releaseVerbName === "close" || releaseVerbName === "unwatch" || releaseVerbName === "unlisten" || BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(releaseVerbName))) return true;
14772
14795
  if (releaseVerbName === "abort" && releaseReceiverKey === getListenerAbortControllerKey(usage, context)) return true;
14796
+ if (releaseVerbName === "abort" && isRetainedAbortControllerRefRelease(callee.object, usage, context)) return true;
14773
14797
  if (usage.receiverKey === null || releaseReceiverKey !== usage.receiverKey) return false;
14774
14798
  const pairedVerbNames = usage.registrationVerbName ? PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB.get(usage.registrationVerbName) : null;
14775
14799
  if (!pairedVerbNames || !matchesPairedReleaseVerb(releaseVerbName, pairedVerbNames)) return false;
@@ -14803,9 +14827,11 @@ const doesReleaseCallMatchUsage = (node, usage, context) => {
14803
14827
  return isNodeOfType(handlerArgument, "Literal") && handlerArgument.value === null;
14804
14828
  }
14805
14829
  if (releaseVerbName === "removeEventListener" || releaseVerbName === "removeListener" || releaseVerbName === "off") {
14806
- const releaseHandler = callNode.arguments?.[1];
14830
+ const usesUnaryListenerSignature = usage.registrationVerbName === "addListener" && isNodeOfType(usage.node, "CallExpression") && usage.node.arguments?.length === 1 && callNode.arguments?.length === 1;
14831
+ const releaseHandler = usesUnaryListenerSignature ? callNode.arguments?.[0] : callNode.arguments?.[1];
14807
14832
  if (!releaseHandler) return releaseVerbName === "off";
14808
- return usage.handlerKey !== null && resolveExpressionKey(releaseHandler, context) === usage.handlerKey;
14833
+ const expectedHandlerKey = usesUnaryListenerSignature ? usage.eventKey : usage.handlerKey;
14834
+ return expectedHandlerKey !== null && resolveExpressionKey(releaseHandler, context) === expectedHandlerKey;
14809
14835
  }
14810
14836
  if (releaseVerbName === "unobserve" && usage.eventKey !== null) return releaseEventKey === usage.eventKey;
14811
14837
  return true;
@@ -14818,8 +14844,7 @@ const isReturnedEffectCleanupFunction = (functionNode) => {
14818
14844
  currentNode = parentNode;
14819
14845
  parentNode = currentNode.parent;
14820
14846
  }
14821
- if (!isNodeOfType(parentNode, "ReturnStatement") || parentNode.argument !== currentNode) return false;
14822
- const effectCallback = findEnclosingFunction$1(parentNode);
14847
+ const effectCallback = isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode ? findEnclosingFunction$1(parentNode) : isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode ? parentNode : null;
14823
14848
  const effectCall = effectCallback?.parent;
14824
14849
  return Boolean(effectCallback && isNodeOfType(effectCall, "CallExpression") && isHookCall$2(effectCall, CLEANUP_EFFECT_HOOK_NAMES));
14825
14850
  };
@@ -15105,6 +15130,11 @@ const doesResourceResultEscape = (resourceNode, allowReturnedResourceEscape, all
15105
15130
  parentNode = currentNode.parent;
15106
15131
  continue;
15107
15132
  }
15133
+ if (isNodeOfType(parentNode, "ConditionalExpression") && (parentNode.consequent === currentNode || parentNode.alternate === currentNode) || isNodeOfType(parentNode, "LogicalExpression") && (parentNode.right === currentNode || parentNode.left === currentNode && parentNode.operator !== "&&")) {
15134
+ currentNode = parentNode;
15135
+ parentNode = currentNode.parent;
15136
+ continue;
15137
+ }
15108
15138
  if (isNodeOfType(parentNode, "VariableDeclarator") && parentNode.init === currentNode && isNodeOfType(parentNode.id, "Identifier") && isNodeOfType(parentNode.parent, "VariableDeclaration") && parentNode.parent.kind === "const") {
15109
15139
  const ownerFunction = findEnclosingFunction$1(resourceNode);
15110
15140
  const resourceSymbol = context.scopes.symbolFor(parentNode.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.9-dev.95f9937",
3
+ "version": "0.7.9-dev.a16e452",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",