oxlint-plugin-react-doctor 0.7.7-dev.afed801 → 0.7.7-dev.c79c897

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 +104 -7
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -11003,6 +11003,14 @@ const PROMISE_CHAIN_METHOD_NAMES$1 = new Set([
11003
11003
  "catch",
11004
11004
  "finally"
11005
11005
  ]);
11006
+ const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
11007
+ const getPromiseChainCallForCallback = (candidate) => {
11008
+ let callbackContainer = candidate.parent;
11009
+ while (callbackContainer && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(callbackContainer.type)) callbackContainer = callbackContainer.parent;
11010
+ if (!isNodeOfType(callbackContainer, "CallExpression")) return null;
11011
+ if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
11012
+ return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
11013
+ };
11006
11014
  const collectEffectInvokedFunctions = (effectCallback) => {
11007
11015
  const invokedFunctions = new Set([effectCallback]);
11008
11016
  const localFunctionBindings = /* @__PURE__ */ new Map();
@@ -11014,7 +11022,6 @@ const collectEffectInvokedFunctions = (effectCallback) => {
11014
11022
  invokedFunctions.add(strippedCandidate);
11015
11023
  pendingFunctions.push(strippedCandidate);
11016
11024
  };
11017
- const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
11018
11025
  while (pendingFunctions.length > 0) {
11019
11026
  const currentFunction = pendingFunctions.pop();
11020
11027
  if (!currentFunction) break;
@@ -11844,6 +11851,94 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
11844
11851
  return didFindUnmountCleanup;
11845
11852
  };
11846
11853
  const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
11854
+ const collectBlockingBooleanStates = (expression, blockedExpressionValue, context) => {
11855
+ const unwrappedExpression = stripParenExpression(expression);
11856
+ if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, context);
11857
+ if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
11858
+ if (!(unwrappedExpression.operator === "||" && blockedExpressionValue || unwrappedExpression.operator === "&&" && !blockedExpressionValue)) return [];
11859
+ return [...collectBlockingBooleanStates(unwrappedExpression.left, blockedExpressionValue, context), ...collectBlockingBooleanStates(unwrappedExpression.right, blockedExpressionValue, context)];
11860
+ }
11861
+ if (isNodeOfType(unwrappedExpression, "BinaryExpression") && [
11862
+ "===",
11863
+ "==",
11864
+ "!==",
11865
+ "!="
11866
+ ].includes(unwrappedExpression.operator)) {
11867
+ const leftValue = readStaticBoolean(unwrappedExpression.left);
11868
+ const rightValue = readStaticBoolean(unwrappedExpression.right);
11869
+ const booleanValue = leftValue ?? rightValue;
11870
+ const comparedKey = resolveExpressionKey(leftValue === null ? unwrappedExpression.left : unwrappedExpression.right, context);
11871
+ if (booleanValue === null || comparedKey === null) return [];
11872
+ return [{
11873
+ key: comparedKey,
11874
+ value: (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==") === blockedExpressionValue ? booleanValue : !booleanValue
11875
+ }];
11876
+ }
11877
+ const expressionKey = resolveExpressionKey(unwrappedExpression, context);
11878
+ return expressionKey === null ? [] : [{
11879
+ key: expressionKey,
11880
+ value: blockedExpressionValue
11881
+ }];
11882
+ };
11883
+ const isDirectEarlyReturnConsequent = (ifStatement) => {
11884
+ if (!isNodeOfType(ifStatement, "IfStatement") || ifStatement.alternate) return false;
11885
+ if (isNodeOfType(ifStatement.consequent, "ReturnStatement")) return true;
11886
+ return isNodeOfType(ifStatement.consequent, "BlockStatement") && ifStatement.consequent.body.length === 1 && isNodeOfType(ifStatement.consequent.body[0], "ReturnStatement");
11887
+ };
11888
+ const collectDeferredUsageGuardStates = (callback, usageNode, context) => {
11889
+ if (!isFunctionLike$2(callback) || callback.async) return [];
11890
+ const guardStates = [];
11891
+ walkAst(callback.body, (child) => {
11892
+ if (child !== callback.body && isFunctionLike$2(child)) return false;
11893
+ if (isNodeOfType(child, "IfStatement") && isDirectEarlyReturnConsequent(child) && doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], callback, context)) guardStates.push(...collectBlockingBooleanStates(child.test, true, context));
11894
+ });
11895
+ let descendant = usageNode;
11896
+ let ancestor = descendant.parent;
11897
+ while (ancestor && ancestor !== callback) {
11898
+ if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant) guardStates.push(...collectBlockingBooleanStates(ancestor.test, false, context));
11899
+ descendant = ancestor;
11900
+ ancestor = ancestor.parent;
11901
+ }
11902
+ return guardStates;
11903
+ };
11904
+ const cleanupReturnInvalidatesGuard = (cleanupReturn, guardState, context) => {
11905
+ if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
11906
+ const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
11907
+ if (!cleanupFunction || !isFunctionLike$2(cleanupFunction) || cleanupFunction.async) return false;
11908
+ let didInvalidateGuard = false;
11909
+ walkAst(cleanupFunction.body, (child) => {
11910
+ if (didInvalidateGuard) return false;
11911
+ if (child !== cleanupFunction.body && isFunctionLike$2(child)) return false;
11912
+ if (isNodeOfType(child, "AssignmentExpression") && child.operator === "=" && resolveExpressionKey(child.left, context) === guardState.key && readStaticBoolean(child.right) === guardState.value && context.cfg.isUnconditionalFromEntry(child)) {
11913
+ didInvalidateGuard = true;
11914
+ return false;
11915
+ }
11916
+ });
11917
+ return didInvalidateGuard;
11918
+ };
11919
+ const deferredUsageWritesGuardBeforeUsage = (callback, usageNode, guardState, context) => {
11920
+ const usageStart = getRangeStart(usageNode);
11921
+ if (!isFunctionLike$2(callback) || usageStart === null) return true;
11922
+ let didWriteGuard = false;
11923
+ walkAst(callback.body, (child) => {
11924
+ if (didWriteGuard) return false;
11925
+ if (child !== callback.body && isFunctionLike$2(child)) return false;
11926
+ const childStart = getRangeStart(child);
11927
+ if (childStart === null || childStart >= usageStart) return;
11928
+ const writtenExpression = isNodeOfType(child, "AssignmentExpression") ? child.left : isNodeOfType(child, "UpdateExpression") ? child.argument : null;
11929
+ if (writtenExpression && resolveExpressionKey(writtenExpression, context) === guardState.key) {
11930
+ didWriteGuard = true;
11931
+ return false;
11932
+ }
11933
+ });
11934
+ return didWriteGuard;
11935
+ };
11936
+ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
11937
+ const usageFunction = findEnclosingFunction(usage.node);
11938
+ const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
11939
+ if (usage.handleKey === null || !usageFunction || usageFunction === callback || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
11940
+ return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
11941
+ };
11847
11942
  const effectHasCleanupForUsage = (callback, usage, context) => {
11848
11943
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
11849
11944
  if (callback.async) return false;
@@ -11883,6 +11978,7 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
11883
11978
  if (!cleanupFunction || !isFunctionLike$2(cleanupFunction)) return;
11884
11979
  if (doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context)) matchingCleanupReturns.push(child);
11885
11980
  });
11981
+ if (hasGuardedDeferredCleanup(callback, usage, matchingCleanupReturns, context)) return true;
11886
11982
  return doMatchingNodesCoverEveryPathAfterUsage(resolveCleanupPathAnchor(usage.node, callback, context), matchingCleanupReturns, context);
11887
11983
  };
11888
11984
  const findFirstUsageWithoutCleanup = (callback, usages, context) => {
@@ -25449,8 +25545,11 @@ const unwrapUseCallback = (node) => {
25449
25545
  const callee = node.callee;
25450
25546
  return isNodeOfType(callee, "Identifier") && callee.name === "useCallback" || isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useCallback" ? node.arguments?.[0] : node;
25451
25547
  };
25548
+ const hasParameterDefinition = (ref) => Boolean(ref.resolved?.defs.some((definition) => definition.type === "Parameter"));
25452
25549
  const resolveToFunction = (ref) => {
25453
- const definitionNode = ref.resolved?.defs[0]?.node;
25550
+ const definition = ref.resolved?.defs[0];
25551
+ if (!definition || hasParameterDefinition(ref)) return null;
25552
+ const definitionNode = definition.node;
25454
25553
  if (!definitionNode) return null;
25455
25554
  if (isFunctionLike$2(definitionNode)) return definitionNode;
25456
25555
  if (isNodeOfType(definitionNode, "VariableDeclarator")) {
@@ -25860,7 +25959,7 @@ const isCleanupReturnArgument = (analysis, node) => {
25860
25959
  if (isNodeOfType(node, "MemberExpression")) return true;
25861
25960
  if (isNodeOfType(node, "Identifier")) {
25862
25961
  const ref = getRef(analysis, node);
25863
- if (ref && resolveToFunction(ref)) return true;
25962
+ if (ref && (hasParameterDefinition(ref) || resolveToFunction(ref))) return true;
25864
25963
  }
25865
25964
  if (isNodeOfType(node, "ConditionalExpression")) return isCleanupReturnArgument(analysis, node.consequent) || isCleanupReturnArgument(analysis, node.alternate);
25866
25965
  return false;
@@ -26332,7 +26431,7 @@ const resolveWrappedCallable = (analysis, node) => {
26332
26431
  if (isNodeOfType(candidate, "Identifier")) {
26333
26432
  const reference = getRef(analysis, candidate);
26334
26433
  if (!reference) return null;
26335
- if (reference.resolved?.defs.some((definition) => definition.type === "Parameter" || definition.type === "ImportBinding")) return null;
26434
+ if (reference.resolved?.defs.some((definition) => definition.type === "ImportBinding")) return null;
26336
26435
  const resolved = resolveToFunction(reference);
26337
26436
  if (resolved) return resolved;
26338
26437
  const definitionNode = reference.resolved?.defs[0]?.node;
@@ -38141,8 +38240,6 @@ const getWrapperHookWrappedFunction = (initializer) => {
38141
38240
  if (!wrapped || !isFunctionLike$2(wrapped)) return null;
38142
38241
  return wrapped;
38143
38242
  };
38144
- const hasParameterDef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "Parameter"));
38145
- const resolvesToFunctionBinding = (ref) => !hasParameterDef(ref) && Boolean(resolveToFunction(ref));
38146
38243
  const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
38147
38244
  const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
38148
38245
  if (!isProp(analysis, innerRef)) return false;
@@ -38463,7 +38560,7 @@ const noPassDataToParent = defineRule({
38463
38560
  if (isConstant(argRef)) return false;
38464
38561
  if (isParentWiredHookResultRef(analysis, argRef)) return false;
38465
38562
  if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
38466
- if (resolvesToFunctionBinding(argRef)) return false;
38563
+ if (resolveToFunction(argRef)) return false;
38467
38564
  const argIdentifier = argRef.identifier;
38468
38565
  if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
38469
38566
  if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.7-dev.afed801",
3
+ "version": "0.7.7-dev.c79c897",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",