From 3a8cade2091bea83769b5140a92dad7b2a277ce0 Mon Sep 17 00:00:00 2001 From: Artur Date: Fri, 29 Apr 2022 10:46:17 +0300 Subject: [PATCH] feat(es/lints): Implement `no-throw-literal` (#4477) --- .../lints/no-throw-literal/default/.swcrc | 9 ++ .../lints/no-throw-literal/default/input.js | 43 +++++++ .../default/output.swc-stderr | 96 +++++++++++++++ crates/swc_ecma_lints/src/config.rs | 4 + crates/swc_ecma_lints/src/rules/mod.rs | 5 + .../src/rules/no_throw_literal.rs | 111 ++++++++++++++++++ 6 files changed, 268 insertions(+) create mode 100644 crates/swc/tests/errors/lints/no-throw-literal/default/.swcrc create mode 100644 crates/swc/tests/errors/lints/no-throw-literal/default/input.js create mode 100644 crates/swc/tests/errors/lints/no-throw-literal/default/output.swc-stderr create mode 100644 crates/swc_ecma_lints/src/rules/no_throw_literal.rs diff --git a/crates/swc/tests/errors/lints/no-throw-literal/default/.swcrc b/crates/swc/tests/errors/lints/no-throw-literal/default/.swcrc new file mode 100644 index 00000000000..27d2aed30f9 --- /dev/null +++ b/crates/swc/tests/errors/lints/no-throw-literal/default/.swcrc @@ -0,0 +1,9 @@ +{ + "jsc": { + "lints": { + "no-throw-literal": [ + "error" + ] + } + } +} \ No newline at end of file diff --git a/crates/swc/tests/errors/lints/no-throw-literal/default/input.js b/crates/swc/tests/errors/lints/no-throw-literal/default/input.js new file mode 100644 index 00000000000..d3bf3b6dac0 --- /dev/null +++ b/crates/swc/tests/errors/lints/no-throw-literal/default/input.js @@ -0,0 +1,43 @@ +throw 'error'; +throw 0; +throw false; +throw null; +throw {}; +throw undefined; +throw 'a' + 'b'; +var b = new Error(); throw 'a' + b; +throw foo = 'error'; +throw foo += new Error(); +throw foo &= new Error(); +throw foo &&= 'literal' +throw new Error(), 1, 2, 3; +throw 'literal' && 'not an Error'; +throw foo && 'literal' +throw foo ? 'not an Error' : 'literal'; +throw `${err}`; + +// valid +throw new Error(); +throw new Error('error'); +throw Error('error'); +var e = new Error(); throw e; +try { throw new Error(); } catch (e) { throw e; }; +throw a; +throw foo(); +throw new foo(); +throw foo.bar; +throw foo[bar]; +class C { #field; foo() { throw foo.#field; } } +throw foo = new Error(); +throw foo.bar ||= 'literal' +throw foo[bar] ??= 'literal' +throw 1, 2, new Error(); +throw 'literal' && new Error(); +throw new Error() || 'literal'; +throw foo ? new Error() : 'literal'; +throw foo ? 'literal' : new Error(); +throw tag`${foo}`; +function* foo() { var index = 0; throw yield index++; } +async function foo() { throw await bar; } +throw obj?.foo; +throw obj?.foo(); diff --git a/crates/swc/tests/errors/lints/no-throw-literal/default/output.swc-stderr b/crates/swc/tests/errors/lints/no-throw-literal/default/output.swc-stderr new file mode 100644 index 00000000000..5cf13a4ac0c --- /dev/null +++ b/crates/swc/tests/errors/lints/no-throw-literal/default/output.swc-stderr @@ -0,0 +1,96 @@ + + x Expected an error object to be thrown + ,---- + 1 | throw 'error'; + : ^^^^^^^^^^^^^^ + `---- + + x Expected an error object to be thrown + ,---- + 2 | throw 0; + : ^^^^^^^^ + `---- + + x Expected an error object to be thrown + ,---- + 3 | throw false; + : ^^^^^^^^^^^^ + `---- + + x Expected an error object to be thrown + ,---- + 4 | throw null; + : ^^^^^^^^^^^ + `---- + + x Expected an error object to be thrown + ,---- + 5 | throw {}; + : ^^^^^^^^^ + `---- + + x Do not throw undefined + ,---- + 6 | throw undefined; + : ^^^^^^^^^^^^^^^^ + `---- + + x Expected an error object to be thrown + ,---- + 7 | throw 'a' + 'b'; + : ^^^^^^^^^^^^^^^^ + `---- + + x Expected an error object to be thrown + ,---- + 9 | throw foo = 'error'; + : ^^^^^^^^^^^^^^^^^^^^ + `---- + + x Expected an error object to be thrown + ,---- + 10 | throw foo += new Error(); + : ^^^^^^^^^^^^^^^^^^^^^^^^^ + `---- + + x Expected an error object to be thrown + ,---- + 11 | throw foo &= new Error(); + : ^^^^^^^^^^^^^^^^^^^^^^^^^ + `---- + + x Expected an error object to be thrown + ,---- + 12 | throw foo &&= 'literal' + : ^^^^^^^^^^^^^^^^^^^^^^^ + `---- + + x Expected an error object to be thrown + ,---- + 13 | throw new Error(), 1, 2, 3; + : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + `---- + + x Expected an error object to be thrown + ,---- + 14 | throw 'literal' && 'not an Error'; + : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + `---- + + x Expected an error object to be thrown + ,---- + 15 | throw foo && 'literal' + : ^^^^^^^^^^^^^^^^^^^^^^ + `---- + + x Expected an error object to be thrown + ,---- + 16 | throw foo ? 'not an Error' : 'literal'; + : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + `---- + + x Expected an error object to be thrown + ,---- + 17 | throw `${err}`; + : ^^^^^^^^^^^^^^^ + `---- diff --git a/crates/swc_ecma_lints/src/config.rs b/crates/swc_ecma_lints/src/config.rs index e2f22c03751..891aa7e3d77 100644 --- a/crates/swc_ecma_lints/src/config.rs +++ b/crates/swc_ecma_lints/src/config.rs @@ -164,4 +164,8 @@ pub struct LintConfig { #[cfg(feature = "non_critical_lints")] #[serde(default, alias = "noObjCalls")] pub no_obj_calls: RuleConfig<()>, + + #[cfg(feature = "non_critical_lints")] + #[serde(default, alias = "noThrowLiteral")] + pub no_throw_literal: RuleConfig<()>, } diff --git a/crates/swc_ecma_lints/src/rules/mod.rs b/crates/swc_ecma_lints/src/rules/mod.rs index 2dc252951f0..ab9e5e546ba 100644 --- a/crates/swc_ecma_lints/src/rules/mod.rs +++ b/crates/swc_ecma_lints/src/rules/mod.rs @@ -30,6 +30,7 @@ pub(crate) mod non_critical_lints { pub mod no_obj_calls; pub mod no_param_reassign; pub mod no_restricted_syntax; + pub mod no_throw_literal; pub mod no_use_before_define; pub mod prefer_regex_literals; pub mod quotes; @@ -155,6 +156,10 @@ pub fn all(lint_params: LintParams) -> Vec> { unresolved_ctxt, &lint_config.no_obj_calls, )); + + rules.extend(no_throw_literal::no_throw_literal( + &lint_config.no_throw_literal, + )) } rules diff --git a/crates/swc_ecma_lints/src/rules/no_throw_literal.rs b/crates/swc_ecma_lints/src/rules/no_throw_literal.rs new file mode 100644 index 00000000000..40333124641 --- /dev/null +++ b/crates/swc_ecma_lints/src/rules/no_throw_literal.rs @@ -0,0 +1,111 @@ +use swc_common::{errors::HANDLER, Span}; +use swc_ecma_ast::*; +use swc_ecma_visit::{Visit, VisitWith}; + +use crate::{ + config::{LintRuleReaction, RuleConfig}, + rule::{visitor_rule, Rule}, + rules::utils::unwrap_seqs_and_parens, +}; + +const EXPECTED_AN_ERROR_OBJECT: &str = "Expected an error object to be thrown"; +const NO_THROW_UNDEFINED: &str = "Do not throw undefined"; + +pub fn no_throw_literal(config: &RuleConfig<()>) -> Option> { + let rule_reaction = config.get_rule_reaction(); + + match rule_reaction { + LintRuleReaction::Off => None, + _ => Some(visitor_rule(NoThrowLiteral::new(rule_reaction))), + } +} + +#[derive(Debug, Default)] +struct NoThrowLiteral { + expected_reaction: LintRuleReaction, +} + +impl NoThrowLiteral { + fn new(expected_reaction: LintRuleReaction) -> Self { + Self { expected_reaction } + } + + fn emit_report(&self, span: Span, message: &str) { + HANDLER.with(|handler| match self.expected_reaction { + LintRuleReaction::Error => { + handler.struct_span_err(span, message).emit(); + } + LintRuleReaction::Warning => { + handler.struct_span_warn(span, message).emit(); + } + _ => {} + }); + } + + fn could_be_error(&self, expr: &Expr) -> bool { + match unwrap_seqs_and_parens(expr) { + Expr::Ident(_) + | Expr::New(_) + | Expr::Call(_) + | Expr::Member(_) + | Expr::TaggedTpl(_) + | Expr::Yield(_) + | Expr::Await(_) + | Expr::OptChain(_) => true, + + Expr::Assign(AssignExpr { + op, left, right, .. + }) => match op { + op!("=") | op!("&&=") => self.could_be_error(right.as_ref()), + op!("||=") | op!("??=") => { + if let PatOrExpr::Expr(left) = left { + self.could_be_error(left.as_ref()) || self.could_be_error(right.as_ref()) + } else { + false + } + } + _ => false, + }, + + Expr::Bin(BinExpr { + op, left, right, .. + }) => { + if let op!("&&") = op { + self.could_be_error(right.as_ref()) + } else { + self.could_be_error(left.as_ref()) || self.could_be_error(right.as_ref()) + } + } + + Expr::Cond(CondExpr { cons, alt, .. }) => { + self.could_be_error(cons.as_ref()) || self.could_be_error(alt.as_ref()) + } + + _ => false, + } + } + + fn check(&self, throw_stmt: &ThrowStmt) { + let arg = unwrap_seqs_and_parens(throw_stmt.arg.as_ref()); + + if !self.could_be_error(arg) { + self.emit_report(throw_stmt.span, EXPECTED_AN_ERROR_OBJECT); + + return; + } + + if let Expr::Ident(Ident { sym, .. }) = arg { + if &*sym == "undefined" { + self.emit_report(throw_stmt.span, NO_THROW_UNDEFINED); + } + } + } +} + +impl Visit for NoThrowLiteral { + fn visit_throw_stmt(&mut self, throw_stmt: &ThrowStmt) { + self.check(throw_stmt); + + throw_stmt.visit_children_with(self); + } +}