]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/unwrap_block.rs
9171874e9d4bbaeb5e18e128ccafb63a8b7b8b63
[rust.git] / crates / ide_assists / src / handlers / unwrap_block.rs
1 use syntax::{
2     ast::{
3         self,
4         edit::{AstNodeEdit, IndentLevel},
5     },
6     AstNode, SyntaxKind, TextRange, T,
7 };
8
9 use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists};
10
11 // Assist: unwrap_block
12 //
13 // This assist removes if...else, for, while and loop control statements to just keep the body.
14 //
15 // ```
16 // fn foo() {
17 //     if true {$0
18 //         println!("foo");
19 //     }
20 // }
21 // ```
22 // ->
23 // ```
24 // fn foo() {
25 //     println!("foo");
26 // }
27 // ```
28 pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
29     let assist_id = AssistId("unwrap_block", AssistKind::RefactorRewrite);
30     let assist_label = "Unwrap block";
31
32     let l_curly_token = ctx.find_token_syntax_at_offset(T!['{'])?;
33     let mut block = ast::BlockExpr::cast(l_curly_token.ancestors().nth(1)?)?;
34     let target = block.syntax().text_range();
35     let mut parent = block.syntax().parent()?;
36     if ast::MatchArm::can_cast(parent.kind()) {
37         parent = parent.ancestors().find(|it| ast::MatchExpr::can_cast(it.kind()))?
38     }
39
40     if matches!(parent.kind(), SyntaxKind::STMT_LIST | SyntaxKind::EXPR_STMT) {
41         return acc.add(assist_id, assist_label, target, |builder| {
42             builder.replace(block.syntax().text_range(), update_expr_string(block.to_string()));
43         });
44     }
45
46     let parent = ast::Expr::cast(parent)?;
47
48     match parent.clone() {
49         ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::LoopExpr(_) => (),
50         ast::Expr::MatchExpr(_) => block = block.dedent(IndentLevel(1)),
51         ast::Expr::IfExpr(if_expr) => {
52             let then_branch = if_expr.then_branch()?;
53             if then_branch == block {
54                 if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) {
55                     // For `else if` blocks
56                     let ancestor_then_branch = ancestor.then_branch()?;
57
58                     return acc.add(assist_id, assist_label, target, |edit| {
59                         let range_to_del_else_if = TextRange::new(
60                             ancestor_then_branch.syntax().text_range().end(),
61                             l_curly_token.text_range().start(),
62                         );
63                         let range_to_del_rest = TextRange::new(
64                             then_branch.syntax().text_range().end(),
65                             if_expr.syntax().text_range().end(),
66                         );
67
68                         edit.delete(range_to_del_rest);
69                         edit.delete(range_to_del_else_if);
70                         edit.replace(
71                             target,
72                             update_expr_string_without_newline(then_branch.to_string()),
73                         );
74                     });
75                 }
76             } else {
77                 return acc.add(assist_id, assist_label, target, |edit| {
78                     let range_to_del = TextRange::new(
79                         then_branch.syntax().text_range().end(),
80                         l_curly_token.text_range().start(),
81                     );
82
83                     edit.delete(range_to_del);
84                     edit.replace(target, update_expr_string_without_newline(block.to_string()));
85                 });
86             }
87         }
88         _ => return None,
89     };
90
91     let unwrapped = unwrap_trivial_block(block);
92     acc.add(assist_id, assist_label, target, |builder| {
93         builder.replace(parent.syntax().text_range(), update_expr_string(unwrapped.to_string()));
94     })
95 }
96
97 fn update_expr_string(expr_string: String) -> String {
98     update_expr_string_with_pat(expr_string, &[' ', '\n'])
99 }
100
101 fn update_expr_string_without_newline(expr_string: String) -> String {
102     update_expr_string_with_pat(expr_string, &[' '])
103 }
104
105 fn update_expr_string_with_pat(expr_str: String, whitespace_pat: &[char]) -> String {
106     // Remove leading whitespace, index [1..] to remove the leading '{',
107     // then continue to remove leading whitespace.
108     let expr_str =
109         expr_str.trim_start_matches(whitespace_pat)[1..].trim_start_matches(whitespace_pat);
110
111     // Remove trailing whitespace, index [..expr_str.len() - 1] to remove the trailing '}',
112     // then continue to remove trailing whitespace.
113     let expr_str = expr_str.trim_end_matches(whitespace_pat);
114     let expr_str = expr_str[..expr_str.len() - 1].trim_end_matches(whitespace_pat);
115
116     expr_str
117         .lines()
118         .map(|line| line.replacen("    ", "", 1)) // Delete indentation
119         .collect::<Vec<String>>()
120         .join("\n")
121 }
122
123 #[cfg(test)]
124 mod tests {
125     use crate::tests::{check_assist, check_assist_not_applicable};
126
127     use super::*;
128
129     #[test]
130     fn unwrap_tail_expr_block() {
131         check_assist(
132             unwrap_block,
133             r#"
134 fn main() {
135     $0{
136         92
137     }
138 }
139 "#,
140             r#"
141 fn main() {
142     92
143 }
144 "#,
145         )
146     }
147
148     #[test]
149     fn unwrap_stmt_expr_block() {
150         check_assist(
151             unwrap_block,
152             r#"
153 fn main() {
154     $0{
155         92;
156     }
157     ()
158 }
159 "#,
160             r#"
161 fn main() {
162     92;
163     ()
164 }
165 "#,
166         );
167         // Pedantically, we should add an `;` here...
168         check_assist(
169             unwrap_block,
170             r#"
171 fn main() {
172     $0{
173         92
174     }
175     ()
176 }
177 "#,
178             r#"
179 fn main() {
180     92
181     ()
182 }
183 "#,
184         );
185     }
186
187     #[test]
188     fn simple_if() {
189         check_assist(
190             unwrap_block,
191             r#"
192 fn main() {
193     bar();
194     if true {$0
195         foo();
196
197         // comment
198         bar();
199     } else {
200         println!("bar");
201     }
202 }
203 "#,
204             r#"
205 fn main() {
206     bar();
207     foo();
208
209     // comment
210     bar();
211 }
212 "#,
213         );
214     }
215
216     #[test]
217     fn simple_if_else() {
218         check_assist(
219             unwrap_block,
220             r#"
221 fn main() {
222     bar();
223     if true {
224         foo();
225
226         // comment
227         bar();
228     } else {$0
229         println!("bar");
230     }
231 }
232 "#,
233             r#"
234 fn main() {
235     bar();
236     if true {
237         foo();
238
239         // comment
240         bar();
241     }
242     println!("bar");
243 }
244 "#,
245         );
246     }
247
248     #[test]
249     fn simple_if_else_if() {
250         check_assist(
251             unwrap_block,
252             r#"
253 fn main() {
254     // bar();
255     if true {
256         println!("true");
257
258         // comment
259         // bar();
260     } else if false {$0
261         println!("bar");
262     } else {
263         println!("foo");
264     }
265 }
266 "#,
267             r#"
268 fn main() {
269     // bar();
270     if true {
271         println!("true");
272
273         // comment
274         // bar();
275     }
276     println!("bar");
277 }
278 "#,
279         );
280     }
281
282     #[test]
283     fn simple_if_else_if_nested() {
284         check_assist(
285             unwrap_block,
286             r#"
287 fn main() {
288     // bar();
289     if true {
290         println!("true");
291
292         // comment
293         // bar();
294     } else if false {
295         println!("bar");
296     } else if true {$0
297         println!("foo");
298     }
299 }
300 "#,
301             r#"
302 fn main() {
303     // bar();
304     if true {
305         println!("true");
306
307         // comment
308         // bar();
309     } else if false {
310         println!("bar");
311     }
312     println!("foo");
313 }
314 "#,
315         );
316     }
317
318     #[test]
319     fn simple_if_else_if_nested_else() {
320         check_assist(
321             unwrap_block,
322             r#"
323 fn main() {
324     // bar();
325     if true {
326         println!("true");
327
328         // comment
329         // bar();
330     } else if false {
331         println!("bar");
332     } else if true {
333         println!("foo");
334     } else {$0
335         println!("else");
336     }
337 }
338 "#,
339             r#"
340 fn main() {
341     // bar();
342     if true {
343         println!("true");
344
345         // comment
346         // bar();
347     } else if false {
348         println!("bar");
349     } else if true {
350         println!("foo");
351     }
352     println!("else");
353 }
354 "#,
355         );
356     }
357
358     #[test]
359     fn simple_if_else_if_nested_middle() {
360         check_assist(
361             unwrap_block,
362             r#"
363 fn main() {
364     // bar();
365     if true {
366         println!("true");
367
368         // comment
369         // bar();
370     } else if false {
371         println!("bar");
372     } else if true {$0
373         println!("foo");
374     } else {
375         println!("else");
376     }
377 }
378 "#,
379             r#"
380 fn main() {
381     // bar();
382     if true {
383         println!("true");
384
385         // comment
386         // bar();
387     } else if false {
388         println!("bar");
389     }
390     println!("foo");
391 }
392 "#,
393         );
394     }
395
396     #[test]
397     fn simple_if_bad_cursor_position() {
398         check_assist_not_applicable(
399             unwrap_block,
400             r#"
401 fn main() {
402     bar();$0
403     if true {
404         foo();
405
406         // comment
407         bar();
408     } else {
409         println!("bar");
410     }
411 }
412 "#,
413         );
414     }
415
416     #[test]
417     fn simple_for() {
418         check_assist(
419             unwrap_block,
420             r#"
421 fn main() {
422     for i in 0..5 {$0
423         if true {
424             foo();
425
426             // comment
427             bar();
428         } else {
429             println!("bar");
430         }
431     }
432 }
433 "#,
434             r#"
435 fn main() {
436     if true {
437         foo();
438
439         // comment
440         bar();
441     } else {
442         println!("bar");
443     }
444 }
445 "#,
446         );
447     }
448
449     #[test]
450     fn simple_if_in_for() {
451         check_assist(
452             unwrap_block,
453             r#"
454 fn main() {
455     for i in 0..5 {
456         if true {$0
457             foo();
458
459             // comment
460             bar();
461         } else {
462             println!("bar");
463         }
464     }
465 }
466 "#,
467             r#"
468 fn main() {
469     for i in 0..5 {
470         foo();
471
472         // comment
473         bar();
474     }
475 }
476 "#,
477         );
478     }
479
480     #[test]
481     fn simple_loop() {
482         check_assist(
483             unwrap_block,
484             r#"
485 fn main() {
486     loop {$0
487         if true {
488             foo();
489
490             // comment
491             bar();
492         } else {
493             println!("bar");
494         }
495     }
496 }
497 "#,
498             r#"
499 fn main() {
500     if true {
501         foo();
502
503         // comment
504         bar();
505     } else {
506         println!("bar");
507     }
508 }
509 "#,
510         );
511     }
512
513     #[test]
514     fn simple_while() {
515         check_assist(
516             unwrap_block,
517             r#"
518 fn main() {
519     while true {$0
520         if true {
521             foo();
522
523             // comment
524             bar();
525         } else {
526             println!("bar");
527         }
528     }
529 }
530 "#,
531             r#"
532 fn main() {
533     if true {
534         foo();
535
536         // comment
537         bar();
538     } else {
539         println!("bar");
540     }
541 }
542 "#,
543         );
544     }
545
546     #[test]
547     fn unwrap_match_arm() {
548         check_assist(
549             unwrap_block,
550             r#"
551 fn main() {
552     match rel_path {
553         Ok(rel_path) => {$0
554             let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
555             Some((*id, rel_path))
556         }
557         Err(_) => None,
558     }
559 }
560 "#,
561             r#"
562 fn main() {
563     let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
564     Some((*id, rel_path))
565 }
566 "#,
567         );
568     }
569
570     #[test]
571     fn simple_if_in_while_bad_cursor_position() {
572         check_assist_not_applicable(
573             unwrap_block,
574             r#"
575 fn main() {
576     while true {
577         if true {
578             foo();$0
579
580             // comment
581             bar();
582         } else {
583             println!("bar");
584         }
585     }
586 }
587 "#,
588         );
589     }
590
591     #[test]
592     fn simple_single_line() {
593         check_assist(
594             unwrap_block,
595             r#"
596 fn main() {
597     {$0 0 }
598 }
599 "#,
600             r#"
601 fn main() {
602     0
603 }
604 "#,
605         );
606     }
607
608     #[test]
609     fn simple_nested_block() {
610         check_assist(
611             unwrap_block,
612             r#"
613 fn main() {
614     $0{
615         {
616             3
617         }
618     }
619 }
620 "#,
621             r#"
622 fn main() {
623     {
624         3
625     }
626 }
627 "#,
628         );
629     }
630
631     #[test]
632     fn nested_single_line() {
633         check_assist(
634             unwrap_block,
635             r#"
636 fn main() {
637     {$0 { println!("foo"); } }
638 }
639 "#,
640             r#"
641 fn main() {
642     { println!("foo"); }
643 }
644 "#,
645         );
646
647         check_assist(
648             unwrap_block,
649             r#"
650 fn main() {
651     {$0 { 0 } }
652 }
653 "#,
654             r#"
655 fn main() {
656     { 0 }
657 }
658 "#,
659         );
660     }
661
662     #[test]
663     fn simple_if_single_line() {
664         check_assist(
665             unwrap_block,
666             r#"
667 fn main() {
668     if true {$0 /* foo */ foo() } else { bar() /* bar */}
669 }
670 "#,
671             r#"
672 fn main() {
673     /* foo */ foo()
674 }
675 "#,
676         );
677     }
678 }