]> git.lizzy.rs Git - rust.git/blob - crates/ra_assists/src/handlers/merge_match_arms.rs
Centralize fixture parsing for assists
[rust.git] / crates / ra_assists / src / handlers / merge_match_arms.rs
1 use std::iter::successors;
2
3 use ra_syntax::{
4     algo::neighbor,
5     ast::{self, AstNode},
6     Direction,
7 };
8
9 use crate::{AssistContext, AssistId, Assists, TextRange};
10
11 // Assist: merge_match_arms
12 //
13 // Merges identical match arms.
14 //
15 // ```
16 // enum Action { Move { distance: u32 }, Stop }
17 //
18 // fn handle(action: Action) {
19 //     match action {
20 //         <|>Action::Move(..) => foo(),
21 //         Action::Stop => foo(),
22 //     }
23 // }
24 // ```
25 // ->
26 // ```
27 // enum Action { Move { distance: u32 }, Stop }
28 //
29 // fn handle(action: Action) {
30 //     match action {
31 //         Action::Move(..) | Action::Stop => foo(),
32 //     }
33 // }
34 // ```
35 pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
36     let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?;
37     // Don't try to handle arms with guards for now - can add support for this later
38     if current_arm.guard().is_some() {
39         return None;
40     }
41     let current_expr = current_arm.expr()?;
42     let current_text_range = current_arm.syntax().text_range();
43
44     // We check if the following match arms match this one. We could, but don't,
45     // compare to the previous match arm as well.
46     let arms_to_merge = successors(Some(current_arm), |it| neighbor(it, Direction::Next))
47         .take_while(|arm| {
48             if arm.guard().is_some() {
49                 return false;
50             }
51             match arm.expr() {
52                 Some(expr) => expr.syntax().text() == current_expr.syntax().text(),
53                 None => false,
54             }
55         })
56         .collect::<Vec<_>>();
57
58     if arms_to_merge.len() <= 1 {
59         return None;
60     }
61
62     acc.add(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| {
63         let pats = if arms_to_merge.iter().any(contains_placeholder) {
64             "_".into()
65         } else {
66             arms_to_merge
67                 .iter()
68                 .filter_map(ast::MatchArm::pat)
69                 .map(|x| x.syntax().to_string())
70                 .collect::<Vec<String>>()
71                 .join(" | ")
72         };
73
74         let arm = format!("{} => {}", pats, current_expr.syntax().text());
75
76         let start = arms_to_merge.first().unwrap().syntax().text_range().start();
77         let end = arms_to_merge.last().unwrap().syntax().text_range().end();
78
79         edit.replace(TextRange::new(start, end), arm);
80     })
81 }
82
83 fn contains_placeholder(a: &ast::MatchArm) -> bool {
84     match a.pat() {
85         Some(ra_syntax::ast::Pat::PlaceholderPat(..)) => true,
86         _ => false,
87     }
88 }
89
90 #[cfg(test)]
91 mod tests {
92     use crate::tests::{check_assist, check_assist_not_applicable};
93
94     use super::*;
95
96     #[test]
97     fn merge_match_arms_single_patterns() {
98         check_assist(
99             merge_match_arms,
100             r#"
101             #[derive(Debug)]
102             enum X { A, B, C }
103
104             fn main() {
105                 let x = X::A;
106                 let y = match x {
107                     X::A => { 1i32<|> }
108                     X::B => { 1i32 }
109                     X::C => { 2i32 }
110                 }
111             }
112             "#,
113             r#"
114             #[derive(Debug)]
115             enum X { A, B, C }
116
117             fn main() {
118                 let x = X::A;
119                 let y = match x {
120                     X::A | X::B => { 1i32 }
121                     X::C => { 2i32 }
122                 }
123             }
124             "#,
125         );
126     }
127
128     #[test]
129     fn merge_match_arms_multiple_patterns() {
130         check_assist(
131             merge_match_arms,
132             r#"
133             #[derive(Debug)]
134             enum X { A, B, C, D, E }
135
136             fn main() {
137                 let x = X::A;
138                 let y = match x {
139                     X::A | X::B => {<|> 1i32 },
140                     X::C | X::D => { 1i32 },
141                     X::E => { 2i32 },
142                 }
143             }
144             "#,
145             r#"
146             #[derive(Debug)]
147             enum X { A, B, C, D, E }
148
149             fn main() {
150                 let x = X::A;
151                 let y = match x {
152                     X::A | X::B | X::C | X::D => { 1i32 },
153                     X::E => { 2i32 },
154                 }
155             }
156             "#,
157         );
158     }
159
160     #[test]
161     fn merge_match_arms_placeholder_pattern() {
162         check_assist(
163             merge_match_arms,
164             r#"
165             #[derive(Debug)]
166             enum X { A, B, C, D, E }
167
168             fn main() {
169                 let x = X::A;
170                 let y = match x {
171                     X::A => { 1i32 },
172                     X::B => { 2i<|>32 },
173                     _ => { 2i32 }
174                 }
175             }
176             "#,
177             r#"
178             #[derive(Debug)]
179             enum X { A, B, C, D, E }
180
181             fn main() {
182                 let x = X::A;
183                 let y = match x {
184                     X::A => { 1i32 },
185                     _ => { 2i32 }
186                 }
187             }
188             "#,
189         );
190     }
191
192     #[test]
193     fn merges_all_subsequent_arms() {
194         check_assist(
195             merge_match_arms,
196             r#"
197             enum X { A, B, C, D, E }
198
199             fn main() {
200                 match X::A {
201                     X::A<|> => 92,
202                     X::B => 92,
203                     X::C => 92,
204                     X::D => 62,
205                     _ => panic!(),
206                 }
207             }
208             "#,
209             r#"
210             enum X { A, B, C, D, E }
211
212             fn main() {
213                 match X::A {
214                     X::A | X::B | X::C => 92,
215                     X::D => 62,
216                     _ => panic!(),
217                 }
218             }
219             "#,
220         )
221     }
222
223     #[test]
224     fn merge_match_arms_rejects_guards() {
225         check_assist_not_applicable(
226             merge_match_arms,
227             r#"
228             #[derive(Debug)]
229             enum X {
230                 A(i32),
231                 B,
232                 C
233             }
234
235             fn main() {
236                 let x = X::A;
237                 let y = match x {
238                     X::A(a) if a > 5 => { <|>1i32 },
239                     X::B => { 1i32 },
240                     X::C => { 2i32 }
241                 }
242             }
243             "#,
244         );
245     }
246 }