aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-10-22 18:10:17 +0100
committerGitHub <[email protected]>2019-10-22 18:10:17 +0100
commitba95de936df92b3901163249aed4e1bd769021df (patch)
treed41e817b741475cac2783bf8b6466acc54308472
parent2dffae6f35ce4b5824df881a5c190daabd3efd3c (diff)
parentfb215dc192005160c70d5026bea7bfd752033292 (diff)
Merge #1933
1933: Adds "replace with guarded return" assist r=matklad a=mikhail-m1 first draft for #1782. I'm pretty sure I missed something. Co-authored-by: Mikhail Modin <[email protected]>
-rw-r--r--crates/ra_assists/src/assists/early_return.rs276
-rw-r--r--crates/ra_assists/src/lib.rs2
-rw-r--r--crates/ra_syntax/src/ast/edit.rs28
-rw-r--r--crates/ra_syntax/src/ast/make.rs8
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
3use crate::{
4 assist_ctx::{Assist, AssistCtx},
5 AssistId,
6};
7use hir::db::HirDatabase;
8use 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};
19use std::ops::RangeInclusive;
20
21pub(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)]
96mod 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
131pub 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
131fn ast_from_text<N: AstNode>(text: &str) -> N { 139fn 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();