feat(rome_analyze): implement the useSingleCaseStatement rule
MODIFY
crates/rome_analyze/src/analyzers.rs
crates/rome_analyze/src/analyzers.rs
@@ -8,6 +8,8 @@ mod no_double_equals;
pub(crate) use no_double_equals::NoDoubleEquals;
mod no_negation_else;
pub(crate) use no_negation_else::NoNegationElse;
+mod use_single_case_statement;
+pub(crate) use use_single_case_statement::UseSingleCaseStatement;
mod use_single_var_declarator;
pub(crate) use use_single_var_declarator::UseSingleVarDeclarator;
mod use_valid_typeof;

ADD
crates/rome_analyze/src/analyzers/use_single_case_statement.rs
+use std::iter;
+
+use rome_console::markup;
+use rome_diagnostics::{Applicability, Severity};
+use rome_js_factory::make;
+use rome_js_syntax::{
+ JsAnyRoot, JsAnyStatement, JsCaseClause, JsCaseClauseFields, JsSyntaxToken, TriviaPieceKind, T,
+};
+use rome_rowan::{AstNode, AstNodeExt, AstNodeList, TriviaPiece};
+
+use crate::registry::{JsRuleAction, Rule, RuleDiagnostic};
+use crate::{ActionCategory, RuleCategory};
+
+/// Enforces case clauses have a single statement, emits a quick fix wrapping
+/// the statements in a block
+pub(crate) enum UseSingleCaseStatement {}
+
+impl Rule for UseSingleCaseStatement {
+ const NAME: &'static str = "useSingleCaseStatement";
+ const CATEGORY: RuleCategory = RuleCategory::Lint;
+
+ type Query = JsCaseClause;
+ type State = ();
+
+ fn run(n: &Self::Query) -> Option<Self::State> {
+ if n.consequent().len() > 1 {
+ Some(())
+ } else {
+ None
+ }
+ }
+
+ fn diagnostic(n: &Self::Query, _: &Self::State) -> Option<RuleDiagnostic> {
+ Some(RuleDiagnostic {
+ severity: Severity::Warning,
+ message: markup! {
+ "A switch case should only have a single statement."
+ }
+ .to_owned(),
+ range: n.consequent().syntax().text_trimmed_range(),
+ })
+ }
+
+ fn action(root: JsAnyRoot, n: &Self::Query, _: &Self::State) -> Option<JsRuleAction> {
+ let JsCaseClauseFields {
+ case_token,
+ colon_token,
+ consequent,
+ ..
+ } = n.as_fields();
+
+ // Move the trailing trivia of the colon token to the opening bracket token,
+ // this ensure comments stay in the right place
+ let mut opening_token = String::from(" {");
+ let mut trailing = Vec::new();
+
+ if let Ok(token) = colon_token {
+ for piece in token.trailing_trivia().pieces() {
+ opening_token.push_str(piece.text());
+ trailing.push(TriviaPiece::new(piece.kind(), piece.text_len()));
+ }
+ }
+
+ // Copy the leading trivia of the case token on the closing bracket token
+ // up to the first newline to align the indentation level
+ let mut closing_token = String::new();
+ let mut leading = Vec::new();
+
+ if let Ok(token) = case_token {
+ let leading_trivia = token.leading_trivia().pieces();
+ let num_pieces = leading_trivia.len();
+ let skip_count = leading_trivia
+ .rev()
+ .position(|piece| piece.is_newline())
+ .and_then(|index| num_pieces.checked_sub(index + 1))
+ .unwrap_or(0);
+
+ for piece in token.leading_trivia().pieces().skip(skip_count) {
+ closing_token.push_str(piece.text());
+ leading.push(TriviaPiece::new(piece.kind(), piece.text_len()));
+ }
+ }
+
+ closing_token.push('}');
+
+ let node = n
+ .clone()
+ .with_consequent(make::js_statement_list(iter::once(
+ JsAnyStatement::JsBlockStatement(make::js_block_statement(
+ JsSyntaxToken::new_detached(
+ T!['{'],
+ &opening_token,
+ [TriviaPiece::new(TriviaPieceKind::Whitespace, 1)],
+ trailing,
+ ),
+ consequent,
+ JsSyntaxToken::new_detached(T!['}'], &closing_token, leading, []),
+ )),
+ )));
+
+ let node = if let Ok(colon_token) = n.colon_token() {
+ node.with_colon_token(colon_token.with_trailing_trivia(iter::empty()))
+ } else {
+ node
+ };
+
+ let root = root
+ .replace_node(n.clone(), node)
+ .expect("failed to replace node");
+
+ Some(JsRuleAction {
+ category: ActionCategory::QuickFix,
+ applicability: Applicability::MaybeIncorrect,
+ message: markup! { "Wrap the statements in a block" }.to_owned(),
+ root,
+ })
+ }
+}

MODIFY
crates/rome_analyze/src/registry.rs
crates/rome_analyze/src/registry.rs
@@ -40,6 +40,7 @@ impl_registry_builders!(
NoDelete,
NoDoubleEquals,
NoNegationElse,
+ UseSingleCaseStatement,
UseSingleVarDeclarator,
UseValidTypeof,
UseWhile,

ADD
crates/rome_analyze/tests/specs/useSingleCaseStatement.js
+switch (foo) {
+ case true:
+ case false:
+ let foo = '';
+ foo;
+}
+
+switch (foo) {
+ // comment
+ case false :
+ let foo = '';
+ foo;
+}
+
+switch (foo) {
+ case false : // comment
+ let foo = '';
+ foo;
+}
+
+switch (foo) {
+ case false
+ /* comment */ :
+ let foo = '';
+ foo;
+}
+
+switch (foo) {
+ case true:
+ case false:
+ 'yes';
+}
+
+switch (foo) {
+ case true: {
+ // empty
+ }
+}
+
+switch (foo) {
+ case true:
+}

feat(rome_analyze): implement the useSingleCaseStatement rule (#2648)