feat(linter/eslint): Implement default_case rule (#3379)

Rule Detail:
[link](https://eslint.org/docs/latest/rules/default-case)

---------

Co-authored-by: Boshen <boshenc@gmail.com>
This commit is contained in:
Jelle van der Waa 2024-05-22 10:49:02 +02:00 committed by GitHub
parent e2dd8ac8fc
commit 9744707b3b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 313 additions and 0 deletions

View file

@ -25,6 +25,7 @@ mod import {
mod eslint {
pub mod array_callback_return;
pub mod constructor_super;
pub mod default_case;
pub mod default_case_last;
pub mod default_param_last;
pub mod eqeqeq;
@ -390,6 +391,7 @@ mod tree_shaking {
oxc_macros::declare_all_lint_rules! {
eslint::array_callback_return,
eslint::constructor_super,
eslint::default_case,
eslint::default_case_last,
eslint::default_param_last,
eslint::eqeqeq,

View file

@ -0,0 +1,258 @@
use oxc_ast::AstKind;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use regex::Regex;
use regex::RegexBuilder;
use crate::{context::LintContext, rule::Rule, AstNode};
fn default_case_diagnostic(span0: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("eslint(default-case): Require default cases in switch statements.")
.with_help("Add a default case.")
.with_labels([span0.into()])
}
#[derive(Debug, Default, Clone)]
pub struct DefaultCase(Box<DefaultCaseConfig>);
#[derive(Debug, Default, Clone)]
pub struct DefaultCaseConfig {
comment_pattern: Option<Regex>,
}
impl std::ops::Deref for DefaultCase {
type Target = DefaultCaseConfig;
fn deref(&self) -> &Self::Target {
&self.0
}
}
declare_oxc_lint!(
/// ### What it does
///
/// Require default cases in switch statements
///
/// ### Why is this bad?
///
/// Some code conventions require that all switch statements have a default case, even if the
/// default case is empty.
///
/// ### Example
/// ```javascript
/// switch (foo) {
/// case 1:
/// break;
/// }
/// ```
DefaultCase,
restriction,
);
impl Rule for DefaultCase {
fn from_configuration(value: serde_json::Value) -> Self {
let mut cfg = DefaultCaseConfig::default();
if let Some(config) = value.get(0) {
if let Some(val) = config.get("commentPattern").and_then(serde_json::Value::as_str) {
cfg.comment_pattern = RegexBuilder::new(val).case_insensitive(true).build().ok();
}
}
Self(Box::new(cfg))
}
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if let AstKind::SwitchStatement(switch) = node.kind() {
let cases = &switch.cases;
if !cases.is_empty() && !cases.iter().any(|case| case.test.is_none()) {
if let Some(last_case) = cases.last() {
let has_default_comment = ctx
.semantic()
.trivias()
.comments_range(last_case.span.start..switch.span.end)
.last()
.is_some_and(|(start, comment)| {
let raw = Span::new(*start, comment.end)
.source_text(ctx.semantic().source_text())
.trim();
match &self.comment_pattern {
Some(comment_pattern) => comment_pattern.is_match(raw),
None => raw.eq_ignore_ascii_case("no default"),
}
});
if !has_default_comment {
ctx.diagnostic(default_case_diagnostic(switch.span));
}
}
}
}
}
}
#[test]
fn test() {
use crate::tester::Tester;
let pass = vec![
("switch (a) { case 1: break; default: break; }", None),
("switch (a) { case 1: break; case 2: default: break; }", None),
(
"switch (a) { case 1: break; default: break;
//no default
}",
None,
),
(
"switch (a) {
case 1: break;
//oh-oh
// no default
}",
None,
),
(
"switch (a) {
case 1:
// no default
}",
None,
),
(
"switch (a) {
case 1:
// No default
}",
None,
),
(
"switch (a) {
case 1:
// no deFAUlt
}",
None,
),
(
"switch (a) {
case 1:
// NO DEFAULT
}",
None,
),
(
"switch (a) {
case 1: a = 4;
// no default
}",
None,
),
(
"switch (a) {
case 1: a = 4;
/* no default */
}",
None,
),
(
"switch (a) {
case 1: a = 4; break; break;
// no default
}",
None,
),
(
"switch (a) { // no default
}",
None,
),
("switch (a) { }", None),
(
"switch (a) { case 1: break; default: break; }",
Some(serde_json::json!([{
"commentPattern": "default case omitted"
}])),
),
(
"switch (a) { case 1: break;
// skip default case
}",
Some(serde_json::json!([{
"commentPattern": "^skip default"
}])),
),
(
"switch (a) { case 1: break;
/*
TODO:
throw error in default case
*/
}",
Some(serde_json::json!([{
"commentPattern": "default"
}])),
),
(
"switch (a) { case 1: break;
//
}",
Some(serde_json::json!([{
"commentPattern": ".?"
}])),
),
];
let fail = vec![
("switch (a) { case 1: break; }", None),
(
"switch (a) {
// no default
case 1: break; }",
None,
),
(
"switch (a) { case 1: break;
// no default
// nope
}",
None,
),
(
"switch (a) { case 1: break;
// no default
}",
Some(serde_json::json!([{
"commentPattern": "skipped default case"
}])),
),
(
"switch (a) {
case 1: break;
// default omitted intentionally
// TODO: add default case
}",
Some(serde_json::json!([{
"commentPattern": "default omitted"
}])),
),
(
"switch (a) {
case 1: break;
}",
Some(serde_json::json!([{
"commentPattern": ".?"
}])),
),
];
Tester::new(DefaultCase::NAME, pass, fail).test_and_snapshot();
}

View file

@ -0,0 +1,53 @@
---
source: crates/oxc_linter/src/tester.rs
expression: default_case
---
⚠ eslint(default-case): Require default cases in switch statements.
╭─[default_case.tsx:1:1]
1 │ switch (a) { case 1: break; }
· ─────────────────────────────
╰────
help: Add a default case.
⚠ eslint(default-case): Require default cases in switch statements.
╭─[default_case.tsx:1:1]
1 │ ╭─▶ switch (a) {
2 │ │ // no default
3 │ ╰─▶ case 1: break; }
╰────
help: Add a default case.
⚠ eslint(default-case): Require default cases in switch statements.
╭─[default_case.tsx:1:1]
1 │ ╭─▶ switch (a) { case 1: break;
2 │ │ // no default
3 │ │ // nope
4 │ ╰─▶ }
╰────
help: Add a default case.
⚠ eslint(default-case): Require default cases in switch statements.
╭─[default_case.tsx:1:1]
1 │ ╭─▶ switch (a) { case 1: break;
2 │ │ // no default
3 │ ╰─▶ }
╰────
help: Add a default case.
⚠ eslint(default-case): Require default cases in switch statements.
╭─[default_case.tsx:1:1]
1 │ ╭─▶ switch (a) {
2 │ │ case 1: break;
3 │ │ // default omitted intentionally
4 │ │ // TODO: add default case
5 │ ╰─▶ }
╰────
help: Add a default case.
⚠ eslint(default-case): Require default cases in switch statements.
╭─[default_case.tsx:1:1]
1 │ ╭─▶ switch (a) {
2 │ │ case 1: break;
3 │ ╰─▶ }
╰────
help: Add a default case.