diff options
Diffstat (limited to 'crates/assists/src')
-rw-r--r-- | crates/assists/src/handlers/extract_assignment.rs | 325 | ||||
-rw-r--r-- | crates/assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/assists/src/tests/generated.rs | 29 |
3 files changed, 356 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/extract_assignment.rs b/crates/assists/src/handlers/extract_assignment.rs new file mode 100644 index 000000000..281cf5d24 --- /dev/null +++ b/crates/assists/src/handlers/extract_assignment.rs | |||
@@ -0,0 +1,325 @@ | |||
1 | use hir::AsName; | ||
2 | use syntax::{ | ||
3 | ast::{self, edit::AstNodeEdit, make}, | ||
4 | AstNode, | ||
5 | }; | ||
6 | use test_utils::mark; | ||
7 | |||
8 | use crate::{ | ||
9 | assist_context::{AssistContext, Assists}, | ||
10 | AssistId, AssistKind, | ||
11 | }; | ||
12 | |||
13 | // Assist: extract_assignment | ||
14 | // | ||
15 | // Extracts variable assigment to outside an if or match statement. | ||
16 | // | ||
17 | // ``` | ||
18 | // fn main() { | ||
19 | // let mut foo = 6; | ||
20 | // | ||
21 | // if true { | ||
22 | // <|>foo = 5; | ||
23 | // } else { | ||
24 | // foo = 4; | ||
25 | // } | ||
26 | // } | ||
27 | // ``` | ||
28 | // -> | ||
29 | // ``` | ||
30 | // fn main() { | ||
31 | // let mut foo = 6; | ||
32 | // | ||
33 | // foo = if true { | ||
34 | // 5 | ||
35 | // } else { | ||
36 | // 4 | ||
37 | // }; | ||
38 | // } | ||
39 | // ``` | ||
40 | pub(crate) fn extract_assigment(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
41 | let name = ctx.find_node_at_offset::<ast::NameRef>()?.as_name(); | ||
42 | |||
43 | let (old_stmt, new_stmt) = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() { | ||
44 | ( | ||
45 | ast::Expr::cast(if_expr.syntax().to_owned())?, | ||
46 | exprify_if(&if_expr, &name)?.indent(if_expr.indent_level()), | ||
47 | ) | ||
48 | } else if let Some(match_expr) = ctx.find_node_at_offset::<ast::MatchExpr>() { | ||
49 | (ast::Expr::cast(match_expr.syntax().to_owned())?, exprify_match(&match_expr, &name)?) | ||
50 | } else { | ||
51 | return None; | ||
52 | }; | ||
53 | |||
54 | let expr_stmt = make::expr_stmt(new_stmt); | ||
55 | |||
56 | acc.add( | ||
57 | AssistId("extract_assignment", AssistKind::RefactorExtract), | ||
58 | "Extract assignment", | ||
59 | old_stmt.syntax().text_range(), | ||
60 | move |edit| { | ||
61 | edit.replace(old_stmt.syntax().text_range(), format!("{} = {};", name, expr_stmt)); | ||
62 | }, | ||
63 | ) | ||
64 | } | ||
65 | |||
66 | fn exprify_match(match_expr: &ast::MatchExpr, name: &hir::Name) -> Option<ast::Expr> { | ||
67 | let new_arm_list = match_expr | ||
68 | .match_arm_list()? | ||
69 | .arms() | ||
70 | .map(|arm| { | ||
71 | if let ast::Expr::BlockExpr(block) = arm.expr()? { | ||
72 | let new_block = exprify_block(&block, name)?.indent(block.indent_level()); | ||
73 | Some(arm.replace_descendant(block, new_block)) | ||
74 | } else { | ||
75 | None | ||
76 | } | ||
77 | }) | ||
78 | .collect::<Option<Vec<_>>>()?; | ||
79 | let new_arm_list = match_expr | ||
80 | .match_arm_list()? | ||
81 | .replace_descendants(match_expr.match_arm_list()?.arms().zip(new_arm_list)); | ||
82 | Some(make::expr_match(match_expr.expr()?, new_arm_list)) | ||
83 | } | ||
84 | |||
85 | fn exprify_if(statement: &ast::IfExpr, name: &hir::Name) -> Option<ast::Expr> { | ||
86 | let then_branch = exprify_block(&statement.then_branch()?, name)?; | ||
87 | let else_branch = match statement.else_branch()? { | ||
88 | ast::ElseBranch::Block(ref block) => ast::ElseBranch::Block(exprify_block(block, name)?), | ||
89 | ast::ElseBranch::IfExpr(expr) => { | ||
90 | mark::hit!(test_extract_assigment_chained_if); | ||
91 | ast::ElseBranch::IfExpr(ast::IfExpr::cast( | ||
92 | exprify_if(&expr, name)?.syntax().to_owned(), | ||
93 | )?) | ||
94 | } | ||
95 | }; | ||
96 | Some(make::expr_if(statement.condition()?, then_branch, Some(else_branch))) | ||
97 | } | ||
98 | |||
99 | fn exprify_block(block: &ast::BlockExpr, name: &hir::Name) -> Option<ast::BlockExpr> { | ||
100 | if block.expr().is_some() { | ||
101 | return None; | ||
102 | } | ||
103 | |||
104 | let mut stmts: Vec<_> = block.statements().collect(); | ||
105 | let stmt = stmts.pop()?; | ||
106 | |||
107 | if let ast::Stmt::ExprStmt(stmt) = stmt { | ||
108 | if let ast::Expr::BinExpr(expr) = stmt.expr()? { | ||
109 | if expr.op_kind()? == ast::BinOp::Assignment | ||
110 | && &expr.lhs()?.name_ref()?.as_name() == name | ||
111 | { | ||
112 | // The last statement in the block is an assignment to the name we want | ||
113 | return Some(make::block_expr(stmts, Some(expr.rhs()?))); | ||
114 | } | ||
115 | } | ||
116 | } | ||
117 | None | ||
118 | } | ||
119 | |||
120 | #[cfg(test)] | ||
121 | mod tests { | ||
122 | use super::*; | ||
123 | |||
124 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
125 | |||
126 | #[test] | ||
127 | fn test_extract_assignment_if() { | ||
128 | check_assist( | ||
129 | extract_assigment, | ||
130 | r#" | ||
131 | fn foo() { | ||
132 | let mut a = 1; | ||
133 | |||
134 | if true { | ||
135 | <|>a = 2; | ||
136 | } else { | ||
137 | a = 3; | ||
138 | } | ||
139 | }"#, | ||
140 | r#" | ||
141 | fn foo() { | ||
142 | let mut a = 1; | ||
143 | |||
144 | a = if true { | ||
145 | 2 | ||
146 | } else { | ||
147 | 3 | ||
148 | }; | ||
149 | }"#, | ||
150 | ); | ||
151 | } | ||
152 | |||
153 | #[test] | ||
154 | fn test_extract_assignment_match() { | ||
155 | check_assist( | ||
156 | extract_assigment, | ||
157 | r#" | ||
158 | fn foo() { | ||
159 | let mut a = 1; | ||
160 | |||
161 | match 1 { | ||
162 | 1 => { | ||
163 | <|>a = 2; | ||
164 | }, | ||
165 | 2 => { | ||
166 | a = 3; | ||
167 | }, | ||
168 | 3 => { | ||
169 | a = 4; | ||
170 | } | ||
171 | } | ||
172 | }"#, | ||
173 | r#" | ||
174 | fn foo() { | ||
175 | let mut a = 1; | ||
176 | |||
177 | a = match 1 { | ||
178 | 1 => { | ||
179 | 2 | ||
180 | }, | ||
181 | 2 => { | ||
182 | 3 | ||
183 | }, | ||
184 | 3 => { | ||
185 | 4 | ||
186 | } | ||
187 | }; | ||
188 | }"#, | ||
189 | ); | ||
190 | } | ||
191 | |||
192 | #[test] | ||
193 | fn test_extract_assignment_not_last_not_applicable() { | ||
194 | check_assist_not_applicable( | ||
195 | extract_assigment, | ||
196 | r#" | ||
197 | fn foo() { | ||
198 | let mut a = 1; | ||
199 | |||
200 | if true { | ||
201 | <|>a = 2; | ||
202 | b = a; | ||
203 | } else { | ||
204 | a = 3; | ||
205 | } | ||
206 | }"#, | ||
207 | ) | ||
208 | } | ||
209 | |||
210 | #[test] | ||
211 | fn test_extract_assignment_chained_if() { | ||
212 | mark::check!(test_extract_assigment_chained_if); | ||
213 | check_assist( | ||
214 | extract_assigment, | ||
215 | r#" | ||
216 | fn foo() { | ||
217 | let mut a = 1; | ||
218 | |||
219 | if true { | ||
220 | <|>a = 2; | ||
221 | } else if false { | ||
222 | a = 3; | ||
223 | } else { | ||
224 | a = 4; | ||
225 | } | ||
226 | }"#, | ||
227 | r#" | ||
228 | fn foo() { | ||
229 | let mut a = 1; | ||
230 | |||
231 | a = if true { | ||
232 | 2 | ||
233 | } else if false { | ||
234 | 3 | ||
235 | } else { | ||
236 | 4 | ||
237 | }; | ||
238 | }"#, | ||
239 | ); | ||
240 | } | ||
241 | |||
242 | #[test] | ||
243 | fn test_extract_assigment_retains_stmts() { | ||
244 | check_assist( | ||
245 | extract_assigment, | ||
246 | r#" | ||
247 | fn foo() { | ||
248 | let mut a = 1; | ||
249 | |||
250 | if true { | ||
251 | let b = 2; | ||
252 | <|>a = 2; | ||
253 | } else { | ||
254 | let b = 3; | ||
255 | a = 3; | ||
256 | } | ||
257 | }"#, | ||
258 | r#" | ||
259 | fn foo() { | ||
260 | let mut a = 1; | ||
261 | |||
262 | a = if true { | ||
263 | let b = 2; | ||
264 | 2 | ||
265 | } else { | ||
266 | let b = 3; | ||
267 | 3 | ||
268 | }; | ||
269 | }"#, | ||
270 | ) | ||
271 | } | ||
272 | |||
273 | #[test] | ||
274 | fn extract_assignment_let_stmt_not_applicable() { | ||
275 | check_assist_not_applicable( | ||
276 | extract_assigment, | ||
277 | r#" | ||
278 | fn foo() { | ||
279 | let mut a = 1; | ||
280 | |||
281 | let b = if true { | ||
282 | <|>a = 2 | ||
283 | } else { | ||
284 | a = 3 | ||
285 | }; | ||
286 | }"#, | ||
287 | ) | ||
288 | } | ||
289 | |||
290 | #[test] | ||
291 | fn extract_assignment_if_missing_assigment_not_applicable() { | ||
292 | check_assist_not_applicable( | ||
293 | extract_assigment, | ||
294 | r#" | ||
295 | fn foo() { | ||
296 | let mut a = 1; | ||
297 | |||
298 | if true { | ||
299 | <|>a = 2; | ||
300 | } else {} | ||
301 | }"#, | ||
302 | ) | ||
303 | } | ||
304 | |||
305 | #[test] | ||
306 | fn extract_assignment_match_missing_assigment_not_applicable() { | ||
307 | check_assist_not_applicable( | ||
308 | extract_assigment, | ||
309 | r#" | ||
310 | fn foo() { | ||
311 | let mut a = 1; | ||
312 | |||
313 | match 1 { | ||
314 | 1 => { | ||
315 | <|>a = 2; | ||
316 | }, | ||
317 | 2 => { | ||
318 | a = 3; | ||
319 | }, | ||
320 | 3 => {}, | ||
321 | } | ||
322 | }"#, | ||
323 | ) | ||
324 | } | ||
325 | } | ||
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs index fdec886e9..212464f85 100644 --- a/crates/assists/src/lib.rs +++ b/crates/assists/src/lib.rs | |||
@@ -116,6 +116,7 @@ mod handlers { | |||
116 | mod convert_integer_literal; | 116 | mod convert_integer_literal; |
117 | mod early_return; | 117 | mod early_return; |
118 | mod expand_glob_import; | 118 | mod expand_glob_import; |
119 | mod extract_assignment; | ||
119 | mod extract_module_to_file; | 120 | mod extract_module_to_file; |
120 | mod extract_struct_from_enum_variant; | 121 | mod extract_struct_from_enum_variant; |
121 | mod extract_variable; | 122 | mod extract_variable; |
@@ -167,6 +168,7 @@ mod handlers { | |||
167 | convert_integer_literal::convert_integer_literal, | 168 | convert_integer_literal::convert_integer_literal, |
168 | early_return::convert_to_guarded_return, | 169 | early_return::convert_to_guarded_return, |
169 | expand_glob_import::expand_glob_import, | 170 | expand_glob_import::expand_glob_import, |
171 | extract_assignment::extract_assigment, | ||
170 | extract_module_to_file::extract_module_to_file, | 172 | extract_module_to_file::extract_module_to_file, |
171 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, | 173 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, |
172 | extract_variable::extract_variable, | 174 | extract_variable::extract_variable, |
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs index d3dfe24e7..b91a816e8 100644 --- a/crates/assists/src/tests/generated.rs +++ b/crates/assists/src/tests/generated.rs | |||
@@ -238,6 +238,35 @@ fn qux(bar: Bar, baz: Baz) {} | |||
238 | } | 238 | } |
239 | 239 | ||
240 | #[test] | 240 | #[test] |
241 | fn doctest_extract_assignment() { | ||
242 | check_doc_test( | ||
243 | "extract_assignment", | ||
244 | r#####" | ||
245 | fn main() { | ||
246 | let mut foo = 6; | ||
247 | |||
248 | if true { | ||
249 | <|>foo = 5; | ||
250 | } else { | ||
251 | foo = 4; | ||
252 | } | ||
253 | } | ||
254 | "#####, | ||
255 | r#####" | ||
256 | fn main() { | ||
257 | let mut foo = 6; | ||
258 | |||
259 | foo = if true { | ||
260 | 5 | ||
261 | } else { | ||
262 | 4 | ||
263 | }; | ||
264 | } | ||
265 | "#####, | ||
266 | ) | ||
267 | } | ||
268 | |||
269 | #[test] | ||
241 | fn doctest_extract_module_to_file() { | 270 | fn doctest_extract_module_to_file() { |
242 | check_doc_test( | 271 | check_doc_test( |
243 | "extract_module_to_file", | 272 | "extract_module_to_file", |