diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 5c8951a4c..e1083f21d 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -1,6 +1,8 @@ mod no_debugger; +mod no_empty; pub use no_debugger::NoDebugger; +pub use no_empty::NoEmpty; use oxc_ast::AstKind; use crate::{context::LintContext, rule::Rule}; @@ -8,12 +10,14 @@ use crate::{context::LintContext, rule::Rule}; #[derive(Debug, Clone)] pub enum RuleEnum { NoDebugger(NoDebugger), + NoEmpty(NoEmpty), } impl RuleEnum { pub fn run<'a>(&self, kind: AstKind<'a>, ctx: &LintContext<'a>) { match self { Self::NoDebugger(rule) => rule.run(kind, ctx), + Self::NoEmpty(rule) => rule.run(kind, ctx), } } } diff --git a/crates/oxc_linter/src/rules/no_debugger.rs b/crates/oxc_linter/src/rules/no_debugger.rs index e7e53e721..beea9b11c 100644 --- a/crates/oxc_linter/src/rules/no_debugger.rs +++ b/crates/oxc_linter/src/rules/no_debugger.rs @@ -26,6 +26,7 @@ impl Rule for NoDebugger { fn test() { use crate::rules::RuleEnum; use crate::tester::Tester; + let pass = vec!["var test = { debugger: 1 }; test.debugger;"]; let fail = vec!["if (foo) debugger"]; diff --git a/crates/oxc_linter/src/rules/no_empty.rs b/crates/oxc_linter/src/rules/no_empty.rs new file mode 100644 index 000000000..8080e6c06 --- /dev/null +++ b/crates/oxc_linter/src/rules/no_empty.rs @@ -0,0 +1,78 @@ +use oxc_ast::{AstKind, Span}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; + +use crate::{context::LintContext, rule::Rule}; + +#[derive(Debug, Error, Diagnostic)] +#[error("eslint(no-empty): Disallow empty block statements")] +#[diagnostic(severity(warning), help("Add comments inside {0} statement"))] +struct NoEmptyDiagnostic(&'static str, #[label("Empty {0} statement")] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct NoEmpty; + +impl Rule for NoEmpty { + fn run<'a>(&self, kind: AstKind<'a>, ctx: &LintContext<'a>) { + match kind { + AstKind::BlockStatement(block) if block.body.is_empty() => { + // TODO: check comment + ctx.diagnostic(NoEmptyDiagnostic("block", block.span)); + } + AstKind::SwitchStatement(switch) if switch.cases.is_empty() => { + ctx.diagnostic(NoEmptyDiagnostic("switch", switch.span)); + } + _ => {} + } + } +} + +#[test] +fn test() { + use crate::rules::RuleEnum; + use crate::tester::Tester; + + let pass = vec![ + "if (foo) { bar() }", + "while (foo) { bar() }", + "for (;foo;) { bar() }", + "try { foo() } catch (ex) { foo() }", + "switch(foo) {case 'foo': break;}", + "(function() { }())", + "var foo = () => {};", + "function foo() { }", + "if (foo) {/* empty */}", + "while (foo) {/* empty */}", + "for (;foo;) {/* empty */}", + "try { foo() } catch (ex) {/* empty */}", + "try { foo() } catch (ex) {// empty\n}", + "try { foo() } finally {// empty\n}", + "try { foo() } finally {// test\n}", + "try { foo() } finally {\n \n // hi i am off no use\n}", + "try { foo() } catch (ex) {/* test111 */}", + "if (foo) { bar() } else { // nothing in me \n}", + "if (foo) { bar() } else { /**/ \n}", + "if (foo) { bar() } else { // \n}", + "try { foo(); } catch (ex) {}", + "try { foo(); } catch (ex) {} finally { bar(); }", + ]; + + let fail = vec![ + "try {} catch (ex) {throw ex}", + "try { foo() } catch (ex) {}", + "try { foo() } catch (ex) {throw ex} finally {}", + "if (foo) {}", + "while (foo) {}", + "for (;foo;) {}", + "switch(foo) {}", + "switch (foo) { /* empty */ }", + "try {} catch (ex) {}", + "try { foo(); } catch (ex) {} finally {}", + "try {} catch (ex) {} finally {}", + "try { foo(); } catch (ex) {} finally {}", + ]; + + Tester::new("no-empty", RuleEnum::NoEmpty(NoEmpty), pass, fail).test_and_snapshot(); +}