]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/convert_while_to_loop.rs
Merge #11481
[rust.git] / crates / ide_assists / src / handlers / convert_while_to_loop.rs
1 use std::iter::once;
2
3 use ide_db::helpers::node_ext::is_pattern_cond;
4 use syntax::{
5     ast::{
6         self,
7         edit::{AstNodeEdit, IndentLevel},
8         make, HasLoopBody,
9     },
10     AstNode, T,
11 };
12
13 use crate::{
14     assist_context::{AssistContext, Assists},
15     utils::invert_boolean_expression,
16     AssistId, AssistKind,
17 };
18
19 // Assist: convert_while_to_loop
20 //
21 // Replace a while with a loop.
22 //
23 // ```
24 // fn main() {
25 //     $0while cond {
26 //         foo();
27 //     }
28 // }
29 // ```
30 // ->
31 // ```
32 // fn main() {
33 //     loop {
34 //         if !cond {
35 //             break;
36 //         }
37 //         foo();
38 //     }
39 // }
40 // ```
41 pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
42     let while_kw = ctx.find_token_syntax_at_offset(T![while])?;
43     let while_expr = while_kw.parent().and_then(ast::WhileExpr::cast)?;
44     let while_body = while_expr.loop_body()?;
45     let while_cond = while_expr.condition()?;
46
47     let target = while_expr.syntax().text_range();
48     acc.add(
49         AssistId("convert_while_to_loop", AssistKind::RefactorRewrite),
50         "Convert while to loop",
51         target,
52         |edit| {
53             let while_indent_level = IndentLevel::from_node(while_expr.syntax());
54
55             let break_block =
56                 make::block_expr(once(make::expr_stmt(make::expr_break(None)).into()), None)
57                     .indent(while_indent_level);
58             let block_expr = if is_pattern_cond(while_cond.clone()) {
59                 let if_expr = make::expr_if(while_cond, while_body, Some(break_block.into()));
60                 let stmts = once(make::expr_stmt(if_expr).into());
61                 make::block_expr(stmts, None)
62             } else {
63                 let if_cond = invert_boolean_expression(while_cond);
64                 let if_expr = make::expr_if(if_cond, break_block, None);
65                 let stmts = once(make::expr_stmt(if_expr).into()).chain(while_body.statements());
66                 make::block_expr(stmts, while_body.tail_expr())
67             };
68
69             let replacement = make::expr_loop(block_expr.indent(while_indent_level));
70             edit.replace(target, replacement.syntax().text())
71         },
72     )
73 }
74
75 #[cfg(test)]
76 mod tests {
77     use crate::tests::{check_assist, check_assist_not_applicable};
78
79     use super::*;
80
81     #[test]
82     fn convert_inside_fn() {
83         check_assist(
84             convert_while_to_loop,
85             r#"
86 fn main() {
87     while$0 cond {
88         foo();
89     }
90 }
91 "#,
92             r#"
93 fn main() {
94     loop {
95         if !cond {
96             break;
97         }
98         foo();
99     }
100 }
101 "#,
102         );
103     }
104
105     #[test]
106     fn convert_busy_wait() {
107         check_assist(
108             convert_while_to_loop,
109             r#"
110 fn main() {
111     while$0 cond() {}
112 }
113 "#,
114             r#"
115 fn main() {
116     loop {
117         if !cond() {
118             break;
119         }
120     }
121 }
122 "#,
123         );
124     }
125
126     #[test]
127     fn convert_trailing_expr() {
128         check_assist(
129             convert_while_to_loop,
130             r#"
131 fn main() {
132     while$0 cond() {
133         bar()
134     }
135 }
136 "#,
137             r#"
138 fn main() {
139     loop {
140         if !cond() {
141             break;
142         }
143         bar()
144     }
145 }
146 "#,
147         );
148     }
149
150     #[test]
151     fn convert_while_let() {
152         check_assist(
153             convert_while_to_loop,
154             r#"
155 fn main() {
156     while$0 let Some(_) = foo() {
157         bar();
158     }
159 }
160 "#,
161             r#"
162 fn main() {
163     loop {
164         if let Some(_) = foo() {
165             bar();
166         } else {
167             break;
168         }
169     }
170 }
171 "#,
172         );
173     }
174
175     #[test]
176     fn ignore_cursor_in_body() {
177         check_assist_not_applicable(
178             convert_while_to_loop,
179             r#"
180 fn main() {
181     while cond {$0
182         bar();
183     }
184 }
185 "#,
186         );
187     }
188 }