3 ast::{self, edit::IndentLevel, make, AstNode},
5 Direction, SyntaxKind, T,
8 use crate::{AssistContext, AssistId, AssistKind, Assists};
10 // Assist: unmerge_match_arm
12 // Splits the current match with a `|` pattern into two arms with identical bodies.
15 // enum Action { Move { distance: u32 }, Stop }
17 // fn handle(action: Action) {
19 // Action::Move(..) $0| Action::Stop => foo(),
25 // enum Action { Move { distance: u32 }, Stop }
27 // fn handle(action: Action) {
29 // Action::Move(..) => foo(),
30 // Action::Stop => foo(),
34 pub(crate) fn unmerge_match_arm(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
35 let pipe_token = ctx.find_token_syntax_at_offset(T![|])?;
36 let or_pat = ast::OrPat::cast(pipe_token.parent()?)?.clone_for_update();
37 let match_arm = ast::MatchArm::cast(or_pat.syntax().parent()?)?;
38 let match_arm_body = match_arm.expr()?;
40 // We don't need to check for leading pipe because it is directly under `MatchArm`
43 let new_parent = match_arm.syntax().parent()?;
44 let old_parent_range = new_parent.text_range();
47 AssistId("unmerge_match_arm", AssistKind::RefactorRewrite),
49 pipe_token.text_range(),
51 let pats_after = pipe_token
52 .siblings_with_tokens(Direction::Next)
53 .filter_map(|it| ast::Pat::cast(it.into_node()?));
54 // FIXME: We should add a leading pipe if the original arm has one.
55 let new_match_arm = make::match_arm(
57 match_arm.guard().and_then(|guard| guard.condition()),
62 let mut pipe_index = pipe_token.index();
64 .prev_sibling_or_token()
65 .map_or(false, |it| it.kind() == SyntaxKind::WHITESPACE)
69 or_pat.syntax().splice_children(
70 pipe_index..or_pat.syntax().children_with_tokens().count(),
74 let mut insert_after_old_arm = Vec::new();
77 // - After the arm. In this case we always want to insert a comma after the newly
79 // - Missing after the arm, with no arms after. In this case we want to insert a
80 // comma before the newly inserted arm. It can not be necessary if there arm
81 // body is a block, but we don't bother to check that.
82 // - Missing after the arm with arms after, if the arm body is a block. In this case
83 // we don't want to insert a comma at all.
85 std::iter::successors(match_arm.syntax().last_child_or_token(), |it| {
86 it.prev_sibling_or_token()
89 .skip_while(|it| it.is_trivia())
92 let has_arms_after = neighbor(&match_arm, Direction::Next).is_some();
93 if !has_comma_after && !has_arms_after {
94 insert_after_old_arm.push(make::token(T![,]).into());
97 let indent = IndentLevel::from_node(match_arm.syntax());
98 insert_after_old_arm.push(make::tokens::whitespace(&format!("\n{indent}")).into());
100 insert_after_old_arm.push(new_match_arm.syntax().clone().into());
102 ted::insert_all_raw(Position::after(match_arm.syntax()), insert_after_old_arm);
106 Position::last_child_of(new_match_arm.syntax()),
111 edit.replace(old_parent_range, new_parent.to_string());
118 use crate::tests::{check_assist, check_assist_not_applicable};
123 fn unmerge_match_arm_single_pipe() {
133 X::A $0| X::B => { 1i32 }
155 fn unmerge_match_arm_guard() {
165 X::A $0| X::B if true => { 1i32 }
177 X::A if true => { 1i32 }
178 X::B if true => { 1i32 }
187 fn unmerge_match_arm_leading_pipe() {
188 check_assist_not_applicable(
203 fn unmerge_match_arm_multiple_pipes() {
208 enum X { A, B, C, D, E }
213 X::A | X::B |$0 X::C | X::D => 1i32,
220 enum X { A, B, C, D, E }
235 fn unmerge_match_arm_inserts_comma_if_required() {
245 X::A $0| X::B => 1i32
265 fn unmerge_match_arm_inserts_comma_if_had_after() {