use super::{Context, LintRule};
use swc_ecmascript::ast::{
ArrowExpr, BlockStmt, BlockStmtOrExpr, Constructor, Function, Module,
SwitchStmt,
};
use swc_ecmascript::visit::{noop_visit_type, Node, Visit};
use std::sync::Arc;
pub struct NoEmpty;
impl LintRule for NoEmpty {
fn new() -> Box<Self> {
Box::new(NoEmpty)
}
fn code(&self) -> &'static str {
"no-empty"
}
fn lint_module(&self, context: Arc<Context>, module: &Module) {
let mut visitor = NoEmptyVisitor::new(context);
visitor.visit_module(module, module);
}
fn docs(&self) -> &'static str {
r#"Disallows the use of empty block statements.
Empty block statements are legal but often represent that something was missed and can make code less readable. This rule ignores block statements that only contain comments. This rule also ignores empty constructors and function bodies (including arrow functions), which are covered by the `no-empty-function` rule.
### Valid:
```typescript
if (foo) {
// empty
}
```
```typescript
while (foo) {
/* empty */
}
```
```typescript
try {
doSomething();
} catch (ex) {
// continue regardless of error
}
```
```typescript
try {
doSomething();
} finally {
/* continue regardless of error */
}
```
### Invalid:
```typescript
if (foo) {
}
```
```typescript
while (foo) {
}
```
```typescript
switch(foo) {
}
```
```typescript
try {
doSomething();
} catch(ex) {
} finally {
}
```"#
}
}
struct NoEmptyVisitor {
context: Arc<Context>,
}
impl NoEmptyVisitor {
fn new(context: Arc<Context>) -> Self {
Self { context }
}
}
impl Visit for NoEmptyVisitor {
noop_visit_type!();
fn visit_function(&mut self, function: &Function, _parent: &dyn Node) {
if let Some(body) = &function.body {
for stmt in &body.stmts {
swc_ecmascript::visit::visit_stmt(self, stmt, body);
}
}
}
fn visit_arrow_expr(&mut self, arrow_expr: &ArrowExpr, _parent: &dyn Node) {
if let BlockStmtOrExpr::BlockStmt(block_stmt) = &arrow_expr.body {
for stmt in &block_stmt.stmts {
swc_ecmascript::visit::visit_stmt(self, stmt, block_stmt);
}
}
}
fn visit_constructor(&mut self, cons: &Constructor, _parent: &dyn Node) {
if let Some(body) = &cons.body {
for stmt in &body.stmts {
swc_ecmascript::visit::visit_stmt(self, stmt, body);
}
}
}
fn visit_block_stmt(&mut self, block_stmt: &BlockStmt, _parent: &dyn Node) {
if block_stmt.stmts.is_empty() {
if !block_stmt.contains_comments(&self.context) {
self.context.add_diagnostic(
block_stmt.span,
"no-empty",
"Empty block statement",
);
}
} else {
for stmt in &block_stmt.stmts {
self.visit_stmt(stmt, _parent);
}
}
}
fn visit_switch_stmt(&mut self, switch: &SwitchStmt, _parent: &dyn Node) {
if switch.cases.is_empty() {
self.context.add_diagnostic(
switch.span,
"no-empty",
"Empty switch statement",
);
}
}
}
trait ContainsComments {
fn contains_comments(&self, context: &Context) -> bool;
}
impl ContainsComments for BlockStmt {
fn contains_comments(&self, context: &Context) -> bool {
context
.leading_comments
.values()
.flatten()
.any(|comment| self.span.contains(comment.span))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::*;
#[test]
fn no_empty_ok() {
assert_lint_ok::<NoEmpty>(r#"function foobar() {}"#);
assert_lint_ok::<NoEmpty>(
r#"
class Foo {
constructor() {}
}
"#,
);
assert_lint_ok::<NoEmpty>(r#"if (foo) { var bar = ""; }"#);
assert_lint_ok::<NoEmpty>(
r#"
if (foo) {
// This block is not empty
}
"#,
);
assert_lint_ok::<NoEmpty>(
r#"
switch (foo) {
case bar:
break;
}
"#,
);
assert_lint_ok::<NoEmpty>(
r#"
if (foo) {
if (bar) {
var baz = "";
}
}
"#,
);
assert_lint_ok::<NoEmpty>("const testFunction = (): void => {};");
}
#[test]
fn it_fails_for_an_empty_if_block() {
assert_lint_err::<NoEmpty>("if (foo) { }", 9);
}
#[test]
fn it_fails_for_an_empty_block_with_preceding_comments() {
assert_lint_err_on_line::<NoEmpty>(
r#"
// This is an empty block
if (foo) { }
"#,
3,
9,
);
}
#[test]
fn it_fails_for_an_empty_while_block() {
assert_lint_err::<NoEmpty>("while (foo) { }", 12);
}
#[test]
fn it_fails_for_an_empty_do_while_block() {
assert_lint_err::<NoEmpty>("do { } while (foo);", 3);
}
#[test]
fn it_fails_for_an_empty_for_block() {
assert_lint_err::<NoEmpty>("for(;;) { }", 8);
}
#[test]
fn it_fails_for_an_empty_for_in_block() {
assert_lint_err::<NoEmpty>("for(var foo in bar) { }", 20);
}
#[test]
fn it_fails_for_an_empty_for_of_block() {
assert_lint_err::<NoEmpty>("for(var foo of bar) { }", 20);
}
#[test]
fn it_fails_for_an_empty_switch_block() {
assert_lint_err::<NoEmpty>("switch (foo) { }", 0);
}
#[test]
fn it_fails_for_an_empty_try_catch_block() {
assert_lint_err_n::<NoEmpty>("try { } catch (err) { }", vec![4, 20]);
}
#[test]
fn it_fails_for_an_empty_try_catch_finally_block() {
assert_lint_err_n::<NoEmpty>(
"try { } catch (err) { } finally { }",
vec![4, 20, 32],
);
}
#[test]
fn it_fails_for_a_nested_empty_if_block() {
assert_lint_err::<NoEmpty>("if (foo) { if (bar) { } }", 20);
}
#[test]
fn it_fails_for_a_nested_empty_while_block() {
assert_lint_err::<NoEmpty>("if (foo) { while (bar) { } }", 23);
}
#[test]
fn it_fails_for_a_nested_empty_do_while_block() {
assert_lint_err::<NoEmpty>("if (foo) { do { } while (bar); }", 14);
}
#[test]
fn it_fails_for_a_nested_empty_for_block() {
assert_lint_err::<NoEmpty>("if (foo) { for(;;) { } }", 19);
}
#[test]
fn it_fails_for_a_nested_empty_for_in_block() {
assert_lint_err::<NoEmpty>("if (foo) { for(var bar in foo) { } }", 31);
}
#[test]
fn it_fails_for_a_nested_empty_for_of_block() {
assert_lint_err::<NoEmpty>("if (foo) { for(var bar of foo) { } }", 31);
}
#[test]
fn it_fails_for_a_nested_empty_switch() {
assert_lint_err::<NoEmpty>("if (foo) { switch (foo) { } }", 11);
}
}