aboutsummaryrefslogtreecommitdiff
path: root/crates/assists
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-01-02 16:23:06 +0000
committerGitHub <[email protected]>2021-01-02 16:23:06 +0000
commit510abef5da1427c542e7b37dcea805c7b022984c (patch)
treef049cbe7e0653b48bc6003bcb1afd86db4b72cf1 /crates/assists
parent3b347eaa4eae6d15921bd618471576b48c81afef (diff)
parentbfe6a8e71afc0d3bee47261f83647b28eca0aae6 (diff)
Merge #7130
7130: Add extract_assignment assist r=Jesse-Bakker a=Jesse-Bakker Add extract-assignment assist (#7006). Assist is for now only implemented on if/match-statements where the assigment is the last statement in every block, as for other cases, one would have to check whether the assignment has effects on the rest of the block and extract a temporary variable for it in the block. Co-authored-by: Jesse Bakker <[email protected]>
Diffstat (limited to 'crates/assists')
-rw-r--r--crates/assists/src/handlers/extract_assignment.rs325
-rw-r--r--crates/assists/src/lib.rs2
-rw-r--r--crates/assists/src/tests/generated.rs29
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 @@
1use hir::AsName;
2use syntax::{
3 ast::{self, edit::AstNodeEdit, make},
4 AstNode,
5};
6use test_utils::mark;
7
8use 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// ```
40pub(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
66fn 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
85fn 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
99fn 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)]
121mod 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#"
131fn foo() {
132 let mut a = 1;
133
134 if true {
135 <|>a = 2;
136 } else {
137 a = 3;
138 }
139}"#,
140 r#"
141fn 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#"
158fn 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#"
174fn 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#"
197fn 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#"
216fn 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#"
228fn 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#"
247fn 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#"
259fn 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#"
278fn 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#"
295fn 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#"
310fn 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]
241fn doctest_extract_assignment() {
242 check_doc_test(
243 "extract_assignment",
244 r#####"
245fn main() {
246 let mut foo = 6;
247
248 if true {
249 <|>foo = 5;
250 } else {
251 foo = 4;
252 }
253}
254"#####,
255 r#####"
256fn 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]
241fn doctest_extract_module_to_file() { 270fn doctest_extract_module_to_file() {
242 check_doc_test( 271 check_doc_test(
243 "extract_module_to_file", 272 "extract_module_to_file",