diff options
-rw-r--r-- | crates/assists/src/handlers/extract_assignment.rs | 238 | ||||
-rw-r--r-- | crates/assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/assists/src/tests/generated.rs | 29 |
3 files changed, 269 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..604297705 --- /dev/null +++ b/crates/assists/src/handlers/extract_assignment.rs | |||
@@ -0,0 +1,238 @@ | |||
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 if_statement = ctx.find_node_at_offset::<ast::IfExpr>()?; | ||
44 | |||
45 | let new_stmt = exprify_if(&if_statement, &name)?.indent(if_statement.indent_level()); | ||
46 | let expr_stmt = make::expr_stmt(new_stmt); | ||
47 | |||
48 | acc.add( | ||
49 | AssistId("extract_assignment", AssistKind::RefactorExtract), | ||
50 | "Extract assignment", | ||
51 | if_statement.syntax().text_range(), | ||
52 | move |edit| { | ||
53 | edit.replace(if_statement.syntax().text_range(), format!("{} = {};", name, expr_stmt)); | ||
54 | }, | ||
55 | ) | ||
56 | } | ||
57 | |||
58 | fn exprify_if(statement: &ast::IfExpr, name: &hir::Name) -> Option<ast::Expr> { | ||
59 | let then_branch = exprify_block(&statement.then_branch()?, name)?; | ||
60 | let else_branch = match statement.else_branch()? { | ||
61 | ast::ElseBranch::Block(block) => ast::ElseBranch::Block(exprify_block(&block, name)?), | ||
62 | ast::ElseBranch::IfExpr(expr) => { | ||
63 | mark::hit!(test_extract_assigment_chained_if); | ||
64 | ast::ElseBranch::IfExpr(ast::IfExpr::cast( | ||
65 | exprify_if(&expr, name)?.syntax().to_owned(), | ||
66 | )?) | ||
67 | } | ||
68 | }; | ||
69 | Some(make::expr_if(statement.condition()?, then_branch, Some(else_branch))) | ||
70 | } | ||
71 | |||
72 | fn exprify_block(block: &ast::BlockExpr, name: &hir::Name) -> Option<ast::BlockExpr> { | ||
73 | if block.expr().is_some() { | ||
74 | return None; | ||
75 | } | ||
76 | |||
77 | let mut stmts: Vec<_> = block.statements().collect(); | ||
78 | let stmt = stmts.pop()?; | ||
79 | |||
80 | if let ast::Stmt::ExprStmt(stmt) = stmt { | ||
81 | if let ast::Expr::BinExpr(expr) = stmt.expr()? { | ||
82 | if expr.op_kind()? == ast::BinOp::Assignment | ||
83 | && &expr.lhs()?.name_ref()?.as_name() == name | ||
84 | { | ||
85 | // The last statement in the block is an assignment to the name we want | ||
86 | return Some(make::block_expr(stmts, Some(expr.rhs()?))); | ||
87 | } | ||
88 | } | ||
89 | } | ||
90 | None | ||
91 | } | ||
92 | |||
93 | #[cfg(test)] | ||
94 | mod tests { | ||
95 | use super::*; | ||
96 | |||
97 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
98 | |||
99 | #[test] | ||
100 | fn test_extract_assignment() { | ||
101 | check_assist( | ||
102 | extract_assigment, | ||
103 | r#" | ||
104 | fn foo() { | ||
105 | let mut a = 1; | ||
106 | |||
107 | if true { | ||
108 | <|>a = 2; | ||
109 | } else { | ||
110 | a = 3; | ||
111 | } | ||
112 | }"#, | ||
113 | r#" | ||
114 | fn foo() { | ||
115 | let mut a = 1; | ||
116 | |||
117 | a = if true { | ||
118 | 2 | ||
119 | } else { | ||
120 | 3 | ||
121 | }; | ||
122 | }"#, | ||
123 | ); | ||
124 | } | ||
125 | |||
126 | #[test] | ||
127 | fn test_extract_assignment_not_last_not_applicable() { | ||
128 | check_assist_not_applicable( | ||
129 | extract_assigment, | ||
130 | r#" | ||
131 | fn foo() { | ||
132 | let mut a = 1; | ||
133 | |||
134 | if true { | ||
135 | <|>a = 2; | ||
136 | b = a; | ||
137 | } else { | ||
138 | a = 3; | ||
139 | } | ||
140 | }"#, | ||
141 | ) | ||
142 | } | ||
143 | |||
144 | #[test] | ||
145 | fn test_extract_assignment_chained_if() { | ||
146 | mark::check!(test_extract_assigment_chained_if); | ||
147 | check_assist( | ||
148 | extract_assigment, | ||
149 | r#" | ||
150 | fn foo() { | ||
151 | let mut a = 1; | ||
152 | |||
153 | if true { | ||
154 | <|>a = 2; | ||
155 | } else if false { | ||
156 | a = 3; | ||
157 | } else { | ||
158 | a = 4; | ||
159 | } | ||
160 | }"#, | ||
161 | r#" | ||
162 | fn foo() { | ||
163 | let mut a = 1; | ||
164 | |||
165 | a = if true { | ||
166 | 2 | ||
167 | } else if false { | ||
168 | 3 | ||
169 | } else { | ||
170 | 4 | ||
171 | }; | ||
172 | }"#, | ||
173 | ); | ||
174 | } | ||
175 | |||
176 | #[test] | ||
177 | fn test_extract_assigment_retains_stmts() { | ||
178 | check_assist( | ||
179 | extract_assigment, | ||
180 | r#" | ||
181 | fn foo() { | ||
182 | let mut a = 1; | ||
183 | |||
184 | if true { | ||
185 | let b = 2; | ||
186 | <|>a = 2; | ||
187 | } else { | ||
188 | let b = 3; | ||
189 | a = 3; | ||
190 | } | ||
191 | }"#, | ||
192 | r#" | ||
193 | fn foo() { | ||
194 | let mut a = 1; | ||
195 | |||
196 | a = if true { | ||
197 | let b = 2; | ||
198 | 2 | ||
199 | } else { | ||
200 | let b = 3; | ||
201 | 3 | ||
202 | }; | ||
203 | }"#, | ||
204 | ) | ||
205 | } | ||
206 | |||
207 | #[test] | ||
208 | fn extract_assignment_let_stmt_not_applicable() { | ||
209 | check_assist_not_applicable( | ||
210 | extract_assigment, | ||
211 | r#" | ||
212 | fn foo() { | ||
213 | let mut a = 1; | ||
214 | |||
215 | let b = if true { | ||
216 | <|>a = 2 | ||
217 | } else { | ||
218 | a = 3 | ||
219 | }; | ||
220 | }"#, | ||
221 | ) | ||
222 | } | ||
223 | |||
224 | #[test] | ||
225 | fn extract_assignment_missing_assigment_not_applicable() { | ||
226 | check_assist_not_applicable( | ||
227 | extract_assigment, | ||
228 | r#" | ||
229 | fn foo() { | ||
230 | let mut a = 1; | ||
231 | |||
232 | if true { | ||
233 | <|>a = 2; | ||
234 | } else {} | ||
235 | }"#, | ||
236 | ) | ||
237 | } | ||
238 | } | ||
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", |