]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/move_guard.rs
Merge iter_for_each_to_for and for_to_iter_for_each assists modules
[rust.git] / crates / ide_assists / src / handlers / move_guard.rs
1 use syntax::{
2     ast::{edit::AstNodeEdit, make, AstNode, BlockExpr, Expr, IfExpr, MatchArm},
3     SyntaxKind::WHITESPACE,
4 };
5
6 use crate::{AssistContext, AssistId, AssistKind, 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 } $0if 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 {
29 //             foo()
30 //         },
31 //         _ => (),
32 //     }
33 // }
34 // ```
35 pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
36     let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
37     let guard = match_arm.guard()?;
38     if ctx.offset() > guard.syntax().text_range().end() {
39         cov_mark::hit!(move_guard_unapplicable_in_arm_body);
40         return None;
41     }
42     let space_before_guard = guard.syntax().prev_sibling_or_token();
43
44     // FIXME: support `if let` guards too
45     if guard.let_token().is_some() {
46         return None;
47     }
48     let guard_condition = guard.expr()?;
49     let arm_expr = match_arm.expr()?;
50     let if_expr = make::expr_if(
51         make::condition(guard_condition, None),
52         make::block_expr(None, Some(arm_expr.clone())),
53         None,
54     )
55     .indent(arm_expr.indent_level());
56
57     let target = guard.syntax().text_range();
58     acc.add(
59         AssistId("move_guard_to_arm_body", AssistKind::RefactorRewrite),
60         "Move guard to arm body",
61         target,
62         |edit| {
63             match space_before_guard {
64                 Some(element) if element.kind() == WHITESPACE => {
65                     edit.delete(element.text_range());
66                 }
67                 _ => (),
68             };
69
70             edit.delete(guard.syntax().text_range());
71             edit.replace_ast(arm_expr, if_expr);
72         },
73     )
74 }
75
76 // Assist: move_arm_cond_to_match_guard
77 //
78 // Moves if expression from match arm body into a guard.
79 //
80 // ```
81 // enum Action { Move { distance: u32 }, Stop }
82 //
83 // fn handle(action: Action) {
84 //     match action {
85 //         Action::Move { distance } => $0if distance > 10 { foo() },
86 //         _ => (),
87 //     }
88 // }
89 // ```
90 // ->
91 // ```
92 // enum Action { Move { distance: u32 }, Stop }
93 //
94 // fn handle(action: Action) {
95 //     match action {
96 //         Action::Move { distance } if distance > 10 => foo(),
97 //         _ => (),
98 //     }
99 // }
100 // ```
101 pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
102     let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
103     let match_pat = match_arm.pat()?;
104     let arm_body = match_arm.expr()?;
105
106     let mut replace_node = None;
107     let if_expr: IfExpr = IfExpr::cast(arm_body.syntax().clone()).or_else(|| {
108         let block_expr = BlockExpr::cast(arm_body.syntax().clone())?;
109         if let Expr::IfExpr(e) = block_expr.tail_expr()? {
110             replace_node = Some(block_expr.syntax().clone());
111             Some(e)
112         } else {
113             None
114         }
115     })?;
116     let replace_node = replace_node.unwrap_or_else(|| if_expr.syntax().clone());
117
118     let cond = if_expr.condition()?;
119     let then_block = if_expr.then_branch()?;
120
121     // Not support if with else branch
122     if if_expr.else_branch().is_some() {
123         return None;
124     }
125     // Not support moving if let to arm guard
126     if cond.is_pattern_cond() {
127         return None;
128     }
129
130     let buf = format!(" if {}", cond.syntax().text());
131
132     acc.add(
133         AssistId("move_arm_cond_to_match_guard", AssistKind::RefactorRewrite),
134         "Move condition to match guard",
135         replace_node.text_range(),
136         |edit| {
137             let then_only_expr = then_block.statements().next().is_none();
138
139             match &then_block.tail_expr() {
140                 Some(then_expr) if then_only_expr => {
141                     edit.replace(replace_node.text_range(), then_expr.syntax().text())
142                 }
143                 _ if replace_node != *if_expr.syntax() => {
144                     // Dedent because if_expr is in a BlockExpr
145                     let replace_with = then_block.dedent(1.into()).syntax().text();
146                     edit.replace(replace_node.text_range(), replace_with)
147                 }
148                 _ => edit.replace(replace_node.text_range(), then_block.syntax().text()),
149             }
150
151             edit.insert(match_pat.syntax().text_range().end(), buf);
152         },
153     )
154 }
155
156 #[cfg(test)]
157 mod tests {
158     use super::*;
159
160     use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
161
162     #[test]
163     fn move_guard_to_arm_body_range() {
164         cov_mark::check!(move_guard_unapplicable_in_arm_body);
165         check_assist_not_applicable(
166             move_guard_to_arm_body,
167             r#"
168 fn main() {
169     match 92 {
170         x if x > 10 => $0false,
171         _ => true
172     }
173 }
174 "#,
175         );
176     }
177     #[test]
178     fn move_guard_to_arm_body_target() {
179         check_assist_target(
180             move_guard_to_arm_body,
181             r#"
182 fn main() {
183     match 92 {
184         x $0if x > 10 => false,
185         _ => true
186     }
187 }
188 "#,
189             r#"if x > 10"#,
190         );
191     }
192
193     #[test]
194     fn move_guard_to_arm_body_works() {
195         check_assist(
196             move_guard_to_arm_body,
197             r#"
198 fn main() {
199     match 92 {
200         x $0if x > 10 => false,
201         _ => true
202     }
203 }
204 "#,
205             r#"
206 fn main() {
207     match 92 {
208         x => if x > 10 {
209             false
210         },
211         _ => true
212     }
213 }
214 "#,
215         );
216     }
217
218     #[test]
219     fn move_guard_to_arm_body_works_complex_match() {
220         check_assist(
221             move_guard_to_arm_body,
222             r#"
223 fn main() {
224     match 92 {
225         $0x @ 4 | x @ 5    if x > 5 => true,
226         _ => false
227     }
228 }
229 "#,
230             r#"
231 fn main() {
232     match 92 {
233         x @ 4 | x @ 5 => if x > 5 {
234             true
235         },
236         _ => false
237     }
238 }
239 "#,
240         );
241     }
242
243     #[test]
244     fn move_arm_cond_to_match_guard_works() {
245         check_assist(
246             move_arm_cond_to_match_guard,
247             r#"
248 fn main() {
249     match 92 {
250         x => if x > 10 { $0false },
251         _ => true
252     }
253 }
254 "#,
255             r#"
256 fn main() {
257     match 92 {
258         x if x > 10 => false,
259         _ => true
260     }
261 }
262 "#,
263         );
264     }
265
266     #[test]
267     fn move_arm_cond_in_block_to_match_guard_works() {
268         check_assist(
269             move_arm_cond_to_match_guard,
270             r#"
271 fn main() {
272     match 92 {
273         x => {
274             $0if x > 10 {
275                 false
276             }
277         },
278         _ => true
279     }
280 }
281 "#,
282             r#"
283 fn main() {
284     match 92 {
285         x if x > 10 => false,
286         _ => true
287     }
288 }
289 "#,
290         );
291     }
292
293     #[test]
294     fn move_arm_cond_to_match_guard_if_let_not_works() {
295         check_assist_not_applicable(
296             move_arm_cond_to_match_guard,
297             r#"
298 fn main() {
299     match 92 {
300         x => if let 62 = x { $0false },
301         _ => true
302     }
303 }
304 "#,
305         );
306     }
307
308     #[test]
309     fn move_arm_cond_to_match_guard_if_empty_body_works() {
310         check_assist(
311             move_arm_cond_to_match_guard,
312             r#"
313 fn main() {
314     match 92 {
315         x => if x > 10 { $0 },
316         _ => true
317     }
318 }
319 "#,
320             r#"
321 fn main() {
322     match 92 {
323         x if x > 10 => {  },
324         _ => true
325     }
326 }
327 "#,
328         );
329     }
330
331     #[test]
332     fn move_arm_cond_to_match_guard_if_multiline_body_works() {
333         check_assist(
334             move_arm_cond_to_match_guard,
335             r#"
336 fn main() {
337     match 92 {
338         x => if x > 10 {
339             92;$0
340             false
341         },
342         _ => true
343     }
344 }
345 "#,
346             r#"
347 fn main() {
348     match 92 {
349         x if x > 10 => {
350             92;
351             false
352         },
353         _ => true
354     }
355 }
356 "#,
357         );
358     }
359
360     #[test]
361     fn move_arm_cond_in_block_to_match_guard_if_multiline_body_works() {
362         check_assist(
363             move_arm_cond_to_match_guard,
364             r#"
365 fn main() {
366     match 92 {
367         x => {
368             if x > 10 {
369                 92;$0
370                 false
371             }
372         }
373         _ => true
374     }
375 }
376 "#,
377             r#"
378 fn main() {
379     match 92 {
380         x if x > 10 => {
381             92;
382             false
383         }
384         _ => true
385     }
386 }
387 "#,
388         )
389     }
390 }