diff options
author | Mikhail Modin <[email protected]> | 2019-10-20 19:00:09 +0100 |
---|---|---|
committer | Mikhail Modin <[email protected]> | 2019-10-20 19:14:32 +0100 |
commit | fb215dc192005160c70d5026bea7bfd752033292 (patch) | |
tree | 11eb4a86bd6a23c067a7bb04fbba3686f6b975d1 /crates | |
parent | 6b9bd7bdd2712a7e85d6bfc70c231dbe36c2e585 (diff) |
Adds "replace with guarded return" assist
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_assists/src/assists/early_return.rs | 276 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/edit.rs | 28 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 8 |
4 files changed, 314 insertions, 0 deletions
diff --git a/crates/ra_assists/src/assists/early_return.rs b/crates/ra_assists/src/assists/early_return.rs new file mode 100644 index 000000000..8c975714c --- /dev/null +++ b/crates/ra_assists/src/assists/early_return.rs | |||
@@ -0,0 +1,276 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::{ | ||
4 | assist_ctx::{Assist, AssistCtx}, | ||
5 | AssistId, | ||
6 | }; | ||
7 | use hir::db::HirDatabase; | ||
8 | use ra_syntax::{ | ||
9 | algo::replace_children, | ||
10 | ast::edit::IndentLevel, | ||
11 | ast::make, | ||
12 | ast::Block, | ||
13 | ast::ContinueExpr, | ||
14 | ast::IfExpr, | ||
15 | ast::ReturnExpr, | ||
16 | AstNode, | ||
17 | SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE}, | ||
18 | }; | ||
19 | use std::ops::RangeInclusive; | ||
20 | |||
21 | pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
22 | let if_expr: IfExpr = ctx.node_at_offset()?; | ||
23 | let expr = if_expr.condition()?.expr()?; | ||
24 | let then_block = if_expr.then_branch()?.block()?; | ||
25 | if if_expr.else_branch().is_some() { | ||
26 | return None; | ||
27 | } | ||
28 | |||
29 | let parent_block = if_expr.syntax().parent()?.ancestors().find_map(Block::cast)?; | ||
30 | |||
31 | if parent_block.expr()? != if_expr.clone().into() { | ||
32 | return None; | ||
33 | } | ||
34 | |||
35 | // check for early return and continue | ||
36 | let first_in_then_block = then_block.syntax().first_child()?.clone(); | ||
37 | if ReturnExpr::can_cast(first_in_then_block.kind()) | ||
38 | || ContinueExpr::can_cast(first_in_then_block.kind()) | ||
39 | || first_in_then_block | ||
40 | .children() | ||
41 | .any(|x| ReturnExpr::can_cast(x.kind()) || ContinueExpr::can_cast(x.kind())) | ||
42 | { | ||
43 | return None; | ||
44 | } | ||
45 | |||
46 | let parent_container = parent_block.syntax().parent()?.parent()?; | ||
47 | |||
48 | let early_expression = match parent_container.kind() { | ||
49 | WHILE_EXPR | LOOP_EXPR => Some("continue;"), | ||
50 | FN_DEF => Some("return;"), | ||
51 | _ => None, | ||
52 | }?; | ||
53 | |||
54 | if then_block.syntax().first_child_or_token().map(|t| t.kind() == L_CURLY).is_none() { | ||
55 | return None; | ||
56 | } | ||
57 | |||
58 | then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; | ||
59 | let cursor_position = ctx.frange.range.start(); | ||
60 | |||
61 | ctx.add_action(AssistId("convert_to_guarded_return"), "convert to guarded return", |edit| { | ||
62 | let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); | ||
63 | let new_if_expr = | ||
64 | if_indent_level.increase_indent(make::if_expression(&expr, early_expression)); | ||
65 | let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone()); | ||
66 | let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); | ||
67 | let end_of_then = | ||
68 | if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { | ||
69 | end_of_then.prev_sibling_or_token().unwrap() | ||
70 | } else { | ||
71 | end_of_then | ||
72 | }; | ||
73 | let mut new_if_and_then_statements = new_if_expr.syntax().children_with_tokens().chain( | ||
74 | then_block_items | ||
75 | .syntax() | ||
76 | .children_with_tokens() | ||
77 | .skip(1) | ||
78 | .take_while(|i| *i != end_of_then), | ||
79 | ); | ||
80 | let new_block = replace_children( | ||
81 | &parent_block.syntax(), | ||
82 | RangeInclusive::new( | ||
83 | if_expr.clone().syntax().clone().into(), | ||
84 | if_expr.syntax().clone().into(), | ||
85 | ), | ||
86 | &mut new_if_and_then_statements, | ||
87 | ); | ||
88 | edit.target(if_expr.syntax().text_range()); | ||
89 | edit.replace_ast(parent_block, Block::cast(new_block).unwrap()); | ||
90 | edit.set_cursor(cursor_position); | ||
91 | }); | ||
92 | ctx.build() | ||
93 | } | ||
94 | |||
95 | #[cfg(test)] | ||
96 | mod tests { | ||
97 | use super::*; | ||
98 | use crate::helpers::{check_assist, check_assist_not_applicable}; | ||
99 | |||
100 | #[test] | ||
101 | fn convert_inside_fn() { | ||
102 | check_assist( | ||
103 | convert_to_guarded_return, | ||
104 | r#" | ||
105 | fn main() { | ||
106 | bar(); | ||
107 | if<|> true { | ||
108 | foo(); | ||
109 | |||
110 | //comment | ||
111 | bar(); | ||
112 | } | ||
113 | } | ||
114 | "#, | ||
115 | r#" | ||
116 | fn main() { | ||
117 | bar(); | ||
118 | if<|> !true { | ||
119 | return; | ||
120 | } | ||
121 | foo(); | ||
122 | |||
123 | //comment | ||
124 | bar(); | ||
125 | } | ||
126 | "#, | ||
127 | ); | ||
128 | } | ||
129 | |||
130 | #[test] | ||
131 | fn convert_inside_while() { | ||
132 | check_assist( | ||
133 | convert_to_guarded_return, | ||
134 | r#" | ||
135 | fn main() { | ||
136 | while true { | ||
137 | if<|> true { | ||
138 | foo(); | ||
139 | bar(); | ||
140 | } | ||
141 | } | ||
142 | } | ||
143 | "#, | ||
144 | r#" | ||
145 | fn main() { | ||
146 | while true { | ||
147 | if<|> !true { | ||
148 | continue; | ||
149 | } | ||
150 | foo(); | ||
151 | bar(); | ||
152 | } | ||
153 | } | ||
154 | "#, | ||
155 | ); | ||
156 | } | ||
157 | |||
158 | #[test] | ||
159 | fn convert_inside_loop() { | ||
160 | check_assist( | ||
161 | convert_to_guarded_return, | ||
162 | r#" | ||
163 | fn main() { | ||
164 | loop { | ||
165 | if<|> true { | ||
166 | foo(); | ||
167 | bar(); | ||
168 | } | ||
169 | } | ||
170 | } | ||
171 | "#, | ||
172 | r#" | ||
173 | fn main() { | ||
174 | loop { | ||
175 | if<|> !true { | ||
176 | continue; | ||
177 | } | ||
178 | foo(); | ||
179 | bar(); | ||
180 | } | ||
181 | } | ||
182 | "#, | ||
183 | ); | ||
184 | } | ||
185 | |||
186 | #[test] | ||
187 | fn ignore_already_converted_if() { | ||
188 | check_assist_not_applicable( | ||
189 | convert_to_guarded_return, | ||
190 | r#" | ||
191 | fn main() { | ||
192 | if<|> true { | ||
193 | return; | ||
194 | } | ||
195 | } | ||
196 | "#, | ||
197 | ); | ||
198 | } | ||
199 | |||
200 | #[test] | ||
201 | fn ignore_already_converted_loop() { | ||
202 | check_assist_not_applicable( | ||
203 | convert_to_guarded_return, | ||
204 | r#" | ||
205 | fn main() { | ||
206 | loop { | ||
207 | if<|> true { | ||
208 | continue; | ||
209 | } | ||
210 | } | ||
211 | } | ||
212 | "#, | ||
213 | ); | ||
214 | } | ||
215 | |||
216 | #[test] | ||
217 | fn ignore_return() { | ||
218 | check_assist_not_applicable( | ||
219 | convert_to_guarded_return, | ||
220 | r#" | ||
221 | fn main() { | ||
222 | if<|> true { | ||
223 | return | ||
224 | } | ||
225 | } | ||
226 | "#, | ||
227 | ); | ||
228 | } | ||
229 | |||
230 | #[test] | ||
231 | fn ignore_else_branch() { | ||
232 | check_assist_not_applicable( | ||
233 | convert_to_guarded_return, | ||
234 | r#" | ||
235 | fn main() { | ||
236 | if<|> true { | ||
237 | foo(); | ||
238 | } else { | ||
239 | bar() | ||
240 | } | ||
241 | } | ||
242 | "#, | ||
243 | ); | ||
244 | } | ||
245 | |||
246 | #[test] | ||
247 | fn ignore_statements_aftert_if() { | ||
248 | check_assist_not_applicable( | ||
249 | convert_to_guarded_return, | ||
250 | r#" | ||
251 | fn main() { | ||
252 | if<|> true { | ||
253 | foo(); | ||
254 | } | ||
255 | bar(); | ||
256 | } | ||
257 | "#, | ||
258 | ); | ||
259 | } | ||
260 | |||
261 | #[test] | ||
262 | fn ignore_statements_inside_if() { | ||
263 | check_assist_not_applicable( | ||
264 | convert_to_guarded_return, | ||
265 | r#" | ||
266 | fn main() { | ||
267 | if false { | ||
268 | if<|> true { | ||
269 | foo(); | ||
270 | } | ||
271 | } | ||
272 | } | ||
273 | "#, | ||
274 | ); | ||
275 | } | ||
276 | } | ||
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index d2376c475..ab77b46a9 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -108,6 +108,7 @@ mod assists { | |||
108 | mod add_missing_impl_members; | 108 | mod add_missing_impl_members; |
109 | mod move_guard; | 109 | mod move_guard; |
110 | mod move_bounds; | 110 | mod move_bounds; |
111 | mod early_return; | ||
111 | 112 | ||
112 | pub(crate) fn all<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { | 113 | pub(crate) fn all<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { |
113 | &[ | 114 | &[ |
@@ -135,6 +136,7 @@ mod assists { | |||
135 | raw_string::make_raw_string, | 136 | raw_string::make_raw_string, |
136 | raw_string::make_usual_string, | 137 | raw_string::make_usual_string, |
137 | raw_string::remove_hash, | 138 | raw_string::remove_hash, |
139 | early_return::convert_to_guarded_return, | ||
138 | ] | 140 | ] |
139 | } | 141 | } |
140 | } | 142 | } |
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index ea92284b8..47bdbb81a 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs | |||
@@ -284,6 +284,34 @@ impl IndentLevel { | |||
284 | .collect(); | 284 | .collect(); |
285 | algo::replace_descendants(&node, &replacements) | 285 | algo::replace_descendants(&node, &replacements) |
286 | } | 286 | } |
287 | |||
288 | pub fn decrease_indent<N: AstNode>(self, node: N) -> N { | ||
289 | N::cast(self._decrease_indent(node.syntax().clone())).unwrap() | ||
290 | } | ||
291 | |||
292 | fn _decrease_indent(self, node: SyntaxNode) -> SyntaxNode { | ||
293 | let replacements: FxHashMap<SyntaxElement, SyntaxElement> = node | ||
294 | .descendants_with_tokens() | ||
295 | .filter_map(|el| el.into_token()) | ||
296 | .filter_map(ast::Whitespace::cast) | ||
297 | .filter(|ws| { | ||
298 | let text = ws.syntax().text(); | ||
299 | text.contains('\n') | ||
300 | }) | ||
301 | .map(|ws| { | ||
302 | ( | ||
303 | ws.syntax().clone().into(), | ||
304 | make::tokens::whitespace( | ||
305 | &ws.syntax() | ||
306 | .text() | ||
307 | .replace(&format!("\n{:1$}", "", self.0 as usize * 4), "\n"), | ||
308 | ) | ||
309 | .into(), | ||
310 | ) | ||
311 | }) | ||
312 | .collect(); | ||
313 | algo::replace_descendants(&node, &replacements) | ||
314 | } | ||
287 | } | 315 | } |
288 | 316 | ||
289 | // FIXME: replace usages with IndentLevel above | 317 | // FIXME: replace usages with IndentLevel above |
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index 143835172..00422ea91 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs | |||
@@ -128,6 +128,14 @@ pub fn where_clause(preds: impl Iterator<Item = ast::WherePred>) -> ast::WhereCl | |||
128 | } | 128 | } |
129 | } | 129 | } |
130 | 130 | ||
131 | pub fn if_expression(condition: &ast::Expr, statement: &str) -> ast::IfExpr { | ||
132 | return ast_from_text(&format!( | ||
133 | "fn f() {{ if !{} {{\n {}\n}}\n}}", | ||
134 | condition.syntax().text(), | ||
135 | statement | ||
136 | )); | ||
137 | } | ||
138 | |||
131 | fn ast_from_text<N: AstNode>(text: &str) -> N { | 139 | fn ast_from_text<N: AstNode>(text: &str) -> N { |
132 | let parse = SourceFile::parse(text); | 140 | let parse = SourceFile::parse(text); |
133 | let res = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); | 141 | let res = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); |