mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(linter/unicorn/error-message): support SuppressedError messages (#25375)
## Summary - Recognize global `SuppressedError` constructors and inspect their third message argument. - Avoid false positives for shadowed error constructors and calls with a preceding spread argument. - Cover supported `SuppressedError` cases while leaving general static-evaluation cases documented.
This commit is contained in:
1 parent
e1659205c9
commit
19109cd139
3 files changed
+140
-25
No files matched your search
@@ -4,6 +4,7 @@ use oxc_ast::{
|
||||
};
|
||||
use oxc_diagnostics::OxcDiagnostic;
|
||||
use oxc_macros::declare_oxc_lint;
|
||||
use oxc_semantic::IsGlobalReference;
|
||||
use oxc_span::Span;
|
||||
|
||||
use crate::{AstNode, context::LintContext, rule::Rule, utils::BUILT_IN_ERRORS};
|
||||
@@ -87,13 +88,26 @@ impl Rule for ErrorMessage {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is `SpreadElement` before message
|
||||
if matches!(args.first(), Some(Argument::SpreadElement(_))) {
|
||||
if !callee.is_global_reference(ctx.scoping()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let constructor_name = &callee.name;
|
||||
let message_argument_idx = usize::from(callee.name == "AggregateError");
|
||||
let message_argument_idx = match callee.name.as_str() {
|
||||
"AggregateError" => 1,
|
||||
"SuppressedError" => 2,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
// A spread at or before the message position makes the argument order unknown.
|
||||
if args
|
||||
.iter()
|
||||
.take(message_argument_idx + 1)
|
||||
.any(|arg| matches!(arg, Argument::SpreadElement(_)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let message_argument = args.get(message_argument_idx);
|
||||
|
||||
let Some(arg) = message_argument else {
|
||||
@@ -144,11 +158,10 @@ fn test() {
|
||||
"/* global x */
|
||||
const a = x;
|
||||
throw x;",
|
||||
// TODO: Get this passing.
|
||||
// "const Error = function () {};
|
||||
// const err = new Error({
|
||||
// name: 'Unauthorized',
|
||||
// });",
|
||||
"const Error = function () {};
|
||||
const err = new Error({
|
||||
name: 'Unauthorized',
|
||||
});",
|
||||
r#"new AggregateError(errors, "message")"#,
|
||||
"new NotAggregateError(errors)",
|
||||
"new AggregateError(...foo)",
|
||||
@@ -161,8 +174,11 @@ fn test() {
|
||||
"new SuppressedError(...foo)",
|
||||
r#"new SuppressedError(...foo, "")"#,
|
||||
"new SuppressedError(error, suppressed, ...foo)",
|
||||
r#"new SuppressedError(error, ...foo, "")"#,
|
||||
r#"new SuppressedError(error, suppressed, message, "")"#,
|
||||
r#"new SuppressedError("", "", message, "")"#,
|
||||
"const SuppressedError = function () {};
|
||||
new SuppressedError(error, suppressed);",
|
||||
];
|
||||
|
||||
let fail = vec![
|
||||
@@ -180,7 +196,7 @@ fn test() {
|
||||
throw err;",
|
||||
"const foo = new TypeError()",
|
||||
"const foo = new SyntaxError()",
|
||||
// TODO: Get all of the comments tests here passing.
|
||||
// These require general static-value evaluation, which the linter does not expose yet.
|
||||
// "const errorMessage = Object.freeze({errorMessage: 1}).errorMessage;
|
||||
// throw new Error(errorMessage)",
|
||||
"throw new Error([])",
|
||||
@@ -207,26 +223,26 @@ fn test() {
|
||||
// "new AggregateError(errors, {foo: 0}.foo)",
|
||||
// "new AggregateError(errors, lineNumber=2)",
|
||||
"const error = new AggregateError;",
|
||||
// TODO: Update the rule to get these tests working.
|
||||
// "new SuppressedError(error, suppressed,)",
|
||||
// "new SuppressedError(error,)",
|
||||
// "new SuppressedError()",
|
||||
// "SuppressedError(error, suppressed,)",
|
||||
// "SuppressedError(error,)",
|
||||
// "SuppressedError()",
|
||||
// r#"new SuppressedError(error, suppressed, "")"#,
|
||||
// "new SuppressedError(error, suppressed, ``)",
|
||||
// r#"new SuppressedError(error, suppressed, "", options)"#,
|
||||
"new SuppressedError(error, suppressed,)",
|
||||
"new SuppressedError(error,)",
|
||||
"new SuppressedError()",
|
||||
"SuppressedError(error, suppressed,)",
|
||||
"SuppressedError(error,)",
|
||||
"SuppressedError()",
|
||||
r#"new SuppressedError(error, suppressed, "")"#,
|
||||
"new SuppressedError(error, suppressed, ``)",
|
||||
r#"new SuppressedError(error, suppressed, "", options)"#,
|
||||
// These require general static-value evaluation, which the linter does not expose yet.
|
||||
// "const errorMessage = Object.freeze({errorMessage: 1}).errorMessage;
|
||||
// throw new SuppressedError(error, suppressed, errorMessage)",
|
||||
// "new SuppressedError(error, suppressed, [])",
|
||||
// "new SuppressedError(error, suppressed, [foo])",
|
||||
"new SuppressedError(error, suppressed, [])",
|
||||
"new SuppressedError(error, suppressed, [foo])",
|
||||
// "new SuppressedError(error, suppressed, [0][0])",
|
||||
// "new SuppressedError(error, suppressed, {})",
|
||||
// "new SuppressedError(error, suppressed, {foo})",
|
||||
"new SuppressedError(error, suppressed, {})",
|
||||
"new SuppressedError(error, suppressed, {foo})",
|
||||
// "new SuppressedError(error, suppressed, {foo: 0}.foo)",
|
||||
// "new SuppressedError(error, suppressed, lineNumber=2)",
|
||||
// "const error = new SuppressedError;",
|
||||
"const error = new SuppressedError;",
|
||||
];
|
||||
|
||||
Tester::new(ErrorMessage::NAME, ErrorMessage::PLUGIN, pass, fail).test_and_snapshot();
|
||||
|
||||
@@ -180,3 +180,101 @@ source: crates/oxc_linter/src/tester.rs
|
||||
· ──────────────────
|
||||
╰────
|
||||
help: A descriptive message makes the error easier to debug when it is caught or logged.
|
||||
|
||||
⚠ unicorn(error-message): Pass a message to the SuppressedError constructor.
|
||||
╭─[error_message.tsx:1:1]
|
||||
1 │ new SuppressedError(error, suppressed,)
|
||||
· ───────────────────────────────────────
|
||||
╰────
|
||||
help: A descriptive message makes the error easier to debug when it is caught or logged.
|
||||
|
||||
⚠ unicorn(error-message): Pass a message to the SuppressedError constructor.
|
||||
╭─[error_message.tsx:1:1]
|
||||
1 │ new SuppressedError(error,)
|
||||
· ───────────────────────────
|
||||
╰────
|
||||
help: A descriptive message makes the error easier to debug when it is caught or logged.
|
||||
|
||||
⚠ unicorn(error-message): Pass a message to the SuppressedError constructor.
|
||||
╭─[error_message.tsx:1:1]
|
||||
1 │ new SuppressedError()
|
||||
· ─────────────────────
|
||||
╰────
|
||||
help: A descriptive message makes the error easier to debug when it is caught or logged.
|
||||
|
||||
⚠ unicorn(error-message): Pass a message to the SuppressedError constructor.
|
||||
╭─[error_message.tsx:1:1]
|
||||
1 │ SuppressedError(error, suppressed,)
|
||||
· ───────────────────────────────────
|
||||
╰────
|
||||
help: A descriptive message makes the error easier to debug when it is caught or logged.
|
||||
|
||||
⚠ unicorn(error-message): Pass a message to the SuppressedError constructor.
|
||||
╭─[error_message.tsx:1:1]
|
||||
1 │ SuppressedError(error,)
|
||||
· ───────────────────────
|
||||
╰────
|
||||
help: A descriptive message makes the error easier to debug when it is caught or logged.
|
||||
|
||||
⚠ unicorn(error-message): Pass a message to the SuppressedError constructor.
|
||||
╭─[error_message.tsx:1:1]
|
||||
1 │ SuppressedError()
|
||||
· ─────────────────
|
||||
╰────
|
||||
help: A descriptive message makes the error easier to debug when it is caught or logged.
|
||||
|
||||
⚠ unicorn(error-message): Error message should not be an empty string.
|
||||
╭─[error_message.tsx:1:40]
|
||||
1 │ new SuppressedError(error, suppressed, "")
|
||||
· ──
|
||||
╰────
|
||||
help: Provide a non-empty string that describes what went wrong.
|
||||
|
||||
⚠ unicorn(error-message): Error message should not be an empty string.
|
||||
╭─[error_message.tsx:1:40]
|
||||
1 │ new SuppressedError(error, suppressed, ``)
|
||||
· ──
|
||||
╰────
|
||||
help: Provide a non-empty string that describes what went wrong.
|
||||
|
||||
⚠ unicorn(error-message): Error message should not be an empty string.
|
||||
╭─[error_message.tsx:1:40]
|
||||
1 │ new SuppressedError(error, suppressed, "", options)
|
||||
· ──
|
||||
╰────
|
||||
help: Provide a non-empty string that describes what went wrong.
|
||||
|
||||
⚠ unicorn(error-message): Error message should be a string.
|
||||
╭─[error_message.tsx:1:40]
|
||||
1 │ new SuppressedError(error, suppressed, [])
|
||||
· ──
|
||||
╰────
|
||||
help: The first argument to an error constructor should be a string describing the error.
|
||||
|
||||
⚠ unicorn(error-message): Error message should be a string.
|
||||
╭─[error_message.tsx:1:40]
|
||||
1 │ new SuppressedError(error, suppressed, [foo])
|
||||
· ─────
|
||||
╰────
|
||||
help: The first argument to an error constructor should be a string describing the error.
|
||||
|
||||
⚠ unicorn(error-message): Error message should be a string.
|
||||
╭─[error_message.tsx:1:40]
|
||||
1 │ new SuppressedError(error, suppressed, {})
|
||||
· ──
|
||||
╰────
|
||||
help: The first argument to an error constructor should be a string describing the error.
|
||||
|
||||
⚠ unicorn(error-message): Error message should be a string.
|
||||
╭─[error_message.tsx:1:40]
|
||||
1 │ new SuppressedError(error, suppressed, {foo})
|
||||
· ─────
|
||||
╰────
|
||||
help: The first argument to an error constructor should be a string describing the error.
|
||||
|
||||
⚠ unicorn(error-message): Pass a message to the SuppressedError constructor.
|
||||
╭─[error_message.tsx:1:15]
|
||||
1 │ const error = new SuppressedError;
|
||||
· ───────────────────
|
||||
╰────
|
||||
help: A descriptive message makes the error easier to debug when it is caught or logged.
|
||||
@@ -22,7 +22,7 @@ pub use boolean::*;
|
||||
|
||||
// Built-in Error constructors
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types
|
||||
pub const BUILT_IN_ERRORS: [&str; 9] = [
|
||||
pub const BUILT_IN_ERRORS: [&str; 10] = [
|
||||
"Error",
|
||||
"EvalError",
|
||||
"RangeError",
|
||||
@@ -32,6 +32,7 @@ pub const BUILT_IN_ERRORS: [&str; 9] = [
|
||||
"URIError",
|
||||
"InternalError",
|
||||
"AggregateError",
|
||||
"SuppressedError",
|
||||
];
|
||||
|
||||
/// Returns `true` when `ident` resolves to any import binding from `module_name`.
|
||||
|
||||
Reference in new issue
Block a user