oxlint-plugin-react-doctor 0.8.3-dev.01820bb → 0.8.3-dev.416fdcc

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 +70 -30
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -8262,6 +8262,56 @@ const isNodeReachableWithinFunction = (node, context) => {
8262
8262
  return false;
8263
8263
  };
8264
8264
  //#endregion
8265
+ //#region src/plugin/utils/is-proven-non-throwing-built-in-call.ts
8266
+ const NON_THROWING_CONSOLE_METHOD_NAMES = new Set([
8267
+ "debug",
8268
+ "error",
8269
+ "info",
8270
+ "log",
8271
+ "trace",
8272
+ "warn"
8273
+ ]);
8274
+ const NON_THROWING_NUMBER_BINARY_OPERATORS = new Set([
8275
+ "+",
8276
+ "-",
8277
+ "*",
8278
+ "/",
8279
+ "%",
8280
+ "**"
8281
+ ]);
8282
+ const isGlobalPerformanceNowCall = (callNode, scopes) => {
8283
+ const callee = stripParenExpression(callNode.callee);
8284
+ if (!isNodeOfType(callee, "MemberExpression") || callNode.arguments.length !== 0) return false;
8285
+ const receiver = stripParenExpression(callee.object);
8286
+ return isNodeOfType(receiver, "Identifier") && receiver.name === "performance" && scopes.isGlobalReference(receiver) && getStaticPropertyName(callee) === "now";
8287
+ };
8288
+ const isProvenNonThrowingNumberExpression = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
8289
+ const strippedExpression = stripParenExpression(expression);
8290
+ if (isNodeOfType(strippedExpression, "Literal")) return typeof strippedExpression.value === "number";
8291
+ if (isNodeOfType(strippedExpression, "CallExpression")) return isGlobalPerformanceNowCall(strippedExpression, scopes);
8292
+ if (isNodeOfType(strippedExpression, "Identifier")) {
8293
+ const symbol = scopes.symbolFor(strippedExpression);
8294
+ if (symbol?.kind !== "const" || !symbol.initializer || symbol.declarationNode.range[0] >= strippedExpression.range[0] || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return false;
8295
+ visitedSymbolIds.add(symbol.id);
8296
+ return isProvenNonThrowingNumberExpression(symbol.initializer, scopes, visitedSymbolIds);
8297
+ }
8298
+ if (isNodeOfType(strippedExpression, "UnaryExpression")) return (strippedExpression.operator === "+" || strippedExpression.operator === "-") && isProvenNonThrowingNumberExpression(strippedExpression.argument, scopes, visitedSymbolIds);
8299
+ if (!isNodeOfType(strippedExpression, "BinaryExpression")) return false;
8300
+ if (!NON_THROWING_NUMBER_BINARY_OPERATORS.has(strippedExpression.operator)) return false;
8301
+ return isProvenNonThrowingNumberExpression(strippedExpression.left, scopes, new Set(visitedSymbolIds)) && isProvenNonThrowingNumberExpression(strippedExpression.right, scopes, new Set(visitedSymbolIds));
8302
+ };
8303
+ const isProvenNonThrowingBuiltInCall = (callNode, scopes) => {
8304
+ if (isGlobalPerformanceNowCall(callNode, scopes)) return true;
8305
+ const callee = stripParenExpression(callNode.callee);
8306
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
8307
+ const receiver = stripParenExpression(callee.object);
8308
+ if (!isNodeOfType(receiver, "Identifier") || !scopes.isGlobalReference(receiver)) return false;
8309
+ const methodName = getStaticPropertyName(callee);
8310
+ if (receiver.name === "console") return NON_THROWING_CONSOLE_METHOD_NAMES.has(methodName ?? "");
8311
+ const firstArgument = callNode.arguments[0];
8312
+ return Boolean(receiver.name === "Math" && methodName === "round" && callNode.arguments.length === 1 && firstArgument && isProvenNonThrowingNumberExpression(firstArgument, scopes));
8313
+ };
8314
+ //#endregion
8265
8315
  //#region src/plugin/utils/serialize-reference-key.ts
8266
8316
  const serializeReferenceKey = ({ node, scopes }) => {
8267
8317
  const expression = stripParenExpression(node);
@@ -9039,11 +9089,10 @@ const functionHasPotentialSynchronousThrow = (functionNode, classBody, scopes, b
9039
9089
  }
9040
9090
  if (!isNodeOfType(candidate, "CallExpression")) return;
9041
9091
  if (cleanupReleaseKeys(candidate, scopes, classBody).length > 0) return;
9092
+ if (isProvenNonThrowingBuiltInCall(candidate, scopes)) return;
9042
9093
  const callee = stripParenExpression(candidate.callee);
9043
9094
  if (isNodeOfType(callee, "MemberExpression")) {
9044
- const receiver = stripParenExpression(callee.object);
9045
- if (isNodeOfType(receiver, "Identifier") && receiver.name === "console" && scopes.isGlobalReference(receiver)) return;
9046
- if (isNodeOfType(receiver, "ThisExpression")) {
9095
+ if (isNodeOfType(stripParenExpression(callee.object), "ThisExpression")) {
9047
9096
  const memberName = getStaticPropertyName(callee);
9048
9097
  const memberFunction = memberName ? classMemberFunction(classBody, memberName, candidate) : null;
9049
9098
  if (memberFunction && !functionHasPotentialSynchronousThrow(memberFunction, classBody, scopes, Number.POSITIVE_INFINITY, new Set(visitedFunctions))) return;
@@ -18015,7 +18064,7 @@ const hasPotentialInterruptionAfterGuard = (callback, guardState, usageNode, con
18015
18064
  if (child !== callback.body && isFunctionLike$1(child)) return false;
18016
18065
  const childStart = getRangeStart(child);
18017
18066
  if (childStart === null || childStart <= guardStart || childStart >= usageStart) return;
18018
- if (isNodeOfType(child, "CallExpression") || isNodeOfType(child, "AwaitExpression") || isNodeOfType(child, "YieldExpression")) {
18067
+ if (isNodeOfType(child, "AwaitExpression") || isNodeOfType(child, "YieldExpression") || isNodeOfType(child, "CallExpression") && !isProvenNonThrowingBuiltInCall(child, context.scopes)) {
18019
18068
  if (canNodeReachLaterNodeWithinFunction(child, usageNode, callback, context) || canInterruptionReachUsageThroughCatch(child, usageNode, callback, context)) {
18020
18069
  hasPotentialInterruption = true;
18021
18070
  return false;
@@ -18181,7 +18230,7 @@ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) =>
18181
18230
  for (const argument of usage.node.arguments ?? []) walkAst(argument, (argumentChild) => {
18182
18231
  if (hasPotentialInterruption) return false;
18183
18232
  if (isFunctionLike$1(argumentChild)) return false;
18184
- if (isNodeOfType(argumentChild, "CallExpression") || isNodeOfType(argumentChild, "AwaitExpression") || isNodeOfType(argumentChild, "YieldExpression")) {
18233
+ if (isNodeOfType(argumentChild, "AwaitExpression") || isNodeOfType(argumentChild, "YieldExpression") || isNodeOfType(argumentChild, "CallExpression") && !isProvenNonThrowingBuiltInCall(argumentChild, context.scopes)) {
18185
18234
  hasPotentialInterruption = true;
18186
18235
  return false;
18187
18236
  }
@@ -63409,10 +63458,9 @@ const chainCarriesRejectionHandler = (node, scopes) => {
63409
63458
  }
63410
63459
  if (isNodeOfType(child, "CallExpression")) {
63411
63460
  const callee = stripParenExpression(child.callee);
63412
- const receiver = isNodeOfType(callee, "MemberExpression") ? stripParenExpression(callee.object) : null;
63413
- const isConsoleCall = isNodeOfType(receiver, "Identifier") && receiver.name === "console" && (!scopes || scopes.isGlobalReference(receiver));
63461
+ const isKnownNonThrowingBuiltInCall = Boolean(scopes && isProvenNonThrowingBuiltInCall(child, scopes));
63414
63462
  const localFunction = scopes && isNodeOfType(callee, "Identifier") ? resolveExactLocalFunction(callee, scopes) : null;
63415
- if (!isConsoleCall && !isPromiseResolveCall(child, scopes) && !chainCarriesRejectionHandler(child, scopes) && (!localFunction || !scopes || subtreeCanThrowSynchronously(localFunction, localFunction, scopes))) {
63463
+ if (!isKnownNonThrowingBuiltInCall && !isPromiseResolveCall(child, scopes) && !chainCarriesRejectionHandler(child, scopes) && (!localFunction || !scopes || subtreeCanThrowSynchronously(localFunction, localFunction, scopes))) {
63416
63464
  canReject = true;
63417
63465
  return false;
63418
63466
  }
@@ -63889,6 +63937,7 @@ const helperHasUnhandledSynchronousCall = (helper, depth, scopes, visitedFunctio
63889
63937
  ancestor = ancestor.parent ?? null;
63890
63938
  }
63891
63939
  if (isInsideNonRethrowingTry(child, helper)) return;
63940
+ if (scopes && isProvenNonThrowingBuiltInCall(child, scopes)) return;
63892
63941
  if (isPromiseResolveCall(child, scopes) || chainCarriesRejectionHandler(child, scopes) || isSyncArrayLiteralMethodCall(child, scopes) || isThunkActionDispatchCall(child) || isNeverRejectingPromiseCombinatorCall(child, depth, scopes)) return;
63893
63942
  const callee = stripParenExpression(child.callee);
63894
63943
  if (scopes && isNodeOfType(callee, "Identifier") && isReactHookResultReference(callee, STATE_HOOK_NAMES, 1, scopes)) return;
@@ -63898,7 +63947,6 @@ const helperHasUnhandledSynchronousCall = (helper, depth, scopes, visitedFunctio
63898
63947
  }
63899
63948
  if (isNodeOfType(callee, "MemberExpression")) {
63900
63949
  const receiver = stripParenExpression(callee.object);
63901
- if (isNodeOfType(receiver, "Identifier") && receiver.name === "console" && (!scopes || scopes.isGlobalReference(receiver))) return;
63902
63950
  if (getStaticPropertyName(callee) === "push" && isNodeOfType(receiver, "Identifier")) {
63903
63951
  const receiverSymbol = scopes?.symbolFor(receiver);
63904
63952
  if (isNodeOfType(receiverSymbol?.initializer ? stripParenExpression(receiverSymbol.initializer) : null, "ArrayExpression") && receiverSymbol?.references.every((reference) => {
@@ -64368,16 +64416,20 @@ const areOnExclusiveBranches = (first, second, functionNode) => {
64368
64416
  };
64369
64417
  const REACT_SETTER_CALLEE_PATTERN = /^set[A-Z]/;
64370
64418
  const isProvenNonThrowingSynchronousCall = (callNode, context) => {
64419
+ if (isProvenNonThrowingBuiltInCall(callNode, context.scopes)) return true;
64371
64420
  const callee = stripParenExpression(callNode.callee);
64372
64421
  if (isNodeOfType(callee, "Identifier")) {
64373
64422
  if (isReactHookResultReference(callee, STATE_HOOK_NAMES, 1, context.scopes) || context.scopes.isGlobalReference(callee) && REACT_SETTER_CALLEE_PATTERN.test(callee.name)) return true;
64423
+ if (context.scopes.isGlobalReference(callee) && callee.name === "String") {
64424
+ const firstArgument = callNode.arguments[0];
64425
+ const strippedArgument = firstArgument ? stripParenExpression(firstArgument) : null;
64426
+ return Boolean(callNode.arguments.length === 1 && strippedArgument && isNodeOfType(strippedArgument, "Identifier") && context.scopes.symbolFor(strippedArgument)?.kind === "catch-clause-parameter");
64427
+ }
64374
64428
  const localFunction = resolveExactLocalFunction(callee, context.scopes);
64375
64429
  if (localFunction && isFunctionLike$1(localFunction) && !localFunction.async) return !subtreeCanThrowSynchronously(localFunction, localFunction, context.scopes) && !helperHasUnhandledSynchronousCall(localFunction, NEVER_REJECTING_ANALYSIS_MAX_DEPTH, context.scopes);
64376
64430
  return false;
64377
64431
  }
64378
- if (!isNodeOfType(callee, "MemberExpression")) return false;
64379
- const receiver = stripParenExpression(callee.object);
64380
- return Boolean(isNodeOfType(receiver, "Identifier") && receiver.name === "console" && context.scopes.isGlobalReference(receiver));
64432
+ return false;
64381
64433
  };
64382
64434
  const subtreeHasAbruptSynchronousOperation = (root, functionBoundary, context) => {
64383
64435
  let canCompleteAbruptly = false;
@@ -73606,14 +73658,6 @@ const REJECTING_PROMISE_COMBINATOR_NAMES = new Set([
73606
73658
  const MAX_INITIATOR_RESOLUTION_DEPTH = 3;
73607
73659
  const STATE_DISPATCHER_HOOK_NAMES = new Set(["useState", "useReducer"]);
73608
73660
  const REF_HOOK_NAMES = new Set(["useRef"]);
73609
- const NON_REJECTING_CONSOLE_METHOD_NAMES = new Set([
73610
- "debug",
73611
- "error",
73612
- "info",
73613
- "log",
73614
- "trace",
73615
- "warn"
73616
- ]);
73617
73661
  const MESSAGE$26 = "This promise chain runs in an effect, ends in a `.then` that sets state or mutates a ref, and has no `.catch` or enclosing try/catch, so a rejection leaves the state unset and surfaces as an unhandled rejection. Add a `.catch` handler on the chain (`.finally` does not count).";
73618
73662
  const isKnownNonThenableHandlerReturn = (expression, context, visitedBindingIdentifiers = /* @__PURE__ */ new Set()) => {
73619
73663
  const strippedExpression = stripParenExpression(expression);
@@ -73650,6 +73694,10 @@ const isKnownNonRejectingHandler = (argument, context) => {
73650
73694
  return false;
73651
73695
  }
73652
73696
  if (!isNodeOfType(child, "CallExpression")) return;
73697
+ if (isProvenNonThrowingBuiltInCall(child, context.scopes)) {
73698
+ didFindKnownNonRejectingCall = true;
73699
+ return;
73700
+ }
73653
73701
  const callee = stripParenExpression(child.callee);
73654
73702
  if (isNodeOfType(callee, "Identifier") && isReactHookResultReference(callee, STATE_DISPATCHER_HOOK_NAMES, 1, context.scopes)) {
73655
73703
  if (context.scopes.symbolFor(callee)?.references.every((reference) => reference.flag === "read")) {
@@ -73657,13 +73705,6 @@ const isKnownNonRejectingHandler = (argument, context) => {
73657
73705
  return;
73658
73706
  }
73659
73707
  }
73660
- if (isNodeOfType(callee, "MemberExpression")) {
73661
- const receiver = stripParenExpression(callee.object);
73662
- if (isNodeOfType(receiver, "Identifier") && receiver.name === "console" && context.scopes.isGlobalReference(receiver) && NON_REJECTING_CONSOLE_METHOD_NAMES.has(getStaticPropertyName(callee) ?? "")) {
73663
- didFindKnownNonRejectingCall = true;
73664
- return;
73665
- }
73666
- }
73667
73708
  canReject = true;
73668
73709
  return false;
73669
73710
  });
@@ -73672,11 +73713,10 @@ const isKnownNonRejectingHandler = (argument, context) => {
73672
73713
  const isTrustedNonThrowingMethodCallee = (member, context) => {
73673
73714
  const parent = member.parent;
73674
73715
  if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== member) return false;
73716
+ if (isProvenNonThrowingBuiltInCall(parent, context.scopes)) return true;
73675
73717
  const receiver = stripParenExpression(member.object);
73676
73718
  if (!isNodeOfType(receiver, "Identifier") || !context.scopes.isGlobalReference(receiver)) return false;
73677
- const methodName = getStaticPropertyName(member);
73678
- if (receiver.name === "console") return NON_REJECTING_CONSOLE_METHOD_NAMES.has(methodName ?? "");
73679
- return receiver.name === "Promise" && methodName === "resolve";
73719
+ return receiver.name === "Promise" && getStaticPropertyName(member) === "resolve";
73680
73720
  };
73681
73721
  const handlerHasPotentiallyThrowingMemberRead = (argument, context) => {
73682
73722
  if (!argument) return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.8.3-dev.01820bb",
3
+ "version": "0.8.3-dev.416fdcc",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",