-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Enhancement: [only-throw-error] allow re-throwing a caught error #10376
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Counter argument to your request - it's no safer really and can lead to ugly behaviour: try {
// imagine this is in a 3rd party library
throw 'lol oops';
} catch (e) {
// this seems safe and wouldn't error in the rule despite it being unsafe.
throw e;
} |
Yes, it's not as safe as using the rule without any options, but I think it is safer than having I don't know if it's too niche, though. |
I am +1. Partially handling/logging, then rethrowing, an error is a valid pattern that does happen. In other languages (for more technical reasons to do with stack traces) there is even a dedicated This is largely analogous to #8538, where propagating a bad This probably makes sense as an option here too. |
I like this idea in theory, but don't think it can be done well practically. Rules targeting specific syntax uses are brittle to refactors. There's no real difference in developer intent between re-throwing directly inside a function doDangerousWork(work: () => void) {
try {
work()
} catch (error) {
logAndRethrow(error);
}
}
function logAndRethrow(error: unknown) {
console.log("oh no", error);
throw error;
} For that reason I'm -1 on this issue. My suggestion would be to reuse the existing |
I get what you're saying here, but am not swayed. The trouble is Why should a user who is trying to do this function doDangerousWork(work) {
try {
work()
} catch (error: unknown) {
console.error('critical thing failed!')
throw error;
}
} be forced to pick between working around the option for an easily-detectable valid pattern function doDangerousWork() {
try {
work()
} catch (error: unknown) {
console.error('critical thing failed!')
// yuck
if (error instanceof Error) {
throw error;
}
throw new Error("error wasn't an error", { cause: error });
}
} or disabling the option and allowing unrelated, bad code function doDangerousWork(args: unknown) {
throw args;
} ? In effect, it encourages eslint-disable-averse users to write a helper to rethrow, which is objectively worse code. function rethrow(error: unknown): never {
// eslint-disable-next-line @typescript-eslint/only-throw-error -- utility to allow rethrowing without linter complaints
throw error;
}
function doDangerousWork(work) {
try {
work()
} catch (error: unknown) {
console.error('critical thing failed!')
rethrow(error);
}
}
function somethingElse(x: unknown) {
rethrow(x); // oops - used rethrowing utility outside of error-handling context.
} Note that another pattern that this messes up is the ability to do conditional behavior in a function doDangerousWork() {
let didThrow = false;
try {
work();
} catch (x) {
didThrow = true;
// why do i have to do this when i am making no attempt to
// handle the error or throw a new exception? I'm just trying
// to observe whether there was an exception.
if (error instanceof Error) {
throw error;
} else {
throw new Error('error wasnt an error', { cause: error });
}
} finally {
doConditionalCleanup(didThrow);
}
} I think there's a meaningful distinction between whether an exception is being created or whether it's being rethrown because the language forces you to, and I don't think we should put the responsibility to repair a possibly-invalid exception during an obviously-detectable rethrowing pattern. Especially since exceptions never have valid types in a catch block. Taking the counterargument ad absurdum, we could even say this line should be flagged since it possibly throws an exception that isn't an error function doWork() {
doesntThrow();
} and therefore every function call in the program should be fixed to function doWork() {
try {
doesntThrow();
} catch (e) {
if (e instanceof Error) {
throw e;
} else {
throw new Error("non-error Exception", { cause: e });
}
}
} |
So, in conclusion, I'd rather take the downside of having a very, very natural and understandable syntax heuristic (which is probably the only correct syntax anyway to get technically clean stack traces), than the downside of flagging on literally every rethrow. |
Yeah, that's fair. I'm still not in favor of this but I'm not opposed. It does make sense that re-throwing inside an else is a reasonable & reasonably common strategy. Thanks for the pushback 😄. Accepting PRs, with the note that docs should very clearly describe why the option is super limited. |
Would it make sense to have this option enabled by default? I've enabled both This is similar to the original plan to have the option added in #8538 enabled by default in a future major. I think this is a significant pain point in using the rule on a somewhat large codebase. |
🤔 maybe enabled in recommended, disabled in strict? 🤷 I wouldn't be upset by just always having it enabled by default. |
+1 to just enabled by default always. @ronami are you asking because you plan to work on this issue? I started working on this today, so I figure I'll share my notes if so; my branch is here: main...kirkwaiblinger:typescript-eslint:only-throw-error-rethrow |
Oh, not at all. Please go ahead and continue. This will be helpful to me too on my own projects. Thanks for taking this! I really enjoyed reading your reasoning in this discussion. I think you've explained why this option is important really well (especially on the difference between creating a bad throw and propagating an existing one). I didn't add any comments because I didn't have anything useful to add, thanks! ❤️ |
Before You File a Proposal Please Confirm You Have Done The Following...
My proposal is suitable for this project
Link to the rule's documentation
https://typescript-eslint.io/rules/only-throw-error
Description
only-throw-error
(andprefer-promise-reject-errors
) disallow throwing non-error values.only-throw-error
(and with #10375prefer-promise-reject-errors
too) can be configured to allow throwingunknown
andany
types in addition to error values.It's necessary to enable
allowThrowingUnknown
so re-throwing a cached error is valid (orany
in sometsconfig.json
configurations):While configuring
allowThrowingUnknown
makes the above pass, it allows potentially unsafe throwing of anunknown
type:This issue proposes an additional option, a stricter form of
allowThrowingUnknown,
which will allow re-throwing a caught error.This is relevant both to
only-throw-error
andprefer-promise-reject-errors
.Fail
Pass
Additional Info
No response
The text was updated successfully, but these errors were encountered: