]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/move_guard.rs
Fix various IDE features
[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_guard_to_arm_body_works_complex_match() {
271         check_assist(
272             move_guard_to_arm_body,
273             r#"
274 fn main() {
275     match 92 {
276         $0x @ 4 | x @ 5    if x > 5 => true,
277         _ => false
278     }
279 }
280 "#,
281             r#"
282 fn main() {
283     match 92 {
284         x @ 4 | x @ 5 => if x > 5 {
285             true
286         },
287         _ => false
288     }
289 }
290 "#,
291         );
292     }
293
294     #[test]
295     fn move_arm_cond_to_match_guard_works() {
296         check_assist(
297             move_arm_cond_to_match_guard,
298             r#"
299 fn main() {
300     match 92 {
301         x => if x > 10 { $0false },
302         _ => true
303     }
304 }
305 "#,
306             r#"
307 fn main() {
308     match 92 {
309         x if x > 10 => false,
310         _ => true
311     }
312 }
313 "#,
314         );
315     }
316
317     #[test]
318     fn move_arm_cond_in_block_to_match_guard_works() {
319         cov_mark::check!(move_guard_ifelse_has_wildcard);
320         check_assist(
321             move_arm_cond_to_match_guard,
322             r#"
323 fn main() {
324     match 92 {
325         x => {
326             $0if x > 10 {
327                 false
328             }
329         },
330         _ => true
331     }
332 }
333 "#,
334             r#"
335 fn main() {
336     match 92 {
337         x if x > 10 => false,
338         _ => true
339     }
340 }
341 "#,
342         );
343     }
344
345     #[test]
346     fn move_arm_cond_in_block_to_match_guard_no_wildcard_works() {
347         cov_mark::check_count!(move_guard_ifelse_has_wildcard, 0);
348         check_assist(
349             move_arm_cond_to_match_guard,
350             r#"
351 fn main() {
352     match 92 {
353         x => {
354             $0if x > 10 {
355                 false
356             }
357         }
358     }
359 }
360 "#,
361             r#"
362 fn main() {
363     match 92 {
364         x if x > 10 => false,
365         x => {}
366     }
367 }
368 "#,
369         );
370     }
371
372     #[test]
373     fn move_arm_cond_in_block_to_match_guard_wildcard_guard_works() {
374         cov_mark::check_count!(move_guard_ifelse_has_wildcard, 0);
375         check_assist(
376             move_arm_cond_to_match_guard,
377             r#"
378 fn main() {
379     match 92 {
380         x => {
381             $0if x > 10 {
382                 false
383             }
384         }
385         _ if x > 10 => true,
386     }
387 }
388 "#,
389             r#"
390 fn main() {
391     match 92 {
392         x if x > 10 => false,
393         x => {}
394         _ if x > 10 => true,
395     }
396 }
397 "#,
398         );
399     }
400
401     #[test]
402     fn move_arm_cond_in_block_to_match_guard_add_comma_works() {
403         check_assist(
404             move_arm_cond_to_match_guard,
405             r#"
406 fn main() {
407     match 92 {
408         x => {
409             $0if x > 10 {
410                 false
411             }
412         }
413         _ => true
414     }
415 }
416 "#,
417             r#"
418 fn main() {
419     match 92 {
420         x if x > 10 => false,
421         _ => true
422     }
423 }
424 "#,
425         );
426     }
427
428     #[test]
429     fn move_arm_cond_to_match_guard_if_let_not_works() {
430         check_assist_not_applicable(
431             move_arm_cond_to_match_guard,
432             r#"
433 fn main() {
434     match 92 {
435         x => if let 62 = x { $0false },
436         _ => true
437     }
438 }
439 "#,
440         );
441     }
442
443     #[test]
444     fn move_arm_cond_to_match_guard_if_empty_body_works() {
445         check_assist(
446             move_arm_cond_to_match_guard,
447             r#"
448 fn main() {
449     match 92 {
450         x => if x > 10 { $0 },
451         _ => true
452     }
453 }
454 "#,
455             r#"
456 fn main() {
457     match 92 {
458         x if x > 10 => {  }
459         _ => true
460     }
461 }
462 "#,
463         );
464     }
465
466     #[test]
467     fn move_arm_cond_to_match_guard_if_multiline_body_works() {
468         check_assist(
469             move_arm_cond_to_match_guard,
470             r#"
471 fn main() {
472     match 92 {
473         x => if x > 10 {
474             92;$0
475             false
476         },
477         _ => true
478     }
479 }
480 "#,
481             r#"
482 fn main() {
483     match 92 {
484         x if x > 10 => {
485             92;
486             false
487         }
488         _ => true
489     }
490 }
491 "#,
492         );
493     }
494
495     #[test]
496     fn move_arm_cond_in_block_to_match_guard_if_multiline_body_works() {
497         check_assist(
498             move_arm_cond_to_match_guard,
499             r#"
500 fn main() {
501     match 92 {
502         x => {
503             if x > 10 {
504                 92;$0
505                 false
506             }
507         }
508         _ => true
509     }
510 }
511 "#,
512             r#"
513 fn main() {
514     match 92 {
515         x if x > 10 => {
516             92;
517             false
518         }
519         _ => true
520     }
521 }
522 "#,
523         )
524     }
525
526     #[test]
527     fn move_arm_cond_to_match_guard_with_else_works() {
528         check_assist(
529             move_arm_cond_to_match_guard,
530             r#"
531 fn main() {
532     match 92 {
533         x => if x > 10 {$0
534             false
535         } else {
536             true
537         }
538         _ => true,
539     }
540 }
541 "#,
542             r#"
543 fn main() {
544     match 92 {
545         x if x > 10 => false,
546         x => true,
547         _ => true,
548     }
549 }
550 "#,
551         )
552     }
553
554     #[test]
555     fn move_arm_cond_to_match_guard_with_else_block_works() {
556         cov_mark::check!(move_guard_ifelse_expr_only);
557         check_assist(
558             move_arm_cond_to_match_guard,
559             r#"
560 fn main() {
561     match 92 {
562         x => {
563             if x > 10 {$0
564                 false
565             } else {
566                 true
567             }
568         }
569         _ => true
570     }
571 }
572 "#,
573             r#"
574 fn main() {
575     match 92 {
576         x if x > 10 => false,
577         x => true,
578         _ => true
579     }
580 }
581 "#,
582         )
583     }
584
585     #[test]
586     fn move_arm_cond_to_match_guard_else_if_empty_body_works() {
587         check_assist(
588             move_arm_cond_to_match_guard,
589             r#"
590 fn main() {
591     match 92 {
592         x => if x > 10 { $0 } else { },
593         _ => true
594     }
595 }
596 "#,
597             r#"
598 fn main() {
599     match 92 {
600         x if x > 10 => {  }
601         x => { }
602         _ => true
603     }
604 }
605 "#,
606         );
607     }
608
609     #[test]
610     fn move_arm_cond_to_match_guard_with_else_multiline_works() {
611         check_assist(
612             move_arm_cond_to_match_guard,
613             r#"
614 fn main() {
615     match 92 {
616         x => if x > 10 {
617             92;$0
618             false
619         } else {
620             true
621         }
622         _ => true
623     }
624 }
625 "#,
626             r#"
627 fn main() {
628     match 92 {
629         x if x > 10 => {
630             92;
631             false
632         }
633         x => true,
634         _ => true
635     }
636 }
637 "#,
638         )
639     }
640
641     #[test]
642     fn move_arm_cond_to_match_guard_with_else_multiline_else_works() {
643         cov_mark::check!(move_guard_ifelse_else_block);
644         check_assist(
645             move_arm_cond_to_match_guard,
646             r#"
647 fn main() {
648     match 92 {
649         x => if x > 10 {$0
650             false
651         } else {
652             42;
653             true
654         }
655         _ => true
656     }
657 }
658 "#,
659             r#"
660 fn main() {
661     match 92 {
662         x if x > 10 => false,
663         x => {
664             42;
665             true
666         }
667         _ => true
668     }
669 }
670 "#,
671         )
672     }
673
674     #[test]
675     fn move_arm_cond_to_match_guard_with_else_multiline_else_block_works() {
676         cov_mark::check!(move_guard_ifelse_in_block);
677         check_assist(
678             move_arm_cond_to_match_guard,
679             r#"
680 fn main() {
681     match 92 {
682         x => {
683             if x > 10 {$0
684                 false
685             } else {
686                 42;
687                 true
688             }
689         }
690         _ => true
691     }
692 }
693 "#,
694             r#"
695 fn main() {
696     match 92 {
697         x if x > 10 => false,
698         x => {
699             42;
700             true
701         }
702         _ => true
703     }
704 }
705 "#,
706         )
707     }
708
709     #[test]
710     fn move_arm_cond_to_match_guard_with_else_last_arm_works() {
711         check_assist(
712             move_arm_cond_to_match_guard,
713             r#"
714 fn main() {
715     match 92 {
716         3 => true,
717         x => {
718             if x > 10 {$0
719                 false
720             } else {
721                 92;
722                 true
723             }
724         }
725     }
726 }
727 "#,
728             r#"
729 fn main() {
730     match 92 {
731         3 => true,
732         x if x > 10 => false,
733         x => {
734             92;
735             true
736         }
737     }
738 }
739 "#,
740         )
741     }
742
743     #[test]
744     fn move_arm_cond_to_match_guard_with_else_comma_works() {
745         check_assist(
746             move_arm_cond_to_match_guard,
747             r#"
748 fn main() {
749     match 92 {
750         3 => true,
751         x => if x > 10 {$0
752             false
753         } else {
754             92;
755             true
756         },
757     }
758 }
759 "#,
760             r#"
761 fn main() {
762     match 92 {
763         3 => true,
764         x if x > 10 => false,
765         x => {
766             92;
767             true
768         }
769     }
770 }
771 "#,
772         )
773     }
774
775     #[test]
776     fn move_arm_cond_to_match_guard_elseif() {
777         check_assist(
778             move_arm_cond_to_match_guard,
779             r#"
780 fn main() {
781     match 92 {
782         3 => true,
783         x => if x > 10 {$0
784             false
785         } else if x > 5 {
786             true
787         } else if x > 4 {
788             false
789         } else {
790             true
791         },
792     }
793 }
794 "#,
795             r#"
796 fn main() {
797     match 92 {
798         3 => true,
799         x if x > 10 => false,
800         x if x > 5 => true,
801         x if x > 4 => false,
802         x => true,
803     }
804 }
805 "#,
806         )
807     }
808
809     #[test]
810     fn move_arm_cond_to_match_guard_elseif_in_block() {
811         cov_mark::check!(move_guard_ifelse_in_block);
812         check_assist(
813             move_arm_cond_to_match_guard,
814             r#"
815 fn main() {
816     match 92 {
817         3 => true,
818         x => {
819             if x > 10 {$0
820                 false
821             } else if x > 5 {
822                 true
823             } else if x > 4 {
824                 false
825             } else {
826                 true
827             }
828         }
829     }
830 }
831 "#,
832             r#"
833 fn main() {
834     match 92 {
835         3 => true,
836         x if x > 10 => false,
837         x if x > 5 => true,
838         x if x > 4 => false,
839         x => true,
840     }
841 }
842 "#,
843         )
844     }
845
846     #[test]
847     fn move_arm_cond_to_match_guard_elseif_chain() {
848         cov_mark::check!(move_guard_ifelse_else_tail);
849         check_assist(
850             move_arm_cond_to_match_guard,
851             r#"
852 fn main() {
853     match 92 {
854         3 => 0,
855         x => if x > 10 {$0
856             1
857         } else if x > 5 {
858             2
859         } else if x > 3 {
860             42;
861             3
862         } else {
863             4
864         },
865     }
866 }
867 "#,
868             r#"
869 fn main() {
870     match 92 {
871         3 => 0,
872         x if x > 10 => 1,
873         x if x > 5 => 2,
874         x if x > 3 => {
875             42;
876             3
877         }
878         x => 4,
879     }
880 }
881 "#,
882         )
883     }
884
885     #[test]
886     fn move_arm_cond_to_match_guard_elseif_iflet() {
887         check_assist_not_applicable(
888             move_arm_cond_to_match_guard,
889             r#"
890 fn main() {
891     match 92 {
892         3 => 0,
893         x => if x > 10 {$0
894             1
895         } else if x > 5 {
896             2
897         } else if let 4 = 4 {
898             42;
899             3
900         } else {
901             4
902         },
903     }
904 }
905 "#,
906         )
907     }
908
909     #[test]
910     fn move_arm_cond_to_match_guard_elseif_notail() {
911         cov_mark::check!(move_guard_ifelse_notail);
912         check_assist(
913             move_arm_cond_to_match_guard,
914             r#"
915 fn main() {
916     match 92 {
917         3 => 0,
918         x => if x > 10 {$0
919             1
920         } else if x > 5 {
921             2
922         } else if x > 4 {
923             42;
924             3
925         },
926     }
927 }
928 "#,
929             r#"
930 fn main() {
931     match 92 {
932         3 => 0,
933         x if x > 10 => 1,
934         x if x > 5 => 2,
935         x if x > 4 => {
936             42;
937             3
938         }
939         x => {}
940     }
941 }
942 "#,
943         )
944     }
945 }