diff options
Diffstat (limited to 'crates/ra_assists/src/handlers')
-rw-r--r-- | crates/ra_assists/src/handlers/unwrap_block.rs | 435 |
1 files changed, 435 insertions, 0 deletions
diff --git a/crates/ra_assists/src/handlers/unwrap_block.rs b/crates/ra_assists/src/handlers/unwrap_block.rs new file mode 100644 index 000000000..35d87bc9e --- /dev/null +++ b/crates/ra_assists/src/handlers/unwrap_block.rs | |||
@@ -0,0 +1,435 @@ | |||
1 | use crate::{Assist, AssistCtx, AssistId}; | ||
2 | |||
3 | use ast::LoopBodyOwner; | ||
4 | use ra_fmt::unwrap_trivial_block; | ||
5 | use ra_syntax::{ast, AstNode}; | ||
6 | |||
7 | // Assist: unwrap_block | ||
8 | // | ||
9 | // This assist removes if...else, for, while and loop control statements to just keep the body. | ||
10 | // | ||
11 | // ``` | ||
12 | // fn foo() { | ||
13 | // if true {<|> | ||
14 | // println!("foo"); | ||
15 | // } | ||
16 | // } | ||
17 | // ``` | ||
18 | // -> | ||
19 | // ``` | ||
20 | // fn foo() { | ||
21 | // println!("foo"); | ||
22 | // } | ||
23 | // ``` | ||
24 | pub(crate) fn unwrap_block(ctx: AssistCtx) -> Option<Assist> { | ||
25 | let res = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() { | ||
26 | // if expression | ||
27 | let mut expr_to_unwrap: Option<ast::Expr> = None; | ||
28 | for block_expr in if_expr.blocks() { | ||
29 | if let Some(block) = block_expr.block() { | ||
30 | let cursor_in_range = | ||
31 | block.l_curly_token()?.text_range().contains_range(ctx.frange.range); | ||
32 | |||
33 | if cursor_in_range { | ||
34 | let exprto = unwrap_trivial_block(block_expr); | ||
35 | expr_to_unwrap = Some(exprto); | ||
36 | break; | ||
37 | } | ||
38 | } | ||
39 | } | ||
40 | let expr_to_unwrap = expr_to_unwrap?; | ||
41 | // Find if we are in a else if block | ||
42 | let ancestor = ctx | ||
43 | .sema | ||
44 | .ancestors_with_macros(if_expr.syntax().clone()) | ||
45 | .skip(1) | ||
46 | .find_map(ast::IfExpr::cast); | ||
47 | |||
48 | if let Some(ancestor) = ancestor { | ||
49 | Some((ast::Expr::IfExpr(ancestor), expr_to_unwrap)) | ||
50 | } else { | ||
51 | Some((ast::Expr::IfExpr(if_expr), expr_to_unwrap)) | ||
52 | } | ||
53 | } else if let Some(for_expr) = ctx.find_node_at_offset::<ast::ForExpr>() { | ||
54 | // for expression | ||
55 | let block_expr = for_expr.loop_body()?; | ||
56 | let block = block_expr.block()?; | ||
57 | let cursor_in_range = block.l_curly_token()?.text_range().contains_range(ctx.frange.range); | ||
58 | |||
59 | if cursor_in_range { | ||
60 | let expr_to_unwrap = unwrap_trivial_block(block_expr); | ||
61 | |||
62 | Some((ast::Expr::ForExpr(for_expr), expr_to_unwrap)) | ||
63 | } else { | ||
64 | None | ||
65 | } | ||
66 | } else if let Some(while_expr) = ctx.find_node_at_offset::<ast::WhileExpr>() { | ||
67 | // while expression | ||
68 | let block_expr = while_expr.loop_body()?; | ||
69 | let block = block_expr.block()?; | ||
70 | let cursor_in_range = block.l_curly_token()?.text_range().contains_range(ctx.frange.range); | ||
71 | |||
72 | if cursor_in_range { | ||
73 | let expr_to_unwrap = unwrap_trivial_block(block_expr); | ||
74 | |||
75 | Some((ast::Expr::WhileExpr(while_expr), expr_to_unwrap)) | ||
76 | } else { | ||
77 | None | ||
78 | } | ||
79 | } else if let Some(loop_expr) = ctx.find_node_at_offset::<ast::LoopExpr>() { | ||
80 | // loop expression | ||
81 | let block_expr = loop_expr.loop_body()?; | ||
82 | let block = block_expr.block()?; | ||
83 | let cursor_in_range = block.l_curly_token()?.text_range().contains_range(ctx.frange.range); | ||
84 | |||
85 | if cursor_in_range { | ||
86 | let expr_to_unwrap = unwrap_trivial_block(block_expr); | ||
87 | |||
88 | Some((ast::Expr::LoopExpr(loop_expr), expr_to_unwrap)) | ||
89 | } else { | ||
90 | None | ||
91 | } | ||
92 | } else { | ||
93 | None | ||
94 | }; | ||
95 | |||
96 | let (expr, expr_to_unwrap) = res?; | ||
97 | ctx.add_assist(AssistId("unwrap_block"), "Unwrap block", |edit| { | ||
98 | edit.set_cursor(expr.syntax().text_range().start()); | ||
99 | edit.target(expr_to_unwrap.syntax().text_range()); | ||
100 | |||
101 | let pat_start: &[_] = &[' ', '{', '\n']; | ||
102 | let expr_to_unwrap = expr_to_unwrap.to_string(); | ||
103 | let expr_string = expr_to_unwrap.trim_start_matches(pat_start); | ||
104 | let mut expr_string_lines: Vec<&str> = expr_string.lines().collect(); | ||
105 | expr_string_lines.pop(); // Delete last line | ||
106 | |||
107 | let expr_string = expr_string_lines | ||
108 | .into_iter() | ||
109 | .map(|line| line.replacen(" ", "", 1)) // Delete indentation | ||
110 | .collect::<Vec<String>>() | ||
111 | .join("\n"); | ||
112 | |||
113 | edit.replace(expr.syntax().text_range(), expr_string); | ||
114 | }) | ||
115 | } | ||
116 | |||
117 | #[cfg(test)] | ||
118 | mod tests { | ||
119 | use crate::helpers::{check_assist, check_assist_not_applicable}; | ||
120 | |||
121 | use super::*; | ||
122 | |||
123 | #[test] | ||
124 | fn simple_if() { | ||
125 | check_assist( | ||
126 | unwrap_block, | ||
127 | r#" | ||
128 | fn main() { | ||
129 | bar(); | ||
130 | if true {<|> | ||
131 | foo(); | ||
132 | |||
133 | //comment | ||
134 | bar(); | ||
135 | } else { | ||
136 | println!("bar"); | ||
137 | } | ||
138 | } | ||
139 | "#, | ||
140 | r#" | ||
141 | fn main() { | ||
142 | bar(); | ||
143 | <|>foo(); | ||
144 | |||
145 | //comment | ||
146 | bar(); | ||
147 | } | ||
148 | "#, | ||
149 | ); | ||
150 | } | ||
151 | |||
152 | #[test] | ||
153 | fn simple_if_else() { | ||
154 | check_assist( | ||
155 | unwrap_block, | ||
156 | r#" | ||
157 | fn main() { | ||
158 | bar(); | ||
159 | if true { | ||
160 | foo(); | ||
161 | |||
162 | //comment | ||
163 | bar(); | ||
164 | } else {<|> | ||
165 | println!("bar"); | ||
166 | } | ||
167 | } | ||
168 | "#, | ||
169 | r#" | ||
170 | fn main() { | ||
171 | bar(); | ||
172 | <|>println!("bar"); | ||
173 | } | ||
174 | "#, | ||
175 | ); | ||
176 | } | ||
177 | |||
178 | #[test] | ||
179 | fn simple_if_else_if() { | ||
180 | check_assist( | ||
181 | unwrap_block, | ||
182 | r#" | ||
183 | fn main() { | ||
184 | //bar(); | ||
185 | if true { | ||
186 | println!("true"); | ||
187 | |||
188 | //comment | ||
189 | //bar(); | ||
190 | } else if false {<|> | ||
191 | println!("bar"); | ||
192 | } else { | ||
193 | println!("foo"); | ||
194 | } | ||
195 | } | ||
196 | "#, | ||
197 | r#" | ||
198 | fn main() { | ||
199 | //bar(); | ||
200 | <|>println!("bar"); | ||
201 | } | ||
202 | "#, | ||
203 | ); | ||
204 | } | ||
205 | |||
206 | #[test] | ||
207 | fn simple_if_bad_cursor_position() { | ||
208 | check_assist_not_applicable( | ||
209 | unwrap_block, | ||
210 | r#" | ||
211 | fn main() { | ||
212 | bar();<|> | ||
213 | if true { | ||
214 | foo(); | ||
215 | |||
216 | //comment | ||
217 | bar(); | ||
218 | } else { | ||
219 | println!("bar"); | ||
220 | } | ||
221 | } | ||
222 | "#, | ||
223 | ); | ||
224 | } | ||
225 | |||
226 | #[test] | ||
227 | fn issue_example_with_if() { | ||
228 | check_assist( | ||
229 | unwrap_block, | ||
230 | r#" | ||
231 | fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) { | ||
232 | if let Some(ty) = &ctx.expected_type {<|> | ||
233 | if let Some(Adt::Enum(enum_data)) = ty.as_adt() { | ||
234 | let variants = enum_data.variants(ctx.db); | ||
235 | |||
236 | let module = if let Some(module) = ctx.scope().module() { | ||
237 | // Compute path from the completion site if available. | ||
238 | module | ||
239 | } else { | ||
240 | // Otherwise fall back to the enum's definition site. | ||
241 | enum_data.module(ctx.db) | ||
242 | }; | ||
243 | |||
244 | for variant in variants { | ||
245 | if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) { | ||
246 | // Variants with trivial paths are already added by the existing completion logic, | ||
247 | // so we should avoid adding these twice | ||
248 | if path.segments.len() > 1 { | ||
249 | acc.add_enum_variant(ctx, variant, Some(path.to_string())); | ||
250 | } | ||
251 | } | ||
252 | } | ||
253 | } | ||
254 | } | ||
255 | } | ||
256 | "#, | ||
257 | r#" | ||
258 | fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) { | ||
259 | <|>if let Some(Adt::Enum(enum_data)) = ty.as_adt() { | ||
260 | let variants = enum_data.variants(ctx.db); | ||
261 | |||
262 | let module = if let Some(module) = ctx.scope().module() { | ||
263 | // Compute path from the completion site if available. | ||
264 | module | ||
265 | } else { | ||
266 | // Otherwise fall back to the enum's definition site. | ||
267 | enum_data.module(ctx.db) | ||
268 | }; | ||
269 | |||
270 | for variant in variants { | ||
271 | if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) { | ||
272 | // Variants with trivial paths are already added by the existing completion logic, | ||
273 | // so we should avoid adding these twice | ||
274 | if path.segments.len() > 1 { | ||
275 | acc.add_enum_variant(ctx, variant, Some(path.to_string())); | ||
276 | } | ||
277 | } | ||
278 | } | ||
279 | } | ||
280 | } | ||
281 | "#, | ||
282 | ); | ||
283 | } | ||
284 | |||
285 | #[test] | ||
286 | fn simple_for() { | ||
287 | check_assist( | ||
288 | unwrap_block, | ||
289 | r#" | ||
290 | fn main() { | ||
291 | for i in 0..5 {<|> | ||
292 | if true { | ||
293 | foo(); | ||
294 | |||
295 | //comment | ||
296 | bar(); | ||
297 | } else { | ||
298 | println!("bar"); | ||
299 | } | ||
300 | } | ||
301 | } | ||
302 | "#, | ||
303 | r#" | ||
304 | fn main() { | ||
305 | <|>if true { | ||
306 | foo(); | ||
307 | |||
308 | //comment | ||
309 | bar(); | ||
310 | } else { | ||
311 | println!("bar"); | ||
312 | } | ||
313 | } | ||
314 | "#, | ||
315 | ); | ||
316 | } | ||
317 | |||
318 | #[test] | ||
319 | fn simple_if_in_for() { | ||
320 | check_assist( | ||
321 | unwrap_block, | ||
322 | r#" | ||
323 | fn main() { | ||
324 | for i in 0..5 { | ||
325 | if true {<|> | ||
326 | foo(); | ||
327 | |||
328 | //comment | ||
329 | bar(); | ||
330 | } else { | ||
331 | println!("bar"); | ||
332 | } | ||
333 | } | ||
334 | } | ||
335 | "#, | ||
336 | r#" | ||
337 | fn main() { | ||
338 | for i in 0..5 { | ||
339 | <|>foo(); | ||
340 | |||
341 | //comment | ||
342 | bar(); | ||
343 | } | ||
344 | } | ||
345 | "#, | ||
346 | ); | ||
347 | } | ||
348 | |||
349 | #[test] | ||
350 | fn simple_loop() { | ||
351 | check_assist( | ||
352 | unwrap_block, | ||
353 | r#" | ||
354 | fn main() { | ||
355 | loop {<|> | ||
356 | if true { | ||
357 | foo(); | ||
358 | |||
359 | //comment | ||
360 | bar(); | ||
361 | } else { | ||
362 | println!("bar"); | ||
363 | } | ||
364 | } | ||
365 | } | ||
366 | "#, | ||
367 | r#" | ||
368 | fn main() { | ||
369 | <|>if true { | ||
370 | foo(); | ||
371 | |||
372 | //comment | ||
373 | bar(); | ||
374 | } else { | ||
375 | println!("bar"); | ||
376 | } | ||
377 | } | ||
378 | "#, | ||
379 | ); | ||
380 | } | ||
381 | |||
382 | #[test] | ||
383 | fn simple_while() { | ||
384 | check_assist( | ||
385 | unwrap_block, | ||
386 | r#" | ||
387 | fn main() { | ||
388 | while true {<|> | ||
389 | if true { | ||
390 | foo(); | ||
391 | |||
392 | //comment | ||
393 | bar(); | ||
394 | } else { | ||
395 | println!("bar"); | ||
396 | } | ||
397 | } | ||
398 | } | ||
399 | "#, | ||
400 | r#" | ||
401 | fn main() { | ||
402 | <|>if true { | ||
403 | foo(); | ||
404 | |||
405 | //comment | ||
406 | bar(); | ||
407 | } else { | ||
408 | println!("bar"); | ||
409 | } | ||
410 | } | ||
411 | "#, | ||
412 | ); | ||
413 | } | ||
414 | |||
415 | #[test] | ||
416 | fn simple_if_in_while_bad_cursor_position() { | ||
417 | check_assist_not_applicable( | ||
418 | unwrap_block, | ||
419 | r#" | ||
420 | fn main() { | ||
421 | while true { | ||
422 | if true { | ||
423 | foo();<|> | ||
424 | |||
425 | //comment | ||
426 | bar(); | ||
427 | } else { | ||
428 | println!("bar"); | ||
429 | } | ||
430 | } | ||
431 | } | ||
432 | "#, | ||
433 | ); | ||
434 | } | ||
435 | } | ||