feat(es/lints): Implement no-throw-literal (#4477)

This commit is contained in:
Artur 2022-04-29 10:46:17 +03:00 committed by GitHub
parent 8611b1bf23
commit 3a8cade209
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 268 additions and 0 deletions

View File

@ -0,0 +1,9 @@
{
"jsc": {
"lints": {
"no-throw-literal": [
"error"
]
}
}
}

View File

@ -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();

View File

@ -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}`;
: ^^^^^^^^^^^^^^^
`----

View File

@ -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<()>,
}

View File

@ -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<Box<dyn Rule>> {
unresolved_ctxt,
&lint_config.no_obj_calls,
));
rules.extend(no_throw_literal::no_throw_literal(
&lint_config.no_throw_literal,
))
}
rules

View File

@ -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<Box<dyn Rule>> {
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);
}
}