feat(es/lints): Add constructor-super rule (#4912)

This commit is contained in:
Artur 2022-06-13 22:24:38 +03:00 committed by GitHub
parent 61d7d881c9
commit 6daeeb0652
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 633 additions and 0 deletions

View File

@ -0,0 +1,9 @@
{
"jsc": {
"lints": {
"constructor-super": [
"error"
]
}
}
}

View File

@ -0,0 +1,43 @@
class A1 extends null { constructor() { super(); } }
class A2 extends null { constructor() { } }
class A3 extends 100 { constructor() { super(); } }
class A4 extends 'test' { constructor() { super(); } }
class A5 extends (B = 5) { constructor() { super(); } }
class A6 extends (B && 5) { constructor() { super(); } }
class A7 extends (B &&= 5) { constructor() { super(); } }
class A8 extends (B += C) { constructor() { super(); } }
class A9 extends (B -= C) { constructor() { super(); } }
class A10 extends (B **= C) { constructor() { super(); } }
class A11 extends (B |= C) { constructor() { super(); } }
class A12 extends (B &= C) { constructor() { super(); } }
class A13 extends B { constructor() { } }
class A14 extends B { constructor() { for (var a of b) super.foo(); } }
class A15 extends B { constructor() { class C extends D { constructor() { super(); } } } }
class A16 extends B { constructor() { var c = class extends D { constructor() { super(); } } } }
class A17 extends B { constructor() { var c = () => super(); } }
class A18 extends B { constructor() { class C extends D { constructor() { super(); } } } }
class A19 extends B { constructor() { var C = class extends D { constructor() { super(); } } } }
class A20 extends B { constructor() { super(); class C extends D { constructor() { } } } }
class A21 extends B { constructor() { super(); var C = class extends D { constructor() { } } } }
class A23 extends B { constructor() { if (a) super(); } }
class A24 extends B { constructor() { x ? super() : null; } }
class A25 extends B { constructor() { switch (x) { case 'a': super(); } } }
class A26 { constructor() { for (let i = 0; i < a.length; i++) { super(); } } }
class A27 { constructor() { for (let i = 0; i < a.length; i++) { super(); } super(); } }
class A28 extends B { constructor() { return; super(); } }
class A29 extends B { constructor() { try { super(); } catch (e) { } } }
class A30 extends B { constructor() { try { } catch (e) { super(); } } }
class A31 extends B { constructor() { try { } catch (e) { super(); } super(); } }
class A30 extends B { constructor() { try { super(); } catch (e) { } finally { super() } } }
// valid
class V1 extends (B, C) { constructor() { super(); } }
class V2 extends (class B { }) { constructor() { super(); } }
class V3 { constructor() { class B extends C { constructor() { super(); } } } }
class V4 extends Object { constructor() { super(); for (let i = 0; i < 0; i++); } }
class V5 { }
class V6 { constructor() { } }
class V7 extends null { }
class V8 { constructor() { } }
class A9 extends B { constructor() { try { } finally { super(); } } }

View File

@ -0,0 +1,240 @@
x the name `A30` is defined multiple times
,-[29:1]
29 | class A30 extends B { constructor() { try { } catch (e) { super(); } } }
: ^|^
: `-- previous definition of `A30` here
30 | class A31 extends B { constructor() { try { } catch (e) { super(); } super(); } }
31 | class A30 extends B { constructor() { try { super(); } catch (e) { } finally { super() } } }
: ^|^
: `-- `A30` redefined here
`----
x the name `A9` is defined multiple times
,-[9:1]
9 | class A9 extends (B -= C) { constructor() { super(); } }
: ^|
: `-- previous definition of `A9` here
10 | class A10 extends (B **= C) { constructor() { super(); } }
11 | class A11 extends (B |= C) { constructor() { super(); } }
12 | class A12 extends (B &= C) { constructor() { super(); } }
13 | class A13 extends B { constructor() { } }
14 | class A14 extends B { constructor() { for (var a of b) super.foo(); } }
15 | class A15 extends B { constructor() { class C extends D { constructor() { super(); } } } }
16 | class A16 extends B { constructor() { var c = class extends D { constructor() { super(); } } } }
17 | class A17 extends B { constructor() { var c = () => super(); } }
18 | class A18 extends B { constructor() { class C extends D { constructor() { super(); } } } }
19 | class A19 extends B { constructor() { var C = class extends D { constructor() { super(); } } } }
20 | class A20 extends B { constructor() { super(); class C extends D { constructor() { } } } }
21 | class A21 extends B { constructor() { super(); var C = class extends D { constructor() { } } } }
22 | class A23 extends B { constructor() { if (a) super(); } }
23 | class A24 extends B { constructor() { x ? super() : null; } }
24 | class A25 extends B { constructor() { switch (x) { case 'a': super(); } } }
25 | class A26 { constructor() { for (let i = 0; i < a.length; i++) { super(); } } }
26 | class A27 { constructor() { for (let i = 0; i < a.length; i++) { super(); } super(); } }
27 | class A28 extends B { constructor() { return; super(); } }
28 | class A29 extends B { constructor() { try { super(); } catch (e) { } } }
29 | class A30 extends B { constructor() { try { } catch (e) { super(); } } }
30 | class A31 extends B { constructor() { try { } catch (e) { super(); } super(); } }
31 | class A30 extends B { constructor() { try { super(); } catch (e) { } finally { super() } } }
32 |
33 |
34 | // valid
35 | class V1 extends (B, C) { constructor() { super(); } }
36 | class V2 extends (class B { }) { constructor() { super(); } }
37 | class V3 { constructor() { class B extends C { constructor() { super(); } } } }
38 | class V4 extends Object { constructor() { super(); for (let i = 0; i < 0; i++); } }
39 | class V5 { }
40 | class V6 { constructor() { } }
41 | class V7 extends null { }
42 | class V8 { constructor() { } }
43 | class A9 extends B { constructor() { try { } finally { super(); } } }
: ^|
: `-- `A9` redefined here
`----
x Unexpected 'super()' because 'super' is not a constructor
,----
1 | class A1 extends null { constructor() { super(); } }
: ^^^^^
`----
x Expected to call 'super()'
,----
2 | class A2 extends null { constructor() { } }
: ^^^^^^^^^^^^^^^^^
`----
x Unexpected 'super()' because 'super' is not a constructor
,----
3 | class A3 extends 100 { constructor() { super(); } }
: ^^^^^
`----
x Unexpected 'super()' because 'super' is not a constructor
,----
4 | class A4 extends 'test' { constructor() { super(); } }
: ^^^^^
`----
x Unexpected 'super()' because 'super' is not a constructor
,----
5 | class A5 extends (B = 5) { constructor() { super(); } }
: ^^^^^
`----
x Unexpected 'super()' because 'super' is not a constructor
,----
6 | class A6 extends (B && 5) { constructor() { super(); } }
: ^^^^^
`----
x Unexpected 'super()' because 'super' is not a constructor
,----
7 | class A7 extends (B &&= 5) { constructor() { super(); } }
: ^^^^^
`----
x Unexpected 'super()' because 'super' is not a constructor
,----
8 | class A8 extends (B += C) { constructor() { super(); } }
: ^^^^^
`----
x Unexpected 'super()' because 'super' is not a constructor
,----
9 | class A9 extends (B -= C) { constructor() { super(); } }
: ^^^^^
`----
x Unexpected 'super()' because 'super' is not a constructor
,----
10 | class A10 extends (B **= C) { constructor() { super(); } }
: ^^^^^
`----
x Unexpected 'super()' because 'super' is not a constructor
,----
11 | class A11 extends (B |= C) { constructor() { super(); } }
: ^^^^^
`----
x Unexpected 'super()' because 'super' is not a constructor
,----
12 | class A12 extends (B &= C) { constructor() { super(); } }
: ^^^^^
`----
x Expected to call 'super()'
,----
13 | class A13 extends B { constructor() { } }
: ^^^^^^^^^^^^^^^^^
`----
x Expected to call 'super()'
,----
14 | class A14 extends B { constructor() { for (var a of b) super.foo(); } }
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
`----
x Expected to call 'super()'
,----
15 | class A15 extends B { constructor() { class C extends D { constructor() { super(); } } } }
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
`----
x Expected to call 'super()'
,----
16 | class A16 extends B { constructor() { var c = class extends D { constructor() { super(); } } } }
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
`----
x Expected to call 'super()'
,----
17 | class A17 extends B { constructor() { var c = () => super(); } }
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
`----
x Expected to call 'super()'
,----
18 | class A18 extends B { constructor() { class C extends D { constructor() { super(); } } } }
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
`----
x Expected to call 'super()'
,----
19 | class A19 extends B { constructor() { var C = class extends D { constructor() { super(); } } } }
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
`----
x Expected to call 'super()'
,----
20 | class A20 extends B { constructor() { super(); class C extends D { constructor() { } } } }
: ^^^^^^^^^^^^^^^^^
`----
x Expected to call 'super()'
,----
21 | class A21 extends B { constructor() { super(); var C = class extends D { constructor() { } } } }
: ^^^^^^^^^^^^^^^^^
`----
x Lacked a call of 'super()' in some code path
,----
22 | class A23 extends B { constructor() { if (a) super(); } }
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
`----
x Lacked a call of 'super()' in some code path
,----
23 | class A24 extends B { constructor() { x ? super() : null; } }
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
`----
x More than one call 'super()' possible into loop
,----
25 | class A26 { constructor() { for (let i = 0; i < a.length; i++) { super(); } } }
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
`----
x More than one call 'super()' possible into loop
,----
26 | class A27 { constructor() { for (let i = 0; i < a.length; i++) { super(); } super(); } }
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
`----
x Unexpected duplicate 'super()'
,----
26 | class A27 { constructor() { for (let i = 0; i < a.length; i++) { super(); } super(); } }
: ^^^^^
`----
x Expected to call 'super()'
,----
27 | class A28 extends B { constructor() { return; super(); } }
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
`----
x Lacked a call of 'super()' in some code path
,----
28 | class A29 extends B { constructor() { try { super(); } catch (e) { } } }
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
`----
x Lacked a call of 'super()' in some code path
,----
29 | class A30 extends B { constructor() { try { } catch (e) { super(); } } }
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
`----
x Unexpected duplicate 'super()'
,----
30 | class A31 extends B { constructor() { try { } catch (e) { super(); } super(); } }
: ^^^^^
`----
x Unexpected duplicate 'super()'
,----
31 | class A30 extends B { constructor() { try { super(); } catch (e) { } finally { super() } } }
: ^^^^^
`----

View File

@ -196,6 +196,10 @@ pub struct LintConfig {
#[serde(default, alias = "noCompareNegZero")]
pub no_compare_neg_zero: RuleConfig<()>,
#[cfg(feature = "non_critical_lints")]
#[serde(default, alias = "constructorSuper")]
pub constructor_super: RuleConfig<()>,
#[cfg(feature = "non_critical_lints")]
#[serde(default, alias = "noSparseArrays")]
pub no_sparse_arrays: RuleConfig<()>,

View File

@ -0,0 +1,332 @@
use std::mem;
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 BAD_SUPER_MESSAGE: &str = "Unexpected 'super()' because 'super' is not a constructor";
const CALL_SUPER_EXCPECTED_MESSAGE: &str = "Expected to call 'super()'";
const UNEXPECTED_DUPLICATE_SUPER_CALL_MESSAGE: &str = "Unexpected duplicate 'super()'";
const LACKED_CALL_SUPER_MESSAGE: &str = "Lacked a call of 'super()' in some code path";
const MORE_THAN_ONE_CALL_POSSIBLE_MESSAGE: &str = "More than one call 'super()' possible into loop";
pub fn constructor_super(config: &RuleConfig<()>) -> Option<Box<dyn Rule>> {
let rule_reaction = config.get_rule_reaction();
match rule_reaction {
LintRuleReaction::Off => None,
_ => Some(visitor_rule(ConstructorSuper::new(rule_reaction))),
}
}
#[derive(Debug, Clone, Copy)]
enum SuperClass {
Valid,
Invalid,
NotSetted,
}
impl Default for SuperClass {
fn default() -> Self {
SuperClass::NotSetted
}
}
#[derive(Debug, Default)]
struct ClassMeta {
super_class: SuperClass,
constructor_scope: usize,
code_path: CodePath,
loop_span: Option<Span>,
}
#[derive(Debug, Default, Clone)]
struct CodePath {
super_calls_count: usize,
possibly_returned: bool,
super_call_missed: bool,
// only for switch-case
break_exists: bool,
}
#[derive(Debug, Default)]
struct ConstructorSuper {
expected_reaction: LintRuleReaction,
class_meta: ClassMeta,
scope: usize,
}
impl ConstructorSuper {
fn new(expected_reaction: LintRuleReaction) -> Self {
Self {
expected_reaction,
scope: 0,
class_meta: Default::default(),
}
}
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 collect_class(&mut self, class: &Class) {
self.class_meta.super_class = match &class.super_class {
Some(super_class) => match unwrap_seqs_and_parens(super_class.as_ref()) {
Expr::Ident(_) | Expr::Class(_) => SuperClass::Valid,
_ => SuperClass::Invalid,
},
None => SuperClass::NotSetted,
};
}
fn check_on_super_call(&mut self, span: Span) {
match self.class_meta.super_class {
SuperClass::Invalid => {
self.emit_report(span, BAD_SUPER_MESSAGE);
}
SuperClass::NotSetted => {
if let Some(span) = self.class_meta.loop_span {
self.emit_report(span, MORE_THAN_ONE_CALL_POSSIBLE_MESSAGE);
} else if self.class_meta.code_path.super_calls_count > 1 {
self.emit_report(span, UNEXPECTED_DUPLICATE_SUPER_CALL_MESSAGE);
} else {
self.emit_report(span, CALL_SUPER_EXCPECTED_MESSAGE);
}
}
SuperClass::Valid => {
if let Some(span) = self.class_meta.loop_span {
self.emit_report(span, MORE_THAN_ONE_CALL_POSSIBLE_MESSAGE);
} else if self.class_meta.code_path.super_calls_count > 1 {
self.emit_report(span, UNEXPECTED_DUPLICATE_SUPER_CALL_MESSAGE);
}
}
}
}
fn check_on_constructor(&self, span: Span) {
match self.class_meta.super_class {
SuperClass::Valid => {
if self.class_meta.code_path.super_call_missed {
self.emit_report(span, LACKED_CALL_SUPER_MESSAGE);
}
if self.class_meta.code_path.super_calls_count == 0 {
self.emit_report(span, CALL_SUPER_EXCPECTED_MESSAGE);
}
}
SuperClass::NotSetted => {}
SuperClass::Invalid => {
if self.class_meta.code_path.super_calls_count == 0 {
self.emit_report(span, CALL_SUPER_EXCPECTED_MESSAGE);
}
}
}
}
fn update_current_code_path(&mut self, ordered_pathes: &[CodePath]) {
let current_code_path = &mut self.class_meta.code_path;
for code_path in ordered_pathes.iter() {
current_code_path.possibly_returned =
current_code_path.possibly_returned || code_path.possibly_returned;
current_code_path.super_calls_count = std::cmp::max(
current_code_path.super_calls_count,
code_path.super_calls_count,
);
current_code_path.super_call_missed = current_code_path.super_call_missed
|| code_path.super_call_missed
|| code_path.super_calls_count == 0;
}
}
}
impl Visit for ConstructorSuper {
fn visit_class(&mut self, class: &Class) {
let prev_class_markers = mem::take(&mut self.class_meta);
self.collect_class(class);
class.visit_children_with(self);
self.class_meta = prev_class_markers;
}
fn visit_constructor(&mut self, constructor: &Constructor) {
self.scope += 1;
self.class_meta.constructor_scope = self.scope;
constructor.visit_children_with(self);
self.check_on_constructor(constructor.span);
self.scope -= 1;
}
fn visit_call_expr(&mut self, call_expr: &CallExpr) {
if let Callee::Super(super_call) = &call_expr.callee {
if !self.class_meta.code_path.possibly_returned
&& self.class_meta.constructor_scope == self.scope
{
self.class_meta.code_path.super_calls_count += 1;
self.class_meta.code_path.super_call_missed = false;
}
self.check_on_super_call(super_call.span);
}
call_expr.visit_children_with(self);
}
fn visit_if_stmt(&mut self, if_stmt: &IfStmt) {
if_stmt.test.visit_children_with(self);
let parent_code_path = self.class_meta.code_path.clone();
if_stmt.cons.visit_children_with(self);
let cons_code_path = mem::replace(&mut self.class_meta.code_path, parent_code_path.clone());
if_stmt.alt.visit_children_with(self);
let alt_code_path = mem::replace(&mut self.class_meta.code_path, parent_code_path);
self.update_current_code_path(&[cons_code_path, alt_code_path]);
}
fn visit_return_stmt(&mut self, n: &ReturnStmt) {
if self.scope == self.class_meta.constructor_scope {
self.class_meta.code_path.possibly_returned = true;
}
n.visit_children_with(self);
}
fn visit_cond_expr(&mut self, cond_expr: &CondExpr) {
cond_expr.test.visit_children_with(self);
let parent_code_path = self.class_meta.code_path.clone();
cond_expr.cons.visit_children_with(self);
let cons_code_path = mem::replace(&mut self.class_meta.code_path, parent_code_path.clone());
cond_expr.alt.visit_children_with(self);
let alt_code_path = mem::replace(&mut self.class_meta.code_path, parent_code_path);
self.update_current_code_path(&[cons_code_path, alt_code_path]);
}
fn visit_switch_stmt(&mut self, switch_stmt: &SwitchStmt) {
switch_stmt.discriminant.visit_children_with(self);
let parent_code_path = self.class_meta.code_path.clone();
let mut cases: Vec<CodePath> = Vec::with_capacity(switch_stmt.cases.len());
for switch_case in switch_stmt.cases.iter() {
switch_case.visit_children_with(self);
if self.class_meta.code_path.break_exists {
cases.push(mem::replace(
&mut self.class_meta.code_path,
parent_code_path.clone(),
));
}
}
if cases.is_empty() {
cases.push(mem::replace(
&mut self.class_meta.code_path,
parent_code_path,
));
}
self.update_current_code_path(cases.as_slice());
}
fn visit_try_stmt(&mut self, try_stmt: &TryStmt) {
let parent_code_path = self.class_meta.code_path.clone();
try_stmt.block.visit_children_with(self);
let block_code_path =
mem::replace(&mut self.class_meta.code_path, parent_code_path.clone());
if try_stmt.handler.is_some() {
try_stmt.handler.visit_children_with(self);
let handler_code_path = mem::replace(&mut self.class_meta.code_path, parent_code_path);
self.update_current_code_path(&[block_code_path, handler_code_path]);
} else {
self.update_current_code_path(&[block_code_path]);
}
try_stmt.finalizer.visit_children_with(self);
}
fn visit_break_stmt(&mut self, break_stmt: &BreakStmt) {
self.class_meta.code_path.break_exists = true;
break_stmt.visit_children_with(self);
}
fn visit_function(&mut self, function: &Function) {
self.scope += 1;
function.visit_children_with(self);
self.scope -= 1;
}
fn visit_for_in_stmt(&mut self, for_in_stmt: &ForInStmt) {
let prev_loop_span = mem::replace(&mut self.class_meta.loop_span, Some(for_in_stmt.span));
for_in_stmt.visit_children_with(self);
self.class_meta.loop_span = prev_loop_span;
}
fn visit_for_of_stmt(&mut self, for_of_stmt: &ForOfStmt) {
let prev_loop_span = mem::replace(&mut self.class_meta.loop_span, Some(for_of_stmt.span));
for_of_stmt.visit_children_with(self);
self.class_meta.loop_span = prev_loop_span;
}
fn visit_for_stmt(&mut self, for_stmt: &ForStmt) {
let prev_loop_span = mem::replace(&mut self.class_meta.loop_span, Some(for_stmt.span));
for_stmt.visit_children_with(self);
self.class_meta.loop_span = prev_loop_span;
}
fn visit_arrow_expr(&mut self, arrow_expr: &ArrowExpr) {
self.scope += 1;
arrow_expr.visit_children_with(self);
self.scope -= 1;
}
}

View File

@ -15,6 +15,7 @@ mod utils;
#[cfg(feature = "non_critical_lints")]
#[path = ""]
pub(crate) mod non_critical_lints {
pub mod constructor_super;
pub mod default_case_last;
pub mod default_param_last;
pub mod dot_notation;
@ -175,6 +176,10 @@ pub fn all(lint_params: LintParams) -> Vec<Box<dyn Rule>> {
&lint_config.no_compare_neg_zero,
));
rules.extend(constructor_super::constructor_super(
&lint_config.constructor_super,
));
rules.extend(no_sparse_arrays::no_sparse_arrays(
&lint_config.no_sparse_arrays,
));