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