deno_lint 0.2.10

lint for deno
Documentation
// Copyright 2020 the Deno authors. All rights reserved. MIT license.
use super::Context;
use super::LintRule;
use swc_ecmascript::ast::{
  DoWhileStmt, EmptyStmt, ForInStmt, ForOfStmt, ForStmt, IfStmt, LabeledStmt,
  Stmt, WhileStmt, WithStmt,
};
use swc_ecmascript::visit::noop_visit_type;
use swc_ecmascript::visit::Node;
use swc_ecmascript::visit::Visit;

pub struct NoExtraSemi;

impl LintRule for NoExtraSemi {
  fn new() -> Box<Self> {
    Box::new(NoExtraSemi)
  }

  fn tags(&self) -> &'static [&'static str] {
    &["recommended"]
  }

  fn code(&self) -> &'static str {
    "no-extra-semi"
  }

  fn lint_program(
    &self,
    context: &mut Context,
    program: &swc_ecmascript::ast::Program,
  ) {
    let mut visitor = NoExtraSemiVisitor::new(context);
    visitor.visit_program(program, program);
  }

  fn docs(&self) -> &'static str {
    r#"Disallows the use of unnecessary semi-colons

Extra (and unnecessary) semi-colons can cause confusion when reading the code as
well as making the code less clean.
    
### Invalid:
```typescript
const x = 5;;

function foo() {};
```

### Valid:
```typescript
const x = 5;

function foo() {}
```
"#
  }
}

struct NoExtraSemiVisitor<'c> {
  context: &'c mut Context,
}

impl<'c> NoExtraSemiVisitor<'c> {
  fn new(context: &'c mut Context) -> Self {
    Self { context }
  }
}

impl<'c> Visit for NoExtraSemiVisitor<'c> {
  noop_visit_type!();

  fn visit_empty_stmt(&mut self, empty_stmt: &EmptyStmt, _parent: &dyn Node) {
    self.context.add_diagnostic_with_hint(
      empty_stmt.span,
      "no-extra-semi",
      "Unnecessary semicolon.",
      "Remove the extra (and unnecessary) semi-colon",
    );
  }

  fn visit_for_stmt(&mut self, for_stmt: &ForStmt, parent: &dyn Node) {
    if matches!(&*for_stmt.body, Stmt::Empty(_)) {
      if let Some(ref init) = for_stmt.init {
        swc_ecmascript::visit::visit_var_decl_or_expr(self, init, parent);
      }
      if let Some(ref test) = for_stmt.test {
        swc_ecmascript::visit::visit_expr(self, test, parent);
      }
      if let Some(ref update) = for_stmt.update {
        swc_ecmascript::visit::visit_expr(self, update, parent);
      }
    } else {
      swc_ecmascript::visit::visit_for_stmt(self, for_stmt, parent);
    }
  }

  fn visit_while_stmt(&mut self, while_stmt: &WhileStmt, parent: &dyn Node) {
    if matches!(&*while_stmt.body, Stmt::Empty(_)) {
      swc_ecmascript::visit::visit_expr(self, &*while_stmt.test, parent);
    } else {
      swc_ecmascript::visit::visit_while_stmt(self, while_stmt, parent);
    }
  }

  fn visit_do_while_stmt(
    &mut self,
    do_while_stmt: &DoWhileStmt,
    parent: &dyn Node,
  ) {
    if matches!(&*do_while_stmt.body, Stmt::Empty(_)) {
      swc_ecmascript::visit::visit_expr(self, &*do_while_stmt.test, parent);
    } else {
      swc_ecmascript::visit::visit_do_while_stmt(self, do_while_stmt, parent);
    }
  }

  fn visit_with_stmt(&mut self, with_stmt: &WithStmt, parent: &dyn Node) {
    if matches!(&*with_stmt.body, Stmt::Empty(_)) {
      swc_ecmascript::visit::visit_expr(self, &*with_stmt.obj, parent);
    } else {
      swc_ecmascript::visit::visit_with_stmt(self, with_stmt, parent);
    }
  }

  fn visit_for_of_stmt(&mut self, for_of_stmt: &ForOfStmt, parent: &dyn Node) {
    if matches!(&*for_of_stmt.body, Stmt::Empty(_)) {
      swc_ecmascript::visit::visit_var_decl_or_pat(
        self,
        &for_of_stmt.left,
        parent,
      );
      swc_ecmascript::visit::visit_expr(self, &*for_of_stmt.right, parent);
    } else {
      swc_ecmascript::visit::visit_for_of_stmt(self, for_of_stmt, parent);
    }
  }

  fn visit_for_in_stmt(&mut self, for_in_stmt: &ForInStmt, parent: &dyn Node) {
    if matches!(&*for_in_stmt.body, Stmt::Empty(_)) {
      swc_ecmascript::visit::visit_var_decl_or_pat(
        self,
        &for_in_stmt.left,
        parent,
      );
      swc_ecmascript::visit::visit_expr(self, &*for_in_stmt.right, parent);
    } else {
      swc_ecmascript::visit::visit_for_in_stmt(self, for_in_stmt, parent);
    }
  }

  fn visit_if_stmt(&mut self, if_stmt: &IfStmt, parent: &dyn Node) {
    swc_ecmascript::visit::visit_expr(self, &*if_stmt.test, parent);
    match &*if_stmt.cons {
      Stmt::Empty(_) => {}
      cons => {
        swc_ecmascript::visit::visit_stmt(self, cons, parent);
      }
    }
    match if_stmt.alt.as_deref() {
      None | Some(Stmt::Empty(_)) => {}
      Some(alt) => {
        swc_ecmascript::visit::visit_stmt(self, alt, parent);
      }
    }
  }

  fn visit_labeled_stmt(
    &mut self,
    labeled_stmt: &LabeledStmt,
    parent: &dyn Node,
  ) {
    swc_ecmascript::visit::visit_ident(self, &labeled_stmt.label, parent);
    match &*labeled_stmt.body {
      Stmt::Empty(_) => {}
      body => {
        swc_ecmascript::visit::visit_stmt(self, body, parent);
      }
    }
  }
}

#[cfg(test)]
mod tests {
  use super::*;
  use crate::test_util::*;

  #[test]
  fn no_extra_semi_valid() {
    assert_lint_ok! {
      NoExtraSemi,
      "var x = 5;",
      "function foo(){}",
      "for(;;);",
      "while(0);",
      "do;while(0);",
      "for(a in b);",
      "for(a of b);",
      "if(true);",
      "if(true); else;",
      "foo: ;",
      "foo: bar: ;",
      "with(foo);",
      "class A { }",
      "var A = class { };",
      "class A { a() { this; } }",
      "var A = class { a() { this; } };",
      "class A { } a;",
    };
  }

  #[test]
  fn no_extra_semi_invalid() {
    assert_lint_err::<NoExtraSemi>("var x = 5;;", 10);
    assert_lint_err::<NoExtraSemi>("function foo(){};", 16);
    assert_lint_err::<NoExtraSemi>("for(;;);;", 8);
    assert_lint_err::<NoExtraSemi>("while(0);;", 9);
    assert_lint_err::<NoExtraSemi>("do;while(0);;", 12);
    assert_lint_err::<NoExtraSemi>("for(a in b);;", 12);
    assert_lint_err::<NoExtraSemi>("for(a of b);;", 12);
    assert_lint_err::<NoExtraSemi>("if(true);;", 9);
    assert_lint_err::<NoExtraSemi>("if(true){} else;;", 16);
    assert_lint_err_n::<NoExtraSemi>("if(true){;} else {;}", vec![9, 18]);
    assert_lint_err::<NoExtraSemi>("foo:;;", 5);
    assert_lint_err::<NoExtraSemi>("with(foo);;", 10);
    assert_lint_err::<NoExtraSemi>("with(foo){;}", 10);
    assert_lint_err::<NoExtraSemi>("class A { ; }", 10);
    assert_lint_err::<NoExtraSemi>("class A { /*a*/; }", 15);
    assert_lint_err::<NoExtraSemi>("class A { ; a() {} }", 10);
    assert_lint_err::<NoExtraSemi>("class A { a() {}; }", 16);
    assert_lint_err::<NoExtraSemi>("class A { a() {}; b() {} }", 16);
    assert_lint_err_n::<NoExtraSemi>(
      "class A {; a() {}; b() {}; }",
      vec![9, 17, 25],
    );
    assert_lint_err::<NoExtraSemi>("class A { a() {}; get b() {} }", 16);

    assert_lint_err_on_line::<NoExtraSemi>(
      r#"
for (let i = 0; i < n; i++) {
  for (;;);;
}
"#,
      3,
      11,
    );
    assert_lint_err_on_line::<NoExtraSemi>(
      r#"
while (a) {
  while (b);;
}
"#,
      3,
      12,
    );
    assert_lint_err_on_line::<NoExtraSemi>(
      r#"
do {
  do {
    ;
  } while(a);
} while(b);
"#,
      4,
      4,
    );
    assert_lint_err_on_line::<NoExtraSemi>(
      r#"
with(a) {
  with(b) {
    ;
  }
}
"#,
      4,
      4,
    );
    assert_lint_err_on_line::<NoExtraSemi>(
      r#"
for (const a of b) {
  for (const c of d) {
    ;
  }
}
"#,
      4,
      4,
    );
    assert_lint_err_on_line::<NoExtraSemi>(
      r#"
for (const a in b) {
  for (const c in d) {
    ;
  }
}
"#,
      4,
      4,
    );
    assert_lint_err_on_line::<NoExtraSemi>(
      r#"
if (a) {
  if (b) {
    ;
  } else;
}
"#,
      4,
      4,
    );
    assert_lint_err_on_line::<NoExtraSemi>(
      r#"
foo: {
  bar: {
    ;
  }
}
"#,
      4,
      4,
    );
    assert_lint_err_on_line::<NoExtraSemi>(
      r#"
class A {
  foo() {
    class B { ; }
  }
}
"#,
      4,
      14,
    );
  }
}