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