]> git.lizzy.rs Git - rust.git/blob - crates/ra_assists/src/handlers/move_guard.rs
Centralize fixture parsing for assists
[rust.git] / crates / ra_assists / src / handlers / move_guard.rs
1 use ra_syntax::{
2     ast::{AstNode, IfExpr, MatchArm},
3     SyntaxKind::WHITESPACE,
4 };
5
6 use crate::{AssistContext, AssistId, Assists};
7
8 // Assist: move_guard_to_arm_body
9 //
10 // Moves match guard into match arm body.
11 //
12 // ```
13 // enum Action { Move { distance: u32 }, Stop }
14 //
15 // fn handle(action: Action) {
16 //     match action {
17 //         Action::Move { distance } <|>if distance > 10 => foo(),
18 //         _ => (),
19 //     }
20 // }
21 // ```
22 // ->
23 // ```
24 // enum Action { Move { distance: u32 }, Stop }
25 //
26 // fn handle(action: Action) {
27 //     match action {
28 //         Action::Move { distance } => if distance > 10 { foo() },
29 //         _ => (),
30 //     }
31 // }
32 // ```
33 pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
34     let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
35     let guard = match_arm.guard()?;
36     let space_before_guard = guard.syntax().prev_sibling_or_token();
37
38     let guard_conditions = guard.expr()?;
39     let arm_expr = match_arm.expr()?;
40     let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text());
41
42     let target = guard.syntax().text_range();
43     acc.add(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| {
44         match space_before_guard {
45             Some(element) if element.kind() == WHITESPACE => {
46                 edit.delete(element.text_range());
47             }
48             _ => (),
49         };
50
51         edit.delete(guard.syntax().text_range());
52         edit.replace_node_and_indent(arm_expr.syntax(), buf);
53     })
54 }
55
56 // Assist: move_arm_cond_to_match_guard
57 //
58 // Moves if expression from match arm body into a guard.
59 //
60 // ```
61 // enum Action { Move { distance: u32 }, Stop }
62 //
63 // fn handle(action: Action) {
64 //     match action {
65 //         Action::Move { distance } => <|>if distance > 10 { foo() },
66 //         _ => (),
67 //     }
68 // }
69 // ```
70 // ->
71 // ```
72 // enum Action { Move { distance: u32 }, Stop }
73 //
74 // fn handle(action: Action) {
75 //     match action {
76 //         Action::Move { distance } if distance > 10 => foo(),
77 //         _ => (),
78 //     }
79 // }
80 // ```
81 pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
82     let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
83     let match_pat = match_arm.pat()?;
84
85     let arm_body = match_arm.expr()?;
86     let if_expr: IfExpr = IfExpr::cast(arm_body.syntax().clone())?;
87     let cond = if_expr.condition()?;
88     let then_block = if_expr.then_branch()?;
89
90     // Not support if with else branch
91     if if_expr.else_branch().is_some() {
92         return None;
93     }
94     // Not support moving if let to arm guard
95     if cond.pat().is_some() {
96         return None;
97     }
98
99     let buf = format!(" if {}", cond.syntax().text());
100
101     let target = if_expr.syntax().text_range();
102     acc.add(
103         AssistId("move_arm_cond_to_match_guard"),
104         "Move condition to match guard",
105         target,
106         |edit| {
107             let then_only_expr = then_block.statements().next().is_none();
108
109             match &then_block.expr() {
110                 Some(then_expr) if then_only_expr => {
111                     edit.replace(if_expr.syntax().text_range(), then_expr.syntax().text())
112                 }
113                 _ => edit.replace(if_expr.syntax().text_range(), then_block.syntax().text()),
114             }
115
116             edit.insert(match_pat.syntax().text_range().end(), buf);
117         },
118     )
119 }
120
121 #[cfg(test)]
122 mod tests {
123     use super::*;
124
125     use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
126
127     #[test]
128     fn move_guard_to_arm_body_target() {
129         check_assist_target(
130             move_guard_to_arm_body,
131             r#"
132             fn f() {
133                 let t = 'a';
134                 let chars = "abcd";
135                 match t {
136                     '\r' <|>if chars.clone().next() == Some('\n') => false,
137                     _ => true
138                 }
139             }
140             "#,
141             r#"if chars.clone().next() == Some('\n')"#,
142         );
143     }
144
145     #[test]
146     fn move_guard_to_arm_body_works() {
147         check_assist(
148             move_guard_to_arm_body,
149             r#"
150             fn f() {
151                 let t = 'a';
152                 let chars = "abcd";
153                 match t {
154                     '\r' <|>if chars.clone().next() == Some('\n') => false,
155                     _ => true
156                 }
157             }
158             "#,
159             r#"
160             fn f() {
161                 let t = 'a';
162                 let chars = "abcd";
163                 match t {
164                     '\r' => if chars.clone().next() == Some('\n') { false },
165                     _ => true
166                 }
167             }
168             "#,
169         );
170     }
171
172     #[test]
173     fn move_guard_to_arm_body_works_complex_match() {
174         check_assist(
175             move_guard_to_arm_body,
176             r#"
177             fn f() {
178                 match x {
179                     <|>y @ 4 | y @ 5    if y > 5 => true,
180                     _ => false
181                 }
182             }
183             "#,
184             r#"
185             fn f() {
186                 match x {
187                     y @ 4 | y @ 5 => if y > 5 { true },
188                     _ => false
189                 }
190             }
191             "#,
192         );
193     }
194
195     #[test]
196     fn move_arm_cond_to_match_guard_works() {
197         check_assist(
198             move_arm_cond_to_match_guard,
199             r#"
200             fn f() {
201                 let t = 'a';
202                 let chars = "abcd";
203                 match t {
204                     '\r' => if chars.clone().next() == Some('\n') { <|>false },
205                     _ => true
206                 }
207             }
208             "#,
209             r#"
210             fn f() {
211                 let t = 'a';
212                 let chars = "abcd";
213                 match t {
214                     '\r' if chars.clone().next() == Some('\n') => false,
215                     _ => true
216                 }
217             }
218             "#,
219         );
220     }
221
222     #[test]
223     fn move_arm_cond_to_match_guard_if_let_not_works() {
224         check_assist_not_applicable(
225             move_arm_cond_to_match_guard,
226             r#"
227             fn f() {
228                 let t = 'a';
229                 let chars = "abcd";
230                 match t {
231                     '\r' => if let Some(_) = chars.clone().next() { <|>false },
232                     _ => true
233                 }
234             }
235             "#,
236         );
237     }
238
239     #[test]
240     fn move_arm_cond_to_match_guard_if_empty_body_works() {
241         check_assist(
242             move_arm_cond_to_match_guard,
243             r#"
244             fn f() {
245                 let t = 'a';
246                 let chars = "abcd";
247                 match t {
248                     '\r' => if chars.clone().next().is_some() { <|> },
249                     _ => true
250                 }
251             }
252             "#,
253             r#"
254             fn f() {
255                 let t = 'a';
256                 let chars = "abcd";
257                 match t {
258                     '\r' if chars.clone().next().is_some() => {  },
259                     _ => true
260                 }
261             }
262             "#,
263         );
264     }
265
266     #[test]
267     fn move_arm_cond_to_match_guard_if_multiline_body_works() {
268         check_assist(
269             move_arm_cond_to_match_guard,
270             r#"
271             fn f() {
272                 let mut t = 'a';
273                 let chars = "abcd";
274                 match t {
275                     '\r' => if chars.clone().next().is_some() {
276                         t = 'e';<|>
277                         false
278                     },
279                     _ => true
280                 }
281             }
282             "#,
283             r#"
284             fn f() {
285                 let mut t = 'a';
286                 let chars = "abcd";
287                 match t {
288                     '\r' if chars.clone().next().is_some() => {
289                         t = 'e';
290                         false
291                     },
292                     _ => true
293                 }
294             }
295             "#,
296         );
297     }
298 }