aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/handlers/early_return.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/handlers/early_return.rs')
-rw-r--r--crates/ra_assists/src/handlers/early_return.rs505
1 files changed, 505 insertions, 0 deletions
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs
new file mode 100644
index 000000000..22f88884f
--- /dev/null
+++ b/crates/ra_assists/src/handlers/early_return.rs
@@ -0,0 +1,505 @@
1use std::{iter::once, ops::RangeInclusive};
2
3use ra_syntax::{
4 algo::replace_children,
5 ast::{self, edit::IndentLevel, make, Block, Pat::TupleStructPat},
6 AstNode,
7 SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE},
8 SyntaxNode,
9};
10
11use crate::{
12 assist_ctx::{Assist, AssistCtx},
13 utils::invert_boolean_expression,
14 AssistId,
15};
16
17// Assist: convert_to_guarded_return
18//
19// Replace a large conditional with a guarded return.
20//
21// ```
22// fn main() {
23// <|>if cond {
24// foo();
25// bar();
26// }
27// }
28// ```
29// ->
30// ```
31// fn main() {
32// if !cond {
33// return;
34// }
35// foo();
36// bar();
37// }
38// ```
39pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
40 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
41 if if_expr.else_branch().is_some() {
42 return None;
43 }
44
45 let cond = if_expr.condition()?;
46
47 // Check if there is an IfLet that we can handle.
48 let if_let_pat = match cond.pat() {
49 None => None, // No IfLet, supported.
50 Some(TupleStructPat(pat)) if pat.args().count() == 1 => {
51 let path = pat.path()?;
52 match path.qualifier() {
53 None => {
54 let bound_ident = pat.args().next().unwrap();
55 Some((path, bound_ident))
56 }
57 Some(_) => return None,
58 }
59 }
60 Some(_) => return None, // Unsupported IfLet.
61 };
62
63 let cond_expr = cond.expr()?;
64 let then_block = if_expr.then_branch()?.block()?;
65
66 let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::Block::cast)?;
67
68 if parent_block.expr()? != if_expr.clone().into() {
69 return None;
70 }
71
72 // check for early return and continue
73 let first_in_then_block = then_block.syntax().first_child()?;
74 if ast::ReturnExpr::can_cast(first_in_then_block.kind())
75 || ast::ContinueExpr::can_cast(first_in_then_block.kind())
76 || first_in_then_block
77 .children()
78 .any(|x| ast::ReturnExpr::can_cast(x.kind()) || ast::ContinueExpr::can_cast(x.kind()))
79 {
80 return None;
81 }
82
83 let parent_container = parent_block.syntax().parent()?.parent()?;
84
85 let early_expression: ast::Expr = match parent_container.kind() {
86 WHILE_EXPR | LOOP_EXPR => make::expr_continue(),
87 FN_DEF => make::expr_return(),
88 _ => return None,
89 };
90
91 if then_block.syntax().first_child_or_token().map(|t| t.kind() == L_CURLY).is_none() {
92 return None;
93 }
94
95 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?;
96 let cursor_position = ctx.frange.range.start();
97
98 ctx.add_assist(AssistId("convert_to_guarded_return"), "Convert to guarded return", |edit| {
99 let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
100 let new_block = match if_let_pat {
101 None => {
102 // If.
103 let new_expr = {
104 let then_branch =
105 make::block_expr(once(make::expr_stmt(early_expression).into()), None);
106 let cond = invert_boolean_expression(cond_expr);
107 let e = make::expr_if(cond, then_branch);
108 if_indent_level.increase_indent(e)
109 };
110 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
111 }
112 Some((path, bound_ident)) => {
113 // If-let.
114 let match_expr = {
115 let happy_arm = make::match_arm(
116 once(
117 make::tuple_struct_pat(
118 path,
119 once(make::bind_pat(make::name("it")).into()),
120 )
121 .into(),
122 ),
123 make::expr_path(make::path_from_name_ref(make::name_ref("it"))),
124 );
125
126 let sad_arm = make::match_arm(
127 // FIXME: would be cool to use `None` or `Err(_)` if appropriate
128 once(make::placeholder_pat().into()),
129 early_expression,
130 );
131
132 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
133 };
134
135 let let_stmt = make::let_stmt(
136 make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(),
137 Some(match_expr),
138 );
139 let let_stmt = if_indent_level.increase_indent(let_stmt);
140 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
141 }
142 };
143 edit.target(if_expr.syntax().text_range());
144 edit.replace_ast(parent_block, ast::Block::cast(new_block).unwrap());
145 edit.set_cursor(cursor_position);
146
147 fn replace(
148 new_expr: &SyntaxNode,
149 then_block: &Block,
150 parent_block: &Block,
151 if_expr: &ast::IfExpr,
152 ) -> SyntaxNode {
153 let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone());
154 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
155 let end_of_then =
156 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
157 end_of_then.prev_sibling_or_token().unwrap()
158 } else {
159 end_of_then
160 };
161 let mut then_statements = new_expr.children_with_tokens().chain(
162 then_block_items
163 .syntax()
164 .children_with_tokens()
165 .skip(1)
166 .take_while(|i| *i != end_of_then),
167 );
168 replace_children(
169 &parent_block.syntax(),
170 RangeInclusive::new(
171 if_expr.clone().syntax().clone().into(),
172 if_expr.syntax().clone().into(),
173 ),
174 &mut then_statements,
175 )
176 }
177 })
178}
179
180#[cfg(test)]
181mod tests {
182 use crate::helpers::{check_assist, check_assist_not_applicable};
183
184 use super::*;
185
186 #[test]
187 fn convert_inside_fn() {
188 check_assist(
189 convert_to_guarded_return,
190 r#"
191 fn main() {
192 bar();
193 if<|> true {
194 foo();
195
196 //comment
197 bar();
198 }
199 }
200 "#,
201 r#"
202 fn main() {
203 bar();
204 if<|> !true {
205 return;
206 }
207 foo();
208
209 //comment
210 bar();
211 }
212 "#,
213 );
214 }
215
216 #[test]
217 fn convert_let_inside_fn() {
218 check_assist(
219 convert_to_guarded_return,
220 r#"
221 fn main(n: Option<String>) {
222 bar();
223 if<|> let Some(n) = n {
224 foo(n);
225
226 //comment
227 bar();
228 }
229 }
230 "#,
231 r#"
232 fn main(n: Option<String>) {
233 bar();
234 le<|>t n = match n {
235 Some(it) => it,
236 _ => return,
237 };
238 foo(n);
239
240 //comment
241 bar();
242 }
243 "#,
244 );
245 }
246
247 #[test]
248 fn convert_if_let_result() {
249 check_assist(
250 convert_to_guarded_return,
251 r#"
252 fn main() {
253 if<|> let Ok(x) = Err(92) {
254 foo(x);
255 }
256 }
257 "#,
258 r#"
259 fn main() {
260 le<|>t x = match Err(92) {
261 Ok(it) => it,
262 _ => return,
263 };
264 foo(x);
265 }
266 "#,
267 );
268 }
269
270 #[test]
271 fn convert_let_ok_inside_fn() {
272 check_assist(
273 convert_to_guarded_return,
274 r#"
275 fn main(n: Option<String>) {
276 bar();
277 if<|> let Ok(n) = n {
278 foo(n);
279
280 //comment
281 bar();
282 }
283 }
284 "#,
285 r#"
286 fn main(n: Option<String>) {
287 bar();
288 le<|>t n = match n {
289 Ok(it) => it,
290 _ => return,
291 };
292 foo(n);
293
294 //comment
295 bar();
296 }
297 "#,
298 );
299 }
300
301 #[test]
302 fn convert_inside_while() {
303 check_assist(
304 convert_to_guarded_return,
305 r#"
306 fn main() {
307 while true {
308 if<|> true {
309 foo();
310 bar();
311 }
312 }
313 }
314 "#,
315 r#"
316 fn main() {
317 while true {
318 if<|> !true {
319 continue;
320 }
321 foo();
322 bar();
323 }
324 }
325 "#,
326 );
327 }
328
329 #[test]
330 fn convert_let_inside_while() {
331 check_assist(
332 convert_to_guarded_return,
333 r#"
334 fn main() {
335 while true {
336 if<|> let Some(n) = n {
337 foo(n);
338 bar();
339 }
340 }
341 }
342 "#,
343 r#"
344 fn main() {
345 while true {
346 le<|>t n = match n {
347 Some(it) => it,
348 _ => continue,
349 };
350 foo(n);
351 bar();
352 }
353 }
354 "#,
355 );
356 }
357
358 #[test]
359 fn convert_inside_loop() {
360 check_assist(
361 convert_to_guarded_return,
362 r#"
363 fn main() {
364 loop {
365 if<|> true {
366 foo();
367 bar();
368 }
369 }
370 }
371 "#,
372 r#"
373 fn main() {
374 loop {
375 if<|> !true {
376 continue;
377 }
378 foo();
379 bar();
380 }
381 }
382 "#,
383 );
384 }
385
386 #[test]
387 fn convert_let_inside_loop() {
388 check_assist(
389 convert_to_guarded_return,
390 r#"
391 fn main() {
392 loop {
393 if<|> let Some(n) = n {
394 foo(n);
395 bar();
396 }
397 }
398 }
399 "#,
400 r#"
401 fn main() {
402 loop {
403 le<|>t n = match n {
404 Some(it) => it,
405 _ => continue,
406 };
407 foo(n);
408 bar();
409 }
410 }
411 "#,
412 );
413 }
414
415 #[test]
416 fn ignore_already_converted_if() {
417 check_assist_not_applicable(
418 convert_to_guarded_return,
419 r#"
420 fn main() {
421 if<|> true {
422 return;
423 }
424 }
425 "#,
426 );
427 }
428
429 #[test]
430 fn ignore_already_converted_loop() {
431 check_assist_not_applicable(
432 convert_to_guarded_return,
433 r#"
434 fn main() {
435 loop {
436 if<|> true {
437 continue;
438 }
439 }
440 }
441 "#,
442 );
443 }
444
445 #[test]
446 fn ignore_return() {
447 check_assist_not_applicable(
448 convert_to_guarded_return,
449 r#"
450 fn main() {
451 if<|> true {
452 return
453 }
454 }
455 "#,
456 );
457 }
458
459 #[test]
460 fn ignore_else_branch() {
461 check_assist_not_applicable(
462 convert_to_guarded_return,
463 r#"
464 fn main() {
465 if<|> true {
466 foo();
467 } else {
468 bar()
469 }
470 }
471 "#,
472 );
473 }
474
475 #[test]
476 fn ignore_statements_aftert_if() {
477 check_assist_not_applicable(
478 convert_to_guarded_return,
479 r#"
480 fn main() {
481 if<|> true {
482 foo();
483 }
484 bar();
485 }
486 "#,
487 );
488 }
489
490 #[test]
491 fn ignore_statements_inside_if() {
492 check_assist_not_applicable(
493 convert_to_guarded_return,
494 r#"
495 fn main() {
496 if false {
497 if<|> true {
498 foo();
499 }
500 }
501 }
502 "#,
503 );
504 }
505}