diff options
Diffstat (limited to 'crates/assists/src/handlers/extract_assignment.rs')
-rw-r--r-- | crates/assists/src/handlers/extract_assignment.rs | 400 |
1 files changed, 400 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..ae99598c0 --- /dev/null +++ b/crates/assists/src/handlers/extract_assignment.rs | |||
@@ -0,0 +1,400 @@ | |||
1 | use syntax::{ | ||
2 | ast::{self, edit::AstNodeEdit, make}, | ||
3 | AstNode, | ||
4 | }; | ||
5 | use test_utils::mark; | ||
6 | |||
7 | use crate::{ | ||
8 | assist_context::{AssistContext, Assists}, | ||
9 | AssistId, AssistKind, | ||
10 | }; | ||
11 | |||
12 | // Assist: extract_assignment | ||
13 | // | ||
14 | // Extracts variable assigment to outside an if or match statement. | ||
15 | // | ||
16 | // ``` | ||
17 | // fn main() { | ||
18 | // let mut foo = 6; | ||
19 | // | ||
20 | // if true { | ||
21 | // <|>foo = 5; | ||
22 | // } else { | ||
23 | // foo = 4; | ||
24 | // } | ||
25 | // } | ||
26 | // ``` | ||
27 | // -> | ||
28 | // ``` | ||
29 | // fn main() { | ||
30 | // let mut foo = 6; | ||
31 | // | ||
32 | // foo = if true { | ||
33 | // 5 | ||
34 | // } else { | ||
35 | // 4 | ||
36 | // }; | ||
37 | // } | ||
38 | // ``` | ||
39 | pub(crate) fn extract_assigment(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
40 | let assign_expr = ctx.find_node_at_offset::<ast::BinExpr>()?; | ||
41 | let name_expr = if assign_expr.op_kind()? == ast::BinOp::Assignment { | ||
42 | assign_expr.lhs()? | ||
43 | } else { | ||
44 | return None; | ||
45 | }; | ||
46 | |||
47 | let (old_stmt, new_stmt) = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() { | ||
48 | ( | ||
49 | ast::Expr::cast(if_expr.syntax().to_owned())?, | ||
50 | exprify_if(&if_expr, &ctx.sema, &name_expr)?.indent(if_expr.indent_level()), | ||
51 | ) | ||
52 | } else if let Some(match_expr) = ctx.find_node_at_offset::<ast::MatchExpr>() { | ||
53 | ( | ||
54 | ast::Expr::cast(match_expr.syntax().to_owned())?, | ||
55 | exprify_match(&match_expr, &ctx.sema, &name_expr)?, | ||
56 | ) | ||
57 | } else { | ||
58 | return None; | ||
59 | }; | ||
60 | |||
61 | let expr_stmt = make::expr_stmt(new_stmt); | ||
62 | |||
63 | acc.add( | ||
64 | AssistId("extract_assignment", AssistKind::RefactorExtract), | ||
65 | "Extract assignment", | ||
66 | old_stmt.syntax().text_range(), | ||
67 | move |edit| { | ||
68 | edit.replace(old_stmt.syntax().text_range(), format!("{} = {};", name_expr, expr_stmt)); | ||
69 | }, | ||
70 | ) | ||
71 | } | ||
72 | |||
73 | fn exprify_match( | ||
74 | match_expr: &ast::MatchExpr, | ||
75 | sema: &hir::Semantics<ide_db::RootDatabase>, | ||
76 | name: &ast::Expr, | ||
77 | ) -> Option<ast::Expr> { | ||
78 | let new_arm_list = match_expr | ||
79 | .match_arm_list()? | ||
80 | .arms() | ||
81 | .map(|arm| { | ||
82 | if let ast::Expr::BlockExpr(block) = arm.expr()? { | ||
83 | let new_block = exprify_block(&block, sema, name)?.indent(block.indent_level()); | ||
84 | Some(arm.replace_descendant(block, new_block)) | ||
85 | } else { | ||
86 | None | ||
87 | } | ||
88 | }) | ||
89 | .collect::<Option<Vec<_>>>()?; | ||
90 | let new_arm_list = match_expr | ||
91 | .match_arm_list()? | ||
92 | .replace_descendants(match_expr.match_arm_list()?.arms().zip(new_arm_list)); | ||
93 | Some(make::expr_match(match_expr.expr()?, new_arm_list)) | ||
94 | } | ||
95 | |||
96 | fn exprify_if( | ||
97 | statement: &ast::IfExpr, | ||
98 | sema: &hir::Semantics<ide_db::RootDatabase>, | ||
99 | name: &ast::Expr, | ||
100 | ) -> Option<ast::Expr> { | ||
101 | let then_branch = exprify_block(&statement.then_branch()?, sema, name)?; | ||
102 | let else_branch = match statement.else_branch()? { | ||
103 | ast::ElseBranch::Block(ref block) => { | ||
104 | ast::ElseBranch::Block(exprify_block(block, sema, name)?) | ||
105 | } | ||
106 | ast::ElseBranch::IfExpr(expr) => { | ||
107 | mark::hit!(test_extract_assigment_chained_if); | ||
108 | ast::ElseBranch::IfExpr(ast::IfExpr::cast( | ||
109 | exprify_if(&expr, sema, name)?.syntax().to_owned(), | ||
110 | )?) | ||
111 | } | ||
112 | }; | ||
113 | Some(make::expr_if(statement.condition()?, then_branch, Some(else_branch))) | ||
114 | } | ||
115 | |||
116 | fn exprify_block( | ||
117 | block: &ast::BlockExpr, | ||
118 | sema: &hir::Semantics<ide_db::RootDatabase>, | ||
119 | name: &ast::Expr, | ||
120 | ) -> Option<ast::BlockExpr> { | ||
121 | if block.expr().is_some() { | ||
122 | return None; | ||
123 | } | ||
124 | |||
125 | let mut stmts: Vec<_> = block.statements().collect(); | ||
126 | let stmt = stmts.pop()?; | ||
127 | |||
128 | if let ast::Stmt::ExprStmt(stmt) = stmt { | ||
129 | if let ast::Expr::BinExpr(expr) = stmt.expr()? { | ||
130 | if expr.op_kind()? == ast::BinOp::Assignment && is_equivalent(sema, &expr.lhs()?, name) | ||
131 | { | ||
132 | // The last statement in the block is an assignment to the name we want | ||
133 | return Some(make::block_expr(stmts, Some(expr.rhs()?))); | ||
134 | } | ||
135 | } | ||
136 | } | ||
137 | None | ||
138 | } | ||
139 | |||
140 | fn is_equivalent( | ||
141 | sema: &hir::Semantics<ide_db::RootDatabase>, | ||
142 | expr0: &ast::Expr, | ||
143 | expr1: &ast::Expr, | ||
144 | ) -> bool { | ||
145 | match (expr0, expr1) { | ||
146 | (ast::Expr::FieldExpr(field_expr0), ast::Expr::FieldExpr(field_expr1)) => { | ||
147 | mark::hit!(test_extract_assignment_field_assignment); | ||
148 | sema.resolve_field(field_expr0) == sema.resolve_field(field_expr1) | ||
149 | } | ||
150 | (ast::Expr::PathExpr(path0), ast::Expr::PathExpr(path1)) => { | ||
151 | let path0 = path0.path(); | ||
152 | let path1 = path1.path(); | ||
153 | if let (Some(path0), Some(path1)) = (path0, path1) { | ||
154 | sema.resolve_path(&path0) == sema.resolve_path(&path1) | ||
155 | } else { | ||
156 | false | ||
157 | } | ||
158 | } | ||
159 | _ => false, | ||
160 | } | ||
161 | } | ||
162 | |||
163 | #[cfg(test)] | ||
164 | mod tests { | ||
165 | use super::*; | ||
166 | |||
167 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
168 | |||
169 | #[test] | ||
170 | fn test_extract_assignment_if() { | ||
171 | check_assist( | ||
172 | extract_assigment, | ||
173 | r#" | ||
174 | fn foo() { | ||
175 | let mut a = 1; | ||
176 | |||
177 | if true { | ||
178 | <|>a = 2; | ||
179 | } else { | ||
180 | a = 3; | ||
181 | } | ||
182 | }"#, | ||
183 | r#" | ||
184 | fn foo() { | ||
185 | let mut a = 1; | ||
186 | |||
187 | a = if true { | ||
188 | 2 | ||
189 | } else { | ||
190 | 3 | ||
191 | }; | ||
192 | }"#, | ||
193 | ); | ||
194 | } | ||
195 | |||
196 | #[test] | ||
197 | fn test_extract_assignment_match() { | ||
198 | check_assist( | ||
199 | extract_assigment, | ||
200 | r#" | ||
201 | fn foo() { | ||
202 | let mut a = 1; | ||
203 | |||
204 | match 1 { | ||
205 | 1 => { | ||
206 | <|>a = 2; | ||
207 | }, | ||
208 | 2 => { | ||
209 | a = 3; | ||
210 | }, | ||
211 | 3 => { | ||
212 | a = 4; | ||
213 | } | ||
214 | } | ||
215 | }"#, | ||
216 | r#" | ||
217 | fn foo() { | ||
218 | let mut a = 1; | ||
219 | |||
220 | a = match 1 { | ||
221 | 1 => { | ||
222 | 2 | ||
223 | }, | ||
224 | 2 => { | ||
225 | 3 | ||
226 | }, | ||
227 | 3 => { | ||
228 | 4 | ||
229 | } | ||
230 | }; | ||
231 | }"#, | ||
232 | ); | ||
233 | } | ||
234 | |||
235 | #[test] | ||
236 | fn test_extract_assignment_not_last_not_applicable() { | ||
237 | check_assist_not_applicable( | ||
238 | extract_assigment, | ||
239 | r#" | ||
240 | fn foo() { | ||
241 | let mut a = 1; | ||
242 | |||
243 | if true { | ||
244 | <|>a = 2; | ||
245 | b = a; | ||
246 | } else { | ||
247 | a = 3; | ||
248 | } | ||
249 | }"#, | ||
250 | ) | ||
251 | } | ||
252 | |||
253 | #[test] | ||
254 | fn test_extract_assignment_chained_if() { | ||
255 | mark::check!(test_extract_assigment_chained_if); | ||
256 | check_assist( | ||
257 | extract_assigment, | ||
258 | r#" | ||
259 | fn foo() { | ||
260 | let mut a = 1; | ||
261 | |||
262 | if true { | ||
263 | <|>a = 2; | ||
264 | } else if false { | ||
265 | a = 3; | ||
266 | } else { | ||
267 | a = 4; | ||
268 | } | ||
269 | }"#, | ||
270 | r#" | ||
271 | fn foo() { | ||
272 | let mut a = 1; | ||
273 | |||
274 | a = if true { | ||
275 | 2 | ||
276 | } else if false { | ||
277 | 3 | ||
278 | } else { | ||
279 | 4 | ||
280 | }; | ||
281 | }"#, | ||
282 | ); | ||
283 | } | ||
284 | |||
285 | #[test] | ||
286 | fn test_extract_assigment_retains_stmts() { | ||
287 | check_assist( | ||
288 | extract_assigment, | ||
289 | r#" | ||
290 | fn foo() { | ||
291 | let mut a = 1; | ||
292 | |||
293 | if true { | ||
294 | let b = 2; | ||
295 | <|>a = 2; | ||
296 | } else { | ||
297 | let b = 3; | ||
298 | a = 3; | ||
299 | } | ||
300 | }"#, | ||
301 | r#" | ||
302 | fn foo() { | ||
303 | let mut a = 1; | ||
304 | |||
305 | a = if true { | ||
306 | let b = 2; | ||
307 | 2 | ||
308 | } else { | ||
309 | let b = 3; | ||
310 | 3 | ||
311 | }; | ||
312 | }"#, | ||
313 | ) | ||
314 | } | ||
315 | |||
316 | #[test] | ||
317 | fn extract_assignment_let_stmt_not_applicable() { | ||
318 | check_assist_not_applicable( | ||
319 | extract_assigment, | ||
320 | r#" | ||
321 | fn foo() { | ||
322 | let mut a = 1; | ||
323 | |||
324 | let b = if true { | ||
325 | <|>a = 2 | ||
326 | } else { | ||
327 | a = 3 | ||
328 | }; | ||
329 | }"#, | ||
330 | ) | ||
331 | } | ||
332 | |||
333 | #[test] | ||
334 | fn extract_assignment_if_missing_assigment_not_applicable() { | ||
335 | check_assist_not_applicable( | ||
336 | extract_assigment, | ||
337 | r#" | ||
338 | fn foo() { | ||
339 | let mut a = 1; | ||
340 | |||
341 | if true { | ||
342 | <|>a = 2; | ||
343 | } else {} | ||
344 | }"#, | ||
345 | ) | ||
346 | } | ||
347 | |||
348 | #[test] | ||
349 | fn extract_assignment_match_missing_assigment_not_applicable() { | ||
350 | check_assist_not_applicable( | ||
351 | extract_assigment, | ||
352 | r#" | ||
353 | fn foo() { | ||
354 | let mut a = 1; | ||
355 | |||
356 | match 1 { | ||
357 | 1 => { | ||
358 | <|>a = 2; | ||
359 | }, | ||
360 | 2 => { | ||
361 | a = 3; | ||
362 | }, | ||
363 | 3 => {}, | ||
364 | } | ||
365 | }"#, | ||
366 | ) | ||
367 | } | ||
368 | |||
369 | #[test] | ||
370 | fn test_extract_assignment_field_assignment() { | ||
371 | mark::check!(test_extract_assignment_field_assignment); | ||
372 | check_assist( | ||
373 | extract_assigment, | ||
374 | r#" | ||
375 | struct A(usize); | ||
376 | |||
377 | fn foo() { | ||
378 | let mut a = A(1); | ||
379 | |||
380 | if true { | ||
381 | <|>a.0 = 2; | ||
382 | } else { | ||
383 | a.0 = 3; | ||
384 | } | ||
385 | }"#, | ||
386 | r#" | ||
387 | struct A(usize); | ||
388 | |||
389 | fn foo() { | ||
390 | let mut a = A(1); | ||
391 | |||
392 | a.0 = if true { | ||
393 | 2 | ||
394 | } else { | ||
395 | 3 | ||
396 | }; | ||
397 | }"#, | ||
398 | ) | ||
399 | } | ||
400 | } | ||