ide_assists/handlers/
convert_while_to_loop.rs1use std::iter;
2
3use ide_db::syntax_helpers::node_ext::is_pattern_cond;
4use syntax::{
5 AstNode, T,
6 ast::{
7 self, HasLoopBody,
8 edit::{AstNodeEdit, IndentLevel},
9 make,
10 },
11 syntax_editor::{Element, Position},
12};
13
14use crate::{
15 AssistId,
16 assist_context::{AssistContext, Assists},
17 utils::invert_boolean_expression_legacy,
18};
19
20pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
43 let while_kw = ctx.find_token_syntax_at_offset(T![while])?;
44 let while_expr = while_kw.parent().and_then(ast::WhileExpr::cast)?;
45 let while_body = while_expr.loop_body()?;
46 let while_cond = while_expr.condition()?;
47 let l_curly = while_body.stmt_list()?.l_curly_token()?;
48
49 let target = while_expr.syntax().text_range();
50 acc.add(
51 AssistId::refactor_rewrite("convert_while_to_loop"),
52 "Convert while to loop",
53 target,
54 |builder| {
55 let mut edit = builder.make_editor(while_expr.syntax());
56 let while_indent_level = IndentLevel::from_node(while_expr.syntax());
57
58 let break_block = make::block_expr(
59 iter::once(make::expr_stmt(make::expr_break(None, None)).into()),
60 None,
61 )
62 .indent(IndentLevel(1));
63
64 edit.replace_all(
65 while_kw.syntax_element()..=while_cond.syntax().syntax_element(),
66 vec![make::token(T![loop]).syntax_element()],
67 );
68
69 if is_pattern_cond(while_cond.clone()) {
70 let then_branch = while_body.reset_indent().indent(IndentLevel(1));
71 let if_expr = make::expr_if(while_cond, then_branch, Some(break_block.into()));
72 let stmts = iter::once(make::expr_stmt(if_expr.into()).into());
73 let block_expr = make::block_expr(stmts, None);
74 edit.replace(while_body.syntax(), block_expr.indent(while_indent_level).syntax());
75 } else {
76 let if_cond = invert_boolean_expression_legacy(while_cond);
77 let if_expr = make::expr_if(if_cond, break_block, None).indent(while_indent_level);
78 if !while_body.syntax().text().contains_char('\n') {
79 edit.insert(
80 Position::after(&l_curly),
81 make::tokens::whitespace(&format!("\n{while_indent_level}")),
82 );
83 }
84 edit.insert_all(
85 Position::after(&l_curly),
86 vec![
87 make::tokens::whitespace(&format!("\n{}", while_indent_level + 1)).into(),
88 if_expr.syntax().syntax_element(),
89 ],
90 );
91 };
92
93 builder.add_file_edits(ctx.vfs_file_id(), edit);
94 },
95 )
96}
97
98#[cfg(test)]
99mod tests {
100 use crate::tests::{check_assist, check_assist_not_applicable};
101
102 use super::*;
103
104 #[test]
105 fn convert_inside_fn() {
106 check_assist(
107 convert_while_to_loop,
108 r#"
109fn main() {
110 while$0 cond {
111 foo();
112 }
113}
114"#,
115 r#"
116fn main() {
117 loop {
118 if !cond {
119 break;
120 }
121 foo();
122 }
123}
124"#,
125 );
126 }
127
128 #[test]
129 fn convert_with_label() {
130 check_assist(
131 convert_while_to_loop,
132 r#"
133fn main() {
134 'x: while$0 cond {
135 foo();
136 break 'x
137 }
138}
139"#,
140 r#"
141fn main() {
142 'x: loop {
143 if !cond {
144 break;
145 }
146 foo();
147 break 'x
148 }
149}
150"#,
151 );
152
153 check_assist(
154 convert_while_to_loop,
155 r#"
156fn main() {
157 'x: while$0 let Some(x) = cond {
158 foo();
159 break 'x
160 }
161}
162"#,
163 r#"
164fn main() {
165 'x: loop {
166 if let Some(x) = cond {
167 foo();
168 break 'x
169 } else {
170 break;
171 }
172 }
173}
174"#,
175 );
176 }
177
178 #[test]
179 fn convert_with_attributes() {
180 check_assist(
181 convert_while_to_loop,
182 r#"
183fn main() {
184 #[allow(unused)]
185 while$0 cond {
186 foo();
187 break 'x
188 }
189}
190"#,
191 r#"
192fn main() {
193 #[allow(unused)]
194 loop {
195 if !cond {
196 break;
197 }
198 foo();
199 break 'x
200 }
201}
202"#,
203 );
204
205 check_assist(
206 convert_while_to_loop,
207 r#"
208fn main() {
209 #[allow(unused)]
210 #[deny(unsafe_code)]
211 while$0 let Some(x) = cond {
212 foo();
213 }
214}
215"#,
216 r#"
217fn main() {
218 #[allow(unused)]
219 #[deny(unsafe_code)]
220 loop {
221 if let Some(x) = cond {
222 foo();
223 } else {
224 break;
225 }
226 }
227}
228"#,
229 );
230 }
231
232 #[test]
233 fn convert_busy_wait() {
234 check_assist(
235 convert_while_to_loop,
236 r#"
237fn main() {
238 while$0 cond() {}
239}
240"#,
241 r#"
242fn main() {
243 loop {
244 if !cond() {
245 break;
246 }
247 }
248}
249"#,
250 );
251 }
252
253 #[test]
254 fn convert_trailing_expr() {
255 check_assist(
256 convert_while_to_loop,
257 r#"
258fn main() {
259 while$0 cond() {
260 bar()
261 }
262}
263"#,
264 r#"
265fn main() {
266 loop {
267 if !cond() {
268 break;
269 }
270 bar()
271 }
272}
273"#,
274 );
275 }
276
277 #[test]
278 fn convert_while_let() {
279 check_assist(
280 convert_while_to_loop,
281 r#"
282fn main() {
283 while$0 let Some(_) = foo() {
284 bar();
285 }
286}
287"#,
288 r#"
289fn main() {
290 loop {
291 if let Some(_) = foo() {
292 bar();
293 } else {
294 break;
295 }
296 }
297}
298"#,
299 );
300 }
301
302 #[test]
303 fn indentation() {
304 check_assist(
305 convert_while_to_loop,
306 r#"
307fn main() {
308 {
309 {
310 while$0 cond {
311 foo(
312 "xxx",
313 );
314 }
315 }
316 }
317}
318"#,
319 r#"
320fn main() {
321 {
322 {
323 loop {
324 if !cond {
325 break;
326 }
327 foo(
328 "xxx",
329 );
330 }
331 }
332 }
333}
334"#,
335 );
336
337 check_assist(
338 convert_while_to_loop,
339 r#"
340fn main() {
341 {
342 {
343 while$0 let Some(_) = foo() {
344 bar(
345 "xxx",
346 );
347 }
348 }
349 }
350}
351"#,
352 r#"
353fn main() {
354 {
355 {
356 loop {
357 if let Some(_) = foo() {
358 bar(
359 "xxx",
360 );
361 } else {
362 break;
363 }
364 }
365 }
366 }
367}
368"#,
369 );
370 }
371
372 #[test]
373 fn ignore_cursor_in_body() {
374 check_assist_not_applicable(
375 convert_while_to_loop,
376 r#"
377fn main() {
378 while cond {$0
379 bar();
380 }
381}
382"#,
383 );
384 }
385
386 #[test]
387 fn preserve_comments() {
388 check_assist(
389 convert_while_to_loop,
390 r#"
391fn main() {
392 let mut i = 0;
393
394 $0while i < 5 {
395 // comment 1
396 dbg!(i);
397 // comment 2
398 i += 1;
399 // comment 3
400 }
401}
402"#,
403 r#"
404fn main() {
405 let mut i = 0;
406
407 loop {
408 if i >= 5 {
409 break;
410 }
411 // comment 1
412 dbg!(i);
413 // comment 2
414 i += 1;
415 // comment 3
416 }
417}
418"#,
419 );
420
421 check_assist(
422 convert_while_to_loop,
423 r#"
424fn main() {
425 let v = vec![1, 2, 3];
426 let iter = v.iter();
427
428 $0while let Some(i) = iter.next() {
429 // comment 1
430 dbg!(i);
431 // comment 2
432 }
433}
434"#,
435 r#"
436fn main() {
437 let v = vec![1, 2, 3];
438 let iter = v.iter();
439
440 loop {
441 if let Some(i) = iter.next() {
442 // comment 1
443 dbg!(i);
444 // comment 2
445 } else {
446 break;
447 }
448 }
449}
450"#,
451 );
452 }
453}