| use syntax::{ |
| algo::neighbor, |
| ast::{self, edit::IndentLevel, make, AstNode}, |
| ted::{self, Position}, |
| Direction, SyntaxKind, T, |
| }; |
| |
| use crate::{AssistContext, AssistId, AssistKind, Assists}; |
| |
| // Assist: unmerge_match_arm |
| // |
| // Splits the current match with a `|` pattern into two arms with identical bodies. |
| // |
| // ``` |
| // enum Action { Move { distance: u32 }, Stop } |
| // |
| // fn handle(action: Action) { |
| // match action { |
| // Action::Move(..) $0| Action::Stop => foo(), |
| // } |
| // } |
| // ``` |
| // -> |
| // ``` |
| // enum Action { Move { distance: u32 }, Stop } |
| // |
| // fn handle(action: Action) { |
| // match action { |
| // Action::Move(..) => foo(), |
| // Action::Stop => foo(), |
| // } |
| // } |
| // ``` |
| pub(crate) fn unmerge_match_arm(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { |
| let pipe_token = ctx.find_token_syntax_at_offset(T![|])?; |
| let or_pat = ast::OrPat::cast(pipe_token.parent()?)?.clone_for_update(); |
| let match_arm = ast::MatchArm::cast(or_pat.syntax().parent()?)?; |
| let match_arm_body = match_arm.expr()?; |
| |
| // We don't need to check for leading pipe because it is directly under `MatchArm` |
| // without `OrPat`. |
| |
| let new_parent = match_arm.syntax().parent()?; |
| let old_parent_range = new_parent.text_range(); |
| |
| acc.add( |
| AssistId("unmerge_match_arm", AssistKind::RefactorRewrite), |
| "Unmerge match arm", |
| pipe_token.text_range(), |
| |edit| { |
| let pats_after = pipe_token |
| .siblings_with_tokens(Direction::Next) |
| .filter_map(|it| ast::Pat::cast(it.into_node()?)); |
| // FIXME: We should add a leading pipe if the original arm has one. |
| let new_match_arm = make::match_arm( |
| pats_after, |
| match_arm.guard().and_then(|guard| guard.condition()), |
| match_arm_body, |
| ) |
| .clone_for_update(); |
| |
| let mut pipe_index = pipe_token.index(); |
| if pipe_token |
| .prev_sibling_or_token() |
| .map_or(false, |it| it.kind() == SyntaxKind::WHITESPACE) |
| { |
| pipe_index -= 1; |
| } |
| or_pat.syntax().splice_children( |
| pipe_index..or_pat.syntax().children_with_tokens().count(), |
| Vec::new(), |
| ); |
| |
| let mut insert_after_old_arm = Vec::new(); |
| |
| // A comma can be: |
| // - After the arm. In this case we always want to insert a comma after the newly |
| // inserted arm. |
| // - Missing after the arm, with no arms after. In this case we want to insert a |
| // comma before the newly inserted arm. It can not be necessary if there arm |
| // body is a block, but we don't bother to check that. |
| // - Missing after the arm with arms after, if the arm body is a block. In this case |
| // we don't want to insert a comma at all. |
| let has_comma_after = |
| std::iter::successors(match_arm.syntax().last_child_or_token(), |it| { |
| it.prev_sibling_or_token() |
| }) |
| .map(|it| it.kind()) |
| .find(|it| !it.is_trivia()) |
| == Some(T![,]); |
| let has_arms_after = neighbor(&match_arm, Direction::Next).is_some(); |
| if !has_comma_after && !has_arms_after { |
| insert_after_old_arm.push(make::token(T![,]).into()); |
| } |
| |
| let indent = IndentLevel::from_node(match_arm.syntax()); |
| insert_after_old_arm.push(make::tokens::whitespace(&format!("\n{indent}")).into()); |
| |
| insert_after_old_arm.push(new_match_arm.syntax().clone().into()); |
| |
| ted::insert_all_raw(Position::after(match_arm.syntax()), insert_after_old_arm); |
| |
| if has_comma_after { |
| ted::insert_raw( |
| Position::last_child_of(new_match_arm.syntax()), |
| make::token(T![,]), |
| ); |
| } |
| |
| edit.replace(old_parent_range, new_parent.to_string()); |
| }, |
| ) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use crate::tests::{check_assist, check_assist_not_applicable}; |
| |
| use super::*; |
| |
| #[test] |
| fn unmerge_match_arm_single_pipe() { |
| check_assist( |
| unmerge_match_arm, |
| r#" |
| #[derive(Debug)] |
| enum X { A, B, C } |
| |
| fn main() { |
| let x = X::A; |
| let y = match x { |
| X::A $0| X::B => { 1i32 } |
| X::C => { 2i32 } |
| }; |
| } |
| "#, |
| r#" |
| #[derive(Debug)] |
| enum X { A, B, C } |
| |
| fn main() { |
| let x = X::A; |
| let y = match x { |
| X::A => { 1i32 } |
| X::B => { 1i32 } |
| X::C => { 2i32 } |
| }; |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn unmerge_match_arm_guard() { |
| check_assist( |
| unmerge_match_arm, |
| r#" |
| #[derive(Debug)] |
| enum X { A, B, C } |
| |
| fn main() { |
| let x = X::A; |
| let y = match x { |
| X::A $0| X::B if true => { 1i32 } |
| _ => { 2i32 } |
| }; |
| } |
| "#, |
| r#" |
| #[derive(Debug)] |
| enum X { A, B, C } |
| |
| fn main() { |
| let x = X::A; |
| let y = match x { |
| X::A if true => { 1i32 } |
| X::B if true => { 1i32 } |
| _ => { 2i32 } |
| }; |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn unmerge_match_arm_leading_pipe() { |
| check_assist_not_applicable( |
| unmerge_match_arm, |
| r#" |
| |
| fn main() { |
| let y = match 0 { |
| |$0 0 => { 1i32 } |
| 1 => { 2i32 } |
| }; |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn unmerge_match_arm_multiple_pipes() { |
| check_assist( |
| unmerge_match_arm, |
| r#" |
| #[derive(Debug)] |
| enum X { A, B, C, D, E } |
| |
| fn main() { |
| let x = X::A; |
| let y = match x { |
| X::A | X::B |$0 X::C | X::D => 1i32, |
| X::E => 2i32, |
| }; |
| } |
| "#, |
| r#" |
| #[derive(Debug)] |
| enum X { A, B, C, D, E } |
| |
| fn main() { |
| let x = X::A; |
| let y = match x { |
| X::A | X::B => 1i32, |
| X::C | X::D => 1i32, |
| X::E => 2i32, |
| }; |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn unmerge_match_arm_inserts_comma_if_required() { |
| check_assist( |
| unmerge_match_arm, |
| r#" |
| #[derive(Debug)] |
| enum X { A, B } |
| |
| fn main() { |
| let x = X::A; |
| let y = match x { |
| X::A $0| X::B => 1i32 |
| }; |
| } |
| "#, |
| r#" |
| #[derive(Debug)] |
| enum X { A, B } |
| |
| fn main() { |
| let x = X::A; |
| let y = match x { |
| X::A => 1i32, |
| X::B => 1i32 |
| }; |
| } |
| "#, |
| ); |
| } |
| |
| #[test] |
| fn unmerge_match_arm_inserts_comma_if_had_after() { |
| check_assist( |
| unmerge_match_arm, |
| r#" |
| #[derive(Debug)] |
| enum X { A, B } |
| |
| fn main() { |
| let x = X::A; |
| match x { |
| X::A $0| X::B => {}, |
| } |
| } |
| "#, |
| r#" |
| #[derive(Debug)] |
| enum X { A, B } |
| |
| fn main() { |
| let x = X::A; |
| match x { |
| X::A => {}, |
| X::B => {}, |
| } |
| } |
| "#, |
| ); |
| } |
| } |