diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 6a256e3509867..67e42da8bffad 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -14,6 +14,7 @@ oxc_macros::declare_all_lint_rules! { eslint::getter_return, eslint::no_array_constructor, eslint::no_async_promise_executor, + eslint::no_case_declarations, eslint::no_bitwise, eslint::no_caller, eslint::no_class_assign, diff --git a/crates/oxc_linter/src/rules/eslint/no_case_declarations.rs b/crates/oxc_linter/src/rules/eslint/no_case_declarations.rs new file mode 100644 index 0000000000000..3a4c0d6ba9ce7 --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/no_case_declarations.rs @@ -0,0 +1,97 @@ +use oxc_ast::{AstKind, ast::{Statement, Declaration}}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Error, Diagnostic)] +#[error("eslint(no-case-declarations): Unexpected lexical declaration in case block.")] +#[diagnostic(severity(warning))] +struct NoCaseDeclarationsDiagnostic(#[label] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct NoCaseDeclarations; + +declare_oxc_lint!( + /// ### What it does + /// Disallow lexical declarations in case clauses. + /// + /// ### Why is this bad? + /// The reason is that the lexical declaration is visible + /// in the entire switch block but it only gets initialized when it is assigned, + /// which will only happen if the case where it is defined is reached. + /// + /// ### Example + /// ```javascript + // switch (foo) { + // case 1: + // let x = 1; + // break; + // case 2: + // const y = 2; + // break; + // case 3: + // function f() {} + // break; + // default: + // class C {} + // } + /// ``` + NoCaseDeclarations, + correctness +); + + +impl Rule for NoCaseDeclarations { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + if let AstKind::SwitchCase(switch_case) = node.kind() { + let consequent = &switch_case.consequent; + + for stmt in consequent { + if let Statement::Declaration(dcl) = stmt { + match dcl { + Declaration::FunctionDeclaration(d) => { + ctx.diagnostic(NoCaseDeclarationsDiagnostic(d.span)); + } + Declaration::ClassDeclaration(d) => { + ctx.diagnostic(NoCaseDeclarationsDiagnostic(d.span)); + } + Declaration::VariableDeclaration(var) if var.kind.is_lexical() => { + ctx.diagnostic(NoCaseDeclarationsDiagnostic(var.span)); + } + _ => {} + } + }; + } + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("switch (a) { case 1: { let x = 1; break; } default: { let x = 2; break; } }", None), + ("switch (a) { case 1: { const x = 1; break; } default: { const x = 2; break; } }", None), + ("switch (a) { case 1: { function f() {} break; } default: { function f() {} break; } }", None), + ("switch (a) { case 1: { class C {} break; } default: { class C {} break; } }", None) + ]; + + let fail = vec![ + ("switch (a) { case 1: let x = 1; break; }", None), + ("switch (a) { default: let x = 2; break; }", None), + ("switch (a) { case 1: const x = 1; break; }", None), + ("switch (a) { default: const x = 2; break; }", None), + ("switch (a) { case 1: function f() {} break; }", None), + ("switch (a) { default: function f() {} break; }", None), + ("switch (a) { case 1: class C {} break; }", None), + ("switch (a) { default: class C {} break; }", None) + ]; + + Tester::new(NoCaseDeclarations::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_case_declarations.snap b/crates/oxc_linter/src/snapshots/no_case_declarations.snap new file mode 100644 index 0000000000000..921bb62b341ed --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_case_declarations.snap @@ -0,0 +1,53 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: no_case_declarations +--- + ⚠ eslint(no-case-declarations): Unexpected lexical declaration in case block. + ╭─[no_case_declarations.tsx:1:1] + 1 │ switch (a) { case 1: let x = 1; break; } + · ────────── + ╰──── + + ⚠ eslint(no-case-declarations): Unexpected lexical declaration in case block. + ╭─[no_case_declarations.tsx:1:1] + 1 │ switch (a) { default: let x = 2; break; } + · ────────── + ╰──── + + ⚠ eslint(no-case-declarations): Unexpected lexical declaration in case block. + ╭─[no_case_declarations.tsx:1:1] + 1 │ switch (a) { case 1: const x = 1; break; } + · ──────────── + ╰──── + + ⚠ eslint(no-case-declarations): Unexpected lexical declaration in case block. + ╭─[no_case_declarations.tsx:1:1] + 1 │ switch (a) { default: const x = 2; break; } + · ──────────── + ╰──── + + ⚠ eslint(no-case-declarations): Unexpected lexical declaration in case block. + ╭─[no_case_declarations.tsx:1:1] + 1 │ switch (a) { case 1: function f() {} break; } + · ─────────────── + ╰──── + + ⚠ eslint(no-case-declarations): Unexpected lexical declaration in case block. + ╭─[no_case_declarations.tsx:1:1] + 1 │ switch (a) { default: function f() {} break; } + · ─────────────── + ╰──── + + ⚠ eslint(no-case-declarations): Unexpected lexical declaration in case block. + ╭─[no_case_declarations.tsx:1:1] + 1 │ switch (a) { case 1: class C {} break; } + · ────────── + ╰──── + + ⚠ eslint(no-case-declarations): Unexpected lexical declaration in case block. + ╭─[no_case_declarations.tsx:1:1] + 1 │ switch (a) { default: class C {} break; } + · ────────── + ╰──── + +