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.
- package/dist/index.js +70 -30
- 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
|
-
|
|
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, "
|
|
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, "
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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;
|