feat(linter): eslint plugin unicorn: no useless switch case (#1463)

This commit is contained in:
Cameron 2023-11-21 02:02:32 +00:00 committed by GitHub
parent f81f15ff71
commit 8b0032d4af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 396 additions and 17 deletions

View file

@ -170,6 +170,7 @@ mod unicorn {
pub mod no_unnecessary_await;
pub mod no_useless_fallback_in_spread;
pub mod no_useless_promise_resolve_reject;
pub mod no_useless_switch_case;
pub mod number_literal_case;
pub mod prefer_add_event_listener;
pub mod prefer_array_flat_map;
@ -331,6 +332,7 @@ oxc_macros::declare_all_lint_rules! {
unicorn::no_unnecessary_await,
unicorn::no_useless_fallback_in_spread,
unicorn::no_useless_promise_resolve_reject,
unicorn::no_useless_switch_case,
unicorn::number_literal_case,
unicorn::prefer_add_event_listener,
unicorn::prefer_array_flat_map,

View file

@ -1,4 +1,4 @@
use oxc_ast::{ast::Statement, AstKind};
use oxc_ast::AstKind;
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
@ -6,7 +6,7 @@ use oxc_diagnostics::{
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use crate::{context::LintContext, rule::Rule};
use crate::{context::LintContext, rule::Rule, utils::is_empty_stmt};
#[derive(Debug, Error, Diagnostic)]
#[error("eslint-plugin-unicorn(no-empty-file): Empty files are not allowed.")]
@ -53,20 +53,6 @@ impl Rule for NoEmptyFile {
}
}
fn is_empty_stmt(stmt: &Statement) -> bool {
match stmt {
Statement::BlockStatement(block_stmt) => {
if block_stmt.body.is_empty() || block_stmt.body.iter().all(|node| is_empty_stmt(node))
{
return true;
}
false
}
Statement::EmptyStatement(_) => true,
_ => false,
}
}
fn has_triple_slash_directive(ctx: &LintContext<'_>) -> bool {
for (start, comment) in ctx.semantic().trivias().comments() {
if !comment.is_single_line() {

View file

@ -0,0 +1,266 @@
use oxc_ast::AstKind;
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
};
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use crate::{context::LintContext, rule::Rule, utils::is_empty_stmt, AstNode};
#[derive(Debug, Error, Diagnostic)]
#[error("eslint-plugin-unicorn(no-useless-switch-case): Useless case in switch statement.")]
#[diagnostic(
severity(warning),
help("Consider removing this case or removing the `default` case.")
)]
struct NoUselessSwitchCaseDiagnostic(#[label] pub Span);
#[derive(Debug, Default, Clone)]
pub struct NoUselessSwitchCase;
declare_oxc_lint!(
/// ### What it does
///
/// Disallows useless default cases in switch statements.
///
/// ### Why is this bad?
///
/// An empty case before the last default case is useless.
///
/// ### Example
/// ```javascript
/// // bad
/// switch (foo) {
/// case 1:
/// default:
/// handleDefaultCase();
/// break;
/// }
/// // good:
/// switch (foo) {
/// case 1:
/// case 2:
/// handleCase1And2();
/// break;
/// }
/// ```
NoUselessSwitchCase,
pedantic
);
impl Rule for NoUselessSwitchCase {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let AstKind::SwitchStatement(switch_statement) = node.kind() else {
return;
};
let cases = &switch_statement.cases;
let default_cases = cases.iter().filter(|v| v.test.is_none()).collect::<Vec<_>>();
if default_cases.len() != 1 {
return;
}
let default_case = default_cases[0];
// Check if the `default` case is the last case
if default_case as *const _ != cases.last().unwrap() as *const _ {
return;
}
let mut useless_cases = vec![];
for case in cases.iter().rev().skip(1) {
if case.consequent.iter().all(|v| is_empty_stmt(v)) {
useless_cases.push(case);
} else {
break;
}
}
if useless_cases.is_empty() {
return;
}
for case in useless_cases {
ctx.diagnostic(NoUselessSwitchCaseDiagnostic(case.span));
}
}
}
#[test]
fn test() {
use crate::tester::Tester;
let pass = vec![
r"
switch (foo) {
case a:
case b:
handleDefaultCase();
break;
}
",
r"
switch (foo) {
case a:
handleCaseA();
break;
default:
handleDefaultCase();
break;
}
",
r"
switch (foo) {
case a:
handleCaseA();
default:
handleDefaultCase();
break;
}
",
r"
switch (foo) {
case a:
break;
default:
handleDefaultCase();
break;
}
",
r"
switch (foo) {
case a:
handleCaseA();
// Fallthrough
default:
handleDefaultCase();
break;
}
",
r"
switch (foo) {
case a:
default:
handleDefaultCase();
break;
case b:
handleCaseB();
break;
}
",
r"
switch (1) {
// This is not useless
case 1:
default:
console.log('1')
case 1:
console.log('2')
}
",
r"
switch (1) {
default:
handleDefaultCase1();
break;
case 1:
default:
handleDefaultCase2();
break;
}
",
];
let fail = vec![
r"
switch (foo) {
case a:
default:
handleDefaultCase();
break;
}
",
r"
switch (foo) {
case a: {
}
default:
handleDefaultCase();
break;
}
",
r"
switch (foo) {
case a: {
;;
{
;;
{
;;
}
}
}
default:
handleDefaultCase();
break;
}
",
r"
switch (foo) {
case a:
case (( b )) :
default:
handleDefaultCase();
break;
}
",
r"
switch (foo) {
case a:
case b:
handleCaseAB();
break;
case d:
case d:
default:
handleDefaultCase();
break;
}
",
r"
switch (foo) {
case a:
case b:
default:
handleDefaultCase();
break;
}
",
r"
switch (foo) {
// eslint-disable-next-line
case a:
case b:
default:
handleDefaultCase();
break;
}
",
r"
switch (foo) {
case a:
// eslint-disable-next-line
case b:
default:
handleDefaultCase();
break;
}
",
];
Tester::new_without_config(NoUselessSwitchCase::NAME, pass, fail).test_and_snapshot();
}

View file

@ -0,0 +1,111 @@
---
source: crates/oxc_linter/src/tester.rs
expression: no_useless_switch_case
---
⚠ eslint-plugin-unicorn(no-useless-switch-case): Useless case in switch statement.
╭─[no_useless_switch_case.tsx:2:1]
2 │ switch (foo) {
3 │ case a:
· ───────
4 │ default:
╰────
help: Consider removing this case or removing the `default` case.
⚠ eslint-plugin-unicorn(no-useless-switch-case): Useless case in switch statement.
╭─[no_useless_switch_case.tsx:2:1]
2 │ switch (foo) {
3 │ ╭─▶ case a: {
4 │ ╰─▶ }
5 │ default:
╰────
help: Consider removing this case or removing the `default` case.
⚠ eslint-plugin-unicorn(no-useless-switch-case): Useless case in switch statement.
╭─[no_useless_switch_case.tsx:2:1]
2 │ switch (foo) {
3 │ ╭─▶ case a: {
4 │ │ ;;
5 │ │ {
6 │ │ ;;
7 │ │ {
8 │ │ ;;
9 │ │ }
10 │ │ }
11 │ ╰─▶ }
12 │ default:
╰────
help: Consider removing this case or removing the `default` case.
⚠ eslint-plugin-unicorn(no-useless-switch-case): Useless case in switch statement.
╭─[no_useless_switch_case.tsx:3:1]
3 │ case a:
4 │ case (( b )) :
· ──────────────────────
5 │ default:
╰────
help: Consider removing this case or removing the `default` case.
⚠ eslint-plugin-unicorn(no-useless-switch-case): Useless case in switch statement.
╭─[no_useless_switch_case.tsx:2:1]
2 │ switch (foo) {
3 │ case a:
· ───────
4 │ case (( b )) :
╰────
help: Consider removing this case or removing the `default` case.
⚠ eslint-plugin-unicorn(no-useless-switch-case): Useless case in switch statement.
╭─[no_useless_switch_case.tsx:7:1]
7 │ case d:
8 │ case d:
· ───────
9 │ default:
╰────
help: Consider removing this case or removing the `default` case.
⚠ eslint-plugin-unicorn(no-useless-switch-case): Useless case in switch statement.
╭─[no_useless_switch_case.tsx:6:1]
6 │ break;
7 │ case d:
· ───────
8 │ case d:
╰────
help: Consider removing this case or removing the `default` case.
⚠ eslint-plugin-unicorn(no-useless-switch-case): Useless case in switch statement.
╭─[no_useless_switch_case.tsx:3:1]
3 │ case a:
4 │ case b:
· ───────
5 │ default:
╰────
help: Consider removing this case or removing the `default` case.
⚠ eslint-plugin-unicorn(no-useless-switch-case): Useless case in switch statement.
╭─[no_useless_switch_case.tsx:2:1]
2 │ switch (foo) {
3 │ case a:
· ───────
4 │ case b:
╰────
help: Consider removing this case or removing the `default` case.
⚠ eslint-plugin-unicorn(no-useless-switch-case): Useless case in switch statement.
╭─[no_useless_switch_case.tsx:4:1]
4 │ case a:
5 │ case b:
· ───────
6 │ default:
╰────
help: Consider removing this case or removing the `default` case.
⚠ eslint-plugin-unicorn(no-useless-switch-case): Useless case in switch statement.
╭─[no_useless_switch_case.tsx:2:1]
2 │ switch (foo) {
3 │ case a:
· ───────
4 │ // eslint-disable-next-line
╰────
help: Consider removing this case or removing the `default` case.

View file

@ -1,4 +1,4 @@
use oxc_ast::ast::Expression;
use oxc_ast::ast::{Expression, Statement};
pub fn is_node_value_not_dom_node(expr: &Expression) -> bool {
matches!(
@ -12,3 +12,17 @@ pub fn is_node_value_not_dom_node(expr: &Expression) -> bool {
| Expression::StringLiteral(_)
)
}
pub fn is_empty_stmt(stmt: &Statement) -> bool {
match stmt {
Statement::BlockStatement(block_stmt) => {
if block_stmt.body.is_empty() || block_stmt.body.iter().all(|node| is_empty_stmt(node))
{
return true;
}
false
}
Statement::EmptyStatement(_) => true,
_ => false,
}
}