diff options
Diffstat (limited to 'crates/ra_assists/src/handlers/early_return.rs')
-rw-r--r-- | crates/ra_assists/src/handlers/early_return.rs | 505 |
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 @@ | |||
1 | use std::{iter::once, ops::RangeInclusive}; | ||
2 | |||
3 | use 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 | |||
11 | use 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 | // ``` | ||
39 | pub(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)] | ||
181 | mod 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 | } | ||