]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/move_guard.rs
f257450a22f8126cd3456ff39d6038c56fb367ec
[rust.git] / crates / ide_assists / src / handlers / move_guard.rs
1 use syntax::{
2     ast::{
3         edit::AstNodeEdit, make, AstNode, BlockExpr, Condition, ElseBranch, Expr, IfExpr, MatchArm,
4     },
5     SyntaxKind::WHITESPACE,
6 };
7
8 use crate::{AssistContext, AssistId, AssistKind, Assists};
9
10 // Assist: move_guard_to_arm_body
11 //
12 // Moves match guard into match arm body.
13 //
14 // ```
15 // enum Action { Move { distance: u32 }, Stop }
16 //
17 // fn handle(action: Action) {
18 //     match action {
19 //         Action::Move { distance } $0if distance > 10 => foo(),
20 //         _ => (),
21 //     }
22 // }
23 // ```
24 // ->
25 // ```
26 // enum Action { Move { distance: u32 }, Stop }
27 //
28 // fn handle(action: Action) {
29 //     match action {
30 //         Action::Move { distance } => if distance > 10 {
31 //             foo()
32 //         },
33 //         _ => (),
34 //     }
35 // }
36 // ```
37 pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
38     let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
39     let guard = match_arm.guard()?;
40     if ctx.offset() > guard.syntax().text_range().end() {
41         cov_mark::hit!(move_guard_unapplicable_in_arm_body);
42         return None;
43     }
44     let space_before_guard = guard.syntax().prev_sibling_or_token();
45
46     // FIXME: support `if let` guards too
47     if guard.let_token().is_some() {
48         return None;
49     }
50     let guard_condition = guard.expr()?;
51     let arm_expr = match_arm.expr()?;
52     let if_expr = make::expr_if(
53         make::condition(guard_condition, None),
54         make::block_expr(None, Some(arm_expr.clone())),
55         None,
56     )
57     .indent(arm_expr.indent_level());
58
59     let target = guard.syntax().text_range();
60     acc.add(
61         AssistId("move_guard_to_arm_body", AssistKind::RefactorRewrite),
62         "Move guard to arm body",
63         target,
64         |edit| {
65             match space_before_guard {
66                 Some(element) if element.kind() == WHITESPACE => {
67                     edit.delete(element.text_range());
68                 }
69                 _ => (),
70             };
71
72             edit.delete(guard.syntax().text_range());
73             edit.replace_ast(arm_expr, if_expr);
74         },
75     )
76 }
77
78 // Assist: move_arm_cond_to_match_guard
79 //
80 // Moves if expression from match arm body into a guard.
81 //
82 // ```
83 // enum Action { Move { distance: u32 }, Stop }
84 //
85 // fn handle(action: Action) {
86 //     match action {
87 //         Action::Move { distance } => $0if distance > 10 { foo() },
88 //         _ => (),
89 //     }
90 // }
91 // ```
92 // ->
93 // ```
94 // enum Action { Move { distance: u32 }, Stop }
95 //
96 // fn handle(action: Action) {
97 //     match action {
98 //         Action::Move { distance } if distance > 10 => foo(),
99 //         Action::Move { distance } => {}
100 //         _ => (),
101 //     }
102 // }
103 // ```
104 pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
105     let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
106     let match_pat = match_arm.pat()?;
107     let arm_body = match_arm.expr()?;
108
109     let mut replace_node = None;
110     let if_expr: IfExpr = IfExpr::cast(arm_body.syntax().clone()).or_else(|| {
111         let block_expr = BlockExpr::cast(arm_body.syntax().clone())?;
112         if let Expr::IfExpr(e) = block_expr.tail_expr()? {
113             replace_node = Some(block_expr.syntax().clone());
114             Some(e)
115         } else {
116             None
117         }
118     })?;
119     let replace_node = replace_node.unwrap_or_else(|| if_expr.syntax().clone());
120     let needs_dedent = replace_node != *if_expr.syntax();
121     let (conds_blocks, tail) = parse_if_chain(if_expr)?;
122
123     acc.add(
124         AssistId("move_arm_cond_to_match_guard", AssistKind::RefactorRewrite),
125         "Move condition to match guard",
126         replace_node.text_range(),
127         |edit| {
128             edit.delete(match_arm.syntax().text_range());
129             // Dedent if if_expr is in a BlockExpr
130             let dedent = if needs_dedent {
131                 cov_mark::hit!(move_guard_ifelse_in_block);
132                 1
133             } else {
134                 cov_mark::hit!(move_guard_ifelse_else_block);
135                 0
136             };
137             let then_arm_end = match_arm.syntax().text_range().end();
138             let indent_level = match_arm.indent_level();
139             let spaces = "    ".repeat(indent_level.0 as _);
140
141             let mut first = true;
142             for (cond, block) in conds_blocks {
143                 if !first {
144                     edit.insert(then_arm_end, format!("\n{}", spaces));
145                 } else {
146                     first = false;
147                 }
148                 let guard = format!("{} if {} => ", match_pat, cond.syntax().text());
149                 edit.insert(then_arm_end, guard);
150                 let only_expr = block.statements().next().is_none();
151                 match &block.tail_expr() {
152                     Some(then_expr) if only_expr => {
153                         edit.insert(then_arm_end, then_expr.syntax().text());
154                         edit.insert(then_arm_end, ",");
155                     }
156                     _ => {
157                         let to_insert = block.dedent(dedent.into()).syntax().text();
158                         edit.insert(then_arm_end, to_insert)
159                     }
160                 }
161             }
162             if let Some(e) = tail {
163                 cov_mark::hit!(move_guard_ifelse_else_tail);
164                 let guard = format!("\n{}{} => ", spaces, match_pat);
165                 edit.insert(then_arm_end, guard);
166                 let only_expr = e.statements().next().is_none();
167                 match &e.tail_expr() {
168                     Some(expr) if only_expr => {
169                         cov_mark::hit!(move_guard_ifelse_expr_only);
170                         edit.insert(then_arm_end, expr.syntax().text());
171                         edit.insert(then_arm_end, ",");
172                     }
173                     _ => {
174                         let to_insert = e.dedent(dedent.into()).syntax().text();
175                         edit.insert(then_arm_end, to_insert)
176                     }
177                 }
178             } else {
179                 // There's no else branch. Add a pattern without guard
180                 cov_mark::hit!(move_guard_ifelse_notail);
181                 edit.insert(then_arm_end, format!("\n{}{} => {{}}", spaces, match_pat));
182             }
183         },
184     )
185 }
186
187 // Parses an if-else-if chain to get the conditons and the then branches until we encounter an else
188 // branch or the end.
189 fn parse_if_chain(if_expr: IfExpr) -> Option<(Vec<(Condition, BlockExpr)>, Option<BlockExpr>)> {
190     let mut conds_blocks = Vec::new();
191     let mut curr_if = if_expr;
192     let tail = loop {
193         let cond = curr_if.condition()?;
194         // Not support moving if let to arm guard
195         if cond.is_pattern_cond() {
196             return None;
197         }
198         conds_blocks.push((cond, curr_if.then_branch()?));
199         match curr_if.else_branch() {
200             Some(ElseBranch::IfExpr(e)) => {
201                 curr_if = e;
202             }
203             Some(ElseBranch::Block(b)) => {
204                 break Some(b);
205             }
206             None => break None,
207         }
208     };
209     Some((conds_blocks, tail))
210 }
211
212 #[cfg(test)]
213 mod tests {
214     use super::*;
215
216     use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
217
218     #[test]
219     fn move_guard_to_arm_body_range() {
220         cov_mark::check!(move_guard_unapplicable_in_arm_body);
221         check_assist_not_applicable(
222             move_guard_to_arm_body,
223             r#"
224 fn main() {
225     match 92 {
226         x if x > 10 => $0false,
227         _ => true
228     }
229 }
230 "#,
231         );
232     }
233     #[test]
234     fn move_guard_to_arm_body_target() {
235         check_assist_target(
236             move_guard_to_arm_body,
237             r#"
238 fn main() {
239     match 92 {
240         x $0if x > 10 => false,
241         _ => true
242     }
243 }
244 "#,
245             r#"if x > 10"#,
246         );
247     }
248
249     #[test]
250     fn move_guard_to_arm_body_works() {
251         check_assist(
252             move_guard_to_arm_body,
253             r#"
254 fn main() {
255     match 92 {
256         x $0if x > 10 => false,
257         _ => true
258     }
259 }
260 "#,
261             r#"
262 fn main() {
263     match 92 {
264         x => if x > 10 {
265             false
266         },
267         _ => true
268     }
269 }
270 "#,
271         );
272     }
273
274     #[test]
275     fn move_guard_to_arm_body_works_complex_match() {
276         check_assist(
277             move_guard_to_arm_body,
278             r#"
279 fn main() {
280     match 92 {
281         $0x @ 4 | x @ 5    if x > 5 => true,
282         _ => false
283     }
284 }
285 "#,
286             r#"
287 fn main() {
288     match 92 {
289         x @ 4 | x @ 5 => if x > 5 {
290             true
291         },
292         _ => false
293     }
294 }
295 "#,
296         );
297     }
298
299     #[test]
300     fn move_arm_cond_to_match_guard_works() {
301         check_assist(
302             move_arm_cond_to_match_guard,
303             r#"
304 fn main() {
305     match 92 {
306         x => if x > 10 { $0false },
307         _ => true
308     }
309 }
310 "#,
311             r#"
312 fn main() {
313     match 92 {
314         x if x > 10 => false,
315         x => {}
316         _ => true
317     }
318 }
319 "#,
320         );
321     }
322
323     #[test]
324     fn move_arm_cond_in_block_to_match_guard_works() {
325         check_assist(
326             move_arm_cond_to_match_guard,
327             r#"
328 fn main() {
329     match 92 {
330         x => {
331             $0if x > 10 {
332                 false
333             }
334         },
335         _ => true
336     }
337 }
338 "#,
339             r#"
340 fn main() {
341     match 92 {
342         x if x > 10 => false,
343         x => {}
344         _ => true
345     }
346 }
347 "#,
348         );
349     }
350
351     #[test]
352     fn move_arm_cond_in_block_to_match_guard_add_comma_works() {
353         check_assist(
354             move_arm_cond_to_match_guard,
355             r#"
356 fn main() {
357     match 92 {
358         x => {
359             $0if x > 10 {
360                 false
361             }
362         }
363         _ => true
364     }
365 }
366 "#,
367             r#"
368 fn main() {
369     match 92 {
370         x if x > 10 => false,
371         x => {}
372         _ => true
373     }
374 }
375 "#,
376         );
377     }
378
379     #[test]
380     fn move_arm_cond_to_match_guard_if_let_not_works() {
381         check_assist_not_applicable(
382             move_arm_cond_to_match_guard,
383             r#"
384 fn main() {
385     match 92 {
386         x => if let 62 = x { $0false },
387         _ => true
388     }
389 }
390 "#,
391         );
392     }
393
394     #[test]
395     fn move_arm_cond_to_match_guard_if_empty_body_works() {
396         check_assist(
397             move_arm_cond_to_match_guard,
398             r#"
399 fn main() {
400     match 92 {
401         x => if x > 10 { $0 },
402         _ => true
403     }
404 }
405 "#,
406             r#"
407 fn main() {
408     match 92 {
409         x if x > 10 => {  }
410         x => {}
411         _ => true
412     }
413 }
414 "#,
415         );
416     }
417
418     #[test]
419     fn move_arm_cond_to_match_guard_if_multiline_body_works() {
420         check_assist(
421             move_arm_cond_to_match_guard,
422             r#"
423 fn main() {
424     match 92 {
425         x => if x > 10 {
426             92;$0
427             false
428         },
429         _ => true
430     }
431 }
432 "#,
433             r#"
434 fn main() {
435     match 92 {
436         x if x > 10 => {
437             92;
438             false
439         }
440         x => {}
441         _ => true
442     }
443 }
444 "#,
445         );
446     }
447
448     #[test]
449     fn move_arm_cond_in_block_to_match_guard_if_multiline_body_works() {
450         check_assist(
451             move_arm_cond_to_match_guard,
452             r#"
453 fn main() {
454     match 92 {
455         x => {
456             if x > 10 {
457                 92;$0
458                 false
459             }
460         }
461         _ => true
462     }
463 }
464 "#,
465             r#"
466 fn main() {
467     match 92 {
468         x if x > 10 => {
469             92;
470             false
471         }
472         x => {}
473         _ => true
474     }
475 }
476 "#,
477         )
478     }
479
480     #[test]
481     fn move_arm_cond_to_match_guard_with_else_works() {
482         check_assist(
483             move_arm_cond_to_match_guard,
484             r#"
485 fn main() {
486     match 92 {
487         x => if x > 10 {$0
488             false
489         } else {
490             true
491         }
492         _ => true,
493     }
494 }
495 "#,
496             r#"
497 fn main() {
498     match 92 {
499         x if x > 10 => false,
500         x => true,
501         _ => true,
502     }
503 }
504 "#,
505         )
506     }
507
508     #[test]
509     fn move_arm_cond_to_match_guard_with_else_block_works() {
510         cov_mark::check!(move_guard_ifelse_expr_only);
511         check_assist(
512             move_arm_cond_to_match_guard,
513             r#"
514 fn main() {
515     match 92 {
516         x => {
517             if x > 10 {$0
518                 false
519             } else {
520                 true
521             }
522         }
523         _ => true
524     }
525 }
526 "#,
527             r#"
528 fn main() {
529     match 92 {
530         x if x > 10 => false,
531         x => true,
532         _ => true
533     }
534 }
535 "#,
536         )
537     }
538
539     #[test]
540     fn move_arm_cond_to_match_guard_else_if_empty_body_works() {
541         check_assist(
542             move_arm_cond_to_match_guard,
543             r#"
544 fn main() {
545     match 92 {
546         x => if x > 10 { $0 } else { },
547         _ => true
548     }
549 }
550 "#,
551             r#"
552 fn main() {
553     match 92 {
554         x if x > 10 => {  }
555         x => { }
556         _ => true
557     }
558 }
559 "#,
560         );
561     }
562
563     #[test]
564     fn move_arm_cond_to_match_guard_with_else_multiline_works() {
565         check_assist(
566             move_arm_cond_to_match_guard,
567             r#"
568 fn main() {
569     match 92 {
570         x => if x > 10 {
571             92;$0
572             false
573         } else {
574             true
575         }
576         _ => true
577     }
578 }
579 "#,
580             r#"
581 fn main() {
582     match 92 {
583         x if x > 10 => {
584             92;
585             false
586         }
587         x => true,
588         _ => true
589     }
590 }
591 "#,
592         )
593     }
594
595     #[test]
596     fn move_arm_cond_to_match_guard_with_else_multiline_else_works() {
597         cov_mark::check!(move_guard_ifelse_else_block);
598         check_assist(
599             move_arm_cond_to_match_guard,
600             r#"
601 fn main() {
602     match 92 {
603         x => if x > 10 {$0
604             false
605         } else {
606             42;
607             true
608         }
609         _ => true
610     }
611 }
612 "#,
613             r#"
614 fn main() {
615     match 92 {
616         x if x > 10 => false,
617         x => {
618             42;
619             true
620         }
621         _ => true
622     }
623 }
624 "#,
625         )
626     }
627
628     #[test]
629     fn move_arm_cond_to_match_guard_with_else_multiline_else_block_works() {
630         cov_mark::check!(move_guard_ifelse_in_block);
631         check_assist(
632             move_arm_cond_to_match_guard,
633             r#"
634 fn main() {
635     match 92 {
636         x => {
637             if x > 10 {$0
638                 false
639             } else {
640                 42;
641                 true
642             }
643         }
644         _ => true
645     }
646 }
647 "#,
648             r#"
649 fn main() {
650     match 92 {
651         x if x > 10 => false,
652         x => {
653             42;
654             true
655         }
656         _ => true
657     }
658 }
659 "#,
660         )
661     }
662
663     #[test]
664     fn move_arm_cond_to_match_guard_with_else_last_arm_works() {
665         check_assist(
666             move_arm_cond_to_match_guard,
667             r#"
668 fn main() {
669     match 92 {
670         3 => true,
671         x => {
672             if x > 10 {$0
673                 false
674             } else {
675                 92;
676                 true
677             }
678         }
679     }
680 }
681 "#,
682             r#"
683 fn main() {
684     match 92 {
685         3 => true,
686         x if x > 10 => false,
687         x => {
688             92;
689             true
690         }
691     }
692 }
693 "#,
694         )
695     }
696
697     #[test]
698     fn move_arm_cond_to_match_guard_with_else_comma_works() {
699         check_assist(
700             move_arm_cond_to_match_guard,
701             r#"
702 fn main() {
703     match 92 {
704         3 => true,
705         x => if x > 10 {$0
706             false
707         } else {
708             92;
709             true
710         },
711     }
712 }
713 "#,
714             r#"
715 fn main() {
716     match 92 {
717         3 => true,
718         x if x > 10 => false,
719         x => {
720             92;
721             true
722         }
723     }
724 }
725 "#,
726         )
727     }
728
729     #[test]
730     fn move_arm_cond_to_match_guard_elseif() {
731         check_assist(
732             move_arm_cond_to_match_guard,
733             r#"
734 fn main() {
735     match 92 {
736         3 => true,
737         x => if x > 10 {$0
738             false
739         } else if x > 5 {
740             true
741         } else if x > 4 {
742             false
743         } else {
744             true
745         },
746     }
747 }
748 "#,
749             r#"
750 fn main() {
751     match 92 {
752         3 => true,
753         x if x > 10 => false,
754         x if x > 5 => true,
755         x if x > 4 => false,
756         x => true,
757     }
758 }
759 "#,
760         )
761     }
762
763     #[test]
764     fn move_arm_cond_to_match_guard_elseif_in_block() {
765         cov_mark::check!(move_guard_ifelse_in_block);
766         check_assist(
767             move_arm_cond_to_match_guard,
768             r#"
769 fn main() {
770     match 92 {
771         3 => true,
772         x => {
773             if x > 10 {$0
774                 false
775             } else if x > 5 {
776                 true
777             } else if x > 4 {
778                 false
779             } else {
780                 true
781             }
782         }
783     }
784 }
785 "#,
786             r#"
787 fn main() {
788     match 92 {
789         3 => true,
790         x if x > 10 => false,
791         x if x > 5 => true,
792         x if x > 4 => false,
793         x => true,
794     }
795 }
796 "#,
797         )
798     }
799
800     #[test]
801     fn move_arm_cond_to_match_guard_elseif_chain() {
802         cov_mark::check!(move_guard_ifelse_else_tail);
803         check_assist(
804             move_arm_cond_to_match_guard,
805             r#"
806 fn main() {
807     match 92 {
808         3 => 0,
809         x => if x > 10 {$0
810             1
811         } else if x > 5 {
812             2
813         } else if x > 3 {
814             42;
815             3
816         } else {
817             4
818         },
819     }
820 }
821 "#,
822             r#"
823 fn main() {
824     match 92 {
825         3 => 0,
826         x if x > 10 => 1,
827         x if x > 5 => 2,
828         x if x > 3 => {
829             42;
830             3
831         }
832         x => 4,
833     }
834 }
835 "#,
836         )
837     }
838
839     #[test]
840     fn move_arm_cond_to_match_guard_elseif_iflet() {
841         check_assist_not_applicable(
842             move_arm_cond_to_match_guard,
843             r#"
844 fn main() {
845     match 92 {
846         3 => 0,
847         x => if x > 10 {$0
848             1
849         } else if x > 5 {
850             2
851         } else if let 4 = 4 {
852             42;
853             3
854         } else {
855             4
856         },
857     }
858 }
859 "#,
860         )
861     }
862
863     #[test]
864     fn move_arm_cond_to_match_guard_elseif_notail() {
865         cov_mark::check!(move_guard_ifelse_notail);
866         check_assist(
867             move_arm_cond_to_match_guard,
868             r#"
869 fn main() {
870     match 92 {
871         3 => 0,
872         x => if x > 10 {$0
873             1
874         } else if x > 5 {
875             2
876         } else if x > 4 {
877             42;
878             3
879         },
880     }
881 }
882 "#,
883             r#"
884 fn main() {
885     match 92 {
886         3 => 0,
887         x if x > 10 => 1,
888         x if x > 5 => 2,
889         x if x > 4 => {
890             42;
891             3
892         }
893         x => {}
894     }
895 }
896 "#,
897         )
898     }
899 }