mirror of
https://github.com/danbulant/oxc
synced 2026-05-22 21:58:36 +00:00
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:
parent
e2dd8ac8fc
commit
9744707b3b
3 changed files with 313 additions and 0 deletions
|
|
@ -25,6 +25,7 @@ mod import {
|
||||||
mod eslint {
|
mod eslint {
|
||||||
pub mod array_callback_return;
|
pub mod array_callback_return;
|
||||||
pub mod constructor_super;
|
pub mod constructor_super;
|
||||||
|
pub mod default_case;
|
||||||
pub mod default_case_last;
|
pub mod default_case_last;
|
||||||
pub mod default_param_last;
|
pub mod default_param_last;
|
||||||
pub mod eqeqeq;
|
pub mod eqeqeq;
|
||||||
|
|
@ -390,6 +391,7 @@ mod tree_shaking {
|
||||||
oxc_macros::declare_all_lint_rules! {
|
oxc_macros::declare_all_lint_rules! {
|
||||||
eslint::array_callback_return,
|
eslint::array_callback_return,
|
||||||
eslint::constructor_super,
|
eslint::constructor_super,
|
||||||
|
eslint::default_case,
|
||||||
eslint::default_case_last,
|
eslint::default_case_last,
|
||||||
eslint::default_param_last,
|
eslint::default_param_last,
|
||||||
eslint::eqeqeq,
|
eslint::eqeqeq,
|
||||||
|
|
|
||||||
258
crates/oxc_linter/src/rules/eslint/default_case.rs
Normal file
258
crates/oxc_linter/src/rules/eslint/default_case.rs
Normal 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();
|
||||||
|
}
|
||||||
53
crates/oxc_linter/src/snapshots/default_case.snap
Normal file
53
crates/oxc_linter/src/snapshots/default_case.snap
Normal 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.
|
||||||
Loading…
Reference in a new issue