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