feat(rome_js_analyze): `noUselessSwitchCase`
MODIFY
crates/rome_diagnostics_categories/src/categories.rs
crates/rome_diagnostics_categories/src/categories.rs
@@ -61,6 +61,7 @@ define_dategories! {
"lint/nursery/noSetterReturn": "https://docs.rome.tools/lint/rules/noSetterReturn",
"lint/nursery/noStringCaseMismatch": "https://docs.rome.tools/lint/rules/noStringCaseMismatch",
"lint/nursery/noUnsafeFinally": "https://docs.rome.tools/lint/rules/noUnsafeFinally",
+ "lint/nursery/noUselessSwitchCase": "https://docs.rome.tools/lint/rules/noUselessSwitchCase",
"lint/nursery/noVar": "https://docs.rome.tools/lint/rules/noVar",
"lint/nursery/noVoidTypeReturn": "https://docs.rome.tools/lint/rules/noVoidTypeReturn",
"lint/nursery/useAriaPropsForRole": "https://docs.rome.tools/lint/rules/useAriaPropsForRole",

MODIFY
crates/rome_js_analyze/src/analyzers/nursery.rs
crates/rome_js_analyze/src/analyzers/nursery.rs
@@ -19,10 +19,11 @@ mod no_redundant_use_strict;
mod no_setter_return;
mod no_string_case_mismatch;
mod no_unsafe_finally;
+mod no_useless_switch_case;
mod no_void_type_return;
mod use_default_parameter_last;
mod use_default_switch_clause_last;
mod use_enum_initializers;
mod use_exponentiation_operator;
mod use_numeric_literals;
-declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_access_key :: NoAccessKey , self :: no_assign_in_expressions :: NoAssignInExpressions , self :: no_banned_types :: NoBannedTypes , self :: no_const_enum :: NoConstEnum , self :: no_constructor_return :: NoConstructorReturn , self :: no_distracting_elements :: NoDistractingElements , self :: no_duplicate_object_keys :: NoDuplicateObjectKeys , self :: no_empty_interface :: NoEmptyInterface , self :: no_extra_non_null_assertion :: NoExtraNonNullAssertion , self :: no_header_scope :: NoHeaderScope , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: no_non_null_assertion :: NoNonNullAssertion , self :: no_precision_loss :: NoPrecisionLoss , self :: no_redundant_alt :: NoRedundantAlt , self :: no_redundant_use_strict :: NoRedundantUseStrict , self :: no_setter_return :: NoSetterReturn , self :: no_string_case_mismatch :: NoStringCaseMismatch , self :: no_unsafe_finally :: NoUnsafeFinally , self :: no_void_type_return :: NoVoidTypeReturn , self :: use_default_parameter_last :: UseDefaultParameterLast , self :: use_default_switch_clause_last :: UseDefaultSwitchClauseLast , self :: use_enum_initializers :: UseEnumInitializers , self :: use_exponentiation_operator :: UseExponentiationOperator , self :: use_numeric_literals :: UseNumericLiterals ,] } }
+declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_access_key :: NoAccessKey , self :: no_assign_in_expressions :: NoAssignInExpressions , self :: no_banned_types :: NoBannedTypes , self :: no_const_enum :: NoConstEnum , self :: no_constructor_return :: NoConstructorReturn , self :: no_distracting_elements :: NoDistractingElements , self :: no_duplicate_object_keys :: NoDuplicateObjectKeys , self :: no_empty_interface :: NoEmptyInterface , self :: no_extra_non_null_assertion :: NoExtraNonNullAssertion , self :: no_header_scope :: NoHeaderScope , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: no_non_null_assertion :: NoNonNullAssertion , self :: no_precision_loss :: NoPrecisionLoss , self :: no_redundant_alt :: NoRedundantAlt , self :: no_redundant_use_strict :: NoRedundantUseStrict , self :: no_setter_return :: NoSetterReturn , self :: no_string_case_mismatch :: NoStringCaseMismatch , self :: no_unsafe_finally :: NoUnsafeFinally , self :: no_useless_switch_case :: NoUselessSwitchCase , self :: no_void_type_return :: NoVoidTypeReturn , self :: use_default_parameter_last :: UseDefaultParameterLast , self :: use_default_switch_clause_last :: UseDefaultSwitchClauseLast , self :: use_enum_initializers :: UseEnumInitializers , self :: use_exponentiation_operator :: UseExponentiationOperator , self :: use_numeric_literals :: UseNumericLiterals ,] } }

ADD
crates/rome_js_analyze/src/analyzers/nursery/no_useless_switch_case.rs
+use rome_analyze::context::RuleContext;
+use rome_analyze::{declare_rule, ActionCategory, Ast, Rule, RuleDiagnostic};
+use rome_console::markup;
+use rome_diagnostics::Applicability;
+use rome_js_syntax::{JsCaseClause, JsDefaultClause};
+use rome_rowan::{AstNode, AstNodeList, BatchMutationExt, Direction, SyntaxElement};
+
+use crate::JsRuleAction;
+
+declare_rule! {
+ /// Disallow useless `case` in `switch` statements.
+ ///
+ /// A `switch` statement can optionally have a `default` clause.
+ ///
+ /// The `default` clause will be still executed only if there is no match in the `case` clauses.
+ /// An empty `case` clause that precedes the `default` clause is thus useless.
+ ///
+ /// Source: https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-useless-switch-case.md
+ ///
+ /// ## Examples
+ ///
+ /// ### Invalid
+ ///
+ /// ```js,expect_diagnostic
+ /// switch (foo) {
+ /// case 0:
+ /// default:
+ /// break;
+ /// case 1:
+ /// break;
+ /// }
+ /// ```
+ ///
+ /// ```js,expect_diagnostic
+ /// switch (foo) {
+ /// default:
+ /// case 0:
+ /// break;
+ /// case 1:
+ /// break;
+ /// }
+ /// ```
+ ///
+ /// ### Valid
+ ///
+ /// ```js
+ /// switch (foo) {
+ /// case 0:
+ /// break;
+ /// default:
+ /// break;
+ /// }
+ /// ```
+ ///
+ /// ```js
+ /// switch (foo) {
+ /// case 0:
+ /// break;
+ /// }
+ /// ```
+ ///
+ pub(crate) NoUselessSwitchCase {
+ version: "12.0.0",
+ name: "noUselessSwitchCase",
+ recommended: true,
+ }
+}
+
+impl Rule for NoUselessSwitchCase {
+ type Query = Ast<JsDefaultClause>;
+ type State = JsCaseClause;
+ type Signals = Vec<Self::State>;
+ type Options = ();
+
+ fn run(ctx: &RuleContext<Self>) -> Self::Signals {
+ let default_clause = ctx.query();
+ let it = default_clause
+ .syntax()
+ .siblings(Direction::Prev)
+ .filter_map(JsCaseClause::cast)
+ .take_while(|case| case.consequent().is_empty());
+ if default_clause.consequent().is_empty() {
+ // The default clause is directly followed by at least a case. e.g.
+ //
+ // ```js
+ // switch (foo) {
+ // default:
+ // case 1:
+ // case 2:
+ // break;
+ // }
+ // ```
+ //
+ it.chain(
+ default_clause
+ .syntax()
+ .siblings(Direction::Next)
+ .filter_map(JsCaseClause::cast)
+ .take_while(|case| case.consequent().is_empty()),
+ )
+ .chain(
+ default_clause
+ .syntax()
+ .siblings(Direction::Next)
+ .filter_map(JsCaseClause::cast)
+ .find(|case| !case.consequent().is_empty()),
+ )
+ .collect()
+ } else {
+ it.collect()
+ }
+ }
+
+ fn diagnostic(ctx: &RuleContext<Self>, useless_case: &Self::State) -> Option<RuleDiagnostic> {
+ let default_clause = ctx.query();
+ Some(
+ RuleDiagnostic::new(
+ rule_category!(),
+ useless_case.range(),
+ markup! {
+ "Useless "<Emphasis>"case clause"</Emphasis>"."
+ },
+ )
+ .detail(
+ default_clause.range(),
+ markup! {
+ "because the "<Emphasis>"default clause"</Emphasis>" is present:"
+ },
+ ),
+ )
+ }
+
+ fn action(ctx: &RuleContext<Self>, useless_case: &Self::State) -> Option<JsRuleAction> {
+ let default_clause = ctx.query();
+ let mut mutation = ctx.root().begin();
+ let consequent = useless_case.consequent();
+ if consequent.len() > 0 {
+ let default_clause_colon_token = default_clause.colon_token().ok()?;
+ let new_default_clause = default_clause
+ .to_owned()
+ .with_consequent(consequent)
+ .with_colon_token(
+ default_clause_colon_token.with_trailing_trivia_pieces(
+ default_clause_colon_token
+ .trailing_trivia()
+ .pieces()
+ .chain(useless_case.colon_token().ok()?.trailing_trivia().pieces())
+ .collect::<Vec<_>>(),
+ ),
+ );
+ mutation.remove_node(default_clause.to_owned());
+ mutation.replace_element(
+ SyntaxElement::Node(useless_case.syntax().to_owned()),
+ SyntaxElement::Node(new_default_clause.syntax().to_owned()),
+ );
+ } else {
+ mutation.remove_node(useless_case.to_owned());
+ }
+ Some(JsRuleAction {
+ mutation,
+ message: markup! {"Remove the useless "<Emphasis>"case"</Emphasis>"."}.to_owned(),
+ category: ActionCategory::QuickFix,
+ applicability: Applicability::MaybeIncorrect,
+ })
+ }
+}

ADD
crates/rome_js_analyze/tests/specs/nursery/noUselessSwitchCase/invalid.js
+switch (foo) {
+ case 0:
+ default:
+ break;
+ case 3:
+ break;
+}
+
+switch (foo) {
+ /* before */case 0:/* after */
+ // comment for default
+ default:
+ case 1:
+ case 2:/* statements */
+ break;
+ case 3:
+ break;
+}
\ No newline at end of file

ADD
crates/rome_js_analyze/tests/specs/nursery/noUselessSwitchCase/valid.js
+switch (foo) {
+ case 0:
+ case 1:
+ break;
+}
+
+switch (foo) {
+ case 0:
+ break;
+ default:
+ break;
+}
+
+switch (foo) {
+ case 1:
+ f();
+ // Fallthrough
+ default:
+ g();
+ break;
+}
+
+switch (foo) {
+ case 1:
+ f();
+ // Fallthrough
+ default:
+ g();
+ // Fallthrough
+ case 2:
+ break;
+}
\ No newline at end of file

ADD
crates/rome_js_analyze/tests/specs/nursery/noUselessSwitchCase/valid.js.snap
+---
+source: crates/rome_js_analyze/tests/spec_tests.rs
+assertion_line: 83
+expression: valid.js
+---
+# Input
+```js
+switch (foo) {
+ case 0:
+ case 1:
+ break;
+}
+
+switch (foo) {
+ case 0:
+ break;
+ default:
+ break;
+}
+
+switch (foo) {
+ case 1:
+ f();
+ // Fallthrough
+ default:
+ g();
+ break;
+}
+
+switch (foo) {
+ case 1:
+ f();
+ // Fallthrough
+ default:
+ g();
+ // Fallthrough
+ case 2:
+ break;
+}
+```
+
+

MODIFY
crates/rome_service/src/configuration/linter/rules.rs
crates/rome_service/src/configuration/linter/rules.rs
@@ -761,6 +761,8 @@ struct NurserySchema {
no_string_case_mismatch: Option<RuleConfiguration>,
#[doc = "Disallow control flow statements in finally blocks."]
no_unsafe_finally: Option<RuleConfiguration>,
+ #[doc = "Disallow useless case in switch statements."]
+ no_useless_switch_case: Option<RuleConfiguration>,
#[doc = "Disallow the use of var"]
no_var: Option<RuleConfiguration>,
#[doc = "Disallow returning a value from a function with the return type 'void'"]
@@ -790,7 +792,7 @@ struct NurserySchema {
}
impl Nursery {
const CATEGORY_NAME: &'static str = "nursery";
- pub(crate) const CATEGORY_RULES: [&'static str; 32] = [
+ pub(crate) const CATEGORY_RULES: [&'static str; 33] = [
"noAccessKey",
"noAssignInExpressions",
"noBannedTypes",
@@ -810,6 +812,7 @@ impl Nursery {
"noSetterReturn",
"noStringCaseMismatch",
"noUnsafeFinally",
+ "noUselessSwitchCase",
"noVar",
"noVoidTypeReturn",
"useAriaPropTypes",
@@ -824,7 +827,7 @@ impl Nursery {
"useHookAtTopLevel",
"useNumericLiterals",
];
- const RECOMMENDED_RULES: [&'static str; 23] = [
+ const RECOMMENDED_RULES: [&'static str; 24] = [
"noAssignInExpressions",
"noBannedTypes",
"noConstEnum",
@@ -839,6 +842,7 @@ impl Nursery {
"noSetterReturn",
"noStringCaseMismatch",
"noUnsafeFinally",
+ "noUselessSwitchCase",
"noVar",
"noVoidTypeReturn",
"useAriaPropsForRole",
@@ -849,7 +853,7 @@ impl Nursery {
"useExhaustiveDependencies",
"useNumericLiterals",
];
- const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 23] = [
+ const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 24] = [
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[1]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[2]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[3]),
@@ -866,13 +870,14 @@ impl Nursery {
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[18]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[19]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[20]),
- RuleFilter::Rule("nursery", Self::CATEGORY_RULES[22]),
- RuleFilter::Rule("nursery", Self::CATEGORY_RULES[24]),
+ RuleFilter::Rule("nursery", Self::CATEGORY_RULES[21]),
+ RuleFilter::Rule("nursery", Self::CATEGORY_RULES[23]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[25]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[26]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[27]),
RuleFilter::Rule("nursery", Self::CATEGORY_RULES[28]),
- RuleFilter::Rule("nursery", Self::CATEGORY_RULES[31]),
+ RuleFilter::Rule("nursery", Self::CATEGORY_RULES[29]),
+ RuleFilter::Rule("nursery", Self::CATEGORY_RULES[32]),
];
pub(crate) fn is_recommended(&self) -> bool { !matches!(self.recommended, Some(false)) }
pub(crate) fn get_enabled_rules(&self) -> IndexSet<RuleFilter> {
@@ -899,7 +904,7 @@ impl Nursery {
pub(crate) fn is_recommended_rule(rule_name: &str) -> bool {
Self::RECOMMENDED_RULES.contains(&rule_name)
}
- pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 23] {
+ pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 24] {
Self::RECOMMENDED_RULES_AS_FILTERS
}
}

MODIFY
editors/vscode/configuration_schema.json
editors/vscode/configuration_schema.json
}
]
},
+ "noUselessSwitchCase": {
+ "description": "Disallow useless case in switch statements.",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/RuleConfiguration"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
"noVar": {
"description": "Disallow the use of var",
"anyOf": [

MODIFY
npm/backend-jsonrpc/src/workspace.ts
npm/backend-jsonrpc/src/workspace.ts
@@ -361,6 +361,10 @@ export interface Nursery {
* Disallow control flow statements in finally blocks.
*/
noUnsafeFinally?: RuleConfiguration;
+ /**
+ * Disallow useless case in switch statements.
+ */
+ noUselessSwitchCase?: RuleConfiguration;
/**
* Disallow the use of var
*/
@@ -705,6 +709,7 @@ export type Category =
| "lint/nursery/noSetterReturn"
| "lint/nursery/noStringCaseMismatch"
| "lint/nursery/noUnsafeFinally"
+ | "lint/nursery/noUselessSwitchCase"
| "lint/nursery/noVar"
| "lint/nursery/noVoidTypeReturn"
| "lint/nursery/useAriaPropsForRole"

MODIFY
npm/rome/configuration_schema.json
npm/rome/configuration_schema.json
}
]
},
+ "noUselessSwitchCase": {
+ "description": "Disallow useless case in switch statements.",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/RuleConfiguration"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
"noVar": {
"description": "Disallow the use of var",
"anyOf": [

MODIFY
website/src/pages/lint/rules/index.mdx
website/src/pages/lint/rules/index.mdx
@@ -590,6 +590,12 @@ Disallow comparison of expressions modifying the string case with non-compliant
Disallow control flow statements in finally blocks.
</section>
<section class="rule">
+<h3 data-toc-exclude id="noUselessSwitchCase">
+ <a href="/lint/rules/noUselessSwitchCase">noUselessSwitchCase</a>
+</h3>
+Disallow useless <code>case</code> in <code>switch</code> statements.
+</section>
+<section class="rule">
<h3 data-toc-exclude id="noVar">
<a href="/lint/rules/noVar">noVar</a>
</h3>

feat(rome_js_analyze): `noUselessSwitchCase` (#3912)

Co-authored-by: Micha Reiser <micha@rome.tools>