aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorBenjamin Coenen <[email protected]>2020-04-29 12:52:55 +0100
committerBenjamin Coenen <[email protected]>2020-04-29 13:08:30 +0100
commit76733f0cd456005295e60da8c45d74c8c48f177c (patch)
treec5fb8ad0429bb9ad4c749612225f776844b4d127 /crates
parentc3c7edb9bc3f2860686383dc1498c988a56859e8 (diff)
Add unwrap block assist #4156
Signed-off-by: Benjamin Coenen <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/src/doc_tests/generated.rs19
-rw-r--r--crates/ra_assists/src/handlers/unwrap_block.rs435
-rw-r--r--crates/ra_assists/src/lib.rs2
-rw-r--r--crates/ra_syntax/src/ast/expr_extensions.rs2
4 files changed, 457 insertions, 1 deletions
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs
index e4fa9ee36..6fe95da6a 100644
--- a/crates/ra_assists/src/doc_tests/generated.rs
+++ b/crates/ra_assists/src/doc_tests/generated.rs
@@ -726,3 +726,22 @@ use std::{collections::HashMap};
726"#####, 726"#####,
727 ) 727 )
728} 728}
729
730#[test]
731fn doctest_unwrap_block() {
732 check(
733 "unwrap_block",
734 r#####"
735fn foo() {
736 if true {<|>
737 println!("foo");
738 }
739}
740"#####,
741 r#####"
742fn foo() {
743 <|>println!("foo");
744}
745"#####,
746 )
747}
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..b98601f1c
--- /dev/null
+++ b/crates/ra_assists/src/handlers/unwrap_block.rs
@@ -0,0 +1,435 @@
1use crate::{Assist, AssistCtx, AssistId};
2
3use ast::LoopBodyOwner;
4use ra_fmt::unwrap_trivial_block;
5use ra_syntax::{ast, AstNode};
6
7// Assist: unwrap_block
8//
9// Removes the `mut` keyword.
10//
11// ```
12// fn foo() {
13// if true {<|>
14// println!("foo");
15// }
16// }
17// ```
18// ->
19// ```
20// fn foo() {
21// <|>println!("foo");
22// }
23// ```
24pub(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)]
118mod 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}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 64bd87afb..c5df86600 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -143,6 +143,7 @@ mod handlers {
143 mod split_import; 143 mod split_import;
144 mod add_from_impl_for_enum; 144 mod add_from_impl_for_enum;
145 mod reorder_fields; 145 mod reorder_fields;
146 mod unwrap_block;
146 147
147 pub(crate) fn all() -> &'static [AssistHandler] { 148 pub(crate) fn all() -> &'static [AssistHandler] {
148 &[ 149 &[
@@ -181,6 +182,7 @@ mod handlers {
181 replace_unwrap_with_match::replace_unwrap_with_match, 182 replace_unwrap_with_match::replace_unwrap_with_match,
182 split_import::split_import, 183 split_import::split_import,
183 add_from_impl_for_enum::add_from_impl_for_enum, 184 add_from_impl_for_enum::add_from_impl_for_enum,
185 unwrap_block::unwrap_block,
184 // These are manually sorted for better priorities 186 // These are manually sorted for better priorities
185 add_missing_impl_members::add_missing_impl_members, 187 add_missing_impl_members::add_missing_impl_members,
186 add_missing_impl_members::add_missing_default_members, 188 add_missing_impl_members::add_missing_default_members,
diff --git a/crates/ra_syntax/src/ast/expr_extensions.rs b/crates/ra_syntax/src/ast/expr_extensions.rs
index 93aa3d45f..1c1134bc5 100644
--- a/crates/ra_syntax/src/ast/expr_extensions.rs
+++ b/crates/ra_syntax/src/ast/expr_extensions.rs
@@ -43,7 +43,7 @@ impl ast::IfExpr {
43 Some(res) 43 Some(res)
44 } 44 }
45 45
46 fn blocks(&self) -> AstChildren<ast::BlockExpr> { 46 pub fn blocks(&self) -> AstChildren<ast::BlockExpr> {
47 support::children(self.syntax()) 47 support::children(self.syntax())
48 } 48 }
49} 49}