From 21bf82531ebfe260d815657d17dae1c785395a03 Mon Sep 17 00:00:00 2001 From: Antoine ZANARDI Date: Sat, 30 Aug 2025 15:33:13 +0200 Subject: [PATCH 1/3] refactor(eslint/default-case): simplify implementation and enhance readability --- .../src/rules/eslint/default_case.rs | 140 ++++++++++-------- 1 file changed, 76 insertions(+), 64 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/default_case.rs b/crates/oxc_linter/src/rules/eslint/default_case.rs index dfc4d3e0a34b4..33ff1d3b74ab0 100644 --- a/crates/oxc_linter/src/rules/eslint/default_case.rs +++ b/crates/oxc_linter/src/rules/eslint/default_case.rs @@ -31,22 +31,37 @@ impl std::ops::Deref for DefaultCase { declare_oxc_lint!( /// ### What it does /// - /// Require default cases in switch statements + /// Enforces that all `switch` statements include a `default` case, + /// unless explicitly marked with a configured comment. /// /// ### Why is this bad? /// - /// Some code conventions require that all switch statements have a default case, - /// even if the default case is empty. The thinking is that it’s better to always - /// explicitly state what the default behavior should be so that it’s clear - /// whether or not the developer forgot to include the default behavior by mistake. + /// Without a `default` case, it is unclear whether the omission was + /// intentional or an oversight. Adding a `default` or a special comment + /// makes the code more explicit and reduces mistakes. /// /// You may optionally include a `// no default` after the last case if there is /// no default case. The comment may be in any desired case, such as `// No Default`. /// - /// ### Examples + /// ### Options + /// + /// First option: + /// - Type: `object` + /// - Properties: + /// - `commentPattern`: `string` (default: `/^no default$/i`) - A regex pattern used to detect comments that mark the absence of a `default` case as intentional. + /// + /// Example configuration: + /// ```json + /// { + /// "default-case": ["error", { "commentPattern": "^skip\\sdefault" }] + /// } + /// ``` /// /// Examples of **incorrect** code for this rule: - /// ```javascript + /// ```js + /// /* default-case: ["error"] */ + /// + /// /* ✘ Bad: */ /// switch (foo) { /// case 1: /// break; @@ -54,56 +69,46 @@ declare_oxc_lint!( /// ``` /// /// Examples of **correct** code for this rule: - /// ```javascript + /// ```js + /// /* default-case: ["error"] */ + /// + /// /* ✔ Good: */ /// switch (a) { /// case 1: - /// /* code */ /// break; - /// /// default: - /// /* code */ /// break; /// } - /// ``` /// - /// ```javascript /// switch (a) { /// case 1: - /// /* code */ /// break; - /// /// // no default /// } /// ``` /// - /// ```javascript + /// #### commentPattern + /// + /// Examples of **incorrect** code for this rule with the `{ "commentPattern": "^skip\\sdefault" }` option: + /// ```js + /// /* default-case: ["error", { "commentPattern": "^skip\\sdefault" }] */ + /// + /// /* ✘ Bad: */ /// switch (a) { /// case 1: - /// /* code */ /// break; - /// - /// // No Default + /// // no default /// } /// ``` /// - /// ### Options - /// - /// #### commentPattern + /// Examples of **correct** code for this rule with the `{ "commentPattern": "^skip\\sdefault" }` option: + /// ```js + /// /* default-case: ["error", { "commentPattern": "^skip\\sdefault" }] */ /// - /// `{ type: string, default: "/^no default$/i" }` - /// - /// This option is for specifying an alternative regular expression which - /// will override the default `/^no default$/i` comment test pattern. - /// - /// For example if `{ "commentPattern": "^skip\\sdefault" }` were used - /// then the following example would not violate the rule: - /// - /// ```javascript - /// switch(a) { + /// /* ✔ Good: */ + /// switch (a) { /// case 1: - /// /* code */ /// break; - /// /// // skip default /// } /// ``` @@ -114,48 +119,55 @@ declare_oxc_lint!( 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(); - } - } + let comment_pattern = value + .get(0) + .and_then(|config| config.get("commentPattern")) + .and_then(serde_json::Value::as_str) + .and_then(|pattern| RegexBuilder::new(pattern).case_insensitive(true).build().ok()); + let case_config = DefaultCaseConfig { comment_pattern }; - Self(Box::new(cfg)) + Self(Box::new(case_config)) } fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - if let AstKind::SwitchStatement(switch) = node.kind() { - let cases = &switch.cases; + let AstKind::SwitchStatement(switch) = node.kind() else { + return; + }; - if cases.is_empty() || cases.iter().any(|case| case.test.is_none()) { - return; - } + let cases = &switch.cases; - let Some(last_case) = cases.last() else { - return; - }; + if cases.is_empty() || cases.iter().any(|case| case.test.is_none()) { + return; + } - let has_default_comment = ctx - .semantic() - .comments_range(last_case.span.start..switch.span.end) - .next_back() - .is_some_and(|comment| { - let raw = ctx.source_range(comment.content_span()).trim(); - match &self.comment_pattern { - Some(comment_pattern) => comment_pattern.is_match(raw), - None => raw.eq_ignore_ascii_case("no default"), - } - }); + let Some(last_case) = cases.last() else { + return; + }; - if !has_default_comment { - ctx.diagnostic(default_case_diagnostic(switch.span)); - } + if !has_default_comment(ctx, switch.span, last_case.span, self.comment_pattern.as_ref()) { + ctx.diagnostic(default_case_diagnostic(switch.span)); } } } +fn has_default_comment( + ctx: &LintContext, + switch_span: Span, + last_case_span: Span, + comment_pattern: Option<&Regex>, +) -> bool { + ctx.semantic().comments_range(last_case_span.start..switch_span.end).next_back().is_some_and( + |comment| { + let raw = ctx.source_range(comment.content_span()).trim(); + + match comment_pattern { + Some(re) => re.is_match(raw), + None => raw.eq_ignore_ascii_case("no default"), + } + }, + ) +} + #[test] fn test() { use crate::tester::Tester; From e592e7fd0c39c22e01f01b1697d1936faefd7904 Mon Sep 17 00:00:00 2001 From: Antoine ZANARDI Date: Sat, 30 Aug 2025 15:48:15 +0200 Subject: [PATCH 2/3] docs(default_case): format commentPattern for improved clarity --- crates/oxc_linter/src/rules/eslint/default_case.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxc_linter/src/rules/eslint/default_case.rs b/crates/oxc_linter/src/rules/eslint/default_case.rs index 33ff1d3b74ab0..a23d8b16bcca5 100644 --- a/crates/oxc_linter/src/rules/eslint/default_case.rs +++ b/crates/oxc_linter/src/rules/eslint/default_case.rs @@ -87,7 +87,7 @@ declare_oxc_lint!( /// } /// ``` /// - /// #### commentPattern + /// #### `commentPattern` /// /// Examples of **incorrect** code for this rule with the `{ "commentPattern": "^skip\\sdefault" }` option: /// ```js From 579c1fb9dd6febe6a2cef7cfbd0e234aee608d1f Mon Sep 17 00:00:00 2001 From: Antoine ZANARDI Date: Tue, 2 Sep 2025 07:47:23 +0200 Subject: [PATCH 3/3] docs(default_case): update examples to remove redundant comments for clarity --- crates/oxc_linter/src/rules/eslint/default_case.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/oxc_linter/src/rules/eslint/default_case.rs b/crates/oxc_linter/src/rules/eslint/default_case.rs index a23d8b16bcca5..caeb686e9ee31 100644 --- a/crates/oxc_linter/src/rules/eslint/default_case.rs +++ b/crates/oxc_linter/src/rules/eslint/default_case.rs @@ -61,7 +61,6 @@ declare_oxc_lint!( /// ```js /// /* default-case: ["error"] */ /// - /// /* ✘ Bad: */ /// switch (foo) { /// case 1: /// break; @@ -72,7 +71,6 @@ declare_oxc_lint!( /// ```js /// /* default-case: ["error"] */ /// - /// /* ✔ Good: */ /// switch (a) { /// case 1: /// break; @@ -93,7 +91,6 @@ declare_oxc_lint!( /// ```js /// /* default-case: ["error", { "commentPattern": "^skip\\sdefault" }] */ /// - /// /* ✘ Bad: */ /// switch (a) { /// case 1: /// break; @@ -105,7 +102,6 @@ declare_oxc_lint!( /// ```js /// /* default-case: ["error", { "commentPattern": "^skip\\sdefault" }] */ /// - /// /* ✔ Good: */ /// switch (a) { /// case 1: /// break;