diff options
-rw-r--r-- | crates/ra_assists/src/assists/early_return.rs | 219 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 17 |
2 files changed, 207 insertions, 29 deletions
diff --git a/crates/ra_assists/src/assists/early_return.rs b/crates/ra_assists/src/assists/early_return.rs index ad6c5695a..570a07a20 100644 --- a/crates/ra_assists/src/assists/early_return.rs +++ b/crates/ra_assists/src/assists/early_return.rs | |||
@@ -3,9 +3,10 @@ use std::ops::RangeInclusive; | |||
3 | use hir::db::HirDatabase; | 3 | use hir::db::HirDatabase; |
4 | use ra_syntax::{ | 4 | use ra_syntax::{ |
5 | algo::replace_children, | 5 | algo::replace_children, |
6 | ast::{self, edit::IndentLevel, make}, | 6 | ast::{self, edit::IndentLevel, make, Block, Pat::TupleStructPat}, |
7 | AstNode, | 7 | AstNode, |
8 | SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE}, | 8 | SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE}, |
9 | SyntaxNode, | ||
9 | }; | 10 | }; |
10 | 11 | ||
11 | use crate::{ | 12 | use crate::{ |
@@ -37,7 +38,23 @@ use crate::{ | |||
37 | // ``` | 38 | // ``` |
38 | pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | 39 | pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { |
39 | let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; | 40 | let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; |
40 | let expr = if_expr.condition()?.expr()?; | 41 | let cond = if_expr.condition()?; |
42 | let mut if_let_ident: Option<String> = None; | ||
43 | |||
44 | // Check if there is an IfLet that we can handle. | ||
45 | match cond.pat() { | ||
46 | None => {} // No IfLet, supported. | ||
47 | Some(TupleStructPat(ref pat)) if pat.args().count() == 1usize => match &pat.path() { | ||
48 | Some(p) => match p.qualifier() { | ||
49 | None => if_let_ident = Some(p.syntax().text().to_string()), | ||
50 | _ => return None, | ||
51 | }, | ||
52 | _ => return None, | ||
53 | }, | ||
54 | _ => return None, // Unsupported IfLet. | ||
55 | }; | ||
56 | |||
57 | let expr = cond.expr()?; | ||
41 | let then_block = if_expr.then_branch()?.block()?; | 58 | let then_block = if_expr.then_branch()?.block()?; |
42 | if if_expr.else_branch().is_some() { | 59 | if if_expr.else_branch().is_some() { |
43 | return None; | 60 | return None; |
@@ -63,8 +80,8 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Opt | |||
63 | let parent_container = parent_block.syntax().parent()?.parent()?; | 80 | let parent_container = parent_block.syntax().parent()?.parent()?; |
64 | 81 | ||
65 | let early_expression = match parent_container.kind() { | 82 | let early_expression = match parent_container.kind() { |
66 | WHILE_EXPR | LOOP_EXPR => Some("continue;"), | 83 | WHILE_EXPR | LOOP_EXPR => Some("continue"), |
67 | FN_DEF => Some("return;"), | 84 | FN_DEF => Some("return"), |
68 | _ => None, | 85 | _ => None, |
69 | }?; | 86 | }?; |
70 | 87 | ||
@@ -77,34 +94,58 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Opt | |||
77 | 94 | ||
78 | ctx.add_assist(AssistId("convert_to_guarded_return"), "convert to guarded return", |edit| { | 95 | ctx.add_assist(AssistId("convert_to_guarded_return"), "convert to guarded return", |edit| { |
79 | let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); | 96 | let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); |
80 | let new_if_expr = | 97 | let new_block = match if_let_ident { |
81 | if_indent_level.increase_indent(make::if_expression(&expr, early_expression)); | 98 | None => { |
82 | let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone()); | 99 | // If. |
83 | let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); | 100 | let early_expression = &(early_expression.to_owned() + ";"); |
84 | let end_of_then = | 101 | let new_expr = |
85 | if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { | 102 | if_indent_level.increase_indent(make::if_expression(&expr, early_expression)); |
86 | end_of_then.prev_sibling_or_token().unwrap() | 103 | replace(new_expr, &then_block, &parent_block, &if_expr) |
87 | } else { | 104 | } |
88 | end_of_then | 105 | Some(if_let_ident) => { |
89 | }; | 106 | // If-let. |
90 | let mut new_if_and_then_statements = new_if_expr.syntax().children_with_tokens().chain( | 107 | let new_expr = if_indent_level.increase_indent(make::let_match_early( |
91 | then_block_items | 108 | expr, |
92 | .syntax() | 109 | &if_let_ident, |
93 | .children_with_tokens() | 110 | early_expression, |
94 | .skip(1) | 111 | )); |
95 | .take_while(|i| *i != end_of_then), | 112 | replace(new_expr, &then_block, &parent_block, &if_expr) |
96 | ); | 113 | } |
97 | let new_block = replace_children( | 114 | }; |
98 | &parent_block.syntax(), | ||
99 | RangeInclusive::new( | ||
100 | if_expr.clone().syntax().clone().into(), | ||
101 | if_expr.syntax().clone().into(), | ||
102 | ), | ||
103 | &mut new_if_and_then_statements, | ||
104 | ); | ||
105 | edit.target(if_expr.syntax().text_range()); | 115 | edit.target(if_expr.syntax().text_range()); |
106 | edit.replace_ast(parent_block, ast::Block::cast(new_block).unwrap()); | 116 | edit.replace_ast(parent_block, ast::Block::cast(new_block).unwrap()); |
107 | edit.set_cursor(cursor_position); | 117 | edit.set_cursor(cursor_position); |
118 | |||
119 | fn replace( | ||
120 | new_expr: impl AstNode, | ||
121 | then_block: &Block, | ||
122 | parent_block: &Block, | ||
123 | if_expr: &ast::IfExpr, | ||
124 | ) -> SyntaxNode { | ||
125 | let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone()); | ||
126 | let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); | ||
127 | let end_of_then = | ||
128 | if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { | ||
129 | end_of_then.prev_sibling_or_token().unwrap() | ||
130 | } else { | ||
131 | end_of_then | ||
132 | }; | ||
133 | let mut then_statements = new_expr.syntax().children_with_tokens().chain( | ||
134 | then_block_items | ||
135 | .syntax() | ||
136 | .children_with_tokens() | ||
137 | .skip(1) | ||
138 | .take_while(|i| *i != end_of_then), | ||
139 | ); | ||
140 | replace_children( | ||
141 | &parent_block.syntax(), | ||
142 | RangeInclusive::new( | ||
143 | if_expr.clone().syntax().clone().into(), | ||
144 | if_expr.syntax().clone().into(), | ||
145 | ), | ||
146 | &mut then_statements, | ||
147 | ) | ||
148 | } | ||
108 | }) | 149 | }) |
109 | } | 150 | } |
110 | 151 | ||
@@ -144,6 +185,68 @@ mod tests { | |||
144 | } | 185 | } |
145 | 186 | ||
146 | #[test] | 187 | #[test] |
188 | fn convert_let_inside_fn() { | ||
189 | check_assist( | ||
190 | convert_to_guarded_return, | ||
191 | r#" | ||
192 | fn main(n: Option<String>) { | ||
193 | bar(); | ||
194 | if<|> let Some(n) = n { | ||
195 | foo(n); | ||
196 | |||
197 | //comment | ||
198 | bar(); | ||
199 | } | ||
200 | } | ||
201 | "#, | ||
202 | r#" | ||
203 | fn main(n: Option<String>) { | ||
204 | bar(); | ||
205 | le<|>t n = match n { | ||
206 | Some(it) => it, | ||
207 | None => return, | ||
208 | }; | ||
209 | foo(n); | ||
210 | |||
211 | //comment | ||
212 | bar(); | ||
213 | } | ||
214 | "#, | ||
215 | ); | ||
216 | } | ||
217 | |||
218 | #[test] | ||
219 | fn convert_let_ok_inside_fn() { | ||
220 | check_assist( | ||
221 | convert_to_guarded_return, | ||
222 | r#" | ||
223 | fn main(n: Option<String>) { | ||
224 | bar(); | ||
225 | if<|> let Ok(n) = n { | ||
226 | foo(n); | ||
227 | |||
228 | //comment | ||
229 | bar(); | ||
230 | } | ||
231 | } | ||
232 | "#, | ||
233 | r#" | ||
234 | fn main(n: Option<String>) { | ||
235 | bar(); | ||
236 | le<|>t n = match n { | ||
237 | Ok(it) => it, | ||
238 | None => return, | ||
239 | }; | ||
240 | foo(n); | ||
241 | |||
242 | //comment | ||
243 | bar(); | ||
244 | } | ||
245 | "#, | ||
246 | ); | ||
247 | } | ||
248 | |||
249 | #[test] | ||
147 | fn convert_inside_while() { | 250 | fn convert_inside_while() { |
148 | check_assist( | 251 | check_assist( |
149 | convert_to_guarded_return, | 252 | convert_to_guarded_return, |
@@ -172,6 +275,35 @@ mod tests { | |||
172 | } | 275 | } |
173 | 276 | ||
174 | #[test] | 277 | #[test] |
278 | fn convert_let_inside_while() { | ||
279 | check_assist( | ||
280 | convert_to_guarded_return, | ||
281 | r#" | ||
282 | fn main() { | ||
283 | while true { | ||
284 | if<|> let Some(n) = n { | ||
285 | foo(n); | ||
286 | bar(); | ||
287 | } | ||
288 | } | ||
289 | } | ||
290 | "#, | ||
291 | r#" | ||
292 | fn main() { | ||
293 | while true { | ||
294 | le<|>t n = match n { | ||
295 | Some(it) => it, | ||
296 | None => continue, | ||
297 | }; | ||
298 | foo(n); | ||
299 | bar(); | ||
300 | } | ||
301 | } | ||
302 | "#, | ||
303 | ); | ||
304 | } | ||
305 | |||
306 | #[test] | ||
175 | fn convert_inside_loop() { | 307 | fn convert_inside_loop() { |
176 | check_assist( | 308 | check_assist( |
177 | convert_to_guarded_return, | 309 | convert_to_guarded_return, |
@@ -200,6 +332,35 @@ mod tests { | |||
200 | } | 332 | } |
201 | 333 | ||
202 | #[test] | 334 | #[test] |
335 | fn convert_let_inside_loop() { | ||
336 | check_assist( | ||
337 | convert_to_guarded_return, | ||
338 | r#" | ||
339 | fn main() { | ||
340 | loop { | ||
341 | if<|> let Some(n) = n { | ||
342 | foo(n); | ||
343 | bar(); | ||
344 | } | ||
345 | } | ||
346 | } | ||
347 | "#, | ||
348 | r#" | ||
349 | fn main() { | ||
350 | loop { | ||
351 | le<|>t n = match n { | ||
352 | Some(it) => it, | ||
353 | None => continue, | ||
354 | }; | ||
355 | foo(n); | ||
356 | bar(); | ||
357 | } | ||
358 | } | ||
359 | "#, | ||
360 | ); | ||
361 | } | ||
362 | |||
363 | #[test] | ||
203 | fn ignore_already_converted_if() { | 364 | fn ignore_already_converted_if() { |
204 | check_assist_not_applicable( | 365 | check_assist_not_applicable( |
205 | convert_to_guarded_return, | 366 | convert_to_guarded_return, |
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index 3d5f18bfa..95062ef6c 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs | |||
@@ -110,6 +110,23 @@ pub fn match_arm_list(arms: impl Iterator<Item = ast::MatchArm>) -> ast::MatchAr | |||
110 | } | 110 | } |
111 | } | 111 | } |
112 | 112 | ||
113 | pub fn let_match_early(expr: ast::Expr, path: &str, early_expression: &str) -> ast::LetStmt { | ||
114 | return from_text(&format!( | ||
115 | r#"let {} = match {} {{ | ||
116 | {}(it) => it, | ||
117 | None => {}, | ||
118 | }};"#, | ||
119 | expr.syntax().text(), | ||
120 | expr.syntax().text(), | ||
121 | path, | ||
122 | early_expression | ||
123 | )); | ||
124 | |||
125 | fn from_text(text: &str) -> ast::LetStmt { | ||
126 | ast_from_text(&format!("fn f() {{ {} }}", text)) | ||
127 | } | ||
128 | } | ||
129 | |||
113 | pub fn where_pred(path: ast::Path, bounds: impl Iterator<Item = ast::TypeBound>) -> ast::WherePred { | 130 | pub fn where_pred(path: ast::Path, bounds: impl Iterator<Item = ast::TypeBound>) -> ast::WherePred { |
114 | let bounds = bounds.map(|b| b.syntax().to_string()).join(" + "); | 131 | let bounds = bounds.map(|b| b.syntax().to_string()).join(" + "); |
115 | return from_text(&format!("{}: {}", path.syntax(), bounds)); | 132 | return from_text(&format!("{}: {}", path.syntax(), bounds)); |